@bardioc/app-sdk 0.4.10 → 0.5.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/dist/dev-proxy.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { createServer } from 'node:http';
2
2
  import { resolveDevAuthEnv } from './dev-auth-env.js';
3
+ // BFF mount of the dynamic relay to published REST backends.
4
+ const RELAY_PATH_PREFIX = '/relay';
3
5
  export async function readRequestBody(req) {
4
6
  return new Promise((resolve, reject) => {
5
7
  const chunks = [];
@@ -95,9 +97,12 @@ export function createDevAuthProxyHandler(options) {
95
97
  }
96
98
  addDevSecretHeader(req, headers, devSecret);
97
99
  dropOriginKeepReferer(headers);
98
- // Dev auth is dev-secret only: never forward X-App-Id, so the BFF app-token
99
- // exchange (which would also demand X-Scope-ID) can never be triggered here.
100
- headers.delete('X-App-Id');
100
+ // Dev auth is dev-secret only, so X-App-Id is dropped and the BFF app-token exchange
101
+ // (which would also demand X-Scope-ID) is never triggered. The relay is the exception:
102
+ // it authorizes on the exchanged app identity and answers 403 without one.
103
+ if (!upstreamPath.startsWith(`${RELAY_PATH_PREFIX}/`)) {
104
+ headers.delete('X-App-Id');
105
+ }
101
106
  // BFF rejects /bardioc/ calls without this CSRF marker header; stamp it like the host does.
102
107
  headers.set('X-Requested-With', 'XMLHttpRequest');
103
108
  let body;
package/dist/dev.d.ts CHANGED
@@ -1,6 +1,12 @@
1
1
  export interface DevBridgeOptions {
2
2
  /** Base URL prepended to SDK transport paths. Defaults to `/rest`. */
3
3
  apiBaseUrl?: string;
4
+ /**
5
+ * The app's ogit/Auth/Application id, sent as X-App-ID on scoped calls like the host
6
+ * does. Needed for `transport.backend(...)`: the relay authorizes on the exchanged app
7
+ * identity and refuses calls without it.
8
+ */
9
+ appId?: string;
4
10
  debug?: boolean;
5
11
  }
6
12
  export declare function isInsideIframe(): boolean;
package/dist/dev.js CHANGED
@@ -12,6 +12,7 @@ export function isDevStandalone() {
12
12
  }
13
13
  export function installDevBridge(options = {}) {
14
14
  const apiBaseUrl = getBaseUrl(options.apiBaseUrl);
15
+ const appId = options.appId?.trim() || undefined;
15
16
  const debug = options.debug ?? false;
16
17
  const origin = window.location.origin;
17
18
  const log = (...args) => {
@@ -49,6 +50,11 @@ export function installDevBridge(options = {}) {
49
50
  }
50
51
  if (resolvedScopeId) {
51
52
  headers.set('X-Scope-ID', resolvedScopeId);
53
+ // The BFF app-token exchange rejects X-App-ID without X-Scope-ID, so a scopeless
54
+ // call goes out as the user, exactly as the host sends it.
55
+ if (appId) {
56
+ headers.set('X-App-ID', appId);
57
+ }
52
58
  }
53
59
  const url = `${apiBaseUrl}${payload.path}`;
54
60
  log('[transport]', payload.method ?? 'GET', url);
@@ -1,3 +1,3 @@
1
1
  import type { HostBridge, HostBridgeConfig } from './types.js';
2
- /** Create a bridge between an iframe app and its host OS via `postMessage`. */
2
+ /** Create a bridge between an app and its host OS, over `postMessage` unless `config.channel` says otherwise. */
3
3
  export declare function createHostBridge(config: HostBridgeConfig): HostBridge;
@@ -1,4 +1,5 @@
1
1
  import { SDK_MSG } from './protocol.js';
2
+ import { BackendTransport } from './transports/backend-transport.js';
2
3
  import { GraphTransport } from './transports/graph-transport.js';
3
4
  import { OsTransport } from './transports/os-transport.js';
4
5
  let counter = 0;
@@ -12,29 +13,49 @@ function assertStoreName(storeName) {
12
13
  }
13
14
  return normalized;
14
15
  }
15
- /** Create a bridge between an iframe app and its host OS via `postMessage`. */
16
+ /** Create a bridge between an app and its host OS, over `postMessage` unless `config.channel` says otherwise. */
16
17
  export function createHostBridge(config) {
17
18
  const { appId: hintAppId, connectTimeout = 5000, debug = false } = config;
18
- const hostWindow = globalThis.window;
19
- // When inside an iframe, document.referrer gives us the parent's origin.
20
- const inferredHostOrigin = (() => {
21
- try {
22
- if (hostWindow.parent !== hostWindow && document.referrer) {
23
- return new URL(document.referrer).origin;
24
- }
25
- }
26
- catch {
27
- // ignore parse errors
28
- }
29
- return hostWindow.location.origin;
30
- })();
31
- const targetOrigin = config.targetOrigin ?? inferredHostOrigin;
32
- const isInsideIframe = hostWindow.parent !== hostWindow;
33
19
  function log(...args) {
34
20
  if (debug)
35
21
  console.log('[app-sdk]', ...args);
36
22
  }
37
- log('createHostBridge', { appId: hintAppId, targetOrigin, isInsideIframe });
23
+ function windowChannel() {
24
+ const hostWindow = globalThis.window;
25
+ // When inside an iframe, document.referrer gives us the parent's origin.
26
+ const inferredHostOrigin = (() => {
27
+ try {
28
+ if (hostWindow.parent !== hostWindow && document.referrer) {
29
+ return new URL(document.referrer).origin;
30
+ }
31
+ }
32
+ catch {
33
+ // ignore parse errors
34
+ }
35
+ return hostWindow.location.origin;
36
+ })();
37
+ const targetOrigin = config.targetOrigin ?? inferredHostOrigin;
38
+ const isInsideIframe = hostWindow.parent !== hostWindow;
39
+ log('createHostBridge', { appId: hintAppId, targetOrigin, isInsideIframe });
40
+ function isAllowedMessageOrigin(origin) {
41
+ return origin === targetOrigin || origin === hostWindow.location.origin;
42
+ }
43
+ return {
44
+ post: message => hostWindow.parent.postMessage(message, targetOrigin),
45
+ subscribe(receive) {
46
+ const onMessage = (event) => {
47
+ if (!isAllowedMessageOrigin(event.origin)) {
48
+ log('ignoring message from', event.origin, '(expected', targetOrigin, ')');
49
+ return;
50
+ }
51
+ receive(event.data);
52
+ };
53
+ hostWindow.addEventListener('message', onMessage);
54
+ return () => hostWindow.removeEventListener('message', onMessage);
55
+ },
56
+ };
57
+ }
58
+ const channel = config.channel ?? windowChannel();
38
59
  let _connected = false;
39
60
  let _context = null;
40
61
  let _destroyed = false;
@@ -42,9 +63,6 @@ export function createHostBridge(config) {
42
63
  const osConfigUpdateCallbacks = new Set();
43
64
  const eventHandlers = new Map();
44
65
  const pending = new Map();
45
- function isAllowedMessageOrigin(origin) {
46
- return origin === targetOrigin || origin === hostWindow.location.origin;
47
- }
48
66
  function handleCommandInvoke(data) {
49
67
  const { commandId } = data.payload;
50
68
  const handler = commandHandlers.get(commandId);
@@ -101,12 +119,7 @@ export function createHostBridge(config) {
101
119
  }
102
120
  request.resolve(data.payload);
103
121
  }
104
- function onMessage(event) {
105
- if (!isAllowedMessageOrigin(event.origin)) {
106
- log('ignoring message from', event.origin, '(expected', targetOrigin, ')');
107
- return;
108
- }
109
- const data = event.data;
122
+ function receive(data) {
110
123
  if (!data || typeof data.type !== 'string')
111
124
  return;
112
125
  if (!data.id)
@@ -130,9 +143,9 @@ export function createHostBridge(config) {
130
143
  return;
131
144
  }
132
145
  }
133
- hostWindow.addEventListener('message', onMessage);
146
+ const unsubscribe = channel.subscribe(receive);
134
147
  function postToHost(message) {
135
- hostWindow.parent.postMessage(message, targetOrigin);
148
+ channel.post(message);
136
149
  }
137
150
  function respondToHost(id, payload, error) {
138
151
  postToHost({
@@ -176,6 +189,7 @@ export function createHostBridge(config) {
176
189
  const transport = {
177
190
  graph: new GraphTransport(baseRequest, { debug }),
178
191
  os: new OsTransport(baseRequest),
192
+ backend: routeKey => new BackendTransport(baseRequest, routeKey),
179
193
  };
180
194
  function idb(storeName) {
181
195
  const normalizedStoreName = assertStoreName(storeName);
@@ -291,7 +305,7 @@ export function createHostBridge(config) {
291
305
  }
292
306
  eventHandlers.clear();
293
307
  _destroyed = true;
294
- hostWindow.removeEventListener('message', onMessage);
308
+ unsubscribe();
295
309
  for (const [, p] of pending)
296
310
  p.reject(new Error('Bridge destroyed'));
297
311
  pending.clear();
package/dist/index.d.ts CHANGED
@@ -5,4 +5,4 @@ export { SDK_CONTRACT_MATRIX, getRequiredPermissionForSdkMessage } from './contr
5
5
  export { ALLOWED_KERNEL_MESSAGES, SDK_MSG } from './protocol.js';
6
6
  export type { SdkMessageType } from './protocol.js';
7
7
  export { EntityNotFoundError, NetworkError, PermissionError, SdkError, TimeoutError, ValidationError, } from './errors.js';
8
- export type { Application, 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';
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, SdkChannel, 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';
package/dist/react.d.ts CHANGED
@@ -2,12 +2,14 @@ import { type ReactNode } from 'react';
2
2
  import type { GraphEvent, HostBridge, HostBridgeConfig, NotifyLevel, OsConfig, SdkCommandDefinition, SdkCommandHandler, SdkMenuRoot } from './types.js';
3
3
  export interface AppSdkProviderProps extends HostBridgeConfig {
4
4
  children: ReactNode;
5
+ /** Apply theme, language and size to this document. Default: `true`; `false` when the app shares the host document. */
6
+ applyOsConfig?: boolean;
5
7
  }
6
8
  export interface AppCommand extends SdkCommandDefinition {
7
9
  handler: SdkCommandHandler;
8
10
  }
9
11
  /** 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;
12
+ export declare function AppSdkProvider({ children, applyOsConfig, ...config }: Readonly<AppSdkProviderProps>): import("react/jsx-runtime").JSX.Element | null;
11
13
  /** Returns the raw {@link HostBridge}. Must be used inside `<AppSdkProvider>`. */
12
14
  export declare function useSdk(): HostBridge;
13
15
  /** Returns a stable callback that fires a host notification toast. */
package/dist/react.js CHANGED
@@ -2,6 +2,8 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { createContext, useCallback, useContext, useEffect, useRef, useState, } from 'react';
3
3
  import { createHostBridge } from './host-bridge.js';
4
4
  const SdkContext = createContext(null);
5
+ // One handshake per bridge, however often the effect that owns it reconnects.
6
+ const connections = new WeakMap();
5
7
  const SYSTEM_SIZE_CLASS_NAMES = ['text-sm', 'text-base', 'text-lg', 'text-xl'];
6
8
  function applyOsConfigToDocument(osConfig) {
7
9
  if (!osConfig) {
@@ -39,28 +41,46 @@ function hasShortcutRoots(menu) {
39
41
  return menu.some(root => hasShortcutItems(root.items));
40
42
  }
41
43
  /** Mounts the SDK bridge and makes it available to descendant hooks. */
42
- export function AppSdkProvider({ children, ...config }) {
44
+ export function AppSdkProvider({ children, applyOsConfig = true, ...config }) {
43
45
  const bridgeRef = useRef(null);
44
- const { appId, connectTimeout, debug, targetOrigin } = config;
46
+ const { appId, channel, connectTimeout, debug, targetOrigin } = config;
45
47
  const configKey = JSON.stringify([
46
48
  appId ?? null,
47
49
  connectTimeout ?? null,
48
50
  debug ?? false,
49
51
  targetOrigin ?? null,
50
52
  ]);
51
- const bridgeConfig = { appId, connectTimeout, debug, targetOrigin };
53
+ const bridgeConfig = { appId, channel, connectTimeout, debug, targetOrigin };
52
54
  const bridgeConfigKeyRef = useRef(null);
55
+ const pendingDestroyRef = useRef(null);
53
56
  const [readyConfigKey, setReadyConfigKey] = useState(null);
54
57
  const [errorState, setErrorState] = useState(null);
55
58
  useEffect(() => {
56
59
  let cancelled = false;
57
- setReadyConfigKey(null);
58
- setErrorState(null);
59
- const bridge = createHostBridge(bridgeConfig);
60
+ // A disconnect followed at once by a reconnect (StrictMode, Activity) keeps the live bridge:
61
+ // the children holding it reconnect their own effects over it before this one runs.
62
+ const pending = pendingDestroyRef.current;
63
+ pendingDestroyRef.current = null;
64
+ if (pending)
65
+ clearTimeout(pending.timer);
66
+ let bridge;
67
+ if (pending && pending.configKey === configKey && pending.channel === channel) {
68
+ bridge = pending.bridge;
69
+ }
70
+ else {
71
+ pending?.destroy();
72
+ setReadyConfigKey(null);
73
+ setErrorState(null);
74
+ bridge = createHostBridge(bridgeConfig);
75
+ }
60
76
  bridgeRef.current = bridge;
61
77
  bridgeConfigKeyRef.current = configKey;
62
- bridge
63
- .connect()
78
+ let connecting = connections.get(bridge);
79
+ if (!connecting) {
80
+ connecting = bridge.connect();
81
+ connections.set(bridge, connecting);
82
+ }
83
+ connecting
64
84
  .then(() => {
65
85
  if (!cancelled) {
66
86
  setReadyConfigKey(configKey);
@@ -75,17 +95,28 @@ export function AppSdkProvider({ children, ...config }) {
75
95
  });
76
96
  return () => {
77
97
  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
- }
98
+ const destroy = () => {
99
+ bridge.destroy();
100
+ if (bridgeRef.current === bridge) {
101
+ bridgeRef.current = null;
102
+ bridgeConfigKeyRef.current = null;
103
+ }
104
+ };
105
+ pendingDestroyRef.current = {
106
+ bridge,
107
+ configKey,
108
+ channel,
109
+ destroy,
110
+ timer: setTimeout(() => {
111
+ pendingDestroyRef.current = null;
112
+ destroy();
113
+ }),
114
+ };
85
115
  };
86
- }, [appId, configKey, connectTimeout, debug, targetOrigin]);
116
+ }, [appId, channel, configKey, connectTimeout, debug, targetOrigin]);
87
117
  useEffect(() => {
88
- if (readyConfigKey !== configKey ||
118
+ if (!applyOsConfig ||
119
+ readyConfigKey !== configKey ||
89
120
  bridgeConfigKeyRef.current !== configKey ||
90
121
  !bridgeRef.current) {
91
122
  return;
@@ -95,7 +126,7 @@ export function AppSdkProvider({ children, ...config }) {
95
126
  applyOsConfigToDocument(newConfig);
96
127
  });
97
128
  return unsubscribe;
98
- }, [configKey, readyConfigKey]);
129
+ }, [applyOsConfig, configKey, readyConfigKey]);
99
130
  if (errorState?.configKey === configKey) {
100
131
  return _jsx("div", { "data-sdk-error": true, children: errorState.message });
101
132
  }
@@ -0,0 +1,24 @@
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
+ }
@@ -0,0 +1,54 @@
1
+ import { ValidationError } from '../errors.js';
2
+ import { parseOsError } from './os-transport.js';
3
+ const RELAY_BASE = '/relay';
4
+ // Mirrors the BFF's route key rule: one path segment, "." and ".." excluded.
5
+ const ROUTE_KEY_PATTERN = /^[A-Za-z0-9._-]+$/;
6
+ export function isRouteKey(routeKey) {
7
+ return routeKey !== '.' && routeKey !== '..' && ROUTE_KEY_PATTERN.test(routeKey);
8
+ }
9
+ /**
10
+ * Client for an application's own REST backend, reached through the BFF relay at
11
+ * `/relay/{routeKey}`. The host stamps the calling app id and scope; the BFF checks the
12
+ * app against the route's whitelist and forwards the request with an app-scoped token.
13
+ */
14
+ export class BackendTransport {
15
+ _request;
16
+ routeKey;
17
+ constructor(_request, routeKey) {
18
+ this._request = _request;
19
+ this.routeKey = routeKey;
20
+ if (!isRouteKey(routeKey)) {
21
+ throw new ValidationError(`Invalid relay route key: "${routeKey}"`, 'routeKey');
22
+ }
23
+ }
24
+ /**
25
+ * Send a request to the backend. `path` is what the backend sees, after the relay
26
+ * stripped `/relay/{routeKey}`. Scope is required unless stated otherwise: the relay
27
+ * refuses an app-scoped call without one.
28
+ *
29
+ * @throws {EntityNotFoundError} on 404 errors
30
+ * @throws {ValidationError} on 400 errors
31
+ * @throws {PermissionError} on 403 errors (app not whitelisted for the route)
32
+ * @throws {NetworkError} on HTTP errors, including 502 when the backend is unreachable
33
+ * @throws {TimeoutError} on request timeout
34
+ */
35
+ async request(options) {
36
+ const path = options.path.startsWith('/') ? options.path : `/${options.path}`;
37
+ try {
38
+ return await this._request({
39
+ path: `${RELAY_BASE}/${this.routeKey}${path}`,
40
+ method: options.method || 'GET',
41
+ body: options.body,
42
+ contentType: options.contentType,
43
+ scopeId: options.scopeId,
44
+ scopePolicy: options.scopePolicy ?? 'required',
45
+ });
46
+ }
47
+ catch (error) {
48
+ throw parseOsError(error, `backend.${this.routeKey}.request`, {
49
+ path: options.path,
50
+ method: options.method,
51
+ });
52
+ }
53
+ }
54
+ }
@@ -1,4 +1,5 @@
1
1
  import type { Application, Configuration, CreateMemberInput, CreateUnitInput, FileEntry, InstanceList, OrgMember, OrgStructure, OrgUnit, OsRequestOptions, SdkTransportPayload, UpdateMemberInput, UpdateUnitInput, UserProfile } from '../types.js';
2
+ export declare function parseOsError(error: any, operation: string, context?: Record<string, unknown>): Error;
2
3
  /** OS transport class providing OS/system operations */
3
4
  export declare class OsTransport {
4
5
  private readonly _request;
@@ -1,7 +1,7 @@
1
1
  import { EntityNotFoundError, NetworkError, PermissionError, SdkError, TimeoutError, ValidationError, } from '../errors.js';
2
2
  const OS_BASE = '/os';
3
3
  // Maps backend errors to typed SDK errors
4
- function parseOsError(error, operation, context) {
4
+ export function parseOsError(error, operation, context) {
5
5
  let statusCode;
6
6
  let errorMessage = error.message || 'Unknown error';
7
7
  if (error.responsePayload && typeof error.responsePayload === 'object') {
package/dist/types.d.ts CHANGED
@@ -168,9 +168,18 @@ export interface SdkIdbQueryPayload {
168
168
  prefix?: string;
169
169
  limit?: number;
170
170
  }
171
+ /** How bridge messages travel between an app and its host. */
172
+ export interface SdkChannel {
173
+ /** Delivers one message to the host. */
174
+ post(message: SdkMessage | SdkResponse): void;
175
+ /** Hands every message from the host to `receive`; returns the unsubscribe. */
176
+ subscribe(receive: (message: SdkResponse) => void): () => void;
177
+ }
171
178
  /** Configuration passed to {@link createHostBridge}. */
172
179
  export interface HostBridgeConfig {
173
180
  appId?: string;
181
+ /** Message transport. Default: `postMessage` with the parent window. */
182
+ channel?: SdkChannel;
174
183
  /** Override the host origin for `postMessage`. Inferred from `document.referrer` by default. */
175
184
  targetOrigin?: string;
176
185
  /** Handshake timeout in ms. Default: `5000`. */
@@ -202,6 +211,7 @@ export interface SdkIdbStore {
202
211
  export interface SdkTransport {
203
212
  graph: GraphTransport;
204
213
  os: OsTransport;
214
+ backend(routeKey: string): BackendTransport;
205
215
  }
206
216
  /** Graph node in raw backend format (ogit/* fields) */
207
217
  export interface GraphNodeRaw {
@@ -363,6 +373,7 @@ export interface OsRequestOptions extends TransportScopeOptions {
363
373
  contentType?: string;
364
374
  scopePolicy?: 'none' | 'optional' | 'required';
365
375
  }
376
+ export type BackendRequestOptions = OsRequestOptions;
366
377
  /** Live connection between an iframe app and its host OS. */
367
378
  export interface HostBridge {
368
379
  /** Perform the SDK handshake. Resolves with host context. Cached after first call. */
@@ -392,3 +403,4 @@ export interface HostBridge {
392
403
  /** Forward declarations for transport classes */
393
404
  export type GraphTransport = import('./transports/graph-transport.js').GraphTransport;
394
405
  export type OsTransport = import('./transports/os-transport.js').OsTransport;
406
+ export type BackendTransport = import('./transports/backend-transport.js').BackendTransport;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bardioc/app-sdk",
3
- "version": "0.4.10",
3
+ "version": "0.5.0",
4
4
  "description": "SDK for building apps for the Bardioc OS",
5
5
  "license": "MIT",
6
6
  "private": false,