@bardioc/app-sdk 0.5.0 → 1.0.1
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/README.md +86 -253
- package/dist/api/graph.d.ts +30 -0
- package/dist/api/graph.js +139 -0
- package/dist/api/os.d.ts +30 -0
- package/dist/api/os.js +48 -0
- package/dist/api/relay.d.ts +7 -0
- package/dist/api/relay.js +16 -0
- package/dist/api/request.d.ts +18 -0
- package/dist/api/request.js +12 -0
- package/dist/client.d.ts +104 -0
- package/dist/client.js +106 -0
- package/dist/contract.d.ts +294 -0
- package/dist/contract.js +150 -0
- package/dist/dev-idb.d.ts +10 -0
- package/dist/dev-idb.js +42 -0
- package/dist/dev-proxy.js +17 -3
- package/dist/dev.d.ts +12 -12
- package/dist/dev.js +101 -120
- package/dist/errors.d.ts +24 -25
- package/dist/errors.js +33 -48
- package/dist/index.d.ts +10 -7
- package/dist/index.js +2 -4
- package/dist/manifest.d.ts +10 -2
- package/dist/manifest.js +28 -24
- package/dist/protocol.d.ts +6 -29
- package/dist/protocol.js +3 -31
- package/dist/react.d.ts +21 -27
- package/dist/react.js +88 -212
- package/dist/serve.d.ts +26 -0
- package/dist/serve.js +64 -0
- package/dist/testing.d.ts +17 -0
- package/dist/testing.js +128 -0
- package/dist/types.d.ts +0 -250
- package/dist/vite.js +3 -0
- package/dist/wire.d.ts +46 -0
- package/dist/wire.js +138 -0
- package/package.json +12 -4
- package/dist/contract-matrix.d.ts +0 -148
- package/dist/contract-matrix.js +0 -150
- package/dist/host-bridge.d.ts +0 -3
- package/dist/host-bridge.js +0 -318
- package/dist/transports/backend-transport.d.ts +0 -24
- package/dist/transports/backend-transport.js +0 -54
- package/dist/transports/graph-transport.d.ts +0 -241
- package/dist/transports/graph-transport.js +0 -616
- package/dist/transports/os-transport.d.ts +0 -190
- package/dist/transports/os-transport.js +0 -444
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
export {
|
|
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,
|
|
4
|
-
export {
|
|
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, 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';
|
|
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 {
|
|
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';
|
package/dist/manifest.d.ts
CHANGED
|
@@ -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
|
}
|
package/dist/protocol.d.ts
CHANGED
|
@@ -1,29 +1,6 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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,33 +1,27 @@
|
|
|
1
|
-
import { type ReactNode } from 'react';
|
|
2
|
-
import
|
|
3
|
-
|
|
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;
|
|
5
|
-
/** Apply theme, language and size to this document. Default: `true
|
|
6
|
+
/** Apply theme, language and size to this document. Default: `true`. */
|
|
6
7
|
applyOsConfig?: boolean;
|
|
7
8
|
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
/** Returns the raw {@link HostBridge}. Must be used inside `<AppSdkProvider>`. */
|
|
14
|
-
export declare function useSdk(): HostBridge;
|
|
15
|
-
/** 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. */
|
|
16
14
|
export declare function useNotify(): (message: string, level?: NotifyLevel) => void;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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. */
|
|
20
21
|
export declare function useActiveTab(activeTab: string | null): void;
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
export declare function useOsConfig(): OsConfig | null;
|
|
26
|
-
/**
|
|
27
|
-
* Subscribes to live graph events for the given OGIT types while mounted.
|
|
28
|
-
* The handler is kept in a ref so its identity may change without resubscribing;
|
|
29
|
-
* the subscription is recreated only when `types` (or extra `deps`) change.
|
|
30
|
-
*/
|
|
31
|
-
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 }: {
|
|
32
26
|
types: string[];
|
|
33
|
-
}, handler: (event: GraphEvent) => void, deps?:
|
|
27
|
+
}, handler: (event: GraphEvent) => void, deps?: DependencyList): void;
|
package/dist/react.js
CHANGED
|
@@ -1,252 +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 {
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
function applyOsConfigToDocument(osConfig) {
|
|
9
|
-
if (!osConfig) {
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
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 }) {
|
|
12
8
|
const root = document.documentElement;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
root.lang = osConfig.language;
|
|
21
|
-
}
|
|
22
|
-
root.classList.remove(...SYSTEM_SIZE_CLASS_NAMES);
|
|
23
|
-
if (osConfig.systemSize && SYSTEM_SIZE_CLASS_NAMES.includes(osConfig.systemSize)) {
|
|
24
|
-
root.classList.add(osConfig.systemSize);
|
|
25
|
-
}
|
|
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);
|
|
26
16
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return typeof item.shortcut === 'string' && item.shortcut.trim().length > 0;
|
|
32
|
-
case 'submenu':
|
|
33
|
-
return hasShortcutItems(item.items);
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
function hasShortcutRoots(menu) {
|
|
38
|
-
if (!menu?.length) {
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
return menu.some(root => hasShortcutItems(root.items));
|
|
42
|
-
}
|
|
43
|
-
/** Mounts the SDK bridge and makes it available to descendant hooks. */
|
|
44
|
-
export function AppSdkProvider({ children, applyOsConfig = true, ...config }) {
|
|
45
|
-
const bridgeRef = useRef(null);
|
|
46
|
-
const { appId, channel, connectTimeout, debug, targetOrigin } = config;
|
|
47
|
-
const configKey = JSON.stringify([
|
|
48
|
-
appId ?? null,
|
|
49
|
-
connectTimeout ?? null,
|
|
50
|
-
debug ?? false,
|
|
51
|
-
targetOrigin ?? null,
|
|
52
|
-
]);
|
|
53
|
-
const bridgeConfig = { appId, channel, connectTimeout, debug, targetOrigin };
|
|
54
|
-
const bridgeConfigKeyRef = useRef(null);
|
|
55
|
-
const pendingDestroyRef = useRef(null);
|
|
56
|
-
const [readyConfigKey, setReadyConfigKey] = useState(null);
|
|
57
|
-
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);
|
|
58
21
|
useEffect(() => {
|
|
59
|
-
let
|
|
60
|
-
|
|
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
|
-
}
|
|
76
|
-
bridgeRef.current = bridge;
|
|
77
|
-
bridgeConfigKeyRef.current = configKey;
|
|
78
|
-
let connecting = connections.get(bridge);
|
|
79
|
-
if (!connecting) {
|
|
80
|
-
connecting = bridge.connect();
|
|
81
|
-
connections.set(bridge, connecting);
|
|
82
|
-
}
|
|
83
|
-
connecting
|
|
84
|
-
.then(() => {
|
|
85
|
-
if (!cancelled) {
|
|
86
|
-
setReadyConfigKey(configKey);
|
|
87
|
-
}
|
|
88
|
-
})
|
|
89
|
-
.catch((err) => {
|
|
90
|
-
if (!cancelled) {
|
|
91
|
-
bridgeRef.current = null;
|
|
92
|
-
bridgeConfigKeyRef.current = null;
|
|
93
|
-
setErrorState({ configKey, message: err.message });
|
|
94
|
-
}
|
|
95
|
-
});
|
|
22
|
+
let active = true;
|
|
23
|
+
connect().then(connected => active && setBardioc(connected), (failure) => active && setError(failure.message));
|
|
96
24
|
return () => {
|
|
97
|
-
|
|
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
|
-
};
|
|
25
|
+
active = false;
|
|
115
26
|
};
|
|
116
|
-
}, [
|
|
27
|
+
}, []);
|
|
117
28
|
useEffect(() => {
|
|
118
|
-
if (!
|
|
119
|
-
readyConfigKey !== configKey ||
|
|
120
|
-
bridgeConfigKeyRef.current !== configKey ||
|
|
121
|
-
!bridgeRef.current) {
|
|
29
|
+
if (!apply || !bardioc)
|
|
122
30
|
return;
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
}, [applyOsConfig, configKey, readyConfigKey]);
|
|
130
|
-
if (errorState?.configKey === configKey) {
|
|
131
|
-
return _jsx("div", { "data-sdk-error": true, children: errorState.message });
|
|
132
|
-
}
|
|
133
|
-
if (readyConfigKey !== configKey ||
|
|
134
|
-
bridgeConfigKeyRef.current !== configKey ||
|
|
135
|
-
!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)
|
|
136
37
|
return null;
|
|
137
|
-
}
|
|
138
|
-
return _jsx(SdkContext.Provider, { value: bridgeRef.current, children: children });
|
|
38
|
+
return _jsx(BardiocContext.Provider, { value: bardioc, children: children });
|
|
139
39
|
}
|
|
140
|
-
/**
|
|
40
|
+
/** The connection to the host. Must be used inside `<AppSdkProvider>`. */
|
|
141
41
|
export function useSdk() {
|
|
142
|
-
const
|
|
143
|
-
if (!
|
|
42
|
+
const bardioc = useContext(BardiocContext);
|
|
43
|
+
if (!bardioc)
|
|
144
44
|
throw new Error('useSdk() must be used inside <AppSdkProvider>');
|
|
145
|
-
return
|
|
45
|
+
return bardioc;
|
|
146
46
|
}
|
|
147
|
-
/**
|
|
47
|
+
/** A stable callback that records a notification in the host. */
|
|
148
48
|
export function useNotify() {
|
|
149
|
-
const
|
|
49
|
+
const { ui } = useSdk();
|
|
150
50
|
return useCallback((message, level) => {
|
|
151
|
-
|
|
152
|
-
}, [
|
|
153
|
-
}
|
|
154
|
-
/** Returns a stable callback that proxies a message to the OS service worker. */
|
|
155
|
-
export function useSendToKernel() {
|
|
156
|
-
const bridge = useSdk();
|
|
157
|
-
return useCallback((kernelType, kernelPayload) => bridge.sendToKernel(kernelType, kernelPayload), [bridge]);
|
|
51
|
+
ui.notify(message, level).catch(reportFailure('notify'));
|
|
52
|
+
}, [ui]);
|
|
158
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. */
|
|
159
56
|
export function useCommandMenu(commands, menu) {
|
|
160
|
-
const
|
|
57
|
+
const { ui } = useSdk();
|
|
161
58
|
useEffect(() => {
|
|
162
|
-
const unregister =
|
|
163
|
-
|
|
164
|
-
|
|
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]);
|
|
165
63
|
useEffect(() => {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
console.error('[app-sdk] failed to sync command state', error);
|
|
169
|
-
});
|
|
170
|
-
void bridge.setMenu(menu).catch((error) => {
|
|
171
|
-
console.error('[app-sdk] failed to sync app menu', error);
|
|
172
|
-
});
|
|
173
|
-
}, [bridge, commands, menu]);
|
|
64
|
+
ui.setMenu(menu).catch(reportFailure('setMenu'));
|
|
65
|
+
}, [ui, menu]);
|
|
174
66
|
useEffect(() => {
|
|
175
|
-
if (!
|
|
67
|
+
if (!menu?.some(root => hasShortcut(root.items)))
|
|
176
68
|
return;
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if (event.defaultPrevented || event.isComposing || event.repeat) {
|
|
69
|
+
const onKeyDown = (event) => {
|
|
70
|
+
if (event.defaultPrevented || event.isComposing || event.repeat)
|
|
180
71
|
return;
|
|
181
|
-
|
|
182
|
-
if (!event.metaKey && !event.ctrlKey && !event.altKey) {
|
|
72
|
+
if (!event.metaKey && !event.ctrlKey && !event.altKey)
|
|
183
73
|
return;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
code: event.code,
|
|
187
|
-
ctrlKey: event.ctrlKey,
|
|
188
|
-
metaKey: event.metaKey,
|
|
189
|
-
altKey: event.altKey,
|
|
190
|
-
shiftKey: event.shiftKey,
|
|
191
|
-
});
|
|
192
|
-
};
|
|
193
|
-
window.addEventListener('keydown', handleKeyDown);
|
|
194
|
-
return () => {
|
|
195
|
-
window.removeEventListener('keydown', handleKeyDown);
|
|
74
|
+
const { code, ctrlKey, metaKey, altKey, shiftKey } = event;
|
|
75
|
+
ui.shortcut({ code, ctrlKey, metaKey, altKey, shiftKey }).catch(reportFailure('shortcut'));
|
|
196
76
|
};
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}, [
|
|
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]);
|
|
204
84
|
}
|
|
205
|
-
|
|
85
|
+
/** Tells the host which tab the app shows. */
|
|
206
86
|
export function useActiveTab(activeTab) {
|
|
207
|
-
const
|
|
87
|
+
const { ui } = useSdk();
|
|
208
88
|
useEffect(() => {
|
|
209
|
-
|
|
210
|
-
console.error('[app-sdk] failed to sync active tab', error);
|
|
211
|
-
});
|
|
89
|
+
ui.setActiveTab(activeTab).catch(reportFailure('setActiveTab'));
|
|
212
90
|
return () => {
|
|
213
|
-
|
|
91
|
+
ui.setActiveTab(null).catch(reportFailure('setActiveTab'));
|
|
214
92
|
};
|
|
215
|
-
}, [
|
|
93
|
+
}, [ui, activeTab]);
|
|
216
94
|
}
|
|
217
|
-
/**
|
|
218
|
-
* Returns the current OS configuration (theme, language, systemSize) from the host.
|
|
219
|
-
* Automatically subscribes to updates and returns the latest values.
|
|
220
|
-
*/
|
|
95
|
+
/** The host's current theme, language and size. */
|
|
221
96
|
export function useOsConfig() {
|
|
222
|
-
const
|
|
223
|
-
const [osConfig, setOsConfig] = useState(
|
|
97
|
+
const bardioc = useSdk();
|
|
98
|
+
const [osConfig, setOsConfig] = useState(bardioc.context.osConfig);
|
|
224
99
|
useEffect(() => {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
}
|
|
229
|
-
// Subscribe to OS config updates from host
|
|
230
|
-
const unsubscribe = bridge.onOsConfigUpdate(newConfig => {
|
|
231
|
-
setOsConfig(newConfig);
|
|
232
|
-
});
|
|
233
|
-
return unsubscribe;
|
|
234
|
-
}, [bridge]);
|
|
100
|
+
setOsConfig(bardioc.context.osConfig);
|
|
101
|
+
return bardioc.onContextChange(context => setOsConfig(context.osConfig));
|
|
102
|
+
}, [bardioc]);
|
|
235
103
|
return osConfig;
|
|
236
104
|
}
|
|
237
|
-
/**
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
* the subscription is recreated only when `types` (or extra `deps`) change.
|
|
241
|
-
*/
|
|
242
|
-
export function useGraphEvents(options, handler, deps = []) {
|
|
243
|
-
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();
|
|
244
108
|
const handlerRef = useRef(handler);
|
|
245
109
|
handlerRef.current = handler;
|
|
246
|
-
const typesKey = JSON.stringify(
|
|
110
|
+
const typesKey = JSON.stringify(types);
|
|
247
111
|
useEffect(() => {
|
|
248
|
-
|
|
249
|
-
|
|
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
|
+
};
|
|
250
126
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
251
|
-
}, [
|
|
127
|
+
}, [events, typesKey, ...deps]);
|
|
252
128
|
}
|
package/dist/serve.d.ts
ADDED
|
@@ -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 {};
|