opentray 0.8.0 → 0.9.0

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/dist/index.mjs CHANGED
@@ -1,5 +1,12 @@
1
- import { t as connectLocalBroker } from "./local-broker-BMxjb-eO.mjs";
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
- async createSpace(spaceOptions) {
13
- const requestId = nextRequestId();
14
- const space = expectResponse(await transport.request({
15
- type: "create-space",
16
- requestId,
17
- ...spaceOptions
18
- }), requestId, "space-created").space;
19
- return createSpaceHandle(transport, space, nextRequestId);
20
- },
21
- async resolveDefaultSpace() {
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 createSpaceHandle(transport, space, nextRequestId = createRequestIdFactory("opentray")) {
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
- spaceId: space.spaceId,
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
- spaceId: space.spaceId,
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
- spaceId: space.spaceId,
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
- spaceId: space.spaceId,
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
- spaceId: space.spaceId,
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
- spaceId: space.spaceId,
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
- spaceId: space.spaceId,
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, handle.space.spaceId, handle.trayId)) return;
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.spaceId !== handle.space.spaceId || frame.trayId !== handle.trayId) return;
137
+ if (frame.appId !== appId || frame.trayId !== handle.trayId) return;
169
138
  handler({
170
139
  scope: {
171
- spaceId: frame.spaceId,
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, spaceId, trayId) => event.type !== "ready" && event.spaceId === spaceId && event.trayId === trayId;
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 "space-created":
252
- case "default-space":
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 "daemon-health": return frame.requestId;
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/sdk.ts
266
- const createSpace = async (options, brokerOptions = {}) => {
267
- return createClient(await connectLocalBroker(brokerOptions)).createSpace(options);
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
- /** @deprecated Use `createSpace`. */
270
- const createSurface = async (options, brokerOptions = {}) => createSpace(options, brokerOptions);
271
- const resolveDefaultSpace = async (brokerOptions = {}) => {
272
- return createClient(await connectLocalBroker(brokerOptions)).resolveDefaultSpace();
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 createTray = async (options, brokerOptions = {}) => {
275
- const { space, ...connectOptions } = brokerOptions;
276
- const connection = await connectLocalBroker(connectOptions);
277
- return (space ? createSpaceHandle(connection, space) : await createClient(connection).resolveDefaultSpace()).createTray(options);
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, createSpace, createSpaceHandle, createSurface, createSurfaceHandle, createTray, createTrayHandle, formatBrokerEndpointName, formatBrokerStateRoot, formatUnixSocketPath, formatWindowsPipeName, isSupportedProtocolVersion, resolveDefaultSpace };
778
+ export { PROTOCOL_VERSION, createBrokerEndpointIdentity, createClient, createInitFrame, createTray, createTrayHandle, formatBrokerEndpointName, formatBrokerStateRoot, formatUnixSocketPath, formatWindowsPipeName, isSupportedProtocolVersion };
281
779
 
282
780
  //# sourceMappingURL=index.mjs.map