opentray 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -181
- package/dist/cli.d.mts +5 -7
- package/dist/cli.d.mts.map +1 -1
- package/dist/cli.mjs +22 -89
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +10 -12
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +578 -80
- package/dist/index.mjs.map +1 -1
- package/dist/native-runtime-Cn8BaffH.d.mts +136 -0
- package/dist/native-runtime-Cn8BaffH.d.mts.map +1 -0
- package/dist/node.d.mts +30 -42
- package/dist/node.d.mts.map +1 -1
- package/dist/node.mjs +16 -2
- package/dist/node.mjs.map +1 -0
- package/dist/runtime-binding-transport-UPunusBT.mjs +177 -0
- package/dist/runtime-binding-transport-UPunusBT.mjs.map +1 -0
- package/package.json +13 -11
- package/dist/client-C27qmRfr.d.mts +0 -102
- package/dist/client-C27qmRfr.d.mts.map +0 -1
- package/dist/local-broker-DOIkewF4.mjs +0 -551
- package/dist/local-broker-DOIkewF4.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
import { t as
|
|
2
|
-
import { PROTOCOL_VERSION, PROTOCOL_VERSION as PROTOCOL_VERSION$1, createBrokerEndpointIdentity, formatBrokerEndpointName, formatBrokerStateRoot, formatUnixSocketPath, formatWindowsPipeName, isSupportedProtocolVersion } from "@opentray/spec";
|
|
1
|
+
import { t as createRuntimeBindingTransport } from "./runtime-binding-transport-UPunusBT.mjs";
|
|
2
|
+
import { PROTOCOL_VERSION, PROTOCOL_VERSION as PROTOCOL_VERSION$1, createBrokerEndpointIdentity, createBrokerEndpointIdentity as createBrokerEndpointIdentity$1, formatBrokerEndpointName, formatBrokerStateRoot, formatUnixSocketPath, formatUnixSocketPath as formatUnixSocketPath$1, formatWindowsPipeName, formatWindowsPipeName as formatWindowsPipeName$1, isSupportedProtocolVersion, parseServerFrame, sanitizeCallerLabel } from "@opentray/spec";
|
|
3
|
+
import { createConnection } from "node:net";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { basename, dirname, join } from "node:path";
|
|
7
|
+
import { constants } from "node:fs";
|
|
8
|
+
import { access, mkdir, open, readFile, rm, unlink, writeFile } from "node:fs/promises";
|
|
9
|
+
import { spawn } from "node:child_process";
|
|
3
10
|
//#region src/client.ts
|
|
4
11
|
const createInitFrame = (clientVersion) => ({
|
|
5
12
|
type: "init",
|
|
@@ -8,46 +15,19 @@ const createInitFrame = (clientVersion) => ({
|
|
|
8
15
|
});
|
|
9
16
|
function createClient(transport, options = {}) {
|
|
10
17
|
const nextRequestId = createRequestIdFactory(options.requestIdPrefix ?? "opentray");
|
|
11
|
-
return {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const requestId = nextRequestId();
|
|
23
|
-
const space = expectResponse(await transport.request({
|
|
24
|
-
type: "resolve-default-space",
|
|
25
|
-
requestId
|
|
26
|
-
}), requestId, "default-space").space;
|
|
27
|
-
return createSpaceHandle(transport, space, nextRequestId);
|
|
28
|
-
},
|
|
29
|
-
async createSurface(spaceOptions) {
|
|
30
|
-
return this.createSpace(spaceOptions);
|
|
31
|
-
}
|
|
32
|
-
};
|
|
18
|
+
return { async createTray(options) {
|
|
19
|
+
const app = await resolveDefaultAppRef(transport, nextRequestId);
|
|
20
|
+
const requestId = nextRequestId();
|
|
21
|
+
const frame = expectResponse(await transport.request({
|
|
22
|
+
type: "create-tray",
|
|
23
|
+
requestId,
|
|
24
|
+
app,
|
|
25
|
+
tray: options
|
|
26
|
+
}), requestId, "tray-created");
|
|
27
|
+
return createTrayHandle(transport, app.appId, frame.trayId ?? options.id, nextRequestId);
|
|
28
|
+
} };
|
|
33
29
|
}
|
|
34
|
-
function
|
|
35
|
-
return {
|
|
36
|
-
space,
|
|
37
|
-
async createTray(options) {
|
|
38
|
-
const requestId = nextRequestId();
|
|
39
|
-
return createTrayHandle(transport, space, expectResponse(await transport.request({
|
|
40
|
-
type: "create-tray",
|
|
41
|
-
requestId,
|
|
42
|
-
space,
|
|
43
|
-
tray: options
|
|
44
|
-
}), requestId, "tray-created").trayId, nextRequestId);
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
/** @deprecated Use `createSpaceHandle`. */
|
|
49
|
-
const createSurfaceHandle = createSpaceHandle;
|
|
50
|
-
function createTrayHandle(transport, space, trayId, nextRequestId = createRequestIdFactory("opentray")) {
|
|
30
|
+
function createTrayHandle(transport, appId, trayId, nextRequestId = createRequestIdFactory("opentray")) {
|
|
51
31
|
let nextMountOrdinal = 1;
|
|
52
32
|
const nextMountId = (extensionName) => {
|
|
53
33
|
const mountId = `${formatMountIdComponent(extensionName)}.${formatMountIdComponent(trayId)}.${nextMountOrdinal}`;
|
|
@@ -55,14 +35,13 @@ function createTrayHandle(transport, space, trayId, nextRequestId = createReques
|
|
|
55
35
|
return mountId;
|
|
56
36
|
};
|
|
57
37
|
const handle = {
|
|
58
|
-
space,
|
|
59
38
|
trayId,
|
|
60
39
|
async getBounds() {
|
|
61
40
|
const requestId = nextRequestId();
|
|
62
41
|
return expectResponse(await transport.request({
|
|
63
42
|
type: "get-tray-bounds",
|
|
64
43
|
requestId,
|
|
65
|
-
|
|
44
|
+
appId,
|
|
66
45
|
trayId
|
|
67
46
|
}), requestId, "tray-bounds").bounds;
|
|
68
47
|
},
|
|
@@ -71,7 +50,7 @@ function createTrayHandle(transport, space, trayId, nextRequestId = createReques
|
|
|
71
50
|
expectResponse(await transport.request({
|
|
72
51
|
type: "set-tray-menu",
|
|
73
52
|
requestId,
|
|
74
|
-
|
|
53
|
+
appId,
|
|
75
54
|
trayId,
|
|
76
55
|
menu
|
|
77
56
|
}), requestId, "ack");
|
|
@@ -81,7 +60,7 @@ function createTrayHandle(transport, space, trayId, nextRequestId = createReques
|
|
|
81
60
|
expectResponse(await transport.request({
|
|
82
61
|
type: "set-tray-tooltip",
|
|
83
62
|
requestId,
|
|
84
|
-
|
|
63
|
+
appId,
|
|
85
64
|
trayId,
|
|
86
65
|
tooltip
|
|
87
66
|
}), requestId, "ack");
|
|
@@ -91,27 +70,17 @@ function createTrayHandle(transport, space, trayId, nextRequestId = createReques
|
|
|
91
70
|
expectResponse(await transport.request({
|
|
92
71
|
type: "set-tray-icon",
|
|
93
72
|
requestId,
|
|
94
|
-
|
|
73
|
+
appId,
|
|
95
74
|
trayId,
|
|
96
75
|
icon
|
|
97
76
|
}), requestId, "ack");
|
|
98
77
|
},
|
|
99
|
-
async setTitle(title) {
|
|
100
|
-
const requestId = nextRequestId();
|
|
101
|
-
expectResponse(await transport.request({
|
|
102
|
-
type: "set-tray-title",
|
|
103
|
-
requestId,
|
|
104
|
-
spaceId: space.spaceId,
|
|
105
|
-
trayId,
|
|
106
|
-
title
|
|
107
|
-
}), requestId, "ack");
|
|
108
|
-
},
|
|
109
78
|
async loadExtension(options) {
|
|
110
79
|
const requestId = nextRequestId();
|
|
111
80
|
expectResponse(await transport.request({
|
|
112
81
|
type: "load-ext",
|
|
113
82
|
requestId,
|
|
114
|
-
|
|
83
|
+
appId,
|
|
115
84
|
name: options.name,
|
|
116
85
|
path: options.path,
|
|
117
86
|
...options.mountId === void 0 ? {} : { mountId: options.mountId }
|
|
@@ -125,7 +94,7 @@ function createTrayHandle(transport, space, trayId, nextRequestId = createReques
|
|
|
125
94
|
const response = await transport.request({
|
|
126
95
|
type: "ext-command",
|
|
127
96
|
requestId,
|
|
128
|
-
|
|
97
|
+
appId,
|
|
129
98
|
trayId,
|
|
130
99
|
ext,
|
|
131
100
|
data
|
|
@@ -146,18 +115,18 @@ function createTrayHandle(transport, space, trayId, nextRequestId = createReques
|
|
|
146
115
|
expectResponse(await transport.request({
|
|
147
116
|
type: "destroy-tray",
|
|
148
117
|
requestId,
|
|
149
|
-
|
|
118
|
+
appId,
|
|
150
119
|
trayId
|
|
151
120
|
}), requestId, "ack");
|
|
152
121
|
}
|
|
153
122
|
};
|
|
154
123
|
if (!isOpenTrayEventSource(transport)) return handle;
|
|
155
|
-
return attachEventfulTrayHandle(handle, transport, nextMountId);
|
|
124
|
+
return attachEventfulTrayHandle(handle, transport, appId, nextMountId);
|
|
156
125
|
}
|
|
157
|
-
const attachEventfulTrayHandle = (handle, source, nextMountId) => {
|
|
126
|
+
const attachEventfulTrayHandle = (handle, source, appId, nextMountId) => {
|
|
158
127
|
const listen = (event, handler) => source.onEvent((frame) => {
|
|
159
128
|
if (frame.type !== "event" || frame.event.type !== event) return;
|
|
160
|
-
if (!isOwnedTrayEvent(frame.event,
|
|
129
|
+
if (!isOwnedTrayEvent(frame.event, appId, handle.trayId)) return;
|
|
161
130
|
handler(frame.event);
|
|
162
131
|
});
|
|
163
132
|
const eventful = {
|
|
@@ -165,10 +134,10 @@ const attachEventfulTrayHandle = (handle, source, nextMountId) => {
|
|
|
165
134
|
listenExtension(ext, handler) {
|
|
166
135
|
return source.onEvent((frame) => {
|
|
167
136
|
if (frame.type !== "ext-event" || frame.ext !== ext) return;
|
|
168
|
-
if (frame.
|
|
137
|
+
if (frame.appId !== appId || frame.trayId !== handle.trayId) return;
|
|
169
138
|
handler({
|
|
170
139
|
scope: {
|
|
171
|
-
|
|
140
|
+
appId: frame.appId,
|
|
172
141
|
trayId: frame.trayId,
|
|
173
142
|
ext: frame.ext
|
|
174
143
|
},
|
|
@@ -194,8 +163,15 @@ const attachEventfulTrayHandle = (handle, source, nextMountId) => {
|
|
|
194
163
|
};
|
|
195
164
|
return eventful;
|
|
196
165
|
};
|
|
197
|
-
const isOwnedTrayEvent = (event,
|
|
166
|
+
const isOwnedTrayEvent = (event, appId, trayId) => event.type !== "ready" && event.appId === appId && event.trayId === trayId;
|
|
198
167
|
const isOpenTrayEventSource = (transport) => "onEvent" in transport && typeof transport.onEvent === "function";
|
|
168
|
+
const resolveDefaultAppRef = async (transport, nextRequestId) => {
|
|
169
|
+
const requestId = nextRequestId();
|
|
170
|
+
return expectResponse(await transport.request({
|
|
171
|
+
type: "resolve-default-app",
|
|
172
|
+
requestId
|
|
173
|
+
}), requestId, "default-app").app;
|
|
174
|
+
};
|
|
199
175
|
const createTrayExtensionContext = (tray, extension, options, nextMountId) => {
|
|
200
176
|
const mount = extension.resolveMount?.(options) ?? {};
|
|
201
177
|
const name = mount.name ?? extension.name;
|
|
@@ -248,13 +224,13 @@ const expectResponse = (frame, requestId, type) => {
|
|
|
248
224
|
};
|
|
249
225
|
const requestIdOf = (frame) => {
|
|
250
226
|
switch (frame.type) {
|
|
251
|
-
case "
|
|
252
|
-
case "default-
|
|
227
|
+
case "app-created":
|
|
228
|
+
case "default-app":
|
|
253
229
|
case "tray-created":
|
|
254
230
|
case "tray-bounds":
|
|
255
231
|
case "ack":
|
|
256
232
|
case "ext-command-result":
|
|
257
|
-
case "
|
|
233
|
+
case "runtime-host-health": return frame.requestId;
|
|
258
234
|
case "error": return frame.requestId;
|
|
259
235
|
case "ready":
|
|
260
236
|
case "event":
|
|
@@ -262,21 +238,543 @@ const requestIdOf = (frame) => {
|
|
|
262
238
|
}
|
|
263
239
|
};
|
|
264
240
|
//#endregion
|
|
265
|
-
//#region src/
|
|
266
|
-
|
|
267
|
-
|
|
241
|
+
//#region src/daemon/caller-label.ts
|
|
242
|
+
/**
|
|
243
|
+
* Derives a caller label using the precedence: explicit > npm_package_name >
|
|
244
|
+
* script basename > neutral default. The result is always sanitized to a
|
|
245
|
+
* filesystem- and process-safe component, so callers can use it directly in
|
|
246
|
+
* endpoint identity, runtime directories, and process titles.
|
|
247
|
+
*/
|
|
248
|
+
const resolveCallerLabel = (options = {}) => {
|
|
249
|
+
const env = options.env ?? process.env;
|
|
250
|
+
const argv = options.argv ?? process.argv;
|
|
251
|
+
const candidates = [];
|
|
252
|
+
if (options.explicit !== void 0 && options.explicit.length > 0) candidates.push(options.explicit);
|
|
253
|
+
const npmName = options.npmPackageName ?? env.npm_package_name;
|
|
254
|
+
if (typeof npmName === "string" && npmName.length > 0) candidates.push(npmName);
|
|
255
|
+
const scriptPath = options.scriptPath ?? argv[1];
|
|
256
|
+
if (typeof scriptPath === "string" && scriptPath.length > 0) candidates.push(basename(scriptPath));
|
|
257
|
+
for (const candidate of candidates) {
|
|
258
|
+
const sanitized = sanitizeCallerLabel(candidate);
|
|
259
|
+
if (sanitized.length > 0) return sanitized;
|
|
260
|
+
}
|
|
261
|
+
return sanitizeCallerLabel(void 0);
|
|
262
|
+
};
|
|
263
|
+
//#endregion
|
|
264
|
+
//#region src/daemon/native-target.ts
|
|
265
|
+
const resolveBrokerNativeTarget = (platform = process.platform, arch = process.arch) => {
|
|
266
|
+
const packageOs = platformToPackageOs(platform);
|
|
267
|
+
return {
|
|
268
|
+
packageName: `@opentray/${packageOs}-${normalizeArch(arch)}`,
|
|
269
|
+
binaryRelativePath: `bin/${packageOs === "windows" ? "opentray.exe" : "opentray"}`
|
|
270
|
+
};
|
|
271
|
+
};
|
|
272
|
+
const platformToPackageOs = (platform) => {
|
|
273
|
+
switch (platform) {
|
|
274
|
+
case "darwin":
|
|
275
|
+
case "linux": return platform;
|
|
276
|
+
case "win32": return "windows";
|
|
277
|
+
default: throw new MissingPlatformBrokerBinaryError(`unsupported OpenTray broker platform: ${platform}`, platform, process.arch);
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
const normalizeArch = (arch) => {
|
|
281
|
+
switch (arch) {
|
|
282
|
+
case "arm64":
|
|
283
|
+
case "x64": return arch;
|
|
284
|
+
default: throw new MissingPlatformBrokerBinaryError(`unsupported OpenTray broker architecture: ${arch}`, process.platform, arch);
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
var MissingPlatformBrokerBinaryError = class extends Error {
|
|
288
|
+
code = "OPENTRAY_MISSING_PLATFORM_BROKER_BINARY";
|
|
289
|
+
platform;
|
|
290
|
+
arch;
|
|
291
|
+
packageName;
|
|
292
|
+
binaryPath;
|
|
293
|
+
constructor(message, platform, arch, options = {}) {
|
|
294
|
+
super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
|
|
295
|
+
this.name = "MissingPlatformBrokerBinaryError";
|
|
296
|
+
this.platform = platform;
|
|
297
|
+
this.arch = arch;
|
|
298
|
+
this.packageName = options.packageName;
|
|
299
|
+
this.binaryPath = options.binaryPath;
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
//#endregion
|
|
303
|
+
//#region src/daemon/broker-command.ts
|
|
304
|
+
const sourceUrl = import.meta.url;
|
|
305
|
+
const resolveBrokerCommand = async (paths, options = {}) => {
|
|
306
|
+
const explicit = (options.env ?? process.env).OPENTRAY_BROKER_BIN;
|
|
307
|
+
if (explicit !== void 0 && explicit.length > 0) return commandForBinary(explicit, paths);
|
|
308
|
+
const sourceDir = options.sourceDir ?? dirname(fileURLToPath(sourceUrl));
|
|
309
|
+
const workspaceRoot = await (options.findWorkspaceRoot ?? findWorkspaceRoot)(sourceDir);
|
|
310
|
+
if (workspaceRoot !== void 0) return commandForBinary(await (options.ensureDevBrokerBinary ?? ensureDevBrokerBinary)(workspaceRoot), paths, workspaceRoot);
|
|
311
|
+
const platform = options.platform ?? process.platform;
|
|
312
|
+
const arch = options.arch ?? process.arch;
|
|
313
|
+
const target = resolveBrokerNativeTarget(platform, arch);
|
|
314
|
+
throw new MissingPlatformBrokerBinaryError(`unable to resolve OpenTray debug broker binary for ${platform}/${arch}; run from the source workspace or set OPENTRAY_BROKER_BIN`, platform, arch, { packageName: target.packageName });
|
|
315
|
+
};
|
|
316
|
+
const commandForBinary = (binary, paths, cwd) => {
|
|
317
|
+
const command = {
|
|
318
|
+
command: binary,
|
|
319
|
+
args: [
|
|
320
|
+
"broker",
|
|
321
|
+
"--endpoint",
|
|
322
|
+
paths.endpoint,
|
|
323
|
+
"--ready-file",
|
|
324
|
+
paths.readyFile,
|
|
325
|
+
"--package-version",
|
|
326
|
+
paths.packageVersion,
|
|
327
|
+
"--protocol-version",
|
|
328
|
+
`${paths.protocolVersion}`,
|
|
329
|
+
"--app-id",
|
|
330
|
+
paths.appId,
|
|
331
|
+
"--app-name",
|
|
332
|
+
paths.appName,
|
|
333
|
+
"--caller-label",
|
|
334
|
+
paths.callerLabel
|
|
335
|
+
]
|
|
336
|
+
};
|
|
337
|
+
if (cwd !== void 0) command.cwd = cwd;
|
|
338
|
+
return command;
|
|
339
|
+
};
|
|
340
|
+
const findWorkspaceRoot = async (start) => {
|
|
341
|
+
let current = start;
|
|
342
|
+
while (true) {
|
|
343
|
+
if (await exists(join(current, "Cargo.toml"))) {
|
|
344
|
+
if (await exists(join(current, "crates", "opentray-bin", "Cargo.toml"))) return current;
|
|
345
|
+
}
|
|
346
|
+
const parent = dirname(current);
|
|
347
|
+
if (parent === current) return;
|
|
348
|
+
current = parent;
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
const resolveDevBrokerBinaryPath = (workspaceRoot, platform = process.platform) => join(workspaceRoot, "target", "debug", platform === "win32" ? "opentray.exe" : "opentray");
|
|
352
|
+
const terminateWorkspaceDevBrokerProcess = async (workspaceRoot, platform = process.platform) => {
|
|
353
|
+
if (platform !== "win32") return;
|
|
354
|
+
await terminateWindowsProcessByExecutable(resolveDevBrokerBinaryPath(workspaceRoot, platform));
|
|
355
|
+
};
|
|
356
|
+
const ensureDevBrokerBinary = async (workspaceRoot) => {
|
|
357
|
+
const binary = resolveDevBrokerBinaryPath(workspaceRoot);
|
|
358
|
+
await terminateWorkspaceDevBrokerProcess(workspaceRoot);
|
|
359
|
+
await runCargoBuild(workspaceRoot);
|
|
360
|
+
return binary;
|
|
361
|
+
};
|
|
362
|
+
const runCargoBuild = (workspaceRoot) => new Promise((resolve, reject) => {
|
|
363
|
+
const child = spawn("cargo", [
|
|
364
|
+
"build",
|
|
365
|
+
"-p",
|
|
366
|
+
"opentray-bin"
|
|
367
|
+
], {
|
|
368
|
+
cwd: workspaceRoot,
|
|
369
|
+
stdio: process.env.OPENTRAY_BROKER_BUILD_LOGS === "1" ? "inherit" : "ignore"
|
|
370
|
+
});
|
|
371
|
+
child.once("error", reject);
|
|
372
|
+
child.once("exit", (code) => {
|
|
373
|
+
if (code === 0) {
|
|
374
|
+
resolve();
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
reject(/* @__PURE__ */ new Error(`cargo build -p opentray-bin failed with code ${code ?? "unknown"}`));
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
const exists = async (path) => {
|
|
381
|
+
try {
|
|
382
|
+
await access(path, constants.F_OK);
|
|
383
|
+
return true;
|
|
384
|
+
} catch {
|
|
385
|
+
return false;
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
const terminateWindowsProcessByExecutable = (binary) => new Promise((resolve, reject) => {
|
|
389
|
+
const child = spawn("powershell.exe", [
|
|
390
|
+
"-NoProfile",
|
|
391
|
+
"-NonInteractive",
|
|
392
|
+
"-ExecutionPolicy",
|
|
393
|
+
"Bypass",
|
|
394
|
+
"-Command",
|
|
395
|
+
String.raw`
|
|
396
|
+
$ErrorActionPreference = 'Stop'
|
|
397
|
+
$target = [System.IO.Path]::GetFullPath(${powerShellString(binary)})
|
|
398
|
+
$processes = @(Get-CimInstance Win32_Process -Filter "Name = 'opentray.exe'" | Where-Object {
|
|
399
|
+
$_.ExecutablePath -and ([System.IO.Path]::GetFullPath($_.ExecutablePath) -ieq $target)
|
|
400
|
+
})
|
|
401
|
+
foreach ($process in $processes) {
|
|
402
|
+
Stop-Process -Id $process.ProcessId -Force -ErrorAction Stop
|
|
403
|
+
}
|
|
404
|
+
foreach ($process in $processes) {
|
|
405
|
+
Wait-Process -Id $process.ProcessId -Timeout 5 -ErrorAction SilentlyContinue
|
|
406
|
+
}
|
|
407
|
+
`
|
|
408
|
+
], { stdio: [
|
|
409
|
+
"ignore",
|
|
410
|
+
"ignore",
|
|
411
|
+
"pipe"
|
|
412
|
+
] });
|
|
413
|
+
let stderr = "";
|
|
414
|
+
child.stderr?.on("data", (chunk) => {
|
|
415
|
+
stderr += String(chunk);
|
|
416
|
+
});
|
|
417
|
+
child.once("error", reject);
|
|
418
|
+
child.once("exit", (code) => {
|
|
419
|
+
if (code === 0) {
|
|
420
|
+
resolve();
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
reject(/* @__PURE__ */ new Error(`failed to stop running dev broker before cargo build: ${stderr.trim() || code}`));
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
const powerShellString = (value) => `'${value.replace(/'/g, "''")}'`;
|
|
427
|
+
//#endregion
|
|
428
|
+
//#region src/daemon/lifecycle.ts
|
|
429
|
+
const DAEMON_STDIO_ENV = "OPENTRAY_DAEMON_STDIO";
|
|
430
|
+
const createNodeDaemonDriver = (cliEntrypoint) => ({
|
|
431
|
+
async isAlive(pid) {
|
|
432
|
+
return isProcessAlive(pid);
|
|
433
|
+
},
|
|
434
|
+
async spawnBroker(paths) {
|
|
435
|
+
const broker = await resolveBrokerCommand(paths);
|
|
436
|
+
const child = spawn(broker.command, broker.args, {
|
|
437
|
+
cwd: broker.cwd,
|
|
438
|
+
detached: true,
|
|
439
|
+
env: {
|
|
440
|
+
...process.env,
|
|
441
|
+
OPENTRAY_DAEMON_HOME: paths.homeDir,
|
|
442
|
+
OPENTRAY_DAEMON_PACKAGE_VERSION: paths.packageVersion,
|
|
443
|
+
OPENTRAY_DAEMON_CLI_ENTRYPOINT: cliEntrypoint,
|
|
444
|
+
OPENTRAY_DAEMON_APP_ID: paths.appId,
|
|
445
|
+
OPENTRAY_DAEMON_APP_NAME: paths.appName,
|
|
446
|
+
OPENTRAY_DAEMON_CALLER_LABEL: paths.callerLabel
|
|
447
|
+
},
|
|
448
|
+
stdio: resolveBrokerStdio(process.env[DAEMON_STDIO_ENV])
|
|
449
|
+
});
|
|
450
|
+
child.unref();
|
|
451
|
+
return child.pid ?? 0;
|
|
452
|
+
},
|
|
453
|
+
async stop(pid) {
|
|
454
|
+
process.kill(pid, "SIGTERM");
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
const resolveBrokerStdio = (value) => {
|
|
458
|
+
return value === "inherit" ? "inherit" : "ignore";
|
|
459
|
+
};
|
|
460
|
+
const startDaemon = async ({ paths, driver, lockTimeoutMs = 1e3 }) => {
|
|
461
|
+
await mkdir(paths.runtimeDir, { recursive: true });
|
|
462
|
+
const lock = await acquireLock(paths.lockFile, lockTimeoutMs);
|
|
463
|
+
try {
|
|
464
|
+
const existingPid = await readPid(paths.pidFile);
|
|
465
|
+
if (existingPid !== void 0 && await driver.isAlive(existingPid)) return {
|
|
466
|
+
status: "already-running",
|
|
467
|
+
pid: existingPid,
|
|
468
|
+
paths
|
|
469
|
+
};
|
|
470
|
+
await cleanupRuntimeFiles(paths);
|
|
471
|
+
const pid = await driver.spawnBroker(paths);
|
|
472
|
+
if (!Number.isInteger(pid) || pid <= 0) throw new Error("daemon broker process did not expose a valid pid");
|
|
473
|
+
await writeFile(paths.pidFile, `${pid}\n`, "utf8");
|
|
474
|
+
await waitForReadyFile(paths.readyFile, driver, pid);
|
|
475
|
+
return {
|
|
476
|
+
status: "started",
|
|
477
|
+
pid,
|
|
478
|
+
paths
|
|
479
|
+
};
|
|
480
|
+
} finally {
|
|
481
|
+
await lock.release();
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
const readPid = async (pidFile) => {
|
|
485
|
+
try {
|
|
486
|
+
const content = await readFile(pidFile, "utf8");
|
|
487
|
+
const pid = Number.parseInt(content.trim(), 10);
|
|
488
|
+
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
489
|
+
} catch (error) {
|
|
490
|
+
if (isNodeError(error) && error.code === "ENOENT") return;
|
|
491
|
+
throw error;
|
|
492
|
+
}
|
|
268
493
|
};
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
494
|
+
const cleanupRuntimeFiles = async (paths) => {
|
|
495
|
+
await rm(paths.readyFile, { force: true });
|
|
496
|
+
await rm(paths.pidFile, { force: true });
|
|
497
|
+
if (process.platform !== "win32") await rm(paths.endpoint, { force: true });
|
|
273
498
|
};
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
499
|
+
const waitForReadyFile = async (readyFile, driver, pid) => {
|
|
500
|
+
for (let attempt = 0; attempt < 40; attempt += 1) try {
|
|
501
|
+
await readFile(readyFile, "utf8");
|
|
502
|
+
return;
|
|
503
|
+
} catch (error) {
|
|
504
|
+
if (!isNodeError(error) || error.code !== "ENOENT") throw error;
|
|
505
|
+
if (!await driver.isAlive(pid)) throw new Error(`daemon broker exited before readiness: pid=${pid}; readyFile=${readyFile}; set ${DAEMON_STDIO_ENV}=inherit to inspect broker stderr`);
|
|
506
|
+
await sleep(50);
|
|
507
|
+
}
|
|
508
|
+
throw new Error(`timed out waiting for daemon readiness: ${readyFile}`);
|
|
509
|
+
};
|
|
510
|
+
const acquireLock = async (lockFile, timeoutMs) => {
|
|
511
|
+
const deadline = Date.now() + timeoutMs;
|
|
512
|
+
while (Date.now() <= deadline) try {
|
|
513
|
+
const handle = await open(lockFile, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY);
|
|
514
|
+
await handle.writeFile(`${process.pid}\n`, "utf8");
|
|
515
|
+
return { async release() {
|
|
516
|
+
await handle.close();
|
|
517
|
+
await unlink(lockFile).catch((error) => {
|
|
518
|
+
if (!isNodeError(error) || error.code !== "ENOENT") throw error;
|
|
519
|
+
});
|
|
520
|
+
} };
|
|
521
|
+
} catch (error) {
|
|
522
|
+
if (isNodeError(error) && error.code === "EEXIST") {
|
|
523
|
+
await sleep(25);
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
throw error;
|
|
527
|
+
}
|
|
528
|
+
throw new Error(`timed out acquiring daemon lock: ${lockFile}`);
|
|
529
|
+
};
|
|
530
|
+
const isProcessAlive = (pid) => {
|
|
531
|
+
try {
|
|
532
|
+
process.kill(pid, 0);
|
|
533
|
+
return true;
|
|
534
|
+
} catch {
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
539
|
+
const isNodeError = (error) => error instanceof Error && "code" in error;
|
|
540
|
+
//#endregion
|
|
541
|
+
//#region src/daemon/package-version.ts
|
|
542
|
+
const readPackageVersion = async (packageJsonUrl) => {
|
|
543
|
+
const value = JSON.parse(await readFile(packageJsonUrl, "utf8"));
|
|
544
|
+
if (!isPackageJsonWithVersion(value)) throw new Error(`package.json is missing a string version: ${packageJsonUrl.href}`);
|
|
545
|
+
return value.version;
|
|
546
|
+
};
|
|
547
|
+
const isPackageJsonWithVersion = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && "version" in value && typeof value.version === "string";
|
|
548
|
+
//#endregion
|
|
549
|
+
//#region src/daemon/paths.ts
|
|
550
|
+
const resolveDaemonPaths = ({ homeDir, packageVersion, callerLabel, appId, appName, platform = process.platform }) => {
|
|
551
|
+
const identity = createBrokerEndpointIdentity$1(callerLabel === void 0 ? { packageVersion } : {
|
|
552
|
+
packageVersion,
|
|
553
|
+
callerLabel
|
|
554
|
+
});
|
|
555
|
+
const resolvedAppId = normalizeAppIdentityField$1(appId, identity.callerLabel);
|
|
556
|
+
const resolvedAppName = normalizeAppIdentityField$1(appName, identity.callerLabel);
|
|
557
|
+
const normalizedHome = homeDir.replace(/[\\/]+$/u, "");
|
|
558
|
+
const stateRoot = `${normalizedHome}/.opentray/${identity.packageVersion}/${identity.callerLabel}`;
|
|
559
|
+
const runtimeDir = `${stateRoot}/runtime`;
|
|
560
|
+
const endpoint = platform === "win32" ? formatWindowsPipeName$1(identity) : formatUnixSocketPath$1(normalizedHome, identity);
|
|
561
|
+
return {
|
|
562
|
+
homeDir: normalizedHome,
|
|
563
|
+
packageVersion: identity.packageVersion,
|
|
564
|
+
callerLabel: identity.callerLabel,
|
|
565
|
+
appId: resolvedAppId,
|
|
566
|
+
appName: resolvedAppName,
|
|
567
|
+
protocolVersion: identity.protocolVersion,
|
|
568
|
+
stateRoot,
|
|
569
|
+
runtimeDir,
|
|
570
|
+
endpoint,
|
|
571
|
+
pidFile: `${runtimeDir}/broker.pid`,
|
|
572
|
+
lockFile: `${runtimeDir}/broker.lock`,
|
|
573
|
+
readyFile: `${runtimeDir}/ready.json`
|
|
574
|
+
};
|
|
575
|
+
};
|
|
576
|
+
const normalizeAppIdentityField$1 = (value, fallback) => {
|
|
577
|
+
const trimmed = value?.trim();
|
|
578
|
+
return trimmed === void 0 || trimmed.length === 0 ? fallback : trimmed;
|
|
579
|
+
};
|
|
580
|
+
//#endregion
|
|
581
|
+
//#region src/local-broker.ts
|
|
582
|
+
const packageJsonUrl = new URL("../package.json", import.meta.url);
|
|
583
|
+
const connectLocalBroker = async (options = {}) => {
|
|
584
|
+
const packageVersion = options.packageVersion ?? await readPackageVersion(packageJsonUrl);
|
|
585
|
+
const clientVersion = options.clientVersion ?? packageVersion;
|
|
586
|
+
const appId = normalizeAppIdentityField(options.appId);
|
|
587
|
+
const appName = normalizeAppIdentityField(options.appName);
|
|
588
|
+
const callerLabel = options.callerLabel ?? appName ?? appId ?? resolveCallerLabel();
|
|
589
|
+
const paths = resolveDaemonPaths({
|
|
590
|
+
homeDir: options.homeDir ?? process.env.OPENTRAY_HOME ?? homedir(),
|
|
591
|
+
packageVersion,
|
|
592
|
+
callerLabel,
|
|
593
|
+
...appId === void 0 ? {} : { appId },
|
|
594
|
+
...appName === void 0 ? {} : { appName }
|
|
595
|
+
});
|
|
596
|
+
const endpoint = options.endpoint ?? paths.endpoint;
|
|
597
|
+
const autoStart = options.autoStart ?? endpoint === paths.endpoint;
|
|
598
|
+
if (autoStart && endpoint !== paths.endpoint) throw new Error("local broker autoStart requires the derived same-version endpoint");
|
|
599
|
+
if (autoStart) {
|
|
600
|
+
const cliEntrypoint = options.cliEntrypoint ?? resolveCliEntrypoint();
|
|
601
|
+
await startDaemon({
|
|
602
|
+
paths,
|
|
603
|
+
driver: options.daemonDriver ?? createNodeDaemonDriver(cliEntrypoint)
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
const connection = new LocalBrokerConnection(await connectSocket(endpoint), endpoint, callerLabel);
|
|
607
|
+
await connection.init(clientVersion, options.protocolVersion ?? PROTOCOL_VERSION$1);
|
|
608
|
+
return connection;
|
|
609
|
+
};
|
|
610
|
+
const normalizeAppIdentityField = (value) => {
|
|
611
|
+
const trimmed = value?.trim();
|
|
612
|
+
return trimmed === void 0 || trimmed.length === 0 ? void 0 : trimmed;
|
|
613
|
+
};
|
|
614
|
+
var LocalBrokerConnection = class {
|
|
615
|
+
socket;
|
|
616
|
+
endpoint;
|
|
617
|
+
callerLabel;
|
|
618
|
+
sessionId = "";
|
|
619
|
+
buffer = "";
|
|
620
|
+
listeners = /* @__PURE__ */ new Set();
|
|
621
|
+
pending = /* @__PURE__ */ new Map();
|
|
622
|
+
ready;
|
|
623
|
+
constructor(socket, endpoint, callerLabel) {
|
|
624
|
+
this.socket = socket;
|
|
625
|
+
this.endpoint = endpoint;
|
|
626
|
+
this.callerLabel = callerLabel;
|
|
627
|
+
socket.setEncoding("utf8");
|
|
628
|
+
socket.on("data", (chunk) => {
|
|
629
|
+
this.consume(String(chunk));
|
|
630
|
+
});
|
|
631
|
+
socket.on("error", (error) => {
|
|
632
|
+
this.rejectAll(error);
|
|
633
|
+
});
|
|
634
|
+
socket.on("close", () => {
|
|
635
|
+
this.rejectAll(/* @__PURE__ */ new Error("broker connection closed"));
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
async init(clientVersion, protocolVersion) {
|
|
639
|
+
const ready = new Promise((resolve, reject) => {
|
|
640
|
+
this.ready = {
|
|
641
|
+
resolve,
|
|
642
|
+
reject
|
|
643
|
+
};
|
|
644
|
+
});
|
|
645
|
+
this.write({
|
|
646
|
+
type: "init",
|
|
647
|
+
protocolVersion,
|
|
648
|
+
clientVersion
|
|
649
|
+
});
|
|
650
|
+
const frame = await ready;
|
|
651
|
+
this.sessionId = frame.sessionId;
|
|
652
|
+
}
|
|
653
|
+
async request(frame) {
|
|
654
|
+
if (this.pending.has(frame.requestId)) throw new Error(`duplicate requestId: ${frame.requestId}`);
|
|
655
|
+
const response = new Promise((resolve, reject) => {
|
|
656
|
+
this.pending.set(frame.requestId, {
|
|
657
|
+
resolve,
|
|
658
|
+
reject
|
|
659
|
+
});
|
|
660
|
+
});
|
|
661
|
+
this.write(frame);
|
|
662
|
+
return response;
|
|
663
|
+
}
|
|
664
|
+
onEvent(listener) {
|
|
665
|
+
this.listeners.add(listener);
|
|
666
|
+
return () => {
|
|
667
|
+
this.listeners.delete(listener);
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
async close() {
|
|
671
|
+
this.write({ type: "exit" });
|
|
672
|
+
await new Promise((resolve) => {
|
|
673
|
+
this.socket.end(resolve);
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
consume(chunk) {
|
|
677
|
+
this.buffer += chunk;
|
|
678
|
+
while (true) {
|
|
679
|
+
const newline = this.buffer.indexOf("\n");
|
|
680
|
+
if (newline < 0) return;
|
|
681
|
+
const line = this.buffer.slice(0, newline).trim();
|
|
682
|
+
this.buffer = this.buffer.slice(newline + 1);
|
|
683
|
+
if (line.length > 0) this.dispatchLine(line);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
dispatchLine(line) {
|
|
687
|
+
const parsed = parseServerFrame(line);
|
|
688
|
+
if (!parsed.ok || parsed.frame === void 0) {
|
|
689
|
+
this.rejectAll(/* @__PURE__ */ new Error(`${parsed.error ?? "invalid server frame"}: ${line}`));
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
this.dispatchFrame(parsed.frame);
|
|
693
|
+
}
|
|
694
|
+
dispatchFrame(frame) {
|
|
695
|
+
if (frame.type === "ready") {
|
|
696
|
+
this.ready?.resolve(frame);
|
|
697
|
+
this.ready = void 0;
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
if (frame.type === "error") {
|
|
701
|
+
const error = /* @__PURE__ */ new Error(`${frame.code}: ${frame.message}`);
|
|
702
|
+
if (frame.requestId !== void 0) {
|
|
703
|
+
this.pending.get(frame.requestId)?.reject(error);
|
|
704
|
+
this.pending.delete(frame.requestId);
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
if (this.ready) {
|
|
708
|
+
this.ready.reject(error);
|
|
709
|
+
this.ready = void 0;
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
this.rejectAll(error);
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
const requestId = responseRequestId(frame);
|
|
716
|
+
if (requestId !== void 0) {
|
|
717
|
+
this.pending.get(requestId)?.resolve(frame);
|
|
718
|
+
this.pending.delete(requestId);
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
if (frame.type === "event" || frame.type === "ext-event") for (const listener of this.listeners) listener(frame);
|
|
722
|
+
}
|
|
723
|
+
write(frame) {
|
|
724
|
+
this.socket.write(`${JSON.stringify(frame)}\n`);
|
|
725
|
+
}
|
|
726
|
+
rejectAll(error) {
|
|
727
|
+
this.ready?.reject(error);
|
|
728
|
+
this.ready = void 0;
|
|
729
|
+
for (const pending of this.pending.values()) pending.reject(error);
|
|
730
|
+
this.pending.clear();
|
|
731
|
+
}
|
|
732
|
+
};
|
|
733
|
+
const connectSocket = (endpoint) => new Promise((resolve, reject) => {
|
|
734
|
+
const socket = createConnection(endpoint);
|
|
735
|
+
socket.once("connect", () => {
|
|
736
|
+
socket.off("error", reject);
|
|
737
|
+
resolve(socket);
|
|
738
|
+
});
|
|
739
|
+
socket.once("error", reject);
|
|
740
|
+
});
|
|
741
|
+
const responseRequestId = (frame) => {
|
|
742
|
+
switch (frame.type) {
|
|
743
|
+
case "app-created":
|
|
744
|
+
case "default-app":
|
|
745
|
+
case "tray-created":
|
|
746
|
+
case "tray-bounds":
|
|
747
|
+
case "ack":
|
|
748
|
+
case "ext-command-result":
|
|
749
|
+
case "runtime-host-health": return frame.requestId;
|
|
750
|
+
case "ready":
|
|
751
|
+
case "event":
|
|
752
|
+
case "ext-event":
|
|
753
|
+
case "error": return;
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
const resolveCliEntrypoint = () => {
|
|
757
|
+
const suffix = fileURLToPath(import.meta.url).endsWith(".ts") ? "cli.ts" : "cli.mjs";
|
|
758
|
+
return fileURLToPath(new URL(`./${suffix}`, import.meta.url));
|
|
759
|
+
};
|
|
760
|
+
//#endregion
|
|
761
|
+
//#region src/sdk.ts
|
|
762
|
+
const createTray = async (options, runtimeOptions = {}) => {
|
|
763
|
+
const bindingTransportOptions = {
|
|
764
|
+
...runtimeOptions.binding === void 0 ? {} : { binding: runtimeOptions.binding },
|
|
765
|
+
...runtimeOptions.packageVersion === void 0 ? {} : { packageVersion: runtimeOptions.packageVersion },
|
|
766
|
+
...runtimeOptions.clientVersion === void 0 ? {} : { clientVersion: runtimeOptions.clientVersion },
|
|
767
|
+
...runtimeOptions.protocolVersion === void 0 ? {} : { protocolVersion: runtimeOptions.protocolVersion },
|
|
768
|
+
...runtimeOptions.appId === void 0 ? {} : { appId: runtimeOptions.appId },
|
|
769
|
+
...runtimeOptions.appName === void 0 ? {} : { appName: runtimeOptions.appName }
|
|
770
|
+
};
|
|
771
|
+
const runtime = runtimeOptions.runtime ?? "visible-binding";
|
|
772
|
+
return createClient(runtime === "local-broker" ? await connectLocalBroker(runtimeOptions) : await createRuntimeBindingTransport({
|
|
773
|
+
...bindingTransportOptions,
|
|
774
|
+
kind: runtime === "headless-binding" ? "headless" : "visible"
|
|
775
|
+
})).createTray(options);
|
|
278
776
|
};
|
|
279
777
|
//#endregion
|
|
280
|
-
export { PROTOCOL_VERSION, createBrokerEndpointIdentity, createClient, createInitFrame,
|
|
778
|
+
export { PROTOCOL_VERSION, createBrokerEndpointIdentity, createClient, createInitFrame, createTray, createTrayHandle, formatBrokerEndpointName, formatBrokerStateRoot, formatUnixSocketPath, formatWindowsPipeName, isSupportedProtocolVersion };
|
|
281
779
|
|
|
282
780
|
//# sourceMappingURL=index.mjs.map
|