├── osx
└── create-dmg
│ ├── .gitignore
│ ├── sample
│ ├── builder
│ └── create-dmg.builder
│ ├── LICENSE
│ ├── support
│ ├── template.applescript
│ └── dmg-license.py
│ ├── README.md
│ └── create-dmg
├── icons
├── logo.png
├── AppIcon.icns
├── dmg-bg.png
└── gitter.ico
├── .jshintrc
├── nwapp
├── img
│ ├── logo.png
│ ├── logo16.png
│ ├── favicon.ico
│ ├── icon-logo-connected.png
│ ├── icon-logo-connected@2x.png
│ ├── icon-logo-disconnected.png
│ ├── osx-icon-logo-connected.tiff
│ ├── win-icon-logo-connected.ico
│ ├── icon-logo-connected-unread.png
│ ├── icon-logo-disconnected@2x.png
│ ├── linux-icon-logo-connected.png
│ ├── win-icon-logo-disconnected.ico
│ ├── icon-logo-connected-selected.png
│ ├── icon-logo-connected-unread@2x.png
│ ├── linux-icon-logo-disconnected.png
│ ├── osx-icon-logo-disconnected.tiff
│ ├── icon-logo-connected-selected@2x.png
│ ├── osx-icon-logo-connected-unread.tiff
│ ├── win-icon-logo-connected-unread.ico
│ ├── linux-icon-logo-connected-unread.png
│ ├── osx-icon-logo-connected-selected.tiff
│ └── win-icon-logo-connected-selected.ico
├── audio
│ ├── gitter-long.ogg
│ └── gitter-ping.ogg
├── utils
│ ├── custom-events.js
│ ├── sounds.js
│ ├── client-type.js
│ ├── quit-app.js
│ ├── trace.js
│ ├── settings.js
│ ├── notifier.js
│ └── auto-update.js
├── main.css
├── index.html
├── loggedin.html
├── loggedin.js
├── oauth.html
├── icons.json
├── components
│ ├── sound-items.js
│ ├── context-menu.js
│ ├── tray.js
│ ├── menu-items.js
│ ├── menu.js
│ └── tray-menu.js
├── package.json
├── lib
│ └── login-view.js
└── index.js
├── .travis.yml
├── README.md
├── linux
├── README.md
├── gitter.desktop
├── after-remove.sh
└── after-install.sh
├── .gitignore
├── .jsbeautifyrc
├── snap
└── snapcraft.yaml
├── latest-template.html
├── windows
├── build.bat
├── README.md
├── gitter.iss
└── build.js
├── bootstrap.sh
├── package.json
├── CHANGELOG.md
└── gulpfile.js
/osx/create-dmg/.gitignore:
--------------------------------------------------------------------------------
1 | .svn
2 |
--------------------------------------------------------------------------------
/icons/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitterHQ/desktop/HEAD/icons/logo.png
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "browser": true,
4 | "unused": true
5 | }
6 |
--------------------------------------------------------------------------------
/icons/AppIcon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitterHQ/desktop/HEAD/icons/AppIcon.icns
--------------------------------------------------------------------------------
/icons/dmg-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitterHQ/desktop/HEAD/icons/dmg-bg.png
--------------------------------------------------------------------------------
/icons/gitter.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitterHQ/desktop/HEAD/icons/gitter.ico
--------------------------------------------------------------------------------
/nwapp/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitterHQ/desktop/HEAD/nwapp/img/logo.png
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.10"
4 | git:
5 | depth: 10
6 |
7 |
--------------------------------------------------------------------------------
/nwapp/img/logo16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitterHQ/desktop/HEAD/nwapp/img/logo16.png
--------------------------------------------------------------------------------
/nwapp/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitterHQ/desktop/HEAD/nwapp/img/favicon.ico
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This project has now moved to [GitLab](https://gitlab.com/gitlab-org/gitter/desktop/).
2 |
--------------------------------------------------------------------------------
/nwapp/audio/gitter-long.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitterHQ/desktop/HEAD/nwapp/audio/gitter-long.ogg
--------------------------------------------------------------------------------
/nwapp/audio/gitter-ping.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitterHQ/desktop/HEAD/nwapp/audio/gitter-ping.ogg
--------------------------------------------------------------------------------
/nwapp/img/icon-logo-connected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitterHQ/desktop/HEAD/nwapp/img/icon-logo-connected.png
--------------------------------------------------------------------------------
/nwapp/img/icon-logo-connected@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitterHQ/desktop/HEAD/nwapp/img/icon-logo-connected@2x.png
--------------------------------------------------------------------------------
/nwapp/img/icon-logo-disconnected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitterHQ/desktop/HEAD/nwapp/img/icon-logo-disconnected.png
--------------------------------------------------------------------------------
/nwapp/img/osx-icon-logo-connected.tiff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitterHQ/desktop/HEAD/nwapp/img/osx-icon-logo-connected.tiff
--------------------------------------------------------------------------------
/nwapp/img/win-icon-logo-connected.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitterHQ/desktop/HEAD/nwapp/img/win-icon-logo-connected.ico
--------------------------------------------------------------------------------
/nwapp/img/icon-logo-connected-unread.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitterHQ/desktop/HEAD/nwapp/img/icon-logo-connected-unread.png
--------------------------------------------------------------------------------
/nwapp/img/icon-logo-disconnected@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitterHQ/desktop/HEAD/nwapp/img/icon-logo-disconnected@2x.png
--------------------------------------------------------------------------------
/nwapp/img/linux-icon-logo-connected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitterHQ/desktop/HEAD/nwapp/img/linux-icon-logo-connected.png
--------------------------------------------------------------------------------
/nwapp/img/win-icon-logo-disconnected.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitterHQ/desktop/HEAD/nwapp/img/win-icon-logo-disconnected.ico
--------------------------------------------------------------------------------
/nwapp/img/icon-logo-connected-selected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitterHQ/desktop/HEAD/nwapp/img/icon-logo-connected-selected.png
--------------------------------------------------------------------------------
/nwapp/img/icon-logo-connected-unread@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitterHQ/desktop/HEAD/nwapp/img/icon-logo-connected-unread@2x.png
--------------------------------------------------------------------------------
/nwapp/img/linux-icon-logo-disconnected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitterHQ/desktop/HEAD/nwapp/img/linux-icon-logo-disconnected.png
--------------------------------------------------------------------------------
/nwapp/img/osx-icon-logo-disconnected.tiff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitterHQ/desktop/HEAD/nwapp/img/osx-icon-logo-disconnected.tiff
--------------------------------------------------------------------------------
/linux/README.md:
--------------------------------------------------------------------------------
1 | # Linux related assets
2 |
3 | - `after-install` and `after-remove` scripts
4 | - `gitter.desktop` for the Unity Launcher
5 |
--------------------------------------------------------------------------------
/nwapp/img/icon-logo-connected-selected@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitterHQ/desktop/HEAD/nwapp/img/icon-logo-connected-selected@2x.png
--------------------------------------------------------------------------------
/nwapp/img/osx-icon-logo-connected-unread.tiff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitterHQ/desktop/HEAD/nwapp/img/osx-icon-logo-connected-unread.tiff
--------------------------------------------------------------------------------
/nwapp/img/win-icon-logo-connected-unread.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitterHQ/desktop/HEAD/nwapp/img/win-icon-logo-connected-unread.ico
--------------------------------------------------------------------------------
/nwapp/img/linux-icon-logo-connected-unread.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitterHQ/desktop/HEAD/nwapp/img/linux-icon-logo-connected-unread.png
--------------------------------------------------------------------------------
/nwapp/img/osx-icon-logo-connected-selected.tiff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitterHQ/desktop/HEAD/nwapp/img/osx-icon-logo-connected-selected.tiff
--------------------------------------------------------------------------------
/nwapp/img/win-icon-logo-connected-selected.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitterHQ/desktop/HEAD/nwapp/img/win-icon-logo-connected-selected.ico
--------------------------------------------------------------------------------
/nwapp/utils/custom-events.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var EventEmitter = require('events').EventEmitter;
4 | var events = new EventEmitter();
5 |
6 | module.exports = events;
7 |
--------------------------------------------------------------------------------
/linux/gitter.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Name=Gitter
3 | Comment=Where developers come to talk
4 | Exec=/opt/Gitter/{{arch}}/Gitter
5 | Icon=/opt/Gitter/{{arch}}/logo.png
6 | Terminal=false
7 | Type=Application
8 | Categories=Utility;
9 |
--------------------------------------------------------------------------------
/nwapp/main.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | margin: 0px;
3 | padding: 0px;
4 | width: 100%;
5 | height: 100%;
6 | font-family: helvetica, arial, sans-serif;
7 | }
8 |
9 | iframe {
10 | width: 100%;
11 | height: 100%;
12 | }
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .ruby-version
3 | *.sw*
4 | *.deb
5 | *.dmg
6 | keys.json
7 | isolate-*-v8.log
8 | node_modules
9 | latest
10 | cache
11 | output
12 | opt
13 | nwapp/oauth.json
14 | certificates/*
15 | artefacts/*
16 | npm-debug.log
17 | env.sh
18 |
--------------------------------------------------------------------------------
/nwapp/utils/sounds.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = [
4 | {
5 | label: 'None',
6 | path: null,
7 | },
8 | {
9 | label: 'Gitter Default',
10 | path: '../audio/gitter-long.ogg'
11 | },
12 | {
13 | label: 'Gitter Ping',
14 | path: '../audio/gitter-ping.ogg'
15 | }
16 | ];
17 |
--------------------------------------------------------------------------------
/nwapp/utils/client-type.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var os = require('os');
4 |
5 | module.exports = (function () {
6 | var platform = os.platform();
7 | if (platform.match(/darwin/)) return 'osx';
8 | if (platform.match(/^win/)) return 'win';
9 | if (platform.match(/linux/)) return 'linux';
10 | return os;
11 | })();
12 |
--------------------------------------------------------------------------------
/nwapp/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Gitter
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/osx/create-dmg/sample:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 | test -f test2.dmg && rm test2.dmg
3 | ./create-dmg --window-size 500 300 --background ~/Projects/eclipse-osx-repackager/build/background.gif --icon-size 96 --volname "Hyper Foo" --app-drop-link 380 205 --icon "Eclipse OS X Repackager" 110 205 test2.dmg /Users/andreyvit/Projects/eclipse-osx-repackager/temp/Eclipse\ OS\ X\ Repackager\ r10/
4 |
--------------------------------------------------------------------------------
/nwapp/utils/quit-app.js:
--------------------------------------------------------------------------------
1 | var log = require('loglevel');
2 | var gui = window.require('nw.gui');
3 | var events = require('./custom-events');
4 |
5 | // We wrap their quit method as it doesn't emit a `quit` or `close` event that
6 | // we can listen to and do any proper cleanup
7 | module.exports = function() {
8 | log.info('Quitting app, app:quit');
9 | events.emit('app:quit');
10 |
11 | gui.App.quit();
12 | };
13 |
--------------------------------------------------------------------------------
/nwapp/loggedin.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Gitter
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.jsbeautifyrc:
--------------------------------------------------------------------------------
1 | {
2 | "indent_with_tabs": false,
3 | "preserve_newlines": true,
4 | "max_preserve_newlines": 10,
5 | "jslint_happy": false,
6 | "space_after_anon_function": false,
7 | "brace_style": "collapse",
8 | "keep_array_indentation": true,
9 | "keep_function_indentation": false,
10 | "space_before_conditional": true,
11 | "break_chained_methods": false,
12 | "eval_code": false,
13 | "unescape_strings": false,
14 | "wrap_line_length": 0
15 | }
16 |
--------------------------------------------------------------------------------
/snap/snapcraft.yaml:
--------------------------------------------------------------------------------
1 | name: gitter
2 | version: master
3 | summary: Where developers come to talk
4 | description: gitter desktop client
5 |
6 | grade: devel # must be 'stable' to release into candidate/stable channels
7 | confinement: classic
8 |
9 | apps:
10 | gitter:
11 | command: opt/Gitter/linux64/Gitter
12 | plugs: [browser-support, network, network-bind, x11, opengl]
13 |
14 | parts:
15 | desktop:
16 | source: https://update.gitter.im/linux64/gitter_3.1.0_amd64.deb
17 | source-type: deb
18 | plugin: dump
19 |
--------------------------------------------------------------------------------
/latest-template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
9 | Page Redirection
10 |
11 |
12 | If you are not redirected automatically, follow the link to download Gitter
13 |
14 |
15 |
--------------------------------------------------------------------------------
/nwapp/loggedin.js:
--------------------------------------------------------------------------------
1 | var gui = require('nw.gui');
2 | var win = gui.Window.get();
3 | var frame = win.window.document.getElementById('mainframe');
4 | var settings = require('./utils/settings');
5 | var version = require('./package.json').version;
6 |
7 | // FIXME This is a hack that allows us to send auth token in the user-agent header.
8 | // The nwUserAgent will be used for the iframe and all the requests that originate inside it
9 | frame.nwUserAgent = navigator.userAgent + ' Gitter/' + version + ' Gitter Token/' + settings.token;
10 |
11 | frame.src = 'https://gitter.im/';
12 |
--------------------------------------------------------------------------------
/linux/after-remove.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # removing what we added on after-install
4 | libpaths=(
5 | "/lib/x86_64-linux-gnu" # Ubuntu, Xubuntu, Mint
6 | "/usr/lib64" # SUSE, Fedora
7 | "/usr/lib" # Arch, Fedora 32bit
8 | "/lib/i386-linux-gnu" # Ubuntu 32bit
9 | )
10 |
11 | for i in "${libpaths[@]}"
12 | do
13 | if [ -f "$i/libudev.so.0" ]
14 | then
15 | rm "$i/libudev.so.0"
16 | break
17 | fi
18 | done
19 |
20 | # Link to the Gitter binary
21 | rm -f /usr/local/bin/gitter
22 |
23 | # Unity Launcher icon
24 | rm -f /usr/share/applications/gitter.desktop
25 |
--------------------------------------------------------------------------------
/linux/after-install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | libpaths=(
4 | "/lib/x86_64-linux-gnu" # Ubuntu, Xubuntu, Mint
5 | "/usr/lib64" # SUSE, Fedora
6 | "/usr/lib" # Arch, Fedora 32bit
7 | "/lib/i386-linux-gnu" # Ubuntu 32bit
8 | )
9 |
10 | for i in "${libpaths[@]}"
11 | do
12 | if [ -f "$i/libudev.so.1" ]
13 | then
14 | ln -sf "$i/libudev.so.1" "$i/libudev.so.0"
15 | break
16 | fi
17 | done
18 |
19 | # Link to the Gitter binary
20 | ln -sf /opt/Gitter/{{arch}}/Gitter /usr/local/bin/gitter
21 |
22 | # Unity Launcher icon
23 | desktop-file-install /opt/Gitter/{{arch}}/gitter.desktop
24 |
--------------------------------------------------------------------------------
/nwapp/oauth.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Gitter Login
4 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/windows/build.bat:
--------------------------------------------------------------------------------
1 | echo "Parameter 1: version x.x.x"
2 | echo "Parameter 2: .pfx password"
3 |
4 | if "%1"=="" (
5 | echo "please provide a version x.x.x"
6 | ) else (
7 | "C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Bin\signtool.exe" sign /f "%cd%\certificates\troupe-cert.pfx" /p "%2" "%cd%\opt\Gitter\win32\Gitter.exe"
8 | "C:\Program Files (x86)\Inno Setup 5\ISCC.exe" "%cd%\windows\gitter.iss"
9 | "C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Bin\signtool.exe" sign /f "%cd%\certificates\troupe-cert.pfx" /p "%2" "%cd%\artefacts\GitterSetup*"
10 | rename "%cd%\artefacts\GitterSetup.exe" "GitterSetup-%1%.exe"
11 | )
12 |
--------------------------------------------------------------------------------
/osx/create-dmg/builder/create-dmg.builder:
--------------------------------------------------------------------------------
1 | SET app_name create-dmg
2 |
3 | VERSION create-dmg.cur create-dmg heads/master
4 |
5 | NEWDIR build.dir temp %-build -
6 |
7 | NEWFILE create-dmg.zip featured %.zip %
8 |
9 |
10 | COPYTO [build.dir]
11 | INTO create-dmg [create-dmg.cur]/create-dmg
12 | INTO sample [create-dmg.cur]/sample
13 | INTO support [create-dmg.cur]/support
14 |
15 | SUBSTVARS [build.dir]/create-dmg [[]]
16 |
17 |
18 | ZIP [create-dmg.zip]
19 | INTO [build-files-prefix] [build.dir]
20 |
21 |
22 | PUT megabox-builds create-dmg.zip
23 | PUT megabox-builds build.log
24 |
25 | PUT s3-builds create-dmg.zip
26 | PUT s3-builds build.log
27 |
--------------------------------------------------------------------------------
/bootstrap.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | echo "Installing dependencies... (assuming XCode is installed)"
4 |
5 | echo "Installing awscli via Brew"
6 | brew install awscli
7 |
8 | echo "Installing Wine via Brew"
9 | brew install wine
10 |
11 | echo "Installing Gnu-tar via Brew"
12 | brew install gnu-tar
13 |
14 | echo "Installing npm deps for the app"
15 | (cd nwapp && npm install)
16 |
17 | echo "Installing packaging deps"
18 | npm install
19 |
20 | echo "Installing fpm Ruby gem"
21 | sudo gem install fpm
22 |
23 | echo "Cloning DMG tools"
24 | git remote add create-dmg git@github.com:andreyvit/create-dmg.git
25 | git subtree add --prefix=osx/create-dmg --squash create-dmg master
26 |
27 | echo "Done. If you need to configure AWS CLI tools, run: aws configure"
28 |
--------------------------------------------------------------------------------
/nwapp/icons.json:
--------------------------------------------------------------------------------
1 | {
2 | "osx" : {
3 | "disconnected" : "img/osx-icon-logo-disconnected.tiff",
4 | "connected" : "img/osx-icon-logo-connected.tiff",
5 | "unread" : "img/osx-icon-logo-connected-unread.tiff",
6 | "selected" : "img/osx-icon-logo-connected-selected.tiff"
7 | },
8 | "win" : {
9 | "disconnected" : "img/icon-logo-disconnected.png",
10 | "connected" : "img/icon-logo-connected.png",
11 | "unread" : "img/icon-logo-connected-unread.png",
12 | "selected" : "img/icon-logo-connected-selected.png"
13 | },
14 | "linux" : {
15 | "disconnected" : "img/linux-icon-logo-disconnected.png",
16 | "connected" : "img/linux-icon-logo-connected.png",
17 | "unread" : "img/linux-icon-logo-connected-unread.png",
18 | "selected" : "img/linux-icon-logo-connected.png"
19 | }
20 | }
21 |
22 |
23 |
--------------------------------------------------------------------------------
/nwapp/components/sound-items.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var events = require('../utils/custom-events');
4 | var settings = require('../utils/settings');
5 | var SOUNDS = require('../utils/sounds');
6 |
7 | // this array will be MenuItems thus the mapping is to add defaults values
8 | var SOUND_ITEMS = SOUNDS
9 | .map(function (sound, index) {
10 | sound.type = 'checkbox';
11 | sound.checked = settings.notificationSound === index;
12 | sound.click = function (e) {
13 | settings.notificationSound = index;
14 | };
15 | return sound;
16 | });
17 |
18 | // update the checked sounds on change
19 | events.on('settings:change:notificationSound', function () {
20 | SOUND_ITEMS.forEach(function (sound, index) {
21 | sound.checked = settings.notificationSound === index;
22 | });
23 | });
24 |
25 | module.exports = SOUND_ITEMS;
26 |
--------------------------------------------------------------------------------
/nwapp/components/context-menu.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gui = window.require('nw.gui');
4 | var menu = new gui.Menu();
5 |
6 | function Menu(cutLabel, copyLabel, pasteLabel) {
7 | var cut = new gui.MenuItem({
8 | label: cutLabel || "Cut",
9 | click: function() {
10 | document.execCommand("cut");
11 | }
12 | });
13 |
14 | var copy = new gui.MenuItem({
15 | label: copyLabel || "Copy",
16 | click: function() {
17 | document.execCommand("copy");
18 | }
19 | });
20 |
21 | var paste = new gui.MenuItem({
22 | label: pasteLabel || "Paste",
23 | click: function() {
24 | document.execCommand("paste");
25 | }
26 | })
27 | ;
28 |
29 | menu.append(cut);
30 | menu.append(copy);
31 | menu.append(paste);
32 |
33 | return menu;
34 | }
35 |
36 |
37 | module.exports = new Menu();
38 |
39 |
--------------------------------------------------------------------------------
/osx/create-dmg/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2008-2014 Andrey Tarantsov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/nwapp/components/tray.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gui = window.require('nw.gui');
4 | var settings = require('../utils/settings');
5 | var events = require('../utils/custom-events');
6 |
7 | var CLIENT_TYPE = require('../utils/client-type');
8 | var icon = require('../icons.json')[CLIENT_TYPE];
9 |
10 | function CustomTray() {
11 | this.tray = new gui.Tray({
12 | icon: icon.disconnected,
13 | alticon: icon.selected,
14 | iconsAreTemplates: false
15 | });
16 |
17 | events.on('user:signedOut', this.disconnect.bind(this));
18 | events.on('user:signedIn', this.connect.bind(this));
19 | events.on('traymenu:read', this.connect.bind(this));
20 | events.on('traymenu:unread', this.unread.bind(this));
21 | }
22 |
23 | CustomTray.prototype.setIcon = function(icon) {
24 | this.tray.icon = icon;
25 | };
26 |
27 | CustomTray.prototype.get = function() {
28 | return this.tray;
29 | };
30 | CustomTray.prototype.disconnect = function() {
31 | this.setIcon(icon.disconnected);
32 | };
33 | CustomTray.prototype.connect = function() {
34 | if (!settings.token) return; // user is signed out
35 | this.setIcon(icon.connected);
36 | };
37 | CustomTray.prototype.unread = function() {
38 | this.setIcon(icon.unread);
39 | };
40 |
41 |
42 | module.exports = CustomTray;
43 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gitter-desktop",
3 | "version": "0.0.1",
4 | "nwversion": "0.12.3",
5 | "description": "Gitter nw.js builder",
6 | "main": "index.js",
7 | "dependencies": {
8 | "gulp": "^3.8.10",
9 | "gulp-shell": "^0.2.11",
10 | "gulp-util": "^3.0.1",
11 | "lodash.template": "^3.0.1",
12 | "nw-builder": "^2.2.0",
13 | "semver": "^4.2.0"
14 | },
15 | "devDependencies": {
16 | "bluebird": "^3.2.2",
17 | "npm": "^3.7.3",
18 | "nw": "^0.12.3",
19 | "rimraf": "^2.2.8",
20 | "s3": "^4.3.1",
21 | "stream-exhaust": "^1.0.1",
22 | "through2": "^2.0.1",
23 | "yargs": "^3.32.0"
24 | },
25 | "scripts": {
26 | "postinstall": "(cd nwapp && npm install)",
27 | "build": "gulp build",
28 | "start": "nw nwapp"
29 | },
30 | "author": "Mauro Pompilio",
31 | "license": "MIT",
32 | "copyright": "copyright flag required by node-webkit-builder",
33 | "directories": {
34 | "test": "test"
35 | },
36 | "repository": {
37 | "type": "git",
38 | "url": "https://github.com/gitterHQ/desktop.git"
39 | },
40 | "keywords": [
41 | "gitter",
42 | "desktop",
43 | "osx",
44 | "windows",
45 | "linux"
46 | ],
47 | "bugs": {
48 | "url": "https://github.com/gitterHQ/desktop/issues"
49 | },
50 | "homepage": "https://github.com/gitterHQ/desktop"
51 | }
52 |
--------------------------------------------------------------------------------
/nwapp/utils/trace.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /* Utility class for tracing calls */
4 | var log = require('loglevel');
5 |
6 | function wrapFunction(name, fn) {
7 | if (typeof fn !== 'function') throw new Error('Cannot wrap ' + fn);
8 | return function traceWrapper() {
9 | log.trace('enter::' + name);
10 | try {
11 | var args = Array.prototype.slice.apply(arguments);
12 | return fn.apply(this, args);
13 | } catch(e) {
14 | log.trace('error::' + e);
15 | throw e;
16 | } finally {
17 | log.trace('exit::' + name);
18 | }
19 | };
20 | }
21 |
22 | function wrapObject(object, name) {
23 | if (object.___traced) return object;
24 |
25 | // Attempt to figure out the name
26 | if (!name) name = object.constructor && object.constructor.name;
27 |
28 | Object.defineProperty(object, '___traced', { configurable: true, enumerable: false, value: true });
29 | var o = {}; // Base object
30 |
31 | Object.keys(object).forEach(function(k) {
32 | if (o.hasOwnProperty(k)) return;
33 |
34 | if (typeof object[k] === 'function') {
35 | object[k] = wrapFunction(name + '::' + k, object[k]);
36 | }
37 | });
38 | if (object.prototype) {
39 | wrapObject(object.prototype, name);
40 | }
41 |
42 | return object;
43 | }
44 |
45 | module.exports = {
46 | wrapFunction: wrapFunction,
47 | wrapObject: wrapObject,
48 | };
49 |
--------------------------------------------------------------------------------
/windows/README.md:
--------------------------------------------------------------------------------
1 | # Windows build
2 |
3 | On a Windows machine:
4 |
5 | - Install Inno Setup: http://www.jrsoftware.org/isdl.php
6 | - Install Microsoft SDK: http://www.microsoft.com/en-gb/download/details.aspx?id=8279
7 | - Checkout the project or use networked file sharing, for example from your OS X machine to the Windows machine: https://support.apple.com/kb/PH18707. [Just make sure the path doesn't have any spaces](https://github.com/nodejs/node/issues/5160).
8 | - Also ensure that you have ran `gulp build` and that the `opt/Gitter/win32` directory exists and has `Gitter.exe` inside of it, see https://github.com/gitterHQ/desktop#releasing-the-app-win32-and-linux3264
9 | - Run `node "\\ERIC-MACBOOK\eric\Documents\github\desktop\windows\build.js" -p thepfxcertpasswordhere`
10 |
11 | You should end up with a `GitterSetup-x.x.x.exe` installer inside `./artefacts/`
12 |
13 | ## Code Sign
14 |
15 | You'll need to sign the Gitter.exe binary and the installer separately.
16 |
17 | ### SignTool (method used in build.js)
18 |
19 | Install Microsoft SDK from: http://www.microsoft.com/en-gb/download/details.aspx?id=8279
20 |
21 | Usage: `signtool.exe sign /f troupe-cert.pfx binary.exe`
22 |
23 | ### kSign
24 |
25 | Has a GUI. Very easy to use. Just use the cert provided: `troupe-cert.pfx`
26 |
27 | ## Inno Setup
28 |
29 | `gitter.iss` contains an Inno Setup manifest to generate an installer for Windows.
30 |
31 | If you need to modify the Inno Setup file, you can use the Inno Script Studio (or a text editor if you know the config): https://www.kymoto.org/products/inno-script-studio
32 |
33 | Get Inno Setup from: http://www.jrsoftware.org/isdl.php
34 |
35 | Generate an installer with: `"C:\Program Files\Inno Setup 5\ISCC.exe" "C:\gitter-desktop\gitter.iss`
36 |
--------------------------------------------------------------------------------
/windows/gitter.iss:
--------------------------------------------------------------------------------
1 | ; Script generated by the Inno Setup Script Wizard.
2 | ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
3 |
4 | #define MyAppName "Gitter"
5 | #define MyAppPublisher "Troupe Technology Limited"
6 | #define MyAppURL "https://www.gitter.im"
7 | #define MyAppExeName "Gitter.exe"
8 |
9 | [Setup]
10 | ; NOTE: The value of AppId uniquely identifies this application.
11 | ; Do not use the same AppId value in installers for other applications.
12 | ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
13 | AppId={{03C07717-35D4-40B2-B4F2-05A0EF1B9F6F}
14 | AppName={#MyAppName}
15 | ;AppVersion={#MyAppVersion}
16 | AppVerName={#MyAppName}
17 | AppPublisher={#MyAppPublisher}
18 | AppPublisherURL={#MyAppURL}
19 | AppSupportURL={#MyAppURL}
20 | AppUpdatesURL={#MyAppURL}
21 | DefaultDirName={userpf}\{#MyAppName}
22 | UninstallFilesDir={userpf}\GitterUninstaller
23 | DefaultGroupName={#MyAppName}
24 | OutputBaseFilename=GitterSetup
25 | Compression=lzma
26 | SolidCompression=yes
27 | ;SignTool=SignTool
28 | OutputDir=..\artefacts
29 | ShowLanguageDialog=no
30 | PrivilegesRequired=lowest
31 |
32 | [Languages]
33 | Name: "english"; MessagesFile: "compiler:Default.isl"
34 |
35 | [Tasks]
36 | Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
37 | Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 0,6.1
38 |
39 | [Files]
40 | Source: "..\opt\Gitter\win32\Gitter.exe"; DestDir: "{app}"; Flags: ignoreversion
41 | Source: "..\opt\Gitter\win32\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
42 | ; NOTE: Don't use "Flags: ignoreversion" on any shared system files
43 |
44 | [Icons]
45 | Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
46 | Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
47 | Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: quicklaunchicon
48 |
49 | [Run]
50 | Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
51 |
--------------------------------------------------------------------------------
/nwapp/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Gitter",
3 | "description": "Where developers come to talk.",
4 | "version": "3.1.0",
5 | "main": "app://gitter/index.html",
6 | "engineStrict": true,
7 | "engines": {
8 | "npm": "^3.0.0"
9 | },
10 | "window": {
11 | "icon": "./img/logo.png",
12 | "show": false,
13 | "toolbar": false,
14 | "frame": true,
15 | "width": 1200,
16 | "height": 800,
17 | "position": "center",
18 | "min_width": 200,
19 | "min_height": 200,
20 | "max_width": 8000,
21 | "max_height": 8000
22 | },
23 | "webkit": {
24 | "plugin": false,
25 | "page-cache": true
26 | },
27 | "user-agent": "%name %ver %nwver %webkit_ver %osinfo",
28 | "dependencies": {
29 | "auto-launch": "^1.1.1",
30 | "bluebird": "^3.2.1",
31 | "extract-zip": "^1.0.3",
32 | "fs-extra": "^0.26.5",
33 | "gitter-realtime-client": "^1.0.0",
34 | "jfs": "^0.2.6",
35 | "loglevel": "^1.2.0",
36 | "node-notifier": "^4.5.0",
37 | "node-webkit-updater": "^0.3.2",
38 | "node-webkit-winstate": "^1.1.0",
39 | "oauth": "^0.9.12",
40 | "object-assign": "^4.0.1",
41 | "open": "0.0.5",
42 | "request": "^2.69.0",
43 | "rimraf": "^2.4.3",
44 | "semver": "^5.1.0",
45 | "temp": "^0.8.3",
46 | "url-parse": "^1.0.5",
47 | "yargs": "^4.1.0"
48 | },
49 | "packages": {
50 | "win": {
51 | "url": "https://update.gitter.im/win/win32.zip"
52 | },
53 | "osx": {
54 | "url": "https://update.gitter.im/osx/osx.zip"
55 | },
56 | "mac": {
57 | "url": "https://update.gitter.im/osx/osx.zip"
58 | },
59 | "linux32": {
60 | "url": "https://update.gitter.im/linux32/linux32.zip"
61 | },
62 | "linux64": {
63 | "url": "https://update.gitter.im/linux64/linux64.zip"
64 | }
65 | },
66 | "single-instance": false,
67 | "devDependencies": {},
68 | "scripts": {
69 | "test": "echo \"Error: no test specified\" && exit 1"
70 | },
71 | "repository": {
72 | "type": "git",
73 | "url": "git@github.com:gitterHQ/desktop.git"
74 | },
75 | "keywords": [
76 | "gitter",
77 | "desktop",
78 | "nwapp"
79 | ],
80 | "license": "MIT",
81 | "bugs": {
82 | "url": "https://github.com/gitterHQ/desktop/issues"
83 | },
84 | "homepage": "https://github.com/gitterHQ/desktop"
85 | }
86 |
--------------------------------------------------------------------------------
/osx/create-dmg/support/template.applescript:
--------------------------------------------------------------------------------
1 | on run (volumeName)
2 | tell application "Finder"
3 | tell disk (volumeName as string)
4 | open
5 |
6 | set theXOrigin to WINX
7 | set theYOrigin to WINY
8 | set theWidth to WINW
9 | set theHeight to WINH
10 |
11 | set theBottomRightX to (theXOrigin + theWidth)
12 | set theBottomRightY to (theYOrigin + theHeight)
13 | set dsStore to "\"" & "/Volumes/" & volumeName & "/" & ".DS_STORE\""
14 |
15 | tell container window
16 | set current view to icon view
17 | set toolbar visible to false
18 | set statusbar visible to false
19 | set the bounds to {theXOrigin, theYOrigin, theBottomRightX, theBottomRightY}
20 | set statusbar visible to false
21 | REPOSITION_HIDDEN_FILES_CLAUSE
22 | end tell
23 |
24 | set opts to the icon view options of container window
25 | tell opts
26 | set icon size to ICON_SIZE
27 | set text size to TEXT_SIZE
28 | set arrangement to not arranged
29 | end tell
30 | BACKGROUND_CLAUSE
31 |
32 | -- Positioning
33 | POSITION_CLAUSE
34 |
35 | -- Hiding
36 | HIDING_CLAUSE
37 |
38 | -- Application Link Clause
39 | APPLICATION_CLAUSE
40 | close
41 | open
42 |
43 | update without registering applications
44 | -- Force saving of the size
45 | delay 1
46 |
47 | tell container window
48 | set statusbar visible to false
49 | set the bounds to {theXOrigin, theYOrigin, theBottomRightX - 10, theBottomRightY - 10}
50 | end tell
51 |
52 | update without registering applications
53 | end tell
54 |
55 | delay 1
56 |
57 | tell disk (volumeName as string)
58 | tell container window
59 | set statusbar visible to false
60 | set the bounds to {theXOrigin, theYOrigin, theBottomRightX, theBottomRightY}
61 | end tell
62 |
63 | update without registering applications
64 | end tell
65 |
66 | --give the finder some time to write the .DS_Store file
67 | delay 3
68 |
69 | set waitTime to 0
70 | set ejectMe to false
71 | repeat while ejectMe is false
72 | delay 1
73 | set waitTime to waitTime + 1
74 |
75 | if (do shell script "[ -f " & dsStore & " ]; echo $?") = "0" then set ejectMe to true
76 | end repeat
77 | log "waited " & waitTime & " seconds for .DS_STORE to be created."
78 | end tell
79 | end run
80 |
--------------------------------------------------------------------------------
/nwapp/components/menu-items.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gui = window.require('nw.gui');
4 | var pkg = require('../package.json');
5 | var settings = require('../utils/settings');
6 | var events = require('../utils/custom-events');
7 | var quitApp = require('../utils/quit-app');
8 |
9 | var SOUNDS = require('../utils/sounds');
10 |
11 | module.exports = [
12 | {
13 | label: 'Gitter v' + pkg.version,
14 | enabled: false,
15 | support: ['linux', 'win']
16 | },
17 | {
18 | label: 'Show in Menu Bar',
19 | type: 'checkbox',
20 | checked: settings.showInMacMenuBar,
21 | support: ['osx'],
22 | click: function () {
23 | settings.showInMacMenuBar = !settings.showInMacMenuBar;
24 | }
25 | },
26 | {
27 | label: 'Launch on startup',
28 | type: 'checkbox',
29 | checked: settings.launchOnStartup,
30 | click: function () {
31 | settings.launchOnStartup = !settings.launchOnStartup;
32 | },
33 | support: ['linux', 'win', 'osx']
34 | },
35 | {
36 | label: 'Launch hidden',
37 | type: 'checkbox',
38 | checked: settings.launchHidden,
39 | click: function () {
40 | settings.launchHidden = !settings.launchHidden;
41 | },
42 | support: ['linux', 'win', 'osx']
43 | },
44 | {
45 | type: 'separator',
46 | },
47 | {
48 | label: 'Show Notifications',
49 | type: 'checkbox',
50 | checked: settings.showNotifications,
51 | click: function () {
52 | settings.showNotifications = !settings.showNotifications;
53 | },
54 | auth: true
55 | },
56 | {
57 | label: 'Notification Sound',
58 | auth: true,
59 | content: SOUNDS
60 | },
61 | {
62 | label: 'Gitter Next',
63 | type: 'checkbox',
64 | checked: settings.next,
65 | click: events.emit.bind(events, 'menu:toggle:next')
66 | },
67 | {
68 | type: 'separator',
69 | },
70 | {
71 | label: 'Developer Tools',
72 | click: events.emit.bind(events, 'menu:toggle:devtools')
73 | },
74 | {
75 | type: 'separator',
76 | },
77 | {
78 | label: 'Sign Out',
79 | click: events.emit.bind(events, 'menu:signout'),
80 | auth: true
81 | },
82 | {
83 | label: 'Exit',
84 | click: quitApp,
85 | support: ['linux', 'win']
86 | }
87 | ].map(function (item, index) {
88 | item.index = index; // FIXME: unsure whether this is a good way to add index
89 | return item;
90 | });
91 |
--------------------------------------------------------------------------------
/nwapp/utils/settings.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var Store = require('jfs');
4 | var gui = window.require('nw.gui');
5 | var events = require('./custom-events');
6 | var log = require('loglevel');
7 | var SOUNDS = require('./sounds');
8 |
9 | function isBool(val) {
10 | return typeof val === "boolean";
11 | }
12 |
13 | // via http://stackoverflow.com/a/1830844/796832
14 | function isNumeric(n) {
15 | return !isNaN(parseFloat(n)) && isFinite(n);
16 | }
17 |
18 | var DEFAULT_SETTINGS = {
19 | showInMacMenuBar: {
20 | value: true,
21 | validate: isBool
22 | },
23 | launchOnStartup: {
24 | value: true,
25 | validate: isBool
26 | },
27 | launchHidden: {
28 | value: false,
29 | validate: isBool
30 | },
31 | next: {
32 | value: false,
33 | validate: isBool
34 | },
35 | showNotifications: {
36 | value: true,
37 | validate: isBool
38 | },
39 | notificationSound: {
40 | value: 1,
41 | validate: function (val) {
42 | return val >= 0 && val <= SOUNDS.length;
43 | }
44 | }
45 | };
46 |
47 | var db = new Store(gui.App.dataPath + '/gitter_preferences.json', { pretty: true }); // FIXME: pretty Boolean - should be environment dependent
48 |
49 | // initial load is done synchronously
50 | var settings = db.getSync('settings');
51 |
52 | // setting up observable
53 | Object.observe(settings, function (changes) {
54 | // save settings everytime a change is made
55 | db.save('settings', settings, function (err) {
56 | // emit an event which indicates failure or success
57 | if (err) {
58 | log.error('ERROR: Could not save settings.');
59 | return events.emit('settings:failed', err);
60 | }
61 | events.emit('settings:saved');
62 | });
63 |
64 | // in case any component is interested on a particular setting change
65 | changes.forEach(function (change) {
66 | events.emit('settings:change:' + change.name, change.object[change.name]);
67 | });
68 |
69 | events.emit('settings:change');
70 | });
71 |
72 | // performs a check to guarantee health of settings
73 | Object.keys(DEFAULT_SETTINGS)
74 | .forEach(function (key) {
75 | if (!settings.hasOwnProperty(key)) {
76 | settings[key] = DEFAULT_SETTINGS[key].value;
77 | }
78 |
79 | var isValid = DEFAULT_SETTINGS[key].validate(settings[key]);
80 |
81 | if (!isValid) {
82 | log.warn('ERROR: Invalid setting:' + key + '. restoring to default');
83 | settings[key] = DEFAULT_SETTINGS[key].value;
84 | }
85 | });
86 |
87 | module.exports = settings;
88 |
--------------------------------------------------------------------------------
/nwapp/components/menu.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var events = require('../utils/custom-events');
4 | var gui = window.require('nw.gui');
5 | var CLIENT = require('../utils/client-type');
6 |
7 | // recursively assembles menus and sub-menus
8 | function assembleMenu(items, parent) {
9 | var fold;
10 |
11 | parent = parent ? parent : new gui.Menu();
12 |
13 | items.forEach(function (item) {
14 | // means that we have a submenu
15 | if (item.content && item.content.length > 0) {
16 | var submenu = new gui.Menu();
17 | fold = assembleMenu(item.content, submenu);
18 | } else {
19 | fold = new gui.MenuItem(item);
20 | }
21 |
22 | if (fold.type === 'contextmenu') {
23 | item.submenu = fold;
24 | fold = new gui.MenuItem(item);
25 | }
26 |
27 | if (item.index && CLIENT === 'osx') {
28 | parent.insert(fold, item.index); // inserting at specified index
29 | } else {
30 | parent.append(fold); // just append
31 | }
32 | });
33 |
34 | return parent;
35 | }
36 |
37 | function CustomMenu(spec) {
38 |
39 | this.menu = new gui.Menu({ type: 'menubar' });
40 | this.items = spec.items || [];
41 | this.filter = spec.filter;
42 | this.label = spec.label;
43 |
44 | events.on('user:signedIn', this.build.bind(this));
45 | events.on('user:signedOut', this.build.bind(this));
46 | events.on('settings:saved', this.build.bind(this));
47 |
48 | this.build(); // initial render
49 |
50 | return this;
51 | }
52 |
53 | // FIXME: this function will probably not work for all platforms
54 | CustomMenu.prototype.clear = function () {
55 | for (var i = this.menu.items.length - 1; i >= 0; i--) {
56 | this.menu.removeAt(i);
57 | }
58 | };
59 |
60 | CustomMenu.prototype.build = function () {
61 | this.clear(); // clear the menu on every "render"
62 | var filteredItems = this.items;
63 |
64 | if (this.filter && typeof this.filter === 'function') {
65 | filteredItems = this.items.filter(this.filter);
66 | }
67 |
68 | // note that we have different targets based on OS
69 | switch (CLIENT) {
70 | case 'osx':
71 | this.menu.createMacBuiltin(this.label);
72 | assembleMenu(filteredItems, this.menu.items[0].submenu);
73 | break;
74 | default:
75 | var sub = new gui.Menu();
76 | assembleMenu(filteredItems, sub);
77 | this.menu.append(new gui.MenuItem({
78 | label: this.label,
79 | submenu: sub
80 | }));
81 | break;
82 | }
83 | events.emit('menu:updated');
84 | };
85 |
86 | CustomMenu.prototype.get = function () {
87 | return this.menu;
88 | };
89 |
90 | module.exports = CustomMenu;
91 |
--------------------------------------------------------------------------------
/nwapp/lib/login-view.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var log = require('loglevel');
4 | var oauthJson = { osx: {}, win: {}, linux: {} };
5 | try {
6 | oauthJson = require('../oauth.json');
7 | } catch (e) {
8 | log.warn('nwapp/oauth.json not found. Hopefully OAUTH_KEY and OAUTH_SECRET are set...');
9 | }
10 |
11 | // See https://github.com/nwjs/nw.js/wiki/window#synopsis
12 | var gui = global.window.nwDispatcher.requireNwGui();
13 | var os = require('../utils/client-type');
14 | var OAuth2 = require('oauth').OAuth2;
15 | var EventEmitter = require('events').EventEmitter;
16 | var util = require('util');
17 |
18 | var clientId = process.env.OAUTH_KEY || oauthJson[os].key || '';
19 | var clientSecret = process.env.OAUTH_SECRET || oauthJson[os].secret || '';
20 | var baseSite = 'https://gitter.im/';
21 | var authorizePath = 'login/oauth/authorize';
22 | var accessTokenPath = 'login/oauth/token';
23 | var redirectUri = 'app://gitter/oauth.html';
24 |
25 |
26 | var LoginView = function(rootWindow) {
27 | var self = this;
28 |
29 | if (!clientId) throw new Error('You must provide an oauth key. Keys can be obtained from https://developer.gitter.im');
30 |
31 | var auth = new OAuth2(clientId, clientSecret, baseSite, authorizePath, accessTokenPath);
32 | var authUrl = auth.getAuthorizeUrl({ redirect_uri: redirectUri, response_type: 'code' });
33 |
34 | // new window for login/oauth
35 | this.oauthWindow = rootWindow.open(authUrl, {
36 | // hide page loading
37 | show: false,
38 | toolbar: false,
39 | icon: 'img/logo.png',
40 |
41 | // just big enough to show github login without scrollbars
42 | width: 1024,
43 | height: 640
44 | });
45 | var mb = new gui.Menu({
46 | type: 'menubar'
47 | });
48 | if(os === 'osx') {
49 | mb.createMacBuiltin('Gitter', {
50 | hideEdit: false
51 | });
52 | }
53 | gui.Window.get().menu = mb;
54 |
55 | this.oauthWindow.on('document-end', function() {
56 | // gitter login page finished loading visible bits
57 | self.oauthWindow.show();
58 | self.oauthWindow.focus();
59 | });
60 |
61 | this.oauthWindow.on('close', function() {
62 | self.destroy();
63 | });
64 |
65 | this.oauthWindow.on('error', function(err) {
66 | log.error('oauth error: ' + JSON.stringify(err));
67 | self.destroy();
68 | });
69 |
70 |
71 | this.oauthWindow.on('codeReceived', function(code) {
72 | // login page no longer needed
73 | self.oauthWindow.hide();
74 |
75 | auth.getOAuthAccessToken(code, { redirect_uri: redirectUri, grant_type: 'authorization_code' }, function(err, accessToken) {
76 | if (err) {
77 | log.error('oauth error: ' + JSON.stringify(err));
78 | self.destroy();
79 | return;
80 | }
81 |
82 | self.emit('accessTokenReceived', accessToken);
83 | });
84 | });
85 | };
86 |
87 | util.inherits(LoginView, EventEmitter);
88 |
89 | LoginView.prototype.destroy = function() {
90 | // skips all on close listeners
91 | this.oauthWindow.close(true);
92 | this.emit('destroy');
93 | };
94 |
95 | module.exports = LoginView;
96 |
--------------------------------------------------------------------------------
/osx/create-dmg/README.md:
--------------------------------------------------------------------------------
1 | create-dmg
2 | ==========
3 |
4 | A shell script to build fancy DMGs.
5 |
6 |
7 | Status and contribution policy
8 | ------------------------------
9 |
10 | This project is maintained thanks to the contributors who send pull requests. The original author has no use for the project, so his only role is reviewing and merging pull requests.
11 |
12 | I will merge any pull request that adds something useful and does not break existing things.
13 |
14 | Starting in January 2015, everyone who gets a pull request merged gets commit access to the repository.
15 |
16 |
17 | Installation
18 | ------------
19 |
20 | By being a shell script, yoursway-create-dmg installation is very simple. Simply download and run.
21 |
22 | > git clone https://github.com/andreyvit/yoursway-create-dmg.git
23 | > cd yoursway-create-dmg
24 | > ./create-dmg [options]
25 |
26 |
27 | Usage
28 | -----
29 |
30 | > create-dmg [options...] [output\_name.dmg] [source\_folder]
31 |
32 | All contents of source\_folder will be copied into the disk image.
33 |
34 | **Options:**
35 |
36 | * **--volname [name]:** set volume name (displayed in the Finder sidebar and window title)
37 | * **--volicon [icon.icns]:** set volume icon
38 | * **--background [pic.png]:** set folder background image (provide png, gif, jpg)
39 | * **--window-pos [x y]:** set position the folder window
40 | * **--window-size [width height]:** set size of the folder window
41 | * **--text-size [text size]:** set window text size (10-16)
42 | * **--icon-size [icon size]:** set window icons size (up to 128)
43 | * **--icon [file name] [x y]:** set position of the file's icon
44 | * **--hide-extension [file name]:** hide the extension of file
45 | * **--custom-icon [file name]/[custom icon]/[sample file] [x y]:** set position and custom icon
46 | * **--app-drop-link [x y]:** make a drop link to Applications, at location x, y
47 | * **--eula [eula file]:** attach a license file to the dmg
48 | * **--no-internet-enable:** disable automatic mount©
49 | * **--version:** show tool version number
50 | * **-h, --help:** display the help
51 |
52 |
53 | Example
54 | -------
55 |
56 | > \#!/bin/sh
57 | > test -f Application-Installer.dmg && rm Application-Installer.dmg
58 | > create-dmg \
59 | > --volname "Application Installer" \
60 | > --volicon "application\_icon.icns" \
61 | > --background "installer\_background.png" \
62 | > --window-pos 200 120 \
63 | > --window-size 800 400 \
64 | > --icon-size 100 \
65 | > --icon Application.app 200 190 \
66 | > --hide-extension Application.app \
67 | > --app-drop-link 600 185 \
68 | > Application-Installer.dmg \
69 | > source\_folder/
70 |
71 |
72 | Alternatives
73 | ------------
74 |
75 | * [node-appdmg](https://github.com/LinusU/node-appdmg)
76 | * [dmgbuild](https://pypi.python.org/pypi/dmgbuild)
77 | * see the [StackOverflow question](http://stackoverflow.com/questions/96882/how-do-i-create-a-nice-looking-dmg-for-mac-os-x-using-command-line-tools)
78 |
--------------------------------------------------------------------------------
/nwapp/components/tray-menu.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gui = window.require('nw.gui');
4 | var log = require('loglevel');
5 | var Gitter = require('gitter-realtime-client');
6 | var settings = require('../utils/settings');
7 | var events = require('../utils/custom-events');
8 | var quitApp = require('../utils/quit-app');
9 |
10 | function TrayMenu() {
11 | this.menu = new gui.Menu();
12 | this.unsetRooms();
13 |
14 | events.on('user:signedOut', function () {
15 | this.unsetRooms();
16 | this.build();
17 | }.bind(this));
18 |
19 | events.on('realtime:connected', this.setRooms.bind(this));
20 | this.build();
21 |
22 | return this;
23 | }
24 |
25 | TrayMenu.prototype.unsetRooms = function () {
26 | if (this.rooms && this.rooms.unlisten) {
27 | this.rooms.unlisten(); // TODO: this may NOT be necessary
28 | }
29 | this.rooms = [];
30 | this.favourites = [];
31 | this.recents = [];
32 | this.unreads = [];
33 | };
34 |
35 | TrayMenu.prototype.setRooms = function (rooms) {
36 | log.debug('TrayMenu: Rooms collection set');
37 |
38 | // TODO: the existing listeners need to be cleared properly...
39 | this.rooms = rooms;
40 | this.favourites = Gitter.filteredRooms.favourites(rooms);
41 | this.recents = Gitter.filteredRooms.recents(rooms);
42 | this.unreads = Gitter.filteredRooms.unreads(rooms);
43 |
44 | this.rooms.on('reset', function () {
45 | this.build();
46 | }.bind(this));
47 |
48 | [this.favourites, this.recents, this.unreads].forEach(function (collection) {
49 | collection.on('add remove', this.build.bind(this)); // Quite wasteful, trashing and regenerating menu everytime.
50 | }.bind(this));
51 | };
52 |
53 | TrayMenu.prototype.clear = function () {
54 | for (var i = this.menu.items.length - 1; i >= 0; i--) {
55 | this.menu.removeAt(i);
56 | }
57 | };
58 |
59 | TrayMenu.prototype.addDefaults = function () {
60 | if (settings.token) {
61 | this.menu.append(new gui.MenuItem({ label: 'Sign Out', click: events.emit.bind(events, 'traymenu:signout') }));
62 | }
63 | this.menu.append(new gui.MenuItem({ label: 'Exit Gitter', click: quitApp }));
64 | };
65 |
66 | TrayMenu.prototype.build = function () {
67 | log.debug('Rebuilding tray menu');
68 | this.clear();
69 |
70 | if (this.unreads.length > 0) {
71 | events.emit('traymenu:unread', this.unreads.length);
72 | } else {
73 | events.emit('traymenu:read');
74 | }
75 |
76 | [{ collection: this.unreads, label: 'unread' },
77 | { collection: this.favourites, label: 'favourites' },
78 | { collection: this.recents, label: 'recents' }
79 | ].forEach(this.addSection.bind(this));
80 |
81 | this.addDefaults();
82 | events.emit('traymenu:updated');
83 | };
84 |
85 | TrayMenu.prototype.addSection = function (spec) {
86 | if (spec.collection.length <= 0) return;
87 |
88 | var appendRoom = function (room) {
89 | this.menu.append(this.toMenuItem(room));
90 | }.bind(this);
91 |
92 | this.menu.append(this.toLabel(spec.label));
93 | spec.collection.slice(0, 5).forEach(appendRoom);
94 | this.menu.append(new gui.MenuItem({ type: 'separator' }));
95 | };
96 |
97 | TrayMenu.prototype.toMenuItem = function (room) {
98 | return new gui.MenuItem({
99 | label: room.get('name'),
100 | click: function () {
101 | events.emit('traymenu:clicked', room.get('url'));
102 | }
103 | });
104 | };
105 |
106 | TrayMenu.prototype.toLabel = function (name) {
107 | return new gui.MenuItem({
108 | label: name.toUpperCase(),
109 | enabled: false
110 | });
111 | };
112 |
113 | TrayMenu.prototype.get = function () {
114 | return this.menu;
115 | };
116 |
117 | module.exports = TrayMenu;
118 |
--------------------------------------------------------------------------------
/windows/build.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 | var path = require('path');
3 | var exec = require('child_process').exec;
4 | var argv = require('yargs').argv;
5 | var Promise = require('bluebird');
6 |
7 | var nwappManifest = require('../nwapp/package.json');
8 | var appVersion = nwappManifest.version;
9 |
10 | var stat = Promise.promisify(fs.stat);
11 |
12 | var possibleProgramFilePaths = [
13 | 'C:\\Program Files (x86)',
14 | 'C:\\Program Files'
15 | ];
16 |
17 | var possibleSignToolPaths = [
18 | 'Windows Kits\\10\\bin\\x64\\signtool.exe',
19 | 'Windows Kits\\10\\bin\\x86\\signtool.exe',
20 | 'Windows Kits\\8.1\\bin\\x64\\signtool.exe',
21 | 'Windows Kits\\8.1\\bin\\x86\\signtool.exe',
22 | 'Windows Kits\\8.0\\bin\\x64\\signtool.exe',
23 | 'Windows Kits\\8.0\\bin\\x86\\signtool.exe',
24 | 'Microsoft SDKs\\Windows\\v7.1\\Bin\\signtool.exe',
25 | 'Microsoft SDKs\\Windows\\v7.1A\\Bin\\signtool.exe'
26 | ];
27 |
28 | var statResults = possibleProgramFilePaths.reduce(function(statResults, programFilePath) {
29 | return statResults.concat(possibleSignToolPaths.map(function(signToolPath) {
30 | var fullSignToolPath = path.join(programFilePath, signToolPath);
31 | return stat(fullSignToolPath)
32 | .then(function() {
33 | return fullSignToolPath;
34 | });
35 | }));
36 | }, []);
37 |
38 |
39 | Promise.any(statResults)
40 | .then(function(signToolPath) {
41 | console.log('signToolPath', signToolPath);
42 | var certPassword = argv.password || argv.p || '';
43 |
44 | var commandList = [
45 | '"' + signToolPath + '" sign /f "' + path.resolve(__dirname, '..\\certificates\\troupe-cert.pfx') + '" /p "' + certPassword + '" "' + path.resolve(__dirname, '..\\opt\\Gitter\\win32\\Gitter.exe') + '"',
46 | '"C:\\Program Files (x86)\\Inno Setup 5\\ISCC.exe" "' + path.resolve(__dirname, 'gitter.iss') + '"',
47 | '"' + signToolPath + '" sign /f "' + path.resolve(__dirname, '..\\certificates\\troupe-cert.pfx') + '" /p "' + certPassword + '" "' + path.resolve(__dirname, '..\\artefacts\\GitterSetup*') + '"',
48 | 'rename "' + path.resolve(__dirname, '..\\artefacts\\GitterSetup.exe') + '" "GitterSetup-' + appVersion + '.exe"',
49 | ];
50 |
51 | var commandRunner = function(command) {
52 | return new Promise(function(resolve, reject) {
53 | var child = exec(command, function(err, stdout, stderr) {
54 | resolve({
55 | command: command,
56 | stdout: stdout,
57 | stderr: stderr,
58 | error: err
59 | });
60 | });
61 |
62 | child.stdout.pipe(process.stdout);
63 | child.stderr.pipe(process.stderr);
64 | });
65 | };
66 |
67 | // Run the commands in series
68 | var commandChain = commandList.reduce(function(seriesCommandChain, command) {
69 | return seriesCommandChain.then(function(commandResult) {
70 | console.log('commandResult', commandResult);
71 | if(commandResult.command) {
72 | console.log('Done: ' + commandResult.command);
73 | console.log('--------------------------------------------');
74 | }
75 | if(commandResult.error) {
76 | console.log(commandResult.stderr);
77 | throw commandResult.error;
78 | }
79 |
80 | // Keep the chain going
81 | console.log('> ' + command);
82 | return commandRunner(command);
83 | });
84 | }, Promise.resolve({}));
85 |
86 | return commandChain
87 | .catch(function(err) {
88 | console.log('err', err, err.stack);
89 | });
90 | })
91 | .catch(Promise.AggregateError, function(err) {
92 | // ignore any failed checks
93 | console.log('Probably could not find the `signtool.exe`, try installing the Microsoft SDK and check that we are looking in the right place: open this file and look at `possibleSignToolPaths`');
94 | });
95 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 | # `3.1.0` - 2016-8-16
3 |
4 | - Add launch hidden feature to only appear in task bar/menu bar when started: #107 - Thanks @nicolas33
5 |
6 |
7 | # `3.0.3` - 2016-2-28
8 |
9 | - Fix app launch crash for non-logged in users on non-OSX platforms: #118
10 |
11 |
12 | # `3.0.2` - 2016-2-26
13 |
14 | - Fix copy/paste issue on GitHub login-view: #116
15 | - Remove duplicate Windows startup file (now handled in registry): #114
16 |
17 |
18 | # `3.0.1` - 2016-2-23
19 |
20 | - Update auto-update logic to check on start and every 24-hours after that
21 | - Add `packages -> osx` `package.json` manifest check entry to play more nicely with our platform checks.
22 | - Split out `linux32` `linux64` package manifest checks
23 |
24 | # `3.0.0` - 2016-2-23
25 |
26 | This was released but quickly pulled down due to not being happy with the legacy auto-update 5 minute polling notification when there is an update.
27 |
28 | - Update docs to add more detail on how to build
29 | - Upgrade to nw.js v0.12.3
30 | - Fixes issue where the org overview pages had an infinite loading spinner: https://github.com/gitterHQ/desktop/issues/102
31 | - Fixes `Cmd+M` shortcut to minimize: https://github.com/gitterHQ/desktop/issues/91
32 | - Fixes tray icon missing on Ubuntu: https://github.com/gitterHQ/desktop/issues/101
33 | - Fix focus issue on initial load with mac. Mac apps do not have focus when they start up
34 | - Update `notifier.js` so that it no longer plays the native sound, fixes https://github.com/gitterHQ/desktop/issues/99
35 | - Add avatars to notifications for Windows
36 | - I assume this covers as the official release of the nw.js version of the mac app: https://github.com/gitterHQ/desktop/issues/47
37 | - Generalize windows desktop build, see `./windows/README.md` and `./windows/build.js`. `./windows/build.bat` has also gotten some love but is deprecated and is only there until we are confident and settled with the node script.
38 | - Remove startup option from Windows installer because it did not obey whatever you set it to(always enabled launch at startup): https://github.com/gitterHQ/desktop/issues/106
39 | - Add context menu option to toggle launch on startup (tested on Mac and Windows): https://github.com/gitterHQ/desktop/issues/45
40 | - Add "Show in Menu bar" toggle (this option existed previously but didn't do anything)
41 | - Add `gulp check-path-safety-for-windows` to build flow so you don't get [unexpected blank screen when developing on Windows](https://github.com/gitterHQ/desktop/issues/59)
42 | - Update `auto-update` params/logic so we can actually use cli params outside of the autoupdate process.
43 | - `--update-url`: Specify custom URL to check and download updates from
44 | - `--passthrough-remote-debugging-url`: So you can debug the new instances we pop throughout the update process
45 |
46 | Here is the v3 PR (note that it also has v3.0.1 changes): https://github.com/gitterHQ/desktop/pull/100
47 |
48 | # `2.4.0` - 2015-09-04
49 | fixes for the autoupdate process
50 |
51 | # `2.3.3` - 2015-09-01
52 | fix for windows startup sometimes showing node-webkit ascii art
53 |
54 | # `2.3.2` - 2015-04-08
55 | updated realtime client
56 |
57 | # `2.3.1` - 2015-03-10
58 | fix for hard websocket crash
59 |
60 | # `2.3.0` - 2015-03-06
61 | stabilty fixes and first open source release
62 |
63 | # `2.2.5` - 2015-02-20
64 | realtime connection stabilty fixes
65 |
66 | # `2.2.4` - 2015-02-12
67 | fixes issues with tray and badge disconnecting
68 |
69 | # `2.2.3` - 2015-02-09
70 | fix Ubuntu libudev symlinking
71 |
72 | # `2.2.2` - 2015-02-04
73 | oauth fixes
74 |
75 | # `2.2.1` - 2015-02-04
76 | oauth fixes
77 |
78 | # `2.2.0` - 2015-02-04
79 | fixed autoupdater trashing the uninstaller
80 |
81 | # `2.1.3` - 2015-02-03
82 | autoupdate caching fixes
83 |
84 | # `2.1.2` - 2015-02-03
85 | autoupdate tweaks
86 |
87 | # `2.1.1` - 2015-02-03
88 | file menu tweaks
89 |
90 | # `2.1.0` - 2015-02-02
91 | added auto update
92 |
93 | # `2.0.4` - 2015-01-22
94 | alpha release for windows
95 |
96 | # `2.0.3` - 2015-01-22
97 | internal windows release
98 |
99 | # `2.0.2` - 2015-01-21
100 | internal windows release
101 |
102 | # `2.0.1` - 2015-01-21
103 | internal windows release
104 |
105 | # `2.0.0` - 2015-01-21
106 | internal windows release
107 |
--------------------------------------------------------------------------------
/nwapp/utils/notifier.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var log = require('loglevel');
4 |
5 | var Promise = require('bluebird');
6 | var os = require('os');
7 | var fs = require('fs-extra');
8 | var ensureDir = Promise.promisify(fs.ensureDir);
9 | var path = require('path');
10 | var temp = require('temp');
11 | var rimraf = require('rimraf');
12 | var request = require('request');
13 | var notifier = require('node-notifier');
14 | var objectAssign = require('object-assign');
15 |
16 | var settings = require('./settings');
17 | var events = require('./custom-events');
18 |
19 | var gui = window.require('nw.gui');
20 | var win = gui.Window.get();
21 |
22 | var CLIENT = require('./client-type');
23 | var SOUNDS_ITEMS = require('../components/sound-items');
24 |
25 |
26 | function playNotificationSound() {
27 | var audio = win.window.document.createElement('audio');
28 | var sound = SOUNDS_ITEMS[settings.notificationSound];
29 |
30 | if (!sound.path) {
31 | return;
32 | }
33 |
34 | audio.src = sound.path;
35 | audio.play();
36 | audio = null;
37 | }
38 |
39 | var checkActivationNotifierResponse = function(response) {
40 | // 'activated', `timeout'`
41 | var resp = response.trim().toLowerCase();
42 | if(resp.match(/^activate/)) {
43 | return true;
44 | }
45 |
46 | return false;
47 | };
48 |
49 |
50 | // Cache lookup
51 | var urlFilePathCacheMap = new Map();
52 | var tempAvatarDirectory = path.join(os.tmpDir(), 'gitter-notification-avatars_');
53 | log.info('Saving notification avatars at: ' + tempAvatarDirectory);
54 | var ensureTempAvatarDirectory = ensureDir(tempAvatarDirectory)
55 | .then(function() {
56 | return tempAvatarDirectory;
57 | });
58 |
59 | events.on('app:quit', function() {
60 | log.info('App is quiting, cleanup avatars');
61 | try {
62 | rimraf.sync(tempAvatarDirectory);
63 | }
64 | catch(err) {
65 | log.error('Something went wrong while cleaning up the avatars: ' + err.message + err.stack);
66 | }
67 | });
68 |
69 | function downloadAndCache(url) {
70 | return new Promise(function(resolve, reject) {
71 | var tempFileStream = temp.createWriteStream({
72 | dir: tempAvatarDirectory
73 | });
74 |
75 | tempFileStream
76 | .on('finish', function() {
77 | var tempFilePath = tempFileStream.path;
78 | // Store it on our map as a cache lookup
79 | urlFilePathCacheMap.set(url, tempFilePath);
80 | resolve(tempFilePath);
81 | })
82 | .on('error', function(err) {
83 | reject(err);
84 | });
85 |
86 | request({
87 | url: url,
88 | method: 'GET',
89 | timeout: 750
90 | })
91 | .on('error', function(err) {
92 | reject(err);
93 | })
94 | .pipe(tempFileStream);
95 | });
96 | }
97 | function getFilePathForHttpUrl(url) {
98 | return ensureTempAvatarDirectory.then(function() {
99 | var cachedFilePath = urlFilePathCacheMap.get(url);
100 | return cachedFilePath || downloadAndCache(url);
101 | })
102 | .then(function(filePath) {
103 | return filePath;
104 | });
105 | }
106 |
107 |
108 | var notifierDefaults = {
109 | title: '',
110 | message: '',
111 | // gitterHQ logo
112 | icon: 'https://avatars.githubusercontent.com/u/5990364?s=60',
113 | click: undefined
114 | };
115 |
116 | module.exports = function (options) {
117 | var opts = objectAssign({}, notifierDefaults, options);
118 |
119 | if (!settings.showNotifications) return;
120 |
121 | getFilePathForHttpUrl(opts.icon)
122 | // We are not worried if they couldn't fetch the image
123 | // as we fallback to the URL itself
124 | .catch(function(err) {
125 | log.error('Trouble getting avatar for notification' + err.message + err.stack);
126 | return opts.icon;
127 | })
128 | .then(function(imagePath) {
129 | playNotificationSound();
130 |
131 | // We use this instead of `new window.Notification(...)` because we can disable the native sound
132 | notifier.notify({
133 | title: opts.title,
134 | message: opts.message,
135 | icon: imagePath,
136 | // Only Notification Center or Windows Toasters
137 | // We handle the sound outside of this above
138 | // And we set to false because Windows 10 by default makes a separate sound
139 | sound: false,
140 | // wait with callback until user action is taken on notification
141 | wait: true
142 | }, function(err, response) {
143 | log.info('Notification response', response);
144 | var didActivate = checkActivationNotifierResponse(response);
145 |
146 | if(err) {
147 | // If you are seeing errors on each notification on Windows,
148 | // it isn't really a issue because everything works normally and
149 | // we are blocked until it gets fixed: https://github.com/mikaelbr/node-notifier/issues/97
150 | log.error('Problem with notification', err, err.stack);
151 | }
152 | if(didActivate && opts.click) {
153 | opts.click();
154 | }
155 | });
156 | })
157 | .catch(function(err) {
158 | log.error('Problem initializing notification', err, err.stack);
159 | });
160 |
161 | };
162 |
--------------------------------------------------------------------------------
/osx/create-dmg/support/dmg-license.py:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env python
2 | """
3 | This script adds a license file to a DMG. Requires Xcode and a plain ascii text
4 | license file.
5 | Obviously only runs on a Mac.
6 |
7 | Copyright (C) 2011-2013 Jared Hobbs
8 |
9 | Permission is hereby granted, free of charge, to any person obtaining a copy
10 | of this software and associated documentation files (the "Software"), to deal
11 | in the Software without restriction, including without limitation the rights
12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | copies of the Software, and to permit persons to whom the Software is
14 | furnished to do so, subject to the following conditions:
15 |
16 | The above copyright notice and this permission notice shall be included in
17 | all copies or substantial portions of the Software.
18 |
19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | THE SOFTWARE.
26 | """
27 | import os
28 | import sys
29 | import tempfile
30 | import optparse
31 |
32 |
33 | class Path(str):
34 | def __enter__(self):
35 | return self
36 |
37 | def __exit__(self, type, value, traceback):
38 | os.unlink(self)
39 |
40 |
41 | def mktemp(dir=None, suffix=''):
42 | (fd, filename) = tempfile.mkstemp(dir=dir, suffix=suffix)
43 | os.close(fd)
44 | return Path(filename)
45 |
46 |
47 | def main(options, args):
48 | dmgFile, license = args
49 | with mktemp('.') as tmpFile:
50 | with open(tmpFile, 'w') as f:
51 | f.write("""data 'TMPL' (128, "LPic") {
52 | $"1344 6566 6175 6C74 204C 616E 6775 6167"
53 | $"6520 4944 4457 5244 0543 6F75 6E74 4F43"
54 | $"4E54 042A 2A2A 2A4C 5354 430B 7379 7320"
55 | $"6C61 6E67 2049 4444 5752 441E 6C6F 6361"
56 | $"6C20 7265 7320 4944 2028 6F66 6673 6574"
57 | $"2066 726F 6D20 3530 3030 4457 5244 1032"
58 | $"2D62 7974 6520 6C61 6E67 7561 6765 3F44"
59 | $"5752 4404 2A2A 2A2A 4C53 5445"
60 | };
61 |
62 | data 'LPic' (5000) {
63 | $"0000 0002 0000 0000 0000 0000 0004 0000"
64 | };
65 |
66 | data 'STR#' (5000, "English buttons") {
67 | $"0006 0D45 6E67 6C69 7368 2074 6573 7431"
68 | $"0541 6772 6565 0844 6973 6167 7265 6505"
69 | $"5072 696E 7407 5361 7665 2E2E 2E7A 4966"
70 | $"2079 6F75 2061 6772 6565 2077 6974 6820"
71 | $"7468 6520 7465 726D 7320 6F66 2074 6869"
72 | $"7320 6C69 6365 6E73 652C 2063 6C69 636B"
73 | $"2022 4167 7265 6522 2074 6F20 6163 6365"
74 | $"7373 2074 6865 2073 6F66 7477 6172 652E"
75 | $"2020 4966 2079 6F75 2064 6F20 6E6F 7420"
76 | $"6167 7265 652C 2070 7265 7373 2022 4469"
77 | $"7361 6772 6565 2E22"
78 | };
79 |
80 | data 'STR#' (5002, "English") {
81 | $"0006 0745 6E67 6C69 7368 0541 6772 6565"
82 | $"0844 6973 6167 7265 6505 5072 696E 7407"
83 | $"5361 7665 2E2E 2E7B 4966 2079 6F75 2061"
84 | $"6772 6565 2077 6974 6820 7468 6520 7465"
85 | $"726D 7320 6F66 2074 6869 7320 6C69 6365"
86 | $"6E73 652C 2070 7265 7373 2022 4167 7265"
87 | $"6522 2074 6F20 696E 7374 616C 6C20 7468"
88 | $"6520 736F 6674 7761 7265 2E20 2049 6620"
89 | $"796F 7520 646F 206E 6F74 2061 6772 6565"
90 | $"2C20 7072 6573 7320 2244 6973 6167 7265"
91 | $"6522 2E"
92 | };\n\n""")
93 | with open(license, 'r') as l:
94 | kind = 'RTF ' if license.lower().endswith('.rtf') else 'TEXT'
95 | f.write('data \'%s\' (5000, "English") {\n' % kind)
96 | def escape(s):
97 | return s.strip().replace('\\', '\\\\').replace('"', '\\"')
98 |
99 | for line in l:
100 | if len(line) < 1000:
101 | f.write(' "' + escape(line) + '\\n"\n')
102 | else:
103 | for liner in line.split('.'):
104 | f.write(' "' + escape(liner) + '. \\n"\n')
105 | f.write('};\n\n')
106 | f.write("""data 'styl' (5000, "English") {
107 | $"0003 0000 0000 000C 0009 0014 0000 0000"
108 | $"0000 0000 0000 0000 0027 000C 0009 0014"
109 | $"0100 0000 0000 0000 0000 0000 002A 000C"
110 | $"0009 0014 0000 0000 0000 0000 0000"
111 | };\n""")
112 | os.system('hdiutil unflatten -quiet "%s"' % dmgFile)
113 | ret = os.system('%s -a %s -o "%s"' %
114 | (options.rez, tmpFile, dmgFile))
115 | os.system('hdiutil flatten -quiet "%s"' % dmgFile)
116 | if options.compression is not None:
117 | os.system('cp %s %s.temp.dmg' % (dmgFile, dmgFile))
118 | os.remove(dmgFile)
119 | if options.compression == "bz2":
120 | os.system('hdiutil convert %s.temp.dmg -format UDBZ -o %s' %
121 | (dmgFile, dmgFile))
122 | elif options.compression == "gz":
123 | os.system('hdiutil convert %s.temp.dmg -format ' % dmgFile +
124 | 'UDZO -imagekey zlib-devel=9 -o %s' % dmgFile)
125 | os.remove('%s.temp.dmg' % dmgFile)
126 | if ret == 0:
127 | print "Successfully added license to '%s'" % dmgFile
128 | else:
129 | print "Failed to add license to '%s'" % dmgFile
130 |
131 | if __name__ == '__main__':
132 | parser = optparse.OptionParser()
133 | parser.set_usage("""%prog [OPTIONS]
134 | This program adds a software license agreement to a DMG file.
135 | It requires Xcode and either a plain ascii text
136 | or a with the RTF contents.
137 |
138 | See --help for more details.""")
139 | parser.add_option(
140 | '--rez',
141 | '-r',
142 | action='store',
143 | default='/Applications/Xcode.app/Contents/Developer/Tools/Rez',
144 | help='The path to the Rez tool. Defaults to %default'
145 | )
146 | parser.add_option(
147 | '--compression',
148 | '-c',
149 | action='store',
150 | choices=['bz2', 'gz'],
151 | default=None,
152 | help='Optionally compress dmg using specified compression type. '
153 | 'Choices are bz2 and gz.'
154 | )
155 | options, args = parser.parse_args()
156 | cond = len(args) != 2
157 | if not os.path.exists(options.rez):
158 | print 'Failed to find Rez at "%s"!\n' % options.rez
159 | cond = True
160 | if cond:
161 | parser.print_usage()
162 | sys.exit(1)
163 | main(options, args)
164 |
--------------------------------------------------------------------------------
/osx/create-dmg/create-dmg:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 |
3 | # Create a read-only disk image of the contents of a folder
4 |
5 | set -e;
6 |
7 | function pure_version() {
8 | echo '1.0.0.2'
9 | }
10 |
11 | function version() {
12 | echo "create-dmg $(pure_version)"
13 | }
14 |
15 | function usage() {
16 | version
17 | echo "Creates a fancy DMG file."
18 | echo "Usage: $(basename $0) options... image.dmg source_folder"
19 | echo "All contents of source_folder will be copied into the disk image."
20 | echo "Options:"
21 | echo " --volname name"
22 | echo " set volume name (displayed in the Finder sidebar and window title)"
23 | echo " --volicon icon.icns"
24 | echo " set volume icon"
25 | echo " --background pic.png"
26 | echo " set folder background image (provide png, gif, jpg)"
27 | echo " --window-pos x y"
28 | echo " set position the folder window"
29 | echo " --window-size width height"
30 | echo " set size of the folder window"
31 | echo " --text-size text_size"
32 | echo " set window text size (10-16)"
33 | echo " --icon-size icon_size"
34 | echo " set window icons size (up to 128)"
35 | echo " --icon file_name x y"
36 | echo " set position of the file's icon"
37 | echo " --hide-extension file_name"
38 | echo " hide the extension of file"
39 | echo " --custom-icon file_name custom_icon_or_sample_file x y"
40 | echo " set position and custom icon"
41 | echo " --app-drop-link x y"
42 | echo " make a drop link to Applications, at location x,y"
43 | echo " --eula eula_file"
44 | echo " attach a license file to the dmg"
45 | echo " --no-internet-enable"
46 | echo " disable automatic mount©"
47 | echo " --version show tool version number"
48 | echo " -h, --help display this help"
49 | exit 0
50 | }
51 |
52 | WINX=10
53 | WINY=60
54 | WINW=500
55 | WINH=350
56 | ICON_SIZE=128
57 | TEXT_SIZE=16
58 |
59 | while test "${1:0:1}" = "-"; do
60 | case $1 in
61 | --volname)
62 | VOLUME_NAME="$2"
63 | shift; shift;;
64 | --volicon)
65 | VOLUME_ICON_FILE="$2"
66 | shift; shift;;
67 | --background)
68 | BACKGROUND_FILE="$2"
69 | BACKGROUND_FILE_NAME="$(basename $BACKGROUND_FILE)"
70 | BACKGROUND_CLAUSE="set background picture of opts to file \".background:$BACKGROUND_FILE_NAME\""
71 | REPOSITION_HIDDEN_FILES_CLAUSE="set position of every item to {theBottomRightX + 100, 100}"
72 | shift; shift;;
73 | --icon-size)
74 | ICON_SIZE="$2"
75 | shift; shift;;
76 | --text-size)
77 | TEXT_SIZE="$2"
78 | shift; shift;;
79 | --window-pos)
80 | WINX=$2; WINY=$3
81 | shift; shift; shift;;
82 | --window-size)
83 | WINW=$2; WINH=$3
84 | shift; shift; shift;;
85 | --icon)
86 | POSITION_CLAUSE="${POSITION_CLAUSE}set position of item \"$2\" to {$3, $4}
87 | "
88 | shift; shift; shift; shift;;
89 | --hide-extension)
90 | HIDING_CLAUSE="${HIDING_CLAUSE}set the extension hidden of item \"$2\" to true"
91 | shift; shift;;
92 | --custom-icon)
93 | shift; shift; shift; shift; shift;;
94 | -h | --help)
95 | usage;;
96 | --version)
97 | version; exit 0;;
98 | --pure-version)
99 | pure_version; exit 0;;
100 | --app-drop-link)
101 | APPLICATION_LINK=$2
102 | APPLICATION_CLAUSE="set position of item \"Applications\" to {$2, $3}
103 | "
104 | shift; shift; shift;;
105 | --eula)
106 | EULA_RSRC=$2
107 | shift; shift;;
108 | --no-internet-enable)
109 | NOINTERNET=1
110 | shift;;
111 | -*)
112 | echo "Unknown option $1. Run with --help for help."
113 | exit 1;;
114 | esac
115 | done
116 |
117 | test -z "$2" && {
118 | echo "Not enough arguments. Invoke with --help for help."
119 | exit 1
120 | }
121 |
122 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
123 | DMG_PATH="$1"
124 | DMG_DIRNAME="$(dirname "$DMG_PATH")"
125 | DMG_DIR="$(cd "$DMG_DIRNAME" > /dev/null; pwd)"
126 | DMG_NAME="$(basename "$DMG_PATH")"
127 | DMG_TEMP_NAME="$DMG_DIR/rw.${DMG_NAME}"
128 | SRC_FOLDER="$(cd "$2" > /dev/null; pwd)"
129 | test -z "$VOLUME_NAME" && VOLUME_NAME="$(basename "$DMG_PATH" .dmg)"
130 |
131 | AUX_PATH="$SCRIPT_DIR/support"
132 |
133 | test -d "$AUX_PATH" || {
134 | echo "Cannot find support directory: $AUX_PATH"
135 | exit 1
136 | }
137 |
138 | if [ -f "$SRC_FOLDER/.DS_Store" ]; then
139 | echo "Deleting any .DS_Store in source folder"
140 | rm "$SRC_FOLDER/.DS_Store"
141 | fi
142 |
143 | # Create the image
144 | echo "Creating disk image..."
145 | test -f "${DMG_TEMP_NAME}" && rm -f "${DMG_TEMP_NAME}"
146 | ACTUAL_SIZE=`du -sm "$SRC_FOLDER" | sed -e 's/ .*//g'`
147 | DISK_IMAGE_SIZE=$(expr $ACTUAL_SIZE + 20)
148 | hdiutil create -srcfolder "$SRC_FOLDER" -volname "${VOLUME_NAME}" -fs HFS+ -fsargs "-c c=64,a=16,e=16" -format UDRW -size ${DISK_IMAGE_SIZE}m "${DMG_TEMP_NAME}"
149 |
150 | # mount it
151 | echo "Mounting disk image..."
152 | MOUNT_DIR="/Volumes/${VOLUME_NAME}"
153 |
154 | # try unmount dmg if it was mounted previously (e.g. developer mounted dmg, installed app and forgot to unmount it)
155 | echo "Unmounting disk image..."
156 | DEV_NAME=$(hdiutil info | egrep '^/dev/' | sed 1q | awk '{print $1}')
157 | test -d "${MOUNT_DIR}" && hdiutil detach "${DEV_NAME}"
158 |
159 | echo "Mount directory: $MOUNT_DIR"
160 | DEV_NAME=$(hdiutil attach -readwrite -noverify -noautoopen "${DMG_TEMP_NAME}" | egrep '^/dev/' | sed 1q | awk '{print $1}')
161 | echo "Device name: $DEV_NAME"
162 |
163 | if ! test -z "$BACKGROUND_FILE"; then
164 | echo "Copying background file..."
165 | test -d "$MOUNT_DIR/.background" || mkdir "$MOUNT_DIR/.background"
166 | cp "$BACKGROUND_FILE" "$MOUNT_DIR/.background/$BACKGROUND_FILE_NAME"
167 | fi
168 |
169 | if ! test -z "$APPLICATION_LINK"; then
170 | echo "making link to Applications dir"
171 | echo $MOUNT_DIR
172 | ln -s /Applications "$MOUNT_DIR/Applications"
173 | fi
174 |
175 | if ! test -z "$VOLUME_ICON_FILE"; then
176 | echo "Copying volume icon file '$VOLUME_ICON_FILE'..."
177 | cp "$VOLUME_ICON_FILE" "$MOUNT_DIR/.VolumeIcon.icns"
178 | SetFile -c icnC "$MOUNT_DIR/.VolumeIcon.icns"
179 | fi
180 |
181 | # run applescript
182 | APPLESCRIPT=$(mktemp -t createdmg)
183 | cat "$AUX_PATH/template.applescript" | sed -e "s/WINX/$WINX/g" -e "s/WINY/$WINY/g" -e "s/WINW/$WINW/g" -e "s/WINH/$WINH/g" -e "s/BACKGROUND_CLAUSE/$BACKGROUND_CLAUSE/g" -e "s/REPOSITION_HIDDEN_FILES_CLAUSE/$REPOSITION_HIDDEN_FILES_CLAUSE/g" -e "s/ICON_SIZE/$ICON_SIZE/g" -e "s/TEXT_SIZE/$TEXT_SIZE/g" | perl -pe "s/POSITION_CLAUSE/$POSITION_CLAUSE/g" | perl -pe "s/APPLICATION_CLAUSE/$APPLICATION_CLAUSE/g" | perl -pe "s/HIDING_CLAUSE/$HIDING_CLAUSE/" >"$APPLESCRIPT"
184 |
185 | echo "Running Applescript: /usr/bin/osascript \"${APPLESCRIPT}\" \"${VOLUME_NAME}\""
186 | "/usr/bin/osascript" "${APPLESCRIPT}" "${VOLUME_NAME}" || true
187 | echo "Done running the applescript..."
188 | sleep 4
189 |
190 | rm "$APPLESCRIPT"
191 |
192 | # make sure it's not world writeable
193 | echo "Fixing permissions..."
194 | chmod -Rf go-w "${MOUNT_DIR}" &> /dev/null || true
195 | echo "Done fixing permissions."
196 |
197 | # make the top window open itself on mount:
198 | echo "Blessing started"
199 | bless --folder "${MOUNT_DIR}" --openfolder "${MOUNT_DIR}"
200 | echo "Blessing finished"
201 |
202 | if ! test -z "$VOLUME_ICON_FILE"; then
203 | # tell the volume that it has a special file attribute
204 | SetFile -a C "$MOUNT_DIR"
205 | fi
206 |
207 | # unmount
208 | echo "Unmounting disk image..."
209 | hdiutil detach "${DEV_NAME}"
210 |
211 | # compress image
212 | echo "Compressing disk image..."
213 | hdiutil convert "${DMG_TEMP_NAME}" -format UDZO -imagekey zlib-level=9 -o "${DMG_DIR}/${DMG_NAME}"
214 | rm -f "${DMG_TEMP_NAME}"
215 |
216 | # adding EULA resources
217 | if [ ! -z "${EULA_RSRC}" -a "${EULA_RSRC}" != "-null-" ]; then
218 | echo "adding EULA resources"
219 | "${AUX_PATH}/dmg-license.py" "${DMG_DIR}/${DMG_NAME}" "${EULA_RSRC}"
220 | fi
221 |
222 | if [ ! -z "${NOINTERNET}" -a "${NOINTERNET}" == 1 ]; then
223 | echo "not setting 'internet-enable' on the dmg"
224 | else
225 | hdiutil internet-enable -yes "${DMG_DIR}/${DMG_NAME}"
226 | fi
227 |
228 | echo "Disk image done"
229 | exit 0
230 |
--------------------------------------------------------------------------------
/nwapp/utils/auto-update.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gui = window.require('nw.gui');
4 | var manifest = require('../package.json');
5 | var log = require('loglevel');
6 | var argv = require('yargs')(gui.App.argv).argv;
7 | var os = require('./client-type');
8 |
9 | var Promise = require('bluebird');
10 | var urlParse = require('url-parse');
11 | var rimraf = require('rimraf');
12 | var path = require('path');
13 | var temp = require('temp');
14 | var request = require('request');
15 | var extract = require('extract-zip');
16 | var Updater = require('node-webkit-updater');
17 |
18 | var notifier = require('./notifier');
19 | var quitApp = require('./quit-app');
20 |
21 |
22 | var getPlatform = function() {
23 | var platformString = os;
24 | if(os === 'linux') {
25 | platformString += (process.arch === 'ia32' ? '32' : '64');
26 | }
27 |
28 | return platformString;
29 | };
30 |
31 |
32 | // 10 minutes
33 | var notifyUpdatePollingRate = 10 * 60 * 1000;
34 | // 24 hours
35 | var notifyUpdateIntervalTime = 24 * 60 * 60 * 1000;
36 | var lastNotifyUpdateTime = -1;
37 | var scheduleUpdateNotifyCallback = function(callback) {
38 | var cb = function() {
39 | callback();
40 | lastNotifyUpdateTime = Date.now();
41 | };
42 |
43 | cb();
44 | setInterval(function() {
45 | var timeSinceNotify = Date.now() - lastNotifyUpdateTime;
46 |
47 | if(timeSinceNotify >= notifyUpdateIntervalTime) {
48 | cb();
49 | }
50 | }, notifyUpdatePollingRate);
51 | };
52 |
53 |
54 | // You can change the place we use to check and download updates with this CLI parameter `--update-url=192.168.0.58:3010`
55 | // We use this for testing a release
56 | var updateUrlOption = argv['update-url'] || 'https://update.gitter.im';
57 | var transposeUpdateUrl = function(targetUrl) {
58 | targetUrl = targetUrl || '';
59 | var parsedTargetUrl = urlParse(targetUrl);
60 | var parsedUpdateUrl = urlParse(updateUrlOption);
61 | parsedTargetUrl.protocol = parsedUpdateUrl.protocol || 'http:';
62 | parsedTargetUrl.auth = parsedUpdateUrl.auth;
63 | parsedTargetUrl.hostname = parsedUpdateUrl.hostname;
64 | parsedTargetUrl.port = parsedUpdateUrl.port;
65 |
66 | var newUrl = parsedTargetUrl.toString();
67 | return newUrl;
68 | };
69 |
70 |
71 | var MANIFEST_URLS = {
72 | win: 'https://update.gitter.im/win/package.json',
73 | osx: 'https://update.gitter.im/osx/package.json',
74 | linux32: 'https://update.gitter.im/linux32/package.json',
75 | linux64: 'https://update.gitter.im/linux64/package.json'
76 | };
77 | Object.keys(MANIFEST_URLS).forEach(function(key) {
78 | MANIFEST_URLS[key] = transposeUpdateUrl(MANIFEST_URLS[key]);
79 | });
80 |
81 |
82 |
83 | var currentManifest = {
84 | name: manifest.name,
85 | version: manifest.version,
86 | manifestUrl: MANIFEST_URLS[getPlatform()]
87 | };
88 |
89 | var updater = new Updater(currentManifest);
90 |
91 |
92 | // Because nw.js doesn't pass along the `--remote-debugging-port` to the child app
93 | // we need to use a separate variable name
94 | var passthroughRemoteDebuggingPort = function() {
95 | // If we were debugging before, be sure to allow them to see the install
96 | var remoteDebuggingPort = argv['passthrough-remote-debugging-port'];
97 | if(remoteDebuggingPort) {
98 | log.info('You will be able to monitor the new app launch with the same remote debugging port (just refresh to see new instances)', remoteDebuggingPort);
99 | return ['--remote-debugging-port=' + remoteDebuggingPort, '--passthrough-remote-debugging-port=' + remoteDebuggingPort];
100 | }
101 |
102 | return [];
103 | };
104 |
105 |
106 |
107 | function download(url, cb) {
108 | var tempFileStream = temp.createWriteStream('gitter-update-zip');
109 |
110 | tempFileStream.on('error', cb)
111 | .on('finish', function() {
112 | cb(null, tempFileStream.path);
113 | });
114 | log.info('downloading update from', url, 'to', tempFileStream.path);
115 |
116 | request.get(url)
117 | .on('error', cb)
118 | .pipe(tempFileStream);
119 | }
120 |
121 | function unpack(zipPath, cb) {
122 | var dir = temp.path('gitter-update-app');
123 |
124 | log.info('unpacking update from', zipPath, 'to', dir);
125 |
126 | extract(zipPath, { dir: dir }, function(err) {
127 | if (err) return cb(err);
128 |
129 | cb(null, dir);
130 | });
131 | }
132 |
133 | function getUpdate(manifest, cb) {
134 | // node-webkit-updater logic from hell
135 | var platform = getPlatform();
136 | var newPackageMap = manifest.packages[platform];
137 | if(!newPackageMap) {
138 | // Welp, something is not going right, we couldn't find their target in the new
139 | // manifest, so tell them to go get it manually
140 | notifyUpdateFallback();
141 |
142 | var availablePlatformsString = Object.keys(manifest.packages)
143 | .map(function(key) {
144 | return '`' + key + '`';
145 | }, '')
146 | .join(', ');
147 |
148 | log.error('We couldn\'t find a package associated with your platform: `' + platform + '`, in the new manifest. Here what was available: ' + availablePlatformsString);
149 | }
150 | else {
151 | var updateBundleUrl = transposeUpdateUrl(newPackageMap.url);
152 |
153 | log.info('Downloading bundle from:', updateBundleUrl);
154 | download(updateBundleUrl, function(err, zipPath) {
155 | if (err) return cb(err);
156 |
157 | unpack(zipPath, function(err, appDir) {
158 | if (err) return cb(err);
159 |
160 | // clean up the zip, but we have no way to track
161 | // the temp appDir, so that cant be cleaned
162 | rimraf(zipPath, function(err) {
163 | if (err) return cb(err);
164 |
165 | cb(null, appDir);
166 | });
167 | });
168 | });
169 | }
170 | }
171 |
172 | function notifyUpdateFallback(version) {
173 | function notify() {
174 | notifier({
175 | title: 'Gitter ' + version + ' Available',
176 | message: 'Head over to gitter.im/apps to update.'
177 | });
178 | }
179 |
180 | scheduleUpdateNotifyCallback(notify);
181 | }
182 |
183 | function notifyWinOsxUser(version, newAppExecutable) {
184 | function notify() {
185 | notifier({
186 | title: 'Gitter ' + version + ' Available',
187 | message: 'Click to restart and apply update.',
188 | click: function() {
189 | log.info('Update notification clicked');
190 |
191 | var installerArgs = [
192 | '--current-install-path=' + updater.getAppPath(),
193 | '--new-executable=' + updater.getAppExec()
194 | ];
195 | // If we were debugging before, be sure to allow them to see the install
196 | installerArgs = installerArgs.concat(passthroughRemoteDebuggingPort());
197 |
198 | log.info('Starting new app to install itself', newAppExecutable, installerArgs);
199 | updater.runInstaller(newAppExecutable, installerArgs, {});
200 |
201 | log.info('Quitting outdated app');
202 | quitApp();
203 | }
204 | });
205 | }
206 |
207 | scheduleUpdateNotifyCallback(notify);
208 | }
209 |
210 |
211 | function poll() {
212 |
213 | function update() {
214 | log.info('checking', currentManifest.manifestUrl, 'for app updates');
215 | updater.checkNewVersion(function (err, newVersionExists, newManifest) {
216 | if (err) {
217 | log.error('request for app update manifest failed', err);
218 | return tryAgainLater();
219 | }
220 |
221 | if (!newVersionExists) {
222 | log.info('app currently at latest version');
223 | return tryAgainLater();
224 | }
225 |
226 | // Update available!
227 | var version = newManifest.version;
228 | log.info('app update ' + version + ' available');
229 |
230 | if (os === 'linux') {
231 | // linux cannot autoupdate (yet)
232 | return notifyUpdateFallback(version);
233 | }
234 |
235 | getUpdate(newManifest, function(err, newAppDir) {
236 | if (err) {
237 | log.error('app update ' + version + ' failed to download and unpack', err.message);
238 | return tryAgainLater();
239 | }
240 |
241 | var executable = newManifest.name + (os === 'win' ? '.exe' : '.app');
242 | var newAppExecutable = path.join(newAppDir, executable);
243 |
244 | return notifyWinOsxUser(version, newAppExecutable);
245 | });
246 | });
247 | }
248 |
249 | // A debug way to trigger another update
250 | window.debugUpdateGitter = update;
251 |
252 | // polling with setInterval can cause multiple downloads
253 | // to occur if left unattended, so its best to wait for the updater
254 | // to finish each poll before triggering a new request.
255 | function tryAgainLater() {
256 | log.info('trying app update again in 30 mins');
257 | setTimeout(update, 30 * 60 * 1000);
258 | }
259 |
260 | // update() retries on failure, so only call once.
261 | update();
262 | }
263 |
264 | function overwriteOldApp(oldAppDir, executable) {
265 | updater.install(oldAppDir, function(err) {
266 | if (err) {
267 | log.error('update failed, shutting down installer', err.stack);
268 | return quitApp();
269 | }
270 |
271 | // The recommended updater.run(execPath, null) [1] doesn't work properly on Windows.
272 | // It spawns a new child process but it doesn't detach so when the installer app quits the new version also quits. :poop:
273 | // [1] https://github.com/edjafarov/node-webkit-updater/blob/master/examples/basic.js#L29
274 | // https://github.com/edjafarov/node-webkit-updater/blob/master/app/updater.js#L404-L416
275 | var newAppArgs = [];
276 | // If we were debugging before, be sure to allow them to see the new app
277 | newAppArgs = newAppArgs.concat(passthroughRemoteDebuggingPort());
278 | log.info('starting new version', executable, newAppArgs);
279 | updater.run(executable, newAppArgs, {});
280 |
281 | // wait for new version to get going...
282 | setTimeout(function() {
283 | log.info('shutting down installer');
284 | quitApp();
285 | }, 5000);
286 | });
287 | }
288 |
289 | module.exports = {
290 | poll: poll,
291 | overwriteOldApp: overwriteOldApp
292 | };
293 |
--------------------------------------------------------------------------------
/nwapp/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var log = require('loglevel');
4 | log.setLevel('debug');
5 |
6 | // @CONST
7 | var CLIENT = require('./utils/client-type');
8 |
9 | var manifest = require('./package.json');
10 | var gui = require('nw.gui');
11 | var pkg = require('./package.json');
12 | var Gitter = require('gitter-realtime-client');
13 | var os = require('os');
14 | var path = require('path');
15 |
16 | var argv = require('yargs')(gui.App.argv).argv;
17 | var Promise = require('bluebird');
18 | var semver = require('semver');
19 | var autoUpdate = require('./utils/auto-update');
20 | var AutoLaunch = require('auto-launch');
21 | var fs = require('fs');
22 | var deleteFile = Promise.promisify(fs.unlink);
23 |
24 | var settings = require('./utils/settings');
25 | var notifier = require('./utils/notifier');
26 | var events = require('./utils/custom-events');
27 | var quitApp = require('./utils/quit-app');
28 |
29 | // components
30 | var MENU_ITEMS = require('./components/menu-items');
31 | var CustomTray = require('./components/tray');
32 | var CustomMenu = require('./components/menu');
33 | var TrayMenu = require('./components/tray-menu');
34 |
35 | var LoginView = require('./lib/login-view');
36 |
37 | var checkFileExistSync = function(target) {
38 | try {
39 | return !!fs.statSync(target);
40 | }
41 | catch(err) {
42 | // swallow the error
43 | }
44 |
45 | return false;
46 | };
47 |
48 | process.on('uncaughtException', function (err) {
49 | log.error('Caught exception: ' + err);
50 | log.error(err.stack);
51 | process.exit(1);
52 | });
53 |
54 | var win;
55 | var mainWindow; // this is the chat window (logged in)
56 | var mainWindowFocused = false; // Focus tracker. NWK doesn't have a way to query if the window is in focus
57 | var loginView; // log in form
58 |
59 |
60 |
61 | // Remove the legacy v2.4.0- startup location
62 | // so that we don't have duplicates
63 | // FIXME: remove after August 2016
64 | if(CLIENT === 'win') {
65 | var windowsStartupPathPiece = 'Microsoft/Windows/Start Menu/Programs/Startup';
66 | var startupBasePaths = [];
67 | if(process.env.SYSTEMDRIVE) {
68 | startupBasePaths.push(path.join(process.env.SYSTEMDRIVE, './ProgramData', windowsStartupPathPiece));
69 | }
70 | if(process.env.APPDATA) {
71 | startupBasePaths.push(path.join(process.env.APPDATA, windowsStartupPathPiece));
72 | }
73 | if(process.env.LOCALAPPDATA) {
74 | startupBasePaths.push(path.join(process.env.LOCALAPPDATA, windowsStartupPathPiece));
75 | }
76 |
77 | startupBasePaths.forEach(function(startupBasePath) {
78 | var startupPath = path.join(startupBasePath, './Gitter.lnk');
79 |
80 | deleteFile(startupPath)
81 | .then(function() {
82 | log.info('Deleted legacy startup file:', startupPath);
83 | })
84 | .catch(function(err) {
85 | // Ignore the file not being found
86 | if(err.code !== 'ENOENT') {
87 | log.error('Failed to cleanup legacy startup file:', startupPath, err, err.stack);
88 | }
89 | });
90 | });
91 | }
92 | var autoLauncher = new AutoLaunch({
93 | name: 'Gitter'
94 | });
95 |
96 |
97 | (function () {
98 | log.info('execPath:', process.execPath);
99 | log.info('argv:', argv);
100 | log.info('version:', pkg.version);
101 |
102 | var legacyCurrentInstallPath;
103 | var legacyNewUpdaterExecutablePath;
104 | if (argv._.length === 2 && checkFileExistSync(argv._[0]) && checkFileExistSync(argv._[1])) {
105 | // This only happens if we have a pre v3.0.0 desktop app attempting to update to v3+
106 | // FIXME: remove after August 2016
107 | legacyCurrentInstallPath = argv._[0];
108 | legacyNewUpdaterExecutablePath = argv._[1];
109 | }
110 |
111 | var currentInstallPath = argv['current-install-path'] || legacyCurrentInstallPath;
112 | var newUpdaterExecutablePath = argv['new-executable'] || legacyNewUpdaterExecutablePath;
113 |
114 | var hasGoodParamsToUpdate = currentInstallPath && newUpdaterExecutablePath;
115 | log.info('Are we going into update mode? ' + (hasGoodParamsToUpdate ? 'Yes' : 'No') + '.', currentInstallPath, newUpdaterExecutablePath);
116 | if(hasGoodParamsToUpdate) {
117 | log.info('I am a new app in a temp dir');
118 | log.info('I will overwrite the old app with myself and then restart it');
119 |
120 | return autoUpdate.overwriteOldApp(currentInstallPath, newUpdaterExecutablePath);
121 | }
122 |
123 | initGUI(); // intialises menu and tray, setting up event listeners for both
124 | initApp();
125 | autoUpdate.poll();
126 |
127 | autoLauncher.isEnabled(function(enabled) {
128 | if(enabled !== settings.launchOnStartup) {
129 | autoLauncher[(settings.launchOnStartup ? 'enable' : 'disable')](function(err) {
130 | if(err) {
131 | throw err;
132 | }
133 | });
134 | }
135 | });
136 |
137 | })();
138 |
139 | // reopen window when they are all closed
140 | function reopen() {
141 | if (!mainWindow) {
142 | if (!settings.token) {
143 | return showAuth();
144 | }
145 | return showLoggedInWindow();
146 | }
147 | }
148 |
149 | function setupTray(win) {
150 | var customTray = new CustomTray();
151 | win.tray = customTray.get();
152 |
153 | var roomMenu = new TrayMenu();
154 | win.tray.menu = roomMenu.get();
155 |
156 | // FIXME: temporary fix, needs to be repainted
157 | events.on('traymenu:updated', function() {
158 | win.tray.menu = roomMenu.get();
159 | });
160 |
161 | // Set unread badge
162 | events.on('traymenu:unread', function(unreadCount) {
163 | win.setBadgeLabel(unreadCount.toString());
164 | });
165 |
166 | // Remove badge
167 | events.on('traymenu:read', function() {
168 | win.setBadgeLabel('');
169 | });
170 |
171 | if (CLIENT !== 'osx') {
172 | win.tray.on('click', reopen);
173 | }
174 | }
175 |
176 | // initialises and adds listeners to the top level components of the UI
177 | function initGUI() {
178 | log.trace('initGUI()');
179 | win = gui.Window.get();
180 |
181 | // Set up tray(OSX)/menu bar(Windows)
182 | if(settings.showInMacMenuBar) {
183 | setupTray(win);
184 | }
185 |
186 | events.on('settings:change:showInMacMenuBar', function(newValue) {
187 | if(newValue) {
188 | setupTray(win);
189 | }
190 | else if(win.tray) {
191 | win.tray.remove();
192 | }
193 | });
194 |
195 | gui.App.on('reopen', reopen); // When someone clicks on the dock icon, show window again.
196 |
197 | win.on('close', function (evt) {
198 | log.trace('win:close');
199 | if (evt === 'quit') {
200 | quitApp();
201 | } else {
202 | this.close(true);
203 | }
204 | });
205 | }
206 |
207 | // establishes faye connection and manages signing in/out flow
208 | function initApp() {
209 |
210 | var token = settings.token;
211 |
212 | // user not logged in
213 | if (!token) {
214 | showAuth();
215 | return;
216 | }
217 |
218 | events.emit('user:signedIn');
219 | events.on('traymenu:clicked', navigateWindowTo);
220 | events.on('traymenu:signout', signout);
221 | events.on('menu:signout', signout);
222 |
223 | events.on('menu:toggle:next', function () {
224 | if (!mainWindow) return;
225 | var target = mainWindow.window.document.getElementById('mainframe');
226 | var nextActive = mainWindow.eval(target, "document.cookie.indexOf('gitter_staging=staged');") >= 0;
227 | if (nextActive) {
228 | settings.next = false;
229 | mainWindow.eval(target, "document.cookie='gitter_staging=none;domain=.gitter.im;path=/;expires=' + new Date(Date.now() + 31536000000).toUTCString(); location.reload();");
230 | } else {
231 | settings.next = true;
232 | mainWindow.eval(target, "document.cookie='gitter_staging=staged;domain=.gitter.im;path=/;expires=' + new Date(Date.now() + 31536000000).toUTCString(); location.reload();");
233 | }
234 | });
235 |
236 | events.on('menu:toggle:devtools', toggleDeveloperTools);
237 |
238 | events.on('user:signedOut', function () {
239 | client.disconnect();
240 | client = null;
241 | });
242 |
243 | events.on('settings:change:launchOnStartup', function(newValue) {
244 | autoLauncher[(newValue ? 'enable' : 'disable')](function(err) {
245 | if(err) throw err;
246 | });
247 | });
248 |
249 | // Realtime client to keep track of the user rooms.
250 | var client = new Gitter.RealtimeClient({
251 | authProvider: function(cb) {
252 | return cb({ token: token, client: CLIENT });
253 | }
254 | });
255 |
256 | var rooms = new Gitter.RoomCollection([], { client: client, listen: true });
257 |
258 | client.on('change:userId', function (userId) {
259 | events.emit('realtime:connected', rooms);
260 | log.trace('realtime connected()');
261 |
262 | if (!client) return;
263 | log.trace('attempting to subscribe()');
264 | var subscription = client.subscribe('/v1/user/' + userId, function (msg) {
265 | if (mainWindowFocused || !msg.notification) return;
266 |
267 | if (msg.notification === 'user_notification') {
268 | notifier({
269 | title: msg.title,
270 | message: msg.text,
271 | icon: msg.icon,
272 | click: function() {
273 | log.info('Notification user_notification clicked. Moving to', msg.link);
274 | navigateWindowTo(msg.link);
275 | }
276 | });
277 | }
278 | });
279 | });
280 |
281 | if (settings.launchHidden !== true) {
282 | showLoggedInWindow();
283 | }
284 | }
285 |
286 | function showAuth() {
287 | if (loginView) return;
288 |
289 | // whitelists app:// protocol used for the oAuth callback
290 | gui.App.addOriginAccessWhitelistEntry('https://gitter.im/', 'app', 'gitter', true);
291 |
292 | loginView = new LoginView(gui.Window);
293 |
294 | loginView.on('accessTokenReceived', function(accessToken) {
295 | log.trace('authWindow:loaded');
296 | settings.token = accessToken;
297 | initApp(accessToken);
298 | loginView.destroy();
299 | });
300 |
301 | loginView.on('destroy', function() {
302 | loginView = null;
303 | });
304 | }
305 |
306 | function signout() {
307 | log.trace('signout()');
308 |
309 | flushCookies()
310 | .then(function () {
311 | settings.token = null;
312 |
313 | // only close the window if we can, otherwise app may crash
314 | if (mainWindow) {
315 | mainWindow.close(true);
316 | }
317 |
318 | showAuth();
319 |
320 | events.emit('user:signedOut');
321 | });
322 | }
323 |
324 | /**
325 | * showLoggedInWindow() handles the logic for displaying loggedin.html
326 | *
327 | * exec - String, code to be evaluated once the iFrame has loaded.
328 | * @void
329 | */
330 | function showLoggedInWindow(exec) {
331 | log.trace('showLoggedInWindow()');
332 |
333 | mainWindow = gui.Window.get(
334 | window.open('loggedin.html', {
335 | focus: true
336 | })
337 | );
338 |
339 | mainWindow.setMinimumSize(400, 200);
340 |
341 | var menu = new CustomMenu({
342 | items: MENU_ITEMS,
343 | label: 'Gitter',
344 | filter: function (item) {
345 |
346 | if (item.support && item.support.indexOf(CLIENT) < 0 ) {
347 | return false;
348 | }
349 |
350 | if (item.auth) {
351 | return item.auth && !!settings.token;
352 | }
353 |
354 | return true;
355 | }
356 | });
357 |
358 | mainWindow.menu = menu.get();
359 |
360 | // FIXME: temporary fix, needs to be repainted
361 | events.on('menu:updated', function () {
362 | if (mainWindow) {
363 | mainWindow.menu = menu.get();
364 | }
365 | });
366 |
367 | mainWindow.on('loaded', function () {
368 | // When a mac app starts up, it doesn't have focus
369 | // When a Windows or Linux app starts up, it has focus
370 | if(CLIENT !== 'osx') {
371 | mainWindowFocused = true;
372 | }
373 |
374 | if (exec) {
375 | var iFrame = mainWindow.window.document.getElementById('mainframe');
376 | iFrame.onload = function () {
377 | mainWindow.eval(iFrame, exec);
378 | };
379 | }
380 | });
381 |
382 | mainWindow.on('closed', function () {
383 | log.trace('mainWindow:closed');
384 | mainWindow = null;
385 | mainWindowFocused = false;
386 | });
387 |
388 | mainWindow.on('focus', function () {
389 | log.trace('mainWindow:focus');
390 | mainWindowFocused = true;
391 | // TODO: Remove this hack
392 | var toExec = "var cf = document.getElementById('content-frame'); if (cf) cf.contentWindow.dispatchEvent(new Event('focus'));";
393 | mainWindow.eval(mainWindow.window.document.getElementById('mainframe'),toExec);
394 | });
395 |
396 | mainWindow.on('blur', function () {
397 | log.trace('mainWindow:blur');
398 | mainWindowFocused = false;
399 | // TODO: Remove this hack
400 | var toExec = "var cf = document.getElementById('content-frame'); if (cf) cf.contentWindow.dispatchEvent(new Event('blur'));";
401 | mainWindow.eval(mainWindow.window.document.getElementById('mainframe'),toExec);
402 | });
403 |
404 | mainWindow.on('new-win-policy', function (frame, url, policy) {
405 | gui.Shell.openExternal(url);
406 | policy.ignore();
407 | });
408 | }
409 |
410 | function toggleDeveloperTools() {
411 | log.trace('toggleDeveloperTools()');
412 | if (mainWindow.isDevToolsOpen()) {
413 | mainWindow.closeDevTools();
414 | } else {
415 | mainWindow.showDevTools();
416 | }
417 | }
418 |
419 | function navigateWindowTo(uri) {
420 | var toExec = "window.gitterLoader('" + uri + "');";
421 |
422 | if (!mainWindow) {
423 | // load window and then navigate
424 | return showLoggedInWindow(toExec);
425 | }
426 |
427 | // simply navigate as we have window
428 | mainWindow.eval(mainWindow.window.document.getElementById('mainframe'), toExec); // FIXME: this is dupe code
429 | mainWindow.focus();
430 | mainWindow.show();
431 | }
432 |
433 | function deleteCookie(cookie) {
434 | return new Promise(function (resolve, reject) {
435 | var cookie_url = "http" + (cookie.secure ? "s" : "") + "://" + cookie.domain + cookie.path;
436 |
437 | win.cookies.remove({ url: cookie_url, name: cookie.name }, function (result) {
438 | if (result) {
439 | result = result[0];
440 | log.info('cookie removed:' + result.name);
441 | resolve(result);
442 | } else {
443 | log.error('failed to remove cookie');
444 | reject(new Error('Failed to delete cookie.'));
445 | }
446 | });
447 | });
448 | }
449 |
450 | function fetchAllCookies() {
451 | log.trace('fetchAllCookies()');
452 | return new Promise(function (resolve) {
453 | win.cookies.getAll({}, function (cookies) {
454 | resolve(cookies);
455 | });
456 | });
457 | }
458 |
459 | function flushCookies() {
460 | return new Promise(function (resolve) {
461 | fetchAllCookies()
462 | .then(function (cookies) {
463 | log.debug('got ' + cookies.length + ' cookies');
464 | return Promise.all(cookies.map(deleteCookie));
465 | })
466 | .then(function () {
467 | log.info('deleted all cookies');
468 | resolve(true);
469 | })
470 | .catch(function (err) {
471 | log.error(err.stack);
472 | });
473 | });
474 | }
475 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | /* jshint node: true */
2 | 'use strict';
3 |
4 |
5 | var SUPPORTED_PLATFORMS = ['win32', 'linux32', 'linux64', 'osx64'];
6 |
7 | var gulp = require('gulp');
8 | var through = require('through2');
9 | var gutil = require('gulp-util');
10 | var shell = require('gulp-shell');
11 | var exhaustively = require('stream-exhaust');
12 | var Promise = require('bluebird');
13 | var os = require('os');
14 | var fs = require('fs');
15 | var s3 = require('s3');
16 | var path = require('path');
17 | var rimraf = require('rimraf');
18 | var manifest = require('./package.json');
19 | var template = require('lodash.template');
20 | var Builder = require('nw-builder');
21 | var appmanifest = require('./nwapp/package.json');
22 |
23 | var LATEST_HTML_TEMPLATE = template(fs.readFileSync('./latest-template.html').toString());
24 |
25 | var CURRENT_OS = (os.platform().match(/darwin/) ? 'osx' : os.platform());
26 |
27 | var YEAR = new Date().getFullYear();
28 | var CACHE_DIR = './cache';
29 | var OUTPUT_DIR = './opt';
30 | var SOURCE_DIR = './nwapp';
31 |
32 | var SIGN_IDENTITY = 'Developer ID Application: Troupe Technology Limited (A86QBWJ43W)'; // To use this identity you must have the appropriate Developer ID cert in your keychain
33 |
34 | var CERTIFICATES_DIR = './certificates';
35 | var CERTIFICATES_FORMAT = {
36 | 'win': 'troupe-cert.pfx',
37 | 'osx': 'DeveloperID.p12'
38 | };
39 |
40 | var ARTEFACTS_DIR = './artefacts';
41 | var LINUX_ARTEFACT_TEMPLATE = template('<%= name %>_<%= version %>_<%= arch %>.deb');
42 |
43 | var ARTEFACTS = {
44 | 'win': template('<%= name %>Setup-<%= version %>.exe')({ name: appmanifest.name, version: appmanifest.version }),
45 | 'osx': template('<%= name %>-<%= version %>.dmg')({ name: appmanifest.name, version: appmanifest.version }),
46 | 'linux32': LINUX_ARTEFACT_TEMPLATE({ name: appmanifest.name.toLowerCase(), version: appmanifest.version, arch: 'i386' }),
47 | 'linux64': LINUX_ARTEFACT_TEMPLATE({ name: appmanifest.name.toLowerCase(), version: appmanifest.version, arch: 'amd64' }),
48 | };
49 |
50 | var ARTEFACTS_URL = Object.keys(ARTEFACTS).reduce(function (fold, item) {
51 | fold[item] = path.join(item, ARTEFACTS[item]);
52 | return fold;
53 | }, {});
54 |
55 | var S3_CONSTS = {
56 | buckets: {
57 | certificates: 'troupe-certs',
58 | updates: 'update.gitter.im'
59 | },
60 | handleError: function (err) {
61 | gutil.log(err.stack);
62 | },
63 | handleProgress: function (msg, file) {
64 | if (!this) {
65 | return gutil.log(gutil.colors.red('no progress report'));
66 | }
67 | var progress = ~~((this.progressAmount / this.progressTotal) * 100);
68 | if (progress % 50 !== 0) return;
69 | gutil.log(msg, gutil.colors.cyan('\'' + file + '\''), progress + '%');
70 | }
71 | };
72 |
73 | var s3Client = s3.createClient({
74 | s3Options: {
75 | accessKeyId: S3_CONSTS.credentials && S3_CONSTS.credentials.key,
76 | secretAccessKey: S3_CONSTS.credentials && S3_CONSTS.credentials.secret
77 | }
78 | });
79 |
80 | function namespace(/* args */) {
81 | return [].slice.call(arguments).join(':');
82 | }
83 |
84 | function fetchS3(params, done) {
85 | var downloader = s3Client.downloadFile(params);
86 | downloader.on('error', S3_CONSTS.handleError.bind(downloader));
87 | downloader.on('progress', S3_CONSTS.handleProgress.bind(downloader, 'Downloading', params.localFile));
88 | downloader.on('end', done);
89 | }
90 |
91 | function pushS3(params) {
92 | var uploader = s3Client.uploadFile(params);
93 | uploader.on('error', S3_CONSTS.handleError.bind(uploader));
94 | uploader.on('progress', S3_CONSTS.handleProgress.bind(uploader, 'Uploading', params.localFile));
95 | return Promise.fromCallback(function(cb) {
96 | uploader.on('end', cb);
97 | });
98 | }
99 |
100 |
101 | // Look for any troubled path lengths so we don't run into problems on the Windows builds: https://github.com/gitterHQ/desktop/issues/59
102 | // If you are running into issues, you can just install the sub-depedency on the root level
103 | // This shouldn't be a problem if we decide to require npm 3 for the desktop builds
104 | gulp.task('check-path-safety-for-windows', function() {
105 | var stream = gulp.src('./nwapp/**/*', {read: false})
106 | .pipe(through.obj(function(chunk, enc, cb) {
107 | var baseWindowsCheckPath = 'C:\\Users\\some-longish-username\\AppData\\Local\\Temp\\nw2128_17940';
108 | var pathToCheck = path.join(baseWindowsCheckPath, path.relative(chunk.base, chunk.path));
109 |
110 | if(pathToCheck.length > 256) {
111 | var nodeModulesMessage = '';
112 | if(pathToCheck.match('node_modules')) {
113 | nodeModulesMessage = ' --- You can try installing a sub-depedency as a root-level module to shorten up the paths';
114 | }
115 | throw new gutil.PluginError('checking-path-lengths', {
116 | message: 'You have a path length that exceeds 256 characters and will cause issues on Windows: ' + chunk.path + '. Note, we checked with a base path: ' + baseWindowsCheckPath + nodeModulesMessage
117 | });
118 | }
119 |
120 | this.push(chunk);
121 | cb();
122 | }));
123 |
124 | // Avoid the high-water mark: https://github.com/gulpjs/gulp/issues/1356
125 | return exhaustively(stream);
126 | });
127 |
128 |
129 | [OUTPUT_DIR, ARTEFACTS_DIR, CACHE_DIR].forEach(function (dir) {
130 | gulp.task('clean:' + path.basename(dir).toLowerCase(), function (done) {
131 | rimraf(dir, done);
132 | });
133 | });
134 |
135 | gulp.task('clean', [OUTPUT_DIR, ARTEFACTS_DIR, CACHE_DIR].map(function (dir) { return 'clean:' + path.basename(dir); }));
136 |
137 | /* cert:fetch:{{ OS }} */
138 | Object.keys(CERTIFICATES_FORMAT).forEach(function(OS) {
139 | var file = CERTIFICATES_FORMAT[OS];
140 | gulp.task('cert:fetch:' + OS, fetchS3.bind(null, {
141 | localFile: path.join(CERTIFICATES_DIR, file),
142 | s3Params: {
143 | Bucket: S3_CONSTS.buckets.certificates,
144 | Key: file
145 | }
146 | }));
147 | });
148 |
149 | /* fetches the current os certificate */
150 | gulp.task('cert:fetch', ['cert:fetch:' + CURRENT_OS]);
151 |
152 | gulp.task('build', ['clean:opt', 'clean:artefacts', 'check-path-safety-for-windows'], function (done) {
153 | fs.mkdirSync(ARTEFACTS_DIR);
154 | var builder = new Builder({
155 | buildDir: OUTPUT_DIR,
156 | version: manifest.nwversion,
157 | files: [path.join(SOURCE_DIR, '**')],
158 | platforms: SUPPORTED_PLATFORMS,
159 | winIco: './icons/gitter.ico',
160 | macIcns: './icons/AppIcon.icns',
161 | macPlist: {
162 | NSHumanReadableCopyright: 'Copyright © 2013-' + YEAR + ' Troupe Technology Limited. All rights reserved.',
163 | CFBundleIdentifier: 'com.gitter.desktop'
164 | }
165 | });
166 |
167 | builder.on('log', gutil.log.bind(gutil, 'nw-builder:'));
168 | builder.build().nodeify(done);
169 | });
170 |
171 | gulp.task('build:linux', ['pack:deb:32', 'pack:deb:64']);
172 | gulp.task('build:osx', ['pack:osx']);
173 | gulp.task('build:all', ['build:linux', 'build:osx']);
174 |
175 | gulp.task('run', function (done){
176 | var builder = new Builder({
177 | version: manifest.nwversion,
178 | files: path.join(SOURCE_DIR, '**')
179 | });
180 |
181 | builder.on('log', gutil.log.bind(gutil, 'nw-builder:'));
182 | builder.run().nodeify(done);
183 | });
184 |
185 | // generated Debian/Ubuntu packages for 32 and 64 bit archs
186 | [32, 64].forEach(function (arch) {
187 |
188 | var fpm = template('fpm -s dir -t deb -a <%= port %> -n <%= name %> --category Utility --after-install ./opt/Gitter/linux<%= arch %>/after-install.sh --after-remove ./opt/Gitter/linux<%= arch %>/after-remove.sh --url "https://gitter.im" --description "Where developers come to talk" --maintainer "Troupe Technology " -p <%= output %> -v <%= version %> ./opt/Gitter/linux<%= arch %>/');
189 | var catsed = gutil.template('cat ./linux/<%= file %> | sed "s/{{arch}}/linux<%= arch %>/g" > ./opt/Gitter/linux<%= arch %>/<%= file %>');
190 |
191 | gulp.task('pack:templates:' + arch, ['build'], shell.task([ // this probably waits for build
192 | catsed({ arch: arch, file: 'gitter.desktop' }) ,
193 | catsed({ arch: arch, file: 'after-install.sh' }),
194 | catsed({ arch: arch, file: 'after-remove.sh' }),
195 | 'cp ./icons/logo.png ./opt/Gitter/linux' + arch + '/logo.png'
196 | ]));
197 |
198 | gulp.task('pack:deb:' + arch, ['pack:templates:' + arch], shell.task(
199 | fpm({
200 | arch: arch,
201 | port: arch === 32 ? 'i386' : 'amd64',
202 | version: appmanifest.version,
203 | name: appmanifest.name.toLowerCase(),
204 | output: path.join(ARTEFACTS_DIR, ARTEFACTS['linux' + arch])
205 | })
206 | ));
207 | });
208 |
209 | var dmg_cmd = template('./osx/create-dmg/create-dmg --icon "<%= name %>" 311 50 --icon-size 32 --app-drop-link 311 364 --window-size 622 683 --volname "<%= name %>" --volicon "./icons/AppIcon.icns" --background "./icons/dmg-bg.png" "<%= output %>" "<%= path %>/Gitter/osx64/Gitter.app"');
210 |
211 | // Only runs on OSX (requires XCode properly configured)
212 | gulp.task('sign:osx', ['build'], shell.task([
213 | /* * /
214 | 'codesign -v -f -s "'+ SIGN_IDENTITY +'" '+ OUTPUT_DIR +'/Gitter/osx64/Gitter.app/Contents/Frameworks/*',
215 | 'codesign -v -f -s "'+ SIGN_IDENTITY +'" '+ OUTPUT_DIR +'/Gitter/osx64/Gitter.app',
216 | 'codesign -v --display '+ OUTPUT_DIR +'/Gitter/osx64/Gitter.app',
217 | 'codesign -v --verify '+ OUTPUT_DIR +'/Gitter/osx64/Gitter.app'
218 | /* */
219 | ]));
220 |
221 | // Only runs on OSX
222 | if (ARTEFACTS.osx) {
223 | gulp.task('pack:osx', ['sign:osx'], shell.task(dmg_cmd({
224 | name: appmanifest.name,
225 | version: appmanifest.version,
226 | path: OUTPUT_DIR,
227 | output: path.join(ARTEFACTS_DIR, ARTEFACTS.osx)
228 | })));
229 | }
230 |
231 | // Generate auto-updater packages for Windows
232 | gulp.task('autoupdate:zip:win', shell.task([
233 | 'cd '+ OUTPUT_DIR + '/Gitter/win32; zip win32.zip ./* > /dev/null 2>&1',
234 | 'mv '+ OUTPUT_DIR + '/Gitter/win32/win32.zip ' + ARTEFACTS_DIR + '/win32.zip',
235 | ]));
236 |
237 | // Generate auto-updater packages for OSX
238 | gulp.task('autoupdate:zip:osx', shell.task([
239 | 'cd '+ OUTPUT_DIR + '/Gitter/osx64; zip -r osx.zip ./Gitter.app > /dev/null 2>&1',
240 | 'mv '+ OUTPUT_DIR + '/Gitter/osx64/osx.zip '+ ARTEFACTS_DIR + '/osx.zip',
241 | ]));
242 |
243 | // Generate auto-updater packages for linux
244 | gulp.task('autoupdate:zip:linux', shell.task([
245 | 'cd '+ OUTPUT_DIR + '/Gitter/osx64; zip -r osx.zip ./Gitter.app > /dev/null 2>&1',
246 | 'mv '+ OUTPUT_DIR + '/Gitter/osx64/osx.zip '+ ARTEFACTS_DIR + '/linux32.zip',
247 | 'cd '+ OUTPUT_DIR + '/Gitter/osx64; zip -r osx.zip ./Gitter.app > /dev/null 2>&1',
248 | 'mv '+ OUTPUT_DIR + '/Gitter/osx64/osx.zip '+ ARTEFACTS_DIR + '/linux64.zip',
249 | ]));
250 |
251 | gulp.task('autoupdate:push:win', function() {
252 | return pushS3({
253 | localFile: ARTEFACTS_DIR + '/win32.zip',
254 | s3Params: {
255 | Bucket: S3_CONSTS.buckets.updates,
256 | Key: 'win/win32.zip',
257 | CacheControl: 'public, max-age=0, no-cache',
258 | ACL: 'public-read'
259 | }
260 | });
261 | });
262 |
263 | gulp.task('autoupdate:push:osx', function() {
264 | return pushS3({
265 | localFile: ARTEFACTS_DIR + '/osx.zip',
266 | s3Params: {
267 | Bucket: S3_CONSTS.buckets.updates,
268 | Key: 'osx/osx.zip',
269 | CacheControl: 'public, max-age=0, no-cache',
270 | ACL: 'public-read'
271 | }
272 | });
273 | });
274 |
275 |
276 | gulp.task('autoupdate:push:linux', function() {
277 | return Promise.all([
278 | pushS3({
279 | localFile: ARTEFACTS_DIR + '/linux32.zip',
280 | s3Params: {
281 | Bucket: S3_CONSTS.buckets.updates,
282 | Key: 'linux32/linux32.zip',
283 | CacheControl: 'public, max-age=0, no-cache',
284 | ACL: 'public-read'
285 | }
286 | }),
287 | pushS3({
288 | localFile: ARTEFACTS_DIR + '/linux64.zip',
289 | s3Params: {
290 | Bucket: S3_CONSTS.buckets.updates,
291 | Key: 'linux64/linux64.zip',
292 | CacheControl: 'public, max-age=0, no-cache',
293 | ACL: 'public-read'
294 | }
295 | })
296 | ]);
297 | });
298 |
299 |
300 |
301 |
302 |
303 | var pushManifestToDest = function(destinationKey) {
304 | return pushS3({
305 | localFile: SOURCE_DIR + '/package.json',
306 | s3Params: {
307 | Bucket: S3_CONSTS.buckets.updates,
308 | Key: destinationKey,
309 | CacheControl: 'public, max-age=0, no-cache',
310 | ACL: 'public-read'
311 | }
312 | });
313 | };
314 | var platformFolders = [
315 | 'osx',
316 | 'win',
317 | 'linux'
318 | ];
319 | gulp.task('manifest:push:osx', function() {
320 | return pushManifestToDest('osx/package.json');
321 | });
322 | gulp.task('manifest:push:win', function() {
323 | return pushManifestToDest('win/package.json');
324 | });
325 | gulp.task('manifest:push:linux', function() {
326 | return Promise.all([
327 | pushManifestToDest('linux32/package.json'),
328 | pushManifestToDest('linux64/package.json')
329 | ]);
330 | });
331 | gulp.task('manifest:push', ['manifest:push:osx', 'manifest:push:win', 'manifest:push:linux'], function() {
332 | return true;
333 | });
334 |
335 | gulp.task('identity', function (done) {
336 | done();
337 | });
338 |
339 | Object.keys(ARTEFACTS).forEach(function (platform) {
340 | console.log(platform);
341 |
342 | var LATEST_TEMPLATE = template('latest_<%= platform %>.html');
343 |
344 | gulp.task(namespace('artefacts', 'push', platform), function() {
345 | return pushS3({
346 | localFile: path.join(ARTEFACTS_DIR, ARTEFACTS[platform]),
347 | s3Params: {
348 | Bucket: S3_CONSTS.buckets.updates,
349 | Key: path.join(platform, ARTEFACTS[platform]),
350 | CacheControl: 'public, max-age=0, no-cache',
351 | ACL: 'public-read'
352 | }
353 | });
354 | });
355 |
356 | // LATEST_HTML_TEMPLATE
357 | gulp.task(namespace('redirect', 'source', platform), function (done) {
358 | fs.writeFile(
359 | path.join(ARTEFACTS_DIR, LATEST_TEMPLATE({ platform: platform })),
360 | LATEST_HTML_TEMPLATE({ url: ARTEFACTS_URL[platform] }),
361 | done
362 | );
363 | });
364 |
365 | gulp.task(namespace('redirect', 'push', platform), function() {
366 | return pushS3({
367 | localFile: path.join(ARTEFACTS_DIR, LATEST_TEMPLATE({ platform: platform })),
368 | s3Params: {
369 | Bucket: S3_CONSTS.buckets.updates,
370 | Key: path.join(platform, 'latest'),
371 | CacheControl: 'public, max-age=0, no-cache',
372 | ACL: 'public-read'
373 | }
374 | });
375 | });
376 | });
377 |
378 | gulp.task('artefacts:push', Object.keys(ARTEFACTS).map(function (platform) { return namespace('artefacts', 'push', platform); }));
379 | gulp.task('redirect:source', Object.keys(ARTEFACTS).map(function (platform) { return namespace('redirect', 'source', platform); }));
380 | gulp.task('redirect:push', Object.keys(ARTEFACTS).map(function (platform) { return namespace('redirect', 'push', platform); }));
381 |
382 | // gulp.task('build:win', ['build'], function (done) {
383 |
384 | // // shell.task([
385 | // // s3cmd+'./'+ OUTPUT_DIR +'/Gitter/'+winInstaller+' '+s3bucket+'/win/',
386 | // // s3cmd+'./'+ OUTPUT_DIR +'/Gitter/win32/win32.zip '+s3bucket+'/win/',
387 | // // s3cmd+'--content-type "text/html" ./latest '+s3bucket+'/win/',
388 | // // s3cmd+'./nwapp/package.json '+s3bucket+'/desktop/'
389 | // // ]));
390 |
391 | // // var winInstaller = "GitterSetup-"+appmanifest.version+".exe";
392 | // // var winDownloadUrl = "https://update.gitter.im/win/" + winInstaller;
393 | // // var uploader = s3Client.uploadFile({
394 | // // localFile: 'README.md',
395 | // // s3Params: {
396 | // // Bucket: S3_CONSTS.buckets.updates,
397 | // // Key: 'nwdev/README.md',
398 | // // CacheControl: 'public, max-age=0, no-cache',
399 | // // ACL: 'public-read'
400 | // // },
401 | // // });
402 | // });
403 |
--------------------------------------------------------------------------------