@bardioc/app-sdk 0.4.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/LICENSE +5 -0
- package/README.md +368 -0
- package/assets/fonts/README.md +11 -0
- package/assets/fonts/bardioc-fonts.css +55 -0
- package/assets/fonts/v1/geist-mono-latin-wght-normal.woff2 +0 -0
- package/assets/fonts/v1/nunito-sans-latin-wght-italic.woff2 +0 -0
- package/assets/fonts/v1/nunito-sans-latin-wght-normal.woff2 +0 -0
- package/dist/contract-matrix.d.ts +130 -0
- package/dist/contract-matrix.js +132 -0
- package/dist/dev-auth-proxy-core.d.ts +24 -0
- package/dist/dev-auth-proxy-core.js +59 -0
- package/dist/dev-auth-vite.d.ts +34 -0
- package/dist/dev-auth-vite.js +221 -0
- package/dist/dev-proxy.d.ts +8 -0
- package/dist/dev-proxy.js +40 -0
- package/dist/dev-session-cli.d.ts +34 -0
- package/dist/dev-session-cli.js +125 -0
- package/dist/dev.d.ts +33 -0
- package/dist/dev.js +223 -0
- package/dist/dot-env.d.ts +2 -0
- package/dist/dot-env.js +22 -0
- package/dist/errors.d.ts +27 -0
- package/dist/errors.js +57 -0
- package/dist/host-bridge.d.ts +3 -0
- package/dist/host-bridge.js +260 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +6 -0
- package/dist/manifest.d.ts +78 -0
- package/dist/manifest.js +169 -0
- package/dist/protocol.d.ts +26 -0
- package/dist/protocol.js +28 -0
- package/dist/react.d.ts +26 -0
- package/dist/react.js +208 -0
- package/dist/transports/graph-transport.d.ts +224 -0
- package/dist/transports/graph-transport.js +584 -0
- package/dist/transports/os-transport.d.ts +189 -0
- package/dist/transports/os-transport.js +444 -0
- package/dist/types.d.ts +343 -0
- package/dist/vite.d.ts +9 -0
- package/dist/vite.js +262 -0
- package/package.json +101 -0
package/dist/dev.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface InjectedDevAuthConfig {
|
|
2
|
+
hostUrl?: string;
|
|
3
|
+
sessionKey?: string;
|
|
4
|
+
proxyUrl?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface DevAuthSession {
|
|
7
|
+
sessionKey?: string;
|
|
8
|
+
hostUrl?: string;
|
|
9
|
+
scopeId: string | null;
|
|
10
|
+
}
|
|
11
|
+
export interface DevAuthStorageOptions {
|
|
12
|
+
appId: string;
|
|
13
|
+
appName?: string;
|
|
14
|
+
storageKey?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface EnsureDevAuthSessionOptions extends DevAuthStorageOptions {
|
|
17
|
+
baseUrl?: string;
|
|
18
|
+
loginPath?: string;
|
|
19
|
+
redirect?: (url: string) => void;
|
|
20
|
+
}
|
|
21
|
+
export interface DevBridgeOptions extends DevAuthStorageOptions {
|
|
22
|
+
debug?: boolean;
|
|
23
|
+
baseUrl?: string;
|
|
24
|
+
proxyPath?: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function getInjectedDevAuthConfig(): InjectedDevAuthConfig | null;
|
|
27
|
+
export declare function isInsideIframe(): boolean;
|
|
28
|
+
export declare function isDevStandalone(): boolean;
|
|
29
|
+
export declare function getDevSession(options: DevAuthStorageOptions): DevAuthSession | null;
|
|
30
|
+
export declare function getDevScopeId(options: DevAuthStorageOptions): string | null;
|
|
31
|
+
export declare function clearDevAuthSession(options: DevAuthStorageOptions): void;
|
|
32
|
+
export declare function ensureDevAuthSession(options: EnsureDevAuthSessionOptions): Promise<void>;
|
|
33
|
+
export declare function installDevBridge(options: DevBridgeOptions): () => void;
|
package/dist/dev.js
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { SDK_MSG } from './protocol.js';
|
|
2
|
+
function getBaseUrl(baseUrl) {
|
|
3
|
+
const metaEnv = import.meta.env;
|
|
4
|
+
return baseUrl ?? metaEnv?.BASE_URL ?? '/';
|
|
5
|
+
}
|
|
6
|
+
function resolveStorageKey(options) {
|
|
7
|
+
return options.storageKey ?? `bardioc.dev.auth.${options.appName ?? options.appId}`;
|
|
8
|
+
}
|
|
9
|
+
function writeDevSession(storageKey, session) {
|
|
10
|
+
localStorage.setItem(storageKey, JSON.stringify(session));
|
|
11
|
+
}
|
|
12
|
+
export function getInjectedDevAuthConfig() {
|
|
13
|
+
const config = globalThis.__BARDIOC_DEV_AUTH__;
|
|
14
|
+
if (!config?.hostUrl || !config?.sessionKey) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
hostUrl: config.hostUrl,
|
|
19
|
+
sessionKey: config.sessionKey,
|
|
20
|
+
proxyUrl: typeof config.proxyUrl === 'string' ? config.proxyUrl : undefined,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function readStoredSession(storageKey) {
|
|
24
|
+
const raw = localStorage.getItem(storageKey);
|
|
25
|
+
if (!raw) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const parsed = JSON.parse(raw);
|
|
30
|
+
const hasHostSession = typeof parsed.sessionKey === 'string' && typeof parsed.hostUrl === 'string';
|
|
31
|
+
if (!hasHostSession) {
|
|
32
|
+
localStorage.removeItem(storageKey);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
sessionKey: typeof parsed.sessionKey === 'string' ? parsed.sessionKey : undefined,
|
|
37
|
+
hostUrl: typeof parsed.hostUrl === 'string' ? parsed.hostUrl : undefined,
|
|
38
|
+
scopeId: typeof parsed.scopeId === 'string' ? parsed.scopeId : null,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
localStorage.removeItem(storageKey);
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function isInsideIframe() {
|
|
47
|
+
const currentWindow = globalThis;
|
|
48
|
+
return currentWindow.parent !== currentWindow;
|
|
49
|
+
}
|
|
50
|
+
export function isDevStandalone() {
|
|
51
|
+
const metaEnv = import.meta.env;
|
|
52
|
+
return Boolean(metaEnv?.DEV) && !isInsideIframe();
|
|
53
|
+
}
|
|
54
|
+
export function getDevSession(options) {
|
|
55
|
+
return readStoredSession(resolveStorageKey(options));
|
|
56
|
+
}
|
|
57
|
+
export function getDevScopeId(options) {
|
|
58
|
+
return getDevSession(options)?.scopeId ?? null;
|
|
59
|
+
}
|
|
60
|
+
export function clearDevAuthSession(options) {
|
|
61
|
+
localStorage.removeItem(resolveStorageKey(options));
|
|
62
|
+
}
|
|
63
|
+
export async function ensureDevAuthSession(options) {
|
|
64
|
+
const storageKey = resolveStorageKey(options);
|
|
65
|
+
const url = new URL(window.location.href);
|
|
66
|
+
const authError = url.searchParams.get('auth_error');
|
|
67
|
+
if (authError) {
|
|
68
|
+
console.error('[dev-auth] Authentication failed:', authError);
|
|
69
|
+
url.searchParams.delete('auth_error');
|
|
70
|
+
window.history.replaceState({}, '', url.toString());
|
|
71
|
+
throw new Error(`Authentication failed: ${authError}`);
|
|
72
|
+
}
|
|
73
|
+
const injectedConfig = getInjectedDevAuthConfig();
|
|
74
|
+
if (injectedConfig) {
|
|
75
|
+
const storedSession = readStoredSession(storageKey);
|
|
76
|
+
if (storedSession?.hostUrl !== injectedConfig.hostUrl ||
|
|
77
|
+
storedSession?.sessionKey !== injectedConfig.sessionKey) {
|
|
78
|
+
writeDevSession(storageKey, {
|
|
79
|
+
hostUrl: injectedConfig.hostUrl,
|
|
80
|
+
sessionKey: injectedConfig.sessionKey,
|
|
81
|
+
scopeId: null,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const session = readStoredSession(storageKey);
|
|
87
|
+
if (session?.sessionKey) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const loginPath = options.loginPath ?? 'login';
|
|
91
|
+
const loginUrl = new URL(loginPath, window.location.origin + getBaseUrl(options.baseUrl)).toString();
|
|
92
|
+
console.info('[dev-auth] redirecting browser to login', {
|
|
93
|
+
currentUrl: url.toString(),
|
|
94
|
+
baseUrl: getBaseUrl(options.baseUrl),
|
|
95
|
+
loginUrl,
|
|
96
|
+
});
|
|
97
|
+
(options.redirect ?? ((nextUrl) => window.location.replace(nextUrl)))(loginUrl);
|
|
98
|
+
await new Promise(() => { });
|
|
99
|
+
}
|
|
100
|
+
export function installDevBridge(options) {
|
|
101
|
+
const { appId, debug = false } = options;
|
|
102
|
+
const origin = window.location.origin;
|
|
103
|
+
const proxyUrl = new URL(options.proxyPath ?? 'proxy', window.location.origin + getBaseUrl(options.baseUrl)).toString();
|
|
104
|
+
const readSession = () => getDevSession(options);
|
|
105
|
+
const log = (...args) => {
|
|
106
|
+
if (debug) {
|
|
107
|
+
console.log('[bardioc-dev-bridge]', ...args);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
function reply(id, payload) {
|
|
111
|
+
window.postMessage({ type: SDK_MSG.RESPONSE, id, payload }, origin);
|
|
112
|
+
}
|
|
113
|
+
function replyError(id, error) {
|
|
114
|
+
window.postMessage({ type: SDK_MSG.RESPONSE, id, payload: null, error }, origin);
|
|
115
|
+
}
|
|
116
|
+
function ack(id, payload) {
|
|
117
|
+
window.postMessage({ type: SDK_MSG.INIT_ACK, id, payload }, origin);
|
|
118
|
+
}
|
|
119
|
+
async function handleTransport(payload) {
|
|
120
|
+
const session = readSession();
|
|
121
|
+
if (!session?.sessionKey) {
|
|
122
|
+
throw new Error('No auth session available');
|
|
123
|
+
}
|
|
124
|
+
const shouldForwardScope = payload.scopePolicy === 'optional' || payload.scopePolicy === 'required';
|
|
125
|
+
const forwardedScopeId = shouldForwardScope
|
|
126
|
+
? (payload.scopeId ?? session.scopeId ?? null)
|
|
127
|
+
: null;
|
|
128
|
+
console.info('[bardioc-dev-bridge][1/2] sending dev session to local proxy', {
|
|
129
|
+
proxyUrl,
|
|
130
|
+
appId,
|
|
131
|
+
path: payload.path,
|
|
132
|
+
method: payload.method ?? 'GET',
|
|
133
|
+
scopePolicy: payload.scopePolicy ?? 'none',
|
|
134
|
+
forwardedScopeId,
|
|
135
|
+
hasDevSessionKey: !!session.sessionKey,
|
|
136
|
+
nextStep: 'local_proxy_relays_to_host_transport',
|
|
137
|
+
});
|
|
138
|
+
const response = await fetch(proxyUrl, {
|
|
139
|
+
method: 'POST',
|
|
140
|
+
headers: { 'Content-Type': 'application/json' },
|
|
141
|
+
body: JSON.stringify({
|
|
142
|
+
sessionKey: session.sessionKey,
|
|
143
|
+
hostUrl: session.hostUrl,
|
|
144
|
+
scopeId: forwardedScopeId,
|
|
145
|
+
path: payload.path,
|
|
146
|
+
method: payload.method ?? 'GET',
|
|
147
|
+
body: payload.body,
|
|
148
|
+
contentType: payload.contentType,
|
|
149
|
+
scopePolicy: payload.scopePolicy,
|
|
150
|
+
}),
|
|
151
|
+
cache: 'no-store',
|
|
152
|
+
});
|
|
153
|
+
const text = await response.text();
|
|
154
|
+
let data = null;
|
|
155
|
+
if (text) {
|
|
156
|
+
try {
|
|
157
|
+
data = JSON.parse(text);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
data = text;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (!response.ok) {
|
|
164
|
+
console.error('[bardioc-dev-bridge] proxy request failed', {
|
|
165
|
+
proxyUrl,
|
|
166
|
+
path: payload.path,
|
|
167
|
+
method: payload.method ?? 'GET',
|
|
168
|
+
status: response.status,
|
|
169
|
+
failureStage: response.headers.get('X-Bardioc-Dev-Auth-Stage'),
|
|
170
|
+
responsePayload: data,
|
|
171
|
+
});
|
|
172
|
+
const err = new Error(`Transport ${payload.path} failed: ${response.status}`);
|
|
173
|
+
Object.assign(err, { status: response.status, responsePayload: data });
|
|
174
|
+
throw err;
|
|
175
|
+
}
|
|
176
|
+
return data;
|
|
177
|
+
}
|
|
178
|
+
async function handleKernelProxy(payload) {
|
|
179
|
+
if (payload.kernelType === 'PING') {
|
|
180
|
+
return { pong: true };
|
|
181
|
+
}
|
|
182
|
+
if (payload.kernelType === 'GET_SESSION_STATE') {
|
|
183
|
+
const session = readSession();
|
|
184
|
+
return { authenticated: !!session?.sessionKey };
|
|
185
|
+
}
|
|
186
|
+
throw new Error(`Kernel message ${payload.kernelType} not supported in dev bridge`);
|
|
187
|
+
}
|
|
188
|
+
async function onMessage(event) {
|
|
189
|
+
if (event.origin !== origin) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const data = event.data;
|
|
193
|
+
if (!data || typeof data.type !== 'string' || !data.id) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (data.type === SDK_MSG.RESPONSE || data.type === SDK_MSG.INIT_ACK) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
log('handle', data.type, data.id);
|
|
200
|
+
try {
|
|
201
|
+
switch (data.type) {
|
|
202
|
+
case SDK_MSG.INIT:
|
|
203
|
+
ack(data.id, { appId, instanceId: null });
|
|
204
|
+
return;
|
|
205
|
+
case SDK_MSG.NOTIFY:
|
|
206
|
+
return;
|
|
207
|
+
case SDK_MSG.TRANSPORT:
|
|
208
|
+
reply(data.id, await handleTransport(data.payload));
|
|
209
|
+
return;
|
|
210
|
+
case SDK_MSG.KERNEL_PROXY:
|
|
211
|
+
reply(data.id, await handleKernelProxy(data.payload));
|
|
212
|
+
return;
|
|
213
|
+
default:
|
|
214
|
+
replyError(data.id, `Dev bridge does not implement ${data.type}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
replyError(data.id, error instanceof Error ? error.message : 'unknown error');
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
window.addEventListener('message', onMessage);
|
|
222
|
+
return () => window.removeEventListener('message', onMessage);
|
|
223
|
+
}
|
package/dist/dot-env.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
export function readDotEnvFile(filePath) {
|
|
3
|
+
const env = {};
|
|
4
|
+
const fileContents = readFileSync(filePath, 'utf8');
|
|
5
|
+
for (const line of fileContents.split(/\r?\n/)) {
|
|
6
|
+
const trimmedLine = line.trim();
|
|
7
|
+
if (!trimmedLine || trimmedLine.startsWith('#')) {
|
|
8
|
+
continue;
|
|
9
|
+
}
|
|
10
|
+
const separatorIndex = trimmedLine.indexOf('=');
|
|
11
|
+
if (separatorIndex <= 0) {
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
const key = trimmedLine.slice(0, separatorIndex).trim();
|
|
15
|
+
const rawValue = trimmedLine.slice(separatorIndex + 1).trim();
|
|
16
|
+
env[key] = rawValue.replace(/^['"]|['"]$/g, '');
|
|
17
|
+
}
|
|
18
|
+
return env;
|
|
19
|
+
}
|
|
20
|
+
export function readDotEnvValue(filePath, key) {
|
|
21
|
+
return readDotEnvFile(filePath)[key] ?? null;
|
|
22
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Base SDK error class with structured error information */
|
|
2
|
+
export declare class SdkError extends Error {
|
|
3
|
+
code: string;
|
|
4
|
+
statusCode?: number | undefined;
|
|
5
|
+
context?: Record<string, unknown> | undefined;
|
|
6
|
+
constructor(message: string, code: string, statusCode?: number | undefined, context?: Record<string, unknown> | undefined);
|
|
7
|
+
}
|
|
8
|
+
/** Request timeout error */
|
|
9
|
+
export declare class TimeoutError extends SdkError {
|
|
10
|
+
constructor(operation: string, timeoutMs: number);
|
|
11
|
+
}
|
|
12
|
+
/** Network/HTTP error (4xx, 5xx) */
|
|
13
|
+
export declare class NetworkError extends SdkError {
|
|
14
|
+
constructor(message: string, statusCode: number);
|
|
15
|
+
}
|
|
16
|
+
/** Invalid parameters or validation error */
|
|
17
|
+
export declare class ValidationError extends SdkError {
|
|
18
|
+
constructor(message: string, field?: string);
|
|
19
|
+
}
|
|
20
|
+
/** Missing permissions error */
|
|
21
|
+
export declare class PermissionError extends SdkError {
|
|
22
|
+
constructor(permission: string);
|
|
23
|
+
}
|
|
24
|
+
/** Entity not found error (404) */
|
|
25
|
+
export declare class EntityNotFoundError extends SdkError {
|
|
26
|
+
constructor(id: string);
|
|
27
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** Base SDK error class with structured error information */
|
|
2
|
+
export class SdkError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
statusCode;
|
|
5
|
+
context;
|
|
6
|
+
constructor(message, code, statusCode, context) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.statusCode = statusCode;
|
|
10
|
+
this.context = context;
|
|
11
|
+
this.name = 'SdkError';
|
|
12
|
+
Object.setPrototypeOf(this, SdkError.prototype);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** Request timeout error */
|
|
16
|
+
export class TimeoutError extends SdkError {
|
|
17
|
+
constructor(operation, timeoutMs) {
|
|
18
|
+
super(`Operation '${operation}' timed out after ${timeoutMs}ms`, 'TIMEOUT', undefined, {
|
|
19
|
+
operation,
|
|
20
|
+
timeoutMs,
|
|
21
|
+
});
|
|
22
|
+
this.name = 'TimeoutError';
|
|
23
|
+
Object.setPrototypeOf(this, TimeoutError.prototype);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Network/HTTP error (4xx, 5xx) */
|
|
27
|
+
export class NetworkError extends SdkError {
|
|
28
|
+
constructor(message, statusCode) {
|
|
29
|
+
super(message, 'NETWORK_ERROR', statusCode);
|
|
30
|
+
this.name = 'NetworkError';
|
|
31
|
+
Object.setPrototypeOf(this, NetworkError.prototype);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Invalid parameters or validation error */
|
|
35
|
+
export class ValidationError extends SdkError {
|
|
36
|
+
constructor(message, field) {
|
|
37
|
+
super(message, 'VALIDATION_ERROR', 400, field ? { field } : undefined);
|
|
38
|
+
this.name = 'ValidationError';
|
|
39
|
+
Object.setPrototypeOf(this, ValidationError.prototype);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** Missing permissions error */
|
|
43
|
+
export class PermissionError extends SdkError {
|
|
44
|
+
constructor(permission) {
|
|
45
|
+
super(`Missing required permission: ${permission}`, 'PERMISSION_ERROR', 403, { permission });
|
|
46
|
+
this.name = 'PermissionError';
|
|
47
|
+
Object.setPrototypeOf(this, PermissionError.prototype);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** Entity not found error (404) */
|
|
51
|
+
export class EntityNotFoundError extends SdkError {
|
|
52
|
+
constructor(id) {
|
|
53
|
+
super(`Entity not found: ${id}`, 'ENTITY_NOT_FOUND', 404, { id });
|
|
54
|
+
this.name = 'EntityNotFoundError';
|
|
55
|
+
Object.setPrototypeOf(this, EntityNotFoundError.prototype);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { getInjectedDevAuthConfig } from './dev.js';
|
|
2
|
+
import { SDK_MSG } from './protocol.js';
|
|
3
|
+
import { GraphTransport } from './transports/graph-transport.js';
|
|
4
|
+
import { OsTransport } from './transports/os-transport.js';
|
|
5
|
+
let counter = 0;
|
|
6
|
+
function nextId() {
|
|
7
|
+
return `sdk_${Date.now()}_${++counter}`;
|
|
8
|
+
}
|
|
9
|
+
function assertStoreName(storeName) {
|
|
10
|
+
const normalized = storeName.trim();
|
|
11
|
+
if (!normalized) {
|
|
12
|
+
throw new Error('storeName must be a non-empty string');
|
|
13
|
+
}
|
|
14
|
+
return normalized;
|
|
15
|
+
}
|
|
16
|
+
/** Create a bridge between an iframe app and its host OS via `postMessage`. */
|
|
17
|
+
export function createHostBridge(config) {
|
|
18
|
+
const { appId: hintAppId, connectTimeout = 5000, debug = false } = config;
|
|
19
|
+
const hostWindow = globalThis.window;
|
|
20
|
+
// When inside an iframe, document.referrer gives us the parent's origin.
|
|
21
|
+
const inferredHostOrigin = (() => {
|
|
22
|
+
try {
|
|
23
|
+
if (hostWindow.parent !== hostWindow && document.referrer) {
|
|
24
|
+
return new URL(document.referrer).origin;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// ignore parse errors
|
|
29
|
+
}
|
|
30
|
+
return hostWindow.location.origin;
|
|
31
|
+
})();
|
|
32
|
+
const targetOrigin = config.targetOrigin ?? inferredHostOrigin;
|
|
33
|
+
const isInsideIframe = hostWindow.parent !== hostWindow;
|
|
34
|
+
function log(...args) {
|
|
35
|
+
if (debug)
|
|
36
|
+
console.log('[app-sdk]', ...args);
|
|
37
|
+
}
|
|
38
|
+
log('createHostBridge', { appId: hintAppId, targetOrigin, isInsideIframe });
|
|
39
|
+
const standaloneDevAuthActive = !isInsideIframe && !!getInjectedDevAuthConfig();
|
|
40
|
+
if (!isInsideIframe && !standaloneDevAuthActive) {
|
|
41
|
+
console.warn('[app-sdk] NOT inside an iframe — bridge will not work standalone.');
|
|
42
|
+
}
|
|
43
|
+
let _connected = false;
|
|
44
|
+
let _context = null;
|
|
45
|
+
let _destroyed = false;
|
|
46
|
+
const commandHandlers = new Map();
|
|
47
|
+
const osConfigUpdateCallbacks = new Set();
|
|
48
|
+
const pending = new Map();
|
|
49
|
+
function isAllowedMessageOrigin(origin) {
|
|
50
|
+
return origin === targetOrigin || origin === hostWindow.location.origin;
|
|
51
|
+
}
|
|
52
|
+
function handleCommandInvoke(data) {
|
|
53
|
+
const { commandId } = data.payload;
|
|
54
|
+
const handler = commandHandlers.get(commandId);
|
|
55
|
+
if (!handler) {
|
|
56
|
+
respondToHost(data.id, null, `Command "${commandId}" is not registered`);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
Promise.resolve()
|
|
60
|
+
.then(() => handler())
|
|
61
|
+
.then(() => {
|
|
62
|
+
respondToHost(data.id, { ok: true });
|
|
63
|
+
})
|
|
64
|
+
.catch((error) => {
|
|
65
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
66
|
+
respondToHost(data.id, null, message);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function handleOsConfigUpdate(data) {
|
|
70
|
+
const newOsConfig = data.payload;
|
|
71
|
+
if (_context) {
|
|
72
|
+
_context.osConfig = newOsConfig;
|
|
73
|
+
}
|
|
74
|
+
for (const callback of osConfigUpdateCallbacks) {
|
|
75
|
+
try {
|
|
76
|
+
callback(newOsConfig);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
console.error('[app-sdk] OS config update callback error:', error);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function handlePendingResponse(data) {
|
|
84
|
+
const request = pending.get(data.id);
|
|
85
|
+
if (!request) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
pending.delete(data.id);
|
|
89
|
+
if (data.error) {
|
|
90
|
+
request.reject(new Error(data.error));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
request.resolve(data.payload);
|
|
94
|
+
}
|
|
95
|
+
function onMessage(event) {
|
|
96
|
+
if (!isAllowedMessageOrigin(event.origin)) {
|
|
97
|
+
log('ignoring message from', event.origin, '(expected', targetOrigin, ')');
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const data = event.data;
|
|
101
|
+
if (!data || typeof data.type !== 'string')
|
|
102
|
+
return;
|
|
103
|
+
if (!data.id)
|
|
104
|
+
return;
|
|
105
|
+
log('received', data.type, data.id, data.error ? `error: ${data.error}` : '');
|
|
106
|
+
switch (data.type) {
|
|
107
|
+
case SDK_MSG.COMMAND_INVOKE:
|
|
108
|
+
handleCommandInvoke(data);
|
|
109
|
+
return;
|
|
110
|
+
case SDK_MSG.OS_CONFIG_UPDATE:
|
|
111
|
+
handleOsConfigUpdate(data);
|
|
112
|
+
return;
|
|
113
|
+
case SDK_MSG.INIT_ACK:
|
|
114
|
+
case SDK_MSG.RESPONSE:
|
|
115
|
+
handlePendingResponse(data);
|
|
116
|
+
return;
|
|
117
|
+
default:
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
hostWindow.addEventListener('message', onMessage);
|
|
122
|
+
function postToHost(message) {
|
|
123
|
+
hostWindow.parent.postMessage(message, targetOrigin);
|
|
124
|
+
}
|
|
125
|
+
function respondToHost(id, payload, error) {
|
|
126
|
+
postToHost({
|
|
127
|
+
type: SDK_MSG.RESPONSE,
|
|
128
|
+
id,
|
|
129
|
+
payload: error ? undefined : payload,
|
|
130
|
+
error,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
function send(type, payload) {
|
|
134
|
+
if (_destroyed)
|
|
135
|
+
throw new Error('Bridge is destroyed — cannot send messages');
|
|
136
|
+
const id = nextId();
|
|
137
|
+
const msg = { type, id, payload };
|
|
138
|
+
log('sending', type, id);
|
|
139
|
+
postToHost(msg);
|
|
140
|
+
return id;
|
|
141
|
+
}
|
|
142
|
+
function request(type, payload, timeout = 60000) {
|
|
143
|
+
return new Promise((resolve, reject) => {
|
|
144
|
+
const id = send(type, payload);
|
|
145
|
+
pending.set(id, { resolve, reject });
|
|
146
|
+
setTimeout(() => {
|
|
147
|
+
if (pending.has(id)) {
|
|
148
|
+
pending.delete(id);
|
|
149
|
+
reject(new Error(`SDK request ${type} timed out after ${timeout}ms`));
|
|
150
|
+
}
|
|
151
|
+
}, timeout);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
const storage = {
|
|
155
|
+
get: key => request(SDK_MSG.STORAGE_GET, { key }),
|
|
156
|
+
set: (key, value) => request(SDK_MSG.STORAGE_SET, { key, value }),
|
|
157
|
+
delete: key => request(SDK_MSG.STORAGE_DELETE, { key }),
|
|
158
|
+
keys: () => request(SDK_MSG.STORAGE_KEYS, {}),
|
|
159
|
+
};
|
|
160
|
+
const baseRequest = (payload) => {
|
|
161
|
+
log('[SDK] transport.request', payload);
|
|
162
|
+
return request(SDK_MSG.TRANSPORT, payload);
|
|
163
|
+
};
|
|
164
|
+
const transport = {
|
|
165
|
+
graph: new GraphTransport(baseRequest, { debug }),
|
|
166
|
+
os: new OsTransport(baseRequest),
|
|
167
|
+
};
|
|
168
|
+
function idb(storeName) {
|
|
169
|
+
const normalizedStoreName = assertStoreName(storeName);
|
|
170
|
+
return {
|
|
171
|
+
get: key => request(SDK_MSG.IDB_GET, { storeName: normalizedStoreName, key }),
|
|
172
|
+
put: (key, value) => request(SDK_MSG.IDB_PUT, { storeName: normalizedStoreName, key, value }),
|
|
173
|
+
delete: key => request(SDK_MSG.IDB_DELETE, { storeName: normalizedStoreName, key }),
|
|
174
|
+
query: (options = {}) => request(SDK_MSG.IDB_QUERY, {
|
|
175
|
+
storeName: normalizedStoreName,
|
|
176
|
+
prefix: options.prefix,
|
|
177
|
+
limit: options.limit,
|
|
178
|
+
}),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
const bridge = {
|
|
182
|
+
get connected() {
|
|
183
|
+
return _connected;
|
|
184
|
+
},
|
|
185
|
+
get context() {
|
|
186
|
+
return _context;
|
|
187
|
+
},
|
|
188
|
+
async connect() {
|
|
189
|
+
if (_connected && _context)
|
|
190
|
+
return _context;
|
|
191
|
+
const result = await request(SDK_MSG.INIT, { appId: hintAppId }, connectTimeout);
|
|
192
|
+
_context = result;
|
|
193
|
+
_connected = true;
|
|
194
|
+
return result;
|
|
195
|
+
},
|
|
196
|
+
notify(message, level = 'info') {
|
|
197
|
+
send(SDK_MSG.NOTIFY, { message, level });
|
|
198
|
+
},
|
|
199
|
+
storage,
|
|
200
|
+
transport,
|
|
201
|
+
idb,
|
|
202
|
+
setCommands(commands) {
|
|
203
|
+
const payload = { commands };
|
|
204
|
+
return request(SDK_MSG.SET_COMMANDS, payload);
|
|
205
|
+
},
|
|
206
|
+
setMenu(menu) {
|
|
207
|
+
const payload = { menu };
|
|
208
|
+
return request(SDK_MSG.SET_MENU, payload);
|
|
209
|
+
},
|
|
210
|
+
setActiveTab(activeTab) {
|
|
211
|
+
const payload = { activeTab };
|
|
212
|
+
return request(SDK_MSG.SET_ACTIVE_TAB, payload);
|
|
213
|
+
},
|
|
214
|
+
sendShortcutEvent(payload) {
|
|
215
|
+
send(SDK_MSG.SHORTCUT_EVENT, payload);
|
|
216
|
+
},
|
|
217
|
+
registerCommandHandlers(handlers) {
|
|
218
|
+
for (const [commandId, handler] of Object.entries(handlers)) {
|
|
219
|
+
commandHandlers.set(commandId, handler);
|
|
220
|
+
}
|
|
221
|
+
return () => {
|
|
222
|
+
for (const [commandId, handler] of Object.entries(handlers)) {
|
|
223
|
+
if (commandHandlers.get(commandId) === handler) {
|
|
224
|
+
commandHandlers.delete(commandId);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
},
|
|
229
|
+
onOsConfigUpdate(callback) {
|
|
230
|
+
osConfigUpdateCallbacks.add(callback);
|
|
231
|
+
return () => {
|
|
232
|
+
osConfigUpdateCallbacks.delete(callback);
|
|
233
|
+
};
|
|
234
|
+
},
|
|
235
|
+
/**
|
|
236
|
+
* @deprecated Legacy/internal only. Hosted apps should use scoped SDK APIs
|
|
237
|
+
* such as `idb`, `transport`, and `launchApp` instead of kernel-proxy.
|
|
238
|
+
*/
|
|
239
|
+
sendToKernel(kernelType, kernelPayload) {
|
|
240
|
+
return request(SDK_MSG.KERNEL_PROXY, { kernelType, kernelPayload });
|
|
241
|
+
},
|
|
242
|
+
launchApp(appId) {
|
|
243
|
+
return request(SDK_MSG.LAUNCH_APP, { appId });
|
|
244
|
+
},
|
|
245
|
+
destroy() {
|
|
246
|
+
if (_destroyed)
|
|
247
|
+
return;
|
|
248
|
+
log('bridge.destroy(), pending requests:', pending.size);
|
|
249
|
+
_destroyed = true;
|
|
250
|
+
hostWindow.removeEventListener('message', onMessage);
|
|
251
|
+
for (const [, p] of pending)
|
|
252
|
+
p.reject(new Error('Bridge destroyed'));
|
|
253
|
+
pending.clear();
|
|
254
|
+
commandHandlers.clear();
|
|
255
|
+
_connected = false;
|
|
256
|
+
_context = null;
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
return bridge;
|
|
260
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { createHostBridge } from './host-bridge.js';
|
|
2
|
+
export { DEFAULT_WINDOW_SIZE, MANIFEST_FILENAME, WINDOW_SIZES, validateManifest, } from './manifest.js';
|
|
3
|
+
export type { AppManifest, AppManifestPermission, AppManifestWindowConfig, CustomWindowSize, WindowSizePreset, } from './manifest.js';
|
|
4
|
+
export { useOsConfig } from './react.js';
|
|
5
|
+
export { SDK_CONTRACT_MATRIX, getRequiredPermissionForSdkMessage } from './contract-matrix.js';
|
|
6
|
+
export { ALLOWED_KERNEL_MESSAGES, SDK_MSG } from './protocol.js';
|
|
7
|
+
export type { SdkMessageType } from './protocol.js';
|
|
8
|
+
export { EntityNotFoundError, NetworkError, PermissionError, SdkError, TimeoutError, ValidationError, } from './errors.js';
|
|
9
|
+
export type { Application, BatchResult, Configuration, CreateMemberInput, CreateUnitInput, DeleteOptions, FileEntry, GetNodeOptions, GraphEdgeRaw, GraphNodeRaw, GraphTransport, GremlinOptions, HistoryEntry, HistoryOptions, HostBridge, HostBridgeConfig, InstanceList, MutationOptions, NotifyLevel, OrgMember, OrgStructure, OrgUnit, OsConfig, OsRequestOptions, OsTransport, QueryOptions, SdkCommandDefinition, SdkCommandHandler, SdkCommandInvokePayload, SdkIdbDeletePayload, SdkIdbEntry, SdkIdbGetPayload, SdkIdbPutPayload, SdkIdbQueryPayload, SdkIdbStore, SdkInitResponse, SdkKernelProxyPayload, SdkMenuItem, SdkMenuRoot, SdkMessage, SdkNotifyPayload, SdkResponse, SdkSetActiveTabPayload, SdkSetCommandsPayload, SdkSetMenuPayload, SdkShortcutEventPayload, SdkStorage, SdkStorageDeletePayload, SdkStorageGetPayload, SdkStorageSetPayload, SdkTransport, SdkTransportPayload, TimeseriesOptions, TimeseriesQueryOptions, TimeseriesValue, TransportScopeOptions, UpdateMemberInput, UpdateUnitInput, UserProfile, } from './types.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { createHostBridge } from './host-bridge.js';
|
|
2
|
+
export { DEFAULT_WINDOW_SIZE, MANIFEST_FILENAME, WINDOW_SIZES, validateManifest, } from './manifest.js';
|
|
3
|
+
export { useOsConfig } from './react.js';
|
|
4
|
+
export { SDK_CONTRACT_MATRIX, getRequiredPermissionForSdkMessage } from './contract-matrix.js';
|
|
5
|
+
export { ALLOWED_KERNEL_MESSAGES, SDK_MSG } from './protocol.js';
|
|
6
|
+
export { EntityNotFoundError, NetworkError, PermissionError, SdkError, TimeoutError, ValidationError, } from './errors.js';
|