Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d27cf8c7d | ||
|
|
1ac2483cc4 | ||
|
|
4d3420dcd0 | ||
|
|
38450a9aed | ||
|
|
24de7ebb97 | ||
|
|
5a571d66d0 | ||
|
|
0ae998a51e | ||
|
|
447dd18b8b | ||
|
|
9a200dc40c | ||
|
|
d42b752ac1 | ||
|
|
2f4103248d | ||
|
|
985d731d2b | ||
|
|
032f95150c | ||
|
|
d1aa5778c3 | ||
|
|
13ce24b75e | ||
|
|
c89ec2faf1 |
6
.gitignore
vendored
@@ -8,7 +8,8 @@
|
|||||||
.transifexrc
|
.transifexrc
|
||||||
|
|
||||||
# Compiled binary build directory
|
# Compiled binary build directory
|
||||||
dist/
|
/dist/
|
||||||
|
/dist-electron/
|
||||||
|
|
||||||
#snap generated files
|
#snap generated files
|
||||||
snap/parts
|
snap/parts
|
||||||
@@ -39,6 +40,3 @@ config.gypi
|
|||||||
# tests/package.json
|
# tests/package.json
|
||||||
|
|
||||||
.python-version
|
.python-version
|
||||||
|
|
||||||
# Ignore all the typescript compiled files
|
|
||||||
app/**/*.js
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
/app/**/*.js
|
|
||||||
/app/translations/*.json
|
|
||||||
/dist
|
/dist
|
||||||
|
/dist-electron
|
||||||
|
/public/translations/*.json
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
host = https://www.transifex.com
|
host = https://www.transifex.com
|
||||||
|
|
||||||
[zulip.desktopjson]
|
[zulip.desktopjson]
|
||||||
file_filter = app/translations/<lang>.json
|
file_filter = public/translations/<lang>.json
|
||||||
minimum_perc = 0
|
minimum_perc = 0
|
||||||
source_file = app/translations/en.json
|
source_file = public/translations/en.json
|
||||||
source_lang = en
|
source_lang = en
|
||||||
type = KEYVALUEJSON
|
type = KEYVALUEJSON
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ import * as Sentry from "@sentry/electron";
|
|||||||
import {JsonDB} from "node-json-db";
|
import {JsonDB} from "node-json-db";
|
||||||
import {DataError} from "node-json-db/dist/lib/Errors";
|
import {DataError} from "node-json-db/dist/lib/Errors";
|
||||||
import type * as z from "zod";
|
import type * as z from "zod";
|
||||||
|
import {app, dialog} from "zulip:remote";
|
||||||
|
|
||||||
import {configSchemata} from "./config-schemata.js";
|
import {configSchemata} from "./config-schemata.js";
|
||||||
import * as EnterpriseUtil from "./enterprise-util.js";
|
import * as EnterpriseUtil from "./enterprise-util.js";
|
||||||
import Logger from "./logger-util.js";
|
import Logger from "./logger-util.js";
|
||||||
import {app, dialog} from "./remote.js";
|
|
||||||
|
|
||||||
export type Config = {
|
export type Config = {
|
||||||
[Key in keyof typeof configSchemata]: z.output<typeof configSchemata[Key]>;
|
[Key in keyof typeof configSchemata]: z.output<(typeof configSchemata)[Key]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const logger = new Logger({
|
const logger = new Logger({
|
||||||
@@ -26,7 +26,7 @@ reloadDb();
|
|||||||
export function getConfigItem<Key extends keyof Config>(
|
export function getConfigItem<Key extends keyof Config>(
|
||||||
key: Key,
|
key: Key,
|
||||||
defaultValue: Config[Key],
|
defaultValue: Config[Key],
|
||||||
): z.output<typeof configSchemata[Key]> {
|
): z.output<(typeof configSchemata)[Key]> {
|
||||||
try {
|
try {
|
||||||
db.reload();
|
db.reload();
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
|
|
||||||
import {app} from "./remote.js";
|
import {app} from "zulip:remote";
|
||||||
|
|
||||||
let setupCompleted = false;
|
let setupCompleted = false;
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import * as ConfigUtil from "./config-util.js";
|
|||||||
|
|
||||||
export type DndSettings = {
|
export type DndSettings = {
|
||||||
[Key in keyof typeof dndSettingsSchemata]: z.output<
|
[Key in keyof typeof dndSettingsSchemata]: z.output<
|
||||||
typeof dndSettingsSchemata[Key]
|
(typeof dndSettingsSchemata)[Key]
|
||||||
>;
|
>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import Logger from "./logger-util.js";
|
|||||||
|
|
||||||
type EnterpriseConfig = {
|
type EnterpriseConfig = {
|
||||||
[Key in keyof typeof enterpriseConfigSchemata]: z.output<
|
[Key in keyof typeof enterpriseConfigSchemata]: z.output<
|
||||||
typeof enterpriseConfigSchemata[Key]
|
(typeof enterpriseConfigSchemata)[Key]
|
||||||
>;
|
>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ import fs from "node:fs";
|
|||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import process from "node:process";
|
import process from "node:process";
|
||||||
|
|
||||||
|
import {app} from "zulip:remote";
|
||||||
|
|
||||||
import {initSetUp} from "./default-util.js";
|
import {initSetUp} from "./default-util.js";
|
||||||
import {app} from "./remote.js";
|
|
||||||
|
|
||||||
type LoggerOptions = {
|
type LoggerOptions = {
|
||||||
file?: string;
|
file?: string;
|
||||||
|
|||||||
15
app/common/paths.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import path from "node:path";
|
||||||
|
import process from "node:process";
|
||||||
|
import url from "node:url";
|
||||||
|
|
||||||
|
export const bundlePath = __dirname;
|
||||||
|
|
||||||
|
export const publicPath = import.meta.env.DEV
|
||||||
|
? path.join(bundlePath, "../public")
|
||||||
|
: bundlePath;
|
||||||
|
|
||||||
|
export const bundleUrl = import.meta.env.DEV
|
||||||
|
? process.env.VITE_DEV_SERVER_URL
|
||||||
|
: url.pathToFileURL(__dirname).href + "/";
|
||||||
|
|
||||||
|
export const publicUrl = bundleUrl;
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import process from "node:process";
|
|
||||||
|
|
||||||
export const {app, dialog} =
|
|
||||||
process.type === "renderer"
|
|
||||||
? // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
||||||
(require("@electron/remote") as typeof import("@electron/remote"))
|
|
||||||
: // eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
||||||
require("electron/main");
|
|
||||||
@@ -3,9 +3,10 @@ import path from "node:path";
|
|||||||
import i18n from "i18n";
|
import i18n from "i18n";
|
||||||
|
|
||||||
import * as ConfigUtil from "./config-util.js";
|
import * as ConfigUtil from "./config-util.js";
|
||||||
|
import {publicPath} from "./paths.js";
|
||||||
|
|
||||||
i18n.configure({
|
i18n.configure({
|
||||||
directory: path.join(__dirname, "../translations/"),
|
directory: path.join(publicPath, "translations/"),
|
||||||
updateFiles: false,
|
updateFiles: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ export type MainMessage = {
|
|||||||
"fetch-user-agent": () => string;
|
"fetch-user-agent": () => string;
|
||||||
"focus-app": () => void;
|
"focus-app": () => void;
|
||||||
"focus-this-webview": () => void;
|
"focus-this-webview": () => void;
|
||||||
|
"get-injected-js": () => string;
|
||||||
|
"new-clipboard-key": () => {key: Uint8Array; sig: Uint8Array};
|
||||||
"permission-callback": (permissionCallbackId: number, grant: boolean) => void;
|
"permission-callback": (permissionCallbackId: number, grant: boolean) => void;
|
||||||
"quit-app": () => void;
|
"quit-app": () => void;
|
||||||
"realm-icon-changed": (serverURL: string, iconURL: string) => void;
|
"realm-icon-changed": (serverURL: string, iconURL: string) => void;
|
||||||
@@ -27,6 +29,7 @@ export type MainMessage = {
|
|||||||
export type MainCall = {
|
export type MainCall = {
|
||||||
"get-server-settings": (domain: string) => ServerConf;
|
"get-server-settings": (domain: string) => ServerConf;
|
||||||
"is-online": (url: string) => boolean;
|
"is-online": (url: string) => boolean;
|
||||||
|
"poll-clipboard": (key: Uint8Array, sig: Uint8Array) => string | undefined;
|
||||||
"save-server-icon": (iconURL: string) => string;
|
"save-server-icon": (iconURL: string) => string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
|
import {clipboard} from "electron/common";
|
||||||
import type {IpcMainEvent, WebContents} from "electron/main";
|
import type {IpcMainEvent, WebContents} from "electron/main";
|
||||||
import {BrowserWindow, app, dialog, powerMonitor, session} from "electron/main";
|
import {BrowserWindow, app, dialog, powerMonitor, session} from "electron/main";
|
||||||
|
import {Buffer} from "node:buffer";
|
||||||
|
import crypto from "node:crypto";
|
||||||
|
import fs from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import process from "node:process";
|
import process from "node:process";
|
||||||
|
|
||||||
@@ -7,6 +11,7 @@ import * as remoteMain from "@electron/remote/main";
|
|||||||
import windowStateKeeper from "electron-window-state";
|
import windowStateKeeper from "electron-window-state";
|
||||||
|
|
||||||
import * as ConfigUtil from "../common/config-util.js";
|
import * as ConfigUtil from "../common/config-util.js";
|
||||||
|
import {bundlePath, bundleUrl, publicPath} from "../common/paths.js";
|
||||||
import type {RendererMessage} from "../common/typed-ipc.js";
|
import type {RendererMessage} from "../common/typed-ipc.js";
|
||||||
import type {MenuProps} from "../common/types.js";
|
import type {MenuProps} from "../common/types.js";
|
||||||
|
|
||||||
@@ -35,13 +40,13 @@ let badgeCount: number;
|
|||||||
|
|
||||||
let isQuitting = false;
|
let isQuitting = false;
|
||||||
|
|
||||||
// Load this url in main window
|
// Load this file in main window
|
||||||
const mainUrl = "file://" + path.join(__dirname, "../renderer", "main.html");
|
const mainUrl = new URL("app/renderer/main.html", bundleUrl).href;
|
||||||
|
|
||||||
const permissionCallbacks = new Map<number, (grant: boolean) => void>();
|
const permissionCallbacks = new Map<number, (grant: boolean) => void>();
|
||||||
let nextPermissionCallbackId = 0;
|
let nextPermissionCallbackId = 0;
|
||||||
|
|
||||||
const appIcon = path.join(__dirname, "../resources", "Icon");
|
const appIcon = path.join(publicPath, "resources/Icon");
|
||||||
|
|
||||||
const iconPath = (): string =>
|
const iconPath = (): string =>
|
||||||
appIcon + (process.platform === "win32" ? ".ico" : ".png");
|
appIcon + (process.platform === "win32" ? ".ico" : ".png");
|
||||||
@@ -74,7 +79,7 @@ function createMainWindow(): BrowserWindow {
|
|||||||
minWidth: 500,
|
minWidth: 500,
|
||||||
minHeight: 400,
|
minHeight: 400,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
preload: require.resolve("../renderer/js/main"),
|
preload: path.join(bundlePath, "renderer.js"),
|
||||||
sandbox: false,
|
sandbox: false,
|
||||||
webviewTag: true,
|
webviewTag: true,
|
||||||
},
|
},
|
||||||
@@ -201,6 +206,49 @@ function createMainWindow(): BrowserWindow {
|
|||||||
configureSpellChecker();
|
configureSpellChecker();
|
||||||
ipcMain.on("configure-spell-checker", configureSpellChecker);
|
ipcMain.on("configure-spell-checker", configureSpellChecker);
|
||||||
|
|
||||||
|
ipcMain.on("get-injected-js", (event) => {
|
||||||
|
event.returnValue = fs.readFileSync(
|
||||||
|
path.join(bundlePath, "injected.js"),
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const clipboardSigKey = crypto.randomBytes(32);
|
||||||
|
|
||||||
|
ipcMain.on("new-clipboard-key", (event) => {
|
||||||
|
const key = crypto.randomBytes(32);
|
||||||
|
const hmac = crypto.createHmac("sha256", clipboardSigKey);
|
||||||
|
hmac.update(key);
|
||||||
|
event.returnValue = {key, sig: hmac.digest()};
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("poll-clipboard", (event, key, sig) => {
|
||||||
|
// Check that the key was generated here.
|
||||||
|
const hmac = crypto.createHmac("sha256", clipboardSigKey);
|
||||||
|
hmac.update(key);
|
||||||
|
if (!crypto.timingSafeEqual(sig, hmac.digest())) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check that the data on the clipboard was encrypted to the key.
|
||||||
|
const data = Buffer.from(clipboard.readText(), "hex");
|
||||||
|
const iv = data.slice(0, 12);
|
||||||
|
const ciphertext = data.slice(12, -16);
|
||||||
|
const authTag = data.slice(-16);
|
||||||
|
const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv, {
|
||||||
|
authTagLength: 16,
|
||||||
|
});
|
||||||
|
decipher.setAuthTag(authTag);
|
||||||
|
return (
|
||||||
|
decipher.update(ciphertext, undefined, "utf8") + decipher.final("utf8")
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
// If the parsing or decryption failed in any way,
|
||||||
|
// the correct token hasn’t been copied yet; try
|
||||||
|
// again next time.
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
AppMenu.setMenu({
|
AppMenu.setMenu({
|
||||||
tabs: [],
|
tabs: [],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {app} from "electron/main";
|
import {app} from "electron/main";
|
||||||
|
|
||||||
import * as Sentry from "@sentry/electron";
|
import * as Sentry from "@sentry/electron/main"; // eslint-disable-line n/file-extension-in-import
|
||||||
|
|
||||||
import {getConfigItem} from "../common/config-util.js";
|
import {getConfigItem} from "../common/config-util.js";
|
||||||
|
|
||||||
|
|||||||
26
app/renderer/about.html
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="stylesheet" href="css/about.css" />
|
||||||
|
|
||||||
|
<!-- Initially hidden to prevent FOUC -->
|
||||||
|
<div class="about" hidden>
|
||||||
|
<img class="logo" src="../resources/zulip.png" />
|
||||||
|
<p class="detail" id="version"></p>
|
||||||
|
<div class="maintenance-info">
|
||||||
|
<p class="detail maintainer">
|
||||||
|
Maintained by
|
||||||
|
<a href="https://zulip.com" target="_blank" rel="noopener noreferrer"
|
||||||
|
>Zulip</a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<p class="detail license">
|
||||||
|
Available under the
|
||||||
|
<a
|
||||||
|
href="https://github.com/zulip/zulip-desktop/blob/main/LICENSE"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>Apache 2.0 License</a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
import {clipboard} from "electron/common";
|
import {ipcRenderer} from "./typed-ipc-renderer.js";
|
||||||
import {Buffer} from "node:buffer";
|
|
||||||
import crypto from "node:crypto";
|
|
||||||
|
|
||||||
// This helper is exposed via electron_bridge for use in the social
|
// This helper is exposed via electron_bridge for use in the social
|
||||||
// login flow.
|
// login flow.
|
||||||
@@ -30,7 +28,8 @@ export class ClipboardDecrypterImpl implements ClipboardDecrypter {
|
|||||||
constructor(_: number) {
|
constructor(_: number) {
|
||||||
// At this time, the only version is 1.
|
// At this time, the only version is 1.
|
||||||
this.version = 1;
|
this.version = 1;
|
||||||
this.key = crypto.randomBytes(32);
|
const {key, sig} = ipcRenderer.sendSync("new-clipboard-key");
|
||||||
|
this.key = key;
|
||||||
this.pasted = new Promise((resolve) => {
|
this.pasted = new Promise((resolve) => {
|
||||||
let interval: NodeJS.Timeout | null = null;
|
let interval: NodeJS.Timeout | null = null;
|
||||||
const startPolling = () => {
|
const startPolling = () => {
|
||||||
@@ -38,7 +37,7 @@ export class ClipboardDecrypterImpl implements ClipboardDecrypter {
|
|||||||
interval = setInterval(poll, 1000);
|
interval = setInterval(poll, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
poll();
|
void poll();
|
||||||
};
|
};
|
||||||
|
|
||||||
const stopPolling = () => {
|
const stopPolling = () => {
|
||||||
@@ -48,30 +47,9 @@ export class ClipboardDecrypterImpl implements ClipboardDecrypter {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const poll = () => {
|
const poll = async () => {
|
||||||
let plaintext;
|
const plaintext = await ipcRenderer.invoke("poll-clipboard", key, sig);
|
||||||
|
if (plaintext === undefined) return;
|
||||||
try {
|
|
||||||
const data = Buffer.from(clipboard.readText(), "hex");
|
|
||||||
const iv = data.slice(0, 12);
|
|
||||||
const ciphertext = data.slice(12, -16);
|
|
||||||
const authTag = data.slice(-16);
|
|
||||||
const decipher = crypto.createDecipheriv(
|
|
||||||
"aes-256-gcm",
|
|
||||||
this.key,
|
|
||||||
iv,
|
|
||||||
{authTagLength: 16},
|
|
||||||
);
|
|
||||||
decipher.setAuthTag(authTag);
|
|
||||||
plaintext =
|
|
||||||
decipher.update(ciphertext, undefined, "utf8") +
|
|
||||||
decipher.final("utf8");
|
|
||||||
} catch {
|
|
||||||
// If the parsing or decryption failed in any way,
|
|
||||||
// the correct token hasn’t been copied yet; try
|
|
||||||
// again next time.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.removeEventListener("focus", startPolling);
|
window.removeEventListener("focus", startPolling);
|
||||||
window.removeEventListener("blur", stopPolling);
|
window.removeEventListener("blur", stopPolling);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import type {WebContents} from "electron/main";
|
import type {WebContents} from "electron/main";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import path from "node:path";
|
|
||||||
import process from "node:process";
|
import process from "node:process";
|
||||||
|
|
||||||
import * as remote from "@electron/remote";
|
import * as remote from "@electron/remote";
|
||||||
@@ -11,6 +10,7 @@ import type {Html} from "../../../common/html.js";
|
|||||||
import {html} from "../../../common/html.js";
|
import {html} from "../../../common/html.js";
|
||||||
import type {RendererMessage} from "../../../common/typed-ipc.js";
|
import type {RendererMessage} from "../../../common/typed-ipc.js";
|
||||||
import type {TabRole} from "../../../common/types.js";
|
import type {TabRole} from "../../../common/types.js";
|
||||||
|
import preloadCss from "../../css/preload.css?raw"; // eslint-disable-line n/file-extension-in-import
|
||||||
import {ipcRenderer} from "../typed-ipc-renderer.js";
|
import {ipcRenderer} from "../typed-ipc-renderer.js";
|
||||||
import * as SystemUtil from "../utils/system-util.js";
|
import * as SystemUtil from "../utils/system-util.js";
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ export default class WebView {
|
|||||||
src="${props.url}"
|
src="${props.url}"
|
||||||
${props.preload === undefined
|
${props.preload === undefined
|
||||||
? html``
|
? html``
|
||||||
: html`preload="${props.preload}" webpreferences="sandbox=no"`}
|
: html`preload="${props.preload}"`}
|
||||||
partition="persist:webviewsession"
|
partition="persist:webviewsession"
|
||||||
allowpopups
|
allowpopups
|
||||||
>
|
>
|
||||||
@@ -56,11 +56,9 @@ export default class WebView {
|
|||||||
) as HTMLElement;
|
) as HTMLElement;
|
||||||
props.$root.append($element);
|
props.$root.append($element);
|
||||||
|
|
||||||
// Wait for did-navigate rather than did-attach to work around
|
|
||||||
// https://github.com/electron/electron/issues/31918
|
|
||||||
await new Promise<void>((resolve) => {
|
await new Promise<void>((resolve) => {
|
||||||
$element.addEventListener(
|
$element.addEventListener(
|
||||||
"did-navigate",
|
"did-attach",
|
||||||
() => {
|
() => {
|
||||||
resolve();
|
resolve();
|
||||||
},
|
},
|
||||||
@@ -210,10 +208,7 @@ export default class WebView {
|
|||||||
this.focus();
|
this.focus();
|
||||||
this.props.onTitleChange();
|
this.props.onTitleChange();
|
||||||
// Injecting preload css in webview to override some css rules
|
// Injecting preload css in webview to override some css rules
|
||||||
(async () =>
|
(async () => this.getWebContents().insertCSS(preloadCss))();
|
||||||
this.getWebContents().insertCSS(
|
|
||||||
fs.readFileSync(path.join(__dirname, "/../../css/preload.css"), "utf8"),
|
|
||||||
))();
|
|
||||||
|
|
||||||
// Get customCSS again from config util to avoid warning user again
|
// Get customCSS again from config util to avoid warning user again
|
||||||
const customCss = ConfigUtil.getConfigItem("customCSS", null);
|
const customCss = ConfigUtil.getConfigItem("customCSS", null);
|
||||||
@@ -229,9 +224,7 @@ export default class WebView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
(async () =>
|
(async () =>
|
||||||
this.getWebContents().insertCSS(
|
this.getWebContents().insertCSS(fs.readFileSync(customCss, "utf8")))();
|
||||||
fs.readFileSync(path.resolve(__dirname, customCss), "utf8"),
|
|
||||||
))();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {EventEmitter} from "node:events";
|
import {EventEmitter} from "events"; // eslint-disable-line unicorn/prefer-node-protocol
|
||||||
|
|
||||||
import type {ClipboardDecrypter} from "./clipboard-decrypter.js";
|
import type {ClipboardDecrypter} from "./clipboard-decrypter.js";
|
||||||
import {ClipboardDecrypterImpl} from "./clipboard-decrypter.js";
|
import {ClipboardDecrypterImpl} from "./clipboard-decrypter.js";
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import {clipboard} from "electron/common";
|
import {clipboard} from "electron/common";
|
||||||
|
import fs from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import process from "node:process";
|
import process from "node:process";
|
||||||
|
import url from "node:url";
|
||||||
|
|
||||||
import {Menu, app, dialog, session} from "@electron/remote";
|
import {Menu, app, dialog, session} from "@electron/remote";
|
||||||
import * as remote from "@electron/remote";
|
import * as remote from "@electron/remote";
|
||||||
@@ -14,6 +16,7 @@ import * as EnterpriseUtil from "../../common/enterprise-util.js";
|
|||||||
import * as LinkUtil from "../../common/link-util.js";
|
import * as LinkUtil from "../../common/link-util.js";
|
||||||
import Logger from "../../common/logger-util.js";
|
import Logger from "../../common/logger-util.js";
|
||||||
import * as Messages from "../../common/messages.js";
|
import * as Messages from "../../common/messages.js";
|
||||||
|
import {bundlePath, bundleUrl} from "../../common/paths.js";
|
||||||
import type {NavItem, ServerConf, TabData} from "../../common/types.js";
|
import type {NavItem, ServerConf, TabData} from "../../common/types.js";
|
||||||
|
|
||||||
import FunctionalTab from "./components/functional-tab.js";
|
import FunctionalTab from "./components/functional-tab.js";
|
||||||
@@ -44,12 +47,13 @@ const logger = new Logger({
|
|||||||
file: "errors.log",
|
file: "errors.log",
|
||||||
});
|
});
|
||||||
|
|
||||||
const rendererDirectory = path.resolve(__dirname, "..");
|
|
||||||
type ServerOrFunctionalTab = ServerTab | FunctionalTab;
|
type ServerOrFunctionalTab = ServerTab | FunctionalTab;
|
||||||
|
|
||||||
const rootWebContents = remote.getCurrentWebContents();
|
const rootWebContents = remote.getCurrentWebContents();
|
||||||
|
|
||||||
const dingSound = new Audio("../resources/sounds/ding.ogg");
|
const dingSound = new Audio(
|
||||||
|
new URL("resources/sounds/ding.ogg", bundleUrl).href,
|
||||||
|
);
|
||||||
|
|
||||||
export class ServerManagerView {
|
export class ServerManagerView {
|
||||||
$addServerButton: HTMLButtonElement;
|
$addServerButton: HTMLButtonElement;
|
||||||
@@ -362,7 +366,10 @@ export class ServerManagerView {
|
|||||||
this.tabs.push(
|
this.tabs.push(
|
||||||
new ServerTab({
|
new ServerTab({
|
||||||
role: "server",
|
role: "server",
|
||||||
icon: server.icon,
|
icon: `data:application/octet-stream;base64,${fs.readFileSync(
|
||||||
|
server.icon,
|
||||||
|
"base64",
|
||||||
|
)}`,
|
||||||
name: server.alias,
|
name: server.alias,
|
||||||
$root: this.$tabsContainer,
|
$root: this.$tabsContainer,
|
||||||
onClick: this.activateLastTab.bind(this, index),
|
onClick: this.activateLastTab.bind(this, index),
|
||||||
@@ -397,7 +404,7 @@ export class ServerManagerView {
|
|||||||
await this.openNetworkTroubleshooting(index);
|
await this.openNetworkTroubleshooting(index);
|
||||||
},
|
},
|
||||||
onTitleChange: this.updateBadge.bind(this),
|
onTitleChange: this.updateBadge.bind(this),
|
||||||
preload: "js/preload.js",
|
preload: url.pathToFileURL(path.join(bundlePath, "preload.js")).href,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -543,7 +550,7 @@ export class ServerManagerView {
|
|||||||
async openFunctionalTab(tabProps: {
|
async openFunctionalTab(tabProps: {
|
||||||
name: string;
|
name: string;
|
||||||
materialIcon: string;
|
materialIcon: string;
|
||||||
makeView: () => Element;
|
makeView: () => Promise<Element>;
|
||||||
destroyView: () => void;
|
destroyView: () => void;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
if (this.functionalTabs.has(tabProps.name)) {
|
if (this.functionalTabs.has(tabProps.name)) {
|
||||||
@@ -555,7 +562,7 @@ export class ServerManagerView {
|
|||||||
this.functionalTabs.set(tabProps.name, index);
|
this.functionalTabs.set(tabProps.name, index);
|
||||||
|
|
||||||
const tabIndex = this.getTabIndex();
|
const tabIndex = this.getTabIndex();
|
||||||
const $view = tabProps.makeView();
|
const $view = await tabProps.makeView();
|
||||||
this.$webviewsContainer.append($view);
|
this.$webviewsContainer.append($view);
|
||||||
|
|
||||||
this.tabs.push(
|
this.tabs.push(
|
||||||
@@ -586,8 +593,8 @@ export class ServerManagerView {
|
|||||||
await this.openFunctionalTab({
|
await this.openFunctionalTab({
|
||||||
name: "Settings",
|
name: "Settings",
|
||||||
materialIcon: "settings",
|
materialIcon: "settings",
|
||||||
makeView: () => {
|
makeView: async () => {
|
||||||
this.preferenceView = new PreferenceView();
|
this.preferenceView = await PreferenceView.create();
|
||||||
this.preferenceView.$view.classList.add("functional-view");
|
this.preferenceView.$view.classList.add("functional-view");
|
||||||
return this.preferenceView.$view;
|
return this.preferenceView.$view;
|
||||||
},
|
},
|
||||||
@@ -605,8 +612,8 @@ export class ServerManagerView {
|
|||||||
await this.openFunctionalTab({
|
await this.openFunctionalTab({
|
||||||
name: "About",
|
name: "About",
|
||||||
materialIcon: "sentiment_very_satisfied",
|
materialIcon: "sentiment_very_satisfied",
|
||||||
makeView() {
|
async makeView() {
|
||||||
aboutView = new AboutView();
|
aboutView = await AboutView.create();
|
||||||
aboutView.$view.classList.add("functional-view");
|
aboutView.$view.classList.add("functional-view");
|
||||||
return aboutView.$view;
|
return aboutView.$view;
|
||||||
},
|
},
|
||||||
@@ -624,7 +631,7 @@ export class ServerManagerView {
|
|||||||
reconnectUtil.pollInternetAndReload();
|
reconnectUtil.pollInternetAndReload();
|
||||||
await webview
|
await webview
|
||||||
.getWebContents()
|
.getWebContents()
|
||||||
.loadURL(`file://${rendererDirectory}/network.html`);
|
.loadURL(new URL("app/renderer/network.html", bundleUrl).href);
|
||||||
}
|
}
|
||||||
|
|
||||||
async activateLastTab(index: number): Promise<void> {
|
async activateLastTab(index: number): Promise<void> {
|
||||||
|
|||||||
@@ -1,41 +1,21 @@
|
|||||||
import {app} from "@electron/remote";
|
import {app} from "@electron/remote";
|
||||||
|
|
||||||
import {html} from "../../../common/html.js";
|
import {bundleUrl} from "../../../common/paths.js";
|
||||||
|
|
||||||
export class AboutView {
|
export class AboutView {
|
||||||
|
static async create(): Promise<AboutView> {
|
||||||
|
return new AboutView(
|
||||||
|
await (await fetch(new URL("app/renderer/about.html", bundleUrl))).text(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
readonly $view: HTMLElement;
|
readonly $view: HTMLElement;
|
||||||
|
|
||||||
constructor() {
|
private constructor(templateHtml: string) {
|
||||||
this.$view = document.createElement("div");
|
this.$view = document.createElement("div");
|
||||||
const $shadow = this.$view.attachShadow({mode: "open"});
|
const $shadow = this.$view.attachShadow({mode: "open"});
|
||||||
$shadow.innerHTML = html`
|
$shadow.innerHTML = templateHtml;
|
||||||
<link rel="stylesheet" href="css/about.css" />
|
$shadow.querySelector("#version")!.textContent = `v${app.getVersion()}`;
|
||||||
<!-- Initially hidden to prevent FOUC -->
|
|
||||||
<div class="about" hidden>
|
|
||||||
<img class="logo" src="../resources/zulip.png" />
|
|
||||||
<p class="detail" id="version">v${app.getVersion()}</p>
|
|
||||||
<div class="maintenance-info">
|
|
||||||
<p class="detail maintainer">
|
|
||||||
Maintained by
|
|
||||||
<a
|
|
||||||
href="https://zulip.com"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>Zulip</a
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
<p class="detail license">
|
|
||||||
Available under the
|
|
||||||
<a
|
|
||||||
href="https://github.com/zulip/zulip-desktop/blob/main/LICENSE"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>Apache 2.0 License</a
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`.html;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ import Tagify from "@yaireo/tagify";
|
|||||||
import ISO6391 from "iso-639-1";
|
import ISO6391 from "iso-639-1";
|
||||||
import * as z from "zod";
|
import * as z from "zod";
|
||||||
|
|
||||||
|
import supportedLocales from "../../../../../public/translations/supported-locales.json";
|
||||||
import * as ConfigUtil from "../../../../common/config-util.js";
|
import * as ConfigUtil from "../../../../common/config-util.js";
|
||||||
import * as EnterpriseUtil from "../../../../common/enterprise-util.js";
|
import * as EnterpriseUtil from "../../../../common/enterprise-util.js";
|
||||||
import {html} from "../../../../common/html.js";
|
import {html} from "../../../../common/html.js";
|
||||||
import * as t from "../../../../common/translation-util.js";
|
import * as t from "../../../../common/translation-util.js";
|
||||||
import supportedLocales from "../../../../translations/supported-locales.json";
|
|
||||||
import {ipcRenderer} from "../../typed-ipc-renderer.js";
|
import {ipcRenderer} from "../../typed-ipc-renderer.js";
|
||||||
|
|
||||||
import {generateSelectHtml, generateSettingOption} from "./base-section.js";
|
import {generateSelectHtml, generateSettingOption} from "./base-section.js";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import process from "node:process";
|
import process from "node:process";
|
||||||
|
|
||||||
import type {DndSettings} from "../../../../common/dnd-util.js";
|
import type {DndSettings} from "../../../../common/dnd-util.js";
|
||||||
import {html} from "../../../../common/html.js";
|
import {bundleUrl} from "../../../../common/paths.js";
|
||||||
import type {NavItem} from "../../../../common/types.js";
|
import type {NavItem} from "../../../../common/types.js";
|
||||||
import {ipcRenderer} from "../../typed-ipc-renderer.js";
|
import {ipcRenderer} from "../../typed-ipc-renderer.js";
|
||||||
|
|
||||||
@@ -13,34 +13,24 @@ import {initServersSection} from "./servers-section.js";
|
|||||||
import {initShortcutsSection} from "./shortcuts-section.js";
|
import {initShortcutsSection} from "./shortcuts-section.js";
|
||||||
|
|
||||||
export class PreferenceView {
|
export class PreferenceView {
|
||||||
|
static async create(): Promise<PreferenceView> {
|
||||||
|
return new PreferenceView(
|
||||||
|
await (
|
||||||
|
await fetch(new URL("app/renderer/preference.html", bundleUrl))
|
||||||
|
).text(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
readonly $view: HTMLElement;
|
readonly $view: HTMLElement;
|
||||||
private readonly $shadow: ShadowRoot;
|
private readonly $shadow: ShadowRoot;
|
||||||
private readonly $settingsContainer: Element;
|
private readonly $settingsContainer: Element;
|
||||||
private readonly nav: Nav;
|
private readonly nav: Nav;
|
||||||
private navItem: NavItem = "General";
|
private navItem: NavItem = "General";
|
||||||
|
|
||||||
constructor() {
|
private constructor(templateHtml: string) {
|
||||||
this.$view = document.createElement("div");
|
this.$view = document.createElement("div");
|
||||||
this.$shadow = this.$view.attachShadow({mode: "open"});
|
this.$shadow = this.$view.attachShadow({mode: "open"});
|
||||||
this.$shadow.innerHTML = html`
|
this.$shadow.innerHTML = templateHtml;
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="${require.resolve("../../../css/fonts.css")}"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="${require.resolve("../../../css/preference.css")}"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="${require.resolve("@yaireo/tagify/dist/tagify.css")}"
|
|
||||||
/>
|
|
||||||
<!-- Initially hidden to prevent FOUC -->
|
|
||||||
<div id="content" hidden>
|
|
||||||
<div id="sidebar"></div>
|
|
||||||
<div id="settings-container"></div>
|
|
||||||
</div>
|
|
||||||
`.html;
|
|
||||||
|
|
||||||
const $sidebarContainer = this.$shadow.querySelector("#sidebar")!;
|
const $sidebarContainer = this.$shadow.querySelector("#sidebar")!;
|
||||||
this.$settingsContainer = this.$shadow.querySelector(
|
this.$settingsContainer = this.$shadow.querySelector(
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import {contextBridge, webFrame} from "electron/renderer";
|
import {contextBridge, webFrame} from "electron/renderer";
|
||||||
import fs from "node:fs";
|
|
||||||
|
|
||||||
import electron_bridge, {bridgeEvents} from "./electron-bridge.js";
|
import electron_bridge, {bridgeEvents} from "./electron-bridge.js";
|
||||||
import * as NetworkError from "./pages/network.js";
|
import * as NetworkError from "./pages/network.js";
|
||||||
@@ -76,6 +75,4 @@ window.addEventListener("load", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
(async () =>
|
(async () =>
|
||||||
webFrame.executeJavaScript(
|
webFrame.executeJavaScript(ipcRenderer.sendSync("get-injected-js")))();
|
||||||
fs.readFileSync(require.resolve("./injected"), "utf8"),
|
|
||||||
))();
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import process from "node:process";
|
|||||||
import {BrowserWindow, Menu, Tray} from "@electron/remote";
|
import {BrowserWindow, Menu, Tray} from "@electron/remote";
|
||||||
|
|
||||||
import * as ConfigUtil from "../../common/config-util.js";
|
import * as ConfigUtil from "../../common/config-util.js";
|
||||||
|
import {publicPath} from "../../common/paths.js";
|
||||||
import type {RendererMessage} from "../../common/typed-ipc.js";
|
import type {RendererMessage} from "../../common/typed-ipc.js";
|
||||||
|
|
||||||
import type {ServerManagerView} from "./main.js";
|
import type {ServerManagerView} from "./main.js";
|
||||||
@@ -14,11 +15,7 @@ import {ipcRenderer} from "./typed-ipc-renderer.js";
|
|||||||
|
|
||||||
let tray: ElectronTray | null = null;
|
let tray: ElectronTray | null = null;
|
||||||
|
|
||||||
const iconDir = "../../resources/tray";
|
const appIcon = path.join(publicPath, "resources/tray/tray");
|
||||||
|
|
||||||
const traySuffix = "tray";
|
|
||||||
|
|
||||||
const appIcon = path.join(__dirname, iconDir, traySuffix);
|
|
||||||
|
|
||||||
const iconPath = (): string => {
|
const iconPath = (): string => {
|
||||||
if (process.platform === "linux") {
|
if (process.platform === "linux") {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<meta name="viewport" content="width=device-width" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
<title>Zulip</title>
|
<title>Zulip</title>
|
||||||
<link rel="stylesheet" href="css/fonts.css" />
|
<link rel="stylesheet" href="css/fonts.css" />
|
||||||
<link rel="stylesheet" href="css/main.css" type="text/css" media="screen" />
|
<link rel="stylesheet" href="css/main.css" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
13
app/renderer/preference.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="stylesheet" href="css/fonts.css" />
|
||||||
|
<link rel="stylesheet" href="css/preference.css" />
|
||||||
|
<script type="module">
|
||||||
|
import "@yaireo/tagify/dist/tagify.css";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Initially hidden to prevent FOUC -->
|
||||||
|
<div id="content" hidden>
|
||||||
|
<div id="sidebar"></div>
|
||||||
|
<div id="settings-container"></div>
|
||||||
|
</div>
|
||||||
14
changelog.md
@@ -2,6 +2,20 @@
|
|||||||
|
|
||||||
All notable changes to the Zulip desktop app are documented in this file.
|
All notable changes to the Zulip desktop app are documented in this file.
|
||||||
|
|
||||||
|
### v5.9.5 --2023-02-06
|
||||||
|
|
||||||
|
**Fixes**:
|
||||||
|
|
||||||
|
- Fixed a hang on startup when an organization cannot be connected at startup.
|
||||||
|
|
||||||
|
**Enhancements**:
|
||||||
|
|
||||||
|
- Enabled Chromium sandboxing in remote renderer processes for improved security hardening.
|
||||||
|
|
||||||
|
**Dependencies**:
|
||||||
|
|
||||||
|
- Upgraded all dependencies, including Electron 22.2.0.
|
||||||
|
|
||||||
### v5.9.4 --2023-01-04
|
### v5.9.4 --2023-01-04
|
||||||
|
|
||||||
**Fixes**:
|
**Fixes**:
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ You'll want Transifex's CLI client, `tx`.
|
|||||||
|
|
||||||
Run `tx push -s`.
|
Run `tx push -s`.
|
||||||
|
|
||||||
This uploads from `app/translations/en.json` to the
|
This uploads from `public/translations/en.json` to the
|
||||||
set of strings Transifex shows for contributors to translate.
|
set of strings Transifex shows for contributors to translate.
|
||||||
(See `.tx/config` for how that's configured.)
|
(See `.tx/config` for how that's configured.)
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ set of strings Transifex shows for contributors to translate.
|
|||||||
|
|
||||||
Run `tools/tx-pull`.
|
Run `tools/tx-pull`.
|
||||||
|
|
||||||
This writes to files `app/translations/<lang>.json`.
|
This writes to files `public/translations/<lang>.json`.
|
||||||
(See `.tx/config` for how that's configured.)
|
(See `.tx/config` for how that's configured.)
|
||||||
|
|
||||||
Then look at the following sections to see if further updates are
|
Then look at the following sections to see if further updates are
|
||||||
@@ -59,7 +59,7 @@ language. This happens when we've opened up a new language for people
|
|||||||
to contribute translations into in the Zulip project on Transifex,
|
to contribute translations into in the Zulip project on Transifex,
|
||||||
which we do when someone expresses interest in contributing them.
|
which we do when someone expresses interest in contributing them.
|
||||||
|
|
||||||
The locales for supported languages are stored in `app/translations/supported-locales.json`
|
The locales for supported languages are stored in `public/translations/supported-locales.json`
|
||||||
|
|
||||||
So, when a new language is added, update the `supported-locales` module.
|
So, when a new language is added, update the `supported-locales` module.
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
[lr]: https://github.com/zulip/zulip-desktop/releases
|
[lr]: https://github.com/zulip/zulip-desktop/releases
|
||||||
|
|
||||||
## OS X
|
## macOS
|
||||||
|
|
||||||
**DMG or zip**:
|
**DMG or zip**:
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
**Using brew**:
|
**Using brew**:
|
||||||
|
|
||||||
1. Run `brew cask install zulip` in your terminal
|
1. Run `brew install --cask zulip` in your terminal
|
||||||
2. The app will be installed in your `Applications`
|
2. The app will be installed in your `Applications`
|
||||||
3. Done! The app will update automatically (you can also use `brew update && brew upgrade zulip`)
|
3. Done! The app will update automatically (you can also use `brew update && brew upgrade zulip`)
|
||||||
|
|
||||||
|
|||||||
8851
package-lock.json
generated
69
package.json
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "zulip",
|
"name": "zulip",
|
||||||
"productName": "Zulip",
|
"productName": "Zulip",
|
||||||
"version": "5.9.4",
|
"version": "5.9.5",
|
||||||
"main": "./app/main",
|
"main": "./dist-electron",
|
||||||
"description": "Zulip Desktop App",
|
"description": "Zulip Desktop App",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"copyright": "Kandra Labs, Inc.",
|
"copyright": "Kandra Labs, Inc.",
|
||||||
@@ -21,8 +21,7 @@
|
|||||||
"node": ">=16.13.2"
|
"node": ">=16.13.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "tsc && electron .",
|
"start": "vite",
|
||||||
"clean-ts-files": "git clean \"app/*.js\" -xf",
|
|
||||||
"watch-ts": "tsc -w",
|
"watch-ts": "tsc -w",
|
||||||
"reinstall": "rimraf node_modules && npm install",
|
"reinstall": "rimraf node_modules && npm install",
|
||||||
"postinstall": "electron-builder install-app-deps",
|
"postinstall": "electron-builder install-app-deps",
|
||||||
@@ -30,11 +29,11 @@
|
|||||||
"lint-html": "htmlhint \"app/**/*.html\"",
|
"lint-html": "htmlhint \"app/**/*.html\"",
|
||||||
"lint-js": "xo",
|
"lint-js": "xo",
|
||||||
"prettier-non-js": "prettier --check --loglevel=warn . \"!**/*.{js,ts}\"",
|
"prettier-non-js": "prettier --check --loglevel=warn . \"!**/*.{js,ts}\"",
|
||||||
"test": "tsc --noEmit && npm run lint-html && npm run lint-css && npm run lint-js && npm run prettier-non-js",
|
"test": "tsc && npm run lint-html && npm run lint-css && npm run lint-js && npm run prettier-non-js",
|
||||||
"test-e2e": "tsc && tape \"tests/**/*.js\"",
|
"test-e2e": "vite build && tape \"tests/**/*.js\"",
|
||||||
"pack": "tsc && electron-builder --dir",
|
"pack": "vite build && electron-builder --dir",
|
||||||
"dist": "tsc && electron-builder",
|
"dist": "vite build && electron-builder",
|
||||||
"mas": "tsc && electron-builder --mac mas"
|
"mas": "vite build && electron-builder --mac mas"
|
||||||
},
|
},
|
||||||
"pre-commit": [
|
"pre-commit": [
|
||||||
"test"
|
"test"
|
||||||
@@ -47,7 +46,7 @@
|
|||||||
"**/*.node"
|
"**/*.node"
|
||||||
],
|
],
|
||||||
"files": [
|
"files": [
|
||||||
"app/**/*"
|
"dist-electron/**/*"
|
||||||
],
|
],
|
||||||
"copyright": "©2020 Kandra Labs, Inc.",
|
"copyright": "©2020 Kandra Labs, Inc.",
|
||||||
"mac": {
|
"mac": {
|
||||||
@@ -142,25 +141,12 @@
|
|||||||
"InstantMessaging"
|
"InstantMessaging"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron/remote": "^2.0.8",
|
"gatemaker": "^1.0.0"
|
||||||
"@sentry/electron": "^4.1.2",
|
|
||||||
"@yaireo/tagify": "^4.5.0",
|
|
||||||
"adm-zip": "^0.5.5",
|
|
||||||
"auto-launch": "^5.0.5",
|
|
||||||
"backoff": "^2.5.0",
|
|
||||||
"electron-log": "^4.3.5",
|
|
||||||
"electron-updater": "^5.0.1",
|
|
||||||
"electron-window-state": "^5.0.3",
|
|
||||||
"escape-goat": "^3.0.0",
|
|
||||||
"gatemaker": "^1.0.0",
|
|
||||||
"get-stream": "^6.0.1",
|
|
||||||
"i18n": "^0.15.1",
|
|
||||||
"iso-639-1": "^2.1.9",
|
|
||||||
"node-json-db": "^1.3.0",
|
|
||||||
"semver": "^7.3.5",
|
|
||||||
"zod": "^3.5.1"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@electron/notarize": "^1.2.3",
|
||||||
|
"@electron/remote": "^2.0.8",
|
||||||
|
"@sentry/electron": "^4.1.2",
|
||||||
"@types/adm-zip": "^0.5.0",
|
"@types/adm-zip": "^0.5.0",
|
||||||
"@types/auto-launch": "^5.0.2",
|
"@types/auto-launch": "^5.0.2",
|
||||||
"@types/backoff": "^2.5.2",
|
"@types/backoff": "^2.5.2",
|
||||||
@@ -168,22 +154,37 @@
|
|||||||
"@types/node": "^16.11.26",
|
"@types/node": "^16.11.26",
|
||||||
"@types/requestidlecallback": "^0.3.4",
|
"@types/requestidlecallback": "^0.3.4",
|
||||||
"@types/yaireo__tagify": "^4.3.2",
|
"@types/yaireo__tagify": "^4.3.2",
|
||||||
|
"@yaireo/tagify": "^4.5.0",
|
||||||
|
"adm-zip": "^0.5.5",
|
||||||
|
"auto-launch": "^5.0.5",
|
||||||
|
"backoff": "^2.5.0",
|
||||||
"dotenv": "^16.0.0",
|
"dotenv": "^16.0.0",
|
||||||
"electron": "^22.0.0",
|
"electron": "^22.0.0",
|
||||||
"electron-builder": "^23.0.3",
|
"electron-builder": "^23.0.3",
|
||||||
"electron-notarize": "^1.0.0",
|
"electron-log": "^4.3.5",
|
||||||
|
"electron-updater": "^5.0.1",
|
||||||
|
"electron-window-state": "^5.0.3",
|
||||||
|
"escape-goat": "^4.0.0",
|
||||||
|
"get-stream": "^6.0.1",
|
||||||
"htmlhint": "^1.1.2",
|
"htmlhint": "^1.1.2",
|
||||||
|
"i18n": "^0.15.1",
|
||||||
|
"iso-639-1": "^2.1.9",
|
||||||
"medium": "^1.2.0",
|
"medium": "^1.2.0",
|
||||||
|
"node-json-db": "^1.3.0",
|
||||||
"playwright-core": "^1.30.0-alpha-jan-3-2023",
|
"playwright-core": "^1.30.0-alpha-jan-3-2023",
|
||||||
"pre-commit": "^1.2.2",
|
"pre-commit": "^1.2.2",
|
||||||
"prettier": "^2.3.2",
|
"prettier": "^2.3.2",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^4.1.2",
|
||||||
|
"semver": "^7.3.5",
|
||||||
"stylelint": "^14.5.3",
|
"stylelint": "^14.5.3",
|
||||||
"stylelint-config-prettier": "^9.0.3",
|
"stylelint-config-prettier": "^9.0.3",
|
||||||
"stylelint-config-standard": "^29.0.0",
|
"stylelint-config-standard": "^29.0.0",
|
||||||
"tape": "^5.2.2",
|
"tape": "^5.2.2",
|
||||||
"typescript": "^4.3.5",
|
"typescript": "^4.3.5",
|
||||||
"xo": "^0.53.1"
|
"vite": "^4.1.1",
|
||||||
|
"vite-plugin-electron": "^0.11.1",
|
||||||
|
"xo": "^0.53.1",
|
||||||
|
"zod": "^3.5.1"
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"bracketSpacing": false,
|
"bracketSpacing": false,
|
||||||
@@ -203,8 +204,7 @@
|
|||||||
"target": "./app/common",
|
"target": "./app/common",
|
||||||
"from": "./app",
|
"from": "./app",
|
||||||
"except": [
|
"except": [
|
||||||
"./common",
|
"./common"
|
||||||
"./translations"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -212,8 +212,7 @@
|
|||||||
"from": "./app",
|
"from": "./app",
|
||||||
"except": [
|
"except": [
|
||||||
"./common",
|
"./common",
|
||||||
"./main",
|
"./main"
|
||||||
"./translations"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -222,7 +221,7 @@
|
|||||||
"except": [
|
"except": [
|
||||||
"./common",
|
"./common",
|
||||||
"./renderer",
|
"./renderer",
|
||||||
"./translations"
|
"./resources"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 321 B After Width: | Height: | Size: 321 B |
|
Before Width: | Height: | Size: 631 B After Width: | Height: | Size: 631 B |
|
Before Width: | Height: | Size: 932 B After Width: | Height: | Size: 932 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
@@ -2,8 +2,8 @@
|
|||||||
const path = require("node:path");
|
const path = require("node:path");
|
||||||
const process = require("node:process");
|
const process = require("node:process");
|
||||||
|
|
||||||
|
const {notarize} = require("@electron/notarize");
|
||||||
const dotenv = require("dotenv");
|
const dotenv = require("dotenv");
|
||||||
const {notarize} = require("electron-notarize");
|
|
||||||
|
|
||||||
dotenv.config({path: path.join(__dirname, "/../.env")});
|
dotenv.config({path: path.join(__dirname, "/../.env")});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "5.9.3",
|
"version": "5.9.3",
|
||||||
"productName": "ZulipTest",
|
"productName": "ZulipTest",
|
||||||
"main": "../app/main/index.js"
|
"main": "../dist-electron"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es2021",
|
"noEmit": true,
|
||||||
"module": "commonjs",
|
"target": "esnext",
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noImplicitOverride": true
|
"noImplicitOverride": true,
|
||||||
|
"types": ["vite/client"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
typings.d.ts
vendored
@@ -3,3 +3,10 @@ declare namespace Electron {
|
|||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||||
interface IncomingMessage extends NodeJS.ReadableStream {}
|
interface IncomingMessage extends NodeJS.ReadableStream {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module "zulip:remote" {
|
||||||
|
export const {
|
||||||
|
app,
|
||||||
|
dialog,
|
||||||
|
}: typeof import("electron/main") | typeof import("@electron/remote");
|
||||||
|
}
|
||||||
|
|||||||
116
vite.config.ts
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
|
|
||||||
|
import * as path from "node:path";
|
||||||
|
|
||||||
|
import {defineConfig} from "vite";
|
||||||
|
import electron from "vite-plugin-electron";
|
||||||
|
|
||||||
|
let resolveInjected: () => void;
|
||||||
|
let resolvePreload: () => void;
|
||||||
|
let resolveRenderer: () => void;
|
||||||
|
const whenInjected = new Promise<void>((resolve) => {
|
||||||
|
resolveInjected = resolve;
|
||||||
|
});
|
||||||
|
const whenPreload = new Promise<void>((resolve) => {
|
||||||
|
resolvePreload = resolve;
|
||||||
|
});
|
||||||
|
const whenRenderer = new Promise<void>((resolve) => {
|
||||||
|
resolveRenderer = resolve;
|
||||||
|
});
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
electron([
|
||||||
|
{
|
||||||
|
entry: {
|
||||||
|
index: "app/main",
|
||||||
|
},
|
||||||
|
async onstart({startup}) {
|
||||||
|
await whenInjected;
|
||||||
|
await whenPreload;
|
||||||
|
await whenRenderer;
|
||||||
|
await startup();
|
||||||
|
},
|
||||||
|
vite: {
|
||||||
|
build: {
|
||||||
|
sourcemap: true,
|
||||||
|
rollupOptions: {
|
||||||
|
external: ["electron", /^electron\//, /^gatemaker\//],
|
||||||
|
},
|
||||||
|
ssr: true,
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"zulip:remote": "electron/main",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ssr: {
|
||||||
|
noExternal: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
entry: {
|
||||||
|
injected: "app/renderer/js/injected.ts",
|
||||||
|
},
|
||||||
|
onstart() {
|
||||||
|
resolveInjected();
|
||||||
|
},
|
||||||
|
vite: {
|
||||||
|
build: {
|
||||||
|
sourcemap: "inline",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
entry: {
|
||||||
|
preload: "app/renderer/js/preload.ts",
|
||||||
|
},
|
||||||
|
onstart() {
|
||||||
|
resolvePreload();
|
||||||
|
},
|
||||||
|
vite: {
|
||||||
|
build: {
|
||||||
|
sourcemap: "inline",
|
||||||
|
rollupOptions: {
|
||||||
|
external: ["electron", /^electron\//],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
entry: {
|
||||||
|
renderer: "app/renderer/js/main.ts",
|
||||||
|
},
|
||||||
|
onstart() {
|
||||||
|
resolveRenderer();
|
||||||
|
},
|
||||||
|
vite: {
|
||||||
|
build: {
|
||||||
|
sourcemap: true,
|
||||||
|
rollupOptions: {
|
||||||
|
external: ["electron", /^electron\//],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"zulip:remote": "@electron/remote",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
build: {
|
||||||
|
outDir: "dist-electron",
|
||||||
|
sourcemap: true,
|
||||||
|
rollupOptions: {
|
||||||
|
input: {
|
||||||
|
renderer: path.join(__dirname, "app/renderer/main.html"),
|
||||||
|
network: path.join(__dirname, "app/renderer/network.html"),
|
||||||
|
about: path.join(__dirname, "app/renderer/about.html"),
|
||||||
|
preference: path.join(__dirname, "app/renderer/preference.html"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||