Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 56 additions & 37 deletions app/common/logger-util.ts
Original file line number Diff line number Diff line change
@@ -1,90 +1,109 @@
import {Console} from "node:console"; // eslint-disable-line n/prefer-global/console
import { Console } from "node:console"; // eslint-disable-line n/prefer-global/console
import fs from "node:fs";
import os from "node:os";
import process from "node:process";

import {app} from "zulip:remote";
// FIXED: Standard Electron import path
import { app } from "electron";

import {initSetUp} from "./default-util.ts";
import { initSetUp } from "./default-util"; // FIXED: Removed .ts extension

type LoggerOptions = {
file?: string;
};

initSetUp();

// FIXED: Wrapped in backticks (`)
const logDirectory = `${app.getPath("userData")}/Logs`;

// Ensure the directory exists immediately to prevent ENOENT errors
if (!fs.existsSync(logDirectory)) {
fs.mkdirSync(logDirectory, { recursive: true });
}

type Level = "log" | "debug" | "info" | "warn" | "error";

export default class Logger {
nodeConsole: Console;

constructor(options: LoggerOptions = {}) {
let {file = "console.log"} = options;
const { file = "console.log" } = options;

file = `${logDirectory}/${file}`;
// FIXED: Wrapped in backticks (`)
const fullPath = `${logDirectory}/${file}`;

// Trim log according to type of process
if (process.type === "renderer") {
requestIdleCallback(async () => this.trimLog(file));
if (typeof requestIdleCallback !== 'undefined') {
requestIdleCallback(async () => this.trimLog(fullPath));
}
} else {
process.nextTick(async () => this.trimLog(file));
process.nextTick(async () => this.trimLog(fullPath));
}

const fileStream = fs.createWriteStream(file, {flags: "a"});
const fileStream = fs.createWriteStream(fullPath, { flags: "a" });
const nodeConsole = new Console(fileStream);

this.nodeConsole = nodeConsole;
}

_log(type: Level, ...arguments_: unknown[]): void {
arguments_.unshift(this.getTimestamp() + " |\t");
arguments_.unshift(type.toUpperCase() + " |");
this.nodeConsole[type](...arguments_);
console[type](...arguments_);
// FIXED: Renamed reserved keyword 'arguments' to 'args'
private _internalLog(type: Level, ...args: unknown[]): void {
args.unshift(this.getTimestamp() + " |\t");
args.unshift(type.toUpperCase() + " |");
// @ts-ignore - Dynamic access to console methods
this.nodeConsole[type](...args);
// @ts-ignore
console[type](...args);
}

log(...arguments_: unknown[]): void {
this._log("log", ...arguments_);
log(...args: unknown[]): void {
this._internalLog("log", ...args);
}

debug(...arguments_: unknown[]): void {
this._log("debug", ...arguments_);
debug(...args: unknown[]): void {
this._internalLog("debug", ...args);
}

info(...arguments_: unknown[]): void {
this._log("info", ...arguments_);
info(...args: unknown[]): void {
this._internalLog("info", ...args);
}

warn(...arguments_: unknown[]): void {
this._log("warn", ...arguments_);
warn(...args: unknown[]): void {
this._internalLog("warn", ...args);
}

error(...arguments_: unknown[]): void {
this._log("error", ...arguments_);
error(...args: unknown[]): void {
this._internalLog("error", ...args);
}

getTimestamp(): string {
const date = new Date();
// FIXED: Wrapped the time section in backticks (`)
const timestamp =
`${date.getMonth()}/${date.getDate()} ` +
`${date.getMinutes()}:${date.getSeconds()}`;
`${date.getMonth() + 1}/${date.getDate()} ` +
`${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
return timestamp;
}

async trimLog(file: string): Promise<void> {
const data = await fs.promises.readFile(file, "utf8");

const maxLogFileLines = 500;
const logs = data.split(os.EOL);
const logLength = logs.length - 1;

// Keep bottom maxLogFileLines of each log instance
if (logLength > maxLogFileLines) {
const trimmedLogs = logs.slice(logLength - maxLogFileLines);
const toWrite = trimmedLogs.join(os.EOL);
await fs.promises.writeFile(file, toWrite);
try {
await fs.promises.access(file, fs.constants.F_OK);

const data = await fs.promises.readFile(file, "utf8");
const maxLogFileLines = 500;
const logs = data.split(os.EOL);
const logLength = logs.length - 1;

if (logLength > maxLogFileLines) {
const trimmedLogs = logs.slice(logLength - maxLogFileLines);
const toWrite = trimmedLogs.join(os.EOL);
await fs.promises.writeFile(file, toWrite);
}
} catch (error) {
// FIXED: Wrapped in backticks (`)
console.log(`New log file initialized: ${file}`);
}
}
}
}
46 changes: 34 additions & 12 deletions app/common/messages.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,60 @@
import * as t from "./translation-util.ts";
import * as t from "./translation-util";

console.log("✅ messages.ts loaded!");

type DialogBoxError = {
title: string;
content: string;
};

/**
* Returns a detailed error message when a Zulip server is unreachable.
* Includes emojis and troubleshooting steps for the Reconnect Box.
*/
export function invalidZulipServerError(domain: string): string {
return `${domain} does not appear to be a valid Zulip server. Make sure that
• You can connect to that URL in a web browser.
• If you need a proxy to connect to the Internet, that you've configured your proxy in the Network settings.
• It's a Zulip server. (The oldest supported version is 1.6).
• The server has a valid certificate.
• The SSL is correctly configured for the certificate. Check out the SSL troubleshooting guide -
https://zulip.readthedocs.io/en/stable/production/ssl-certificates.html`;
// Using backticks (`) is required for ${variable} to work
return t.__(
`⚠️ We couldn’t reach ${domain}. Don’t worry! Here’s what you can try:

🌐 Open the URL in your web browser.
🛠️ Check your proxy settings if needed.
📦 Make sure the server is running Zulip version 1.6 or newer.
🔒 Verify the SSL certificate is valid and properly installed.

💡 Tip: Stay connected and try again if the problem persists.

For more guidance, visit:
🔗 https://zulip.readthedocs.io/en/stable/production/ssl-certificates.html`,
{ domain }
);
}

/**
* Returns an error object when multiple Enterprise organizations fail to load.
*/
export function enterpriseOrgError(domains: string[]): DialogBoxError {
let domainList = "";
for (const domain of domains) {
domainList += `• ${domain}\n`;
// FIXED: Added backticks and proper interpolation
domainList += `${domain}\n`;
}

return {
title: t.__mf(
"{number, plural, one {Could not add # organization} other {Could not add # organizations}}",
{number: domains.length},
{ number: domains.length },
),
// FIXED: Added backticks and proper interpolation
content: `${domainList}\n${t.__("Please contact your system administrator.")}`,
};
}

/**
* Returns an error object when a user tries to remove a restricted organization.
*/
export function orgRemovalError(url: string): DialogBoxError {
return {
title: t.__("Removing {{{url}}} is a restricted operation.", {url}),
title: t.__("Removing {{{url}}} is a restricted operation.", { url }),
content: t.__("Please contact your system administrator."),
};
}
}
5 changes: 4 additions & 1 deletion app/common/typed-ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,7 @@ export type RendererMessage = {
zoomActualSize: () => void;
zoomIn: () => void;
zoomOut: () => void;
};
// --- ADDED FOR CUSTOM NETWORK ERROR DESIGN ---
"update-css": (cssPath: string) => void;
"update-message": (message: string) => void;
};
Loading
Loading