opentray 0.15.0 → 0.17.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/README.md +65 -3
- package/dist/index.d.mts +33 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +435 -26
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -8
package/dist/index.mjs
CHANGED
|
@@ -1,13 +1,240 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { PROTOCOL_VERSION, PROTOCOL_VERSION as PROTOCOL_VERSION$1, brokerArtifactIdentityEquals, createBrokerEndpointIdentity, createBrokerEndpointIdentity as createBrokerEndpointIdentity$1, formatBrokerEndpointName, formatBrokerStateRoot, formatUnixSocketPath, formatUnixSocketPath as formatUnixSocketPath$1, formatWindowsPipeName, formatWindowsPipeName as formatWindowsPipeName$1, isBrokerArtifactIdentity, isSupportedProtocolVersion, parseServerFrame, sanitizeCallerLabel } from "@opentray/spec";
|
|
3
3
|
import { access, mkdir, open, readFile, realpath, rm, unlink, writeFile } from "node:fs/promises";
|
|
4
|
-
import { basename, dirname, join } from "node:path";
|
|
4
|
+
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
5
5
|
import { createConnection } from "node:net";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { clearDarwinAppBundleOwner, ensureDarwinAppBundle, resolveDefaultDarwinAppBundlePath, resolveOpenTrayPackageIdentity, writeDarwinAppBundleOwner } from "@opentray/packaging";
|
|
8
9
|
import { constants } from "node:fs";
|
|
9
10
|
import { spawn } from "node:child_process";
|
|
10
11
|
import { createHash } from "node:crypto";
|
|
12
|
+
//#region src/app-icon.ts
|
|
13
|
+
const DEFAULT_APP_ICON_VARIANT = "default";
|
|
14
|
+
const APP_ICON_VARIANT_PATTERN = /^[a-z][a-z0-9-]{0,63}$/;
|
|
15
|
+
var InvalidAppIconError = class extends Error {
|
|
16
|
+
code = "OPENTRAY_INVALID_APP_ICON";
|
|
17
|
+
reason;
|
|
18
|
+
constructor(reason, platform = process.platform) {
|
|
19
|
+
super(`invalid appIcon for ${platform}: ${reason}`);
|
|
20
|
+
this.name = "InvalidAppIconError";
|
|
21
|
+
this.reason = reason;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
var AppIconVariantNotFoundError = class extends Error {
|
|
25
|
+
code = "OPENTRAY_APP_ICON_VARIANT_NOT_FOUND";
|
|
26
|
+
variant;
|
|
27
|
+
constructor(variant, platform = process.platform) {
|
|
28
|
+
super(`appIcon variant ${variant} is unavailable for ${platform}`);
|
|
29
|
+
this.name = "AppIconVariantNotFoundError";
|
|
30
|
+
this.variant = variant;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
/** Validate an explicit AppIcon, including native file signatures, before broker startup. */
|
|
34
|
+
const validateAppIcon = async (icon, platform = process.platform) => {
|
|
35
|
+
if (!Array.isArray(icon) || icon.length === 0) throw new InvalidAppIconError("the asset array must not be empty", platform);
|
|
36
|
+
const variantPlatforms = /* @__PURE__ */ new Map();
|
|
37
|
+
const seenDarwin = /* @__PURE__ */ new Set();
|
|
38
|
+
const seenWindows = /* @__PURE__ */ new Set();
|
|
39
|
+
const seenLinuxPngSizes = /* @__PURE__ */ new Set();
|
|
40
|
+
const seenLinuxSvg = /* @__PURE__ */ new Set();
|
|
41
|
+
for (const asset of icon) {
|
|
42
|
+
if (!isRecord$1(asset)) throw new InvalidAppIconError("asset must be an object", platform);
|
|
43
|
+
const assetPlatform = asset.platform;
|
|
44
|
+
const format = asset.format;
|
|
45
|
+
const variants = validateVariantNames(asset.variant, platform);
|
|
46
|
+
if (assetPlatform === "darwin" && format === "icns") {
|
|
47
|
+
assertExactKeys(asset, [
|
|
48
|
+
"platform",
|
|
49
|
+
"format",
|
|
50
|
+
"source"
|
|
51
|
+
], platform, ["variant"]);
|
|
52
|
+
rejectDuplicateVariants(seenDarwin, variants, "darwin", platform);
|
|
53
|
+
await validateSource(asset.source, "icns", platform);
|
|
54
|
+
recordVariantPlatforms(variantPlatforms, variants, "darwin");
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (assetPlatform === "windows" && format === "ico") {
|
|
58
|
+
assertExactKeys(asset, [
|
|
59
|
+
"platform",
|
|
60
|
+
"format",
|
|
61
|
+
"source"
|
|
62
|
+
], platform, ["variant"]);
|
|
63
|
+
rejectDuplicateVariants(seenWindows, variants, "windows", platform);
|
|
64
|
+
await validateSource(asset.source, "ico", platform);
|
|
65
|
+
recordVariantPlatforms(variantPlatforms, variants, "windows");
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (assetPlatform === "linux" && format === "png") {
|
|
69
|
+
assertExactKeys(asset, [
|
|
70
|
+
"platform",
|
|
71
|
+
"format",
|
|
72
|
+
"size",
|
|
73
|
+
"source"
|
|
74
|
+
], platform, ["variant"]);
|
|
75
|
+
if (!Number.isInteger(asset.size) || typeof asset.size !== "number" || asset.size <= 0) throw new InvalidAppIconError("Linux PNG assets require a positive integer size", platform);
|
|
76
|
+
for (const variant of variants) {
|
|
77
|
+
const key = `${variant}\0${asset.size}`;
|
|
78
|
+
if (seenLinuxPngSizes.has(key)) throw new InvalidAppIconError(`duplicate Linux PNG size ${asset.size} for variant ${variant}`, platform);
|
|
79
|
+
seenLinuxPngSizes.add(key);
|
|
80
|
+
}
|
|
81
|
+
await validateSource(asset.source, "png", platform);
|
|
82
|
+
recordVariantPlatforms(variantPlatforms, variants, "linux");
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (assetPlatform === "linux" && format === "svg") {
|
|
86
|
+
assertExactKeys(asset, [
|
|
87
|
+
"platform",
|
|
88
|
+
"format",
|
|
89
|
+
"source"
|
|
90
|
+
], platform, ["variant"]);
|
|
91
|
+
rejectDuplicateVariants(seenLinuxSvg, variants, "Linux SVG", platform);
|
|
92
|
+
await validateSource(asset.source, "svg", platform);
|
|
93
|
+
recordVariantPlatforms(variantPlatforms, variants, "linux");
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
throw new InvalidAppIconError(`unsupported platform/format pair ${String(assetPlatform)}/${String(format)}`, platform);
|
|
97
|
+
}
|
|
98
|
+
const currentPlatform = normalizePlatform(platform);
|
|
99
|
+
if (currentPlatform !== void 0) {
|
|
100
|
+
if (!variantPlatforms.get("default")?.has(currentPlatform)) throw new InvalidAppIconError(`no ${currentPlatform} ${DEFAULT_APP_ICON_VARIANT} asset was provided`, platform);
|
|
101
|
+
for (const [variant, platforms] of variantPlatforms) if (!platforms.has(currentPlatform)) throw new InvalidAppIconError(`variant ${variant} has no ${currentPlatform} asset`, platform);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
/** Normalize encoded sources into JSON array bytes for the Rust wire protocol. */
|
|
105
|
+
const normalizeAppIcon = (icon) => icon.map((asset) => {
|
|
106
|
+
const source = normalizeSource(asset.source);
|
|
107
|
+
const variant = normalizeVariant(asset.variant);
|
|
108
|
+
if (asset.platform === "darwin") return {
|
|
109
|
+
...asset,
|
|
110
|
+
variant,
|
|
111
|
+
source
|
|
112
|
+
};
|
|
113
|
+
if (asset.platform === "windows") return {
|
|
114
|
+
...asset,
|
|
115
|
+
variant,
|
|
116
|
+
source
|
|
117
|
+
};
|
|
118
|
+
if (asset.format === "png") return {
|
|
119
|
+
...asset,
|
|
120
|
+
variant,
|
|
121
|
+
source
|
|
122
|
+
};
|
|
123
|
+
return {
|
|
124
|
+
...asset,
|
|
125
|
+
variant,
|
|
126
|
+
source
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
/** Select one semantic variant while retaining every platform's matching native asset. */
|
|
130
|
+
const selectAppIconVariant = (icon, variant, platform = process.platform) => {
|
|
131
|
+
validateVariantName(variant, platform);
|
|
132
|
+
const selected = icon.filter((asset) => normalizedVariantNames(asset.variant).includes(variant));
|
|
133
|
+
const currentPlatform = normalizePlatform(platform);
|
|
134
|
+
if (selected.length === 0 || currentPlatform !== void 0 && !selected.some((asset) => asset.platform === currentPlatform)) throw new AppIconVariantNotFoundError(variant, platform);
|
|
135
|
+
return selected;
|
|
136
|
+
};
|
|
137
|
+
const normalizeSource = (source) => source.type === "encoded" ? {
|
|
138
|
+
type: "encoded",
|
|
139
|
+
data: Array.from(source.data)
|
|
140
|
+
} : {
|
|
141
|
+
type: "file",
|
|
142
|
+
path: resolve(source.path)
|
|
143
|
+
};
|
|
144
|
+
const normalizeVariant = (variant) => variant === void 0 ? DEFAULT_APP_ICON_VARIANT : typeof variant === "string" ? variant : [...variant];
|
|
145
|
+
const normalizedVariantNames = (variant) => variant === void 0 ? [DEFAULT_APP_ICON_VARIANT] : typeof variant === "string" ? [variant] : variant;
|
|
146
|
+
const validateVariantNames = (variant, platform) => {
|
|
147
|
+
const names = variant === void 0 ? [DEFAULT_APP_ICON_VARIANT] : typeof variant === "string" ? [variant] : Array.isArray(variant) ? variant : [];
|
|
148
|
+
if (names.length === 0) throw new InvalidAppIconError("variant must be a name or non-empty name array", platform);
|
|
149
|
+
const seen = /* @__PURE__ */ new Set();
|
|
150
|
+
for (const name of names) {
|
|
151
|
+
validateVariantName(name, platform);
|
|
152
|
+
if (seen.has(name)) throw new InvalidAppIconError(`duplicate variant name ${name} on one asset`, platform);
|
|
153
|
+
seen.add(name);
|
|
154
|
+
}
|
|
155
|
+
return names;
|
|
156
|
+
};
|
|
157
|
+
function validateVariantName(variant, platform) {
|
|
158
|
+
if (typeof variant !== "string" || !APP_ICON_VARIANT_PATTERN.test(variant)) throw new InvalidAppIconError(`variant must match ${APP_ICON_VARIANT_PATTERN}`, platform);
|
|
159
|
+
}
|
|
160
|
+
const rejectDuplicateVariants = (seen, variants, label, hostPlatform) => {
|
|
161
|
+
for (const variant of variants) {
|
|
162
|
+
if (seen.has(variant)) throw new InvalidAppIconError(`duplicate ${label} asset for variant ${variant}`, hostPlatform);
|
|
163
|
+
seen.add(variant);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
const recordVariantPlatforms = (variants, names, platform) => {
|
|
167
|
+
for (const name of names) {
|
|
168
|
+
const platforms = variants.get(name) ?? /* @__PURE__ */ new Set();
|
|
169
|
+
platforms.add(platform);
|
|
170
|
+
variants.set(name, platforms);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
const validateSource = async (source, format, platform) => {
|
|
174
|
+
if (!isRecord$1(source)) throw new InvalidAppIconError("asset source must be a file or encoded payload", platform);
|
|
175
|
+
let bytes;
|
|
176
|
+
if (source.type === "file") {
|
|
177
|
+
assertExactKeys(source, ["type", "path"], platform);
|
|
178
|
+
if (typeof source.path !== "string" || source.path.trim().length === 0) throw new InvalidAppIconError("file source path must not be empty", platform);
|
|
179
|
+
try {
|
|
180
|
+
bytes = await readFile(source.path);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
throw new InvalidAppIconError(`unable to read ${source.path}: ${errorMessage(error)}`, platform);
|
|
183
|
+
}
|
|
184
|
+
} else if (source.type === "encoded") {
|
|
185
|
+
assertExactKeys(source, ["type", "data"], platform);
|
|
186
|
+
bytes = encodedBytes(source.data, platform);
|
|
187
|
+
} else throw new InvalidAppIconError("unsupported app icon source type", platform);
|
|
188
|
+
if (!matchesFormat(bytes, format)) throw new InvalidAppIconError(`source bytes do not contain a valid ${format.toUpperCase()} asset`, platform);
|
|
189
|
+
};
|
|
190
|
+
const encodedBytes = (data, platform) => {
|
|
191
|
+
if (data instanceof Uint8Array && data.byteLength > 0) return data;
|
|
192
|
+
if (Array.isArray(data) && data.length > 0 && data.every((value) => Number.isInteger(value) && value >= 0 && value <= 255)) return Uint8Array.from(data);
|
|
193
|
+
throw new InvalidAppIconError("encoded source must contain one or more bytes in the range 0..255", platform);
|
|
194
|
+
};
|
|
195
|
+
const matchesFormat = (bytes, format) => {
|
|
196
|
+
if (format === "icns") return hasPrefix(bytes, [
|
|
197
|
+
105,
|
|
198
|
+
99,
|
|
199
|
+
110,
|
|
200
|
+
115
|
|
201
|
+
]);
|
|
202
|
+
if (format === "ico") return bytes.length >= 6 && hasPrefix(bytes, [
|
|
203
|
+
0,
|
|
204
|
+
0,
|
|
205
|
+
1,
|
|
206
|
+
0
|
|
207
|
+
]) && bytes[4] !== 0;
|
|
208
|
+
if (format === "png") return hasPrefix(bytes, [
|
|
209
|
+
137,
|
|
210
|
+
80,
|
|
211
|
+
78,
|
|
212
|
+
71,
|
|
213
|
+
13,
|
|
214
|
+
10,
|
|
215
|
+
26,
|
|
216
|
+
10
|
|
217
|
+
]);
|
|
218
|
+
const source = new TextDecoder().decode(bytes.subarray(0, 4096)).replace(/^\uFEFF/, "");
|
|
219
|
+
return /<(?:\?xml[^>]*>\s*)?(?:!--[\s\S]*?-->\s*)*svg(?:\s|>)/i.test(source.trimStart());
|
|
220
|
+
};
|
|
221
|
+
const hasPrefix = (bytes, prefix) => bytes.length >= prefix.length && prefix.every((byte, index) => bytes[index] === byte);
|
|
222
|
+
const assertExactKeys = (record, expected, platform, optional = []) => {
|
|
223
|
+
const actual = Object.keys(record);
|
|
224
|
+
const allowed = new Set([...expected, ...optional]);
|
|
225
|
+
const unexpected = actual.find((key) => !allowed.has(key));
|
|
226
|
+
if (unexpected !== void 0) throw new InvalidAppIconError(`unexpected asset field ${unexpected}`, platform);
|
|
227
|
+
const missing = expected.find((key) => !(key in record));
|
|
228
|
+
if (missing !== void 0) throw new InvalidAppIconError(`missing asset field ${missing}`, platform);
|
|
229
|
+
};
|
|
230
|
+
const isRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
231
|
+
const errorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
232
|
+
const normalizePlatform = (platform) => {
|
|
233
|
+
if (platform === "darwin") return "darwin";
|
|
234
|
+
if (platform === "win32") return "windows";
|
|
235
|
+
if (platform === "linux") return "linux";
|
|
236
|
+
};
|
|
237
|
+
//#endregion
|
|
11
238
|
//#region src/native-extension-artifact.ts
|
|
12
239
|
var NativeExtensionArtifactResolutionError = class extends Error {
|
|
13
240
|
target;
|
|
@@ -129,8 +356,26 @@ const createInitFrame = (clientVersion) => ({
|
|
|
129
356
|
});
|
|
130
357
|
function createClient(transport, options = {}) {
|
|
131
358
|
const nextRequestId = createRequestIdFactory(options.requestIdPrefix ?? "opentray");
|
|
359
|
+
let appPromise;
|
|
360
|
+
const ensureApp = async (trayOptions) => {
|
|
361
|
+
appPromise ??= (async () => {
|
|
362
|
+
const app = await resolveDefaultAppRef(transport, nextRequestId);
|
|
363
|
+
const handle = createAppHandle(transport, app.appId, nextRequestId);
|
|
364
|
+
const configured = options.appOptions;
|
|
365
|
+
if (configured?.name !== void 0) await handle.setName(configured.name);
|
|
366
|
+
if (configured?.appIcon !== void 0) await handle.setAppIcon(configured.appIcon);
|
|
367
|
+
return {
|
|
368
|
+
app,
|
|
369
|
+
handle
|
|
370
|
+
};
|
|
371
|
+
})().catch((error) => {
|
|
372
|
+
appPromise = void 0;
|
|
373
|
+
throw error;
|
|
374
|
+
});
|
|
375
|
+
return appPromise;
|
|
376
|
+
};
|
|
132
377
|
return { async createTray(options) {
|
|
133
|
-
const app = await
|
|
378
|
+
const { app } = await ensureApp(options);
|
|
134
379
|
const requestId = nextRequestId();
|
|
135
380
|
const frame = expectResponse(await transport.request({
|
|
136
381
|
type: "create-tray",
|
|
@@ -150,6 +395,7 @@ function createTrayHandle(transport, appId, trayId, nextRequestId = createReques
|
|
|
150
395
|
};
|
|
151
396
|
const handle = {
|
|
152
397
|
trayId,
|
|
398
|
+
app: createAppHandle(transport, appId, nextRequestId),
|
|
153
399
|
async getBounds() {
|
|
154
400
|
const requestId = nextRequestId();
|
|
155
401
|
return expectResponse(await transport.request({
|
|
@@ -288,6 +534,62 @@ const resolveDefaultAppRef = async (transport, nextRequestId) => {
|
|
|
288
534
|
requestId
|
|
289
535
|
}), requestId, "default-app").app;
|
|
290
536
|
};
|
|
537
|
+
const createAppHandle = (transport, appId, nextRequestId) => {
|
|
538
|
+
const requestIdentity = async () => {
|
|
539
|
+
const requestId = nextRequestId();
|
|
540
|
+
return expectResponse(await transport.request({
|
|
541
|
+
type: "get-app-identity",
|
|
542
|
+
requestId,
|
|
543
|
+
appId
|
|
544
|
+
}), requestId, "app-identity").identity;
|
|
545
|
+
};
|
|
546
|
+
return {
|
|
547
|
+
appId,
|
|
548
|
+
async getName() {
|
|
549
|
+
return (await requestIdentity()).appName;
|
|
550
|
+
},
|
|
551
|
+
async setName(name) {
|
|
552
|
+
const requestId = nextRequestId();
|
|
553
|
+
expectResponse(await transport.request({
|
|
554
|
+
type: "set-app-name",
|
|
555
|
+
requestId,
|
|
556
|
+
appId,
|
|
557
|
+
name
|
|
558
|
+
}), requestId, "ack");
|
|
559
|
+
return (await requestIdentity()).appName;
|
|
560
|
+
},
|
|
561
|
+
async getAppIcon() {
|
|
562
|
+
return (await requestIdentity()).appIcon ?? null;
|
|
563
|
+
},
|
|
564
|
+
async getAppIconVariant() {
|
|
565
|
+
return (await requestIdentity()).appIconVariant ?? null;
|
|
566
|
+
},
|
|
567
|
+
async setAppIcon(iconOrVariant) {
|
|
568
|
+
if (typeof iconOrVariant === "string") {
|
|
569
|
+
const identity = await requestIdentity();
|
|
570
|
+
if (identity.appIcon === void 0) selectAppIconVariant([], iconOrVariant);
|
|
571
|
+
else selectAppIconVariant(identity.appIcon, iconOrVariant);
|
|
572
|
+
const requestId = nextRequestId();
|
|
573
|
+
expectResponse(await transport.request({
|
|
574
|
+
type: "set-app-icon-variant",
|
|
575
|
+
requestId,
|
|
576
|
+
appId,
|
|
577
|
+
variant: iconOrVariant
|
|
578
|
+
}), requestId, "ack");
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
if (iconOrVariant !== null) await validateAppIcon(iconOrVariant);
|
|
582
|
+
const appIcon = iconOrVariant === null ? null : normalizeAppIcon(iconOrVariant);
|
|
583
|
+
const requestId = nextRequestId();
|
|
584
|
+
expectResponse(await transport.request({
|
|
585
|
+
type: "set-app-icon",
|
|
586
|
+
requestId,
|
|
587
|
+
appId,
|
|
588
|
+
appIcon
|
|
589
|
+
}), requestId, "ack");
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
};
|
|
291
593
|
const createTrayExtensionContext = (tray, appId, extension, options, nextMountId) => {
|
|
292
594
|
const mount = extension.resolveMount?.(options) ?? {};
|
|
293
595
|
const name = mount.name ?? extension.name;
|
|
@@ -344,6 +646,7 @@ const requestIdOf = (frame) => {
|
|
|
344
646
|
switch (frame.type) {
|
|
345
647
|
case "app-created":
|
|
346
648
|
case "default-app":
|
|
649
|
+
case "app-identity":
|
|
347
650
|
case "tray-created":
|
|
348
651
|
case "tray-bounds":
|
|
349
652
|
case "ack":
|
|
@@ -384,7 +687,8 @@ const resolveBrokerNativeTarget = (platform = process.platform, arch = process.a
|
|
|
384
687
|
const packageOs = platformToPackageOs(platform);
|
|
385
688
|
return {
|
|
386
689
|
packageName: `@opentray/${packageOs}-${normalizeArch(arch)}`,
|
|
387
|
-
binaryRelativePath: `bin/${packageOs === "windows" ? "opentray.exe" : "opentray"}
|
|
690
|
+
binaryRelativePath: `bin/${packageOs === "windows" ? "opentray.exe" : "opentray"}`,
|
|
691
|
+
...packageOs === "darwin" ? { carrierTemplateRelativePath: "app/Info.plist" } : {}
|
|
388
692
|
};
|
|
389
693
|
};
|
|
390
694
|
const resolveBrokerArtifactTarget = (platform = process.platform, arch = process.arch) => {
|
|
@@ -460,10 +764,19 @@ const resolveBrokerCommand = async (paths, options = {}) => {
|
|
|
460
764
|
const arch = options.arch ?? process.arch;
|
|
461
765
|
const target = resolveBrokerNativeTarget(platform, arch);
|
|
462
766
|
const installed = await (options.resolveInstalledBrokerBinary ?? resolveInstalledBrokerBinary)(target, { platform });
|
|
463
|
-
if (installed.binary !== void 0)
|
|
767
|
+
if (installed.binary !== void 0) {
|
|
768
|
+
if (platform !== "darwin") return commandForBinary(installed.binary, paths);
|
|
769
|
+
if (options.appBundle === void 0) return commandForBinary(installed.binary, paths);
|
|
770
|
+
if (installed.carrierTemplate === void 0) throw missingDarwinCarrierError(target, installed, arch);
|
|
771
|
+
return commandForBinary(await ensureDarwinBundle(options, paths, installed.binary, installed.carrierTemplate), paths);
|
|
772
|
+
}
|
|
464
773
|
const sourceDir = options.sourceDir ?? dirname(fileURLToPath(sourceUrl));
|
|
465
774
|
const workspaceRoot = await (options.findWorkspaceRoot ?? findWorkspaceRoot)(sourceDir);
|
|
466
|
-
if (workspaceRoot !== void 0)
|
|
775
|
+
if (workspaceRoot !== void 0) {
|
|
776
|
+
const binary = await (options.ensureDevBrokerBinary ?? ensureDevBrokerBinary)(workspaceRoot, paths, platform);
|
|
777
|
+
if (platform !== "darwin") return commandForBinary(binary, paths);
|
|
778
|
+
return commandForBinary(options.appBundle === void 0 ? binary : await ensureDarwinBundle(options, paths, binary, await (options.ensureDevDarwinCarrierTemplate ?? ensureDevDarwinCarrierTemplate)(workspaceRoot, paths)), paths);
|
|
779
|
+
}
|
|
467
780
|
throw new MissingPlatformBrokerBinaryError(installed.binaryPath === void 0 ? `unable to resolve OpenTray broker binary for ${platform}/${arch}; install "${target.packageName}" for this platform or set OPENTRAY_BROKER_BIN` : `unable to resolve OpenTray broker binary for ${platform}/${arch}; package "${target.packageName}" was found but binary "${installed.binaryPath}" is not accessible; restage the runtime package or set OPENTRAY_BROKER_BIN`, platform, arch, {
|
|
468
781
|
packageName: target.packageName,
|
|
469
782
|
...installed.binaryPath === void 0 ? {} : { binaryPath: installed.binaryPath }
|
|
@@ -479,14 +792,28 @@ const resolveInstalledBrokerBinary = async (target, options = {}) => {
|
|
|
479
792
|
throw error;
|
|
480
793
|
}
|
|
481
794
|
const binaryPath = join(dirname(packageJsonPath), target.binaryRelativePath);
|
|
795
|
+
const carrierTemplatePath = target.carrierTemplateRelativePath === void 0 ? void 0 : join(dirname(packageJsonPath), target.carrierTemplateRelativePath);
|
|
796
|
+
let carrierTemplate;
|
|
797
|
+
if (carrierTemplatePath !== void 0) try {
|
|
798
|
+
await (options.assertCarrierAccessible ?? assertInstalledCarrierAccessible)(carrierTemplatePath);
|
|
799
|
+
carrierTemplate = carrierTemplatePath;
|
|
800
|
+
} catch (error) {
|
|
801
|
+
if (!isNodeError$1(error) || error.code !== "ENOENT" && error.code !== "EACCES" && error.code !== "EPERM") throw error;
|
|
802
|
+
}
|
|
482
803
|
try {
|
|
483
804
|
await (options.assertBinaryAccessible ?? assertInstalledBrokerBinaryAccessible)(binaryPath, platform);
|
|
484
805
|
return {
|
|
485
806
|
binary: binaryPath,
|
|
486
|
-
binaryPath
|
|
807
|
+
binaryPath,
|
|
808
|
+
...carrierTemplatePath === void 0 ? {} : { carrierTemplatePath },
|
|
809
|
+
...carrierTemplate === void 0 ? {} : { carrierTemplate }
|
|
487
810
|
};
|
|
488
811
|
} catch (error) {
|
|
489
|
-
if (isNodeError$1(error) && (error.code === "ENOENT" || error.code === "EACCES" || error.code === "EPERM")) return {
|
|
812
|
+
if (isNodeError$1(error) && (error.code === "ENOENT" || error.code === "EACCES" || error.code === "EPERM")) return {
|
|
813
|
+
binaryPath,
|
|
814
|
+
...carrierTemplatePath === void 0 ? {} : { carrierTemplatePath },
|
|
815
|
+
...carrierTemplate === void 0 ? {} : { carrierTemplate }
|
|
816
|
+
};
|
|
490
817
|
throw error;
|
|
491
818
|
}
|
|
492
819
|
};
|
|
@@ -533,15 +860,39 @@ const ensureDevBrokerBinary = async (workspaceRoot, paths, platform) => {
|
|
|
533
860
|
await runCargoBuild(workspaceRoot, platform === "win32" ? targetDir : void 0);
|
|
534
861
|
return binary;
|
|
535
862
|
};
|
|
536
|
-
const
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
863
|
+
const ensureDevDarwinCarrierTemplate = async (workspaceRoot, _paths) => {
|
|
864
|
+
return join(workspaceRoot, "packages/darwin-app-carrier/Info.plist");
|
|
865
|
+
};
|
|
866
|
+
const ensureDarwinBundle = async (options, paths, brokerPath, templatePath) => {
|
|
867
|
+
const appBundle = options.appBundle;
|
|
868
|
+
if (appBundle === void 0) return brokerPath;
|
|
869
|
+
const arch = options.arch ?? process.arch;
|
|
870
|
+
if (arch !== "arm64" && arch !== "x64") throw new Error(`unsupported Darwin app bundle architecture: ${arch}`);
|
|
871
|
+
return ensureDarwinAppBundle({
|
|
872
|
+
bundlePath: appBundle.path,
|
|
873
|
+
packageName: options.packageIdentity?.name ?? paths.callerLabel,
|
|
874
|
+
appId: paths.appId,
|
|
875
|
+
appName: paths.appName,
|
|
876
|
+
target: {
|
|
877
|
+
os: "darwin",
|
|
878
|
+
arch
|
|
879
|
+
},
|
|
880
|
+
brokerPath,
|
|
881
|
+
templatePath,
|
|
882
|
+
...options.appIcon === void 0 ? {} : { appIcon: options.appIcon },
|
|
883
|
+
...appBundle.reinitialize === void 0 ? {} : { reinitialize: appBundle.reinitialize }
|
|
884
|
+
});
|
|
885
|
+
};
|
|
886
|
+
const runCargoBuild = (workspaceRoot, targetDir) => runProcess("cargo", [
|
|
887
|
+
"build",
|
|
888
|
+
"-p",
|
|
889
|
+
"opentray-bin",
|
|
890
|
+
...targetDir === void 0 ? [] : ["--target-dir", targetDir]
|
|
891
|
+
], workspaceRoot, process.env.OPENTRAY_BROKER_BUILD_LOGS === "1" ? "inherit" : "ignore");
|
|
892
|
+
const runProcess = (command, args, cwd, stdio = "ignore") => new Promise((resolve, reject) => {
|
|
893
|
+
const child = spawn(command, [...args], {
|
|
894
|
+
cwd,
|
|
895
|
+
stdio
|
|
545
896
|
});
|
|
546
897
|
child.once("error", reject);
|
|
547
898
|
child.once("exit", (code) => {
|
|
@@ -549,7 +900,7 @@ const runCargoBuild = (workspaceRoot, targetDir) => new Promise((resolve, reject
|
|
|
549
900
|
resolve();
|
|
550
901
|
return;
|
|
551
902
|
}
|
|
552
|
-
reject(/* @__PURE__ */ new Error(
|
|
903
|
+
reject(/* @__PURE__ */ new Error(`${command} ${args.join(" ")} failed with code ${code ?? "unknown"}`));
|
|
553
904
|
});
|
|
554
905
|
});
|
|
555
906
|
const exists = async (path) => {
|
|
@@ -564,6 +915,13 @@ const binaryAccessMode = (platform) => platform === "win32" ? constants.F_OK : c
|
|
|
564
915
|
const assertInstalledBrokerBinaryAccessible = async (binaryPath, platform) => {
|
|
565
916
|
await access(binaryPath, binaryAccessMode(platform));
|
|
566
917
|
};
|
|
918
|
+
const assertInstalledCarrierAccessible = async (carrierPath) => {
|
|
919
|
+
await access(carrierPath, constants.F_OK);
|
|
920
|
+
};
|
|
921
|
+
const missingDarwinCarrierError = (target, resolution, arch) => new MissingPlatformBrokerBinaryError(`unable to resolve OpenTray Darwin app bundle template for ${target.packageName}; expected ${resolution.carrierTemplatePath ?? target.carrierTemplateRelativePath ?? "app/Info.plist"}`, "darwin", arch, {
|
|
922
|
+
packageName: target.packageName,
|
|
923
|
+
...resolution.carrierTemplatePath === void 0 ? {} : { binaryPath: resolution.carrierTemplatePath }
|
|
924
|
+
});
|
|
567
925
|
function isNodeError$1(error) {
|
|
568
926
|
return error instanceof Error && "code" in error;
|
|
569
927
|
}
|
|
@@ -571,9 +929,13 @@ function isNodeError$1(error) {
|
|
|
571
929
|
//#region src/daemon/lifecycle.ts
|
|
572
930
|
const DAEMON_STDIO_ENV = "OPENTRAY_DAEMON_STDIO";
|
|
573
931
|
const DEFAULT_DAEMON_LOCK_TIMEOUT_MS = 5e3;
|
|
574
|
-
const createNodeDaemonDriver = (cliEntrypoint) => ({
|
|
932
|
+
const createNodeDaemonDriver = (cliEntrypoint, options = {}) => ({
|
|
575
933
|
async resolveBroker(paths) {
|
|
576
|
-
return resolveBrokerArtifact(paths
|
|
934
|
+
return resolveBrokerArtifact(paths, {
|
|
935
|
+
...options.appBundle === void 0 ? {} : { appBundle: options.appBundle },
|
|
936
|
+
...options.appIcon === void 0 ? {} : { appIcon: options.appIcon },
|
|
937
|
+
...options.packageIdentity === void 0 ? {} : { packageIdentity: options.packageIdentity }
|
|
938
|
+
});
|
|
577
939
|
},
|
|
578
940
|
async isAlive(pid) {
|
|
579
941
|
return isProcessAlive(pid);
|
|
@@ -594,22 +956,36 @@ const createNodeDaemonDriver = (cliEntrypoint) => ({
|
|
|
594
956
|
stdio: resolveBrokerStdio(process.env[DAEMON_STDIO_ENV])
|
|
595
957
|
});
|
|
596
958
|
child.unref();
|
|
597
|
-
|
|
959
|
+
const pid = child.pid ?? 0;
|
|
960
|
+
if (process.platform === "darwin" && options.appBundle !== void 0 && pid > 0) await writeDarwinAppBundleOwner(options.appBundle.path, pid, broker.artifactIdentity.executableHash);
|
|
961
|
+
return pid;
|
|
598
962
|
},
|
|
599
963
|
async stop(pid) {
|
|
600
964
|
process.kill(pid, "SIGTERM");
|
|
965
|
+
if (process.platform === "darwin" && options.appBundle !== void 0) await clearDarwinAppBundleOwner(options.appBundle.path, pid);
|
|
601
966
|
}
|
|
602
967
|
});
|
|
603
968
|
const resolveBrokerStdio = (value) => {
|
|
604
969
|
return value === "inherit" ? "inherit" : "ignore";
|
|
605
970
|
};
|
|
606
971
|
const startDaemon = async ({ paths, driver, lockTimeoutMs = DEFAULT_DAEMON_LOCK_TIMEOUT_MS }) => {
|
|
607
|
-
const broker = await driver.resolveBroker(paths);
|
|
608
972
|
await mkdir(paths.runtimeDir, { recursive: true });
|
|
609
973
|
const lock = await acquireLock(paths.lockFile, lockTimeoutMs);
|
|
610
974
|
try {
|
|
611
975
|
const existingPid = await readPid(paths.pidFile);
|
|
612
|
-
|
|
976
|
+
let existingAlive = existingPid !== void 0 && await driver.isAlive(existingPid);
|
|
977
|
+
let broker;
|
|
978
|
+
try {
|
|
979
|
+
broker = await driver.resolveBroker(paths);
|
|
980
|
+
} catch (error) {
|
|
981
|
+
if (existingAlive && isBundleInUseError(error) && existingPid !== void 0) {
|
|
982
|
+
await driver.stop(existingPid);
|
|
983
|
+
if (!await waitUntilStopped(driver, existingPid)) throw new Error(`incompatible daemon broker did not stop within the bounded shutdown window: pid=${existingPid}`);
|
|
984
|
+
existingAlive = false;
|
|
985
|
+
broker = await driver.resolveBroker(paths);
|
|
986
|
+
} else throw error;
|
|
987
|
+
}
|
|
988
|
+
if (existingPid !== void 0 && existingAlive) {
|
|
613
989
|
if (readyMatchesBroker(await readReadyMetadata(paths.readyFile), paths, existingPid, broker)) return {
|
|
614
990
|
status: "already-running",
|
|
615
991
|
pid: existingPid,
|
|
@@ -698,6 +1074,7 @@ const readReadyMetadata = async (readyFile) => {
|
|
|
698
1074
|
};
|
|
699
1075
|
};
|
|
700
1076
|
const readyMatchesBroker = (ready, paths, pid, broker) => ready !== void 0 && ready.pid === pid && ready.endpoint === paths.endpoint && ready.packageVersion === paths.packageVersion && ready.protocolVersion === paths.protocolVersion && ready.appId === paths.appId && ready.appName === paths.appName && ready.callerLabel === paths.callerLabel && executablePathsEqual(ready.executablePath, broker.executablePath) && brokerArtifactIdentityEquals(ready.brokerArtifactIdentity, broker.artifactIdentity);
|
|
1077
|
+
const isBundleInUseError = (error) => error instanceof Error && "code" in error && error.code === "bundle_in_use";
|
|
701
1078
|
const executablePathsEqual = (left, right, platform = process.platform) => {
|
|
702
1079
|
if (platform !== "win32") return left === right;
|
|
703
1080
|
const normalize = (value) => value.replaceAll("\\", "/").replace(/^\/\/?\?\//u, "").toLowerCase();
|
|
@@ -794,7 +1171,16 @@ const connectLocalBroker = async (options = {}) => {
|
|
|
794
1171
|
const autoStart = options.autoStart ?? endpoint === paths.endpoint;
|
|
795
1172
|
if (autoStart && endpoint !== paths.endpoint) throw new Error("local broker autoStart requires the derived same-version endpoint");
|
|
796
1173
|
const cliEntrypoint = options.cliEntrypoint ?? resolveCliEntrypoint();
|
|
797
|
-
const
|
|
1174
|
+
const packageIdentity = process.platform === "darwin" && autoStart ? await resolveOpenTrayPackageIdentity({
|
|
1175
|
+
...options.packageName === void 0 ? {} : { packageName: options.packageName },
|
|
1176
|
+
...options.packageRoot === void 0 ? {} : { packageRoot: options.packageRoot }
|
|
1177
|
+
}) : void 0;
|
|
1178
|
+
const appBundle = process.platform === "darwin" && autoStart ? resolveDarwinAppBundleOptions(options.appBundle, paths, packageIdentity) : void 0;
|
|
1179
|
+
const driver = options.daemonDriver ?? createNodeDaemonDriver(cliEntrypoint, {
|
|
1180
|
+
...appBundle === void 0 ? {} : { appBundle },
|
|
1181
|
+
...options.appIcon === void 0 ? {} : { appIcon: options.appIcon },
|
|
1182
|
+
...packageIdentity === void 0 ? {} : { packageIdentity }
|
|
1183
|
+
});
|
|
798
1184
|
const expectedBrokerArtifactIdentity = autoStart ? (await startDaemon({
|
|
799
1185
|
paths,
|
|
800
1186
|
driver
|
|
@@ -808,6 +1194,20 @@ const connectLocalBroker = async (options = {}) => {
|
|
|
808
1194
|
}
|
|
809
1195
|
return connection;
|
|
810
1196
|
};
|
|
1197
|
+
const resolveDarwinAppBundleOptions = (configured, paths, packageIdentity) => {
|
|
1198
|
+
if (packageIdentity === void 0) throw new Error("Darwin app bundle resolution requires the caller package identity");
|
|
1199
|
+
const configuredPath = configured?.path;
|
|
1200
|
+
return {
|
|
1201
|
+
path: configuredPath === void 0 ? resolveDefaultDarwinAppBundlePath({
|
|
1202
|
+
homeDir: paths.homeDir,
|
|
1203
|
+
packageName: packageIdentity.name,
|
|
1204
|
+
appName: paths.appName
|
|
1205
|
+
}) : typeof configuredPath === "string" ? isAbsolute(configuredPath) ? configuredPath : resolve(packageIdentity.root, configuredPath) : configuredPath.protocol === "file:" ? fileURLToPath(configuredPath) : (() => {
|
|
1206
|
+
throw new Error(`Darwin appBundle.path must be a file URL: ${configuredPath.href}`);
|
|
1207
|
+
})(),
|
|
1208
|
+
...configured?.reinitialize === void 0 ? {} : { reinitialize: configured.reinitialize }
|
|
1209
|
+
};
|
|
1210
|
+
};
|
|
811
1211
|
const normalizeAppIdentityField = (value) => {
|
|
812
1212
|
const trimmed = value?.trim();
|
|
813
1213
|
return trimmed === void 0 || trimmed.length === 0 ? void 0 : trimmed;
|
|
@@ -944,6 +1344,7 @@ const responseRequestId = (frame) => {
|
|
|
944
1344
|
switch (frame.type) {
|
|
945
1345
|
case "app-created":
|
|
946
1346
|
case "default-app":
|
|
1347
|
+
case "app-identity":
|
|
947
1348
|
case "tray-created":
|
|
948
1349
|
case "tray-bounds":
|
|
949
1350
|
case "ack":
|
|
@@ -1080,10 +1481,18 @@ const registerClickId = (state, id) => {
|
|
|
1080
1481
|
//#endregion
|
|
1081
1482
|
//#region src/sdk.ts
|
|
1082
1483
|
const createTray = async (options, runtimeOptions = {}) => {
|
|
1484
|
+
if (runtimeOptions.appIcon !== void 0) await validateAppIcon(runtimeOptions.appIcon);
|
|
1485
|
+
const appIcon = runtimeOptions.appIcon === void 0 ? void 0 : normalizeAppIcon(runtimeOptions.appIcon);
|
|
1083
1486
|
const normalized = normalizeCreateTrayOptions(options);
|
|
1084
|
-
const connection = await connectLocalBroker(
|
|
1487
|
+
const connection = await connectLocalBroker({
|
|
1488
|
+
...runtimeOptions,
|
|
1489
|
+
...appIcon === void 0 ? {} : { appIcon }
|
|
1490
|
+
});
|
|
1085
1491
|
try {
|
|
1086
|
-
const tray = await createClient(connection
|
|
1492
|
+
const tray = await createClient(connection, { appOptions: {
|
|
1493
|
+
...runtimeOptions.appName === void 0 ? {} : { name: runtimeOptions.appName },
|
|
1494
|
+
...appIcon === void 0 ? {} : { appIcon }
|
|
1495
|
+
} }).createTray(normalized.options);
|
|
1087
1496
|
return wrapCreateTrayHandle(tray, {
|
|
1088
1497
|
closeConnection: () => connection.close(),
|
|
1089
1498
|
destroyPromise: void 0,
|
|
@@ -1147,6 +1556,6 @@ const bindMenuClickHandlers = (tray, handlers) => {
|
|
|
1147
1556
|
};
|
|
1148
1557
|
const noop = () => {};
|
|
1149
1558
|
//#endregion
|
|
1150
|
-
export { NativeExtensionArtifactResolutionError, PROTOCOL_VERSION, createBrokerEndpointIdentity, createClient, createInitFrame, createTray, createTrayHandle, formatBrokerEndpointName, formatBrokerStateRoot, formatUnixSocketPath, formatWindowsPipeName, isSupportedProtocolVersion, resolveNativeExtensionArtifact };
|
|
1559
|
+
export { AppIconVariantNotFoundError, DEFAULT_APP_ICON_VARIANT, InvalidAppIconError, NativeExtensionArtifactResolutionError, PROTOCOL_VERSION, createBrokerEndpointIdentity, createClient, createInitFrame, createTray, createTrayHandle, formatBrokerEndpointName, formatBrokerStateRoot, formatUnixSocketPath, formatWindowsPipeName, isSupportedProtocolVersion, resolveNativeExtensionArtifact, validateAppIcon };
|
|
1151
1560
|
|
|
1152
1561
|
//# sourceMappingURL=index.mjs.map
|