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
 | 
			
		||||
 | 
			
		||||
# Compiled binary build directory
 | 
			
		||||
dist/
 | 
			
		||||
/dist/
 | 
			
		||||
/dist-electron/
 | 
			
		||||
 | 
			
		||||
#snap generated files
 | 
			
		||||
snap/parts
 | 
			
		||||
@@ -39,6 +40,3 @@ config.gypi
 | 
			
		||||
# tests/package.json
 | 
			
		||||
 | 
			
		||||
.python-version
 | 
			
		||||
 | 
			
		||||
# Ignore all the typescript compiled files
 | 
			
		||||
app/**/*.js
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,3 @@
 | 
			
		||||
/app/**/*.js
 | 
			
		||||
/app/translations/*.json
 | 
			
		||||
/dist
 | 
			
		||||
/dist-electron
 | 
			
		||||
/public/translations/*.json
 | 
			
		||||
 
 | 
			
		||||
@@ -2,8 +2,8 @@
 | 
			
		||||
host = https://www.transifex.com
 | 
			
		||||
 | 
			
		||||
[zulip.desktopjson]
 | 
			
		||||
file_filter = app/translations/<lang>.json
 | 
			
		||||
file_filter = public/translations/<lang>.json
 | 
			
		||||
minimum_perc = 0
 | 
			
		||||
source_file = app/translations/en.json
 | 
			
		||||
source_file = public/translations/en.json
 | 
			
		||||
source_lang = en
 | 
			
		||||
type = KEYVALUEJSON
 | 
			
		||||
 
 | 
			
		||||
@@ -5,14 +5,14 @@ import * as Sentry from "@sentry/electron";
 | 
			
		||||
import {JsonDB} from "node-json-db";
 | 
			
		||||
import {DataError} from "node-json-db/dist/lib/Errors";
 | 
			
		||||
import type * as z from "zod";
 | 
			
		||||
import {app, dialog} from "zulip:remote";
 | 
			
		||||
 | 
			
		||||
import {configSchemata} from "./config-schemata.js";
 | 
			
		||||
import * as EnterpriseUtil from "./enterprise-util.js";
 | 
			
		||||
import Logger from "./logger-util.js";
 | 
			
		||||
import {app, dialog} from "./remote.js";
 | 
			
		||||
 | 
			
		||||
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({
 | 
			
		||||
@@ -26,7 +26,7 @@ reloadDb();
 | 
			
		||||
export function getConfigItem<Key extends keyof Config>(
 | 
			
		||||
  key: Key,
 | 
			
		||||
  defaultValue: Config[Key],
 | 
			
		||||
): z.output<typeof configSchemata[Key]> {
 | 
			
		||||
): z.output<(typeof configSchemata)[Key]> {
 | 
			
		||||
  try {
 | 
			
		||||
    db.reload();
 | 
			
		||||
  } catch (error: unknown) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import fs from "node:fs";
 | 
			
		||||
 | 
			
		||||
import {app} from "./remote.js";
 | 
			
		||||
import {app} from "zulip:remote";
 | 
			
		||||
 | 
			
		||||
let setupCompleted = false;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ import * as ConfigUtil from "./config-util.js";
 | 
			
		||||
 | 
			
		||||
export type DndSettings = {
 | 
			
		||||
  [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 = {
 | 
			
		||||
  [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 process from "node:process";
 | 
			
		||||
 | 
			
		||||
import {app} from "zulip:remote";
 | 
			
		||||
 | 
			
		||||
import {initSetUp} from "./default-util.js";
 | 
			
		||||
import {app} from "./remote.js";
 | 
			
		||||
 | 
			
		||||
type LoggerOptions = {
 | 
			
		||||
  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 * as ConfigUtil from "./config-util.js";
 | 
			
		||||
import {publicPath} from "./paths.js";
 | 
			
		||||
 | 
			
		||||
i18n.configure({
 | 
			
		||||
  directory: path.join(__dirname, "../translations/"),
 | 
			
		||||
  directory: path.join(publicPath, "translations/"),
 | 
			
		||||
  updateFiles: false,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,8 @@ export type MainMessage = {
 | 
			
		||||
  "fetch-user-agent": () => string;
 | 
			
		||||
  "focus-app": () => void;
 | 
			
		||||
  "focus-this-webview": () => void;
 | 
			
		||||
  "get-injected-js": () => string;
 | 
			
		||||
  "new-clipboard-key": () => {key: Uint8Array; sig: Uint8Array};
 | 
			
		||||
  "permission-callback": (permissionCallbackId: number, grant: boolean) => void;
 | 
			
		||||
  "quit-app": () => void;
 | 
			
		||||
  "realm-icon-changed": (serverURL: string, iconURL: string) => void;
 | 
			
		||||
@@ -27,6 +29,7 @@ export type MainMessage = {
 | 
			
		||||
export type MainCall = {
 | 
			
		||||
  "get-server-settings": (domain: string) => ServerConf;
 | 
			
		||||
  "is-online": (url: string) => boolean;
 | 
			
		||||
  "poll-clipboard": (key: Uint8Array, sig: Uint8Array) => string | undefined;
 | 
			
		||||
  "save-server-icon": (iconURL: string) => string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,9 @@
 | 
			
		||||
import {clipboard} from "electron/common";
 | 
			
		||||
import type {IpcMainEvent, WebContents} 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 process from "node:process";
 | 
			
		||||
 | 
			
		||||
@@ -7,6 +11,7 @@ import * as remoteMain from "@electron/remote/main";
 | 
			
		||||
import windowStateKeeper from "electron-window-state";
 | 
			
		||||
 | 
			
		||||
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 {MenuProps} from "../common/types.js";
 | 
			
		||||
 | 
			
		||||
@@ -35,13 +40,13 @@ let badgeCount: number;
 | 
			
		||||
 | 
			
		||||
let isQuitting = false;
 | 
			
		||||
 | 
			
		||||
// Load this url in main window
 | 
			
		||||
const mainUrl = "file://" + path.join(__dirname, "../renderer", "main.html");
 | 
			
		||||
// Load this file in main window
 | 
			
		||||
const mainUrl = new URL("app/renderer/main.html", bundleUrl).href;
 | 
			
		||||
 | 
			
		||||
const permissionCallbacks = new Map<number, (grant: boolean) => void>();
 | 
			
		||||
let nextPermissionCallbackId = 0;
 | 
			
		||||
 | 
			
		||||
const appIcon = path.join(__dirname, "../resources", "Icon");
 | 
			
		||||
const appIcon = path.join(publicPath, "resources/Icon");
 | 
			
		||||
 | 
			
		||||
const iconPath = (): string =>
 | 
			
		||||
  appIcon + (process.platform === "win32" ? ".ico" : ".png");
 | 
			
		||||
@@ -74,7 +79,7 @@ function createMainWindow(): BrowserWindow {
 | 
			
		||||
    minWidth: 500,
 | 
			
		||||
    minHeight: 400,
 | 
			
		||||
    webPreferences: {
 | 
			
		||||
      preload: require.resolve("../renderer/js/main"),
 | 
			
		||||
      preload: path.join(bundlePath, "renderer.js"),
 | 
			
		||||
      sandbox: false,
 | 
			
		||||
      webviewTag: true,
 | 
			
		||||
    },
 | 
			
		||||
@@ -201,6 +206,49 @@ function createMainWindow(): BrowserWindow {
 | 
			
		||||
  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({
 | 
			
		||||
    tabs: [],
 | 
			
		||||
  });
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
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";
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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 {Buffer} from "node:buffer";
 | 
			
		||||
import crypto from "node:crypto";
 | 
			
		||||
import {ipcRenderer} from "./typed-ipc-renderer.js";
 | 
			
		||||
 | 
			
		||||
// This helper is exposed via electron_bridge for use in the social
 | 
			
		||||
// login flow.
 | 
			
		||||
@@ -30,7 +28,8 @@ export class ClipboardDecrypterImpl implements ClipboardDecrypter {
 | 
			
		||||
  constructor(_: number) {
 | 
			
		||||
    // At this time, the only version is 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) => {
 | 
			
		||||
      let interval: NodeJS.Timeout | null = null;
 | 
			
		||||
      const startPolling = () => {
 | 
			
		||||
@@ -38,7 +37,7 @@ export class ClipboardDecrypterImpl implements ClipboardDecrypter {
 | 
			
		||||
          interval = setInterval(poll, 1000);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        poll();
 | 
			
		||||
        void poll();
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      const stopPolling = () => {
 | 
			
		||||
@@ -48,30 +47,9 @@ export class ClipboardDecrypterImpl implements ClipboardDecrypter {
 | 
			
		||||
        }
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      const poll = () => {
 | 
			
		||||
        let plaintext;
 | 
			
		||||
 | 
			
		||||
        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;
 | 
			
		||||
        }
 | 
			
		||||
      const poll = async () => {
 | 
			
		||||
        const plaintext = await ipcRenderer.invoke("poll-clipboard", key, sig);
 | 
			
		||||
        if (plaintext === undefined) return;
 | 
			
		||||
 | 
			
		||||
        window.removeEventListener("focus", startPolling);
 | 
			
		||||
        window.removeEventListener("blur", stopPolling);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
import type {WebContents} from "electron/main";
 | 
			
		||||
import fs from "node:fs";
 | 
			
		||||
import path from "node:path";
 | 
			
		||||
import process from "node:process";
 | 
			
		||||
 | 
			
		||||
import * as remote from "@electron/remote";
 | 
			
		||||
@@ -11,6 +10,7 @@ import type {Html} from "../../../common/html.js";
 | 
			
		||||
import {html} from "../../../common/html.js";
 | 
			
		||||
import type {RendererMessage} from "../../../common/typed-ipc.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 * as SystemUtil from "../utils/system-util.js";
 | 
			
		||||
 | 
			
		||||
@@ -42,7 +42,7 @@ export default class WebView {
 | 
			
		||||
        src="${props.url}"
 | 
			
		||||
        ${props.preload === undefined
 | 
			
		||||
          ? html``
 | 
			
		||||
          : html`preload="${props.preload}" webpreferences="sandbox=no"`}
 | 
			
		||||
          : html`preload="${props.preload}"`}
 | 
			
		||||
        partition="persist:webviewsession"
 | 
			
		||||
        allowpopups
 | 
			
		||||
      >
 | 
			
		||||
@@ -56,11 +56,9 @@ export default class WebView {
 | 
			
		||||
    ) as HTMLElement;
 | 
			
		||||
    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) => {
 | 
			
		||||
      $element.addEventListener(
 | 
			
		||||
        "did-navigate",
 | 
			
		||||
        "did-attach",
 | 
			
		||||
        () => {
 | 
			
		||||
          resolve();
 | 
			
		||||
        },
 | 
			
		||||
@@ -210,10 +208,7 @@ export default class WebView {
 | 
			
		||||
    this.focus();
 | 
			
		||||
    this.props.onTitleChange();
 | 
			
		||||
    // Injecting preload css in webview to override some css rules
 | 
			
		||||
    (async () =>
 | 
			
		||||
      this.getWebContents().insertCSS(
 | 
			
		||||
        fs.readFileSync(path.join(__dirname, "/../../css/preload.css"), "utf8"),
 | 
			
		||||
      ))();
 | 
			
		||||
    (async () => this.getWebContents().insertCSS(preloadCss))();
 | 
			
		||||
 | 
			
		||||
    // Get customCSS again from config util to avoid warning user again
 | 
			
		||||
    const customCss = ConfigUtil.getConfigItem("customCSS", null);
 | 
			
		||||
@@ -229,9 +224,7 @@ export default class WebView {
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      (async () =>
 | 
			
		||||
        this.getWebContents().insertCSS(
 | 
			
		||||
          fs.readFileSync(path.resolve(__dirname, customCss), "utf8"),
 | 
			
		||||
        ))();
 | 
			
		||||
        this.getWebContents().insertCSS(fs.readFileSync(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 {ClipboardDecrypterImpl} from "./clipboard-decrypter.js";
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
import {clipboard} from "electron/common";
 | 
			
		||||
import fs from "node:fs";
 | 
			
		||||
import path from "node:path";
 | 
			
		||||
import process from "node:process";
 | 
			
		||||
import url from "node:url";
 | 
			
		||||
 | 
			
		||||
import {Menu, app, dialog, session} 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 Logger from "../../common/logger-util.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 FunctionalTab from "./components/functional-tab.js";
 | 
			
		||||
@@ -44,12 +47,13 @@ const logger = new Logger({
 | 
			
		||||
  file: "errors.log",
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const rendererDirectory = path.resolve(__dirname, "..");
 | 
			
		||||
type ServerOrFunctionalTab = ServerTab | FunctionalTab;
 | 
			
		||||
 | 
			
		||||
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 {
 | 
			
		||||
  $addServerButton: HTMLButtonElement;
 | 
			
		||||
@@ -362,7 +366,10 @@ export class ServerManagerView {
 | 
			
		||||
    this.tabs.push(
 | 
			
		||||
      new ServerTab({
 | 
			
		||||
        role: "server",
 | 
			
		||||
        icon: server.icon,
 | 
			
		||||
        icon: `data:application/octet-stream;base64,${fs.readFileSync(
 | 
			
		||||
          server.icon,
 | 
			
		||||
          "base64",
 | 
			
		||||
        )}`,
 | 
			
		||||
        name: server.alias,
 | 
			
		||||
        $root: this.$tabsContainer,
 | 
			
		||||
        onClick: this.activateLastTab.bind(this, index),
 | 
			
		||||
@@ -397,7 +404,7 @@ export class ServerManagerView {
 | 
			
		||||
            await this.openNetworkTroubleshooting(index);
 | 
			
		||||
          },
 | 
			
		||||
          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: {
 | 
			
		||||
    name: string;
 | 
			
		||||
    materialIcon: string;
 | 
			
		||||
    makeView: () => Element;
 | 
			
		||||
    makeView: () => Promise<Element>;
 | 
			
		||||
    destroyView: () => void;
 | 
			
		||||
  }): Promise<void> {
 | 
			
		||||
    if (this.functionalTabs.has(tabProps.name)) {
 | 
			
		||||
@@ -555,7 +562,7 @@ export class ServerManagerView {
 | 
			
		||||
    this.functionalTabs.set(tabProps.name, index);
 | 
			
		||||
 | 
			
		||||
    const tabIndex = this.getTabIndex();
 | 
			
		||||
    const $view = tabProps.makeView();
 | 
			
		||||
    const $view = await tabProps.makeView();
 | 
			
		||||
    this.$webviewsContainer.append($view);
 | 
			
		||||
 | 
			
		||||
    this.tabs.push(
 | 
			
		||||
@@ -586,8 +593,8 @@ export class ServerManagerView {
 | 
			
		||||
    await this.openFunctionalTab({
 | 
			
		||||
      name: "Settings",
 | 
			
		||||
      materialIcon: "settings",
 | 
			
		||||
      makeView: () => {
 | 
			
		||||
        this.preferenceView = new PreferenceView();
 | 
			
		||||
      makeView: async () => {
 | 
			
		||||
        this.preferenceView = await PreferenceView.create();
 | 
			
		||||
        this.preferenceView.$view.classList.add("functional-view");
 | 
			
		||||
        return this.preferenceView.$view;
 | 
			
		||||
      },
 | 
			
		||||
@@ -605,8 +612,8 @@ export class ServerManagerView {
 | 
			
		||||
    await this.openFunctionalTab({
 | 
			
		||||
      name: "About",
 | 
			
		||||
      materialIcon: "sentiment_very_satisfied",
 | 
			
		||||
      makeView() {
 | 
			
		||||
        aboutView = new AboutView();
 | 
			
		||||
      async makeView() {
 | 
			
		||||
        aboutView = await AboutView.create();
 | 
			
		||||
        aboutView.$view.classList.add("functional-view");
 | 
			
		||||
        return aboutView.$view;
 | 
			
		||||
      },
 | 
			
		||||
@@ -624,7 +631,7 @@ export class ServerManagerView {
 | 
			
		||||
    reconnectUtil.pollInternetAndReload();
 | 
			
		||||
    await webview
 | 
			
		||||
      .getWebContents()
 | 
			
		||||
      .loadURL(`file://${rendererDirectory}/network.html`);
 | 
			
		||||
      .loadURL(new URL("app/renderer/network.html", bundleUrl).href);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async activateLastTab(index: number): Promise<void> {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,41 +1,21 @@
 | 
			
		||||
import {app} from "@electron/remote";
 | 
			
		||||
 | 
			
		||||
import {html} from "../../../common/html.js";
 | 
			
		||||
import {bundleUrl} from "../../../common/paths.js";
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
  constructor() {
 | 
			
		||||
  private constructor(templateHtml: string) {
 | 
			
		||||
    this.$view = document.createElement("div");
 | 
			
		||||
    const $shadow = this.$view.attachShadow({mode: "open"});
 | 
			
		||||
    $shadow.innerHTML = html`
 | 
			
		||||
      <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">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;
 | 
			
		||||
    $shadow.innerHTML = templateHtml;
 | 
			
		||||
    $shadow.querySelector("#version")!.textContent = `v${app.getVersion()}`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  destroy() {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,11 +9,11 @@ import Tagify from "@yaireo/tagify";
 | 
			
		||||
import ISO6391 from "iso-639-1";
 | 
			
		||||
import * as z from "zod";
 | 
			
		||||
 | 
			
		||||
import supportedLocales from "../../../../../public/translations/supported-locales.json";
 | 
			
		||||
import * as ConfigUtil from "../../../../common/config-util.js";
 | 
			
		||||
import * as EnterpriseUtil from "../../../../common/enterprise-util.js";
 | 
			
		||||
import {html} from "../../../../common/html.js";
 | 
			
		||||
import * as t from "../../../../common/translation-util.js";
 | 
			
		||||
import supportedLocales from "../../../../translations/supported-locales.json";
 | 
			
		||||
import {ipcRenderer} from "../../typed-ipc-renderer.js";
 | 
			
		||||
 | 
			
		||||
import {generateSelectHtml, generateSettingOption} from "./base-section.js";
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import process from "node:process";
 | 
			
		||||
 | 
			
		||||
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 {ipcRenderer} from "../../typed-ipc-renderer.js";
 | 
			
		||||
 | 
			
		||||
@@ -13,34 +13,24 @@ import {initServersSection} from "./servers-section.js";
 | 
			
		||||
import {initShortcutsSection} from "./shortcuts-section.js";
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
  private readonly $shadow: ShadowRoot;
 | 
			
		||||
  private readonly $settingsContainer: Element;
 | 
			
		||||
  private readonly nav: Nav;
 | 
			
		||||
  private navItem: NavItem = "General";
 | 
			
		||||
 | 
			
		||||
  constructor() {
 | 
			
		||||
  private constructor(templateHtml: string) {
 | 
			
		||||
    this.$view = document.createElement("div");
 | 
			
		||||
    this.$shadow = this.$view.attachShadow({mode: "open"});
 | 
			
		||||
    this.$shadow.innerHTML = html`
 | 
			
		||||
      <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;
 | 
			
		||||
    this.$shadow.innerHTML = templateHtml;
 | 
			
		||||
 | 
			
		||||
    const $sidebarContainer = this.$shadow.querySelector("#sidebar")!;
 | 
			
		||||
    this.$settingsContainer = this.$shadow.querySelector(
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
import {contextBridge, webFrame} from "electron/renderer";
 | 
			
		||||
import fs from "node:fs";
 | 
			
		||||
 | 
			
		||||
import electron_bridge, {bridgeEvents} from "./electron-bridge.js";
 | 
			
		||||
import * as NetworkError from "./pages/network.js";
 | 
			
		||||
@@ -76,6 +75,4 @@ window.addEventListener("load", () => {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
(async () =>
 | 
			
		||||
  webFrame.executeJavaScript(
 | 
			
		||||
    fs.readFileSync(require.resolve("./injected"), "utf8"),
 | 
			
		||||
  ))();
 | 
			
		||||
  webFrame.executeJavaScript(ipcRenderer.sendSync("get-injected-js")))();
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ import process from "node:process";
 | 
			
		||||
import {BrowserWindow, Menu, Tray} from "@electron/remote";
 | 
			
		||||
 | 
			
		||||
import * as ConfigUtil from "../../common/config-util.js";
 | 
			
		||||
import {publicPath} from "../../common/paths.js";
 | 
			
		||||
import type {RendererMessage} from "../../common/typed-ipc.js";
 | 
			
		||||
 | 
			
		||||
import type {ServerManagerView} from "./main.js";
 | 
			
		||||
@@ -14,11 +15,7 @@ import {ipcRenderer} from "./typed-ipc-renderer.js";
 | 
			
		||||
 | 
			
		||||
let tray: ElectronTray | null = null;
 | 
			
		||||
 | 
			
		||||
const iconDir = "../../resources/tray";
 | 
			
		||||
 | 
			
		||||
const traySuffix = "tray";
 | 
			
		||||
 | 
			
		||||
const appIcon = path.join(__dirname, iconDir, traySuffix);
 | 
			
		||||
const appIcon = path.join(publicPath, "resources/tray/tray");
 | 
			
		||||
 | 
			
		||||
const iconPath = (): string => {
 | 
			
		||||
  if (process.platform === "linux") {
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
    <meta name="viewport" content="width=device-width" />
 | 
			
		||||
    <title>Zulip</title>
 | 
			
		||||
    <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>
 | 
			
		||||
 | 
			
		||||
  <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.
 | 
			
		||||
 | 
			
		||||
### 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
 | 
			
		||||
 | 
			
		||||
**Fixes**:
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,7 @@ You'll want Transifex's CLI client, `tx`.
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
(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`.
 | 
			
		||||
 | 
			
		||||
This writes to files `app/translations/<lang>.json`.
 | 
			
		||||
This writes to files `public/translations/<lang>.json`.
 | 
			
		||||
(See `.tx/config` for how that's configured.)
 | 
			
		||||
 | 
			
		||||
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,
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
 | 
			
		||||
[lr]: https://github.com/zulip/zulip-desktop/releases
 | 
			
		||||
 | 
			
		||||
## OS X
 | 
			
		||||
## macOS
 | 
			
		||||
 | 
			
		||||
**DMG or zip**:
 | 
			
		||||
 | 
			
		||||
@@ -17,7 +17,7 @@
 | 
			
		||||
 | 
			
		||||
**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`
 | 
			
		||||
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",
 | 
			
		||||
  "productName": "Zulip",
 | 
			
		||||
  "version": "5.9.4",
 | 
			
		||||
  "main": "./app/main",
 | 
			
		||||
  "version": "5.9.5",
 | 
			
		||||
  "main": "./dist-electron",
 | 
			
		||||
  "description": "Zulip Desktop App",
 | 
			
		||||
  "license": "Apache-2.0",
 | 
			
		||||
  "copyright": "Kandra Labs, Inc.",
 | 
			
		||||
@@ -21,8 +21,7 @@
 | 
			
		||||
    "node": ">=16.13.2"
 | 
			
		||||
  },
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "start": "tsc && electron .",
 | 
			
		||||
    "clean-ts-files": "git clean \"app/*.js\" -xf",
 | 
			
		||||
    "start": "vite",
 | 
			
		||||
    "watch-ts": "tsc -w",
 | 
			
		||||
    "reinstall": "rimraf node_modules && npm install",
 | 
			
		||||
    "postinstall": "electron-builder install-app-deps",
 | 
			
		||||
@@ -30,11 +29,11 @@
 | 
			
		||||
    "lint-html": "htmlhint \"app/**/*.html\"",
 | 
			
		||||
    "lint-js": "xo",
 | 
			
		||||
    "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-e2e": "tsc && tape \"tests/**/*.js\"",
 | 
			
		||||
    "pack": "tsc && electron-builder --dir",
 | 
			
		||||
    "dist": "tsc && electron-builder",
 | 
			
		||||
    "mas": "tsc && electron-builder --mac mas"
 | 
			
		||||
    "test": "tsc && npm run lint-html && npm run lint-css && npm run lint-js && npm run prettier-non-js",
 | 
			
		||||
    "test-e2e": "vite build && tape \"tests/**/*.js\"",
 | 
			
		||||
    "pack": "vite build && electron-builder --dir",
 | 
			
		||||
    "dist": "vite build && electron-builder",
 | 
			
		||||
    "mas": "vite build && electron-builder --mac mas"
 | 
			
		||||
  },
 | 
			
		||||
  "pre-commit": [
 | 
			
		||||
    "test"
 | 
			
		||||
@@ -47,7 +46,7 @@
 | 
			
		||||
      "**/*.node"
 | 
			
		||||
    ],
 | 
			
		||||
    "files": [
 | 
			
		||||
      "app/**/*"
 | 
			
		||||
      "dist-electron/**/*"
 | 
			
		||||
    ],
 | 
			
		||||
    "copyright": "©2020 Kandra Labs, Inc.",
 | 
			
		||||
    "mac": {
 | 
			
		||||
@@ -142,25 +141,12 @@
 | 
			
		||||
    "InstantMessaging"
 | 
			
		||||
  ],
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@electron/remote": "^2.0.8",
 | 
			
		||||
    "@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"
 | 
			
		||||
    "gatemaker": "^1.0.0"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@electron/notarize": "^1.2.3",
 | 
			
		||||
    "@electron/remote": "^2.0.8",
 | 
			
		||||
    "@sentry/electron": "^4.1.2",
 | 
			
		||||
    "@types/adm-zip": "^0.5.0",
 | 
			
		||||
    "@types/auto-launch": "^5.0.2",
 | 
			
		||||
    "@types/backoff": "^2.5.2",
 | 
			
		||||
@@ -168,22 +154,37 @@
 | 
			
		||||
    "@types/node": "^16.11.26",
 | 
			
		||||
    "@types/requestidlecallback": "^0.3.4",
 | 
			
		||||
    "@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",
 | 
			
		||||
    "electron": "^22.0.0",
 | 
			
		||||
    "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",
 | 
			
		||||
    "i18n": "^0.15.1",
 | 
			
		||||
    "iso-639-1": "^2.1.9",
 | 
			
		||||
    "medium": "^1.2.0",
 | 
			
		||||
    "node-json-db": "^1.3.0",
 | 
			
		||||
    "playwright-core": "^1.30.0-alpha-jan-3-2023",
 | 
			
		||||
    "pre-commit": "^1.2.2",
 | 
			
		||||
    "prettier": "^2.3.2",
 | 
			
		||||
    "rimraf": "^3.0.2",
 | 
			
		||||
    "rimraf": "^4.1.2",
 | 
			
		||||
    "semver": "^7.3.5",
 | 
			
		||||
    "stylelint": "^14.5.3",
 | 
			
		||||
    "stylelint-config-prettier": "^9.0.3",
 | 
			
		||||
    "stylelint-config-standard": "^29.0.0",
 | 
			
		||||
    "tape": "^5.2.2",
 | 
			
		||||
    "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": {
 | 
			
		||||
    "bracketSpacing": false,
 | 
			
		||||
@@ -203,8 +204,7 @@
 | 
			
		||||
              "target": "./app/common",
 | 
			
		||||
              "from": "./app",
 | 
			
		||||
              "except": [
 | 
			
		||||
                "./common",
 | 
			
		||||
                "./translations"
 | 
			
		||||
                "./common"
 | 
			
		||||
              ]
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
@@ -212,8 +212,7 @@
 | 
			
		||||
              "from": "./app",
 | 
			
		||||
              "except": [
 | 
			
		||||
                "./common",
 | 
			
		||||
                "./main",
 | 
			
		||||
                "./translations"
 | 
			
		||||
                "./main"
 | 
			
		||||
              ]
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
@@ -222,7 +221,7 @@
 | 
			
		||||
              "except": [
 | 
			
		||||
                "./common",
 | 
			
		||||
                "./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 process = require("node:process");
 | 
			
		||||
 | 
			
		||||
const {notarize} = require("@electron/notarize");
 | 
			
		||||
const dotenv = require("dotenv");
 | 
			
		||||
const {notarize} = require("electron-notarize");
 | 
			
		||||
 | 
			
		||||
dotenv.config({path: path.join(__dirname, "/../.env")});
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
{
 | 
			
		||||
  "version": "5.9.3",
 | 
			
		||||
  "productName": "ZulipTest",
 | 
			
		||||
  "main": "../app/main/index.js"
 | 
			
		||||
  "main": "../dist-electron"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,13 @@
 | 
			
		||||
{
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "target": "es2021",
 | 
			
		||||
    "module": "commonjs",
 | 
			
		||||
    "noEmit": true,
 | 
			
		||||
    "target": "esnext",
 | 
			
		||||
    "module": "esnext",
 | 
			
		||||
    "moduleResolution": "node",
 | 
			
		||||
    "esModuleInterop": true,
 | 
			
		||||
    "resolveJsonModule": 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
 | 
			
		||||
  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"),
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||