@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/dist/index.d.ts CHANGED
@@ -1,8 +1,11 @@
1
- export { createHostBridge } from './host-bridge.js';
1
+ export type { GraphApi } from './api/graph.js';
2
+ export type { OsApi } from './api/os.js';
3
+ export type { RelayApi } from './api/relay.js';
4
+ export { connect } from './client.js';
5
+ export type { Bardioc } from './client.js';
6
+ export { BardiocError } from './errors.js';
7
+ export type { ErrorCode } from './errors.js';
8
+ export type { AppRecord, AppUpload, BffRequest, Command, Context, GraphEvent, IdbEntry, InstalledApp, MenuItem, MenuRoot, NotifyLevel, OsConfig, Shortcut, UploadResult, } from './contract.js';
2
9
  export { DEFAULT_WINDOW_SIZE, MANIFEST_FILENAME, WINDOW_SIZES, validateManifest, } from './manifest.js';
3
- export type { AppManifest, AppManifestPermission, AppManifestWindowConfig, CustomWindowSize, WindowSizePreset, } from './manifest.js';
4
- export { SDK_CONTRACT_MATRIX, getRequiredPermissionForSdkMessage } from './contract-matrix.js';
5
- export { ALLOWED_KERNEL_MESSAGES, SDK_MSG } from './protocol.js';
6
- export type { SdkMessageType } from './protocol.js';
7
- export { EntityNotFoundError, NetworkError, PermissionError, SdkError, TimeoutError, ValidationError, } from './errors.js';
8
- export type { Application, BackendRequestOptions, BackendTransport, BatchResult, CombinedQueryOptions, Configuration, CreateMemberInput, CreateUnitInput, DeleteOptions, EventSubscribeRequest, EventSubscribeResponse, EventUnsubscribeRequest, EventsApi, FileEntry, GetNodeOptions, GraphEdgeRaw, GraphEvent, GraphEventPushPayload, GraphEventType, GraphNodeRaw, GraphTransport, GremlinOptions, HistoryEntry, HistoryOptions, HostBridge, HostBridgeConfig, InstanceList, MutationOptions, NotifyLevel, OrgMember, OrgStructure, OrgUnit, OsConfig, OsRequestOptions, OsTransport, QueryOptions, SdkCommandDefinition, SdkCommandHandler, SdkCommandInvokePayload, SdkIdbDeletePayload, SdkIdbEntry, SdkIdbGetPayload, SdkIdbPutPayload, SdkIdbQueryPayload, SdkIdbStore, SdkInitResponse, SdkKernelProxyPayload, SdkMenuItem, SdkMenuRoot, SdkMessage, SdkNotifyPayload, SdkResponse, SdkSetActiveTabPayload, SdkSetCommandsPayload, SdkSetMenuPayload, SdkShortcutEventPayload, SdkStorage, SdkStorageDeletePayload, SdkStorageGetPayload, SdkStorageSetPayload, SdkTransport, SdkTransportPayload, TimeseriesAggregate, TimeseriesHistory, TimeseriesHistoryOptions, TimeseriesOptions, TimeseriesQueryOptions, TimeseriesValue, TransportScopeOptions, UpdateMemberInput, UpdateUnitInput, UserProfile, } from './types.js';
10
+ export type { AppManifest, AppManifestRole, AppManifestRules, AppManifestWindowConfig, CustomWindowSize, WindowSizePreset, } from './manifest.js';
11
+ export type { Application, BatchResult, CombinedQueryOptions, Configuration, CreateMemberInput, CreateUnitInput, DeleteOptions, FileEntry, GetNodeOptions, GraphEdgeRaw, GraphNodeRaw, GremlinOptions, HistoryEntry, HistoryOptions, InstanceList, MutationOptions, OrgMember, OrgStructure, OrgUnit, QueryOptions, TimeseriesAggregate, TimeseriesHistory, TimeseriesHistoryOptions, TimeseriesOptions, TimeseriesQueryOptions, TimeseriesValue, TransportScopeOptions, UpdateMemberInput, UpdateUnitInput, UserProfile, } from './types.js';
package/dist/index.js CHANGED
@@ -1,5 +1,3 @@
1
- export { createHostBridge } from './host-bridge.js';
1
+ export { connect } from './client.js';
2
+ export { BardiocError } from './errors.js';
2
3
  export { DEFAULT_WINDOW_SIZE, MANIFEST_FILENAME, WINDOW_SIZES, validateManifest, } from './manifest.js';
3
- export { SDK_CONTRACT_MATRIX, getRequiredPermissionForSdkMessage } from './contract-matrix.js';
4
- export { ALLOWED_KERNEL_MESSAGES, SDK_MSG } from './protocol.js';
5
- export { EntityNotFoundError, NetworkError, PermissionError, SdkError, TimeoutError, ValidationError, } from './errors.js';
@@ -1,5 +1,4 @@
1
1
  /** Schema for `public/app-manifest.json` — required in every iframe app's build output. */
2
- export type AppManifestPermission = 'notify' | 'storage' | 'indexdb' | 'kernel-proxy' | 'transport' | 'events';
3
2
  export interface CustomWindowSize {
4
3
  width: number;
5
4
  height: number;
@@ -51,6 +50,15 @@ export interface AppManifestWindowConfig {
51
50
  centered?: boolean;
52
51
  isHeaderless?: boolean;
53
52
  }
53
+ /** Graph access of one role the app declares. */
54
+ export interface AppManifestRole {
55
+ vertexRule?: string;
56
+ edgeRule?: string;
57
+ }
58
+ export interface AppManifestRules {
59
+ /** Role name to its graph access; the names are the roles the app requests at review. */
60
+ roles?: Record<string, AppManifestRole>;
61
+ }
54
62
  export interface AppManifest {
55
63
  /** Schema version. Use `2` for new apps. */
56
64
  manifestVersion: 1 | 2;
@@ -65,11 +73,11 @@ export interface AppManifest {
65
73
  name: string;
66
74
  url?: string;
67
75
  };
68
- permissions?: AppManifestPermission[];
69
76
  categories?: string[];
70
77
  showInDock?: boolean;
71
78
  showInDockAppsMenu?: boolean;
72
79
  window?: AppManifestWindowConfig;
80
+ rules?: AppManifestRules;
73
81
  }
74
82
  export declare const MANIFEST_FILENAME = "app-manifest.json";
75
83
  export declare function validateManifest(data: unknown): {
package/dist/manifest.js CHANGED
@@ -10,15 +10,6 @@ export const WINDOW_SIZES = {
10
10
  // size used when an app declares no window.size
11
11
  export const DEFAULT_WINDOW_SIZE = 'md';
12
12
  export const MANIFEST_FILENAME = 'app-manifest.json';
13
- const VALID_PERMISSIONS = new Set([
14
- 'notify',
15
- 'storage',
16
- 'indexdb',
17
- // Deprecated: legacy/internal only. Do not request for normal hosted apps.
18
- 'kernel-proxy',
19
- 'transport',
20
- 'events',
21
- ]);
22
13
  function validateBooleanField(value, fieldPath, errors) {
23
14
  if (value !== undefined && typeof value !== 'boolean') {
24
15
  errors.push(`${fieldPath} must be a boolean`);
@@ -120,20 +111,6 @@ function validateAuthor(m, errors) {
120
111
  }
121
112
  }
122
113
  }
123
- function validatePermissions(m, errors) {
124
- if (m.permissions !== undefined) {
125
- if (!Array.isArray(m.permissions)) {
126
- errors.push('permissions must be an array');
127
- }
128
- else {
129
- for (const perm of m.permissions) {
130
- if (typeof perm !== 'string' || !VALID_PERMISSIONS.has(perm)) {
131
- errors.push(`Invalid permission: "${String(perm)}". Valid: ${[...VALID_PERMISSIONS].join(', ')}`);
132
- }
133
- }
134
- }
135
- }
136
- }
137
114
  function validateCategories(m, errors) {
138
115
  if (m.categories !== undefined) {
139
116
  if (!Array.isArray(m.categories)) {
@@ -147,6 +124,31 @@ function validateCategories(m, errors) {
147
124
  }
148
125
  }
149
126
  }
127
+ const isPlainObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
128
+ // The rule grammar is checked at review, so only the shape is validated here.
129
+ function validateRules(rules, errors) {
130
+ if (!isPlainObject(rules)) {
131
+ errors.push('rules must be an object');
132
+ return;
133
+ }
134
+ if (rules.roles === undefined)
135
+ return;
136
+ if (!isPlainObject(rules.roles)) {
137
+ errors.push('rules.roles must be an object');
138
+ return;
139
+ }
140
+ for (const [name, role] of Object.entries(rules.roles)) {
141
+ if (!isPlainObject(role)) {
142
+ errors.push(`rules.roles.${name} must be an object`);
143
+ continue;
144
+ }
145
+ for (const field of ['vertexRule', 'edgeRule']) {
146
+ if (role[field] !== undefined && typeof role[field] !== 'string') {
147
+ errors.push(`rules.roles.${name}.${field} must be a string`);
148
+ }
149
+ }
150
+ }
151
+ }
150
152
  function validateDockFields(m, errors) {
151
153
  validateBooleanField(m.showInDock, 'showInDock', errors);
152
154
  validateBooleanField(m.showInDockAppsMenu, 'showInDockAppsMenu', errors);
@@ -160,11 +162,13 @@ export function validateManifest(data) {
160
162
  validateCoreFields(m, errors);
161
163
  validateOptionalFields(m, errors);
162
164
  validateAuthor(m, errors);
163
- validatePermissions(m, errors);
164
165
  validateCategories(m, errors);
165
166
  validateDockFields(m, errors);
166
167
  if (m.window !== undefined) {
167
168
  validateWindowConfig(m.window, errors);
168
169
  }
170
+ if (m.rules !== undefined) {
171
+ validateRules(m.rules, errors);
172
+ }
169
173
  return { valid: errors.length === 0, errors };
170
174
  }
@@ -1,29 +1,6 @@
1
- export type { EventSubscribeRequest, EventSubscribeResponse, EventUnsubscribeRequest, GraphEvent, GraphEventPushPayload, GraphEventType, SdkCommandDefinition, SdkCommandInvokePayload, SdkIdbDeletePayload, SdkIdbGetPayload, SdkIdbPutPayload, SdkIdbQueryPayload, SdkMenuItem, SdkMenuRoot, SdkMessage, SdkNotifyPayload, SdkResponse, SdkSetActiveTabPayload, SdkSetCommandsPayload, SdkSetMenuPayload, SdkShortcutEventPayload, SdkStorageGetPayload, SdkStorageSetPayload, SdkTransportPayload, } from './types.js';
2
- export declare const ALLOWED_KERNEL_MESSAGES: ReadonlySet<string>;
3
- export declare const SDK_MSG: {
4
- readonly INIT: "SDK_INIT";
5
- readonly INIT_ACK: "SDK_INIT_ACK";
6
- readonly RESPONSE: "SDK_RESPONSE";
7
- readonly NOTIFY: "SDK_NOTIFY";
8
- readonly KERNEL_PROXY: "SDK_KERNEL_PROXY";
9
- readonly TRANSPORT: "SDK_TRANSPORT";
10
- readonly SET_COMMANDS: "SDK_SET_COMMANDS";
11
- readonly SET_MENU: "SDK_SET_MENU";
12
- readonly SET_ACTIVE_TAB: "SDK_SET_ACTIVE_TAB";
13
- readonly SHORTCUT_EVENT: "SDK_SHORTCUT_EVENT";
14
- readonly COMMAND_INVOKE: "SDK_COMMAND_INVOKE";
15
- readonly STORAGE_GET: "SDK_STORAGE_GET";
16
- readonly STORAGE_SET: "SDK_STORAGE_SET";
17
- readonly STORAGE_DELETE: "SDK_STORAGE_DELETE";
18
- readonly STORAGE_KEYS: "SDK_STORAGE_KEYS";
19
- readonly IDB_PUT: "SDK_IDB_PUT";
20
- readonly IDB_GET: "SDK_IDB_GET";
21
- readonly IDB_DELETE: "SDK_IDB_DELETE";
22
- readonly IDB_QUERY: "SDK_IDB_QUERY";
23
- readonly LAUNCH_APP: "SDK_LAUNCH_APP";
24
- readonly OS_CONFIG_UPDATE: "SDK_OS_CONFIG_UPDATE";
25
- readonly EVENT_SUBSCRIBE: "SDK_EVENT_SUBSCRIBE";
26
- readonly EVENT_UNSUBSCRIBE: "SDK_EVENT_UNSUBSCRIBE";
27
- readonly GRAPH_EVENT: "SDK_GRAPH_EVENT";
28
- };
29
- export type SdkMessageType = (typeof SDK_MSG)[keyof typeof SDK_MSG];
1
+ export { APP_METHODS, GRAPH_EVENT_TYPES, HOST_METHODS, PROTOCOL_VERSION, graphEventFilter, } from './contract.js';
2
+ export type { AppMethods, AppRecord, AppUpload, BffRequest, CallMethod, Command, Context, GraphEvent, HostEvents, HostMethods, IdbEntry, InstalledApp, MenuItem, MenuRoot, Method, NotifyLevel, OsConfig, Params, Result, Shortcut, StreamMethod, UploadResult, } from './contract.js';
3
+ export { BardiocError } from './errors.js';
4
+ export type { ErrorCode, WireError } from './errors.js';
5
+ export { acceptApp, serve } from './serve.js';
6
+ export type { AcceptOptions, Connection, HostHandlers } from './serve.js';
package/dist/protocol.js CHANGED
@@ -1,31 +1,3 @@
1
- // host ↔ iframe postMessage protocol — wire-format types and constants
2
- // Legacy/internal kernel proxy allow-list. Generic resource CRUD is intentionally
3
- // not iframe-reachable; apps should use scoped SDK APIs such as idb/transport.
4
- export const ALLOWED_KERNEL_MESSAGES = new Set(['PING']);
5
- // All postMessage type strings used in the SDK protocol
6
- export const SDK_MSG = {
7
- INIT: 'SDK_INIT',
8
- INIT_ACK: 'SDK_INIT_ACK',
9
- RESPONSE: 'SDK_RESPONSE',
10
- NOTIFY: 'SDK_NOTIFY',
11
- KERNEL_PROXY: 'SDK_KERNEL_PROXY',
12
- TRANSPORT: 'SDK_TRANSPORT',
13
- SET_COMMANDS: 'SDK_SET_COMMANDS',
14
- SET_MENU: 'SDK_SET_MENU',
15
- SET_ACTIVE_TAB: 'SDK_SET_ACTIVE_TAB',
16
- SHORTCUT_EVENT: 'SDK_SHORTCUT_EVENT',
17
- COMMAND_INVOKE: 'SDK_COMMAND_INVOKE',
18
- STORAGE_GET: 'SDK_STORAGE_GET',
19
- STORAGE_SET: 'SDK_STORAGE_SET',
20
- STORAGE_DELETE: 'SDK_STORAGE_DELETE',
21
- STORAGE_KEYS: 'SDK_STORAGE_KEYS',
22
- IDB_PUT: 'SDK_IDB_PUT',
23
- IDB_GET: 'SDK_IDB_GET',
24
- IDB_DELETE: 'SDK_IDB_DELETE',
25
- IDB_QUERY: 'SDK_IDB_QUERY',
26
- LAUNCH_APP: 'SDK_LAUNCH_APP',
27
- OS_CONFIG_UPDATE: 'SDK_OS_CONFIG_UPDATE',
28
- EVENT_SUBSCRIBE: 'SDK_EVENT_SUBSCRIBE',
29
- EVENT_UNSUBSCRIBE: 'SDK_EVENT_UNSUBSCRIBE',
30
- GRAPH_EVENT: 'SDK_GRAPH_EVENT',
31
- };
1
+ export { APP_METHODS, GRAPH_EVENT_TYPES, HOST_METHODS, PROTOCOL_VERSION, graphEventFilter, } from './contract.js';
2
+ export { BardiocError } from './errors.js';
3
+ export { acceptApp, serve } from './serve.js';
package/dist/react.d.ts CHANGED
@@ -1,31 +1,27 @@
1
- import { type ReactNode } from 'react';
2
- import type { GraphEvent, HostBridge, HostBridgeConfig, NotifyLevel, OsConfig, SdkCommandDefinition, SdkCommandHandler, SdkMenuRoot } from './types.js';
3
- export interface AppSdkProviderProps extends HostBridgeConfig {
1
+ import { type DependencyList, type ReactNode } from 'react';
2
+ import { type Bardioc } from './client.js';
3
+ import type { Command, GraphEvent, MenuRoot, NotifyLevel, OsConfig } from './contract.js';
4
+ export interface AppSdkProviderProps {
4
5
  children: ReactNode;
6
+ /** Apply theme, language and size to this document. Default: `true`. */
7
+ applyOsConfig?: boolean;
5
8
  }
6
- export interface AppCommand extends SdkCommandDefinition {
7
- handler: SdkCommandHandler;
8
- }
9
- /** Mounts the SDK bridge and makes it available to descendant hooks. */
10
- export declare function AppSdkProvider({ children, ...config }: Readonly<AppSdkProviderProps>): import("react/jsx-runtime").JSX.Element | null;
11
- /** Returns the raw {@link HostBridge}. Must be used inside `<AppSdkProvider>`. */
12
- export declare function useSdk(): HostBridge;
13
- /** Returns a stable callback that fires a host notification toast. */
9
+ /** Connects to the host and renders `children` once connected. */
10
+ export declare function AppSdkProvider({ children, applyOsConfig: apply, }: Readonly<AppSdkProviderProps>): import("react/jsx-runtime").JSX.Element | null;
11
+ /** The connection to the host. Must be used inside `<AppSdkProvider>`. */
12
+ export declare function useSdk(): Bardioc;
13
+ /** A stable callback that records a notification in the host. */
14
14
  export declare function useNotify(): (message: string, level?: NotifyLevel) => void;
15
- /** Returns a stable callback that proxies a message to the OS service worker. */
16
- export declare function useSendToKernel(): <R = unknown>(kernelType: string, kernelPayload?: unknown) => Promise<R>;
17
- export declare function useCommandMenu(commands: AppCommand[], menu: SdkMenuRoot[] | null): void;
15
+ export interface AppCommand extends Command {
16
+ handler: () => unknown;
17
+ }
18
+ /** Publishes the app's commands and menu to the host while mounted. */
19
+ export declare function useCommandMenu(commands: AppCommand[], menu: MenuRoot[] | null): void;
20
+ /** Tells the host which tab the app shows. */
18
21
  export declare function useActiveTab(activeTab: string | null): void;
19
- /**
20
- * Returns the current OS configuration (theme, language, systemSize) from the host.
21
- * Automatically subscribes to updates and returns the latest values.
22
- */
23
- export declare function useOsConfig(): OsConfig | null;
24
- /**
25
- * Subscribes to live graph events for the given OGIT types while mounted.
26
- * The handler is kept in a ref so its identity may change without resubscribing;
27
- * the subscription is recreated only when `types` (or extra `deps`) change.
28
- */
29
- export declare function useGraphEvents(options: {
22
+ /** The host's current theme, language and size. */
23
+ export declare function useOsConfig(): OsConfig;
24
+ /** Streams graph events of `types` while mounted; `handler` may change without resubscribing. */
25
+ export declare function useGraphEvents({ types }: {
30
26
  types: string[];
31
- }, handler: (event: GraphEvent) => void, deps?: React.DependencyList): void;
27
+ }, handler: (event: GraphEvent) => void, deps?: DependencyList): void;
package/dist/react.js CHANGED
@@ -1,221 +1,128 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { createContext, useCallback, useContext, useEffect, useRef, useState, } from 'react';
3
- import { createHostBridge } from './host-bridge.js';
4
- const SdkContext = createContext(null);
5
- const SYSTEM_SIZE_CLASS_NAMES = ['text-sm', 'text-base', 'text-lg', 'text-xl'];
6
- function applyOsConfigToDocument(osConfig) {
7
- if (!osConfig) {
8
- return;
9
- }
3
+ import { connect } from './client.js';
4
+ const BardiocContext = createContext(null);
5
+ const reportFailure = (what) => (error) => console.error(`[app-sdk] ${what} failed`, error);
6
+ const SYSTEM_SIZES = ['text-sm', 'text-base', 'text-lg', 'text-xl'];
7
+ function applyOsConfig({ theme, language, systemSize }) {
10
8
  const root = document.documentElement;
11
- const isDark = osConfig.theme === 'dark';
12
- root.classList.toggle('dark', isDark);
13
- // Advertise the resolved theme to the UA so externally-referenced SVGs (app icon.svg
14
- // via <img>, favicons) theme-switch through @media (prefers-color-scheme). Mirrors
15
- // webos-host (next-themes enableColorScheme) and Storybook's preview decorator.
16
- root.style.colorScheme = isDark ? 'dark' : 'light';
17
- if (osConfig.language) {
18
- root.lang = osConfig.language;
19
- }
20
- root.classList.remove(...SYSTEM_SIZE_CLASS_NAMES);
21
- if (osConfig.systemSize && SYSTEM_SIZE_CLASS_NAMES.includes(osConfig.systemSize)) {
22
- root.classList.add(osConfig.systemSize);
23
- }
9
+ root.classList.toggle('dark', theme === 'dark');
10
+ // Externally referenced SVGs (icons, favicons) theme through prefers-color-scheme.
11
+ root.style.colorScheme = theme;
12
+ root.lang = language;
13
+ root.classList.remove(...SYSTEM_SIZES);
14
+ if (systemSize && SYSTEM_SIZES.includes(systemSize))
15
+ root.classList.add(systemSize);
24
16
  }
25
- function hasShortcutItems(items) {
26
- return items.some(item => {
27
- switch (item.type) {
28
- case 'command':
29
- return typeof item.shortcut === 'string' && item.shortcut.trim().length > 0;
30
- case 'submenu':
31
- return hasShortcutItems(item.items);
32
- }
33
- });
34
- }
35
- function hasShortcutRoots(menu) {
36
- if (!menu?.length) {
37
- return false;
38
- }
39
- return menu.some(root => hasShortcutItems(root.items));
40
- }
41
- /** Mounts the SDK bridge and makes it available to descendant hooks. */
42
- export function AppSdkProvider({ children, ...config }) {
43
- const bridgeRef = useRef(null);
44
- const { appId, connectTimeout, debug, targetOrigin } = config;
45
- const configKey = JSON.stringify([
46
- appId ?? null,
47
- connectTimeout ?? null,
48
- debug ?? false,
49
- targetOrigin ?? null,
50
- ]);
51
- const bridgeConfig = { appId, connectTimeout, debug, targetOrigin };
52
- const bridgeConfigKeyRef = useRef(null);
53
- const [readyConfigKey, setReadyConfigKey] = useState(null);
54
- const [errorState, setErrorState] = useState(null);
17
+ /** Connects to the host and renders `children` once connected. */
18
+ export function AppSdkProvider({ children, applyOsConfig: apply = true, }) {
19
+ const [bardioc, setBardioc] = useState(null);
20
+ const [error, setError] = useState(null);
55
21
  useEffect(() => {
56
- let cancelled = false;
57
- setReadyConfigKey(null);
58
- setErrorState(null);
59
- const bridge = createHostBridge(bridgeConfig);
60
- bridgeRef.current = bridge;
61
- bridgeConfigKeyRef.current = configKey;
62
- bridge
63
- .connect()
64
- .then(() => {
65
- if (!cancelled) {
66
- setReadyConfigKey(configKey);
67
- }
68
- })
69
- .catch((err) => {
70
- if (!cancelled) {
71
- bridgeRef.current = null;
72
- bridgeConfigKeyRef.current = null;
73
- setErrorState({ configKey, message: err.message });
74
- }
75
- });
22
+ let active = true;
23
+ connect().then(connected => active && setBardioc(connected), (failure) => active && setError(failure.message));
76
24
  return () => {
77
- cancelled = true;
78
- bridge.destroy();
79
- if (bridgeRef.current === bridge) {
80
- bridgeRef.current = null;
81
- }
82
- if (bridgeConfigKeyRef.current === configKey) {
83
- bridgeConfigKeyRef.current = null;
84
- }
25
+ active = false;
85
26
  };
86
- }, [appId, configKey, connectTimeout, debug, targetOrigin]);
27
+ }, []);
87
28
  useEffect(() => {
88
- if (readyConfigKey !== configKey ||
89
- bridgeConfigKeyRef.current !== configKey ||
90
- !bridgeRef.current) {
29
+ if (!apply || !bardioc)
91
30
  return;
92
- }
93
- applyOsConfigToDocument(bridgeRef.current.context?.osConfig ?? null);
94
- const unsubscribe = bridgeRef.current.onOsConfigUpdate(newConfig => {
95
- applyOsConfigToDocument(newConfig);
96
- });
97
- return unsubscribe;
98
- }, [configKey, readyConfigKey]);
99
- if (errorState?.configKey === configKey) {
100
- return _jsx("div", { "data-sdk-error": true, children: errorState.message });
101
- }
102
- if (readyConfigKey !== configKey ||
103
- bridgeConfigKeyRef.current !== configKey ||
104
- !bridgeRef.current) {
31
+ applyOsConfig(bardioc.context.osConfig);
32
+ return bardioc.onContextChange(context => applyOsConfig(context.osConfig));
33
+ }, [apply, bardioc]);
34
+ if (error)
35
+ return _jsx("div", { "data-sdk-error": true, children: error });
36
+ if (!bardioc)
105
37
  return null;
106
- }
107
- return _jsx(SdkContext.Provider, { value: bridgeRef.current, children: children });
38
+ return _jsx(BardiocContext.Provider, { value: bardioc, children: children });
108
39
  }
109
- /** Returns the raw {@link HostBridge}. Must be used inside `<AppSdkProvider>`. */
40
+ /** The connection to the host. Must be used inside `<AppSdkProvider>`. */
110
41
  export function useSdk() {
111
- const bridge = useContext(SdkContext);
112
- if (!bridge)
42
+ const bardioc = useContext(BardiocContext);
43
+ if (!bardioc)
113
44
  throw new Error('useSdk() must be used inside <AppSdkProvider>');
114
- return bridge;
45
+ return bardioc;
115
46
  }
116
- /** Returns a stable callback that fires a host notification toast. */
47
+ /** A stable callback that records a notification in the host. */
117
48
  export function useNotify() {
118
- const bridge = useSdk();
49
+ const { ui } = useSdk();
119
50
  return useCallback((message, level) => {
120
- bridge.notify(message, level);
121
- }, [bridge]);
122
- }
123
- /** Returns a stable callback that proxies a message to the OS service worker. */
124
- export function useSendToKernel() {
125
- const bridge = useSdk();
126
- return useCallback((kernelType, kernelPayload) => bridge.sendToKernel(kernelType, kernelPayload), [bridge]);
51
+ ui.notify(message, level).catch(reportFailure('notify'));
52
+ }, [ui]);
127
53
  }
54
+ const hasShortcut = (items) => items.some(item => item.type === 'command' ? Boolean(item.shortcut?.trim()) : hasShortcut(item.items));
55
+ /** Publishes the app's commands and menu to the host while mounted. */
128
56
  export function useCommandMenu(commands, menu) {
129
- const bridge = useSdk();
57
+ const { ui } = useSdk();
130
58
  useEffect(() => {
131
- const unregister = bridge.registerCommandHandlers(Object.fromEntries(commands.map(command => [command.id, command.handler])));
132
- return unregister;
133
- }, [bridge, commands]);
59
+ const unregister = commands.map(({ id, handler }) => ui.onCommand(id, handler));
60
+ ui.setCommands(commands.map(({ handler: _, ...command }) => command)).catch(reportFailure('setCommands'));
61
+ return () => unregister.forEach(stop => stop());
62
+ }, [ui, commands]);
134
63
  useEffect(() => {
135
- const commandState = commands.map(({ handler: _handler, ...command }) => command);
136
- void bridge.setCommands(commandState).catch((error) => {
137
- console.error('[app-sdk] failed to sync command state', error);
138
- });
139
- void bridge.setMenu(menu).catch((error) => {
140
- console.error('[app-sdk] failed to sync app menu', error);
141
- });
142
- }, [bridge, commands, menu]);
64
+ ui.setMenu(menu).catch(reportFailure('setMenu'));
65
+ }, [ui, menu]);
143
66
  useEffect(() => {
144
- if (!hasShortcutRoots(menu)) {
67
+ if (!menu?.some(root => hasShortcut(root.items)))
145
68
  return;
146
- }
147
- const handleKeyDown = (event) => {
148
- if (event.defaultPrevented || event.isComposing || event.repeat) {
69
+ const onKeyDown = (event) => {
70
+ if (event.defaultPrevented || event.isComposing || event.repeat)
149
71
  return;
150
- }
151
- if (!event.metaKey && !event.ctrlKey && !event.altKey) {
72
+ if (!event.metaKey && !event.ctrlKey && !event.altKey)
152
73
  return;
153
- }
154
- bridge.sendShortcutEvent({
155
- code: event.code,
156
- ctrlKey: event.ctrlKey,
157
- metaKey: event.metaKey,
158
- altKey: event.altKey,
159
- shiftKey: event.shiftKey,
160
- });
161
- };
162
- window.addEventListener('keydown', handleKeyDown);
163
- return () => {
164
- window.removeEventListener('keydown', handleKeyDown);
74
+ const { code, ctrlKey, metaKey, altKey, shiftKey } = event;
75
+ ui.shortcut({ code, ctrlKey, metaKey, altKey, shiftKey }).catch(reportFailure('shortcut'));
165
76
  };
166
- }, [bridge, menu]);
167
- useEffect(() => {
168
- return () => {
169
- void bridge.setMenu(null).catch(() => undefined);
170
- void bridge.setCommands([]).catch(() => undefined);
171
- };
172
- }, [bridge]);
77
+ window.addEventListener('keydown', onKeyDown);
78
+ return () => window.removeEventListener('keydown', onKeyDown);
79
+ }, [ui, menu]);
80
+ useEffect(() => () => {
81
+ ui.setMenu(null).catch(reportFailure('setMenu'));
82
+ ui.setCommands([]).catch(reportFailure('setCommands'));
83
+ }, [ui]);
173
84
  }
174
- // interface for active tab for iframe app
85
+ /** Tells the host which tab the app shows. */
175
86
  export function useActiveTab(activeTab) {
176
- const bridge = useSdk();
87
+ const { ui } = useSdk();
177
88
  useEffect(() => {
178
- void bridge.setActiveTab(activeTab).catch((error) => {
179
- console.error('[app-sdk] failed to sync active tab', error);
180
- });
89
+ ui.setActiveTab(activeTab).catch(reportFailure('setActiveTab'));
181
90
  return () => {
182
- void bridge.setActiveTab(null).catch(() => undefined);
91
+ ui.setActiveTab(null).catch(reportFailure('setActiveTab'));
183
92
  };
184
- }, [bridge, activeTab]);
93
+ }, [ui, activeTab]);
185
94
  }
186
- /**
187
- * Returns the current OS configuration (theme, language, systemSize) from the host.
188
- * Automatically subscribes to updates and returns the latest values.
189
- */
95
+ /** The host's current theme, language and size. */
190
96
  export function useOsConfig() {
191
- const bridge = useSdk();
192
- const [osConfig, setOsConfig] = useState(() => bridge.context?.osConfig ?? null);
97
+ const bardioc = useSdk();
98
+ const [osConfig, setOsConfig] = useState(bardioc.context.osConfig);
193
99
  useEffect(() => {
194
- // Sync with current context on mount
195
- if (bridge.context?.osConfig) {
196
- setOsConfig(bridge.context.osConfig);
197
- }
198
- // Subscribe to OS config updates from host
199
- const unsubscribe = bridge.onOsConfigUpdate(newConfig => {
200
- setOsConfig(newConfig);
201
- });
202
- return unsubscribe;
203
- }, [bridge]);
100
+ setOsConfig(bardioc.context.osConfig);
101
+ return bardioc.onContextChange(context => setOsConfig(context.osConfig));
102
+ }, [bardioc]);
204
103
  return osConfig;
205
104
  }
206
- /**
207
- * Subscribes to live graph events for the given OGIT types while mounted.
208
- * The handler is kept in a ref so its identity may change without resubscribing;
209
- * the subscription is recreated only when `types` (or extra `deps`) change.
210
- */
211
- export function useGraphEvents(options, handler, deps = []) {
212
- const bridge = useSdk();
105
+ /** Streams graph events of `types` while mounted; `handler` may change without resubscribing. */
106
+ export function useGraphEvents({ types }, handler, deps = []) {
107
+ const { events } = useSdk();
213
108
  const handlerRef = useRef(handler);
214
109
  handlerRef.current = handler;
215
- const typesKey = JSON.stringify(options.types);
110
+ const typesKey = JSON.stringify(types);
216
111
  useEffect(() => {
217
- const unsubscribe = bridge.events.subscribe({ types: JSON.parse(typesKey) }, event => handlerRef.current(event));
218
- return unsubscribe;
112
+ let end;
113
+ let active = true;
114
+ events
115
+ .graph({ types: JSON.parse(typesKey) }, event => handlerRef.current(event))
116
+ .then(stop => {
117
+ if (active)
118
+ end = stop;
119
+ else
120
+ stop();
121
+ }, reportFailure('events.graph'));
122
+ return () => {
123
+ active = false;
124
+ end?.();
125
+ };
219
126
  // eslint-disable-next-line react-hooks/exhaustive-deps
220
- }, [bridge, typesKey, ...deps]);
127
+ }, [events, typesKey, ...deps]);
221
128
  }
@@ -0,0 +1,26 @@
1
+ import { type AppMethods, type CallMethod, type Context, type HostEvents, type HostMethods, type Method, type Params, type Result, type StreamMethod } from './contract.js';
2
+ type HostHandler<M extends Method> = M extends CallMethod ? (params: Params<M>, signal: AbortSignal) => Result<M> | Promise<Result<M>> : M extends StreamMethod ? (params: Params<M>, emit: (value: Result<M>) => void) => () => void : never;
3
+ /** What a host implements: one handler per app method. */
4
+ export type HostHandlers = {
5
+ [K in keyof AppMethods]: HostHandler<AppMethods[K]>;
6
+ };
7
+ /** A host's end of one app connection. */
8
+ export interface Connection {
9
+ call<K extends keyof HostMethods>(method: K, params: Params<HostMethods[K]>): Promise<Result<HostMethods[K]>>;
10
+ emit<K extends keyof HostEvents>(event: K, payload: HostEvents[K]): void;
11
+ close(): void;
12
+ }
13
+ /** Answers an app's calls on `port` with `handlers`, validating every call against the contract. */
14
+ export declare function serve(port: MessagePort, handlers: HostHandlers): Connection;
15
+ export interface AcceptOptions {
16
+ /** The app's window, read per message: a reload keeps the frame but not its document. */
17
+ source: () => Window | null | undefined;
18
+ origin: string;
19
+ context: () => Context;
20
+ onConnect: (port: MessagePort) => void;
21
+ /** An app speaking another protocol version; it has already been told. */
22
+ onReject?: (protocol: number) => void;
23
+ }
24
+ /** Waits for an app's `ready` and answers each one with a fresh port. */
25
+ export declare function acceptApp({ source, origin, context, onConnect, onReject }: AcceptOptions): () => void;
26
+ export {};