Skip to content

Commit d0f0a66

Browse files
committed
domain: Add id field to server objects.
This id will be passed around in events to uniquely identify a server. This id is not user generated, it is generated in app by domain-util.ts and stored in our JSON DB. This commit just adds the logic for generating and storing the id in the JSON DB. This commit does not make use of this newly added id, that will be done in further commits to keep the commits readable. In further commits, we will also start setting tabId in the rendered code to this id. We generate a random hex string for this id instead of having a numeric incremental ID. The reason for this is to avoid overhead of maintaining an ever increasingly numeric id while trying to make sure that ID collisions don't happen. Since we don't store our data in a real database, we can't have incremental primary keys by default and it makes little sense to implement that on our own, when this field is just being passed around to communicate b/w code and is not shown to humans.
1 parent be75baf commit d0f0a66

File tree

4 files changed

+64
-12
lines changed

4 files changed

+64
-12
lines changed

app/common/typed-ipc.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type {DndSettings} from "./dnd-util.ts";
2-
import type {MenuProperties, ServerConfig} from "./types.ts";
2+
import type {MenuProperties, ServerSettings} from "./types.ts";
33

44
export type MainMessage = {
55
"clear-app-settings": () => void;
@@ -26,7 +26,7 @@ export type MainMessage = {
2626
};
2727

2828
export type MainCall = {
29-
"get-server-settings": (domain: string) => ServerConfig;
29+
"get-server-settings": (domain: string) => ServerSettings;
3030
"is-online": (url: string) => boolean;
3131
"poll-clipboard": (key: Uint8Array, sig: Uint8Array) => string | undefined;
3232
"save-server-icon": (iconURL: string) => string | null;

app/common/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@ export type NavigationItem =
1212
| "Shortcuts";
1313

1414
export type ServerConfig = {
15+
id: string;
1516
url: string;
1617
alias: string;
1718
icon: string;
1819
zulipVersion: string;
1920
zulipFeatureLevel: number;
2021
};
2122

23+
export type ServerSettings = Omit<ServerConfig, "id">;
24+
2225
export type TabRole = "server" | "function";
2326
export type TabPage = "Settings" | "About";
2427

app/main/request.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {z} from "zod";
1010

1111
import Logger from "../common/logger-util.ts";
1212
import * as Messages from "../common/messages.ts";
13-
import type {ServerConfig} from "../common/types.ts";
13+
import type {ServerSettings} from "../common/types.ts";
1414

1515
/* Request: domain-util */
1616

@@ -42,7 +42,7 @@ const generateFilePath = (url: string): string => {
4242
export const _getServerSettings = async (
4343
domain: string,
4444
session: Session,
45-
): Promise<ServerConfig> => {
45+
): Promise<ServerSettings> => {
4646
const response = await session.fetch(domain + "/api/v1/server_settings");
4747
if (!response.ok) {
4848
throw new Error(Messages.invalidZulipServerError(domain));

app/renderer/js/utils/domain-util.ts

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import assert from "node:assert";
2+
import {randomBytes} from "node:crypto";
13
import fs from "node:fs";
24
import path from "node:path";
35

@@ -11,26 +13,43 @@ import * as EnterpriseUtil from "../../../common/enterprise-util.ts";
1113
import Logger from "../../../common/logger-util.ts";
1214
import * as Messages from "../../../common/messages.ts";
1315
import * as t from "../../../common/translation-util.ts";
14-
import type {ServerConfig} from "../../../common/types.ts";
16+
import type {ServerConfig, ServerSettings} from "../../../common/types.ts";
1517
import defaultIcon from "../../img/icon.png";
1618
import {ipcRenderer} from "../typed-ipc-renderer.ts";
1719

1820
const logger = new Logger({
1921
file: "domain-util.log",
2022
});
2123

24+
export function generateDomainId(): string {
25+
return randomBytes(5).toString("hex");
26+
}
27+
2228
// For historical reasons, we store this string in domain.json to denote a
2329
// missing icon; it does not change with the actual icon location.
2430
export const defaultIconSentinel = "../renderer/img/icon.png";
2531

26-
const serverConfigSchema = z.object({
32+
const storedServerSchema = z.object({
33+
id: z.string().optional(),
2734
url: z.url(),
2835
alias: z.string(),
2936
icon: z.string(),
3037
zulipVersion: z.string().default("unknown"),
3138
zulipFeatureLevel: z.number().default(0),
3239
});
3340

41+
const serverConfigSchema = storedServerSchema.extend({
42+
id: z.string(),
43+
});
44+
45+
function addServerId(server: z.infer<typeof storedServerSchema>): ServerConfig {
46+
assert.ok(server.id === undefined);
47+
return serverConfigSchema.parse({
48+
...server,
49+
id: generateDomainId(),
50+
});
51+
}
52+
3453
let database!: JsonDB;
3554

3655
reloadDatabase();
@@ -88,8 +107,8 @@ export async function addDomain(server: {
88107
server.icon = defaultIconSentinel;
89108
}
90109

91-
serverConfigSchema.parse(server);
92-
database.push("/domains[]", server, true);
110+
const serverWithId = addServerId(storedServerSchema.parse(server));
111+
database.push("/domains[]", serverWithId, true);
93112
reloadDatabase();
94113
}
95114

@@ -117,7 +136,7 @@ export function duplicateDomain(domain: string): boolean {
117136
export async function checkDomain(
118137
domain: string,
119138
silent = false,
120-
): Promise<ServerConfig> {
139+
): Promise<ServerSettings> {
121140
if (!silent && duplicateDomain(domain)) {
122141
// Do not check duplicate in silent mode
123142
throw new Error("This server has been added.");
@@ -126,13 +145,13 @@ export async function checkDomain(
126145
domain = formatUrl(domain);
127146

128147
try {
129-
return await getServerSettings(domain);
148+
return storedServerSchema.parse(await getServerSettings(domain));
130149
} catch {
131150
throw new Error(Messages.invalidZulipServerError(domain));
132151
}
133152
}
134153

135-
async function getServerSettings(domain: string): Promise<ServerConfig> {
154+
async function getServerSettings(domain: string): Promise<ServerSettings> {
136155
return ipcRenderer.invoke("get-server-settings", domain);
137156
}
138157

@@ -151,7 +170,11 @@ export async function updateSavedServer(
151170
const serverConfig = getDomain(index);
152171
const oldIcon = serverConfig.icon;
153172
try {
154-
const newServerConfig = await checkDomain(url, true);
173+
const newServerSetting = await checkDomain(url, true);
174+
const newServerConfig: ServerConfig = {
175+
...newServerSetting,
176+
id: serverConfig.id,
177+
};
155178
const localIconUrl = await saveServerIcon(newServerConfig.icon);
156179
if (!oldIcon || localIconUrl !== defaultIconSentinel) {
157180
newServerConfig.icon = localIconUrl;
@@ -168,6 +191,31 @@ export async function updateSavedServer(
168191
}
169192
}
170193

194+
function ensureDomainIds(): void {
195+
try {
196+
const domains = storedServerSchema
197+
.array()
198+
.parse(database.getObject<unknown>("/domains"));
199+
200+
let changed = false;
201+
202+
const updatedDomains = domains.map((server) => {
203+
if (server.id === undefined) {
204+
changed = true;
205+
server = addServerId(server);
206+
}
207+
208+
return server;
209+
});
210+
211+
if (changed) {
212+
database.push("/domains", updatedDomains, true);
213+
}
214+
} catch (error: unknown) {
215+
if (!(error instanceof DataError)) throw error;
216+
}
217+
}
218+
171219
function reloadDatabase(): void {
172220
const domainJsonPath = path.join(
173221
app.getPath("userData"),
@@ -192,6 +240,7 @@ function reloadDatabase(): void {
192240
}
193241

194242
database = new JsonDB(domainJsonPath, true, true);
243+
ensureDomainIds();
195244
}
196245

197246
export function formatUrl(domain: string): string {

0 commit comments

Comments
 (0)