@bardioc/app-sdk 0.4.11 → 1.0.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.
Files changed (47) hide show
  1. package/README.md +86 -253
  2. package/dist/api/graph.d.ts +30 -0
  3. package/dist/api/graph.js +139 -0
  4. package/dist/api/os.d.ts +30 -0
  5. package/dist/api/os.js +48 -0
  6. package/dist/api/relay.d.ts +7 -0
  7. package/dist/api/relay.js +16 -0
  8. package/dist/api/request.d.ts +18 -0
  9. package/dist/api/request.js +12 -0
  10. package/dist/client.d.ts +104 -0
  11. package/dist/client.js +106 -0
  12. package/dist/contract.d.ts +294 -0
  13. package/dist/contract.js +150 -0
  14. package/dist/dev-idb.d.ts +10 -0
  15. package/dist/dev-idb.js +42 -0
  16. package/dist/dev-proxy.js +17 -3
  17. package/dist/dev.d.ts +16 -12
  18. package/dist/dev.js +101 -119
  19. package/dist/errors.d.ts +24 -25
  20. package/dist/errors.js +33 -48
  21. package/dist/index.d.ts +10 -7
  22. package/dist/index.js +2 -4
  23. package/dist/manifest.d.ts +10 -2
  24. package/dist/manifest.js +28 -24
  25. package/dist/protocol.d.ts +6 -29
  26. package/dist/protocol.js +3 -31
  27. package/dist/react.d.ts +22 -26
  28. package/dist/react.js +88 -181
  29. package/dist/serve.d.ts +26 -0
  30. package/dist/serve.js +64 -0
  31. package/dist/testing.d.ts +17 -0
  32. package/dist/testing.js +128 -0
  33. package/dist/types.d.ts +0 -241
  34. package/dist/vite.js +3 -0
  35. package/dist/wire.d.ts +46 -0
  36. package/dist/wire.js +138 -0
  37. package/package.json +12 -4
  38. package/dist/contract-matrix.d.ts +0 -148
  39. package/dist/contract-matrix.js +0 -150
  40. package/dist/host-bridge.d.ts +0 -3
  41. package/dist/host-bridge.js +0 -306
  42. package/dist/transports/backend-transport.d.ts +0 -24
  43. package/dist/transports/backend-transport.js +0 -54
  44. package/dist/transports/graph-transport.d.ts +0 -241
  45. package/dist/transports/graph-transport.js +0 -616
  46. package/dist/transports/os-transport.d.ts +0 -190
  47. package/dist/transports/os-transport.js +0 -444
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bardioc/app-sdk",
3
- "version": "0.4.11",
4
- "description": "SDK for building apps for the Bardioc OS",
3
+ "version": "1.0.0",
4
+ "description": "SDK for building apps for Bardioc",
5
5
  "license": "MIT",
6
6
  "private": false,
7
7
  "type": "module",
@@ -41,6 +41,10 @@
41
41
  "types": "./dist/dev-proxy.d.ts",
42
42
  "default": "./dist/dev-proxy.js"
43
43
  },
44
+ "./testing": {
45
+ "types": "./dist/testing.d.ts",
46
+ "default": "./dist/testing.js"
47
+ },
44
48
  "./types": {
45
49
  "types": "./dist/types.d.ts"
46
50
  }
@@ -69,16 +73,20 @@
69
73
  }
70
74
  },
71
75
  "devDependencies": {
72
- "@vitest/coverage-v8": "^4.1.8",
76
+ "@testing-library/react": "16.3.2",
73
77
  "@types/node": "^25.9.1",
74
78
  "@types/react": "19.1.16",
75
- "@testing-library/react": "16.3.2",
79
+ "@vitest/coverage-v8": "^4.1.8",
80
+ "fake-indexeddb": "^6.2.6",
76
81
  "jsdom": "29.1.0",
77
82
  "react": "19.2.6",
78
83
  "react-dom": "19.2.6",
79
84
  "typescript": "6.0.3",
80
85
  "vitest": "^4.1.8"
81
86
  },
87
+ "dependencies": {
88
+ "zod": "^4.4.3"
89
+ },
82
90
  "scripts": {
83
91
  "build": "tsc",
84
92
  "lint": "eslint .",
@@ -1,148 +0,0 @@
1
- import type { AppManifestPermission } from './manifest.js';
2
- export declare const SDK_CONTRACT_MATRIX: {
3
- readonly SDK_INIT: {
4
- readonly requestPayload: "{ appId?: string }";
5
- readonly responsePayload: "SdkInitResponse";
6
- readonly permission: null;
7
- readonly expectedHostBehavior: "Handshake the iframe app and return SDK-owned host context.";
8
- };
9
- readonly SDK_NOTIFY: {
10
- readonly requestPayload: "SdkNotifyPayload";
11
- readonly responsePayload: "{ ok: true }";
12
- readonly permission: "notify";
13
- readonly expectedHostBehavior: "Surface a host-level notification and acknowledge delivery.";
14
- };
15
- readonly SDK_KERNEL_PROXY: {
16
- readonly requestPayload: "SdkKernelProxyPayload";
17
- readonly responsePayload: "KernelProtocol response";
18
- readonly permission: "kernel-proxy";
19
- readonly expectedHostBehavior: "Forward only allow-listed kernel messages through the host.";
20
- };
21
- readonly SDK_TRANSPORT: {
22
- readonly requestPayload: "SdkTransportPayload";
23
- readonly responsePayload: "Upstream transport response payload";
24
- readonly permission: "transport";
25
- readonly expectedHostBehavior: "Relay app-scoped transport requests with scope-aware host mediation.";
26
- };
27
- readonly SDK_SET_COMMANDS: {
28
- readonly requestPayload: "SdkSetCommandsPayload";
29
- readonly responsePayload: "{ ok: true }";
30
- readonly permission: null;
31
- readonly expectedHostBehavior: "Store the app command registry for host command invocation.";
32
- };
33
- readonly SDK_SET_MENU: {
34
- readonly requestPayload: "SdkSetMenuPayload";
35
- readonly responsePayload: "{ ok: true }";
36
- readonly permission: null;
37
- readonly expectedHostBehavior: "Store the app menu tree without degrading supported SDK menu kinds.";
38
- };
39
- readonly SDK_SET_ACTIVE_TAB: {
40
- readonly requestPayload: "SdkSetActiveTabPayload";
41
- readonly responsePayload: "{ ok: true }";
42
- readonly permission: null;
43
- readonly expectedHostBehavior: "Persist the app-reported active tab label in host window state.";
44
- };
45
- readonly SDK_SHORTCUT_EVENT: {
46
- readonly requestPayload: "SdkShortcutEventPayload";
47
- readonly responsePayload: null;
48
- readonly permission: null;
49
- readonly expectedHostBehavior: "Route a focused iframe shortcut event through host shortcut handling.";
50
- };
51
- readonly SDK_STORAGE_GET: {
52
- readonly requestPayload: "SdkStorageGetPayload";
53
- readonly responsePayload: "unknown";
54
- readonly permission: "storage";
55
- readonly expectedHostBehavior: "Host-owned storage bridge; currently deferred in bdf.";
56
- };
57
- readonly SDK_STORAGE_SET: {
58
- readonly requestPayload: "SdkStorageSetPayload";
59
- readonly responsePayload: "void";
60
- readonly permission: "storage";
61
- readonly expectedHostBehavior: "Host-owned storage bridge; currently deferred in bdf.";
62
- };
63
- readonly SDK_STORAGE_DELETE: {
64
- readonly requestPayload: "SdkStorageDeletePayload";
65
- readonly responsePayload: "void";
66
- readonly permission: "storage";
67
- readonly expectedHostBehavior: "Host-owned storage bridge; currently deferred in bdf.";
68
- };
69
- readonly SDK_STORAGE_KEYS: {
70
- readonly requestPayload: "{}";
71
- readonly responsePayload: "string[]";
72
- readonly permission: "storage";
73
- readonly expectedHostBehavior: "Host-owned storage bridge; currently deferred in bdf.";
74
- };
75
- readonly SDK_IDB_GET: {
76
- readonly requestPayload: "SdkIdbGetPayload";
77
- readonly responsePayload: "SdkIdbEntry | null";
78
- readonly permission: "indexdb";
79
- readonly expectedHostBehavior: "Perform app-scoped IndexedDB reads in host-owned storage.";
80
- };
81
- readonly SDK_IDB_PUT: {
82
- readonly requestPayload: "SdkIdbPutPayload";
83
- readonly responsePayload: "void";
84
- readonly permission: "indexdb";
85
- readonly expectedHostBehavior: "Perform app-scoped IndexedDB writes in host-owned storage.";
86
- };
87
- readonly SDK_IDB_DELETE: {
88
- readonly requestPayload: "SdkIdbDeletePayload";
89
- readonly responsePayload: "void";
90
- readonly permission: "indexdb";
91
- readonly expectedHostBehavior: "Perform app-scoped IndexedDB deletes in host-owned storage.";
92
- };
93
- readonly SDK_IDB_QUERY: {
94
- readonly requestPayload: "SdkIdbQueryPayload";
95
- readonly responsePayload: "SdkIdbEntry[]";
96
- readonly permission: "indexdb";
97
- readonly expectedHostBehavior: "Perform app-scoped IndexedDB queries in host-owned storage.";
98
- };
99
- readonly SDK_LAUNCH_APP: {
100
- readonly requestPayload: "{ appId: string }";
101
- readonly responsePayload: "{ windowId: string | null }";
102
- readonly permission: null;
103
- readonly expectedHostBehavior: "Launch another registered app through the host shell.";
104
- };
105
- readonly SDK_COMMAND_INVOKE: {
106
- readonly requestPayload: "SdkCommandInvokePayload";
107
- readonly responsePayload: "{ ok: true }";
108
- readonly permission: null;
109
- readonly expectedHostBehavior: "Host-to-app command callback delivery.";
110
- };
111
- readonly SDK_EVENT_SUBSCRIBE: {
112
- readonly requestPayload: "EventSubscribeRequest";
113
- readonly responsePayload: "EventSubscribeResponse";
114
- readonly permission: "events";
115
- readonly expectedHostBehavior: "Open a scoped graph-event SSE stream for the requested OGIT types.";
116
- };
117
- readonly SDK_EVENT_UNSUBSCRIBE: {
118
- readonly requestPayload: "EventUnsubscribeRequest";
119
- readonly responsePayload: "{ ok: true }";
120
- readonly permission: "events";
121
- readonly expectedHostBehavior: "Close the graph-event SSE stream for the given subscription.";
122
- };
123
- readonly SDK_GRAPH_EVENT: {
124
- readonly requestPayload: "GraphEventPushPayload";
125
- readonly responsePayload: null;
126
- readonly permission: null;
127
- readonly expectedHostBehavior: "Host-to-app graph-event push delivery (no ack).";
128
- };
129
- readonly SDK_OS_CONFIG_UPDATE: {
130
- readonly requestPayload: "OsConfig";
131
- readonly responsePayload: null;
132
- readonly permission: null;
133
- readonly expectedHostBehavior: "Host-to-app runtime OS configuration update delivery.";
134
- };
135
- readonly SDK_RESPONSE: {
136
- readonly requestPayload: "SdkResponse";
137
- readonly responsePayload: null;
138
- readonly permission: null;
139
- readonly expectedHostBehavior: "Request/response envelope used by the bridge runtime.";
140
- };
141
- readonly SDK_INIT_ACK: {
142
- readonly requestPayload: "SdkInitResponse";
143
- readonly responsePayload: null;
144
- readonly permission: null;
145
- readonly expectedHostBehavior: "Handshake acknowledgement used by the bridge runtime.";
146
- };
147
- };
148
- export declare function getRequiredPermissionForSdkMessage(messageType: string): AppManifestPermission | null;
@@ -1,150 +0,0 @@
1
- import { SDK_MSG } from './protocol.js';
2
- export const SDK_CONTRACT_MATRIX = {
3
- [SDK_MSG.INIT]: {
4
- requestPayload: '{ appId?: string }',
5
- responsePayload: 'SdkInitResponse',
6
- permission: null,
7
- expectedHostBehavior: 'Handshake the iframe app and return SDK-owned host context.',
8
- },
9
- [SDK_MSG.NOTIFY]: {
10
- requestPayload: 'SdkNotifyPayload',
11
- responsePayload: '{ ok: true }',
12
- permission: 'notify',
13
- expectedHostBehavior: 'Surface a host-level notification and acknowledge delivery.',
14
- },
15
- [SDK_MSG.KERNEL_PROXY]: {
16
- requestPayload: 'SdkKernelProxyPayload',
17
- responsePayload: 'KernelProtocol response',
18
- permission: 'kernel-proxy',
19
- expectedHostBehavior: 'Forward only allow-listed kernel messages through the host.',
20
- },
21
- [SDK_MSG.TRANSPORT]: {
22
- requestPayload: 'SdkTransportPayload',
23
- responsePayload: 'Upstream transport response payload',
24
- permission: 'transport',
25
- expectedHostBehavior: 'Relay app-scoped transport requests with scope-aware host mediation.',
26
- },
27
- [SDK_MSG.SET_COMMANDS]: {
28
- requestPayload: 'SdkSetCommandsPayload',
29
- responsePayload: '{ ok: true }',
30
- permission: null,
31
- expectedHostBehavior: 'Store the app command registry for host command invocation.',
32
- },
33
- [SDK_MSG.SET_MENU]: {
34
- requestPayload: 'SdkSetMenuPayload',
35
- responsePayload: '{ ok: true }',
36
- permission: null,
37
- expectedHostBehavior: 'Store the app menu tree without degrading supported SDK menu kinds.',
38
- },
39
- [SDK_MSG.SET_ACTIVE_TAB]: {
40
- requestPayload: 'SdkSetActiveTabPayload',
41
- responsePayload: '{ ok: true }',
42
- permission: null,
43
- expectedHostBehavior: 'Persist the app-reported active tab label in host window state.',
44
- },
45
- [SDK_MSG.SHORTCUT_EVENT]: {
46
- requestPayload: 'SdkShortcutEventPayload',
47
- responsePayload: null,
48
- permission: null,
49
- expectedHostBehavior: 'Route a focused iframe shortcut event through host shortcut handling.',
50
- },
51
- [SDK_MSG.STORAGE_GET]: {
52
- requestPayload: 'SdkStorageGetPayload',
53
- responsePayload: 'unknown',
54
- permission: 'storage',
55
- expectedHostBehavior: 'Host-owned storage bridge; currently deferred in bdf.',
56
- },
57
- [SDK_MSG.STORAGE_SET]: {
58
- requestPayload: 'SdkStorageSetPayload',
59
- responsePayload: 'void',
60
- permission: 'storage',
61
- expectedHostBehavior: 'Host-owned storage bridge; currently deferred in bdf.',
62
- },
63
- [SDK_MSG.STORAGE_DELETE]: {
64
- requestPayload: 'SdkStorageDeletePayload',
65
- responsePayload: 'void',
66
- permission: 'storage',
67
- expectedHostBehavior: 'Host-owned storage bridge; currently deferred in bdf.',
68
- },
69
- [SDK_MSG.STORAGE_KEYS]: {
70
- requestPayload: '{}',
71
- responsePayload: 'string[]',
72
- permission: 'storage',
73
- expectedHostBehavior: 'Host-owned storage bridge; currently deferred in bdf.',
74
- },
75
- [SDK_MSG.IDB_GET]: {
76
- requestPayload: 'SdkIdbGetPayload',
77
- responsePayload: 'SdkIdbEntry | null',
78
- permission: 'indexdb',
79
- expectedHostBehavior: 'Perform app-scoped IndexedDB reads in host-owned storage.',
80
- },
81
- [SDK_MSG.IDB_PUT]: {
82
- requestPayload: 'SdkIdbPutPayload',
83
- responsePayload: 'void',
84
- permission: 'indexdb',
85
- expectedHostBehavior: 'Perform app-scoped IndexedDB writes in host-owned storage.',
86
- },
87
- [SDK_MSG.IDB_DELETE]: {
88
- requestPayload: 'SdkIdbDeletePayload',
89
- responsePayload: 'void',
90
- permission: 'indexdb',
91
- expectedHostBehavior: 'Perform app-scoped IndexedDB deletes in host-owned storage.',
92
- },
93
- [SDK_MSG.IDB_QUERY]: {
94
- requestPayload: 'SdkIdbQueryPayload',
95
- responsePayload: 'SdkIdbEntry[]',
96
- permission: 'indexdb',
97
- expectedHostBehavior: 'Perform app-scoped IndexedDB queries in host-owned storage.',
98
- },
99
- [SDK_MSG.LAUNCH_APP]: {
100
- requestPayload: '{ appId: string }',
101
- responsePayload: '{ windowId: string | null }',
102
- permission: null,
103
- expectedHostBehavior: 'Launch another registered app through the host shell.',
104
- },
105
- [SDK_MSG.COMMAND_INVOKE]: {
106
- requestPayload: 'SdkCommandInvokePayload',
107
- responsePayload: '{ ok: true }',
108
- permission: null,
109
- expectedHostBehavior: 'Host-to-app command callback delivery.',
110
- },
111
- [SDK_MSG.EVENT_SUBSCRIBE]: {
112
- requestPayload: 'EventSubscribeRequest',
113
- responsePayload: 'EventSubscribeResponse',
114
- permission: 'events',
115
- expectedHostBehavior: 'Open a scoped graph-event SSE stream for the requested OGIT types.',
116
- },
117
- [SDK_MSG.EVENT_UNSUBSCRIBE]: {
118
- requestPayload: 'EventUnsubscribeRequest',
119
- responsePayload: '{ ok: true }',
120
- permission: 'events',
121
- expectedHostBehavior: 'Close the graph-event SSE stream for the given subscription.',
122
- },
123
- [SDK_MSG.GRAPH_EVENT]: {
124
- requestPayload: 'GraphEventPushPayload',
125
- responsePayload: null,
126
- permission: null,
127
- expectedHostBehavior: 'Host-to-app graph-event push delivery (no ack).',
128
- },
129
- [SDK_MSG.OS_CONFIG_UPDATE]: {
130
- requestPayload: 'OsConfig',
131
- responsePayload: null,
132
- permission: null,
133
- expectedHostBehavior: 'Host-to-app runtime OS configuration update delivery.',
134
- },
135
- [SDK_MSG.RESPONSE]: {
136
- requestPayload: 'SdkResponse',
137
- responsePayload: null,
138
- permission: null,
139
- expectedHostBehavior: 'Request/response envelope used by the bridge runtime.',
140
- },
141
- [SDK_MSG.INIT_ACK]: {
142
- requestPayload: 'SdkInitResponse',
143
- responsePayload: null,
144
- permission: null,
145
- expectedHostBehavior: 'Handshake acknowledgement used by the bridge runtime.',
146
- },
147
- };
148
- export function getRequiredPermissionForSdkMessage(messageType) {
149
- return SDK_CONTRACT_MATRIX[messageType]?.permission ?? null;
150
- }
@@ -1,3 +0,0 @@
1
- import type { HostBridge, HostBridgeConfig } from './types.js';
2
- /** Create a bridge between an iframe app and its host OS via `postMessage`. */
3
- export declare function createHostBridge(config: HostBridgeConfig): HostBridge;
@@ -1,306 +0,0 @@
1
- import { SDK_MSG } from './protocol.js';
2
- import { BackendTransport } from './transports/backend-transport.js';
3
- import { GraphTransport } from './transports/graph-transport.js';
4
- import { OsTransport } from './transports/os-transport.js';
5
- let counter = 0;
6
- function nextId() {
7
- return `sdk_${Date.now()}_${++counter}`;
8
- }
9
- function assertStoreName(storeName) {
10
- const normalized = storeName.trim();
11
- if (!normalized) {
12
- throw new Error('storeName must be a non-empty string');
13
- }
14
- return normalized;
15
- }
16
- /** Create a bridge between an iframe app and its host OS via `postMessage`. */
17
- export function createHostBridge(config) {
18
- const { appId: hintAppId, connectTimeout = 5000, debug = false } = config;
19
- const hostWindow = globalThis.window;
20
- // When inside an iframe, document.referrer gives us the parent's origin.
21
- const inferredHostOrigin = (() => {
22
- try {
23
- if (hostWindow.parent !== hostWindow && document.referrer) {
24
- return new URL(document.referrer).origin;
25
- }
26
- }
27
- catch {
28
- // ignore parse errors
29
- }
30
- return hostWindow.location.origin;
31
- })();
32
- const targetOrigin = config.targetOrigin ?? inferredHostOrigin;
33
- const isInsideIframe = hostWindow.parent !== hostWindow;
34
- function log(...args) {
35
- if (debug)
36
- console.log('[app-sdk]', ...args);
37
- }
38
- log('createHostBridge', { appId: hintAppId, targetOrigin, isInsideIframe });
39
- let _connected = false;
40
- let _context = null;
41
- let _destroyed = false;
42
- const commandHandlers = new Map();
43
- const osConfigUpdateCallbacks = new Set();
44
- const eventHandlers = new Map();
45
- const pending = new Map();
46
- function isAllowedMessageOrigin(origin) {
47
- return origin === targetOrigin || origin === hostWindow.location.origin;
48
- }
49
- function handleCommandInvoke(data) {
50
- const { commandId } = data.payload;
51
- const handler = commandHandlers.get(commandId);
52
- if (!handler) {
53
- respondToHost(data.id, null, `Command "${commandId}" is not registered`);
54
- return;
55
- }
56
- Promise.resolve()
57
- .then(() => handler())
58
- .then(() => {
59
- respondToHost(data.id, { ok: true });
60
- })
61
- .catch((error) => {
62
- const message = error instanceof Error ? error.message : String(error);
63
- respondToHost(data.id, null, message);
64
- });
65
- }
66
- function handleOsConfigUpdate(data) {
67
- const newOsConfig = data.payload;
68
- if (_context) {
69
- _context.osConfig = newOsConfig;
70
- }
71
- for (const callback of osConfigUpdateCallbacks) {
72
- try {
73
- callback(newOsConfig);
74
- }
75
- catch (error) {
76
- console.error('[app-sdk] OS config update callback error:', error);
77
- }
78
- }
79
- }
80
- function handleGraphEvent(data) {
81
- const { subscriptionId, event } = data.payload;
82
- const handler = eventHandlers.get(subscriptionId);
83
- if (!handler) {
84
- return;
85
- }
86
- try {
87
- handler(event);
88
- }
89
- catch (error) {
90
- console.error('[app-sdk] graph event handler error:', error);
91
- }
92
- }
93
- function handlePendingResponse(data) {
94
- const request = pending.get(data.id);
95
- if (!request) {
96
- return;
97
- }
98
- pending.delete(data.id);
99
- if (data.error) {
100
- request.reject(new Error(data.error));
101
- return;
102
- }
103
- request.resolve(data.payload);
104
- }
105
- function onMessage(event) {
106
- if (!isAllowedMessageOrigin(event.origin)) {
107
- log('ignoring message from', event.origin, '(expected', targetOrigin, ')');
108
- return;
109
- }
110
- const data = event.data;
111
- if (!data || typeof data.type !== 'string')
112
- return;
113
- if (!data.id)
114
- return;
115
- log('received', data.type, data.id, data.error ? `error: ${data.error}` : '');
116
- switch (data.type) {
117
- case SDK_MSG.COMMAND_INVOKE:
118
- handleCommandInvoke(data);
119
- return;
120
- case SDK_MSG.OS_CONFIG_UPDATE:
121
- handleOsConfigUpdate(data);
122
- return;
123
- case SDK_MSG.GRAPH_EVENT:
124
- handleGraphEvent(data);
125
- return;
126
- case SDK_MSG.INIT_ACK:
127
- case SDK_MSG.RESPONSE:
128
- handlePendingResponse(data);
129
- return;
130
- default:
131
- return;
132
- }
133
- }
134
- hostWindow.addEventListener('message', onMessage);
135
- function postToHost(message) {
136
- hostWindow.parent.postMessage(message, targetOrigin);
137
- }
138
- function respondToHost(id, payload, error) {
139
- postToHost({
140
- type: SDK_MSG.RESPONSE,
141
- id,
142
- payload: error ? undefined : payload,
143
- error,
144
- });
145
- }
146
- function send(type, payload) {
147
- if (_destroyed)
148
- throw new Error('Bridge is destroyed — cannot send messages');
149
- const id = nextId();
150
- const msg = { type, id, payload };
151
- log('sending', type, id);
152
- postToHost(msg);
153
- return id;
154
- }
155
- function request(type, payload, timeout = 60000) {
156
- return new Promise((resolve, reject) => {
157
- const id = send(type, payload);
158
- pending.set(id, { resolve, reject });
159
- setTimeout(() => {
160
- if (pending.has(id)) {
161
- pending.delete(id);
162
- reject(new Error(`SDK request ${type} timed out after ${timeout}ms`));
163
- }
164
- }, timeout);
165
- });
166
- }
167
- const storage = {
168
- get: key => request(SDK_MSG.STORAGE_GET, { key }),
169
- set: (key, value) => request(SDK_MSG.STORAGE_SET, { key, value }),
170
- delete: key => request(SDK_MSG.STORAGE_DELETE, { key }),
171
- keys: () => request(SDK_MSG.STORAGE_KEYS, {}),
172
- };
173
- const baseRequest = (payload) => {
174
- log('[SDK] transport.request', payload);
175
- return request(SDK_MSG.TRANSPORT, payload);
176
- };
177
- const transport = {
178
- graph: new GraphTransport(baseRequest, { debug }),
179
- os: new OsTransport(baseRequest),
180
- backend: routeKey => new BackendTransport(baseRequest, routeKey),
181
- };
182
- function idb(storeName) {
183
- const normalizedStoreName = assertStoreName(storeName);
184
- return {
185
- get: key => request(SDK_MSG.IDB_GET, { storeName: normalizedStoreName, key }),
186
- put: (key, value) => request(SDK_MSG.IDB_PUT, { storeName: normalizedStoreName, key, value }),
187
- delete: key => request(SDK_MSG.IDB_DELETE, { storeName: normalizedStoreName, key }),
188
- query: (options = {}) => request(SDK_MSG.IDB_QUERY, {
189
- storeName: normalizedStoreName,
190
- prefix: options.prefix,
191
- limit: options.limit,
192
- }),
193
- };
194
- }
195
- const events = {
196
- subscribe({ types }, handler) {
197
- const subscriptionId = nextId();
198
- eventHandlers.set(subscriptionId, handler);
199
- // Fire-and-forget the ack: the app-generated id removes any race, so a
200
- // failed ack only means no events will arrive — surface it and clean up.
201
- request(SDK_MSG.EVENT_SUBSCRIBE, { subscriptionId, types }).catch((error) => {
202
- eventHandlers.delete(subscriptionId);
203
- console.error('[app-sdk] events.subscribe failed:', error);
204
- });
205
- return () => {
206
- if (!eventHandlers.delete(subscriptionId)) {
207
- return;
208
- }
209
- if (_destroyed) {
210
- return;
211
- }
212
- send(SDK_MSG.EVENT_UNSUBSCRIBE, { subscriptionId });
213
- };
214
- },
215
- };
216
- const bridge = {
217
- get connected() {
218
- return _connected;
219
- },
220
- get context() {
221
- return _context;
222
- },
223
- async connect() {
224
- if (_connected && _context)
225
- return _context;
226
- const result = await request(SDK_MSG.INIT, { appId: hintAppId }, connectTimeout);
227
- _context = result;
228
- _connected = true;
229
- return result;
230
- },
231
- notify(message, level = 'info') {
232
- send(SDK_MSG.NOTIFY, { message, level });
233
- },
234
- storage,
235
- transport,
236
- idb,
237
- events,
238
- setCommands(commands) {
239
- const payload = { commands };
240
- return request(SDK_MSG.SET_COMMANDS, payload);
241
- },
242
- setMenu(menu) {
243
- const payload = { menu };
244
- return request(SDK_MSG.SET_MENU, payload);
245
- },
246
- setActiveTab(activeTab) {
247
- const payload = { activeTab };
248
- return request(SDK_MSG.SET_ACTIVE_TAB, payload);
249
- },
250
- sendShortcutEvent(payload) {
251
- send(SDK_MSG.SHORTCUT_EVENT, payload);
252
- },
253
- registerCommandHandlers(handlers) {
254
- for (const [commandId, handler] of Object.entries(handlers)) {
255
- commandHandlers.set(commandId, handler);
256
- }
257
- return () => {
258
- for (const [commandId, handler] of Object.entries(handlers)) {
259
- if (commandHandlers.get(commandId) === handler) {
260
- commandHandlers.delete(commandId);
261
- }
262
- }
263
- };
264
- },
265
- onOsConfigUpdate(callback) {
266
- osConfigUpdateCallbacks.add(callback);
267
- return () => {
268
- osConfigUpdateCallbacks.delete(callback);
269
- };
270
- },
271
- /**
272
- * @deprecated Legacy/internal only. Hosted apps should use scoped SDK APIs
273
- * such as `idb`, `transport`, and `launchApp` instead of kernel-proxy.
274
- */
275
- sendToKernel(kernelType, kernelPayload) {
276
- return request(SDK_MSG.KERNEL_PROXY, { kernelType, kernelPayload });
277
- },
278
- launchApp(appId) {
279
- return request(SDK_MSG.LAUNCH_APP, { appId });
280
- },
281
- destroy() {
282
- if (_destroyed)
283
- return;
284
- log('bridge.destroy(), pending requests:', pending.size);
285
- // Best-effort unsubscribe every open stream before flipping the flag.
286
- for (const subscriptionId of eventHandlers.keys()) {
287
- try {
288
- send(SDK_MSG.EVENT_UNSUBSCRIBE, { subscriptionId });
289
- }
290
- catch {
291
- // ignore — host will drop the frame on window close anyway
292
- }
293
- }
294
- eventHandlers.clear();
295
- _destroyed = true;
296
- hostWindow.removeEventListener('message', onMessage);
297
- for (const [, p] of pending)
298
- p.reject(new Error('Bridge destroyed'));
299
- pending.clear();
300
- commandHandlers.clear();
301
- _connected = false;
302
- _context = null;
303
- },
304
- };
305
- return bridge;
306
- }
@@ -1,24 +0,0 @@
1
- import type { BackendRequestOptions, SdkTransportPayload } from '../types.js';
2
- export declare function isRouteKey(routeKey: string): boolean;
3
- /**
4
- * Client for an application's own REST backend, reached through the BFF relay at
5
- * `/relay/{routeKey}`. The host stamps the calling app id and scope; the BFF checks the
6
- * app against the route's whitelist and forwards the request with an app-scoped token.
7
- */
8
- export declare class BackendTransport {
9
- private _request;
10
- private readonly routeKey;
11
- constructor(_request: <T = unknown>(payload: SdkTransportPayload) => Promise<T>, routeKey: string);
12
- /**
13
- * Send a request to the backend. `path` is what the backend sees, after the relay
14
- * stripped `/relay/{routeKey}`. Scope is required unless stated otherwise: the relay
15
- * refuses an app-scoped call without one.
16
- *
17
- * @throws {EntityNotFoundError} on 404 errors
18
- * @throws {ValidationError} on 400 errors
19
- * @throws {PermissionError} on 403 errors (app not whitelisted for the route)
20
- * @throws {NetworkError} on HTTP errors, including 502 when the backend is unreachable
21
- * @throws {TimeoutError} on request timeout
22
- */
23
- request<T = unknown>(options: BackendRequestOptions): Promise<T>;
24
- }