opentray 0.17.0 → 0.18.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 +44 -1
- package/dist/index.d.mts +9 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +227 -34
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -123,6 +123,11 @@ const tray = await createTray(options, {
|
|
|
123
123
|
appId: "com.example.first-app",
|
|
124
124
|
appName: "First App",
|
|
125
125
|
appIcon,
|
|
126
|
+
appLaunch: {
|
|
127
|
+
command: process.execPath,
|
|
128
|
+
args: ["./dist/main.mjs", "--desktop"],
|
|
129
|
+
cwd: process.cwd(),
|
|
130
|
+
},
|
|
126
131
|
appBundle: {
|
|
127
132
|
// Relative paths resolve from the caller package root.
|
|
128
133
|
path: "dist/First App.app",
|
|
@@ -132,9 +137,47 @@ const tray = await createTray(options, {
|
|
|
132
137
|
});
|
|
133
138
|
```
|
|
134
139
|
|
|
140
|
+
`appLaunch` is optional. When omitted or `null`, OpenTray snapshots
|
|
141
|
+
`process.execPath`, `process.argv.slice(1)`, and `process.cwd()` after the local
|
|
142
|
+
runtime handshake succeeds. Opening the stable macOS bundle later launches that
|
|
143
|
+
latest vector directly, without a shell. Explicit commands may name an
|
|
144
|
+
executable or script launcher and may provide `args`/`cwd`; OpenTray never stores
|
|
145
|
+
the full process environment in the bundle.
|
|
146
|
+
|
|
135
147
|
`appBundle.reinitialize` defaults to `true`. A prebuilt bundle must contain the
|
|
136
148
|
matching broker, `Info.plist`, target, icon, and OpenTray manifest; incompatible
|
|
137
|
-
bundles fail with a typed error instead of being silently rebuilt.
|
|
149
|
+
bundles fail with a typed error instead of being silently rebuilt. In prebuilt
|
|
150
|
+
mode those assets remain read-only, while the runtime-owned
|
|
151
|
+
`Contents/Resources/opentray-launch.json` descriptor is still atomically updated
|
|
152
|
+
after a successful handshake.
|
|
153
|
+
|
|
154
|
+
Cold launch is currently a Darwin carrier capability. A live retained session
|
|
155
|
+
does not execute this descriptor: the broker emits an app-scoped
|
|
156
|
+
`reopenRequested` event instead. `@opentray/ext-webview` consumes that intent by
|
|
157
|
+
revealing and focusing the most recently active retained `appMode` window.
|
|
158
|
+
Ordinary Windows/Linux taskbar entries are not claimed as persistent post-exit
|
|
159
|
+
launchers.
|
|
160
|
+
|
|
161
|
+
Development callers must describe the command that owns the complete process
|
|
162
|
+
tree. For a pnpm/Vite app, persist `process.execPath` with the absolute
|
|
163
|
+
`npm_execpath` and directly address the Vite-owning workspace, for example
|
|
164
|
+
`[npm_execpath, "--dir", absoluteWebuiDir, "dev"]`, plus the repository-root
|
|
165
|
+
`cwd`. Every transitive command must resolve from the package graph or be
|
|
166
|
+
absolute. Persisting only a daemon child, a shell string, or a script that later
|
|
167
|
+
invokes bare `pnpm` cannot reliably restore Vite, its proxy, and the final
|
|
168
|
+
WebView URL from Finder's minimal environment.
|
|
169
|
+
|
|
170
|
+
### Runtime diagnostics
|
|
171
|
+
|
|
172
|
+
Detached brokers append stdout and stderr to the caller-scoped
|
|
173
|
+
`<home>/.opentray/<package-version>/<caller-label>/runtime/broker.log` by default.
|
|
174
|
+
Set `OPENTRAY_DAEMON_STDIO=inherit` for an interactive terminal or
|
|
175
|
+
`OPENTRAY_DAEMON_STDIO=ignore` when silence is deliberate. Broker readiness uses
|
|
176
|
+
a bounded native cold-start budget while continuing to validate process liveness
|
|
177
|
+
and exact artifact identity; early-exit and timeout errors include the broker log
|
|
178
|
+
path. When a Darwin app carrier is opened without the private `broker` arguments, its
|
|
179
|
+
`Contents/Resources/opentray-launch.log` records descriptor parsing, spawn PID or
|
|
180
|
+
error, and the relaunched consumer's stdout/stderr.
|
|
138
181
|
|
|
139
182
|
## Runtime Ownership
|
|
140
183
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AppIcon, AppIcon as AppIcon$1, AppIconAsset, AppIconSource, AppIconVariant, AppIconVariantOf, AppId, AppIdentity, AppOptions, AppRef, BrokerEndpointIdentity, BrokerEndpointIdentityOptions, ClientFrame, ClientFrame as ClientFrame$1, ClientRequestFrame, ClientRequestFrame as ClientRequestFrame$1, DarwinIcon, DarwinIconText, ExpectedExtensionIdentity, ExtensionEnvelope, ExtensionEnvelope as ExtensionEnvelope$1, Icon, Icon as Icon$1, Icon as TrayIcon, IconCandidates, IconImage, IconText, LinuxIcon, Menu, Menu as Menu$1, MenuItem, MenuItemId, MenuItemId as MenuItemId$1, MouseButton, PROTOCOL_VERSION, Rect, RequestId, ServerFrame, ServerFrame as ServerFrame$1, SimpleIcon, Tooltip, Tooltip as Tooltip$1, Tooltip as TrayTooltip, TrayBoundsKind, TrayBoundsResult, TrayBoundsResult as TrayBoundsResult$1, TrayEvent, TrayEvent as TrayEvent$1, TrayId, TrayId as TrayId$1, TrayOptions, TrayOptions as TrayOptions$1, Win32Icon, createBrokerEndpointIdentity, formatBrokerEndpointName, formatBrokerStateRoot, formatUnixSocketPath, formatWindowsPipeName, isSupportedProtocolVersion } from "@opentray/spec";
|
|
2
|
-
import { OpenTrayAppBundleOptions, OpenTrayAppBundleOptions as OpenTrayAppBundleOptions$1 } from "@opentray/packaging";
|
|
1
|
+
import { AppEvent, AppEvent as AppEvent$1, AppIcon, AppIcon as AppIcon$1, AppIconAsset, AppIconSource, AppIconVariant, AppIconVariantOf, AppId, AppIdentity, AppOptions, AppRef, BrokerEndpointIdentity, BrokerEndpointIdentityOptions, ClientFrame, ClientFrame as ClientFrame$1, ClientRequestFrame, ClientRequestFrame as ClientRequestFrame$1, DarwinIcon, DarwinIconText, ExpectedExtensionIdentity, ExtensionEnvelope, ExtensionEnvelope as ExtensionEnvelope$1, Icon, Icon as Icon$1, Icon as TrayIcon, IconCandidates, IconImage, IconText, LinuxIcon, Menu, Menu as Menu$1, MenuItem, MenuItemId, MenuItemId as MenuItemId$1, MouseButton, PROTOCOL_VERSION, Rect, RequestId, ServerFrame, ServerFrame as ServerFrame$1, SimpleIcon, Tooltip, Tooltip as Tooltip$1, Tooltip as TrayTooltip, TrayBoundsKind, TrayBoundsResult, TrayBoundsResult as TrayBoundsResult$1, TrayEvent, TrayEvent as TrayEvent$1, TrayId, TrayId as TrayId$1, TrayOptions, TrayOptions as TrayOptions$1, Win32Icon, createBrokerEndpointIdentity, formatBrokerEndpointName, formatBrokerStateRoot, formatUnixSocketPath, formatWindowsPipeName, isSupportedProtocolVersion } from "@opentray/spec";
|
|
2
|
+
import { OpenTrayAppBundleOptions, OpenTrayAppBundleOptions as OpenTrayAppBundleOptions$1, OpenTrayAppLaunchOptions, OpenTrayAppLaunchOptions as OpenTrayAppLaunchOptions$1 } from "@opentray/packaging";
|
|
3
3
|
|
|
4
4
|
//#region src/native-extension-artifact.d.ts
|
|
5
5
|
type NativeExtensionArch = "arm64" | "x64";
|
|
@@ -54,7 +54,7 @@ interface OpenTrayTransport {
|
|
|
54
54
|
request(frame: ClientRequestFrame$1): Promise<ServerFrame$1>;
|
|
55
55
|
}
|
|
56
56
|
type OpenTrayEventFrame = Extract<ServerFrame$1, {
|
|
57
|
-
type: "event" | "ext-event";
|
|
57
|
+
type: "event" | "app-event" | "ext-event";
|
|
58
58
|
}>;
|
|
59
59
|
interface OpenTrayEventSource {
|
|
60
60
|
onEvent(listener: (frame: OpenTrayEventFrame) => void): () => void;
|
|
@@ -101,6 +101,9 @@ interface EventfulTrayHandle extends TrayHandle {
|
|
|
101
101
|
onMenuClick(handler: (event: TrayEventByType<"menuClick">) => void): () => void;
|
|
102
102
|
onTrayClick(handler: (event: TrayEventByType<"trayClick">) => void): () => void;
|
|
103
103
|
onTrayDoubleClick(handler: (event: TrayEventByType<"trayDoubleClick">) => void): () => void;
|
|
104
|
+
onAppReopenRequested(handler: (event: Extract<AppEvent$1, {
|
|
105
|
+
type: "reopenRequested";
|
|
106
|
+
}>) => void): () => void;
|
|
104
107
|
}
|
|
105
108
|
interface ExtensionLoadOptions {
|
|
106
109
|
name: string;
|
|
@@ -196,6 +199,8 @@ interface OpenTrayRuntimeOptions {
|
|
|
196
199
|
appName?: string;
|
|
197
200
|
appIcon?: AppIcon$1;
|
|
198
201
|
appBundle?: OpenTrayAppBundleOptions$1;
|
|
202
|
+
/** Stable app-entry launch vector. Omitted/null snapshots the current invocation. */
|
|
203
|
+
appLaunch?: OpenTrayAppLaunchOptions$1 | null;
|
|
199
204
|
autoStart?: boolean;
|
|
200
205
|
}
|
|
201
206
|
/** App-facing tray options accepted by top-level createTray. */
|
|
@@ -228,5 +233,5 @@ declare class AppIconVariantNotFoundError extends Error {
|
|
|
228
233
|
/** Validate an explicit AppIcon, including native file signatures, before broker startup. */
|
|
229
234
|
declare const validateAppIcon: (icon: unknown, platform?: NodeJS.Platform) => Promise<void>;
|
|
230
235
|
//#endregion
|
|
231
|
-
export { type AppHandle, type AppIcon, type AppIconAsset, type AppIconSource, type AppIconVariant, AppIconVariantNotFoundError, type AppIconVariantOf, type AppId, type AppIdentity, type AppOptions, type AppRef, type BrokerEndpointIdentity, type BrokerEndpointIdentityOptions, type ClientFrame, type ClientRequestFrame, type CreateClientOptions, type CreateTrayCheckItem, type CreateTrayHandle, type CreateTrayItem, type CreateTrayMenu, type CreateTrayMenu as TrayMenu, type CreateTrayMenuClickHandler, type CreateTrayMenuItem, type CreateTrayOptions, type CreateTrayRadioItem, type CreateTraySeparatorItem, type CreateTraySubmenuItem, DEFAULT_APP_ICON_VARIANT, type DarwinIcon, type DarwinIconText, type EventfulTrayHandle, type ExtensionEnvelope, type ExtensionLoadOptions, type Icon, type IconCandidates, type IconImage, type IconText, InvalidAppIconError, type LinuxIcon, type Menu, type MenuItem, type MenuItemId, type MouseButton, type NativeExtensionArch, type NativeExtensionArtifact, NativeExtensionArtifactResolutionError, type NativeExtensionExpectedIdentity, type NativeExtensionFileArtifact, type NativeExtensionIdentitySource, type NativeExtensionPackageArtifact, type NativeExtensionPackageTarget, type NativeExtensionTarget, type OpenTrayAppBundleOptions, type OpenTrayClient, type OpenTrayConnection, type OpenTrayEventFrame, type OpenTrayEventSource, type OpenTrayRuntimeOptions, type OpenTrayTransport, PROTOCOL_VERSION, type Rect, type ResolvedNativeExtensionArtifact, type ServerFrame, type SimpleIcon, type Tooltip, type TrayBoundsKind, type TrayBoundsResult, type TrayEvent, type TrayEventByType, type TrayEventType, type TrayExtension, type TrayExtensionContext, type TrayExtensionMountSpec, type TrayHandle, type TrayIcon, type TrayId, type TrayOptions, type TrayTooltip, type Win32Icon, createBrokerEndpointIdentity, createClient, createInitFrame, createTray, createTrayHandle, formatBrokerEndpointName, formatBrokerStateRoot, formatUnixSocketPath, formatWindowsPipeName, isSupportedProtocolVersion, resolveNativeExtensionArtifact, validateAppIcon };
|
|
236
|
+
export { type AppEvent, type AppHandle, type AppIcon, type AppIconAsset, type AppIconSource, type AppIconVariant, AppIconVariantNotFoundError, type AppIconVariantOf, type AppId, type AppIdentity, type AppOptions, type AppRef, type BrokerEndpointIdentity, type BrokerEndpointIdentityOptions, type ClientFrame, type ClientRequestFrame, type CreateClientOptions, type CreateTrayCheckItem, type CreateTrayHandle, type CreateTrayItem, type CreateTrayMenu, type CreateTrayMenu as TrayMenu, type CreateTrayMenuClickHandler, type CreateTrayMenuItem, type CreateTrayOptions, type CreateTrayRadioItem, type CreateTraySeparatorItem, type CreateTraySubmenuItem, DEFAULT_APP_ICON_VARIANT, type DarwinIcon, type DarwinIconText, type EventfulTrayHandle, type ExtensionEnvelope, type ExtensionLoadOptions, type Icon, type IconCandidates, type IconImage, type IconText, InvalidAppIconError, type LinuxIcon, type Menu, type MenuItem, type MenuItemId, type MouseButton, type NativeExtensionArch, type NativeExtensionArtifact, NativeExtensionArtifactResolutionError, type NativeExtensionExpectedIdentity, type NativeExtensionFileArtifact, type NativeExtensionIdentitySource, type NativeExtensionPackageArtifact, type NativeExtensionPackageTarget, type NativeExtensionTarget, type OpenTrayAppBundleOptions, type OpenTrayAppLaunchOptions, type OpenTrayClient, type OpenTrayConnection, type OpenTrayEventFrame, type OpenTrayEventSource, type OpenTrayRuntimeOptions, type OpenTrayTransport, PROTOCOL_VERSION, type Rect, type ResolvedNativeExtensionArtifact, type ServerFrame, type SimpleIcon, type Tooltip, type TrayBoundsKind, type TrayBoundsResult, type TrayEvent, type TrayEventByType, type TrayEventType, type TrayExtension, type TrayExtensionContext, type TrayExtensionMountSpec, type TrayHandle, type TrayIcon, type TrayId, type TrayOptions, type TrayTooltip, type Win32Icon, createBrokerEndpointIdentity, createClient, createInitFrame, createTray, createTrayHandle, formatBrokerEndpointName, formatBrokerStateRoot, formatUnixSocketPath, formatWindowsPipeName, isSupportedProtocolVersion, resolveNativeExtensionArtifact, validateAppIcon };
|
|
232
237
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/native-extension-artifact.ts","../src/client.ts","../src/menu-input.ts","../src/sdk.ts","../src/app-icon.ts"],"mappings":";;;;KAUY,mBAAA;AAAA,KACA,qBAAA,MAA2B,MAAA,CAAO,QAAA,IAAY,mBAAmB;AAAA,UAE5D,4BAAA;EACf,WAAA;EACA,WAAW;AAAA;AAAA,UAGI,6BAAA;EACf,cAAA;EACA,mBAAmB;AAAA;AAAA,UAGJ,8BAAA,SACP,6BAAA;EACR,IAAA;EACA,OAAA,EAAS,OAAA,CAAQ,MAAA,CAAO,qBAAA,EAAuB,4BAAA;AAAA;AAAA,UAGvC,+BAAA;EACR,IAAA;EACA,IAAI;AAAA;AAAA,KAGM,2BAAA,GAA8B,+BAAA;EAGlC,gBAAA,EAAkB,+BAAA;EAClB,cAAA;AAAA;EAGA,gBAAA;EACA,cAAA,EAAgB,6BAAA;AAAA;AAAA,KAIZ,uBAAA,GACR,8BAAA,GACA,2BAA2B;AAAA,KAEnB,+BAAA,GAAkC,yBAAyB;AAAA,UAEtD,+BAAA;EACf,IAAA;EACA,WAAA;EACA,cAAA;EACA,gBAAA,EAAkB,+BAAA;EAClB,MAAA,EAAQ,qBAAqB;AAAA;AAAA,cAGlB,sCAAA,SAA+C,KAAA;EAAA,SAO/C,MAAA,EAAQ,qBAAA;EAAA,SANV,IAAA;EAAA,SACA,WAAA;EAAA,SACA,oBAAA;cAGP,OAAA,UACS,MAAA,EAAQ,qBAAA,EACjB,OAAA,GAAS,YAAA;IACP,WAAA;IACA,oBAAA;EAAA;AAAA;;cAeO,8BAAA,GACX,QAAA,EAAU,uBAAA,EACV,QAAA,GAAU,MAAA,CAAO,QAAA,EACjB,IAAA,cACC,OAAA,CAAQ,+BAAA;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/native-extension-artifact.ts","../src/client.ts","../src/menu-input.ts","../src/sdk.ts","../src/app-icon.ts"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
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
|
-
import { access, mkdir, open, readFile, realpath, rm, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { access, appendFile, link, mkdir, open, readFile, realpath, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
4
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
|
+
import { clearDarwinAppBundleOwner, convergeDarwinAppBundleIdentity, ensureDarwinAppBundle, resolveDefaultDarwinAppBundlePath, resolveOpenTrayPackageIdentity, updateDarwinAppLaunchDescriptor, writeDarwinAppBundleOwner } from "@opentray/packaging";
|
|
9
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
9
10
|
import { constants } from "node:fs";
|
|
10
11
|
import { spawn } from "node:child_process";
|
|
11
|
-
import { createHash } from "node:crypto";
|
|
12
12
|
//#region src/app-icon.ts
|
|
13
13
|
const DEFAULT_APP_ICON_VARIANT = "default";
|
|
14
14
|
const APP_ICON_VARIANT_PATTERN = /^[a-z][a-z0-9-]{0,63}$/;
|
|
@@ -517,6 +517,11 @@ const attachEventfulTrayHandle = (handle, source, appId, nextMountId) => {
|
|
|
517
517
|
onTrayDoubleClick(handler) {
|
|
518
518
|
return listen("trayDoubleClick", handler);
|
|
519
519
|
},
|
|
520
|
+
onAppReopenRequested(handler) {
|
|
521
|
+
return source.onEvent((frame) => {
|
|
522
|
+
if (frame.type === "app-event" && frame.event.type === "reopenRequested" && frame.event.appId === appId) handler(frame.event);
|
|
523
|
+
});
|
|
524
|
+
},
|
|
520
525
|
extend(extension, options) {
|
|
521
526
|
const context = createTrayExtensionContext(eventful, appId, extension, options, nextMountId);
|
|
522
527
|
const capability = extension.extend(eventful, context, options);
|
|
@@ -655,6 +660,7 @@ const requestIdOf = (frame) => {
|
|
|
655
660
|
case "error": return frame.requestId;
|
|
656
661
|
case "ready":
|
|
657
662
|
case "event":
|
|
663
|
+
case "app-event":
|
|
658
664
|
case "ext-event": return;
|
|
659
665
|
}
|
|
660
666
|
};
|
|
@@ -928,7 +934,10 @@ function isNodeError$1(error) {
|
|
|
928
934
|
//#endregion
|
|
929
935
|
//#region src/daemon/lifecycle.ts
|
|
930
936
|
const DAEMON_STDIO_ENV = "OPENTRAY_DAEMON_STDIO";
|
|
931
|
-
const
|
|
937
|
+
const DAEMON_POLL_INTERVAL_MS = 50;
|
|
938
|
+
const DEFAULT_DAEMON_READY_TIMEOUT_MS = 1e4;
|
|
939
|
+
const DEFAULT_DAEMON_LOCK_TIMEOUT_MS = 15e3;
|
|
940
|
+
const ABANDONED_RECLAIM_CLAIM_MS = 1e3;
|
|
932
941
|
const createNodeDaemonDriver = (cliEntrypoint, options = {}) => ({
|
|
933
942
|
async resolveBroker(paths) {
|
|
934
943
|
return resolveBrokerArtifact(paths, {
|
|
@@ -941,19 +950,71 @@ const createNodeDaemonDriver = (cliEntrypoint, options = {}) => ({
|
|
|
941
950
|
return isProcessAlive(pid);
|
|
942
951
|
},
|
|
943
952
|
async spawnBroker(paths, broker) {
|
|
944
|
-
const
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
953
|
+
const env = options.env ?? process.env;
|
|
954
|
+
const stdio = resolveBrokerStdio(env[DAEMON_STDIO_ENV]);
|
|
955
|
+
if (stdio === "log") await mkdir(paths.runtimeDir, { recursive: true });
|
|
956
|
+
const logHandle = stdio === "log" ? await open(paths.brokerLog, "a") : void 0;
|
|
957
|
+
if (logHandle !== void 0) await logHandle.appendFile(`${JSON.stringify({
|
|
958
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
959
|
+
event: "broker-spawn",
|
|
960
|
+
command: broker.command,
|
|
961
|
+
cwd: broker.cwd ?? process.cwd(),
|
|
962
|
+
appId: paths.appId,
|
|
963
|
+
appName: paths.appName
|
|
964
|
+
})}\n`, "utf8");
|
|
965
|
+
if (isAbsolute(broker.command)) try {
|
|
966
|
+
await access(broker.command, process.platform === "win32" ? constants.F_OK : constants.X_OK);
|
|
967
|
+
} catch (error) {
|
|
968
|
+
if (stdio === "log") await appendFile(paths.brokerLog, `${JSON.stringify({
|
|
969
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
970
|
+
event: "broker-spawn-error",
|
|
971
|
+
command: broker.command,
|
|
972
|
+
error: error instanceof Error ? error.message : String(error)
|
|
973
|
+
})}\n`, "utf8").catch(() => void 0);
|
|
974
|
+
await logHandle?.close();
|
|
975
|
+
return 0;
|
|
976
|
+
}
|
|
977
|
+
let child;
|
|
978
|
+
try {
|
|
979
|
+
child = spawn(broker.command, broker.args, {
|
|
980
|
+
cwd: broker.cwd,
|
|
981
|
+
detached: true,
|
|
982
|
+
env: {
|
|
983
|
+
...env,
|
|
984
|
+
OPENTRAY_DAEMON_HOME: paths.homeDir,
|
|
985
|
+
OPENTRAY_DAEMON_PACKAGE_VERSION: paths.packageVersion,
|
|
986
|
+
OPENTRAY_DAEMON_CLI_ENTRYPOINT: cliEntrypoint,
|
|
987
|
+
OPENTRAY_DAEMON_APP_ID: paths.appId,
|
|
988
|
+
OPENTRAY_DAEMON_APP_NAME: paths.appName,
|
|
989
|
+
OPENTRAY_DAEMON_CALLER_LABEL: paths.callerLabel
|
|
990
|
+
},
|
|
991
|
+
stdio: logHandle === void 0 ? stdio === "log" ? "ignore" : stdio : [
|
|
992
|
+
"ignore",
|
|
993
|
+
logHandle.fd,
|
|
994
|
+
logHandle.fd
|
|
995
|
+
]
|
|
996
|
+
});
|
|
997
|
+
} finally {
|
|
998
|
+
await logHandle?.close();
|
|
999
|
+
}
|
|
1000
|
+
child.on("error", (error) => {
|
|
1001
|
+
if (stdio !== "log") return;
|
|
1002
|
+
appendFile(paths.brokerLog, `${JSON.stringify({
|
|
1003
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1004
|
+
event: "broker-spawn-error",
|
|
1005
|
+
command: broker.command,
|
|
1006
|
+
error: error.message
|
|
1007
|
+
})}\n`, "utf8").catch(() => void 0);
|
|
1008
|
+
});
|
|
1009
|
+
child.once("exit", (code, signal) => {
|
|
1010
|
+
if (stdio !== "log") return;
|
|
1011
|
+
appendFile(paths.brokerLog, `${JSON.stringify({
|
|
1012
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1013
|
+
event: "broker-exit",
|
|
1014
|
+
command: broker.command,
|
|
1015
|
+
code,
|
|
1016
|
+
signal
|
|
1017
|
+
})}\n`, "utf8").catch(() => void 0);
|
|
957
1018
|
});
|
|
958
1019
|
child.unref();
|
|
959
1020
|
const pid = child.pid ?? 0;
|
|
@@ -966,11 +1027,13 @@ const createNodeDaemonDriver = (cliEntrypoint, options = {}) => ({
|
|
|
966
1027
|
}
|
|
967
1028
|
});
|
|
968
1029
|
const resolveBrokerStdio = (value) => {
|
|
969
|
-
|
|
1030
|
+
if (value === "inherit") return "inherit";
|
|
1031
|
+
if (value === "ignore") return "ignore";
|
|
1032
|
+
return "log";
|
|
970
1033
|
};
|
|
971
|
-
const startDaemon = async ({ paths, driver, lockTimeoutMs = DEFAULT_DAEMON_LOCK_TIMEOUT_MS }) => {
|
|
1034
|
+
const startDaemon = async ({ paths, driver, lockTimeoutMs = DEFAULT_DAEMON_LOCK_TIMEOUT_MS, readinessTimeoutMs = DEFAULT_DAEMON_READY_TIMEOUT_MS }) => {
|
|
972
1035
|
await mkdir(paths.runtimeDir, { recursive: true });
|
|
973
|
-
const lock = await
|
|
1036
|
+
const lock = await acquireDaemonLock(paths.lockFile, lockTimeoutMs);
|
|
974
1037
|
try {
|
|
975
1038
|
const existingPid = await readPid(paths.pidFile);
|
|
976
1039
|
let existingAlive = existingPid !== void 0 && await driver.isAlive(existingPid);
|
|
@@ -1000,7 +1063,7 @@ const startDaemon = async ({ paths, driver, lockTimeoutMs = DEFAULT_DAEMON_LOCK_
|
|
|
1000
1063
|
if (!Number.isInteger(pid) || pid <= 0) throw new Error("daemon broker process did not expose a valid pid");
|
|
1001
1064
|
await writeFile(paths.pidFile, `${pid}\n`, "utf8");
|
|
1002
1065
|
try {
|
|
1003
|
-
await waitForReadyFile(paths, driver, pid, broker);
|
|
1066
|
+
await waitForReadyFile(paths, driver, pid, broker, readinessTimeoutMs);
|
|
1004
1067
|
} catch (error) {
|
|
1005
1068
|
if (await driver.isAlive(pid)) {
|
|
1006
1069
|
await driver.stop(pid);
|
|
@@ -1037,19 +1100,22 @@ const cleanupRuntimeFiles = async (paths) => {
|
|
|
1037
1100
|
const waitUntilStopped = async (driver, pid) => {
|
|
1038
1101
|
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
1039
1102
|
if (!await driver.isAlive(pid)) return true;
|
|
1040
|
-
await sleep(
|
|
1103
|
+
await sleep(DAEMON_POLL_INTERVAL_MS);
|
|
1041
1104
|
}
|
|
1042
1105
|
return false;
|
|
1043
1106
|
};
|
|
1044
|
-
const waitForReadyFile = async (paths, driver, pid, broker) => {
|
|
1045
|
-
|
|
1107
|
+
const waitForReadyFile = async (paths, driver, pid, broker, timeoutMs) => {
|
|
1108
|
+
const deadline = Date.now() + timeoutMs;
|
|
1109
|
+
while (true) {
|
|
1046
1110
|
const ready = await readReadyMetadata(paths.readyFile);
|
|
1047
1111
|
if (readyMatchesBroker(ready, paths, pid, broker)) return;
|
|
1048
1112
|
if (ready !== void 0) throw new Error(`daemon readiness artifact identity mismatch: expected=${JSON.stringify(broker.artifactIdentity)} actual=${JSON.stringify(ready.brokerArtifactIdentity)}`);
|
|
1049
|
-
if (!await driver.isAlive(pid)) throw new Error(`daemon broker exited before readiness: pid=${pid}; readyFile=${paths.readyFile}; set ${DAEMON_STDIO_ENV}=inherit to
|
|
1050
|
-
|
|
1113
|
+
if (!await driver.isAlive(pid)) throw new Error(`daemon broker exited before readiness: pid=${pid}; readyFile=${paths.readyFile}; brokerLog=${paths.brokerLog}; set ${DAEMON_STDIO_ENV}=inherit to mirror broker stderr`);
|
|
1114
|
+
const remainingMs = deadline - Date.now();
|
|
1115
|
+
if (remainingMs <= 0) break;
|
|
1116
|
+
await sleep(Math.min(DAEMON_POLL_INTERVAL_MS, remainingMs));
|
|
1051
1117
|
}
|
|
1052
|
-
throw new Error(`timed out waiting for daemon readiness
|
|
1118
|
+
throw new Error(`timed out waiting for daemon readiness after ${timeoutMs}ms: pid=${pid}; readyFile=${paths.readyFile}; brokerLog=${paths.brokerLog}; set ${DAEMON_STDIO_ENV}=inherit to mirror broker stderr`);
|
|
1053
1119
|
};
|
|
1054
1120
|
const readReadyMetadata = async (readyFile) => {
|
|
1055
1121
|
let value;
|
|
@@ -1080,19 +1146,25 @@ const executablePathsEqual = (left, right, platform = process.platform) => {
|
|
|
1080
1146
|
const normalize = (value) => value.replaceAll("\\", "/").replace(/^\/\/?\?\//u, "").toLowerCase();
|
|
1081
1147
|
return normalize(left) === normalize(right);
|
|
1082
1148
|
};
|
|
1083
|
-
const
|
|
1149
|
+
const acquireDaemonLock = async (lockFile, timeoutMs) => {
|
|
1084
1150
|
const deadline = Date.now() + timeoutMs;
|
|
1151
|
+
const owner = `${JSON.stringify({
|
|
1152
|
+
pid: process.pid,
|
|
1153
|
+
token: randomUUID()
|
|
1154
|
+
})}\n`;
|
|
1085
1155
|
while (Date.now() <= deadline) try {
|
|
1086
1156
|
const handle = await open(lockFile, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY);
|
|
1087
|
-
await handle.writeFile(
|
|
1157
|
+
await handle.writeFile(owner, "utf8");
|
|
1088
1158
|
return { async release() {
|
|
1089
1159
|
await handle.close();
|
|
1160
|
+
if (await readLockSource(lockFile) !== owner) return;
|
|
1090
1161
|
await unlink(lockFile).catch((error) => {
|
|
1091
1162
|
if (!isNodeError(error) || error.code !== "ENOENT") throw error;
|
|
1092
1163
|
});
|
|
1093
1164
|
} };
|
|
1094
1165
|
} catch (error) {
|
|
1095
1166
|
if (isNodeError(error) && error.code === "EEXIST") {
|
|
1167
|
+
if (await reclaimDeadLockOwner(lockFile)) continue;
|
|
1096
1168
|
await sleep(25);
|
|
1097
1169
|
continue;
|
|
1098
1170
|
}
|
|
@@ -1100,12 +1172,72 @@ const acquireLock = async (lockFile, timeoutMs) => {
|
|
|
1100
1172
|
}
|
|
1101
1173
|
throw new Error(`timed out acquiring daemon lock: ${lockFile}`);
|
|
1102
1174
|
};
|
|
1175
|
+
const reclaimDeadLockOwner = async (lockFile) => {
|
|
1176
|
+
const observed = await readLockSource(lockFile);
|
|
1177
|
+
if (observed === void 0) return true;
|
|
1178
|
+
const owner = parseLockOwner(observed);
|
|
1179
|
+
if (owner === void 0 || isProcessAlive(owner.pid)) return false;
|
|
1180
|
+
const claimFile = `${lockFile}.reclaim-${createHash("sha256").update(observed).digest("hex").slice(0, 16)}`;
|
|
1181
|
+
try {
|
|
1182
|
+
await link(lockFile, claimFile);
|
|
1183
|
+
} catch (error) {
|
|
1184
|
+
if (isNodeError(error) && error.code === "ENOENT") return true;
|
|
1185
|
+
if (isNodeError(error) && error.code === "EEXIST") {
|
|
1186
|
+
await clearAbandonedReclaimClaim(claimFile);
|
|
1187
|
+
return false;
|
|
1188
|
+
}
|
|
1189
|
+
throw error;
|
|
1190
|
+
}
|
|
1191
|
+
try {
|
|
1192
|
+
if (await readLockSource(lockFile) !== observed || isProcessAlive(owner.pid)) return false;
|
|
1193
|
+
try {
|
|
1194
|
+
await unlink(lockFile);
|
|
1195
|
+
return true;
|
|
1196
|
+
} catch (error) {
|
|
1197
|
+
if (isNodeError(error) && error.code === "ENOENT") return true;
|
|
1198
|
+
throw error;
|
|
1199
|
+
}
|
|
1200
|
+
} finally {
|
|
1201
|
+
await unlink(claimFile).catch((error) => {
|
|
1202
|
+
if (!isNodeError(error) || error.code !== "ENOENT") throw error;
|
|
1203
|
+
});
|
|
1204
|
+
}
|
|
1205
|
+
};
|
|
1206
|
+
const clearAbandonedReclaimClaim = async (claimFile) => {
|
|
1207
|
+
try {
|
|
1208
|
+
const claim = await stat(claimFile);
|
|
1209
|
+
if (Date.now() - claim.ctimeMs < ABANDONED_RECLAIM_CLAIM_MS) return;
|
|
1210
|
+
await unlink(claimFile);
|
|
1211
|
+
} catch (error) {
|
|
1212
|
+
if (!isNodeError(error) || error.code !== "ENOENT") throw error;
|
|
1213
|
+
}
|
|
1214
|
+
};
|
|
1215
|
+
const readLockSource = async (lockFile) => {
|
|
1216
|
+
try {
|
|
1217
|
+
return await readFile(lockFile, "utf8");
|
|
1218
|
+
} catch (error) {
|
|
1219
|
+
if (isNodeError(error) && error.code === "ENOENT") return void 0;
|
|
1220
|
+
throw error;
|
|
1221
|
+
}
|
|
1222
|
+
};
|
|
1223
|
+
const parseLockOwner = (source) => {
|
|
1224
|
+
const legacyPid = Number(source.trim());
|
|
1225
|
+
if (Number.isInteger(legacyPid) && legacyPid > 0) return { pid: legacyPid };
|
|
1226
|
+
let value;
|
|
1227
|
+
try {
|
|
1228
|
+
value = JSON.parse(source);
|
|
1229
|
+
} catch {
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
if (!isRecord(value) || typeof value.pid !== "number" || !Number.isInteger(value.pid) || value.pid <= 0 || typeof value.token !== "string" || value.token.length === 0) return;
|
|
1233
|
+
return { pid: value.pid };
|
|
1234
|
+
};
|
|
1103
1235
|
const isProcessAlive = (pid) => {
|
|
1104
1236
|
try {
|
|
1105
1237
|
process.kill(pid, 0);
|
|
1106
1238
|
return true;
|
|
1107
|
-
} catch {
|
|
1108
|
-
return
|
|
1239
|
+
} catch (error) {
|
|
1240
|
+
return !(isNodeError(error) && error.code === "ESRCH");
|
|
1109
1241
|
}
|
|
1110
1242
|
};
|
|
1111
1243
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -1144,7 +1276,8 @@ const resolveDaemonPaths = ({ homeDir, packageVersion, callerLabel, appId, appNa
|
|
|
1144
1276
|
endpoint,
|
|
1145
1277
|
pidFile: `${runtimeDir}/broker.pid`,
|
|
1146
1278
|
lockFile: `${runtimeDir}/broker.lock`,
|
|
1147
|
-
readyFile: `${runtimeDir}/ready.json
|
|
1279
|
+
readyFile: `${runtimeDir}/ready.json`,
|
|
1280
|
+
brokerLog: `${runtimeDir}/broker.log`
|
|
1148
1281
|
};
|
|
1149
1282
|
};
|
|
1150
1283
|
const normalizeAppIdentityField$1 = (value, fallback) => {
|
|
@@ -1188,12 +1321,37 @@ const connectLocalBroker = async (options = {}) => {
|
|
|
1188
1321
|
const connection = new LocalBrokerConnection(await connectSocket(endpoint), endpoint, callerLabel);
|
|
1189
1322
|
try {
|
|
1190
1323
|
await connection.init(clientVersion, options.protocolVersion ?? PROTOCOL_VERSION$1, expectedBrokerArtifactIdentity);
|
|
1324
|
+
if (appBundle !== void 0 && options.appLaunch !== void 0) {
|
|
1325
|
+
await updateDarwinAppLaunchDescriptor(appBundle.path, options.appLaunch);
|
|
1326
|
+
const convergence = await convergeDarwinAppBundleIdentity({
|
|
1327
|
+
currentBundlePath: appBundle.path,
|
|
1328
|
+
appId: paths.appId,
|
|
1329
|
+
managedAppsRoot: resolve(paths.homeDir, ".opentray/apps"),
|
|
1330
|
+
legacyRuntimeRoot: resolve(paths.homeDir, ".opentray"),
|
|
1331
|
+
legacyBundlePaths: [resolve(paths.runtimeDir, "darwin-carrier/OpenTray.app")]
|
|
1332
|
+
});
|
|
1333
|
+
await appendBrokerDiagnostic(paths.brokerLog, {
|
|
1334
|
+
event: "bundle-identity-convergence",
|
|
1335
|
+
removed: convergence.removed,
|
|
1336
|
+
skippedLiveOwner: convergence.skippedLiveOwner,
|
|
1337
|
+
failedUnregister: convergence.failedUnregister
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1191
1340
|
} catch (error) {
|
|
1192
1341
|
await connection.close();
|
|
1193
1342
|
throw error;
|
|
1194
1343
|
}
|
|
1195
1344
|
return connection;
|
|
1196
1345
|
};
|
|
1346
|
+
const appendBrokerDiagnostic = async (path, value) => {
|
|
1347
|
+
try {
|
|
1348
|
+
await mkdir(dirname(path), { recursive: true });
|
|
1349
|
+
await appendFile(path, `${JSON.stringify({
|
|
1350
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1351
|
+
...value
|
|
1352
|
+
})}\n`, "utf8");
|
|
1353
|
+
} catch {}
|
|
1354
|
+
};
|
|
1197
1355
|
const resolveDarwinAppBundleOptions = (configured, paths, packageIdentity) => {
|
|
1198
1356
|
if (packageIdentity === void 0) throw new Error("Darwin app bundle resolution requires the caller package identity");
|
|
1199
1357
|
const configuredPath = configured?.path;
|
|
@@ -1320,7 +1478,7 @@ var LocalBrokerConnection = class {
|
|
|
1320
1478
|
this.pending.delete(requestId);
|
|
1321
1479
|
return;
|
|
1322
1480
|
}
|
|
1323
|
-
if (frame.type === "event" || frame.type === "ext-event") for (const listener of this.listeners) listener(frame);
|
|
1481
|
+
if (frame.type === "event" || frame.type === "app-event" || frame.type === "ext-event") for (const listener of this.listeners) listener(frame);
|
|
1324
1482
|
}
|
|
1325
1483
|
write(frame) {
|
|
1326
1484
|
this.socket.write(`${JSON.stringify(frame)}\n`);
|
|
@@ -1352,6 +1510,7 @@ const responseRequestId = (frame) => {
|
|
|
1352
1510
|
case "runtime-host-health": return frame.requestId;
|
|
1353
1511
|
case "ready":
|
|
1354
1512
|
case "event":
|
|
1513
|
+
case "app-event":
|
|
1355
1514
|
case "ext-event":
|
|
1356
1515
|
case "error": return;
|
|
1357
1516
|
}
|
|
@@ -1479,14 +1638,48 @@ const registerClickId = (state, id) => {
|
|
|
1479
1638
|
state.seenClickIds = seen;
|
|
1480
1639
|
};
|
|
1481
1640
|
//#endregion
|
|
1641
|
+
//#region src/app-launch.ts
|
|
1642
|
+
const normalizeAppLaunch = (configured, context = {}) => {
|
|
1643
|
+
const currentCwd = context.cwd ?? process.cwd();
|
|
1644
|
+
if (configured === void 0 || configured === null) return {
|
|
1645
|
+
schemaVersion: 1,
|
|
1646
|
+
command: validateRequiredText(context.execPath ?? process.execPath, "process.execPath"),
|
|
1647
|
+
args: [...(context.argv ?? process.argv).slice(1)].map((arg, index) => validateArg(arg, `process.argv[${index + 1}]`)),
|
|
1648
|
+
cwd: validateRequiredText(currentCwd, "process.cwd()")
|
|
1649
|
+
};
|
|
1650
|
+
const command = validateRequiredText(configured.command, "appLaunch.command");
|
|
1651
|
+
const cwd = resolve(currentCwd, configured.cwd === void 0 ? currentCwd : validateRequiredText(configured.cwd, "appLaunch.cwd"));
|
|
1652
|
+
const args = [...configured.args ?? []].map((arg, index) => validateArg(arg, `appLaunch.args[${index}]`));
|
|
1653
|
+
return {
|
|
1654
|
+
schemaVersion: 1,
|
|
1655
|
+
command: resolvePathLikeCommand(command, currentCwd),
|
|
1656
|
+
args,
|
|
1657
|
+
cwd
|
|
1658
|
+
};
|
|
1659
|
+
};
|
|
1660
|
+
const resolvePathLikeCommand = (command, cwd) => {
|
|
1661
|
+
if (isAbsolute(command) || command.includes("/") || command.includes("\\")) return resolve(cwd, command);
|
|
1662
|
+
return command;
|
|
1663
|
+
};
|
|
1664
|
+
const validateRequiredText = (value, field) => {
|
|
1665
|
+
if (value.trim().length === 0 || value.includes("\0")) throw new Error(`${field} must be a non-empty string without NUL`);
|
|
1666
|
+
return value;
|
|
1667
|
+
};
|
|
1668
|
+
const validateArg = (value, field) => {
|
|
1669
|
+
if (value.includes("\0")) throw new Error(`${field} must be a string without NUL`);
|
|
1670
|
+
return value;
|
|
1671
|
+
};
|
|
1672
|
+
//#endregion
|
|
1482
1673
|
//#region src/sdk.ts
|
|
1483
1674
|
const createTray = async (options, runtimeOptions = {}) => {
|
|
1484
1675
|
if (runtimeOptions.appIcon !== void 0) await validateAppIcon(runtimeOptions.appIcon);
|
|
1485
1676
|
const appIcon = runtimeOptions.appIcon === void 0 ? void 0 : normalizeAppIcon(runtimeOptions.appIcon);
|
|
1486
1677
|
const normalized = normalizeCreateTrayOptions(options);
|
|
1678
|
+
const appLaunch = normalizeAppLaunch(runtimeOptions.appLaunch);
|
|
1487
1679
|
const connection = await connectLocalBroker({
|
|
1488
1680
|
...runtimeOptions,
|
|
1489
|
-
...appIcon === void 0 ? {} : { appIcon }
|
|
1681
|
+
...appIcon === void 0 ? {} : { appIcon },
|
|
1682
|
+
appLaunch
|
|
1490
1683
|
});
|
|
1491
1684
|
try {
|
|
1492
1685
|
const tray = await createClient(connection, { appOptions: {
|