@opentray/spec 0.0.0 → 0.2.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
@@ -5,7 +5,26 @@ Shared TypeScript protocol and contract package for OpenTray.
5
5
  ## Role
6
6
 
7
7
  - Define JSON-RPC payload shapes.
8
+ - Define broker protocol version and endpoint identity helpers.
8
9
  - Define public `Surface`, `Tray`, `Lease`, and extension contract types.
9
10
  - Keep protocol types reusable by the `opentray` package and official extensions.
10
11
 
11
12
  This package must stay platform-neutral and must not import native implementation packages.
13
+
14
+ ## Example
15
+
16
+ Run a protocol parser example that shows successful server-frame parsing and malformed-frame rejection:
17
+
18
+ ```bash
19
+ pnpm --filter @opentray/spec example:parse
20
+ ```
21
+
22
+ Endpoint identity is version-scoped during the current unstable broker stage:
23
+
24
+ ```ts
25
+ import { createBrokerEndpointIdentity, formatUnixSocketPath } from "@opentray/spec";
26
+
27
+ const identity = createBrokerEndpointIdentity({ packageVersion: "0.1.0" });
28
+ console.log(formatUnixSocketPath("~", identity));
29
+ // ~/.opentray/0.1.0/opentray-p1.sock
30
+ ```
@@ -0,0 +1,251 @@
1
+ //#region src/index.d.ts
2
+ type LeaseId = string;
3
+ type RequestId = string;
4
+ type SurfaceId = string;
5
+ type TrayId = string;
6
+ type MenuItemId = number;
7
+ declare const PROTOCOL_VERSION = 1;
8
+ interface BrokerEndpointIdentity {
9
+ packageVersion: string;
10
+ protocolVersion: number;
11
+ }
12
+ interface BrokerEndpointIdentityOptions {
13
+ packageVersion: string;
14
+ protocolVersion?: number;
15
+ }
16
+ declare const createBrokerEndpointIdentity: ({
17
+ packageVersion,
18
+ protocolVersion
19
+ }: BrokerEndpointIdentityOptions) => BrokerEndpointIdentity;
20
+ declare const isSupportedProtocolVersion: (protocolVersion: number) => boolean;
21
+ declare const formatBrokerEndpointName: (identity: BrokerEndpointIdentity) => string;
22
+ declare const formatBrokerStateRoot: (homeDir: string, identity: BrokerEndpointIdentity) => string;
23
+ declare const formatUnixSocketPath: (homeDir: string, identity: BrokerEndpointIdentity) => string;
24
+ declare const formatWindowsPipeName: (identity: BrokerEndpointIdentity) => string;
25
+ interface SurfaceOptions {
26
+ appId: string;
27
+ title?: string;
28
+ icon?: Icon;
29
+ default?: boolean;
30
+ }
31
+ interface SurfaceRef {
32
+ surfaceId: SurfaceId;
33
+ appId: string;
34
+ }
35
+ interface TrayOptions {
36
+ trayId?: TrayId;
37
+ appId?: string;
38
+ title?: string;
39
+ tooltip?: Tooltip;
40
+ icon: Icon;
41
+ menu?: Menu;
42
+ }
43
+ interface Tooltip {
44
+ title: string;
45
+ description: string;
46
+ }
47
+ interface Menu {
48
+ items: MenuItem[];
49
+ }
50
+ type MenuItem = {
51
+ type: "item";
52
+ id: MenuItemId;
53
+ title: string;
54
+ enabled?: boolean;
55
+ shortcut?: string;
56
+ } | {
57
+ type: "check";
58
+ id: MenuItemId;
59
+ title: string;
60
+ enabled?: boolean;
61
+ checked?: boolean;
62
+ } | {
63
+ type: "radio";
64
+ id: MenuItemId;
65
+ title: string;
66
+ enabled?: boolean;
67
+ checked?: boolean;
68
+ group: number;
69
+ } | {
70
+ type: "separator";
71
+ } | {
72
+ type: "submenu";
73
+ title: string;
74
+ enabled?: boolean;
75
+ items: MenuItem[];
76
+ };
77
+ type Icon = {
78
+ type: "rgba";
79
+ data: Uint8Array | number[];
80
+ width: number;
81
+ height: number;
82
+ } | {
83
+ type: "encoded";
84
+ data: Uint8Array | number[];
85
+ } | {
86
+ type: "file";
87
+ path: string;
88
+ };
89
+ interface Rect {
90
+ x: number;
91
+ y: number;
92
+ width: number;
93
+ height: number;
94
+ }
95
+ type MouseButton = "left" | "right" | "middle";
96
+ type TrayEvent = {
97
+ type: "ready";
98
+ surfaceId: SurfaceId;
99
+ } | {
100
+ type: "menuClick";
101
+ surfaceId: SurfaceId;
102
+ trayId: TrayId;
103
+ itemId: MenuItemId;
104
+ } | {
105
+ type: "trayClick";
106
+ surfaceId: SurfaceId;
107
+ button: MouseButton;
108
+ x: number;
109
+ y: number;
110
+ } | {
111
+ type: "trayDoubleClick";
112
+ surfaceId: SurfaceId;
113
+ button: MouseButton;
114
+ x: number;
115
+ y: number;
116
+ };
117
+ interface ExtensionScope {
118
+ surfaceId: SurfaceId;
119
+ trayId?: TrayId;
120
+ ext: string;
121
+ }
122
+ interface ExtensionEnvelope<TData = unknown> {
123
+ scope: ExtensionScope;
124
+ data: TData;
125
+ }
126
+ interface DaemonSessionHealth {
127
+ sessionId: number;
128
+ leaseId?: LeaseId;
129
+ initialized: boolean;
130
+ }
131
+ interface DaemonHealth {
132
+ pid: number;
133
+ packageVersion: string;
134
+ protocolVersion: number;
135
+ endpoint: string;
136
+ sessionCount: number;
137
+ sessions: DaemonSessionHealth[];
138
+ }
139
+ type ClientFrame = {
140
+ type: "init";
141
+ protocolVersion: number;
142
+ clientVersion: string;
143
+ } | ClientRequestFrame | {
144
+ type: "exit";
145
+ };
146
+ type ClientRequestFrame = ({
147
+ type: "create-surface";
148
+ requestId: RequestId;
149
+ } & SurfaceOptions) | {
150
+ type: "resolve-default-surface";
151
+ requestId: RequestId;
152
+ } | {
153
+ type: "create-tray";
154
+ requestId: RequestId;
155
+ surface: SurfaceRef;
156
+ tray: TrayOptions;
157
+ } | {
158
+ type: "destroy-tray";
159
+ requestId: RequestId;
160
+ surfaceId: SurfaceId;
161
+ trayId: TrayId;
162
+ } | {
163
+ type: "set-tray-menu";
164
+ requestId: RequestId;
165
+ surfaceId: SurfaceId;
166
+ trayId: TrayId;
167
+ menu: Menu;
168
+ } | {
169
+ type: "set-tray-icon";
170
+ requestId: RequestId;
171
+ surfaceId: SurfaceId;
172
+ trayId: TrayId;
173
+ icon: Icon;
174
+ } | {
175
+ type: "set-tray-tooltip";
176
+ requestId: RequestId;
177
+ surfaceId: SurfaceId;
178
+ trayId: TrayId;
179
+ tooltip: Tooltip;
180
+ } | {
181
+ type: "load-ext";
182
+ requestId: RequestId;
183
+ surfaceId: SurfaceId;
184
+ name: string;
185
+ path: string;
186
+ } | {
187
+ type: "ext-command";
188
+ requestId: RequestId;
189
+ surfaceId: SurfaceId;
190
+ trayId: TrayId;
191
+ ext: string;
192
+ data: unknown;
193
+ } | {
194
+ type: "unload-ext";
195
+ requestId: RequestId;
196
+ surfaceId: SurfaceId;
197
+ name: string;
198
+ } | {
199
+ type: "health";
200
+ requestId: RequestId;
201
+ };
202
+ type ServerFrame = {
203
+ type: "ready";
204
+ protocolVersion: number;
205
+ brokerVersion: string;
206
+ leaseId: LeaseId;
207
+ } | {
208
+ type: "surface-created";
209
+ requestId: RequestId;
210
+ surface: SurfaceRef;
211
+ } | {
212
+ type: "default-surface";
213
+ requestId: RequestId;
214
+ surface: SurfaceRef;
215
+ } | {
216
+ type: "tray-created";
217
+ requestId: RequestId;
218
+ surfaceId: SurfaceId;
219
+ trayId: TrayId;
220
+ } | {
221
+ type: "ack";
222
+ requestId: RequestId;
223
+ } | {
224
+ type: "daemon-health";
225
+ requestId: RequestId;
226
+ health: DaemonHealth;
227
+ } | {
228
+ type: "event";
229
+ event: TrayEvent;
230
+ } | {
231
+ type: "ext-event";
232
+ surfaceId: SurfaceId;
233
+ trayId: TrayId;
234
+ ext: string;
235
+ data: unknown;
236
+ } | {
237
+ type: "error";
238
+ requestId?: RequestId;
239
+ code: string;
240
+ message: string;
241
+ };
242
+ interface ParseResult<T> {
243
+ ok: boolean;
244
+ frame?: T;
245
+ error?: string;
246
+ }
247
+ declare const parseServerFrame: (line: string) => ParseResult<ServerFrame>;
248
+ declare const isServerFrame: (value: unknown) => value is ServerFrame;
249
+ //#endregion
250
+ export { BrokerEndpointIdentity, BrokerEndpointIdentityOptions, ClientFrame, ClientRequestFrame, DaemonHealth, DaemonSessionHealth, ExtensionEnvelope, ExtensionScope, Icon, LeaseId, Menu, MenuItem, MenuItemId, MouseButton, PROTOCOL_VERSION, ParseResult, Rect, RequestId, ServerFrame, SurfaceId, SurfaceOptions, SurfaceRef, Tooltip, TrayEvent, TrayId, TrayOptions, createBrokerEndpointIdentity, formatBrokerEndpointName, formatBrokerStateRoot, formatUnixSocketPath, formatWindowsPipeName, isServerFrame, isSupportedProtocolVersion, parseServerFrame };
251
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":"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"}
package/dist/index.mjs ADDED
@@ -0,0 +1,85 @@
1
+ //#region src/index.ts
2
+ const PROTOCOL_VERSION = 1;
3
+ const createBrokerEndpointIdentity = ({ packageVersion, protocolVersion = 1 }) => {
4
+ assertEndpointComponent(packageVersion, "packageVersion");
5
+ if (!Number.isInteger(protocolVersion) || protocolVersion <= 0) throw new Error(`protocolVersion must be a positive integer: ${protocolVersion}`);
6
+ return {
7
+ packageVersion,
8
+ protocolVersion
9
+ };
10
+ };
11
+ const isSupportedProtocolVersion = (protocolVersion) => protocolVersion === 1;
12
+ const formatBrokerEndpointName = (identity) => {
13
+ assertEndpointIdentity(identity);
14
+ return `opentray-${identity.packageVersion}-p${identity.protocolVersion}`;
15
+ };
16
+ const formatBrokerStateRoot = (homeDir, identity) => {
17
+ assertEndpointIdentity(identity);
18
+ if (homeDir.length === 0) throw new Error("homeDir must not be empty");
19
+ return `${homeDir.replace(/[\\/]+$/u, "")}/.opentray/${identity.packageVersion}`;
20
+ };
21
+ const formatUnixSocketPath = (homeDir, identity) => `${formatBrokerStateRoot(homeDir, identity)}/opentray-p${identity.protocolVersion}.sock`;
22
+ const formatWindowsPipeName = (identity) => `\\\\.\\pipe\\${formatBrokerEndpointName(identity)}`;
23
+ const endpointComponentPattern = /^[0-9A-Za-z._+-]+$/u;
24
+ const assertEndpointIdentity = (identity) => {
25
+ assertEndpointComponent(identity.packageVersion, "packageVersion");
26
+ if (!Number.isInteger(identity.protocolVersion) || identity.protocolVersion <= 0) throw new Error(`protocolVersion must be a positive integer: ${identity.protocolVersion}`);
27
+ };
28
+ const assertEndpointComponent = (value, name) => {
29
+ if (value.length === 0) throw new Error(`${name} must not be empty`);
30
+ if (!endpointComponentPattern.test(value)) throw new Error(`${name} contains invalid endpoint characters: ${value}`);
31
+ };
32
+ const parseServerFrame = (line) => {
33
+ try {
34
+ const value = JSON.parse(line);
35
+ if (!isServerFrame(value)) return {
36
+ ok: false,
37
+ error: "invalid server frame"
38
+ };
39
+ return {
40
+ ok: true,
41
+ frame: value
42
+ };
43
+ } catch (error) {
44
+ return {
45
+ ok: false,
46
+ error: error instanceof Error ? error.message : "unknown parse error"
47
+ };
48
+ }
49
+ };
50
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
51
+ const isServerFrame = (value) => {
52
+ if (!isRecord(value) || typeof value.type !== "string") return false;
53
+ switch (value.type) {
54
+ case "ready": return typeof value.protocolVersion === "number" && typeof value.brokerVersion === "string" && typeof value.leaseId === "string";
55
+ case "surface-created": return typeof value.requestId === "string" && isRecord(value.surface);
56
+ case "default-surface": return typeof value.requestId === "string" && isRecord(value.surface);
57
+ case "tray-created": return typeof value.requestId === "string" && typeof value.surfaceId === "string" && typeof value.trayId === "string";
58
+ case "ack": return typeof value.requestId === "string";
59
+ case "daemon-health": return typeof value.requestId === "string" && isDaemonHealth(value.health);
60
+ case "event": return isTrayEvent(value.event);
61
+ case "ext-event": return typeof value.surfaceId === "string" && typeof value.trayId === "string" && typeof value.ext === "string";
62
+ case "error": return (value.requestId === void 0 || typeof value.requestId === "string") && typeof value.code === "string" && typeof value.message === "string";
63
+ default: return false;
64
+ }
65
+ };
66
+ const isDaemonHealth = (value) => {
67
+ if (!isRecord(value)) return false;
68
+ return typeof value.pid === "number" && typeof value.packageVersion === "string" && typeof value.protocolVersion === "number" && typeof value.endpoint === "string" && typeof value.sessionCount === "number" && Array.isArray(value.sessions) && value.sessions.every(isDaemonSessionHealth);
69
+ };
70
+ const isDaemonSessionHealth = (value) => isRecord(value) && typeof value.sessionId === "number" && (value.leaseId === void 0 || typeof value.leaseId === "string") && typeof value.initialized === "boolean";
71
+ const isTrayEvent = (value) => {
72
+ if (!isRecord(value) || typeof value.type !== "string") return false;
73
+ switch (value.type) {
74
+ case "ready": return typeof value.surfaceId === "string";
75
+ case "menuClick": return typeof value.surfaceId === "string" && typeof value.trayId === "string" && typeof value.itemId === "number";
76
+ case "trayClick":
77
+ case "trayDoubleClick": return typeof value.surfaceId === "string" && isMouseButton(value.button) && typeof value.x === "number" && typeof value.y === "number";
78
+ default: return false;
79
+ }
80
+ };
81
+ const isMouseButton = (value) => value === "left" || value === "right" || value === "middle";
82
+ //#endregion
83
+ export { PROTOCOL_VERSION, createBrokerEndpointIdentity, formatBrokerEndpointName, formatBrokerStateRoot, formatUnixSocketPath, formatWindowsPipeName, isServerFrame, isSupportedProtocolVersion, parseServerFrame };
84
+
85
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["export type LeaseId = string;\nexport type RequestId = string;\nexport type SurfaceId = string;\nexport type TrayId = string;\nexport type MenuItemId = number;\n\nexport const PROTOCOL_VERSION = 1;\n\nexport interface BrokerEndpointIdentity {\n packageVersion: string;\n protocolVersion: number;\n}\n\nexport interface BrokerEndpointIdentityOptions {\n packageVersion: string;\n protocolVersion?: number;\n}\n\nexport const createBrokerEndpointIdentity = ({\n packageVersion,\n protocolVersion = PROTOCOL_VERSION,\n}: BrokerEndpointIdentityOptions): BrokerEndpointIdentity => {\n assertEndpointComponent(packageVersion, \"packageVersion\");\n if (!Number.isInteger(protocolVersion) || protocolVersion <= 0) {\n throw new Error(`protocolVersion must be a positive integer: ${protocolVersion}`);\n }\n\n return {\n packageVersion,\n protocolVersion,\n };\n};\n\nexport const isSupportedProtocolVersion = (protocolVersion: number): boolean =>\n protocolVersion === PROTOCOL_VERSION;\n\nexport const formatBrokerEndpointName = (identity: BrokerEndpointIdentity): string => {\n assertEndpointIdentity(identity);\n return `opentray-${identity.packageVersion}-p${identity.protocolVersion}`;\n};\n\nexport const formatBrokerStateRoot = (homeDir: string, identity: BrokerEndpointIdentity): string => {\n assertEndpointIdentity(identity);\n if (homeDir.length === 0) {\n throw new Error(\"homeDir must not be empty\");\n }\n\n const normalizedHome = homeDir.replace(/[\\\\/]+$/u, \"\");\n return `${normalizedHome}/.opentray/${identity.packageVersion}`;\n};\n\nexport const formatUnixSocketPath = (homeDir: string, identity: BrokerEndpointIdentity): string =>\n `${formatBrokerStateRoot(homeDir, identity)}/opentray-p${identity.protocolVersion}.sock`;\n\nexport const formatWindowsPipeName = (identity: BrokerEndpointIdentity): string =>\n `\\\\\\\\.\\\\pipe\\\\${formatBrokerEndpointName(identity)}`;\n\nconst endpointComponentPattern = /^[0-9A-Za-z._+-]+$/u;\n\nconst assertEndpointIdentity = (identity: BrokerEndpointIdentity): void => {\n assertEndpointComponent(identity.packageVersion, \"packageVersion\");\n if (!Number.isInteger(identity.protocolVersion) || identity.protocolVersion <= 0) {\n throw new Error(`protocolVersion must be a positive integer: ${identity.protocolVersion}`);\n }\n};\n\nconst assertEndpointComponent = (value: string, name: string): void => {\n if (value.length === 0) {\n throw new Error(`${name} must not be empty`);\n }\n if (!endpointComponentPattern.test(value)) {\n throw new Error(`${name} contains invalid endpoint characters: ${value}`);\n }\n};\n\nexport interface SurfaceOptions {\n appId: string;\n title?: string;\n icon?: Icon;\n default?: boolean;\n}\n\nexport interface SurfaceRef {\n surfaceId: SurfaceId;\n appId: string;\n}\n\nexport interface TrayOptions {\n trayId?: TrayId;\n appId?: string;\n title?: string;\n tooltip?: Tooltip;\n icon: Icon;\n menu?: Menu;\n}\n\nexport interface Tooltip {\n title: string;\n description: string;\n}\n\nexport interface Menu {\n items: MenuItem[];\n}\n\nexport type MenuItem =\n | {\n type: \"item\";\n id: MenuItemId;\n title: string;\n enabled?: boolean;\n shortcut?: string;\n }\n | {\n type: \"check\";\n id: MenuItemId;\n title: string;\n enabled?: boolean;\n checked?: boolean;\n }\n | {\n type: \"radio\";\n id: MenuItemId;\n title: string;\n enabled?: boolean;\n checked?: boolean;\n group: number;\n }\n | {\n type: \"separator\";\n }\n | {\n type: \"submenu\";\n title: string;\n enabled?: boolean;\n items: MenuItem[];\n };\n\nexport type Icon =\n | { type: \"rgba\"; data: Uint8Array | number[]; width: number; height: number }\n | { type: \"encoded\"; data: Uint8Array | number[] }\n | { type: \"file\"; path: string };\n\nexport interface Rect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport type MouseButton = \"left\" | \"right\" | \"middle\";\n\nexport type TrayEvent =\n | { type: \"ready\"; surfaceId: SurfaceId }\n | { type: \"menuClick\"; surfaceId: SurfaceId; trayId: TrayId; itemId: MenuItemId }\n | { type: \"trayClick\"; surfaceId: SurfaceId; button: MouseButton; x: number; y: number }\n | { type: \"trayDoubleClick\"; surfaceId: SurfaceId; button: MouseButton; x: number; y: number };\n\nexport interface ExtensionScope {\n surfaceId: SurfaceId;\n trayId?: TrayId;\n ext: string;\n}\n\nexport interface ExtensionEnvelope<TData = unknown> {\n scope: ExtensionScope;\n data: TData;\n}\n\nexport interface DaemonSessionHealth {\n sessionId: number;\n leaseId?: LeaseId;\n initialized: boolean;\n}\n\nexport interface DaemonHealth {\n pid: number;\n packageVersion: string;\n protocolVersion: number;\n endpoint: string;\n sessionCount: number;\n sessions: DaemonSessionHealth[];\n}\n\nexport type ClientFrame =\n | { type: \"init\"; protocolVersion: number; clientVersion: string }\n | ClientRequestFrame\n | { type: \"exit\" };\n\nexport type ClientRequestFrame =\n | ({ type: \"create-surface\"; requestId: RequestId } & SurfaceOptions)\n | { type: \"resolve-default-surface\"; requestId: RequestId }\n | { type: \"create-tray\"; requestId: RequestId; surface: SurfaceRef; tray: TrayOptions }\n | { type: \"destroy-tray\"; requestId: RequestId; surfaceId: SurfaceId; trayId: TrayId }\n | { type: \"set-tray-menu\"; requestId: RequestId; surfaceId: SurfaceId; trayId: TrayId; menu: Menu }\n | { type: \"set-tray-icon\"; requestId: RequestId; surfaceId: SurfaceId; trayId: TrayId; icon: Icon }\n | { type: \"set-tray-tooltip\"; requestId: RequestId; surfaceId: SurfaceId; trayId: TrayId; tooltip: Tooltip }\n | { type: \"load-ext\"; requestId: RequestId; surfaceId: SurfaceId; name: string; path: string }\n | { type: \"ext-command\"; requestId: RequestId; surfaceId: SurfaceId; trayId: TrayId; ext: string; data: unknown }\n | { type: \"unload-ext\"; requestId: RequestId; surfaceId: SurfaceId; name: string }\n | { type: \"health\"; requestId: RequestId };\n\nexport type ServerFrame =\n | { type: \"ready\"; protocolVersion: number; brokerVersion: string; leaseId: LeaseId }\n | { type: \"surface-created\"; requestId: RequestId; surface: SurfaceRef }\n | { type: \"default-surface\"; requestId: RequestId; surface: SurfaceRef }\n | { type: \"tray-created\"; requestId: RequestId; surfaceId: SurfaceId; trayId: TrayId }\n | { type: \"ack\"; requestId: RequestId }\n | { type: \"daemon-health\"; requestId: RequestId; health: DaemonHealth }\n | { type: \"event\"; event: TrayEvent }\n | { type: \"ext-event\"; surfaceId: SurfaceId; trayId: TrayId; ext: string; data: unknown }\n | { type: \"error\"; requestId?: RequestId; code: string; message: string };\n\nexport interface ParseResult<T> {\n ok: boolean;\n frame?: T;\n error?: string;\n}\n\nexport const parseServerFrame = (line: string): ParseResult<ServerFrame> => {\n try {\n const value: unknown = JSON.parse(line);\n if (!isServerFrame(value)) {\n return { ok: false, error: \"invalid server frame\" };\n }\n return { ok: true, frame: value };\n } catch (error) {\n return {\n ok: false,\n error: error instanceof Error ? error.message : \"unknown parse error\",\n };\n }\n};\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nexport const isServerFrame = (value: unknown): value is ServerFrame => {\n if (!isRecord(value) || typeof value.type !== \"string\") {\n return false;\n }\n\n switch (value.type) {\n case \"ready\":\n return (\n typeof value.protocolVersion === \"number\" &&\n typeof value.brokerVersion === \"string\" &&\n typeof value.leaseId === \"string\"\n );\n case \"surface-created\":\n return typeof value.requestId === \"string\" && isRecord(value.surface);\n case \"default-surface\":\n return typeof value.requestId === \"string\" && isRecord(value.surface);\n case \"tray-created\":\n return (\n typeof value.requestId === \"string\" &&\n typeof value.surfaceId === \"string\" &&\n typeof value.trayId === \"string\"\n );\n case \"ack\":\n return typeof value.requestId === \"string\";\n case \"daemon-health\":\n return typeof value.requestId === \"string\" && isDaemonHealth(value.health);\n case \"event\":\n return isTrayEvent(value.event);\n case \"ext-event\":\n return (\n typeof value.surfaceId === \"string\" &&\n typeof value.trayId === \"string\" &&\n typeof value.ext === \"string\"\n );\n case \"error\":\n return (\n (value.requestId === undefined || typeof value.requestId === \"string\") &&\n typeof value.code === \"string\" &&\n typeof value.message === \"string\"\n );\n default:\n return false;\n }\n};\n\nconst isDaemonHealth = (value: unknown): value is DaemonHealth => {\n if (!isRecord(value)) {\n return false;\n }\n\n return (\n typeof value.pid === \"number\" &&\n typeof value.packageVersion === \"string\" &&\n typeof value.protocolVersion === \"number\" &&\n typeof value.endpoint === \"string\" &&\n typeof value.sessionCount === \"number\" &&\n Array.isArray(value.sessions) &&\n value.sessions.every(isDaemonSessionHealth)\n );\n};\n\nconst isDaemonSessionHealth = (value: unknown): value is DaemonSessionHealth =>\n isRecord(value) &&\n typeof value.sessionId === \"number\" &&\n (value.leaseId === undefined || typeof value.leaseId === \"string\") &&\n typeof value.initialized === \"boolean\";\n\nconst isTrayEvent = (value: unknown): value is TrayEvent => {\n if (!isRecord(value) || typeof value.type !== \"string\") {\n return false;\n }\n\n switch (value.type) {\n case \"ready\":\n return typeof value.surfaceId === \"string\";\n case \"menuClick\":\n return (\n typeof value.surfaceId === \"string\" &&\n typeof value.trayId === \"string\" &&\n typeof value.itemId === \"number\"\n );\n case \"trayClick\":\n case \"trayDoubleClick\":\n return (\n typeof value.surfaceId === \"string\" &&\n isMouseButton(value.button) &&\n typeof value.x === \"number\" &&\n typeof value.y === \"number\"\n );\n default:\n return false;\n }\n};\n\nconst isMouseButton = (value: unknown): value is MouseButton =>\n value === \"left\" || value === \"right\" || value === \"middle\";\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,13 +1,17 @@
1
1
  {
2
2
  "name": "@opentray/spec",
3
- "version": "0.0.0",
3
+ "version": "0.2.0",
4
4
  "description": "Shared TypeScript protocol and contract types for OpenTray.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/jixoai/opentray"
10
+ },
7
11
  "exports": {
8
12
  ".": {
9
- "types": "./dist/index.d.ts",
10
- "import": "./dist/index.js"
13
+ "types": "./dist/index.d.mts",
14
+ "import": "./dist/index.mjs"
11
15
  },
12
16
  "./package.json": "./package.json"
13
17
  },
@@ -15,5 +19,16 @@
15
19
  "dist",
16
20
  "README.md"
17
21
  ],
18
- "sideEffects": false
22
+ "sideEffects": false,
23
+ "devDependencies": {
24
+ "tsdown": "^0.22.1",
25
+ "typescript": "^6.0.3",
26
+ "vitest": "^4.1.7"
27
+ },
28
+ "scripts": {
29
+ "build": "tsdown src/index.ts --format esm --dts",
30
+ "example:parse": "bun run examples/parse-frame.ts",
31
+ "test": "vitest run",
32
+ "typecheck": "tsc --noEmit"
33
+ }
19
34
  }