diff --git a/app/main/index.ts b/app/main/index.ts
index 74b2cbe2..e677f116 100644
--- a/app/main/index.ts
+++ b/app/main/index.ts
@@ -38,13 +38,20 @@ const mainURL = 'file://' + path.join(__dirname, '../renderer', 'main.html');
const singleInstanceLock = app.requestSingleInstanceLock();
if (singleInstanceLock) {
- app.on('second-instance', () => {
+ app.on('second-instance', (event, argv) => {
if (mainWindow) {
if (mainWindow.isMinimized()) {
mainWindow.restore();
}
mainWindow.show();
+
+ // URI scheme handler for systems other than macOS.
+ // For macOS, see 'open-url' event below.
+ if (process.platform !== 'darwin') {
+ const deepLinkingUrl = argv.slice(1);
+ handleDeepLink(deepLinkingUrl[(isDev && process.platform === 'win32') ? 1 : 0]);
+ }
}
});
} else {
@@ -66,6 +73,11 @@ const toggleApp = (): any => {
}
};
+function handleDeepLink(url: string): void {
+ mainWindow.webContents.focus();
+ mainWindow.webContents.send('deep-linking-url', url);
+}
+
function createMainWindow(): Electron.BrowserWindow {
// Load the previous state with fallback to defaults
const mainWindowState: windowStateKeeper.State = windowStateKeeper({
@@ -145,6 +157,20 @@ function createMainWindow(): Electron.BrowserWindow {
// Decrease load on GPU (experimental)
app.disableHardwareAcceleration();
+if (process.platform === 'win32' && isDev) {
+ app.setAsDefaultProtocolClient('zulip', process.execPath, [path.resolve(process.argv[1])]);
+} else {
+ app.setAsDefaultProtocolClient('zulip');
+}
+
+// URI scheme handler for macOS
+app.on('open-url', (event, url) => {
+ event.preventDefault();
+ if (mainWindow) {
+ mainWindow.webContents.send('deep-linking-url', url);
+ }
+});
+
// Temporary fix for Electron render colors differently
// More info here - https://github.com/electron/electron/issues/10732
app.commandLine.appendSwitch('force-color-profile', 'srgb');
diff --git a/app/package.json b/app/package.json
index 228ad7bd..7c404c2f 100644
--- a/app/package.json
+++ b/app/package.json
@@ -43,7 +43,8 @@
"node-json-db": "0.9.2",
"request": "2.85.0",
"semver": "5.4.1",
- "wurl": "2.5.0"
+ "wurl": "2.5.0",
+ "crypto-random-string": "3.1.0"
},
"optionalDependencies": {
"node-mac-notifier": "1.1.0"
diff --git a/app/renderer/js/components/webview.ts b/app/renderer/js/components/webview.ts
index 2cf6b30b..249f917b 100644
--- a/app/renderer/js/components/webview.ts
+++ b/app/renderer/js/components/webview.ts
@@ -254,6 +254,10 @@ class WebView extends BaseComponent {
this.$el.openDevTools();
}
+ loadURL(url: string): void {
+ this.$el.loadURL(url);
+ }
+
back(): void {
if (this.$el.canGoBack()) {
this.$el.goBack();
diff --git a/app/renderer/js/main.ts b/app/renderer/js/main.ts
index ca49ec80..8333162e 100644
--- a/app/renderer/js/main.ts
+++ b/app/renderer/js/main.ts
@@ -21,6 +21,7 @@ import ReconnectUtil = require('./utils/reconnect-util');
import Logger = require('./utils/logger-util');
import CommonUtil = require('./utils/common-util');
import EnterpriseUtil = require('./utils/enterprise-util');
+import AuthUtil = require('./utils/auth-util');
import Messages = require('./../../resources/messages');
interface FunctionalTabProps {
@@ -47,6 +48,7 @@ interface SettingsOptions {
autoUpdate: boolean;
betaUpdate: boolean;
errorReporting: boolean;
+ loginInApp: boolean;
customCSS: boolean;
silent: boolean;
lastActiveTab: number;
@@ -199,6 +201,7 @@ class ServerManagerView {
autoUpdate: true,
betaUpdate: false,
errorReporting: true,
+ loginInApp: false,
customCSS: false,
silent: false,
lastActiveTab: 0,
@@ -808,6 +811,30 @@ class ServerManagerView {
});
}
+ ipcRenderer.on('deep-linking-url', (event: Event, url: string) => {
+ if (!ConfigUtil.getConfigItem('desktopOtp')) {
+ return;
+ }
+ const urlObject = new URL(decodeURIComponent(url));
+ const serverURL = urlObject.searchParams.get('realm');
+ let apiKey = urlObject.searchParams.get('otp_encrypted_login_key');
+ const desktopOtp = ConfigUtil.getConfigItem('desktopOtp');
+ apiKey = AuthUtil.hexToAscii(AuthUtil.xorStrings(apiKey, desktopOtp));
+
+ // Use this apiKey to login the realm if it exists
+ if (apiKey === '') {
+ console.log('Invalid API Key');
+ } else {
+ DomainUtil.getDomains().forEach((domain: any, index: number) => {
+ if (domain.url.includes(serverURL)) {
+ this.activateTab(index);
+ this.tabs[index].webview.loadURL(`${serverURL}/accounts/login/subdomain/${apiKey}`);
+ }
+ });
+ ConfigUtil.setConfigItem('desktopOtp', null);
+ }
+ });
+
ipcRenderer.on('show-network-error', (event: Event, index: number) => {
this.openNetworkTroubleshooting(index);
});
diff --git a/app/renderer/js/pages/preference/general-section.ts b/app/renderer/js/pages/preference/general-section.ts
index 3795c086..adb41992 100644
--- a/app/renderer/js/pages/preference/general-section.ts
+++ b/app/renderer/js/pages/preference/general-section.ts
@@ -93,29 +93,33 @@ class GeneralSection extends BaseSection {
${t.__('Advanced')}
-
-
${t.__('Enable error reporting (requires restart)')}
-
-
-
-
${t.__('Show downloaded files in file manager')}
-
-
-
-
- ${t.__('Add custom CSS')}
-
-
-
-
-
-
${ConfigUtil.getConfigItem('customCSS')}
-
-
- indeterminate_check_box
- ${t.__('Delete')}
-
-
+
+
${t.__('Enable error reporting (requires restart)')}
+
+
+
+
${t.__('Force social login in app instead of browser')}
+
+
+
+
${t.__('Show downloaded files in file manager')}
+
+
+
+
+ ${t.__('Add custom CSS')}
+
+
+
+
+
+
${ConfigUtil.getConfigItem('customCSS')}
+
+
+ indeterminate_check_box
+ ${t.__('Delete')}
+
+
${t.__('Default download location')}
@@ -131,7 +135,6 @@ class GeneralSection extends BaseSection {
${t.__('Ask where to save files before downloading')}
-
${t.__('Reset Application Data')}
@@ -166,6 +169,7 @@ class GeneralSection extends BaseSection {
this.updateQuitOnCloseOption();
this.updatePromptDownloadOption();
this.enableErrorReporting();
+ this.enableLoginInApp();
// Platform specific settings
@@ -367,6 +371,18 @@ class GeneralSection extends BaseSection {
});
}
+ enableLoginInApp(): void {
+ this.generateSettingOption({
+ $element: document.querySelector('#force-login-app .setting-control'),
+ value: ConfigUtil.getConfigItem('loginInApp', true),
+ clickHandler: () => {
+ const newValue = !ConfigUtil.getConfigItem('loginInApp');
+ ConfigUtil.setConfigItem('loginInApp', newValue);
+ this.enableLoginInApp();
+ }
+ });
+ }
+
clearAppDataDialog(): void {
const clearAppDataMessage = 'By clicking proceed you will be removing all added accounts and preferences from Zulip. When the application restarts, it will be as if you are starting Zulip for the first time.';
const getAppPath = path.join(app.getPath('appData'), app.getName());
diff --git a/app/renderer/js/preload.ts b/app/renderer/js/preload.ts
index 4164b91a..9adf9f58 100644
--- a/app/renderer/js/preload.ts
+++ b/app/renderer/js/preload.ts
@@ -11,6 +11,8 @@ import SetupSpellChecker from './spellchecker';
import isDev = require('electron-is-dev');
import LinkUtil = require('./utils/link-util');
import params = require('./utils/params-util');
+import AuthUtil = require('./utils/auth-util');
+import ConfigUtil = require('./utils/config-util');
import NetworkError = require('./pages/network');
@@ -79,7 +81,24 @@ process.once('loaded', (): void => {
// To prevent failing this script on linux we need to load it after the document loaded
document.addEventListener('DOMContentLoaded', (): void => {
if (params.isPageParams()) {
- // Get the default language of the server
+ const authMethods = page_params.external_authentication_methods; // eslint-disable-line no-undef
+ const loginInApp = ConfigUtil.getConfigItem('loginInApp');
+ console.log(loginInApp);
+ if (authMethods && !loginInApp) {
+ for (const authMethod of authMethods) {
+ const { button_id_suffix } = authMethod;
+ const $socialButton = document.querySelector(`button[id$="${button_id_suffix}"]`);
+ if ($socialButton) {
+ $socialButton.addEventListener('click', event => {
+ event.preventDefault();
+ const socialLink = $socialButton.closest('form').action;
+ AuthUtil.openInBrowser(socialLink);
+ });
+ }
+ }
+ }
+
+ // Get the default language of the server
const serverLanguage = page_params.default_language; // eslint-disable-line no-undef
if (serverLanguage) {
// Init spellchecker
diff --git a/app/renderer/js/utils/auth-util.ts b/app/renderer/js/utils/auth-util.ts
new file mode 100644
index 00000000..4cbbf335
--- /dev/null
+++ b/app/renderer/js/utils/auth-util.ts
@@ -0,0 +1,36 @@
+import { remote } from 'electron';
+
+import cryptoRandomString = require('crypto-random-string');
+import ConfigUtil = require('./config-util');
+
+const { shell } = remote;
+
+class AuthUtil {
+ openInBrowser = (link: string) => {
+ const otp = cryptoRandomString({length: 64});
+ ConfigUtil.setConfigItem('desktopOtp', otp);
+ shell.openExternal(`${link}?desktop_flow_otp=${otp}`);
+ };
+
+ xorStrings = (a: string, b: string): string => {
+ if (a.length === b.length) {
+ return a
+ .split('')
+ .map((char, i) => (parseInt(a[i], 16) ^ parseInt(b[i], 16)).toString(16))
+ .join('')
+ .toUpperCase();
+ } else {
+ return '';
+ }
+ };
+
+ hexToAscii = (hex: string) => {
+ let ascii = '';
+ for (let i = 0; i < hex.length; i += 2) {
+ ascii += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
+ }
+ return ascii;
+ };
+}
+
+export = new AuthUtil();
diff --git a/app/translations/en-GB.json b/app/translations/en-GB.json
index 6f690c76..02838c4c 100644
--- a/app/translations/en-GB.json
+++ b/app/translations/en-GB.json
@@ -115,5 +115,9 @@
"View Shortcuts": "View Shortcuts",
"Enter Full Screen": "Enter Full Screen",
"History Shortcuts": "History Shortcuts",
- "Quit when the window is closed": "Quit when the window is closed"
+ "Quit when the window is closed": "Quit when the window is closed",
+ "File": "File",
+ "Network and Proxy Settings": "Network and Proxy Settings",
+ "Ask where to save files before downloading": "Ask where to save files before downloading",
+ "Force social login in app instead of browser (not recommended)": "Force social login in app instead of browser (not recommended)"
}
\ No newline at end of file
diff --git a/package.json b/package.json
index e702e11f..b06e41ef 100644
--- a/package.json
+++ b/package.json
@@ -64,6 +64,15 @@
"entitlementsInherit": "build/entitlements.mac.plist",
"gatekeeperAssess": false
},
+ "protocols": [
+ {
+ "name": "zulip",
+ "role": "Viewer",
+ "schemes": [
+ "zulip"
+ ]
+ }
+ ],
"linux": {
"category": "Chat;GNOME;GTK;Network;InstantMessaging",
"icon": "build/icon.icns",
diff --git a/typings.d.ts b/typings.d.ts
index 5555c928..f3a3df3f 100644
--- a/typings.d.ts
+++ b/typings.d.ts
@@ -13,10 +13,12 @@ declare module 'fs-extra';
declare module 'wurl';
declare module 'i18n';
declare module 'backoff';
+declare module 'crypto-random-string';
interface PageParamsObject {
- realm_uri: string;
- default_language: string;
+ realm_uri: string;
+ default_language: string;
+ external_authentication_methods: any;
}
declare var page_params: PageParamsObject;