@bardioc/app-sdk 0.4.0 → 0.4.2

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.
@@ -108,6 +108,24 @@ export declare const SDK_CONTRACT_MATRIX: {
108
108
  readonly permission: null;
109
109
  readonly expectedHostBehavior: "Host-to-app command callback delivery.";
110
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
+ };
111
129
  readonly SDK_OS_CONFIG_UPDATE: {
112
130
  readonly requestPayload: "OsConfig";
113
131
  readonly responsePayload: null;
@@ -108,6 +108,24 @@ export const SDK_CONTRACT_MATRIX = {
108
108
  permission: null,
109
109
  expectedHostBehavior: 'Host-to-app command callback delivery.',
110
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
+ },
111
129
  [SDK_MSG.OS_CONFIG_UPDATE]: {
112
130
  requestPayload: 'OsConfig',
113
131
  responsePayload: null,
@@ -45,6 +45,7 @@ export function createHostBridge(config) {
45
45
  let _destroyed = false;
46
46
  const commandHandlers = new Map();
47
47
  const osConfigUpdateCallbacks = new Set();
48
+ const eventHandlers = new Map();
48
49
  const pending = new Map();
49
50
  function isAllowedMessageOrigin(origin) {
50
51
  return origin === targetOrigin || origin === hostWindow.location.origin;
@@ -80,6 +81,19 @@ export function createHostBridge(config) {
80
81
  }
81
82
  }
82
83
  }
84
+ function handleGraphEvent(data) {
85
+ const { subscriptionId, event } = data.payload;
86
+ const handler = eventHandlers.get(subscriptionId);
87
+ if (!handler) {
88
+ return;
89
+ }
90
+ try {
91
+ handler(event);
92
+ }
93
+ catch (error) {
94
+ console.error('[app-sdk] graph event handler error:', error);
95
+ }
96
+ }
83
97
  function handlePendingResponse(data) {
84
98
  const request = pending.get(data.id);
85
99
  if (!request) {
@@ -110,6 +124,9 @@ export function createHostBridge(config) {
110
124
  case SDK_MSG.OS_CONFIG_UPDATE:
111
125
  handleOsConfigUpdate(data);
112
126
  return;
127
+ case SDK_MSG.GRAPH_EVENT:
128
+ handleGraphEvent(data);
129
+ return;
113
130
  case SDK_MSG.INIT_ACK:
114
131
  case SDK_MSG.RESPONSE:
115
132
  handlePendingResponse(data);
@@ -178,6 +195,27 @@ export function createHostBridge(config) {
178
195
  }),
179
196
  };
180
197
  }
198
+ const events = {
199
+ subscribe({ types }, handler) {
200
+ const subscriptionId = nextId();
201
+ eventHandlers.set(subscriptionId, handler);
202
+ // Fire-and-forget the ack: the app-generated id removes any race, so a
203
+ // failed ack only means no events will arrive — surface it and clean up.
204
+ request(SDK_MSG.EVENT_SUBSCRIBE, { subscriptionId, types }).catch((error) => {
205
+ eventHandlers.delete(subscriptionId);
206
+ console.error('[app-sdk] events.subscribe failed:', error);
207
+ });
208
+ return () => {
209
+ if (!eventHandlers.delete(subscriptionId)) {
210
+ return;
211
+ }
212
+ if (_destroyed) {
213
+ return;
214
+ }
215
+ send(SDK_MSG.EVENT_UNSUBSCRIBE, { subscriptionId });
216
+ };
217
+ },
218
+ };
181
219
  const bridge = {
182
220
  get connected() {
183
221
  return _connected;
@@ -199,6 +237,7 @@ export function createHostBridge(config) {
199
237
  storage,
200
238
  transport,
201
239
  idb,
240
+ events,
202
241
  setCommands(commands) {
203
242
  const payload = { commands };
204
243
  return request(SDK_MSG.SET_COMMANDS, payload);
@@ -246,6 +285,16 @@ export function createHostBridge(config) {
246
285
  if (_destroyed)
247
286
  return;
248
287
  log('bridge.destroy(), pending requests:', pending.size);
288
+ // Best-effort unsubscribe every open stream before flipping the flag.
289
+ for (const subscriptionId of eventHandlers.keys()) {
290
+ try {
291
+ send(SDK_MSG.EVENT_UNSUBSCRIBE, { subscriptionId });
292
+ }
293
+ catch {
294
+ // ignore — host will drop the frame on window close anyway
295
+ }
296
+ }
297
+ eventHandlers.clear();
249
298
  _destroyed = true;
250
299
  hostWindow.removeEventListener('message', onMessage);
251
300
  for (const [, p] of pending)
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  export { createHostBridge } from './host-bridge.js';
2
2
  export { DEFAULT_WINDOW_SIZE, MANIFEST_FILENAME, WINDOW_SIZES, validateManifest, } from './manifest.js';
3
3
  export type { AppManifest, AppManifestPermission, AppManifestWindowConfig, CustomWindowSize, WindowSizePreset, } from './manifest.js';
4
- export { useOsConfig } from './react.js';
4
+ export { useGraphEvents, useOsConfig } from './react.js';
5
5
  export { SDK_CONTRACT_MATRIX, getRequiredPermissionForSdkMessage } from './contract-matrix.js';
6
6
  export { ALLOWED_KERNEL_MESSAGES, SDK_MSG } from './protocol.js';
7
7
  export type { SdkMessageType } from './protocol.js';
8
8
  export { EntityNotFoundError, NetworkError, PermissionError, SdkError, TimeoutError, ValidationError, } from './errors.js';
9
- export type { Application, BatchResult, Configuration, CreateMemberInput, CreateUnitInput, DeleteOptions, FileEntry, GetNodeOptions, GraphEdgeRaw, 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, TimeseriesOptions, TimeseriesQueryOptions, TimeseriesValue, TransportScopeOptions, UpdateMemberInput, UpdateUnitInput, UserProfile, } from './types.js';
9
+ export type { Application, BatchResult, 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, TimeseriesOptions, TimeseriesQueryOptions, TimeseriesValue, TransportScopeOptions, UpdateMemberInput, UpdateUnitInput, UserProfile, } from './types.js';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export { createHostBridge } from './host-bridge.js';
2
2
  export { DEFAULT_WINDOW_SIZE, MANIFEST_FILENAME, WINDOW_SIZES, validateManifest, } from './manifest.js';
3
- export { useOsConfig } from './react.js';
3
+ export { useGraphEvents, useOsConfig } from './react.js';
4
4
  export { SDK_CONTRACT_MATRIX, getRequiredPermissionForSdkMessage } from './contract-matrix.js';
5
5
  export { ALLOWED_KERNEL_MESSAGES, SDK_MSG } from './protocol.js';
6
6
  export { EntityNotFoundError, NetworkError, PermissionError, SdkError, TimeoutError, ValidationError, } from './errors.js';
@@ -1,5 +1,5 @@
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';
2
+ export type AppManifestPermission = 'notify' | 'storage' | 'indexdb' | 'kernel-proxy' | 'transport' | 'events';
3
3
  export interface CustomWindowSize {
4
4
  width: number;
5
5
  height: number;
package/dist/manifest.js CHANGED
@@ -17,6 +17,7 @@ const VALID_PERMISSIONS = new Set([
17
17
  // Deprecated: legacy/internal only. Do not request for normal hosted apps.
18
18
  'kernel-proxy',
19
19
  'transport',
20
+ 'events',
20
21
  ]);
21
22
  function validateBooleanField(value, fieldPath, errors) {
22
23
  if (value !== undefined && typeof value !== 'boolean') {
@@ -1,4 +1,4 @@
1
- export type { SdkCommandDefinition, SdkCommandInvokePayload, SdkIdbDeletePayload, SdkIdbGetPayload, SdkIdbPutPayload, SdkIdbQueryPayload, SdkMenuItem, SdkMenuRoot, SdkMessage, SdkNotifyPayload, SdkResponse, SdkSetActiveTabPayload, SdkSetCommandsPayload, SdkSetMenuPayload, SdkShortcutEventPayload, SdkStorageGetPayload, SdkStorageSetPayload, SdkTransportPayload, } from './types.js';
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
2
  export declare const ALLOWED_KERNEL_MESSAGES: ReadonlySet<string>;
3
3
  export declare const SDK_MSG: {
4
4
  readonly INIT: "SDK_INIT";
@@ -22,5 +22,8 @@ export declare const SDK_MSG: {
22
22
  readonly IDB_QUERY: "SDK_IDB_QUERY";
23
23
  readonly LAUNCH_APP: "SDK_LAUNCH_APP";
24
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";
25
28
  };
26
29
  export type SdkMessageType = (typeof SDK_MSG)[keyof typeof SDK_MSG];
package/dist/protocol.js CHANGED
@@ -25,4 +25,7 @@ export const SDK_MSG = {
25
25
  IDB_QUERY: 'SDK_IDB_QUERY',
26
26
  LAUNCH_APP: 'SDK_LAUNCH_APP',
27
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',
28
31
  };
package/dist/react.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { type ReactNode } from 'react';
2
2
  import { type DevAuthStorageOptions } from './dev.js';
3
- import type { HostBridge, HostBridgeConfig, NotifyLevel, OsConfig, SdkCommandDefinition, SdkCommandHandler, SdkMenuRoot } from './types.js';
3
+ import type { GraphEvent, HostBridge, HostBridgeConfig, NotifyLevel, OsConfig, SdkCommandDefinition, SdkCommandHandler, SdkMenuRoot } from './types.js';
4
4
  export interface AppSdkProviderProps extends HostBridgeConfig {
5
5
  children: ReactNode;
6
6
  }
@@ -22,5 +22,13 @@ export declare function useActiveTab(activeTab: string | null): void;
22
22
  * Automatically subscribes to updates and returns the latest values.
23
23
  */
24
24
  export declare function useOsConfig(): OsConfig | null;
25
+ /**
26
+ * Subscribes to live graph events for the given OGIT types while mounted.
27
+ * The handler is kept in a ref so its identity may change without resubscribing;
28
+ * the subscription is recreated only when `types` (or extra `deps`) change.
29
+ */
30
+ export declare function useGraphEvents(options: {
31
+ types: string[];
32
+ }, handler: (event: GraphEvent) => void, deps?: React.DependencyList): void;
25
33
  /** Returns a stable callback that clears standalone dev auth state and reloads the page. */
26
34
  export declare function useStandaloneDevLogout(options: DevAuthStorageOptions): () => void;
package/dist/react.js CHANGED
@@ -199,6 +199,22 @@ export function useOsConfig() {
199
199
  }, [bridge]);
200
200
  return osConfig;
201
201
  }
202
+ /**
203
+ * Subscribes to live graph events for the given OGIT types while mounted.
204
+ * The handler is kept in a ref so its identity may change without resubscribing;
205
+ * the subscription is recreated only when `types` (or extra `deps`) change.
206
+ */
207
+ export function useGraphEvents(options, handler, deps = []) {
208
+ const bridge = useSdk();
209
+ const handlerRef = useRef(handler);
210
+ handlerRef.current = handler;
211
+ const typesKey = JSON.stringify(options.types);
212
+ useEffect(() => {
213
+ const unsubscribe = bridge.events.subscribe({ types: JSON.parse(typesKey) }, event => handlerRef.current(event));
214
+ return unsubscribe;
215
+ // eslint-disable-next-line react-hooks/exhaustive-deps
216
+ }, [bridge, typesKey, ...deps]);
217
+ }
202
218
  /** Returns a stable callback that clears standalone dev auth state and reloads the page. */
203
219
  export function useStandaloneDevLogout(options) {
204
220
  return useCallback(() => {
package/dist/types.d.ts CHANGED
@@ -84,6 +84,42 @@ export interface SdkShortcutEventPayload {
84
84
  altKey: boolean;
85
85
  shiftKey: boolean;
86
86
  }
87
+ /** Mirrors the BFF `GraphEvent` SSE contract delivered by the host. */
88
+ export type GraphEventType = 'CREATE' | 'REPLACE' | 'UPDATE' | 'CONNECT' | 'DISCONNECT' | 'DELETE' | 'WRITE_TIMESERIES';
89
+ export interface GraphEvent {
90
+ id: string;
91
+ type: GraphEventType;
92
+ timestamp: number;
93
+ nanotime?: number;
94
+ body?: Record<string, unknown>;
95
+ metadata?: Record<string, unknown>;
96
+ }
97
+ export interface EventSubscribeRequest {
98
+ /** App-generated subscription id, echoed by the host and stamped on pushes. */
99
+ subscriptionId: string;
100
+ /** OGIT types to subscribe to, e.g. `['ogit/Documents/Document']`. */
101
+ types: string[];
102
+ }
103
+ export interface EventSubscribeResponse {
104
+ subscriptionId: string;
105
+ }
106
+ export interface EventUnsubscribeRequest {
107
+ subscriptionId: string;
108
+ }
109
+ export interface GraphEventPushPayload {
110
+ subscriptionId: string;
111
+ event: GraphEvent;
112
+ }
113
+ /** Live graph-event subscription surface exposed to hosted apps. */
114
+ export interface EventsApi {
115
+ /**
116
+ * Subscribe to graph mutations for the given OGIT types. Returns an
117
+ * unsubscribe function; the handler fires for each matching event.
118
+ */
119
+ subscribe(options: {
120
+ types: string[];
121
+ }, handler: (event: GraphEvent) => void): () => void;
122
+ }
87
123
  export type NotifyLevel = 'success' | 'error' | 'info' | 'warning';
88
124
  export interface SdkNotifyPayload {
89
125
  message: string;
@@ -325,6 +361,7 @@ export interface HostBridge {
325
361
  storage: SdkStorage;
326
362
  idb(storeName: string): SdkIdbStore;
327
363
  transport: SdkTransport;
364
+ events: EventsApi;
328
365
  setCommands(commands: SdkCommandDefinition[]): Promise<void>;
329
366
  setMenu(menu: SdkMenuRoot[] | null): Promise<void>;
330
367
  setActiveTab(activeTab: string | null): Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bardioc/app-sdk",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "SDK for building apps for the Bardioc OS",
5
5
  "license": "MIT",
6
6
  "private": false,