opentray 0.10.2 → 0.11.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
@@ -30,18 +30,33 @@ Use `alpha-A-B` for alpha packages on the same protocol line. Replace `A-B` with
30
30
  For the first app, call `createTray()` directly. The quickstart stays in one file and does not ask the user to wire a worker or a host loop first:
31
31
 
32
32
  ```ts
33
- import { createTray } from "opentray";
34
-
35
- const tray = await createTray({
33
+ import {
34
+ createTray,
35
+ type CreateTrayHandle,
36
+ type CreateTrayOptions,
37
+ type TrayIcon,
38
+ } from "opentray";
39
+
40
+ const icon: TrayIcon = { "text-only": "OT" };
41
+ let tray: CreateTrayHandle;
42
+ const options: CreateTrayOptions = {
36
43
  id: "com.example.first-app",
37
- icon: { "text-only": "OT" },
38
- menu: { items: [{ type: "item", id: 1, title: "Quit", primaryEvent: true }] },
39
- }, {
44
+ icon,
45
+ menu: {
46
+ items: [
47
+ {
48
+ title: "Quit",
49
+ primaryEvent: true,
50
+ onMenuClick: () => void tray.destroy(),
51
+ },
52
+ ],
53
+ },
54
+ };
55
+
56
+ tray = await createTray(options, {
40
57
  appId: "com.example.first-app",
41
58
  appName: "First App",
42
59
  });
43
-
44
- tray.onMenuClick(({ itemId }) => void (itemId === 1 && tray.destroy()));
45
60
  ```
46
61
 
47
62
  `createTray()` remains the direct tray API when the caller already owns the runtime process shape.
@@ -62,28 +77,55 @@ const tray = await createTray({
62
77
  description: "Background service is running",
63
78
  },
64
79
  menu: {
65
- items: [{ type: "item", id: 1, title: "Open", primaryEvent: true }],
80
+ items: [
81
+ {
82
+ title: "Open",
83
+ primaryEvent: true,
84
+ onMenuClick: () => {
85
+ // Open an app-owned window, command, or extension surface.
86
+ },
87
+ },
88
+ "-",
89
+ ["More", ["Settings", "Quit"]],
90
+ ],
66
91
  },
67
92
  });
68
-
69
- tray.onMenuClick(({ itemId }) => {
70
- if (itemId === 1) {
71
- // Open an app-owned window, command, or extension surface.
72
- }
73
- });
74
93
  ```
75
94
 
76
- Visible tray text belongs to `icon.text`, `icon["text-only"]`, or `icon["icon-text"].text`. The current tray-first API does not export `createSpace()`, `createSurface()`, `resolveDefaultSpace()`, or `TrayHandle.setTitle()`.
95
+ Visible tray text belongs to `icon.text`, `icon["text-only"]`, or `icon["icon-text"].text`. If no visible icon/text survives projection, native tray backends fall back to the runtime `appName`. The current tray-first API does not export `createSpace()`, `createSurface()`, `resolveDefaultSpace()`, or `TrayHandle.setTitle()`.
96
+
97
+ The package re-exports common application-facing types including `CreateTrayOptions`, `TrayIcon`, `TrayMenu`, `TrayTooltip`, `TrayEvent`, and `TrayBoundsResult`. Use those public names instead of deriving SDK shapes with `typeof` in application code.
98
+
99
+ Top-level `createTray(...)` and its returned `setMenu(...)` accept app-facing
100
+ menu shorthand. Lower-level `createClient(...)` remains protocol-only for tools
101
+ that need exact wire shapes.
102
+
103
+ `primaryEvent` is a role on a normal menu item and emits the usual `menuClick`.
104
+ Use `tray.onTrayClick(...)` when you want to listen to raw tray-icon clicks
105
+ without making a menu item the primary route.
77
106
 
78
107
  ## Runtime Ownership
79
108
 
80
109
  OpenTray does not ask developers to create a public broker object. The application process or an application-owned background service imports `opentray`, calls `createTray()`, owns event handlers, and releases the tray when that process exits.
81
110
 
82
- Node platform packages carry the host-loadable runtime artifact at `runtime/opentray_runtime.node`. `opentray/node` exposes `runVisibleRuntimeHost()`, `loadOpenTrayRuntimeBinding()`, and `resolveInstalledRuntimeBindingPath()` for Node-specific host-loop ownership, runtime diagnostics, and packaging checks.
111
+ Platform runtime packages carry the packaged runtime executable at `bin/opentray` or `bin/opentray.exe`.
112
+
113
+ When a local consumer links this workspace, refresh native package artifacts explicitly instead of relying on `pnpm run build` alone:
114
+
115
+ ```bash
116
+ pnpm run npm:cp-bin:runtime
117
+ pnpm run npm:cp-bin:webview
118
+ ```
119
+
120
+ Use `pnpm run npm:cp-bin` to refresh both the packaged runtime executable and the current platform WebView native library. Without `--target`, it compares existing `target/debug` and `target/release` artifacts and copies the newest binary for each kind. Pass `--target debug`, `-t debug`, or `--target release` to build and copy a specific target into the package projection:
83
121
 
84
- By default, `createTray()` routes through the local broker and starts it on first use when needed. `runVisibleRuntimeHost()` remains available from `opentray/node` as an explicit diagnostic path for the visible-binding smoke mode. That diagnostic path still follows the application main-thread law on macOS and the app-owned event-loop law on Windows.
122
+ ```bash
123
+ pnpm run npm:cp-bin
124
+ pnpm run npm:cp-bin -- --target debug
125
+ pnpm run npm:cp-bin:webview -- -t debug
126
+ ```
85
127
 
86
- For protocol/session diagnostics, `createTray(options, { runtime: "headless-binding" })` routes through the Node binding without visible native state. Source-tree diagnostics may use `{ runtime: "visible-binding" }` or `{ runtime: "local-broker" }`, but those transports are not the default package runtime.
128
+ By default, `createTray()` routes through the local runtime host and starts it on first use when needed. The executable host remains the source of truth for tray lifecycle, session cleanup, and native event routing on supported platforms.
87
129
 
88
130
  Runtime options may also carry app identity facts:
89
131
 
@@ -96,8 +138,6 @@ await createTray(options, {
96
138
 
97
139
  The runtime host reports those facts as `appId` and `appName` in `runtime-host-health`; tray icon text, menu labels, and tooltip text remain projection data.
98
140
 
99
- The `opentray/node` subpath exposes binding-resolution, visible host-loop, and binding-transport helpers. Source-tree diagnostics may still use the internal local broker transport, but that transport is not exported as a package contract.
100
-
101
141
  ## Examples
102
142
 
103
143
  Run the quickstart example and the protocol-only example:
@@ -111,17 +151,13 @@ Run the finite source-tree smoke matrix instead of relying on shell expansion fo
111
151
 
112
152
  ```bash
113
153
  pnpm --filter opentray example:matrix
114
- pnpm --filter opentray example:matrix -- --row visible-binding
154
+ pnpm --filter opentray example:matrix -- --row webview-control
115
155
  ```
116
156
 
117
157
  Run human-visible tray and extension examples from a source checkout:
118
158
 
119
159
  ```bash
120
- cargo build -p opentray-runtime-node
121
- pnpm --filter opentray build
122
- bun run scripts/binaries/stage-local.ts --kind runtime --source target/debug/libopentray_runtime_node.dylib
123
- OPENTRAY_EXAMPLE_EXIT_AFTER_MS=1500 pnpm --filter opentray example:visible-binding
124
-
160
+ cargo build -p opentray-bin
125
161
  pnpm --filter opentray example:debug-runtime-tray
126
162
  pnpm --filter opentray example:webview-control
127
163
  pnpm --filter opentray example:tray-panel
@@ -130,4 +166,4 @@ pnpm --filter opentray example:mediaQuery
130
166
  pnpm --filter opentray example:debug-runtime-lynx -- --bundle packages/cli/assets/lynx-review/main.lynx.bundle
131
167
  ```
132
168
 
133
- The example matrix stages the generated Node runtime artifact before `visible-binding`, warms the broker binary before `first-app`, skips unsupported or missing native extension carrier artifacts with an explicit reason, and labels contributor-only extension rows as `extension-debug-runtime` coverage. The first-app example exercises the default package runtime. The visible-binding example is an explicit diagnostic row. The debug-runtime examples exercise the contributor-only source-tree transport for extension and panel iteration. The public API they demonstrate is tray-first: application code creates trays directly and treats background/service lifecycle as application-owned.
169
+ The example matrix warms the runtime executable before `first-app`, skips unsupported or missing native extension carrier artifacts with an explicit reason, and labels contributor-only extension rows as `extension-debug-runtime` coverage. The first-app example exercises the default package runtime. The debug-runtime examples exercise the contributor-only source-tree transport for extension and panel iteration. The public API they demonstrate is tray-first: application code creates trays directly and treats background/service lifecycle as application-owned.
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.mts","names":[],"sources":["../src/cli.ts"],"mappings":";KAMK,UAAA;EACC,IAAA;EAAc,QAAA;AAAA;EACd,IAAA;EAAqB,OAAA;AAAA;AAAA,cAEd,eAAA,GAAmB,IAAA,eAAiB,UAMhD;AAAA,cAEY,MAAA,GAAgB,IAAA,eAAiB,OAAO;AAAA,cAuBxC,eAAA,GACX,aAAA,sBACA,UAAkB"}
1
+ {"version":3,"file":"cli.d.mts","names":[],"sources":["../src/cli.ts"],"mappings":";KAMK,UAAA;EACC,IAAA;EAAc,QAAA;AAAA;EACd,IAAA;EAAqB,OAAA;AAAA;AAAA,cAEd,eAAA,GAAmB,IAAA,eAAiB,UAMhD;AAAA,cAEY,MAAA,GAAgB,IAAA,eAAiB,OAAO;AAAA,cAsBxC,eAAA,GACX,aAAA,sBACA,UAAkB"}
package/dist/cli.mjs CHANGED
@@ -22,12 +22,11 @@ const runCli = async (argv) => {
22
22
  const printHelp = (unsupportedCommand) => {
23
23
  if (unsupportedCommand !== void 0) console.error(`Unsupported opentray command: ${unsupportedCommand}`);
24
24
  console.error([
25
- "OpenTray v0.9 does not expose daemon lifecycle commands.",
25
+ "OpenTray does not expose daemon lifecycle commands.",
26
26
  "Create trays from an app-owned process with:",
27
27
  "",
28
28
  " import { createTray } from \"opentray\";",
29
29
  "",
30
- "Node runtime diagnostics live under the opentray/node binding helpers.",
31
30
  "Source-tree visual diagnostics live in packages/cli/examples."
32
31
  ].join("\n"));
33
32
  };
package/dist/cli.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { realpathSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\n\nconst cliModulePath = fileURLToPath(import.meta.url);\n\ntype CliCommand =\n | { type: \"help\"; exitCode: 0 }\n | { type: \"unsupported\"; command: string | undefined };\n\nexport const parseCliCommand = (argv: string[]): CliCommand => {\n const [command] = argv;\n if (command === undefined || command === \"--help\" || command === \"-h\") {\n return { type: \"help\", exitCode: 0 };\n }\n return { type: \"unsupported\", command };\n};\n\nexport const runCli = async (argv: string[]): Promise<number> => {\n const command = parseCliCommand(argv);\n printHelp(command.type === \"unsupported\" ? command.command : undefined);\n return command.type === \"help\" ? command.exitCode : 1;\n};\n\nconst printHelp = (unsupportedCommand?: string): void => {\n if (unsupportedCommand !== undefined) {\n console.error(`Unsupported opentray command: ${unsupportedCommand}`);\n }\n console.error(\n [\n \"OpenTray v0.9 does not expose daemon lifecycle commands.\",\n \"Create trays from an app-owned process with:\",\n \"\",\n ' import { createTray } from \"opentray\";',\n \"\",\n \"Node runtime diagnostics live under the opentray/node binding helpers.\",\n \"Source-tree visual diagnostics live in packages/cli/examples.\",\n ].join(\"\\n\")\n );\n};\n\nexport const isCliEntrypoint = (\n argvEntryPath: string | undefined,\n modulePath: string\n): boolean => {\n if (argvEntryPath === undefined) return false;\n try {\n return realpathSync(argvEntryPath) === realpathSync(modulePath);\n } catch {\n return argvEntryPath === modulePath;\n }\n};\n\nif (isCliEntrypoint(process.argv[1], cliModulePath)) {\n runCli(process.argv.slice(2)).then((code) => {\n process.exitCode = code;\n });\n}\n"],"mappings":";;;;AAIA,MAAM,gBAAgB,cAAc,OAAO,KAAK,GAAG;AAMnD,MAAa,mBAAmB,SAA+B;CAC7D,MAAM,CAAC,WAAW;CAClB,IAAI,YAAY,KAAA,KAAa,YAAY,YAAY,YAAY,MAC/D,OAAO;EAAE,MAAM;EAAQ,UAAU;CAAE;CAErC,OAAO;EAAE,MAAM;EAAe;CAAQ;AACxC;AAEA,MAAa,SAAS,OAAO,SAAoC;CAC/D,MAAM,UAAU,gBAAgB,IAAI;CACpC,UAAU,QAAQ,SAAS,gBAAgB,QAAQ,UAAU,KAAA,CAAS;CACtE,OAAO,QAAQ,SAAS,SAAS,QAAQ,WAAW;AACtD;AAEA,MAAM,aAAa,uBAAsC;CACvD,IAAI,uBAAuB,KAAA,GACzB,QAAQ,MAAM,iCAAiC,oBAAoB;CAErE,QAAQ,MACN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;CACF,EAAE,KAAK,IAAI,CACb;AACF;AAEA,MAAa,mBACX,eACA,eACY;CACZ,IAAI,kBAAkB,KAAA,GAAW,OAAO;CACxC,IAAI;EACF,OAAO,aAAa,aAAa,MAAM,aAAa,UAAU;CAChE,QAAQ;EACN,OAAO,kBAAkB;CAC3B;AACF;AAEA,IAAI,gBAAgB,QAAQ,KAAK,IAAI,aAAa,GAChD,OAAO,QAAQ,KAAK,MAAM,CAAC,CAAC,EAAE,MAAM,SAAS;CAC3C,QAAQ,WAAW;AACrB,CAAC"}
1
+ {"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { realpathSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\n\nconst cliModulePath = fileURLToPath(import.meta.url);\n\ntype CliCommand =\n | { type: \"help\"; exitCode: 0 }\n | { type: \"unsupported\"; command: string | undefined };\n\nexport const parseCliCommand = (argv: string[]): CliCommand => {\n const [command] = argv;\n if (command === undefined || command === \"--help\" || command === \"-h\") {\n return { type: \"help\", exitCode: 0 };\n }\n return { type: \"unsupported\", command };\n};\n\nexport const runCli = async (argv: string[]): Promise<number> => {\n const command = parseCliCommand(argv);\n printHelp(command.type === \"unsupported\" ? command.command : undefined);\n return command.type === \"help\" ? command.exitCode : 1;\n};\n\nconst printHelp = (unsupportedCommand?: string): void => {\n if (unsupportedCommand !== undefined) {\n console.error(`Unsupported opentray command: ${unsupportedCommand}`);\n }\n console.error(\n [\n \"OpenTray does not expose daemon lifecycle commands.\",\n \"Create trays from an app-owned process with:\",\n \"\",\n ' import { createTray } from \"opentray\";',\n \"\",\n \"Source-tree visual diagnostics live in packages/cli/examples.\",\n ].join(\"\\n\")\n );\n};\n\nexport const isCliEntrypoint = (\n argvEntryPath: string | undefined,\n modulePath: string\n): boolean => {\n if (argvEntryPath === undefined) return false;\n try {\n return realpathSync(argvEntryPath) === realpathSync(modulePath);\n } catch {\n return argvEntryPath === modulePath;\n }\n};\n\nif (isCliEntrypoint(process.argv[1], cliModulePath)) {\n runCli(process.argv.slice(2)).then((code) => {\n process.exitCode = code;\n });\n}\n"],"mappings":";;;;AAIA,MAAM,gBAAgB,cAAc,OAAO,KAAK,GAAG;AAMnD,MAAa,mBAAmB,SAA+B;CAC7D,MAAM,CAAC,WAAW;CAClB,IAAI,YAAY,KAAA,KAAa,YAAY,YAAY,YAAY,MAC/D,OAAO;EAAE,MAAM;EAAQ,UAAU;CAAE;CAErC,OAAO;EAAE,MAAM;EAAe;CAAQ;AACxC;AAEA,MAAa,SAAS,OAAO,SAAoC;CAC/D,MAAM,UAAU,gBAAgB,IAAI;CACpC,UAAU,QAAQ,SAAS,gBAAgB,QAAQ,UAAU,KAAA,CAAS;CACtE,OAAO,QAAQ,SAAS,SAAS,QAAQ,WAAW;AACtD;AAEA,MAAM,aAAa,uBAAsC;CACvD,IAAI,uBAAuB,KAAA,GACzB,QAAQ,MAAM,iCAAiC,oBAAoB;CAErE,QAAQ,MACN;EACE;EACA;EACA;EACA;EACA;EACA;CACF,EAAE,KAAK,IAAI,CACb;AACF;AAEA,MAAa,mBACX,eACA,eACY;CACZ,IAAI,kBAAkB,KAAA,GAAW,OAAO;CACxC,IAAI;EACF,OAAO,aAAa,aAAa,MAAM,aAAa,UAAU;CAChE,QAAQ;EACN,OAAO,kBAAkB;CAC3B;AACF;AAEA,IAAI,gBAAgB,QAAQ,KAAK,IAAI,aAAa,GAChD,OAAO,QAAQ,KAAK,MAAM,CAAC,CAAC,EAAE,MAAM,SAAS;CAC3C,QAAQ,WAAW;AACrB,CAAC"}
package/dist/index.d.mts CHANGED
@@ -1,10 +1,130 @@
1
- import { C as TrayHandle, E as createTrayHandle, S as TrayExtensionMountSpec, T as createInitFrame, _ as OpenTrayTransport, b as TrayExtension, d as EventfulTrayHandle, f as ExtensionLoadOptions, g as OpenTrayEventSource, h as OpenTrayEventFrame, m as OpenTrayConnection, n as OpenTrayRuntimeBinding, p as OpenTrayClient, u as CreateClientOptions, v as TrayEventByType, w as createClient, x as TrayExtensionContext, y as TrayEventType } from "./native-runtime-Cn8BaffH.mjs";
2
- import { BrokerEndpointIdentity, BrokerEndpointIdentityOptions, PROTOCOL_VERSION, TrayOptions, createBrokerEndpointIdentity, formatBrokerEndpointName, formatBrokerStateRoot, formatUnixSocketPath, formatWindowsPipeName, isSupportedProtocolVersion } from "@opentray/spec";
1
+ import { AppId, AppIdentity, AppOptions, AppRef, BrokerEndpointIdentity, BrokerEndpointIdentityOptions, ClientFrame, ClientFrame as ClientFrame$1, ClientRequestFrame, ClientRequestFrame as ClientRequestFrame$1, DarwinIcon, DarwinIconText, 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";
3
2
 
3
+ //#region src/client.d.ts
4
+ interface OpenTrayTransport {
5
+ request(frame: ClientRequestFrame$1): Promise<ServerFrame$1>;
6
+ }
7
+ type OpenTrayEventFrame = Extract<ServerFrame$1, {
8
+ type: "event" | "ext-event";
9
+ }>;
10
+ interface OpenTrayEventSource {
11
+ onEvent(listener: (frame: OpenTrayEventFrame) => void): () => void;
12
+ }
13
+ interface OpenTrayConnection extends OpenTrayTransport, OpenTrayEventSource {}
14
+ interface OpenTrayClient {
15
+ createTray(options: TrayOptions$1): Promise<TrayHandle>;
16
+ }
17
+ interface OpenTrayEventfulClient extends OpenTrayClient {
18
+ createTray(options: TrayOptions$1): Promise<EventfulTrayHandle>;
19
+ }
20
+ interface TrayHandle {
21
+ trayId: TrayId$1;
22
+ getBounds(): Promise<TrayBoundsResult$1>;
23
+ setMenu(menu: Menu$1): Promise<void>;
24
+ setTooltip(tooltip: Tooltip$1): Promise<void>;
25
+ setIcon(icon: Icon$1): Promise<void>;
26
+ loadExtension(options: ExtensionLoadOptions): Promise<void>;
27
+ commandExtension(ext: string, data: unknown): Promise<void>;
28
+ requestExtension(ext: string, data: unknown): Promise<ExtensionEnvelope$1[]>;
29
+ extend<TCapability extends object, TOptions = undefined>(extension: TrayExtension<TCapability, TOptions>, options?: TOptions): TrayHandle & TCapability;
30
+ destroy(): Promise<void>;
31
+ }
32
+ type TrayScopedEvent = Exclude<TrayEvent$1, {
33
+ type: "ready";
34
+ }>;
35
+ type TrayEventType = TrayScopedEvent["type"];
36
+ type TrayEventByType<TEvent extends TrayEventType> = Extract<TrayScopedEvent, {
37
+ type: TEvent;
38
+ }>;
39
+ interface EventfulTrayHandle extends TrayHandle {
40
+ extend<TCapability extends object, TOptions = undefined>(extension: TrayExtension<TCapability, TOptions>, options?: TOptions): EventfulTrayHandle & TCapability;
41
+ listenExtension<TData = unknown>(ext: string, handler: (event: ExtensionEnvelope$1<TData>) => void): () => void;
42
+ listen<TEvent extends TrayEventType>(event: TEvent, handler: (event: TrayEventByType<TEvent>) => void): () => void;
43
+ onMenuClick(handler: (event: TrayEventByType<"menuClick">) => void): () => void;
44
+ onTrayClick(handler: (event: TrayEventByType<"trayClick">) => void): () => void;
45
+ onTrayDoubleClick(handler: (event: TrayEventByType<"trayDoubleClick">) => void): () => void;
46
+ }
47
+ interface ExtensionLoadOptions {
48
+ name: string;
49
+ path: string;
50
+ mountId?: string;
51
+ }
52
+ interface TrayExtensionMountSpec {
53
+ name?: string;
54
+ path?: string;
55
+ mountId?: string;
56
+ }
57
+ interface TrayExtensionContext {
58
+ readonly appId: string;
59
+ readonly trayId: TrayId$1;
60
+ readonly name: string;
61
+ readonly path: string;
62
+ readonly mountId: string;
63
+ ensureLoaded(): Promise<void>;
64
+ command(data: unknown): Promise<void>;
65
+ request(data: unknown): Promise<ExtensionEnvelope$1[]>;
66
+ }
67
+ interface TrayExtension<TCapability extends object, TOptions = undefined> {
68
+ readonly name: string;
69
+ readonly path: string;
70
+ resolveMount?(options: TOptions | undefined): TrayExtensionMountSpec;
71
+ extend(tray: TrayHandle, context: TrayExtensionContext, options: TOptions | undefined): TCapability;
72
+ }
73
+ declare const createInitFrame: (clientVersion: string) => ClientFrame$1;
74
+ interface CreateClientOptions {
75
+ requestIdPrefix?: string;
76
+ }
77
+ declare function createClient(transport: OpenTrayConnection, options?: CreateClientOptions): OpenTrayEventfulClient;
78
+ declare function createClient(transport: OpenTrayTransport, options?: CreateClientOptions): OpenTrayClient;
79
+ declare function createTrayHandle(transport: OpenTrayConnection, appId: string, trayId: TrayId$1, nextRequestId?: () => RequestId): EventfulTrayHandle;
80
+ declare function createTrayHandle(transport: OpenTrayTransport, appId: string, trayId: TrayId$1, nextRequestId?: () => RequestId): TrayHandle;
81
+ //#endregion
82
+ //#region src/menu-input.d.ts
83
+ type CreateTrayMenuClickHandler = (event: TrayEventByType<"menuClick">) => void;
84
+ /** App-facing menu input accepted by top-level createTray and setMenu. */
85
+ type CreateTrayMenu = {
86
+ items: CreateTrayMenuItem[];
87
+ };
88
+ /** Menu item shorthand normalized to protocol MenuItem before runtime transport. */
89
+ type CreateTrayMenuItem = string | readonly [title: string, items: readonly CreateTrayMenuItem[]] | CreateTrayItem | CreateTrayCheckItem | CreateTrayRadioItem | CreateTraySeparatorItem | CreateTraySubmenuItem;
90
+ type CreateTrayItem = {
91
+ type?: "item";
92
+ id?: MenuItemId$1;
93
+ title: string;
94
+ primaryEvent?: boolean;
95
+ enabled?: boolean;
96
+ shortcut?: string;
97
+ onMenuClick?: CreateTrayMenuClickHandler;
98
+ };
99
+ type CreateTrayCheckItem = {
100
+ type: "check";
101
+ id?: MenuItemId$1;
102
+ title: string;
103
+ enabled?: boolean;
104
+ checked?: boolean;
105
+ onMenuClick?: CreateTrayMenuClickHandler;
106
+ };
107
+ type CreateTrayRadioItem = {
108
+ type: "radio";
109
+ id?: MenuItemId$1;
110
+ title: string;
111
+ enabled?: boolean;
112
+ checked?: boolean;
113
+ group: number;
114
+ onMenuClick?: CreateTrayMenuClickHandler;
115
+ };
116
+ type CreateTraySeparatorItem = {
117
+ type: "separator";
118
+ };
119
+ type CreateTraySubmenuItem = {
120
+ type: "submenu";
121
+ title: string;
122
+ enabled?: boolean;
123
+ items: readonly CreateTrayMenuItem[];
124
+ };
125
+ //#endregion
4
126
  //#region src/sdk.d.ts
5
- type OpenTrayRuntimeMode = "visible-binding" | "local-broker" | "headless-binding";
6
127
  interface OpenTrayRuntimeOptions {
7
- runtime?: OpenTrayRuntimeMode;
8
128
  endpoint?: string;
9
129
  homeDir?: string;
10
130
  packageVersion?: string;
@@ -13,9 +133,21 @@ interface OpenTrayRuntimeOptions {
13
133
  appId?: string;
14
134
  appName?: string;
15
135
  autoStart?: boolean;
16
- binding?: OpenTrayRuntimeBinding;
17
136
  }
18
- declare const createTray: (options: TrayOptions, runtimeOptions?: OpenTrayRuntimeOptions) => Promise<EventfulTrayHandle>;
137
+ /** App-facing tray options accepted by top-level createTray. */
138
+ interface CreateTrayOptions {
139
+ id: string;
140
+ tooltip?: Tooltip$1;
141
+ icon?: Icon$1;
142
+ menu?: CreateTrayMenu;
143
+ }
144
+ /** Tray handle returned by top-level createTray with ergonomic setMenu input. */
145
+ interface CreateTrayHandle extends Omit<EventfulTrayHandle, "setMenu" | "extend" | "destroy"> {
146
+ setMenu(menu: CreateTrayMenu): Promise<void>;
147
+ extend<TCapability extends object, TOptions = undefined>(extension: TrayExtension<TCapability, TOptions>, options?: TOptions): CreateTrayHandle & TCapability;
148
+ destroy(): Promise<void>;
149
+ }
150
+ declare const createTray: (options: CreateTrayOptions, runtimeOptions?: OpenTrayRuntimeOptions) => Promise<CreateTrayHandle>;
19
151
  //#endregion
20
- export { type BrokerEndpointIdentity, type BrokerEndpointIdentityOptions, type CreateClientOptions, type EventfulTrayHandle, type ExtensionLoadOptions, type OpenTrayClient, type OpenTrayConnection, type OpenTrayEventFrame, type OpenTrayEventSource, type OpenTrayRuntimeOptions, type OpenTrayTransport, PROTOCOL_VERSION, type TrayEventByType, type TrayEventType, type TrayExtension, type TrayExtensionContext, type TrayExtensionMountSpec, type TrayHandle, createBrokerEndpointIdentity, createClient, createInitFrame, createTray, createTrayHandle, formatBrokerEndpointName, formatBrokerStateRoot, formatUnixSocketPath, formatWindowsPipeName, isSupportedProtocolVersion };
152
+ export { 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, type DarwinIcon, type DarwinIconText, type EventfulTrayHandle, type ExtensionEnvelope, type ExtensionLoadOptions, type Icon, type IconCandidates, type IconImage, type IconText, type LinuxIcon, type Menu, type MenuItem, type MenuItemId, type MouseButton, type OpenTrayClient, type OpenTrayConnection, type OpenTrayEventFrame, type OpenTrayEventSource, type OpenTrayRuntimeOptions, type OpenTrayTransport, PROTOCOL_VERSION, type Rect, 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 };
21
153
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/sdk.ts"],"mappings":";;;;KAOY,mBAAA;AAAA,UAKK,sBAAA;EACf,OAAA,GAAU,mBAAA;EACV,QAAA;EACA,OAAA;EACA,cAAA;EACA,eAAA;EACA,aAAA;EACA,KAAA;EACA,OAAA;EACA,SAAA;EACA,OAAA,GAAU,sBAAsB;AAAA;AAAA,cAGrB,UAAA,GACX,OAAA,EAAS,WAAA,EACT,cAAA,GAAgB,sBAAA,KACf,OAAA,CAAQ,kBAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/client.ts","../src/menu-input.ts","../src/sdk.ts"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -1,4 +1,3 @@
1
- import { t as createRuntimeBindingTransport } from "./runtime-binding-transport-UPunusBT.mjs";
2
1
  import { PROTOCOL_VERSION, PROTOCOL_VERSION as PROTOCOL_VERSION$1, createBrokerEndpointIdentity, createBrokerEndpointIdentity as createBrokerEndpointIdentity$1, formatBrokerEndpointName, formatBrokerStateRoot, formatUnixSocketPath, formatUnixSocketPath as formatUnixSocketPath$1, formatWindowsPipeName, formatWindowsPipeName as formatWindowsPipeName$1, isSupportedProtocolVersion, parseServerFrame, sanitizeCallerLabel } from "@opentray/spec";
3
2
  import { createConnection } from "node:net";
4
3
  import { homedir } from "node:os";
@@ -106,7 +105,7 @@ function createTrayHandle(transport, appId, trayId, nextRequestId = createReques
106
105
  return expectResponse(response, requestId, "ext-command-result").events;
107
106
  },
108
107
  extend(extension, options) {
109
- const context = createTrayExtensionContext(handle, extension, options, nextMountId);
108
+ const context = createTrayExtensionContext(handle, appId, extension, options, nextMountId);
110
109
  const capability = extension.extend(handle, context, options);
111
110
  return Object.assign({}, handle, capability);
112
111
  },
@@ -156,7 +155,7 @@ const attachEventfulTrayHandle = (handle, source, appId, nextMountId) => {
156
155
  return listen("trayDoubleClick", handler);
157
156
  },
158
157
  extend(extension, options) {
159
- const context = createTrayExtensionContext(eventful, extension, options, nextMountId);
158
+ const context = createTrayExtensionContext(eventful, appId, extension, options, nextMountId);
160
159
  const capability = extension.extend(eventful, context, options);
161
160
  return Object.assign({}, eventful, capability);
162
161
  }
@@ -172,7 +171,7 @@ const resolveDefaultAppRef = async (transport, nextRequestId) => {
172
171
  requestId
173
172
  }), requestId, "default-app").app;
174
173
  };
175
- const createTrayExtensionContext = (tray, extension, options, nextMountId) => {
174
+ const createTrayExtensionContext = (tray, appId, extension, options, nextMountId) => {
176
175
  const mount = extension.resolveMount?.(options) ?? {};
177
176
  const name = mount.name ?? extension.name;
178
177
  const path = mount.path ?? extension.path;
@@ -190,6 +189,8 @@ const createTrayExtensionContext = (tray, extension, options, nextMountId) => {
190
189
  await loadPromise;
191
190
  };
192
191
  return {
192
+ appId,
193
+ trayId: tray.trayId,
193
194
  name,
194
195
  path,
195
196
  mountId,
@@ -307,7 +308,7 @@ const resolveBrokerCommand = async (paths, options = {}) => {
307
308
  if (explicit !== void 0 && explicit.length > 0) return commandForBinary(explicit, paths);
308
309
  const sourceDir = options.sourceDir ?? dirname(fileURLToPath(sourceUrl));
309
310
  const workspaceRoot = await (options.findWorkspaceRoot ?? findWorkspaceRoot)(sourceDir);
310
- if (workspaceRoot !== void 0) return commandForBinary(await (options.ensureDevBrokerBinary ?? ensureDevBrokerBinary)(workspaceRoot), paths, workspaceRoot);
311
+ if (workspaceRoot !== void 0) return commandForBinary(await (options.ensureDevBrokerBinary ?? ensureDevBrokerBinary)(workspaceRoot), paths);
311
312
  const platform = options.platform ?? process.platform;
312
313
  const arch = options.arch ?? process.arch;
313
314
  const target = resolveBrokerNativeTarget(platform, arch);
@@ -758,22 +759,176 @@ const resolveCliEntrypoint = () => {
758
759
  return fileURLToPath(new URL(`./${suffix}`, import.meta.url));
759
760
  };
760
761
  //#endregion
762
+ //#region src/menu-input.ts
763
+ const normalizeCreateTrayMenu = (menu) => {
764
+ const state = {
765
+ usedIds: collectExplicitMenuIds(menu.items),
766
+ nextId: 1,
767
+ handlers: /* @__PURE__ */ new Map()
768
+ };
769
+ return {
770
+ menu: { items: menu.items.map((item) => normalizeCreateTrayMenuItem(item, state)) },
771
+ handlers: state.handlers
772
+ };
773
+ };
774
+ const normalizeCreateTrayMenuItem = (item, state) => {
775
+ if (typeof item === "string") {
776
+ if (isSeparatorShortcut(item)) return { type: "separator" };
777
+ return normalizePlainItem({ title: item }, state);
778
+ }
779
+ if (isSubmenuTuple(item)) {
780
+ const [title, items] = item;
781
+ return {
782
+ type: "submenu",
783
+ title,
784
+ items: items.map((child) => normalizeCreateTrayMenuItem(child, state))
785
+ };
786
+ }
787
+ switch (item.type) {
788
+ case void 0:
789
+ case "item": return normalizePlainItem(item, state);
790
+ case "check": return normalizeCheckItem(item, state);
791
+ case "radio": return normalizeRadioItem(item, state);
792
+ case "separator": return item;
793
+ case "submenu": return {
794
+ type: "submenu",
795
+ title: item.title,
796
+ ...item.enabled === void 0 ? {} : { enabled: item.enabled },
797
+ items: item.items.map((child) => normalizeCreateTrayMenuItem(child, state))
798
+ };
799
+ }
800
+ throw new Error("unsupported menu item");
801
+ };
802
+ const normalizeClickableItem = (item, state) => {
803
+ const id = item.id ?? allocateMenuItemId(state);
804
+ registerClickId(state, id);
805
+ if (item.onMenuClick !== void 0) {
806
+ const handlers = state.handlers.get(id) ?? [];
807
+ handlers.push(item.onMenuClick);
808
+ state.handlers.set(id, handlers);
809
+ }
810
+ return id;
811
+ };
812
+ const normalizePlainItem = (item, state) => {
813
+ return {
814
+ type: "item",
815
+ id: normalizeClickableItem(item, state),
816
+ title: item.title,
817
+ ...item.primaryEvent === void 0 ? {} : { primaryEvent: item.primaryEvent },
818
+ ...item.enabled === void 0 ? {} : { enabled: item.enabled },
819
+ ...item.shortcut === void 0 ? {} : { shortcut: item.shortcut }
820
+ };
821
+ };
822
+ const normalizeCheckItem = (item, state) => {
823
+ return {
824
+ type: "check",
825
+ id: normalizeClickableItem(item, state),
826
+ title: item.title,
827
+ ...item.enabled === void 0 ? {} : { enabled: item.enabled },
828
+ ...item.checked === void 0 ? {} : { checked: item.checked }
829
+ };
830
+ };
831
+ const normalizeRadioItem = (item, state) => {
832
+ return {
833
+ type: "radio",
834
+ id: normalizeClickableItem(item, state),
835
+ title: item.title,
836
+ ...item.enabled === void 0 ? {} : { enabled: item.enabled },
837
+ ...item.checked === void 0 ? {} : { checked: item.checked },
838
+ group: item.group
839
+ };
840
+ };
841
+ const allocateMenuItemId = (state) => {
842
+ while (state.usedIds.has(state.nextId)) state.nextId += 1;
843
+ const id = state.nextId;
844
+ state.usedIds.add(id);
845
+ state.nextId += 1;
846
+ return id;
847
+ };
848
+ const collectExplicitMenuIds = (items) => {
849
+ const ids = /* @__PURE__ */ new Set();
850
+ for (const item of items) {
851
+ if (typeof item === "string") continue;
852
+ if (isSubmenuTuple(item)) {
853
+ for (const id of collectExplicitMenuIds(item[1])) ids.add(id);
854
+ continue;
855
+ }
856
+ switch (item.type) {
857
+ case void 0:
858
+ case "item":
859
+ case "check":
860
+ case "radio":
861
+ if (item.id !== void 0) ids.add(item.id);
862
+ break;
863
+ case "submenu":
864
+ for (const id of collectExplicitMenuIds(item.items)) ids.add(id);
865
+ break;
866
+ case "separator": break;
867
+ }
868
+ }
869
+ return ids;
870
+ };
871
+ const isSeparatorShortcut = (value) => /^-+$/.test(value);
872
+ const isSubmenuTuple = (item) => Array.isArray(item);
873
+ const registerClickId = (state, id) => {
874
+ const seen = state.seenClickIds ?? /* @__PURE__ */ new Set();
875
+ if (seen.has(id)) throw new Error(`duplicate menu item id: ${id}`);
876
+ seen.add(id);
877
+ state.seenClickIds = seen;
878
+ };
879
+ //#endregion
761
880
  //#region src/sdk.ts
762
881
  const createTray = async (options, runtimeOptions = {}) => {
763
- const bindingTransportOptions = {
764
- ...runtimeOptions.binding === void 0 ? {} : { binding: runtimeOptions.binding },
765
- ...runtimeOptions.packageVersion === void 0 ? {} : { packageVersion: runtimeOptions.packageVersion },
766
- ...runtimeOptions.clientVersion === void 0 ? {} : { clientVersion: runtimeOptions.clientVersion },
767
- ...runtimeOptions.protocolVersion === void 0 ? {} : { protocolVersion: runtimeOptions.protocolVersion },
768
- ...runtimeOptions.appId === void 0 ? {} : { appId: runtimeOptions.appId },
769
- ...runtimeOptions.appName === void 0 ? {} : { appName: runtimeOptions.appName }
882
+ const connection = await connectLocalBroker(runtimeOptions);
883
+ const normalized = normalizeCreateTrayOptions(options);
884
+ const tray = await createClient(connection).createTray(normalized.options);
885
+ return wrapCreateTrayHandle(tray, { menuUnsubscribe: bindMenuClickHandlers(tray, normalized.menuHandlers) });
886
+ };
887
+ const normalizeCreateTrayOptions = (options) => {
888
+ const protocolOptions = {
889
+ id: options.id,
890
+ ...options.tooltip === void 0 ? {} : { tooltip: options.tooltip },
891
+ ...options.icon === void 0 ? {} : { icon: options.icon }
770
892
  };
771
- const runtime = runtimeOptions.runtime ?? "local-broker";
772
- return createClient(runtime === "local-broker" ? await connectLocalBroker(runtimeOptions) : await createRuntimeBindingTransport({
773
- ...bindingTransportOptions,
774
- kind: runtime === "headless-binding" ? "headless" : "visible"
775
- })).createTray(options);
893
+ if (options.menu === void 0) return {
894
+ options: protocolOptions,
895
+ menuHandlers: /* @__PURE__ */ new Map()
896
+ };
897
+ const normalized = normalizeCreateTrayMenu(options.menu);
898
+ return {
899
+ options: {
900
+ ...protocolOptions,
901
+ menu: normalized.menu
902
+ },
903
+ menuHandlers: normalized.handlers
904
+ };
905
+ };
906
+ const wrapCreateTrayHandle = (tray, state) => {
907
+ return {
908
+ ...tray,
909
+ async setMenu(menu) {
910
+ const normalized = normalizeCreateTrayMenu(menu);
911
+ await tray.setMenu(normalized.menu);
912
+ state.menuUnsubscribe();
913
+ state.menuUnsubscribe = bindMenuClickHandlers(tray, normalized.handlers);
914
+ },
915
+ extend(extension, options) {
916
+ return wrapCreateTrayHandle(tray.extend(extension, options), state);
917
+ },
918
+ async destroy() {
919
+ state.menuUnsubscribe();
920
+ state.menuUnsubscribe = noop;
921
+ await tray.destroy();
922
+ }
923
+ };
924
+ };
925
+ const bindMenuClickHandlers = (tray, handlers) => {
926
+ if (handlers.size === 0) return noop;
927
+ return tray.onMenuClick((event) => {
928
+ for (const handler of handlers.get(event.itemId) ?? []) handler(event);
929
+ });
776
930
  };
931
+ const noop = () => {};
777
932
  //#endregion
778
933
  export { PROTOCOL_VERSION, createBrokerEndpointIdentity, createClient, createInitFrame, createTray, createTrayHandle, formatBrokerEndpointName, formatBrokerStateRoot, formatUnixSocketPath, formatWindowsPipeName, isSupportedProtocolVersion };
779
934