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 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
@@ -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;;;UC7DM,iBAAA;EACf,OAAA,CAAQ,KAAA,EAAO,oBAAA,GAAqB,OAAA,CAAQ,aAAA;AAAA;AAAA,KAGlC,kBAAA,GAAqB,OAAO,CACtC,aAAA;EACE,IAAA;AAAA;AAAA,UAGa,mBAAA;EACf,OAAA,CAAQ,QAAA,GAAW,KAAA,EAAO,kBAAkB;AAAA;AAAA,UAG7B,kBAAA,SACP,iBAAA,EACN,mBAAmB;AAAA,UAEN,cAAA;EACf,UAAA,CAAW,OAAA,EAAS,aAAA,GAAc,OAAA,CAAQ,UAAA;AAAA;AAAA,UAG3B,sBAAA,SAA+B,cAAA;EAC9C,UAAA,CAAW,OAAA,EAAS,aAAA,GAAc,OAAA,CAAQ,kBAAA;AAAA;AAAA,UAG3B,UAAA;EACf,MAAA,EAAQ,QAAA;EACR,GAAA,EAAK,SAAA;EACL,SAAA,IAAa,OAAA,CAAQ,kBAAA;EACrB,OAAA,CAAQ,IAAA,EAAM,MAAA,GAAO,OAAA;EACrB,UAAA,CAAW,OAAA,EAAS,SAAA,GAAU,OAAA;EAC9B,OAAA,CAAQ,IAAA,EAAM,MAAA,GAAO,OAAA;EACrB,aAAA,CAAc,OAAA,EAAS,oBAAA,GAAuB,OAAA;EAC9C,gBAAA,CAAiB,GAAA,UAAa,IAAA,YAAgB,OAAA;EAC9C,gBAAA,CAAiB,GAAA,UAAa,IAAA,YAAgB,OAAA,CAAQ,mBAAA;EACtD,MAAA,mDACE,SAAA,EAAW,aAAA,CAAc,WAAA,EAAa,QAAA,GACtC,OAAA,GAAU,QAAA,GACT,UAAA,GAAa,WAAA;EAChB,OAAA,IAAW,OAAA;AAAA;AAAA,UAGI,SAAA;EAAA,SACN,KAAA;EACT,OAAA,IAAW,OAAA;EACX,OAAA,CAAQ,IAAA,WAAe,OAAA;EACvB,UAAA,IAAc,OAAA,CAAQ,SAAA;EACtB,iBAAA,IAAqB,OAAA;EACrB,UAAA,CAAW,aAAA,EAAe,SAAA,mBAA0B,OAAA;AAAA;AAAA,KAG1C,eAAA,GAAkB,OAAO,CAAC,WAAA;EAAa,IAAA;AAAA;AAAA,KACvC,aAAA,GAAgB,eAAe;AAAA,KAC/B,eAAA,gBAA+B,aAAA,IAAiB,OAAA,CAC1D,eAAA;EACE,IAAA,EAAM,MAAA;AAAA;AAAA,UAGO,kBAAA,SAA2B,UAAA;EAC1C,MAAA,mDACE,SAAA,EAAW,aAAA,CAAc,WAAA,EAAa,QAAA,GACtC,OAAA,GAAU,QAAA,GACT,kBAAA,GAAqB,WAAA;EACxB,eAAA,kBACE,GAAA,UACA,OAAA,GAAU,KAAA,EAAO,mBAAA,CAAkB,KAAA;EAErC,MAAA,gBAAsB,aAAA,EACpB,KAAA,EAAO,MAAA,EACP,OAAA,GAAU,KAAA,EAAO,eAAA,CAAgB,MAAA;EAEnC,WAAA,CACE,OAAA,GAAU,KAAA,EAAO,eAAA;EAEnB,WAAA,CACE,OAAA,GAAU,KAAA,EAAO,eAAA;EAEnB,iBAAA,CACE,OAAA,GAAU,KAAA,EAAO,eAAA;AAAA;AAAA,UAIJ,oBAAA;EACf,IAAA;EACA,QAAA,EAAU,uBAAuB;EACjC,OAAA;AAAA;AAAA,UAGe,sBAAA;EACf,IAAA;EACA,QAAA,GAAW,uBAAuB;EAClC,OAAA;AAAA;AAAA,UAGe,oBAAA;EAAA,SACN,KAAA;EAAA,SACA,MAAA,EAAQ,QAAA;EAAA,SACR,IAAA;EAAA,SACA,QAAA,EAAU,uBAAA;EAAA,SACV,OAAA;EACT,YAAA,IAAgB,OAAA;EAChB,OAAA,CAAQ,IAAA,YAAgB,OAAA;EACxB,OAAA,CAAQ,IAAA,YAAgB,OAAA,CAAQ,mBAAA;AAAA;AAAA,UAGjB,aAAA;EAAA,SAIN,IAAA;EAAA,SACA,QAAA,EAAU,uBAAA;EACnB,YAAA,EAAc,OAAA,EAAS,QAAA,eAAuB,sBAAA;EAC9C,MAAA,CACE,IAAA,EAAM,UAAA,EACN,OAAA,EAAS,oBAAA,EACT,OAAA,EAAS,QAAA,eACR,WAAA;AAAA;AAAA,cAGQ,eAAA,GAAmB,aAAA,aAAwB,aAItD;AAAA,UAEe,mBAAA;EACf,eAAA;EACA,UAAA;IACE,IAAA;IACA,OAAA,GAAU,SAAO;EAAA;AAAA;AAAA,iBAIL,YAAA,CACd,SAAA,EAAW,kBAAA,EACX,OAAA,GAAU,mBAAA,GACT,sBAAA;AAAA,iBACa,YAAA,CACd,SAAA,EAAW,iBAAA,EACX,OAAA,GAAU,mBAAA,GACT,cAAA;AAAA,iBAoDa,gBAAA,CACd,SAAA,EAAW,kBAAA,EACX,KAAA,UACA,MAAA,EAAQ,QAAA,EACR,aAAA,SAAsB,SAAA,GACrB,kBAAA;AAAA,iBACa,gBAAA,CACd,SAAA,EAAW,iBAAA,EACX,KAAA,UACA,MAAA,EAAQ,QAAA,EACR,aAAA,SAAsB,SAAA,GACrB,UAAA;;;KCnOS,0BAAA,IACV,KAAmC,EAA5B,eAAe;;KAIZ,cAAA;EACV,KAAA,EAAO,kBAAkB;AAAA;;KAIf,kBAAA,sBAEE,KAAA,UAAe,KAAA,WAAgB,kBAAA,MACzC,cAAA,GACA,mBAAA,GACA,mBAAA,GACA,uBAAA,GACA,qBAAA;AAAA,KAEQ,cAAA;EACV,IAAA;EACA,EAAA,GAAK,YAAA;EACL,KAAA;EACA,YAAA;EACA,OAAA;EACA,QAAA;EACA,WAAA,GAAc,0BAA0B;AAAA;AAAA,KAG9B,mBAAA;EACV,IAAA;EACA,EAAA,GAAK,YAAA;EACL,KAAA;EACA,OAAA;EACA,OAAA;EACA,WAAA,GAAc,0BAA0B;AAAA;AAAA,KAG9B,mBAAA;EACV,IAAA;EACA,EAAA,GAAK,YAAA;EACL,KAAA;EACA,OAAA;EACA,OAAA;EACA,KAAA;EACA,WAAA,GAAc,0BAA0B;AAAA;AAAA,KAG9B,uBAAA;EACV,IAAI;AAAA;AAAA,KAGM,qBAAA;EACV,IAAA;EACA,KAAA;EACA,OAAA;EACA,KAAA,WAAgB,kBAAkB;AAAA;;;UCvCnB,sBAAA;EACf,QAAA;EACA,OAAA;EACA,cAAA;EACA,eAAA;EACA,aAAA;EACA,KAAA;EACA,OAAA;EACA,OAAA,GAAU,SAAA;EACV,SAAA,GAAY,0BAAwB;EACpC,SAAA;AAAA;;UAIe,iBAAA;EACf,EAAA;EACA,OAAA,GAAU,SAAA;EACV,IAAA,GAAO,MAAA;EACP,IAAA,GAAO,cAAA;AAAA;AHrBT;AAAA,UGyBiB,gBAAA,SACP,IAAA,CAAK,kBAAA;EACb,OAAA,CAAQ,IAAA,EAAM,cAAA,GAAiB,OAAA;EAC/B,MAAA,mDACE,SAAA,EAAW,aAAA,CAAc,WAAA,EAAa,QAAA,GACtC,OAAA,GAAU,QAAA,GACT,gBAAA,GAAmB,WAAA;EACtB,OAAA,IAAW,OAAA;AAAA;AAAA,cAGA,UAAA,GACX,OAAA,EAAS,iBAAA,EACT,cAAA,GAAgB,sBAAA,KACf,OAAA,CAAQ,gBAAA;;;cCxCE,wBAAA;AAAA,cAGA,mBAAA,SAA4B,KAAK;EAAA,SACnC,IAAA;EAAA,SACA,MAAA;cAEG,MAAA,UAAgB,QAAA,GAAU,MAAA,CAAO,QAAA;AAAA;AAAA,cAOlC,2BAAA,SAAoC,KAAK;EAAA,SAC3C,IAAA;EAAA,SACA,OAAA;cAEG,OAAA,UAAiB,QAAA,GAAU,MAAA,CAAO,QAAA;AAAA;;cAQnC,eAAA,GACX,IAAA,WACA,QAAA,GAAU,MAAA,CAAO,QAAA,KAChB,OAAO"}
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 DEFAULT_DAEMON_LOCK_TIMEOUT_MS = 5e3;
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 child = spawn(broker.command, broker.args, {
945
- cwd: broker.cwd,
946
- detached: true,
947
- env: {
948
- ...process.env,
949
- OPENTRAY_DAEMON_HOME: paths.homeDir,
950
- OPENTRAY_DAEMON_PACKAGE_VERSION: paths.packageVersion,
951
- OPENTRAY_DAEMON_CLI_ENTRYPOINT: cliEntrypoint,
952
- OPENTRAY_DAEMON_APP_ID: paths.appId,
953
- OPENTRAY_DAEMON_APP_NAME: paths.appName,
954
- OPENTRAY_DAEMON_CALLER_LABEL: paths.callerLabel
955
- },
956
- stdio: resolveBrokerStdio(process.env[DAEMON_STDIO_ENV])
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
- return value === "inherit" ? "inherit" : "ignore";
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 acquireLock(paths.lockFile, lockTimeoutMs);
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(50);
1103
+ await sleep(DAEMON_POLL_INTERVAL_MS);
1041
1104
  }
1042
1105
  return false;
1043
1106
  };
1044
- const waitForReadyFile = async (paths, driver, pid, broker) => {
1045
- for (let attempt = 0; attempt < 40; attempt += 1) {
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 inspect broker stderr`);
1050
- await sleep(50);
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: ${paths.readyFile}`);
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 acquireLock = async (lockFile, timeoutMs) => {
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(`${process.pid}\n`, "utf8");
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 false;
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: {