opentray 0.16.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 +24 -6
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +377 -32
- 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;
|
|
@@ -136,8 +363,7 @@ function createClient(transport, options = {}) {
|
|
|
136
363
|
const handle = createAppHandle(transport, app.appId, nextRequestId);
|
|
137
364
|
const configured = options.appOptions;
|
|
138
365
|
if (configured?.name !== void 0) await handle.setName(configured.name);
|
|
139
|
-
if (configured?.
|
|
140
|
-
else if (trayOptions.icon !== void 0 && await handle.getIcon() === null) await handle.setIcon(trayOptions.icon);
|
|
366
|
+
if (configured?.appIcon !== void 0) await handle.setAppIcon(configured.appIcon);
|
|
141
367
|
return {
|
|
142
368
|
app,
|
|
143
369
|
handle
|
|
@@ -332,18 +558,35 @@ const createAppHandle = (transport, appId, nextRequestId) => {
|
|
|
332
558
|
}), requestId, "ack");
|
|
333
559
|
return (await requestIdentity()).appName;
|
|
334
560
|
},
|
|
335
|
-
async
|
|
336
|
-
return (await requestIdentity()).
|
|
561
|
+
async getAppIcon() {
|
|
562
|
+
return (await requestIdentity()).appIcon ?? null;
|
|
337
563
|
},
|
|
338
|
-
async
|
|
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);
|
|
339
583
|
const requestId = nextRequestId();
|
|
340
584
|
expectResponse(await transport.request({
|
|
341
585
|
type: "set-app-icon",
|
|
342
586
|
requestId,
|
|
343
587
|
appId,
|
|
344
|
-
|
|
588
|
+
appIcon
|
|
345
589
|
}), requestId, "ack");
|
|
346
|
-
return icon;
|
|
347
590
|
}
|
|
348
591
|
};
|
|
349
592
|
};
|
|
@@ -444,7 +687,8 @@ const resolveBrokerNativeTarget = (platform = process.platform, arch = process.a
|
|
|
444
687
|
const packageOs = platformToPackageOs(platform);
|
|
445
688
|
return {
|
|
446
689
|
packageName: `@opentray/${packageOs}-${normalizeArch(arch)}`,
|
|
447
|
-
binaryRelativePath: `bin/${packageOs === "windows" ? "opentray.exe" : "opentray"}
|
|
690
|
+
binaryRelativePath: `bin/${packageOs === "windows" ? "opentray.exe" : "opentray"}`,
|
|
691
|
+
...packageOs === "darwin" ? { carrierTemplateRelativePath: "app/Info.plist" } : {}
|
|
448
692
|
};
|
|
449
693
|
};
|
|
450
694
|
const resolveBrokerArtifactTarget = (platform = process.platform, arch = process.arch) => {
|
|
@@ -520,10 +764,19 @@ const resolveBrokerCommand = async (paths, options = {}) => {
|
|
|
520
764
|
const arch = options.arch ?? process.arch;
|
|
521
765
|
const target = resolveBrokerNativeTarget(platform, arch);
|
|
522
766
|
const installed = await (options.resolveInstalledBrokerBinary ?? resolveInstalledBrokerBinary)(target, { platform });
|
|
523
|
-
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
|
+
}
|
|
524
773
|
const sourceDir = options.sourceDir ?? dirname(fileURLToPath(sourceUrl));
|
|
525
774
|
const workspaceRoot = await (options.findWorkspaceRoot ?? findWorkspaceRoot)(sourceDir);
|
|
526
|
-
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
|
+
}
|
|
527
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, {
|
|
528
781
|
packageName: target.packageName,
|
|
529
782
|
...installed.binaryPath === void 0 ? {} : { binaryPath: installed.binaryPath }
|
|
@@ -539,14 +792,28 @@ const resolveInstalledBrokerBinary = async (target, options = {}) => {
|
|
|
539
792
|
throw error;
|
|
540
793
|
}
|
|
541
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
|
+
}
|
|
542
803
|
try {
|
|
543
804
|
await (options.assertBinaryAccessible ?? assertInstalledBrokerBinaryAccessible)(binaryPath, platform);
|
|
544
805
|
return {
|
|
545
806
|
binary: binaryPath,
|
|
546
|
-
binaryPath
|
|
807
|
+
binaryPath,
|
|
808
|
+
...carrierTemplatePath === void 0 ? {} : { carrierTemplatePath },
|
|
809
|
+
...carrierTemplate === void 0 ? {} : { carrierTemplate }
|
|
547
810
|
};
|
|
548
811
|
} catch (error) {
|
|
549
|
-
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
|
+
};
|
|
550
817
|
throw error;
|
|
551
818
|
}
|
|
552
819
|
};
|
|
@@ -593,15 +860,39 @@ const ensureDevBrokerBinary = async (workspaceRoot, paths, platform) => {
|
|
|
593
860
|
await runCargoBuild(workspaceRoot, platform === "win32" ? targetDir : void 0);
|
|
594
861
|
return binary;
|
|
595
862
|
};
|
|
596
|
-
const
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
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
|
|
605
896
|
});
|
|
606
897
|
child.once("error", reject);
|
|
607
898
|
child.once("exit", (code) => {
|
|
@@ -609,7 +900,7 @@ const runCargoBuild = (workspaceRoot, targetDir) => new Promise((resolve, reject
|
|
|
609
900
|
resolve();
|
|
610
901
|
return;
|
|
611
902
|
}
|
|
612
|
-
reject(/* @__PURE__ */ new Error(
|
|
903
|
+
reject(/* @__PURE__ */ new Error(`${command} ${args.join(" ")} failed with code ${code ?? "unknown"}`));
|
|
613
904
|
});
|
|
614
905
|
});
|
|
615
906
|
const exists = async (path) => {
|
|
@@ -624,6 +915,13 @@ const binaryAccessMode = (platform) => platform === "win32" ? constants.F_OK : c
|
|
|
624
915
|
const assertInstalledBrokerBinaryAccessible = async (binaryPath, platform) => {
|
|
625
916
|
await access(binaryPath, binaryAccessMode(platform));
|
|
626
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
|
+
});
|
|
627
925
|
function isNodeError$1(error) {
|
|
628
926
|
return error instanceof Error && "code" in error;
|
|
629
927
|
}
|
|
@@ -631,9 +929,13 @@ function isNodeError$1(error) {
|
|
|
631
929
|
//#region src/daemon/lifecycle.ts
|
|
632
930
|
const DAEMON_STDIO_ENV = "OPENTRAY_DAEMON_STDIO";
|
|
633
931
|
const DEFAULT_DAEMON_LOCK_TIMEOUT_MS = 5e3;
|
|
634
|
-
const createNodeDaemonDriver = (cliEntrypoint) => ({
|
|
932
|
+
const createNodeDaemonDriver = (cliEntrypoint, options = {}) => ({
|
|
635
933
|
async resolveBroker(paths) {
|
|
636
|
-
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
|
+
});
|
|
637
939
|
},
|
|
638
940
|
async isAlive(pid) {
|
|
639
941
|
return isProcessAlive(pid);
|
|
@@ -654,22 +956,36 @@ const createNodeDaemonDriver = (cliEntrypoint) => ({
|
|
|
654
956
|
stdio: resolveBrokerStdio(process.env[DAEMON_STDIO_ENV])
|
|
655
957
|
});
|
|
656
958
|
child.unref();
|
|
657
|
-
|
|
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;
|
|
658
962
|
},
|
|
659
963
|
async stop(pid) {
|
|
660
964
|
process.kill(pid, "SIGTERM");
|
|
965
|
+
if (process.platform === "darwin" && options.appBundle !== void 0) await clearDarwinAppBundleOwner(options.appBundle.path, pid);
|
|
661
966
|
}
|
|
662
967
|
});
|
|
663
968
|
const resolveBrokerStdio = (value) => {
|
|
664
969
|
return value === "inherit" ? "inherit" : "ignore";
|
|
665
970
|
};
|
|
666
971
|
const startDaemon = async ({ paths, driver, lockTimeoutMs = DEFAULT_DAEMON_LOCK_TIMEOUT_MS }) => {
|
|
667
|
-
const broker = await driver.resolveBroker(paths);
|
|
668
972
|
await mkdir(paths.runtimeDir, { recursive: true });
|
|
669
973
|
const lock = await acquireLock(paths.lockFile, lockTimeoutMs);
|
|
670
974
|
try {
|
|
671
975
|
const existingPid = await readPid(paths.pidFile);
|
|
672
|
-
|
|
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) {
|
|
673
989
|
if (readyMatchesBroker(await readReadyMetadata(paths.readyFile), paths, existingPid, broker)) return {
|
|
674
990
|
status: "already-running",
|
|
675
991
|
pid: existingPid,
|
|
@@ -758,6 +1074,7 @@ const readReadyMetadata = async (readyFile) => {
|
|
|
758
1074
|
};
|
|
759
1075
|
};
|
|
760
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";
|
|
761
1078
|
const executablePathsEqual = (left, right, platform = process.platform) => {
|
|
762
1079
|
if (platform !== "win32") return left === right;
|
|
763
1080
|
const normalize = (value) => value.replaceAll("\\", "/").replace(/^\/\/?\?\//u, "").toLowerCase();
|
|
@@ -854,7 +1171,16 @@ const connectLocalBroker = async (options = {}) => {
|
|
|
854
1171
|
const autoStart = options.autoStart ?? endpoint === paths.endpoint;
|
|
855
1172
|
if (autoStart && endpoint !== paths.endpoint) throw new Error("local broker autoStart requires the derived same-version endpoint");
|
|
856
1173
|
const cliEntrypoint = options.cliEntrypoint ?? resolveCliEntrypoint();
|
|
857
|
-
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
|
+
});
|
|
858
1184
|
const expectedBrokerArtifactIdentity = autoStart ? (await startDaemon({
|
|
859
1185
|
paths,
|
|
860
1186
|
driver
|
|
@@ -868,6 +1194,20 @@ const connectLocalBroker = async (options = {}) => {
|
|
|
868
1194
|
}
|
|
869
1195
|
return connection;
|
|
870
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
|
+
};
|
|
871
1211
|
const normalizeAppIdentityField = (value) => {
|
|
872
1212
|
const trimmed = value?.trim();
|
|
873
1213
|
return trimmed === void 0 || trimmed.length === 0 ? void 0 : trimmed;
|
|
@@ -1141,12 +1481,17 @@ const registerClickId = (state, id) => {
|
|
|
1141
1481
|
//#endregion
|
|
1142
1482
|
//#region src/sdk.ts
|
|
1143
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);
|
|
1144
1486
|
const normalized = normalizeCreateTrayOptions(options);
|
|
1145
|
-
const connection = await connectLocalBroker(
|
|
1487
|
+
const connection = await connectLocalBroker({
|
|
1488
|
+
...runtimeOptions,
|
|
1489
|
+
...appIcon === void 0 ? {} : { appIcon }
|
|
1490
|
+
});
|
|
1146
1491
|
try {
|
|
1147
1492
|
const tray = await createClient(connection, { appOptions: {
|
|
1148
1493
|
...runtimeOptions.appName === void 0 ? {} : { name: runtimeOptions.appName },
|
|
1149
|
-
...
|
|
1494
|
+
...appIcon === void 0 ? {} : { appIcon }
|
|
1150
1495
|
} }).createTray(normalized.options);
|
|
1151
1496
|
return wrapCreateTrayHandle(tray, {
|
|
1152
1497
|
closeConnection: () => connection.close(),
|
|
@@ -1211,6 +1556,6 @@ const bindMenuClickHandlers = (tray, handlers) => {
|
|
|
1211
1556
|
};
|
|
1212
1557
|
const noop = () => {};
|
|
1213
1558
|
//#endregion
|
|
1214
|
-
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 };
|
|
1215
1560
|
|
|
1216
1561
|
//# sourceMappingURL=index.mjs.map
|