@bardioc/app-sdk 0.4.10 → 0.4.11

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,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;
@@ -176,6 +177,7 @@ export function createHostBridge(config) {
176
177
  const transport = {
177
178
  graph: new GraphTransport(baseRequest, { debug }),
178
179
  os: new OsTransport(baseRequest),
180
+ backend: routeKey => new BackendTransport(baseRequest, routeKey),
179
181
  };
180
182
  function idb(storeName) {
181
183
  const normalizedStoreName = assertStoreName(storeName);
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, 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';
@@ -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
@@ -202,6 +202,7 @@ export interface SdkIdbStore {
202
202
  export interface SdkTransport {
203
203
  graph: GraphTransport;
204
204
  os: OsTransport;
205
+ backend(routeKey: string): BackendTransport;
205
206
  }
206
207
  /** Graph node in raw backend format (ogit/* fields) */
207
208
  export interface GraphNodeRaw {
@@ -363,6 +364,7 @@ export interface OsRequestOptions extends TransportScopeOptions {
363
364
  contentType?: string;
364
365
  scopePolicy?: 'none' | 'optional' | 'required';
365
366
  }
367
+ export type BackendRequestOptions = OsRequestOptions;
366
368
  /** Live connection between an iframe app and its host OS. */
367
369
  export interface HostBridge {
368
370
  /** Perform the SDK handshake. Resolves with host context. Cached after first call. */
@@ -392,3 +394,4 @@ export interface HostBridge {
392
394
  /** Forward declarations for transport classes */
393
395
  export type GraphTransport = import('./transports/graph-transport.js').GraphTransport;
394
396
  export type OsTransport = import('./transports/os-transport.js').OsTransport;
397
+ 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.4.11",
4
4
  "description": "SDK for building apps for the Bardioc OS",
5
5
  "license": "MIT",
6
6
  "private": false,