Compare commits

..

2 Commits

Author SHA1 Message Date
Akash Nimare
8ad74081c4 setting: Refactor auto-update setting. 2018-05-16 15:56:17 +05:30
Akash Nimare
6163ad85e0 setting: Add an option to disable the auto-updates. 2018-05-16 15:29:34 +05:30
46 changed files with 281 additions and 13726 deletions

View File

@@ -15,7 +15,7 @@ addons:
language: node_js
node_js:
- '8'
- '6'
before_install:
- ./scripts/travis-xvfb.sh

View File

@@ -12,7 +12,7 @@ Zulip-Desktop app is built on top of [Electron](http://electron.atom.io/). If yo
* The whole Zulip documentation, such as setting up a development environment, setting up with the Zulip webapp project, and testing, can be read [here](https://zulip.readthedocs.io).
* If you have any questions regarding zulip-electron, open an [issue](https://github.com/zulip/zulip-electron/issues/new/) or ask it on [chat.zulip.org](https://chat.zulip.org/#narrow/stream/16-desktop).
* If you have any questions regarding zulip-electron, open an [issue](https://github.com/zulip/zulip-electron/issues/new/) or ask it on [chat.zulip.org](https://chat.zulip.org/#narrow/stream/electron).
## Issue
Ensure the bug was not already reported by searching on GitHub under [issues](https://github.com/zulip/zulip-electron/issues). If you're unable to find an open issue addressing the bug, open a [new issue](https://github.com/zulip/zulip-electron/issues/new).

View File

@@ -2,26 +2,31 @@
[![Build Status](https://travis-ci.org/zulip/zulip-electron.svg?branch=master)](https://travis-ci.org/zulip/zulip-electron)
[![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/zulip/zulip-electron?branch=master&svg=true)](https://ci.appveyor.com/project/akashnimare/zulip-electron/branch/master)
[![XO code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/sindresorhus/xo)
[![project chat](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg)](https://chat.zulip.org)
Desktop client for Zulip. Available for Mac, Linux, and Windows.
Desktop client for Zulip. Available for Mac, Linux and Windows.
<img src="http://i.imgur.com/ChzTq4F.png"/>
# Download
Please see the [installation guide](https://zulipchat.com/help/desktop-app-install-guide).
Please see [installation guide](https://zulipchat.com/help/desktop-app-install-guide).
# Features
* Sign in to multiple teams
* Desktop notifications with inline reply
* Tray/dock integration
* Multi-language spell checker
* Automatic updates
* Desktop Notifications with inline reply support
* Multilanguage SpellChecker
* OSX/Win/Linux installers
* Automatic Updates (macOS/Windows/Linux)
* Keyboard shortcuts
# Development
Please see our [development guide](./development.md) to get started and run app locally.
# Contribute
First, join us on the [Zulip community server](https://zulip.readthedocs.io/en/latest/contributing/chat-zulip-org.html)!
Also see our [contribution guidelines](./CONTRIBUTING.md) and our [development guide](./development.md).
If you want to contribute please make sure to read [our documentation about contributing](./CONTRIBUTING.md) first.
* [Issue Tracker](https://github.com/zulip/zulip-electron/issues)
* [Source Code](https://github.com/zulip/zulip-electron/)
# License
Released under the [Apache-2.0](./LICENSE) license.

View File

@@ -0,0 +1,16 @@
'use strict';
const { crashReporter } = require('electron');
const crashHandler = () => {
crashReporter.start({
productName: 'zulip-electron',
companyName: 'Kandra Labs, Inc.',
submitURL: 'https://zulip-sentry.herokuapp.com/crashreport',
uploadToServer: true
});
};
module.exports = {
crashHandler
};

View File

@@ -5,6 +5,7 @@ const windowStateKeeper = require('electron-window-state');
const isDev = require('electron-is-dev');
const appMenu = require('./menu');
const { appUpdater } = require('./autoupdater');
const { crashHandler } = require('./crash-reporter');
const { setAutoLaunch } = require('./startup');
@@ -12,8 +13,6 @@ const { app, ipcMain } = electron;
const BadgeSettings = require('./../renderer/js/pages/preference/badge-settings.js');
const ConfigUtil = require('./../renderer/js/utils/config-util.js');
const ProxyUtil = require('./../renderer/js/utils/proxy-util.js');
const { sentryInit } = require('./../renderer/js/utils/sentry-util.js');
// Adds debug features like hotkeys for triggering dev tools and reload
// in development mode
@@ -54,8 +53,7 @@ function createMainWindow() {
// Load the previous state with fallback to defaults
const mainWindowState = windowStateKeeper({
defaultWidth: 1100,
defaultHeight: 720,
path: `${app.getPath('userData')}/config`
defaultHeight: 720
});
// Let's keep the window position global so that we can access it in other process
@@ -73,8 +71,7 @@ function createMainWindow() {
minHeight: 400,
webPreferences: {
plugins: true,
nodeIntegration: true,
partition: 'persist:webviewsession'
nodeIntegration: true
},
show: false
});
@@ -134,10 +131,6 @@ function createMainWindow() {
// Decrease load on GPU (experimental)
app.disableHardwareAcceleration();
// Temporary fix for Electron render colors differently
// More info here - https://github.com/electron/electron/issues/10732
app.commandLine.appendSwitch('force-color-profile', 'srgb');
// eslint-disable-next-line max-params
app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
event.preventDefault();
@@ -156,15 +149,6 @@ app.on('ready', () => {
});
mainWindow = createMainWindow();
// Initialize sentry for main process
sentryInit();
const isSystemProxy = ConfigUtil.getConfigItem('useSystemProxy');
if (isSystemProxy) {
ProxyUtil.resolveSystemProxy(mainWindow);
}
const page = mainWindow.webContents;
page.on('dom-ready', () => {
@@ -176,10 +160,11 @@ app.on('ready', () => {
});
page.once('did-frame-finish-load', () => {
// Initiate auto-updates on MacOS and Windows
// Initate auto-updates on MacOS and Windows
if (ConfigUtil.getConfigItem('autoUpdate')) {
appUpdater();
}
crashHandler();
});
// Temporarily remove this event
@@ -196,32 +181,6 @@ app.on('ready', () => {
app.quit();
});
// Show pdf in a new BrowserWindow
ipcMain.on('pdf-view', (event, url) => {
// Paddings for pdfWindow so that it fits into the main browserWindow
const paddingWidth = 55;
const paddingHeight = 22;
// Get the config of main browserWindow
const mainWindowState = global.mainWindowState;
// Window to view the pdf file
const pdfWindow = new electron.BrowserWindow({
x: mainWindowState.x + paddingWidth,
y: mainWindowState.y + paddingHeight,
width: mainWindowState.width - paddingWidth,
height: mainWindowState.height - paddingHeight,
webPreferences: {
plugins: true,
partition: 'persist:webviewsession'
}
});
pdfWindow.loadURL(url);
// We don't want to have the menu bar in pdf window
pdfWindow.setMenu(null);
});
// Reload full app not just webview, useful in debugging
ipcMain.on('reload-full-app', () => {
mainWindow.reload();
@@ -267,48 +226,6 @@ app.on('ready', () => {
ipcMain.on('toggleAutoLauncher', (event, AutoLaunchValue) => {
setAutoLaunch(AutoLaunchValue);
});
ipcMain.on('downloadFile', (event, url, downloadPath) => {
page.downloadURL(url);
page.session.once('will-download', (event, item) => {
const filePath = path.join(downloadPath, item.getFilename());
item.setSavePath(filePath);
item.on('updated', (event, state) => {
switch (state) {
case 'interrupted': {
// Can interrupted to due to network error, cancel download then
console.log('Download interrupted, cancelling and fallback to dialog download.');
item.cancel();
break;
}
case 'progressing': {
if (item.isPaused()) {
item.cancel();
}
// This event can also be used to show progres in percentage in future.
break;
}
default: {
console.info('Unknown updated state of download item');
}
}
});
item.once('done', (event, state) => {
if (state === 'completed') {
page.send('downloadFileCompleted', item.getSavePath(), item.getFilename());
} else {
console.log('Download failed state: ', state);
page.send('downloadFileFailed');
}
// To stop item for listening to updated events of this file
item.removeAllListeners('updated');
});
});
});
ipcMain.on('realm-icon-changed', (event, serverURL, iconURL) => {
page.send('update-realm-icon', serverURL, iconURL);
});
});
app.on('before-quit', () => {

View File

@@ -4,30 +4,20 @@ const { Notification } = require('electron');
const request = require('request');
const semver = require('semver');
const ConfigUtil = require('../renderer/js/utils/config-util');
const ProxyUtil = require('../renderer/js/utils/proxy-util');
const LinuxUpdateUtil = require('../renderer/js/utils/linux-update-util');
const Logger = require('../renderer/js/utils/logger-util');
const logger = new Logger({
file: 'linux-update-util.log',
timestamp: true
});
function linuxUpdateNotification() {
let url = 'https://api.github.com/repos/zulip/zulip-electron/releases';
url = ConfigUtil.getConfigItem('betaUpdate') ? url : url + '/latest';
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
const options = {
url,
headers: {'User-Agent': 'request'},
proxy: proxyEnabled ? ProxyUtil.getProxy(url) : ''
headers: {'User-Agent': 'request'}
};
request(options, (error, response, body) => {
if (error) {
logger.error('Linux update error.');
logger.error(error);
console.log('Error:', error);
return;
}
if (response.statusCode < 400) {
@@ -42,7 +32,7 @@ function linuxUpdateNotification() {
}
}
} else {
logger.log('Linux update response status: ', response.statusCode);
console.log('Status:', response.statusCode);
}
});
}

View File

@@ -4,20 +4,13 @@ const path = require('path');
const { app, shell, BrowserWindow, Menu, dialog } = require('electron');
const fs = require('fs-extra');
const AdmZip = require('adm-zip');
const { appUpdater } = require('./autoupdater');
const ConfigUtil = require(__dirname + '/../renderer/js/utils/config-util.js');
const DNDUtil = require(__dirname + '/../renderer/js/utils/dnd-util.js');
const Logger = require(__dirname + '/../renderer/js/utils/logger-util.js');
const appName = app.getName();
const logger = new Logger({
file: 'errors.log',
timestamp: true
});
class AppMenu {
getHistorySubmenu() {
return [{
@@ -142,26 +135,13 @@ class AppMenu {
}, {
label: 'Show App Logs',
click() {
const zip = new AdmZip();
let date = new Date();
date = date.toLocaleDateString().replace(/\//g, '-');
// Create a zip file of all the logs and config data
zip.addLocalFolder(`${app.getPath('appData')}/${appName}/Logs`);
zip.addLocalFolder(`${app.getPath('appData')}/${appName}/config`);
// Put the log file in downloads folder
const logFilePath = `${app.getPath('downloads')}/Zulip-logs-${date}.zip`;
zip.writeZip(logFilePath);
// Open and select the log file
shell.showItemInFolder(logFilePath);
shell.openItem(app.getPath('userData'));
}
}, {
label: 'Report an Issue...',
click() {
// the goal is to notify the main.html BrowserWindow
// which may not be the focused window.
// the goal is to notify the main.html BrowserWindow
// which may not be the focused window.
BrowserWindow.getAllWindows().forEach(window => {
window.webContents.send('open-feedback-modal');
});
@@ -216,12 +196,12 @@ class AppMenu {
AppMenu.sendAction('open-about');
}
}
}, {
}, {
label: `Check for Update`,
click() {
AppMenu.checkForUpdate();
}
}, {
}, {
type: 'separator'
}, {
label: 'Desktop App Settings',
@@ -328,12 +308,12 @@ class AppMenu {
AppMenu.sendAction('open-about');
}
}
}, {
}, {
label: `Check for Update`,
click() {
AppMenu.checkForUpdate();
}
}, {
}, {
type: 'separator'
}, {
label: 'Desktop App Settings',
@@ -437,7 +417,7 @@ class AppMenu {
const resetAppSettingsMessage = 'By proceeding you will be removing all connected organizations and preferences from Zulip.';
// We save App's settings/configurations in following files
const settingFiles = ['config/window-state.json', 'config/domain.json', 'config/settings.json', 'config/certificates.json'];
const settingFiles = ['window-state.json', 'domain.json', 'settings.json'];
dialog.showMessageBox({
type: 'warning',
@@ -451,8 +431,7 @@ class AppMenu {
const getSettingFilesPath = path.join(app.getPath('appData'), appName, settingFileName);
fs.access(getSettingFilesPath, error => {
if (error) {
logger.error('Error while resetting app settings.');
logger.error(error);
console.log(error);
} else {
fs.unlink(getSettingFilesPath, () => {
AppMenu.sendAction('clear-app-data');

1667
app/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "zulip",
"productName": "Zulip",
"version": "2.3.6",
"version": "2.2.0-beta",
"description": "Zulip Desktop App",
"license": "Apache-2.0",
"copyright": "Kandra Labs, Inc.",
@@ -27,15 +27,12 @@
],
"dependencies": {
"@electron-elements/send-feedback": "1.0.7",
"@sentry/electron": "0.8.1",
"adm-zip": "0.4.11",
"auto-launch": "5.0.5",
"electron-is-dev": "0.3.0",
"electron-log": "2.2.14",
"electron-spellchecker": "1.1.2",
"electron-updater": "2.23.3",
"electron-updater": "2.21.10",
"electron-window-state": "4.1.1",
"escape-html": "1.0.3",
"is-online": "7.0.0",
"node-json-db": "0.7.3",
"request": "2.85.0",

View File

@@ -258,8 +258,8 @@ img.server-info-icon {
border: #4EBFAC 2px solid;
}
.manual-proxy-block {
width: 96%;
.setting-block {
width: 100%;
}
.actions-container {
@@ -364,8 +364,7 @@ i.open-tab-button {
}
.reset-data-button,
.custom-css-button,
.download-folder-button {
.custom-css-button {
display: inline-block;
border: none;
padding: 10px;
@@ -385,10 +384,10 @@ i.open-tab-button {
color: #fff;
}
.selected-css-path,
.download-folder-path {
.selected-css-path {
background: #eeeeee;
padding: 5px 10px;
padding: 10px;
margin-top: 10px;
margin-right: 10px;
display: flex;
width: 90%;
@@ -558,53 +557,11 @@ input.toggle-round:checked+label:after {
background: #329588;
}
.certificates-card {
width:70%
}
.certificate-input {
width:100%;
margin-top: 10px;
display:inline-flex;
}
.certificate-input div {
align-self:center;
}
.certificate-input .setting-input-value {
margin-left:10px;
max-width: 100%;
}
#add-certificate-button {
width:20%;
margin-right:0px;
height: 35px;
}
.tip {
background-color: hsl(46,63%,95%);
border: 1px solid hsl(46,63%,84%);
border-radius: 4px;
}
.md-14 {
font-size: 14px;
vertical-align: middle;
padding-right: 6px;
}
#open-hotkeys-link {
text-decoration: underline;
cursor: pointer;
}
/* responsive grid */
@media (max-width: 650px) {
.selected-css-path,
.download-folder-path {
.selected-css-path {
margin-right: 15px;
}
#css-delete-action {

View File

@@ -1,73 +1,30 @@
const { ipcRenderer } = require('electron');
const { shell, app } = require('electron').remote;
const { shell } = require('electron').remote;
const LinkUtil = require('../utils/link-util');
const DomainUtil = require('../utils/domain-util');
const ConfigUtil = require('../utils/config-util');
const dingSound = new Audio('../resources/sounds/ding.ogg');
function handleExternalLink(event) {
const { url } = event;
const domainPrefix = DomainUtil.getDomain(this.props.index).url;
const downloadPath = ConfigUtil.getConfigItem('downloadsPath', `${app.getPath('downloads')}`);
const shouldShowInFolder = ConfigUtil.getConfigItem('showDownloadFolder', false);
// Whitelist URLs which are allowed to be opened in the app
// Whitelist URLs which are allowed to be opened in the app
const {
isInternalUrl: isWhiteListURL,
isUploadsUrl: isUploadsURL
} = LinkUtil.isInternal(domainPrefix, url);
isInternalUrl: isWhiteListURL,
isUploadsUrl: isUploadsURL
} = LinkUtil.isInternal(domainPrefix, url);
if (isWhiteListURL) {
event.preventDefault();
// Show pdf attachments in a new window
if (LinkUtil.isPDF(url) && isUploadsURL) {
ipcRenderer.send('pdf-view', url);
// download txt, pdf, mp3, mp4 etc.. by using downloadURL in the
// main process which allows the user to save the files to their desktop
// and not trigger webview reload while image in webview will
// do nothing and will not save it
if (!LinkUtil.isImage(url) && isUploadsURL) {
this.$el.downloadURL(url);
return;
}
// download txt, mp3, mp4 etc.. by using downloadURL in the
// main process which allows the user to save the files to their desktop
// and not trigger webview reload while image in webview will
// do nothing and will not save it
if (!LinkUtil.isImage(url) && !LinkUtil.isPDF(url) && isUploadsURL) {
ipcRenderer.send('downloadFile', url, downloadPath);
ipcRenderer.once('downloadFileCompleted', (event, filePath, fileName) => {
const downloadNotification = new Notification('Download Complete', {
body: shouldShowInFolder ? `Click to show ${fileName} in folder` : `Click to open ${fileName}`,
silent: true // We'll play our own sound - ding.ogg
});
// Play sound to indicate download complete
if (!ConfigUtil.getConfigItem('silent')) {
dingSound.play();
}
downloadNotification.onclick = () => {
if (shouldShowInFolder) {
// Reveal file in download folder
shell.showItemInFolder(filePath);
} else {
// Open file in the default native app
shell.openItem(filePath);
}
};
ipcRenderer.removeAllListeners('downloadFileFailed');
});
ipcRenderer.once('downloadFileFailed', () => {
// Automatic download failed, so show save dialog prompt and download
// through webview
this.$el.downloadURL(url);
ipcRenderer.removeAllListeners('downloadFileCompleted');
});
return;
}
// open internal urls inside the current webview.
// open internal urls inside the current webview.
this.$el.loadURL(url);
} else {
event.preventDefault();

View File

@@ -75,16 +75,11 @@ class WebView extends BaseComponent {
this.$el.addEventListener('page-favicon-updated', event => {
const { favicons } = event;
// This returns a string of favicons URL. If there is a PM counts in unread messages then the URL would be like
// https://chat.zulip.org/static/images/favicon/favicon-pms.png
if (favicons[0].indexOf('favicon-pms') > 0 && process.platform === 'darwin') {
// This api is only supported on macOS
app.dock.setBadge('●');
// bounce the dock
if (ConfigUtil.getConfigItem('dockBouncing')) {
app.dock.bounce();
}
}
});

View File

@@ -1,39 +0,0 @@
const events = require('events');
const { ipcRenderer } = require('electron');
// we have and will have some non camelcase stuff
// while working with zulip so just turning the rule off
// for the whole file.
/* eslint-disable camelcase */
class ElectronBridge extends events {
send_event(...args) {
this.emit(...args);
}
on_event(...args) {
this.on(...args);
}
}
const electron_bridge = new ElectronBridge();
electron_bridge.on('total_unread_count', (...args) => {
ipcRenderer.send('unread-count', ...args);
});
electron_bridge.on('realm_name', (...args) => {
ipcRenderer.send('realm-name-changed', ...args);
});
electron_bridge.on('realm_icon_url', iconURL => {
const serverURL = location.origin;
iconURL = iconURL.includes('http') ? iconURL : `${serverURL}${iconURL}`;
ipcRenderer.send('realm-icon-changed', serverURL, iconURL);
});
// this follows node's idiomatic implementation of event
// emitters to make event handling more simpler instead of using
// functions zulip side will emit event using ElectronBrigde.send_event
// which is alias of .emit and on this side we can handle the data by adding
// a listener for the event.
module.exports = electron_bridge;

View File

@@ -3,7 +3,7 @@
const { ipcRenderer, remote } = require('electron');
const isDev = require('electron-is-dev');
const { session, app } = remote;
const { session } = remote;
require(__dirname + '/js/tray.js');
const DomainUtil = require(__dirname + '/js/utils/domain-util.js');
@@ -13,16 +13,8 @@ const FunctionalTab = require(__dirname + '/js/components/functional-tab.js');
const ConfigUtil = require(__dirname + '/js/utils/config-util.js');
const DNDUtil = require(__dirname + '/js/utils/dnd-util.js');
const ReconnectUtil = require(__dirname + '/js/utils/reconnect-util.js');
const Logger = require(__dirname + '/js/utils/logger-util.js');
const CommonUtil = require(__dirname + '/js/utils/common-util.js');
const { feedbackHolder } = require(__dirname + '/js/feedback.js');
const logger = new Logger({
file: 'errors.log',
timestamp: true
});
class ServerManagerView {
constructor() {
this.$addServerButton = document.getElementById('add-tab');
@@ -66,17 +58,7 @@ class ServerManagerView {
loadProxy() {
return new Promise(resolve => {
// To change proxyEnable to useManualProxy in older versions
const proxyEnabledOld = ConfigUtil.isConfigItemExists('useProxy');
if (proxyEnabledOld) {
const proxyEnableOldState = ConfigUtil.getConfigItem('useProxy');
if (proxyEnableOldState) {
ConfigUtil.setConfigItem('useManualProxy', true);
}
ConfigUtil.removeConfigItem('useProxy');
}
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
const proxyEnabled = ConfigUtil.getConfigItem('useProxy', false);
if (proxyEnabled) {
session.fromPartition('persist:webviewsession').setProxy({
pacScript: ConfigUtil.getConfigItem('proxyPAC', ''),
@@ -100,8 +82,7 @@ class ServerManagerView {
// Default settings which should be respected
const settingOptions = {
trayIcon: true,
useManualProxy: false,
useSystemProxy: false,
useProxy: false,
showSidebar: true,
badgeOption: true,
startAtLogin: false,
@@ -116,9 +97,7 @@ class ServerManagerView {
dndPreviousSettings: {
showNotification: true,
silent: false
},
downloadsPath: `${app.getPath('downloads')}`,
showDownloadFolder: false
}
};
// Platform specific settings
@@ -129,11 +108,6 @@ class ServerManagerView {
settingOptions.dndPreviousSettings.flashTaskbarOnMessage = true;
}
if (process.platform === 'darwin') {
// Only available on macOS
settingOptions.dockBouncing = true;
}
for (const i in settingOptions) {
if (ConfigUtil.getConfigItem(i) === null) {
ConfigUtil.setConfigItem(i, settingOptions[i]);
@@ -179,7 +153,7 @@ class ServerManagerView {
index,
tabIndex,
url: server.url,
name: CommonUtil.decodeString(server.alias),
name: server.alias,
isActive: () => {
return index === this.activeTabIndex;
},
@@ -397,22 +371,6 @@ class ServerManagerView {
const webContents = webview.getWebContents();
webContents.send('toggle-dnd', state, newSettings);
});
ipcRenderer.on('update-realm-icon', (event, serverURL, iconURL) => {
DomainUtil.getDomains().forEach((domain, index) => {
if (domain.url.includes(serverURL)) {
DomainUtil.saveServerIcon(iconURL).then(localIconUrl => {
const serverImgsSelector = `.tab .server-icons`;
const serverImgs = document.querySelectorAll(serverImgsSelector);
serverImgs[index].src = localIconUrl;
domain.icon = localIconUrl;
DomainUtil.db.push(`/domains[${index}]`, domain, true);
DomainUtil.reloadDB();
});
}
});
});
}
destroyTab(name, index) {
@@ -537,7 +495,6 @@ class ServerManagerView {
this.loadProxy().then(() => {
if (showAlert) {
alert('Proxy settings saved!');
ipcRenderer.send('reload-full-app');
}
});
});
@@ -612,7 +569,7 @@ window.onload = () => {
window.addEventListener('offline', () => {
reconnectUtil.clearState();
logger.log('No internet connection, you are offline.');
console.log('No internet connection, you are offline.');
});
// only start electron-connect (auto reload on change) when its ran

View File

@@ -1,10 +1,4 @@
const { remote } = require('electron');
const Logger = require('../utils/logger-util.js');
const logger = new Logger({
file: 'errors.log',
timestamp: true
});
// Do not change this
const appId = 'org.zulip.zulip-electron';
@@ -34,8 +28,8 @@ function loadBots(sync = false) {
botsListLoaded = true;
})
.fail(error => {
logger.log('Load bots request failed: ', error.responseText);
logger.log('Load bots request status: ', error.statusText);
console.log('Request failed: ', error.responseText);
console.log('Request status: ', error.statusText);
});
if (sync) {
$.ajaxSetup({async: true});

View File

@@ -4,7 +4,6 @@ const {
remote: { app }
} = require('electron');
const params = require('../utils/params-util.js');
const DefaultNotification = require('./default-notification');
const { appId, loadBots } = require('./helpers');
@@ -20,8 +19,9 @@ if (process.platform === 'darwin') {
}
window.addEventListener('load', () => {
// Call this function only when user is logged in
// eslint-disable-next-line no-undef, camelcase
if (params.isPageParams() && page_params.realm_uri) {
if (page_params.realm_uri) {
loadBots();
}
});

View File

@@ -1,91 +0,0 @@
'use-strict';
const { dialog } = require('electron').remote;
const BaseComponent = require(__dirname + '/../../components/base.js');
const CertificateUtil = require(__dirname + '/../../utils/certificate-util.js');
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
class AddCertificate extends BaseComponent {
constructor(props) {
super();
this.props = props;
this._certFile = '';
}
template() {
return `
<div class="settings-card server-center certificates-card">
<div class="certificate-input">
<div>Organization URL :</div>
<input class="setting-input-value" autofocus placeholder="your-organization.zulipchat.com or zulip.your-organization.com"/>
</div>
<div class="certificate-input">
<div>Custom CA's certificate file :</div>
<button id="add-certificate-button">Add</button>
</div>
</div>
`;
}
init() {
this.$addCertificate = this.generateNodeFromTemplate(this.template());
this.props.$root.appendChild(this.$addCertificate);
this.addCertificateButton = this.$addCertificate.querySelector('#add-certificate-button');
this.serverUrl = this.$addCertificate.querySelectorAll('input.setting-input-value')[0];
this.initListeners();
}
validateAndAdd() {
const certificate = this._certFile;
const serverUrl = this.serverUrl.value;
if (certificate !== '' && serverUrl !== '') {
const server = encodeURIComponent(DomainUtil.formatUrl(serverUrl));
const fileName = certificate.substring(certificate.lastIndexOf('/') + 1);
const copy = CertificateUtil.copyCertificate(server, certificate, fileName);
if (!copy) {
return;
}
CertificateUtil.setCertificate(server, fileName);
dialog.showMessageBox({
title: 'Success',
message: `Certificate saved!`
});
this.serverUrl.value = '';
} else {
dialog.showErrorBox('Error', `Please, ${serverUrl === '' ?
'Enter an Organization URL' : 'Choose certificate file'}`);
}
}
addHandler() {
const showDialogOptions = {
title: 'Select file',
defaultId: 1,
properties: ['openFile'],
filters: [{ name: 'crt, pem', extensions: ['crt', 'pem'] }]
};
dialog.showOpenDialog(showDialogOptions, selectedFile => {
if (selectedFile) {
this._certFile = selectedFile[0] || '';
this.validateAndAdd();
}
});
}
initListeners() {
this.addCertificateButton.addEventListener('click', () => {
this.addHandler();
});
this.serverUrl.addEventListener('keypress', event => {
const EnterkeyCode = event.keyCode;
if (EnterkeyCode === 13) {
this.addHandler();
}
});
}
}
module.exports = AddCertificate;

View File

@@ -3,7 +3,6 @@
const BaseSection = require(__dirname + '/base-section.js');
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
const ServerInfoForm = require(__dirname + '/server-info-form.js');
const AddCertificate = require(__dirname + '/add-certificate.js');
class ConnectedOrgSection extends BaseSection {
constructor(props) {
@@ -17,9 +16,6 @@ class ConnectedOrgSection extends BaseSection {
<div class="page-title">Connected organizations</div>
<div class="title" id="existing-servers">All the connected orgnizations will appear here.</div>
<div id="server-info-container"></div>
<div class="page-title">Add Custom Certificates</div>
<div id="add-certificate-container"></div>
</div>
`;
}
@@ -48,15 +44,6 @@ class ConnectedOrgSection extends BaseSection {
onChange: this.reloadApp
}).init();
}
this.$addCertificateContainer = document.getElementById('add-certificate-container');
this.initAddCertificate();
}
initAddCertificate() {
new AddCertificate({
$root: this.$addCertificateContainer
}).init();
}
}

View File

@@ -24,17 +24,13 @@ class GeneralSection extends BaseSection {
<div class="setting-control"></div>
</div>
<div class="setting-row" id="sidebar-option">
<div class="setting-description">Show sidebar (<span class="code">Cmd Or Ctrl+Shift+S</span>)</div>
<div class="setting-description">Show sidebar (<span class="code">Cmd Or Ctrl+S</span>)</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="badge-option">
<div class="setting-description">Show app unread badge</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="dock-bounce-option" style= "display:${process.platform === 'darwin' ? '' : 'none'}">
<div class="setting-description">Bounce dock on new private message</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="flash-taskbar-option" style= "display:${process.platform === 'win32' ? '' : 'none'}">
<div class="setting-description">Flash taskbar on new message</div>
<div class="setting-control"></div>
@@ -95,25 +91,6 @@ class GeneralSection extends BaseSection {
</div>
</div>
</div>
<div class="title">Advanced</div>
<div class="settings-card">
<div class="setting-row" id="show-download-folder">
<div class="setting-description">Show downloaded file in the file manager</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="download-folder">
<div class="setting-description">
Default download location
</div>
<button class="download-folder-button blue">Choose</button>
</div>
<div class="setting-row">
<div class="setting-description">
<div class="download-folder-path">${ConfigUtil.getConfigItem('downloadsPath', `${app.getPath('downloads')}`)}</div>
</div>
</div>
</div>
<div class="title">Reset Application Data</div>
<div class="settings-card">
<div class="setting-row" id="resetdata-option">
@@ -142,19 +119,12 @@ class GeneralSection extends BaseSection {
this.addCustomCSS();
this.showCustomCSSPath();
this.removeCustomCSS();
this.downloadFolder();
this.showDownloadFolder();
// Platform specific settings
// Flashing taskbar on Windows
if (process.platform === 'win32') {
this.updateFlashTaskbar();
}
// Dock bounce on macOS
if (process.platform === 'darwin') {
this.updateDockBouncing();
}
}
updateTrayOption() {
@@ -183,18 +153,6 @@ class GeneralSection extends BaseSection {
});
}
updateDockBouncing() {
this.generateSettingOption({
$element: document.querySelector('#dock-bounce-option .setting-control'),
value: ConfigUtil.getConfigItem('dockBouncing', true),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('dockBouncing');
ConfigUtil.setConfigItem('dockBouncing', newValue);
this.updateDockBouncing();
}
});
}
updateFlashTaskbar() {
this.generateSettingOption({
$element: document.querySelector('#flash-taskbar-option .setting-control'),
@@ -369,40 +327,6 @@ class GeneralSection extends BaseSection {
});
}
downloadFolderDialog() {
const showDialogOptions = {
title: 'Select Download Location',
defaultId: 1,
properties: ['openDirectory']
};
dialog.showOpenDialog(showDialogOptions, selectedFolder => {
if (selectedFolder) {
ConfigUtil.setConfigItem('downloadsPath', selectedFolder[0]);
const downloadFolderPath = document.querySelector('.download-folder-path');
downloadFolderPath.innerText = selectedFolder[0];
}
});
}
downloadFolder() {
const downloadFolder = document.querySelector('#download-folder .download-folder-button');
downloadFolder.addEventListener('click', () => {
this.downloadFolderDialog();
});
}
showDownloadFolder() {
this.generateSettingOption({
$element: document.querySelector('#show-download-folder .setting-control'),
value: ConfigUtil.getConfigItem('showDownloadFolder', false),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('showDownloadFolder');
ConfigUtil.setConfigItem('showDownloadFolder', newValue);
this.showDownloadFolder();
}
});
}
}
module.exports = GeneralSection;

View File

@@ -16,15 +16,11 @@ class NetworkSection extends BaseSection {
<div class="settings-pane">
<div class="title">Proxy</div>
<div id="appearance-option-settings" class="settings-card">
<div class="setting-row" id="use-system-settings">
<div class="setting-description">Use system proxy settings (requires restart)</div>
<div class="setting-row" id="use-proxy-option">
<div class="setting-description">Connect servers through a proxy</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="use-manual-settings">
<div class="setting-description">Manual proxy configuration</div>
<div class="setting-control"></div>
</div>
<div class="manual-proxy-block">
<div class="setting-block">
<div class="setting-row" id="proxy-pac-option">
<span class="setting-input-key">PAC script</span>
<input class="setting-input-value" placeholder="e.g. foobar.com/pacfile.js"/>
@@ -55,7 +51,7 @@ class NetworkSection extends BaseSection {
this.$proxyRules = document.querySelector('#proxy-rules-option .setting-input-value');
this.$proxyBypass = document.querySelector('#proxy-bypass-option .setting-input-value');
this.$proxySaveAction = document.getElementById('proxy-save-action');
this.$manualProxyBlock = this.props.$root.querySelector('.manual-proxy-block');
this.$settingBlock = this.props.$root.querySelector('.setting-block');
this.initProxyOption();
this.$proxyPAC.value = ConfigUtil.getConfigItem('proxyPAC', '');
@@ -72,54 +68,31 @@ class NetworkSection extends BaseSection {
}
initProxyOption() {
const manualProxyEnabled = ConfigUtil.getConfigItem('useManualProxy', false);
this.toggleManualProxySettings(manualProxyEnabled);
const proxyEnabled = ConfigUtil.getConfigItem('useProxy', false);
this.toggleProxySettings(proxyEnabled);
this.updateProxyOption();
}
toggleManualProxySettings(option) {
toggleProxySettings(option) {
if (option) {
this.$manualProxyBlock.classList.remove('hidden');
this.$settingBlock.classList.remove('hidden');
} else {
this.$manualProxyBlock.classList.add('hidden');
this.$settingBlock.classList.add('hidden');
}
}
updateProxyOption() {
this.generateSettingOption({
$element: document.querySelector('#use-system-settings .setting-control'),
value: ConfigUtil.getConfigItem('useSystemProxy', false),
$element: document.querySelector('#use-proxy-option .setting-control'),
value: ConfigUtil.getConfigItem('useProxy', false),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('useSystemProxy');
const manualProxyValue = ConfigUtil.getConfigItem('useManualProxy');
if (manualProxyValue && newValue) {
ConfigUtil.setConfigItem('useManualProxy', !manualProxyValue);
this.toggleManualProxySettings(!manualProxyValue);
}
const newValue = !ConfigUtil.getConfigItem('useProxy');
ConfigUtil.setConfigItem('useProxy', newValue);
this.toggleProxySettings(newValue);
if (newValue === false) {
// Remove proxy system proxy settings
ConfigUtil.setConfigItem('proxyRules', '');
ipcRenderer.send('forward-message', 'reload-proxy', true);
// Reload proxy if the proxy is turned off
ipcRenderer.send('forward-message', 'reload-proxy', false);
}
ConfigUtil.setConfigItem('useSystemProxy', newValue);
this.updateProxyOption();
}
});
this.generateSettingOption({
$element: document.querySelector('#use-manual-settings .setting-control'),
value: ConfigUtil.getConfigItem('useManualProxy', false),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('useManualProxy');
const systemProxyValue = ConfigUtil.getConfigItem('useSystemProxy');
this.toggleManualProxySettings(newValue);
if (systemProxyValue && newValue) {
ConfigUtil.setConfigItem('useSystemProxy', !systemProxyValue);
}
ConfigUtil.setConfigItem('proxyRules', '');
ConfigUtil.setConfigItem('useManualProxy', newValue);
// Reload app only when turning manual proxy off, hence !newValue
ipcRenderer.send('forward-message', 'reload-proxy', !newValue);
this.updateProxyOption();
}
});

View File

@@ -1,7 +1,6 @@
'use strict';
const BaseSection = require(__dirname + '/base-section.js');
const shell = require('electron').shell;
class ShortcutsSection extends BaseSection {
constructor(props) {
@@ -160,7 +159,6 @@ class ShortcutsSection extends BaseSection {
</table>
<div class="setting-control"></div>
</div>
<div class="settings-card tip"><b><i class="material-icons md-14">settings</i>Tip: </b>These desktop app shortcuts extend the Zulip webapp's <span id="open-hotkeys-link">keyboard shortcuts</span>.</div>
</div>
`;
}
@@ -300,22 +298,13 @@ class ShortcutsSection extends BaseSection {
</table>
<div class="setting-control"></div>
</div>
<div class="tip"><b><i class="material-icons md-14">lightbulb_outline</i>Tip: </b>These desktop app shortcuts extend the Zulip webapp's <span id="open-hotkeys-link">keyboard shortcuts</span>.</div>
</div>
`;
}
openHotkeysExternalLink() {
const link = 'https://zulipchat.com/help/keyboard-shortcuts';
const externalCreateNewOrgEl = document.getElementById('open-hotkeys-link');
externalCreateNewOrgEl.addEventListener('click', () => {
shell.openExternal(link);
});
}
init() {
this.props.$root.innerHTML = (process.platform === 'darwin') ?
this.templateMac() : this.templateWinLin();
this.openHotkeysExternalLink();
}
}

View File

@@ -1,11 +1,10 @@
'use strict';
const { ipcRenderer, shell } = require('electron');
const { ipcRenderer } = require('electron');
const SetupSpellChecker = require('./spellchecker');
const ConfigUtil = require(__dirname + '/utils/config-util.js');
const LinkUtil = require(__dirname + '/utils/link-util.js');
const params = require(__dirname + '/utils/params-util.js');
// eslint-disable-next-line import/no-unassigned-import
require('./notification');
@@ -13,9 +12,6 @@ require('./notification');
// Prevent drag and drop event in main process which prevents remote code executaion
require(__dirname + '/shared/preventdrag.js');
// eslint-disable-next-line camelcase
window.electron_bridge = require('./electron-bridge');
const logout = () => {
// Create the menu for the below
document.querySelector('.dropdown-toggle').click();
@@ -43,44 +39,41 @@ process.once('loaded', () => {
// To prevent failing this script on linux we need to load it after the document loaded
document.addEventListener('DOMContentLoaded', () => {
if (params.isPageParams()) {
// Get the default language of the server
const serverLanguage = page_params.default_language; // eslint-disable-line no-undef, camelcase
if (serverLanguage) {
// Set spellcheker language
ConfigUtil.setConfigItem('spellcheckerLanguage', serverLanguage);
// Init spellchecker
SetupSpellChecker.init();
}
// redirect users to network troubleshooting page
const getRestartButton = document.querySelector('.restart_get_events_button');
if (getRestartButton) {
getRestartButton.addEventListener('click', () => {
ipcRenderer.send('forward-message', 'reload-viewer');
});
}
// Open image attachment link in the lightbox instead of opening in the default browser
const { $, lightbox } = window;
$('#main_div').on('click', '.message_content p a', function (e) {
const url = $(this).attr('href');
const serverLanguage = page_params.default_language; // eslint-disable-line no-undef, camelcase
if (LinkUtil.isImage(url)) {
const $img = $(this).parent().siblings('.message_inline_image').find('img');
if (serverLanguage) {
// Set spellcheker language
ConfigUtil.setConfigItem('spellcheckerLanguage', serverLanguage);
// Init spellchecker
SetupSpellChecker.init();
}
// prevent the image link from opening in a new page.
e.preventDefault();
// prevent the message compose dialog from happening.
e.stopPropagation();
// Open image in the default browser if image preview is unavailable
if (!$img[0]) {
shell.openExternal(window.location.origin + url);
}
// Open image in lightbox
lightbox.open($img);
}
// redirect users to network troubleshooting page
const getRestartButton = document.querySelector('.restart_get_events_button');
if (getRestartButton) {
getRestartButton.addEventListener('click', () => {
ipcRenderer.send('forward-message', 'reload-viewer');
});
}
// Open image attachment link in the lightbox instead of opening in the default browser
const { $, lightbox } = window;
$('#main_div').on('click', '.message_content p a', function (e) {
const url = $(this).attr('href');
if (LinkUtil.isImage(url)) {
const $img = $(this).parent().siblings('.message_inline_image').find('img');
// prevent the image link from opening in a new page.
e.preventDefault();
// prevent the message compose dialog from happening.
e.stopPropagation();
lightbox.open($img);
}
});
});
// Clean up spellchecker events after you navigate away from this page;

View File

@@ -3,12 +3,6 @@
const { SpellCheckHandler, ContextMenuListener, ContextMenuBuilder } = require('electron-spellchecker');
const ConfigUtil = require(__dirname + '/utils/config-util.js');
const Logger = require(__dirname + '/utils/logger-util.js');
const logger = new Logger({
file: 'errors.log',
timestamp: true
});
class SetupSpellChecker {
init() {
@@ -22,7 +16,7 @@ class SetupSpellChecker {
try {
this.SpellCheckHandler = new SpellCheckHandler();
} catch (err) {
logger.error(err);
console.log(err);
}
}

View File

@@ -1,86 +0,0 @@
'use strict';
const { app, dialog } = require('electron').remote;
const fs = require('fs');
const path = require('path');
const JsonDB = require('node-json-db');
const Logger = require('./logger-util');
const { initSetUp } = require('./default-util');
initSetUp();
const logger = new Logger({
file: `certificate-util.log`,
timestamp: true
});
let instance = null;
const certificatesDir = `${app.getPath('userData')}/certificates`;
class CertificateUtil {
constructor() {
if (instance) {
return instance;
} else {
instance = this;
}
this.reloadDB();
return instance;
}
getCertificate(server, defaultValue = null) {
this.reloadDB();
const value = this.db.getData('/')[server];
if (value === undefined) {
return defaultValue;
} else {
return value;
}
}
// Function to copy the certificate to userData folder
copyCertificate(server, location, fileName) {
let copied = false;
const filePath = `${certificatesDir}/${fileName}`;
try {
fs.copyFileSync(location, filePath);
copied = true;
} catch (err) {
dialog.showErrorBox(
'Error saving certificate',
'We encountered error while saving the certificate.'
);
logger.error('Error while copying the certificate to certificates folder.');
logger.error(err);
}
return copied;
}
setCertificate(server, fileName) {
const filePath = `${certificatesDir}/${fileName}`;
this.db.push(`/${server}`, filePath, true);
this.reloadDB();
}
removeCertificate(server) {
this.db.delete(`/${server}`);
this.reloadDB();
}
reloadDB() {
const settingsJsonPath = path.join(app.getPath('userData'), '/config/certificates.json');
try {
const file = fs.readFileSync(settingsJsonPath, 'utf8');
JSON.parse(file);
} catch (err) {
if (fs.existsSync(settingsJsonPath)) {
fs.unlinkSync(settingsJsonPath);
dialog.showErrorBox(
'Error saving settings',
'We encountered error while saving the certificate.'
);
logger.error('Error while JSON parsing certificates.json: ');
logger.error(err);
}
}
this.db = new JsonDB(settingsJsonPath, true, true);
}
}
module.exports = new CertificateUtil();

View File

@@ -1,25 +0,0 @@
'use strict';
let instance = null;
class CommonUtil {
constructor() {
if (instance) {
return instance;
} else {
instance = this;
}
return instance;
}
// unescape already encoded/escaped strings
decodeString(string) {
const parser = new DOMParser();
const dom = parser.parseFromString(
'<!doctype html><body>' + string,
'text/html');
return dom.body.textContent;
}
}
module.exports = new CommonUtil();

View File

@@ -48,12 +48,6 @@ class ConfigUtil {
return value;
}
}
// This function returns whether a key exists in the configuration file (settings.json)
isConfigItemExists(key) {
this.reloadDB();
const value = this.db.getData('/')[key];
return (value !== undefined);
}
setConfigItem(key, value) {
this.db.push(`/${key}`, value, true);
@@ -66,7 +60,7 @@ class ConfigUtil {
}
reloadDB() {
const settingsJsonPath = path.join(app.getPath('userData'), '/config/settings.json');
const settingsJsonPath = path.join(app.getPath('userData'), '/settings.json');
try {
const file = fs.readFileSync(settingsJsonPath, 'utf8');
JSON.parse(file);
@@ -75,11 +69,10 @@ class ConfigUtil {
fs.unlinkSync(settingsJsonPath);
dialog.showErrorBox(
'Error saving settings',
'We encountered an error while saving the settings.'
'We encountered error while saving current settings.'
);
logger.error('Error while JSON parsing settings.json: ');
logger.error(err);
logger.reportSentry(err);
}
}
this.db = new JsonDB(settingsJsonPath, true, true);

View File

@@ -10,8 +10,6 @@ if (process.type === 'renderer') {
const zulipDir = app.getPath('userData');
const logDir = `${zulipDir}/Logs/`;
const certificatesDir = `${zulipDir}/certificates/`;
const configDir = `${zulipDir}/config/`;
const initSetUp = () => {
// if it is the first time the app is running
// create zulip dir in userData folder to
@@ -24,51 +22,6 @@ const initSetUp = () => {
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir);
}
if (!fs.existsSync(certificatesDir)) {
fs.mkdirSync(certificatesDir);
}
// Migrate config files from app data folder to config folder inside app
// data folder. This will be done once when a user updates to the new version.
if (!fs.existsSync(configDir)) {
fs.mkdirSync(configDir);
const domainJson = `${zulipDir}/domain.json`;
const certificatesJson = `${zulipDir}/certificates.json`;
const settingsJson = `${zulipDir}/settings.json`;
const updatesJson = `${zulipDir}/updates.json`;
const windowStateJson = `${zulipDir}/window-state.json`;
const configData = [
{
path: domainJson,
fileName: `domain.json`
},
{
path: certificatesJson,
fileName: `certificates.json`
},
{
path: settingsJson,
fileName: `settings.json`
},
{
path: updatesJson,
fileName: `updates.json`
}
];
configData.forEach(data => {
if (fs.existsSync(data.path)) {
fs.copyFileSync(data.path, configDir + data.fileName);
fs.unlinkSync(data.path);
}
});
// window-state.json is only deleted not moved, as the electron-window-state
// package will recreate the file in the config folder.
if (fs.existsSync(windowStateJson)) {
fs.unlinkSync(windowStateJson);
}
}
setupCompleted = true;
}
};

View File

@@ -5,14 +5,8 @@ const fs = require('fs');
const path = require('path');
const JsonDB = require('node-json-db');
const request = require('request');
const escape = require('escape-html');
const Logger = require('./logger-util');
const CertificateUtil = require(__dirname + '/certificate-util.js');
const ProxyUtil = require(__dirname + '/proxy-util.js');
const ConfigUtil = require(__dirname + '/config-util.js');
const logger = new Logger({
file: `domain-util.log`,
timestamp: true
@@ -110,27 +104,7 @@ class DomainUtil {
domain = this.formatUrl(domain);
const certificate = CertificateUtil.getCertificate(encodeURIComponent(domain));
let certificateLocation = '';
if (certificate) {
// To handle case where certificate has been moved from the location in certificates.json
try {
certificateLocation = fs.readFileSync(certificate);
} catch (err) {
logger.warn('Error while trying to get certificate: ' + err);
}
}
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
// If certificate for the domain exists add it as a ca key in the request's parameter else consider only domain as the parameter for request
// Add proxy as a parameter if it sbeing used.
const checkDomain = {
url: domain + '/static/audio/zulip.ogg',
ca: (certificateLocation) ? certificateLocation : '',
proxy: proxyEnabled ? ProxyUtil.getProxy(domain) : ''
};
const checkDomain = domain + '/static/audio/zulip.ogg';
const serverConf = {
icon: defaultIconUrl,
@@ -140,16 +114,21 @@ class DomainUtil {
return new Promise((resolve, reject) => {
request(checkDomain, (error, response) => {
const certsError =
[
'Error: self signed certificate',
'Error: unable to verify the first certificate',
'Error: unable to get local issuer certificate'
];
// If the domain contains following strings we just bypass the server
const whitelistDomains = [
'zulipdev.org'
];
// make sure that error is an error or string not undefined
// make sure that error is a error or string not undefined
// so validation does not throw error.
error = error || '';
const certsError = error.toString().includes('certificate');
if (!error && response.statusCode < 400) {
// Correct
this.getServerSettings(domain).then(serverSettings => {
@@ -157,7 +136,7 @@ class DomainUtil {
}, () => {
resolve(serverConf);
});
} else if (domain.indexOf(whitelistDomains) >= 0 || certsError) {
} else if (domain.indexOf(whitelistDomains) >= 0 || certsError.indexOf(error.toString()) >= 0) {
if (silent) {
this.getServerSettings(domain).then(serverSettings => {
resolve(serverSettings);
@@ -165,19 +144,15 @@ class DomainUtil {
resolve(serverConf);
});
} else {
// Report error to sentry to get idea of possible certificate errors
// users get when adding the servers
logger.reportSentry(new Error(error));
const certErrorMessage = `Do you trust certificate from ${domain}? \n ${error}`;
const certErrorDetail = `The organization you're connecting to is either someone impersonating the Zulip server you entered, or the server you're trying to connect to is configured in an insecure way.
\nIf you have a valid certificate please add it from Settings>Organizations and try to add the organization again.
\nUnless you have a good reason to believe otherwise, you should not proceed.
\nYou can click here if you'd like to proceed with the connection.`;
const certErrorDetail = `The server you're connecting to is either someone impersonating the Zulip server you entered, or the server you're trying to connect to is configured in an insecure way.
\n Unless you have a good reason to believe otherwise, you should not proceed.
\n You can click here if you'd like to proceed with the connection.`;
dialog.showMessageBox({
type: 'warning',
buttons: ['Yes', 'No'],
defaultId: 1,
defaultId: 0,
message: certErrorMessage,
detail: certErrorDetail
}, response => {
@@ -194,8 +169,7 @@ class DomainUtil {
}
} else {
const invalidZulipServerError = `${domain} does not appear to be a valid Zulip server. Make sure that \
\n (1) you can connect to that URL in a web browser and \n (2) if you need a proxy to connect to the Internet, that you've configured your proxy in the Network settings \n (3) its a zulip server \
\n (4) the server has a valid certificate, you can add custom certificates in Settings>Organizations`;
\n(1) you can connect to that URL in a web browser and \n (2) if you need a proxy to connect to the Internet, that you've configured your proxy in the Network settings \n (3) its a zulip server`;
reject(invalidZulipServerError);
}
});
@@ -203,13 +177,9 @@ class DomainUtil {
}
getServerSettings(domain) {
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
const serverSettingsOptions = {
url: domain + '/api/v1/server_settings',
proxy: proxyEnabled ? ProxyUtil.getProxy(domain) : ''
};
const serverSettingsUrl = domain + '/api/v1/server_settings';
return new Promise((resolve, reject) => {
request(serverSettingsOptions, (error, response) => {
request(serverSettingsUrl, (error, response) => {
if (!error && response.statusCode === 200) {
const data = JSON.parse(response.body);
if (data.hasOwnProperty('realm_icon') && data.realm_icon) {
@@ -218,7 +188,7 @@ class DomainUtil {
// Following check handles both the cases
icon: data.realm_icon.startsWith('/') ? data.realm_uri + data.realm_icon : data.realm_icon,
url: data.realm_uri,
alias: escape(data.realm_name)
alias: data.realm_name
});
}
} else {
@@ -229,36 +199,25 @@ class DomainUtil {
}
saveServerIcon(url) {
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
const serverIconOptions = {
url,
proxy: proxyEnabled ? ProxyUtil.getProxy(url) : ''
};
// The save will always succeed. If url is invalid, downgrade to default icon.
return new Promise(resolve => {
const filePath = this.generateFilePath(url);
const file = fs.createWriteStream(filePath);
try {
request(serverIconOptions).on('response', response => {
request(url).on('response', response => {
response.on('error', err => {
logger.log('Could not get server icon.');
logger.log(err);
logger.reportSentry(err);
console.log(err);
resolve(defaultIconUrl);
});
response.pipe(file).on('finish', () => {
resolve(filePath);
});
}).on('error', err => {
logger.log('Could not get server icon.');
logger.log(err);
logger.reportSentry(err);
console.log(err);
resolve(defaultIconUrl);
});
} catch (err) {
logger.log('Could not get server icon.');
logger.log(err);
logger.reportSentry(err);
console.log(err);
resolve(defaultIconUrl);
}
});
@@ -276,7 +235,7 @@ class DomainUtil {
}
reloadDB() {
const domainJsonPath = path.join(app.getPath('userData'), 'config/domain.json');
const domainJsonPath = path.join(app.getPath('userData'), '/domain.json');
try {
const file = fs.readFileSync(domainJsonPath, 'utf8');
JSON.parse(file);
@@ -290,7 +249,6 @@ class DomainUtil {
);
logger.error('Error while JSON parsing domain.json: ');
logger.error(err);
logger.reportSentry(err);
}
}
this.db = new JsonDB(domainJsonPath, true, true);

View File

@@ -34,12 +34,6 @@ class LinkUtil {
const isImageUrl = /\.(bmp|gif|jpg|jpeg|png|webp)\?*.*$/i;
return isImageUrl.test(url);
}
isPDF(url) {
// test for pdf extension
const isPDFUrl = /\.(pdf)\?*.*$/i;
return isPDFUrl.test(url);
}
}
module.exports = new LinkUtil();

View File

@@ -52,7 +52,7 @@ class LinuxUpdateUtil {
}
reloadDB() {
const linuxUpdateJsonPath = path.join(app.getPath('userData'), '/config/updates.json');
const linuxUpdateJsonPath = path.join(app.getPath('userData'), '/updates.json');
try {
const file = fs.readFileSync(linuxUpdateJsonPath, 'utf8');
JSON.parse(file);
@@ -61,7 +61,7 @@ class LinuxUpdateUtil {
fs.unlinkSync(linuxUpdateJsonPath);
dialog.showErrorBox(
'Error saving update notifications.',
'We encountered an error while saving the update notifications.'
'We encountered error while saving update notifications.'
);
logger.error('Error while JSON parsing updates.json: ');
logger.error(err);

View File

@@ -2,10 +2,8 @@ const NodeConsole = require('console').Console;
const fs = require('fs');
const isDev = require('electron-is-dev');
const { initSetUp } = require('./default-util');
const { sentryInit, captureException } = require('./sentry-util');
initSetUp();
sentryInit();
let app = null;
if (process.type === 'renderer') {
app = require('electron').remote.app;
@@ -84,10 +82,6 @@ class Logger {
`${date.getMinutes()}:${date.getSeconds()}`;
return timestamp;
}
reportSentry(err) {
captureException(err);
}
}
module.exports = Logger;

View File

@@ -1,15 +0,0 @@
// This util function returns the page params if they're present else returns null
function isPageParams() {
let webpageParams = null;
try {
// eslint-disable-next-line no-undef, camelcase
webpageParams = page_params;
} catch (err) {
webpageParams = null;
}
return webpageParams;
}
module.exports = {
isPageParams
};

View File

@@ -1,130 +0,0 @@
'use strict';
const url = require('url');
const ConfigUtil = require('./config-util.js');
let instance = null;
class ProxyUtil {
constructor() {
if (instance) {
return instance;
} else {
instance = this;
}
return instance;
}
// Return proxy to be used for a particular uri, to be used for request
getProxy(uri) {
uri = url.parse(uri);
const proxyRules = ConfigUtil.getConfigItem('proxyRules', '').split(';');
// If SPS is on and system uses no proxy then request should not try to use proxy from
// environment. NO_PROXY = '*' makes request ignore all environment proxy variables.
if (proxyRules[0] === '') {
process.env.NO_PROXY = '*';
return;
}
const proxyRule = {};
if (uri.protocol === 'http:') {
proxyRules.forEach(proxy => {
if (proxy.includes('http=')) {
proxyRule.hostname = proxy.split('http=')[1].trim().split(':')[0];
proxyRule.port = proxy.split('http=')[1].trim().split(':')[1];
}
});
return proxyRule;
}
if (uri.protocol === 'https:') {
proxyRules.forEach(proxy => {
if (proxy.includes('https=')) {
proxyRule.hostname = proxy.split('https=')[1].trim().split(':')[0];
proxyRule.port = proxy.split('https=')[1].trim().split(':')[1];
}
});
return proxyRule;
}
}
resolveSystemProxy(mainWindow) {
const page = mainWindow.webContents;
const ses = page.session;
const resolveProxyUrl = 'www.google.com';
// Check HTTP Proxy
const httpProxy = new Promise(resolve => {
ses.resolveProxy('http://' + resolveProxyUrl, proxy => {
let httpString = '';
if (proxy !== 'DIRECT') {
// in case of proxy HTTPS url:port, windows gives first word as HTTPS while linux gives PROXY
// for all other HTTP or direct url:port both uses PROXY
if (proxy.includes('PROXY') || proxy.includes('HTTPS')) {
httpString = 'http=' + proxy.split('PROXY')[1] + ';';
}
}
resolve(httpString);
});
});
// Check HTTPS Proxy
const httpsProxy = new Promise(resolve => {
ses.resolveProxy('https://' + resolveProxyUrl, proxy => {
let httpsString = '';
if (proxy !== 'DIRECT' || proxy.includes('HTTPS')) {
// in case of proxy HTTPS url:port, windows gives first word as HTTPS while linux gives PROXY
// for all other HTTP or direct url:port both uses PROXY
if (proxy.includes('PROXY' || proxy.includes('HTTPS'))) {
httpsString += 'https=' + proxy.split('PROXY')[1] + ';';
}
}
resolve(httpsString);
});
});
// Check FTP Proxy
const ftpProxy = new Promise(resolve => {
ses.resolveProxy('ftp://' + resolveProxyUrl, proxy => {
let ftpString = '';
if (proxy !== 'DIRECT') {
if (proxy.includes('PROXY')) {
ftpString += 'ftp=' + proxy.split('PROXY')[1] + ';';
}
}
resolve(ftpString);
});
});
// Check SOCKS Proxy
const socksProxy = new Promise(resolve => {
ses.resolveProxy('socks4://' + resolveProxyUrl, proxy => {
let socksString = '';
if (proxy !== 'DIRECT') {
if (proxy.includes('SOCKS5')) {
socksString += 'socks=' + proxy.split('SOCKS5')[1] + ';';
} else if (proxy.includes('SOCKS4')) {
socksString += 'socks=' + proxy.split('SOCKS4')[1] + ';';
} else if (proxy.includes('PROXY')) {
socksString += 'socks=' + proxy.split('PROXY')[1] + ';';
}
}
resolve(socksString);
});
});
Promise.all([httpProxy, httpsProxy, ftpProxy, socksProxy]).then(values => {
let proxyString = '';
values.forEach(proxy => {
proxyString += proxy;
});
ConfigUtil.setConfigItem('systemProxyRules', proxyString);
const useSystemProxy = ConfigUtil.getConfigItem('useSystemProxy');
if (useSystemProxy) {
ConfigUtil.setConfigItem('proxyRules', proxyString);
}
});
}
}
module.exports = new ProxyUtil();

View File

@@ -1,10 +1,4 @@
const isOnline = require('is-online');
const Logger = require('./logger-util');
const logger = new Logger({
file: `domain-util.log`,
timestamp: true
});
class ReconnectUtil {
constructor(serverManagerView) {
@@ -37,11 +31,11 @@ class ReconnectUtil {
if (!this.alreadyReloaded) {
this.serverManagerView.reloadView();
}
logger.log('You\'re back online.');
console.log('You\'re back online.');
return resolve(true);
}
logger.log('There is no internet connection, try checking network cables, modem and router.');
console.log('There is no internet connection, try checking network cables, modem and router.');
const errMsgHolder = document.querySelector('#description');
if (errMsgHolder) {
errMsgHolder.innerHTML = `

View File

@@ -1,16 +0,0 @@
const { init, captureException } = require('@sentry/electron');
const isDev = require('electron-is-dev');
const sentryInit = () => {
if (!isDev) {
init({
dsn: 'SENTRY_DSN',
sendTimeout: 30 // wait 30 seconds before considering the sending capture to have failed, default is 1 second
});
}
};
module.exports = {
sentryInit,
captureException
};

Binary file not shown.

View File

@@ -6,9 +6,9 @@ os: Previous Visual Studio 2015
cache:
- node_modules
install:
- ps: Install-Product node 8 x64
- ps: Install-Product node 6 x64
- git reset --hard HEAD
- npm install npm -g
- node --version
@@ -21,4 +21,4 @@ build: off
test_script:
- npm run test
# - npm run test-e2e
- npm run test-e2e

View File

@@ -1,722 +0,0 @@
# Version History
All notable changes to the Zulip desktop app are documented in this file.
### v2.3.6 --2018-08-27
**New features**:
* Add proxy details while validating a server. This fixes the server validating issue for users who are using the proxy settings.
**Fixes**:
* Fix youtube video not playing in lightbox.
* Fix realm name not escaped properly.
<hr>
### v2.3.5 --2018-08-03
**New features**:
* Add a setting option to show downloaded file in file manager.
* Added electron bridge to communicate with webapp in real time.
**Fixes**:
* Fix failing attached file downloads.
* Fix page_params error.
* gulpfile: Update syntax and methods for gulp v4.x.
<hr>
### v2.3.4-beta --2018-07-24
**Fixes**:
* Fix downloading functionality of file attachments.
* Fix null of downloadPath when settings.json fails.
<hr>
### v2.3.3 --2018-07-14
**Enhancements**:
* Add dock bounce effect on macOS
* Add a setting option to use the system proxy settings
* Add support for self/custom signed certificate
* Add Sentry support to get the bug reports
* Show a notification when a user clicks on file attachments and open the same in default native app
**Fixes**:
* Fix auto-updates on Windows
* Fix image attachments not opening up in the app
* Security fix - Do proper HTML escaping for server data to avoid XSS attacks
* Other minor fixes
**Updated dependencies**:
electron-builder: v20.20.4
electron-updater: v2.23.3
<hr>
### v2.3.2 --2018-05-28
This is a quick release since we recently updated the certificate for code signing the app and we want to make sure the previous versions of the app get auto-updates without any conflicts.
<hr>
### v2.3.1 --2018-05-23
**Enhancements**:
* Add a new setting to disable auto-updates
* Add a menu item to check for updates manually
* Other minor improvements
**Fixes**:
* Fix app not rendering colors properly
* **Security fix** - Do proper HTML escaping for server data to avoid XSS attacks
* Fix loading indicator when server is loaded
* Other minor fixes
**Updated dependencies**:
electron - `v2.0.1`
electron-builder - `v20.13.4`
electron-updater - `v2.21.10`
<hr>
### v2.2.0-beta --2018-05-08
**Enhancements**:
* Added do not disturb feature
* New DMG installer image
* Minor improvements
**Fixes**:
* Fix download functionality of file attachment links
* Fix tray icon not showing unread counts
**Updated dependencies**:
electron - `v2.0.0`
electron-builder - `v2.21.10`
electron-updater - `v2.21.8`
<hr>
### v2.0.0 -- 2018-04-20
**Enhancements**:
* [Snap](https://snapcraft.io/zulip) support for Linux
* Add an option to download the file attachments instead of opening it in the browser
* Open image link in webapp lightbox
* Add scrollbar for list of organizations on overflow
* Better report issue UX
* Add F5 shortcut for reloading the app
* Responsive UI for connected orgs in smaller window sizes
* Minor improvements
**Fixes**:
* Remove unused shortcuts
* Update toggle sidebar shortcut to CMD/CTRL+SHIFT+S
* Warning dialog box for Reset App Settings
* Fix reinstall script for working across all platforms
* Other minor fixes
**Updated dependencies**:
* Update electron-builder to `v20.8.1`
<hr>
### v1.9.0 --2018-03-23
**Enhancements**:
* Major UI changes in the add new server and setting page
* Some users wanted to change the look of the Zulip. Now you have the power. Feel free to add your own CSS using the all-new setting option **Add Custom CSS**
* Added i18n locale helper script. Internalization is coming in the next release
* Added **What's new** in `help` submenu so that you can see all the latest changes in the app
* Other small improvements
**Fixes**:
* Add Zulip binary link to the bin. It was a regression we missed somehow. You can now start the app using `zulip` from a terminal [Linux]
* Fix app settings overriding issue. This bug was unnoticed from `v1.4.0`. The bug was overriding the app settings which was pretty bad. Don't worry this works as expected now
* Prevent drag and drop events to avoid few security risks
* Do not allow running insecure contents in the app
* The ugly white flickering in the webview is now fixed
* Fix position of left-sidebar on toggle
* Fix actions tooltip not visible on hover
* Other small fixes
**Updated dependencies**:
* electron - `v1.8.4`
* Spectron - `v3.8.0`
<hr>
### v1.8.2 --2018-02-27
**Enhancements**:
* Add bots mention support to reply option
* Show a notification when a new update available [Linux]
* Add back button in left-sidebar
* Add a Loading indication for new server button
* Improve help menu
* Add menu-item to reveal app logs in file manager
* Setting page and left-sidebar UI improvements
* Other minor improvments
**Fixes**:
* Fix broken last active tab
* Fix Zoom In shortcut
* Check whether the internet is working before reloading
* New organization link overlapping existing servers
* Disable electron-connect when it's run from npm start
* Do not auto-reload app when the system comes back from sleep
* Only toggle the setting state if the element is present
* Other minor fixes
**Updated dependencies**:
* electron - `v1.8.2`
<hr>
### v1.8.1 --2018-01-19
**Enhancements**:
* Reply from notifications [macOS]
* Add a setting option to start the app in the background
* 32-bit Debian installer
* 32-bit AppImage installer
* Automatically add apt-repo and GPG public keys on installing Debian file [Linux]
* Remove app data, config files etc on uninstalling Debian installer [Linux]
* Add logger utility for debugging that makes easier to sharing the apps
**Fixes**:
* Handle corrupted config files
* Reload full app on system hibernation
* Load default icon if organization icon is not available
* Focus app when a notification is triggered
* Reflect changes in the preference page (#362)
* Set the default value of flash taskbar setting [Windows]
* Rename Zoom In keyboard shortcut
* Fix checkDomain, so it checks all error codes
* Disable hardware acceleration to decrease the load on GPU
* Minor bug fixes
**Updated dependencies**:
* electron - `v1.7.10`
* electron-builder - `v19.53.6`
* electron-updater - `v2.18.2`
<hr>
### v1.7.0 --2017-11-24
**Enhancements**:
* Updated to Electron `v1.6.15`, electron-builder `v19.46.4`
* Improved setting page for new users
* On the developer side, we have added few tests to make sure app doesn't fail
* Minor improvements
<hr>
### 1.6.0-beta --2017-11-16
**Enhancements**:
* Added setting option to enable/disable spellcheck
* Added setting option to control Windows taskbar flashing [Windows]
* Auto-updates for Linux [AppImage]
* Better tray icons for retina display
* Reset app settings from Menu item
* Show detailed error message on invalid Zulip server
**Fixes**:
* Fixed a bug which was caused by app's shortcuts. From now on our shortcuts won't hijack other apps shortcuts
* Removed [electron-localshortcut](https://github.com/parro-it/electron-localshortcut) completely. Now we only depends on menu accelerators for keyboard shortcuts
* Handle certificate issue properly
* Other minor fixes
<hr>
### v1.5.0 --2017-10-11
**Enhancements**:
* Added an option to clear app data
* Added an option to show/hide desktop notifications
* Redesigned setting page
* Red dot over dock icon for PM [macOS]
* Show server-info on hovering the server icon
**Fixes**:
* Fetch correct organization icon from server_settings API
* Minor improvements
<hr>
### v1.4.0 --2017-09-04
**Enhancements**:
* Added proxy support
* Added setting option for badges
* Start app at login setting option
* Added app category for Linux
* Minor improvements in settings page
* apt-repo on bintray for linux - [here](https://bintray.com/zulip/debian/zulip-elec)
**Fixes**
* Fixed auto-updates
* Better warning message on certificate error
* Don't allow duplicate servers
<hr>
### 1.3.0-beta -- 2017-08-09
**Enhancements**:
* Added back/forward option under `History` submenu
* Added taskbar overlay icon [Windows]
* Added `Window` submenu by which users can switch to other servers
* Added flashing taskbar icon on incoming message [Windows]
* Option to hide the left sidebar
* Staged rollouts
* Minor style improvements
**Fixes**
* Handle focus event properly
* Open external links in default browser
* Allow user to change installation directory
* Fixed server-icon path error [Windows]
* Fixed server validation error
* Fetch server details from Zulip api
* Added tooltip in left sidebar
* Added publisher name in windows installer
**Updated dependencies**:
* electron - `v1.6.11`
* spellchecker - `v1.2.0`
* electron-builder - `v19.9.1`
<hr>
### v1.2.0-beta -- 2017-07-12
**Enhancements**:
* Added new setting page
* Setting for beta-updates. You can opt in for beta updates via selecting it from setting page
* Added Keyboard shortcuts to switch between multiple servers
* Desktop notifications support on Windows
**Fixes**
* Unregister keyboard shortcuts on windows close
* Show warning dialog while deleting server
* Allow server which is signed by root cert
* Other minor bug fixes
<hr>
### v1.1.0-beta --2017-06-23
**Enhancements**:
* New network error page. App will auto-reload once the network comes back
* Quit shortcut - CTRL+Q [Windows]
* Improved preferences
**Fixes**:
* Fixed power-monitor module error
* Fixed code signing error [macOS]
<hr>
### v1.0.0-beta --2017-06-21
**Enhancements**:
* Sign in to multiple teams
* Windows 7 desktop notification support
* Show badge count for each Zulip server
* Toggle window on clicking tray icon [Windows]
* Windows installer is properly code signed using [DigiCert](https://www.digicert.com/) certificate. You may get a warning regarding the certificate, please make it false positive
**Fixes**:
* Close/hide app properly
* Keep app running in background on clicking X
<hr>
### v0.5.10 --2017-05-12
**Enhancements**:
* Added self-signed server support
* Unread message counts in tray icon
<hr>
### v0.5.9 --2017-04-12
**Enhancements**:
* Properly signed app for macOS
* Toggle tray icon
* Better error handling when no internet connenction
**Fixes**:
* Fixed permission issue on windows
* Fixed wrong keyboard shortcuts
<hr>
### v0.5.8 --2017-02-13
**Enhacements**:
Smaller Windows installer size
<hr>
### v0.5.7 --2017-02-08
Minor improvements
<hr>
### v0.5.6 --2017-02-07
**Enhacements**:
- Using NSIS instead of [Squirrel.Windows](https://github.com/Squirrel/Squirrel.Windows) on Windows
- Autoupdates: now using [electron-updater](https://www.npmjs.com/package/electron-updater)
- Removed nuts dependency
**Fixes**:
- Windows installer error
- Compile and build error in native node modules + spellchecker [Windows]
- Missing icon on installing [Windows]
<hr>
### v0.5.4 --2017-01-09
**Fixes**:
- Fixed :
- Auto-updates
- Spellchecker
- Zooming functionality
- Removed unused node modules
- Using stable version for node modules
- Added icon for AppImage
<hr>
### v0.5.3 --2016-12-24
**Enhancements**:
- 🎉 Added automatic update support on Windows
**Fixes**:
- Fixed javascript error when electron has been left running and try to right-click
- Fixed error - Unregister Spellchecker while window is closed
- Fixed - Keyboard shortcuts not getting unregistered
- Minor UI Changes
<hr>
### v0.5.2 --2016-12-13
**Enhancements**:
- ⚡️ Added automatic app updates for macOS
- Added windows installer for 32 bit machine
- Better tray icons
**Fixes**:
- Blurry Unity icon fixed
<hr>
### v0.5.1 --2016-11-23
**Enhacements**:
- Added Spellchecker support with correct spell suggestions
- Added Code-Signing on MAC
- Added Win/linux/Mac installers
- Added Continuous Integration on travis and appveyor
- Open internal links in app only and external in default browser
- Better icons
- Electron version updated to `v1.4.7`
- Using two package.json structure
- Node integration disabled in main window due to jquery error
- Now using electron-builder for packaging instead of electron-packager
- Removed electron-context-menu and simple-spellchecker dependency
**Fixes**:
- Better error handling when user enters wrong zulip server
- Fixed close Zulip window JavaScript error
- Fixed opening image in default browser
- Fixed native notifications issue on linux
<hr>
### v0.0.1-alpha -- 2016-08-31
**Enhacements**:
* Added DMG installer for macOS

View File

@@ -10,11 +10,11 @@ gulp.task('dev', () => {
// Start browser process
electron.start();
// Restart browser process
gulp.watch('app/main/*.js', gulp.series('restart:browser'));
gulp.watch('app/main/*.js', ['restart:browser']);
// Reload renderer process
gulp.watch('app/renderer/css/*.css', gulp.series('reload:renderer'));
gulp.watch('app/renderer/*.html', gulp.series('reload:renderer'));
gulp.watch('app/renderer/js/**/*.js', gulp.series('reload:renderer'));
gulp.watch('app/renderer/css/*.css', ['reload:renderer']);
gulp.watch('app/renderer/*.html', ['reload:renderer']);
gulp.watch('app/renderer/js/**/*.js', ['reload:renderer']);
});
gulp.task('restart:browser', done => {
@@ -36,4 +36,4 @@ gulp.task('test-e2e', () => {
}));
});
gulp.task('default', gulp.parallel('dev', 'test-e2e'));
gulp.task('default', ['dev', 'test-e2e']);

105
help.md Normal file
View File

@@ -0,0 +1,105 @@
# User Guide
> Welcome! This guide will walk you through the basics of using Zulip Desktop.
## Get Zulip Desktop
## Connect to a Server
### Connect through a proxy
It's possible to connect to your server through a proxy.
You can enter the proxy settings in the `Network` section of App Settings.
There are three fields provided:
* `PAC script` - The URL associated with the PAC file.
* `Proxy rules` - Rules indicating which proxies to use.
* `Proxy bypass rules` - Rules indicating which URLs should
bypass the proxy settings.
For a typical setup where internet access is required to use an HTTP proxy,
but URLs on the local network should be accessed directly, configure as follows:
`Proxy rules = proxy.example.com`
Your HTTP proxy server
`Proxy bypass rules = *.example.com;10.0.0.0/8`
Directly connect to your own domain and private IP subnet
for more complex setups, read below to configure complex proxy rules and proxy bypass rules.
### Sets the proxy settings.
When `PAC script` and `Proxy rules` are provided together, the `Proxy rules`
option is ignored and `PAC script` configuration is applied.
The `Proxy rules` has to follow the rules below:
```
proxyRules = schemeProxies[";"<schemeProxies>]
schemeProxies = [<urlScheme>"="]<proxyURIList>
urlScheme = "http" | "https" | "ftp" | "socks"
proxyURIList = <proxyURL>[","<proxyURIList>]
proxyURL = [<proxyScheme>"://"]<proxyHost>[":"<proxyPort>]
```
For example:
* `http=foopy:80;ftp=foopy2` - Use HTTP proxy `foopy:80` for `http://` URLs, and
HTTP proxy `foopy2:80` for `ftp://` URLs.
* `foopy:80` - Use HTTP proxy `foopy:80` for all URLs.
* `foopy:80,bar,direct://` - Use HTTP proxy `foopy:80` for all URLs, failing
over to `bar` if `foopy:80` is unavailable, and after that using no proxy.
* `socks4://foopy` - Use SOCKS v4 proxy `foopy:1080` for all URLs.
* `http=foopy,socks5://bar.com` - Use HTTP proxy `foopy` for http URLs, and fail
over to the SOCKS5 proxy `bar.com` if `foopy` is unavailable.
* `http=foopy,direct://` - Use HTTP proxy `foopy` for http URLs, and use no
proxy if `foopy` is unavailable.
* `http=foopy;socks=foopy2` - Use HTTP proxy `foopy` for http URLs, and use
`socks4://foopy2` for all other URLs.
The `Proxy bypass rules` is a comma separated list of rules described below:
* `[ URL_SCHEME "://" ] HOSTNAME_PATTERN [ ":" <port> ]`
Match all hostnames that match the pattern HOSTNAME_PATTERN.
Examples:
"foobar.com", "*foobar.com", "*.foobar.com", "*foobar.com:99",
"https://x.*.y.com:99"
* `"." HOSTNAME_SUFFIX_PATTERN [ ":" PORT ]`
Match a particular domain suffix.
Examples:
".google.com", ".com", "http://.google.com"
* `[ SCHEME "://" ] IP_LITERAL [ ":" PORT ]`
Match URLs which are IP address literals.
Examples:
"127.0.1", "[0:0::1]", "[::1]", "http://[::1]:99"
* `IP_LITERAL "/" PREFIX_LENGHT_IN_BITS`
Match any URL that is to an IP literal that falls between the
given range. IP range is specified using CIDR notation.
Examples:
"192.168.1.1/16", "fefe:13::abc/33".
* `<local>`
Match local addresses. The meaning of `<local>` is whether the
host matches one of: "127.0.0.1", "::1", "localhost".
## Change App Preferences
## Reporting an Issue

9984
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "zulip",
"productName": "Zulip",
"version": "2.3.6",
"version": "2.2.0-beta",
"main": "./app/main",
"description": "Zulip Desktop App",
"license": "Apache-2.0",
@@ -88,8 +88,8 @@
}
],
"window": {
"width": 500,
"height": 500
"width": 500,
"height": 500
}
},
"win": {
@@ -123,12 +123,12 @@
"assert": "1.4.1",
"cp-file": "^5.0.0",
"devtron": "1.4.0",
"electron": "2.0.8",
"electron-builder": "20.20.4",
"electron": "2.0.0",
"electron-builder": "20.13.4",
"electron-connect": "0.6.2",
"electron-debug": "1.4.0",
"google-translate-api": "2.3.0",
"gulp": "^4.0.0",
"gulp": "3.9.1",
"gulp-tape": "0.0.9",
"is-ci": "^1.0.10",
"nodemon": "^1.14.11",
@@ -182,4 +182,4 @@
"mocha"
]
}
}
}

View File

@@ -15,6 +15,6 @@ fi
npm run test
# if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
# npm run test-e2e
# fi
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
npm run test-e2e
fi

View File

@@ -1,185 +0,0 @@
{
"name": "zulip",
"productName": "ZulipTest",
"version": "2.3.3",
"main": "../../app/main",
"description": "Zulip Desktop App",
"license": "Apache-2.0",
"copyright": "Kandra Labs, Inc.",
"author": {
"name": "Kandra Labs, Inc.",
"email": "support@zulipchat.com"
},
"repository": {
"type": "git",
"url": "https://github.com/zulip/zulip-electron.git"
},
"bugs": {
"url": "https://github.com/zulip/zulip-electron/issues"
},
"engines": {
"node": ">=6.0.0"
},
"scripts": {
"start": "electron app --disable-http-cache --no-electron-connect",
"reinstall": "node ./tools/reinstall-node-modules.js",
"postinstall": "electron-builder install-app-deps",
"test": "xo",
"test-e2e": "gulp test-e2e",
"dev": "gulp dev & nodemon --watch app/main --watch app/renderer --exec 'npm test' -e html,css,js",
"pack": "electron-builder --dir",
"dist": "electron-builder",
"mas": "electron-builder --mac mas",
"travis": "cd ./scripts && ./travis-build-test.sh",
"build-locales": "node tools/locale-helper"
},
"pre-commit": [
"test"
],
"build": {
"appId": "org.zulip.zulip-electron",
"asar": true,
"files": [
"**/*",
"!docs${/*}",
"!node_modules/@paulcbetts/cld/deps/cld${/*}"
],
"copyright": "©2017 Kandra Labs, Inc.",
"mac": {
"category": "public.app-category.productivity",
"artifactName": "${productName}-${version}-${arch}.${ext}"
},
"linux": {
"category": "Chat;GNOME;GTK;Network;InstantMessaging",
"packageCategory": "GNOME;GTK;Network;InstantMessaging",
"description": "Zulip Desktop Client for Linux",
"target": [
"deb",
"zip",
"AppImage",
"snap"
],
"maintainer": "Akash Nimare <svnitakash@gmail.com>",
"artifactName": "${productName}-${version}-${arch}.${ext}"
},
"deb": {
"synopsis": "Zulip Desktop App",
"afterInstall": "./scripts/debian-add-repo.sh",
"afterRemove": "./scripts/debian-uninstaller.sh"
},
"snap": {
"synopsis": "Zulip Desktop App"
},
"dmg": {
"background": "build/appdmg.png",
"icon": "build/icon.icns",
"iconSize": 100,
"contents": [
{
"x": 380,
"y": 280,
"type": "link",
"path": "/Applications"
},
{
"x": 110,
"y": 280,
"type": "file"
}
],
"window": {
"width": 500,
"height": 500
}
},
"win": {
"target": [
{
"target": "nsis-web",
"arch": [
"x64",
"ia32"
]
}
],
"icon": "build/icon.ico",
"publisherName": "Kandra Labs, Inc."
},
"nsis": {
"perMachine": true,
"oneClick": false,
"allowToChangeInstallationDirectory": true
}
},
"keywords": [
"Zulip",
"Group Chat app",
"electron-app",
"electron",
"Desktop app",
"InstantMessaging"
],
"devDependencies": {
"assert": "1.4.1",
"cp-file": "^5.0.0",
"devtron": "1.4.0",
"electron": "2.0.1",
"electron-builder": "20.20.4",
"electron-connect": "0.6.2",
"electron-debug": "1.4.0",
"google-translate-api": "2.3.0",
"gulp": "^4.0.0",
"gulp-tape": "0.0.9",
"is-ci": "^1.0.10",
"nodemon": "^1.14.11",
"pre-commit": "1.2.2",
"spectron": "3.8.0",
"tap-colorize": "^1.2.0",
"tape": "^4.8.0",
"xo": "0.18.2"
},
"xo": {
"parserOptions": {
"sourceType": "script",
"ecmaFeatures": {
"globalReturn": true
}
},
"esnext": true,
"overrides": [
{
"files": "app*/**/*.js",
"rules": {
"max-lines": [
"warn",
{
"max": 500,
"skipBlankLines": true,
"skipComments": true
}
],
"no-warning-comments": 0,
"object-curly-spacing": 0,
"capitalized-comments": 0,
"no-else-return": 0,
"no-path-concat": 0,
"no-alert": 0,
"guard-for-in": 0,
"prefer-promise-reject-errors": 0,
"import/no-unresolved": 0,
"import/no-extraneous-dependencies": 0,
"no-prototype-builtins": 0
}
}
],
"ignore": [
"tests/e2e/*.js",
"tools/locale-helper/*.js"
],
"envs": [
"node",
"browser",
"mocha"
]
}
}

View File

@@ -1,81 +0,0 @@
#!/bin/bash
set -e
usage () {
cat >&2 <<EOF
usage: $0 PULL_REQUEST_ID [REMOTE]
Force-push our HEAD to the given GitHub pull request branch.
Useful for a maintainer to run just before pushing to master,
after tweaking the branch and/or rebasing to latest. This causes
GitHub to see the subsequent push to master as representing a
merge of the PR, rather than requiring the PR to be manually
(and to the casual observer misleadingly) closed instead.
REMOTE defaults to the value of the Git config variable
\`zulip.zulipRemote\` if set, else to \`upstream\`.
See also \`reset-to-pull-request\`.
EOF
exit 1
}
remote_default="$(git config zulip.zulipRemote || echo upstream)"
pr_id="$1"
remote="${2:-"$remote_default"}"
if [ -z "$pr_id" ]; then
usage
fi
remote_url="$(git config remote."$remote".url)"
repo_fq="$(echo "$remote_url" | perl -lne 'print $1 if (
m, ^ git\@github\.com:
([^/]+ / [^/]+?)
(?:\.git)?
$ ,x )')"
if [ -z "$repo_fq" ]; then
# We're pretty specific about what we expect the URL to look like;
# there are probably more cases we could legitimately cover, which
# we can add if/when they come up for someone.
echo "error: couldn't parse remote URL as GitHub repo" >&2
exit 1
fi
# See https://developer.github.com/v3/pulls/#get-a-single-pull-request .
# This is the old REST API; the new GraphQL API does look neat, but it
# seems to require authentication even for simple lookups of public data,
# and that'd be a pain for a simple script like this.
pr_url=https://api.github.com/repos/"${repo_fq}"/pulls/"${pr_id}"
pr_details="$(curl -s "$pr_url")"
pr_jq () {
echo "$pr_details" | jq "$@"
}
if [ "$(pr_jq -r .message)" = "Not Found" ]; then
echo "Invalid PR URL: $pr_url"
exit 1
fi
if [ "$(pr_jq .maintainer_can_modify)" != "true" ]; then
# This happens when the PR has already been merged or closed, or
# if the contributor has turned off the (default) setting to allow
# maintainers of the target repo to push to their PR branch.
#
# The latter seems to be rare (in Greg's experience doing the
# manual equivalent of this script for many different
# contributors, none have ever chosen this setting), but give a
# decent error message if it does happen.
echo "error: PR already closed, or contributor has disallowed pushing to branch" >&2
exit 1
fi
pr_head_repo_fq="$(pr_jq -r .head.repo.full_name)"
pr_head_refname="$(pr_jq -r .head.ref)"
set -x
exec git push git@github.com:"$pr_head_repo_fq" +@:"$pr_head_refname"

View File

@@ -1,19 +0,0 @@
#!/bin/bash
set -e
if ! git diff-index --quiet HEAD; then
set +x
echo "There are uncommitted changes:"
git status --short
echo "Doing nothing to avoid losing your work."
exit 1
fi
remote_default="$(git config zulip.zulipRemote || echo upstream)"
request_id="$1"
remote=${2:-"$remote_default"}
set -x
git fetch "$remote" "pull/$request_id/head"
git reset --hard FETCH_HEAD