@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.
- 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 +16 -12
- package/dist/dev.js +101 -119
- 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 +22 -26
- package/dist/react.js +88 -181
- 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 -241
- 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 -306
- 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, 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,31 +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;
|
|
6
|
+
/** Apply theme, language and size to this document. Default: `true`. */
|
|
7
|
+
applyOsConfig?: boolean;
|
|
5
8
|
}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
21
|
-
|
|
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?:
|
|
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 {
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
57
|
-
|
|
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
|
-
|
|
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
|
-
}, [
|
|
27
|
+
}, []);
|
|
87
28
|
useEffect(() => {
|
|
88
|
-
if (
|
|
89
|
-
bridgeConfigKeyRef.current !== configKey ||
|
|
90
|
-
!bridgeRef.current) {
|
|
29
|
+
if (!apply || !bardioc)
|
|
91
30
|
return;
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
});
|
|
97
|
-
|
|
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
|
-
/**
|
|
40
|
+
/** The connection to the host. Must be used inside `<AppSdkProvider>`. */
|
|
110
41
|
export function useSdk() {
|
|
111
|
-
const
|
|
112
|
-
if (!
|
|
42
|
+
const bardioc = useContext(BardiocContext);
|
|
43
|
+
if (!bardioc)
|
|
113
44
|
throw new Error('useSdk() must be used inside <AppSdkProvider>');
|
|
114
|
-
return
|
|
45
|
+
return bardioc;
|
|
115
46
|
}
|
|
116
|
-
/**
|
|
47
|
+
/** A stable callback that records a notification in the host. */
|
|
117
48
|
export function useNotify() {
|
|
118
|
-
const
|
|
49
|
+
const { ui } = useSdk();
|
|
119
50
|
return useCallback((message, level) => {
|
|
120
|
-
|
|
121
|
-
}, [
|
|
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
|
|
57
|
+
const { ui } = useSdk();
|
|
130
58
|
useEffect(() => {
|
|
131
|
-
const unregister =
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
136
|
-
|
|
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 (!
|
|
67
|
+
if (!menu?.some(root => hasShortcut(root.items)))
|
|
145
68
|
return;
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}, [
|
|
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
|
-
|
|
85
|
+
/** Tells the host which tab the app shows. */
|
|
175
86
|
export function useActiveTab(activeTab) {
|
|
176
|
-
const
|
|
87
|
+
const { ui } = useSdk();
|
|
177
88
|
useEffect(() => {
|
|
178
|
-
|
|
179
|
-
console.error('[app-sdk] failed to sync active tab', error);
|
|
180
|
-
});
|
|
89
|
+
ui.setActiveTab(activeTab).catch(reportFailure('setActiveTab'));
|
|
181
90
|
return () => {
|
|
182
|
-
|
|
91
|
+
ui.setActiveTab(null).catch(reportFailure('setActiveTab'));
|
|
183
92
|
};
|
|
184
|
-
}, [
|
|
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
|
|
192
|
-
const [osConfig, setOsConfig] = useState(
|
|
97
|
+
const bardioc = useSdk();
|
|
98
|
+
const [osConfig, setOsConfig] = useState(bardioc.context.osConfig);
|
|
193
99
|
useEffect(() => {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
208
|
-
|
|
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(
|
|
110
|
+
const typesKey = JSON.stringify(types);
|
|
216
111
|
useEffect(() => {
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
}, [
|
|
127
|
+
}, [events, typesKey, ...deps]);
|
|
221
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 {};
|