@bardioc/app-sdk 0.4.0 → 0.4.3
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 +4 -9
- package/dist/contract-matrix.d.ts +18 -0
- package/dist/contract-matrix.js +18 -0
- package/dist/dev-auth-vite.d.ts +3 -7
- package/dist/dev-auth-vite.js +34 -176
- package/dist/dev-proxy.d.ts +12 -3
- package/dist/dev-proxy.js +81 -22
- package/dist/dev-session-cli.js +0 -0
- package/dist/dev.d.ts +4 -29
- package/dist/dev.js +36 -141
- package/dist/host-bridge.js +49 -5
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/manifest.d.ts +1 -1
- package/dist/manifest.js +1 -0
- package/dist/protocol.d.ts +4 -1
- package/dist/protocol.js +3 -0
- package/dist/react.d.ts +9 -4
- package/dist/react.js +15 -7
- package/dist/types.d.ts +37 -0
- package/dist/vite.js +1 -6
- package/package.json +1 -8
package/README.md
CHANGED
|
@@ -56,7 +56,7 @@ createRoot(document.getElementById('root')!).render(
|
|
|
56
56
|
| --------------------------- | ------------------------------------------- |
|
|
57
57
|
| `@bardioc/app-sdk` | `createHostBridge`, errors, types, manifest |
|
|
58
58
|
| `@bardioc/app-sdk/react` | `AppSdkProvider`, hooks (`useSdk`, etc.) |
|
|
59
|
-
| `@bardioc/app-sdk/dev` |
|
|
59
|
+
| `@bardioc/app-sdk/dev` | standalone dev bridge helpers |
|
|
60
60
|
| `@bardioc/app-sdk/types` | Type-only TypeScript exports |
|
|
61
61
|
| `@bardioc/app-sdk/protocol` | `SDK_MSG` constants, wire format types |
|
|
62
62
|
| `@bardioc/app-sdk/vite` | `bardiocApp()` Vite plugin |
|
|
@@ -91,12 +91,10 @@ import {
|
|
|
91
91
|
useSdk,
|
|
92
92
|
useNotify,
|
|
93
93
|
useSendToKernel,
|
|
94
|
-
useStandaloneDevLogout,
|
|
95
94
|
} from '@bardioc/app-sdk/react';
|
|
96
95
|
|
|
97
96
|
// Standalone dev helpers
|
|
98
97
|
import {
|
|
99
|
-
ensureDevAuthSession,
|
|
100
98
|
installDevBridge,
|
|
101
99
|
isDevStandalone,
|
|
102
100
|
isInsideIframe,
|
|
@@ -137,12 +135,10 @@ New apps scaffolded with `create-bardioc-app` include the `<link>` already.
|
|
|
137
135
|
|
|
138
136
|
Standalone local development is host-backed only.
|
|
139
137
|
|
|
140
|
-
1.
|
|
141
|
-
2.
|
|
142
|
-
3. Paste the snippet into your local `.env`.
|
|
143
|
-
4. Set `VITE_ENABLE_DEV_AUTH=true` and run `pnpm dev`.
|
|
138
|
+
1. Set `DEV_SECRET` and `API_BASE_URL` in your local `.env`.
|
|
139
|
+
2. Run `pnpm dev`.
|
|
144
140
|
|
|
145
|
-
|
|
141
|
+
The Vite plugin proxies `/rest/*` requests to `API_BASE_URL` with the `X-Dev-Secret` header, and `installDevBridge()` handles SDK messages when running in standalone dev.
|
|
146
142
|
|
|
147
143
|
For public testing against a live URL, run `pnpm dev:live` and use the emitted tunnel URL as the app's live URL in the host App Store configuration.
|
|
148
144
|
|
|
@@ -151,7 +147,6 @@ For public testing against a live URL, run `pnpm dev:live` and use the emitted t
|
|
|
151
147
|
- `useSdk()` — raw bridge instance
|
|
152
148
|
- `useNotify()` — fire-and-forget toast
|
|
153
149
|
- `useSendToKernel()` — proxy a whitelisted message to the OS service worker
|
|
154
|
-
- `useStandaloneDevLogout()` — clear standalone dev auth state and reload the page
|
|
155
150
|
|
|
156
151
|
### IndexedDB persistence
|
|
157
152
|
|
|
@@ -108,6 +108,24 @@ export declare const SDK_CONTRACT_MATRIX: {
|
|
|
108
108
|
readonly permission: null;
|
|
109
109
|
readonly expectedHostBehavior: "Host-to-app command callback delivery.";
|
|
110
110
|
};
|
|
111
|
+
readonly SDK_EVENT_SUBSCRIBE: {
|
|
112
|
+
readonly requestPayload: "EventSubscribeRequest";
|
|
113
|
+
readonly responsePayload: "EventSubscribeResponse";
|
|
114
|
+
readonly permission: "events";
|
|
115
|
+
readonly expectedHostBehavior: "Open a scoped graph-event SSE stream for the requested OGIT types.";
|
|
116
|
+
};
|
|
117
|
+
readonly SDK_EVENT_UNSUBSCRIBE: {
|
|
118
|
+
readonly requestPayload: "EventUnsubscribeRequest";
|
|
119
|
+
readonly responsePayload: "{ ok: true }";
|
|
120
|
+
readonly permission: "events";
|
|
121
|
+
readonly expectedHostBehavior: "Close the graph-event SSE stream for the given subscription.";
|
|
122
|
+
};
|
|
123
|
+
readonly SDK_GRAPH_EVENT: {
|
|
124
|
+
readonly requestPayload: "GraphEventPushPayload";
|
|
125
|
+
readonly responsePayload: null;
|
|
126
|
+
readonly permission: null;
|
|
127
|
+
readonly expectedHostBehavior: "Host-to-app graph-event push delivery (no ack).";
|
|
128
|
+
};
|
|
111
129
|
readonly SDK_OS_CONFIG_UPDATE: {
|
|
112
130
|
readonly requestPayload: "OsConfig";
|
|
113
131
|
readonly responsePayload: null;
|
package/dist/contract-matrix.js
CHANGED
|
@@ -108,6 +108,24 @@ export const SDK_CONTRACT_MATRIX = {
|
|
|
108
108
|
permission: null,
|
|
109
109
|
expectedHostBehavior: 'Host-to-app command callback delivery.',
|
|
110
110
|
},
|
|
111
|
+
[SDK_MSG.EVENT_SUBSCRIBE]: {
|
|
112
|
+
requestPayload: 'EventSubscribeRequest',
|
|
113
|
+
responsePayload: 'EventSubscribeResponse',
|
|
114
|
+
permission: 'events',
|
|
115
|
+
expectedHostBehavior: 'Open a scoped graph-event SSE stream for the requested OGIT types.',
|
|
116
|
+
},
|
|
117
|
+
[SDK_MSG.EVENT_UNSUBSCRIBE]: {
|
|
118
|
+
requestPayload: 'EventUnsubscribeRequest',
|
|
119
|
+
responsePayload: '{ ok: true }',
|
|
120
|
+
permission: 'events',
|
|
121
|
+
expectedHostBehavior: 'Close the graph-event SSE stream for the given subscription.',
|
|
122
|
+
},
|
|
123
|
+
[SDK_MSG.GRAPH_EVENT]: {
|
|
124
|
+
requestPayload: 'GraphEventPushPayload',
|
|
125
|
+
responsePayload: null,
|
|
126
|
+
permission: null,
|
|
127
|
+
expectedHostBehavior: 'Host-to-app graph-event push delivery (no ack).',
|
|
128
|
+
},
|
|
111
129
|
[SDK_MSG.OS_CONFIG_UPDATE]: {
|
|
112
130
|
requestPayload: 'OsConfig',
|
|
113
131
|
responsePayload: null,
|
package/dist/dev-auth-vite.d.ts
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import { type DevAuthProxyOptions } from './dev-proxy.js';
|
|
2
3
|
type VitePlugin = {
|
|
3
4
|
name: string;
|
|
4
5
|
apply?: 'serve' | 'build';
|
|
5
6
|
enforce?: 'pre' | 'post';
|
|
6
7
|
configureServer?: (server: ViteDevServerLike) => void;
|
|
7
|
-
transformIndexHtml?: () => Array<{
|
|
8
|
-
tag: string;
|
|
9
|
-
attrs?: Record<string, string>;
|
|
10
|
-
children?: string;
|
|
11
|
-
injectTo?: 'head' | 'body' | 'head-prepend' | 'body-prepend';
|
|
12
|
-
}> | void;
|
|
13
8
|
};
|
|
14
9
|
interface ViteDevServerLike {
|
|
15
10
|
config: {
|
|
@@ -28,7 +23,8 @@ interface ViteDevServerLike {
|
|
|
28
23
|
}
|
|
29
24
|
export interface BardiocDevAuthOptions {
|
|
30
25
|
appName: string;
|
|
31
|
-
|
|
26
|
+
/** Optional explicit proxy options. If omitted, DEV_SECRET and API_BASE_URL are read from env. */
|
|
27
|
+
proxy?: DevAuthProxyOptions;
|
|
32
28
|
}
|
|
33
29
|
export declare function bardiocDevAuth(options: BardiocDevAuthOptions): VitePlugin;
|
|
34
30
|
export {};
|
package/dist/dev-auth-vite.js
CHANGED
|
@@ -1,221 +1,79 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
|
-
import {
|
|
3
|
+
import { createDevAuthProxyHandler } from './dev-proxy.js';
|
|
4
4
|
import { readDotEnvFile } from './dot-env.js';
|
|
5
|
-
function resolveSessionStorageKey(options) {
|
|
6
|
-
return options.sessionStorageKey ?? `bardioc.dev.auth.${options.appName}`;
|
|
7
|
-
}
|
|
8
5
|
function readRuntimeEnv(server) {
|
|
9
6
|
const envDir = server.config.envDir ?? server.config.root;
|
|
10
7
|
const mode = server.config.mode?.trim();
|
|
11
|
-
const
|
|
8
|
+
const env = {};
|
|
9
|
+
for (const fileName of [
|
|
12
10
|
'.env',
|
|
13
11
|
'.env.local',
|
|
14
12
|
mode ? `.env.${mode}` : null,
|
|
15
13
|
mode ? `.env.${mode}.local` : null,
|
|
16
|
-
]
|
|
17
|
-
|
|
18
|
-
for (const fileName of envFileNames) {
|
|
19
|
-
if (!fileName) {
|
|
14
|
+
]) {
|
|
15
|
+
if (!fileName)
|
|
20
16
|
continue;
|
|
21
|
-
}
|
|
22
17
|
const filePath = resolve(envDir, fileName);
|
|
23
|
-
if (
|
|
24
|
-
|
|
18
|
+
if (existsSync(filePath)) {
|
|
19
|
+
Object.assign(env, readDotEnvFile(filePath));
|
|
25
20
|
}
|
|
26
|
-
Object.assign(env, readDotEnvFile(filePath));
|
|
27
21
|
}
|
|
28
22
|
for (const [key, value] of Object.entries(process.env)) {
|
|
29
23
|
if (typeof value === 'string') {
|
|
30
24
|
env[key] = value;
|
|
31
25
|
}
|
|
32
26
|
}
|
|
33
|
-
return env;
|
|
34
|
-
}
|
|
35
|
-
function readDevAuthEnv(server, options) {
|
|
36
|
-
const env = readRuntimeEnv(server);
|
|
37
|
-
const hostUrl = env.DEV_AUTH_HOST_URL?.trim().replace(/\/$/, '');
|
|
38
|
-
const devSessionKey = env.DEV_SESSION_KEY?.trim();
|
|
39
|
-
const exchangeAppId = env.VITE_APP_ID?.trim();
|
|
40
|
-
if (!hostUrl || !devSessionKey || !exchangeAppId) {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
27
|
return {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
devSessionKey,
|
|
47
|
-
sessionStorageKey: resolveSessionStorageKey(options),
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
function getRequestPathname(req) {
|
|
51
|
-
const originalUrl = req.__bardiocOriginalUrl;
|
|
52
|
-
return new URL(originalUrl ?? req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`)
|
|
53
|
-
.pathname;
|
|
54
|
-
}
|
|
55
|
-
function getBasePath(req, routePath) {
|
|
56
|
-
const pathname = getRequestPathname(req);
|
|
57
|
-
if (pathname.endsWith(routePath)) {
|
|
58
|
-
const basePath = pathname.slice(0, -routePath.length);
|
|
59
|
-
return basePath || '/';
|
|
60
|
-
}
|
|
61
|
-
return '/';
|
|
62
|
-
}
|
|
63
|
-
function buildAppPath(basePath, path) {
|
|
64
|
-
const normalizedBase = basePath === '/' ? '' : basePath.replace(/\/$/, '');
|
|
65
|
-
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
|
66
|
-
return `${normalizedBase}${normalizedPath}`;
|
|
67
|
-
}
|
|
68
|
-
function sendHtml(res, status, html) {
|
|
69
|
-
res.statusCode = status;
|
|
70
|
-
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
71
|
-
res.setHeader('Cache-Control', 'no-store');
|
|
72
|
-
res.end(html);
|
|
73
|
-
}
|
|
74
|
-
function renderLoginHtml(session, basePath, sessionStorageKey) {
|
|
75
|
-
const payload = JSON.stringify(JSON.stringify(session))
|
|
76
|
-
.replace(/</g, '\\u003c')
|
|
77
|
-
.replace(/>/g, '\\u003e')
|
|
78
|
-
.replace(/&/g, '\\u0026');
|
|
79
|
-
const redirectPath = buildAppPath(basePath, '/');
|
|
80
|
-
return `<!doctype html>
|
|
81
|
-
<html lang="en">
|
|
82
|
-
<head>
|
|
83
|
-
<meta charset="utf-8" />
|
|
84
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
85
|
-
<title>Signing in...</title>
|
|
86
|
-
</head>
|
|
87
|
-
<body>
|
|
88
|
-
<script>
|
|
89
|
-
window.localStorage.removeItem('${sessionStorageKey.replace('bardioc.dev.auth.', '')}.dev.auth');
|
|
90
|
-
window.localStorage.setItem('${sessionStorageKey}', ${payload});
|
|
91
|
-
window.location.replace(${JSON.stringify(redirectPath)});
|
|
92
|
-
</script>
|
|
93
|
-
</body>
|
|
94
|
-
</html>`;
|
|
95
|
-
}
|
|
96
|
-
function createLoginHandler(env) {
|
|
97
|
-
return async (req, res) => {
|
|
98
|
-
const basePath = getBasePath(req, '/login');
|
|
99
|
-
sendHtml(res, 200, renderLoginHtml({
|
|
100
|
-
sessionKey: env.devSessionKey,
|
|
101
|
-
hostUrl: env.hostUrl,
|
|
102
|
-
scopeId: null,
|
|
103
|
-
}, basePath, env.sessionStorageKey));
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
function createProxyHandler(options) {
|
|
107
|
-
return async (req, res) => {
|
|
108
|
-
const payload = await readJsonBody(req);
|
|
109
|
-
if (!payload?.path) {
|
|
110
|
-
sendJson(res, 400, { error: 'invalid_payload' });
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
try {
|
|
114
|
-
const transportResponse = await forwardDevAuthProxyRequest(payload, {
|
|
115
|
-
appId: options.appId,
|
|
116
|
-
hostUrl: options.env.hostUrl,
|
|
117
|
-
devSessionKey: options.env.devSessionKey,
|
|
118
|
-
});
|
|
119
|
-
const responseText = await transportResponse.text();
|
|
120
|
-
res.statusCode = transportResponse.status;
|
|
121
|
-
const contentType = transportResponse.headers.get('content-type');
|
|
122
|
-
if (contentType) {
|
|
123
|
-
res.setHeader('Content-Type', contentType);
|
|
124
|
-
}
|
|
125
|
-
res.setHeader('Cache-Control', 'no-store');
|
|
126
|
-
res.end(responseText);
|
|
127
|
-
}
|
|
128
|
-
catch (error) {
|
|
129
|
-
if (error instanceof Error && error.message === 'missing_dev_session') {
|
|
130
|
-
sendJson(res, 400, { error: 'missing_dev_session' });
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
sendJson(res, 502, { error: 'host_transport_failed' });
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
function createDevAuthRouter(options) {
|
|
138
|
-
const handleLogin = createLoginHandler(options.env);
|
|
139
|
-
const handleProxy = createProxyHandler(options);
|
|
140
|
-
const handlers = {
|
|
141
|
-
'GET /login': handleLogin,
|
|
142
|
-
'POST /proxy': handleProxy,
|
|
143
|
-
};
|
|
144
|
-
return async (req, res) => {
|
|
145
|
-
const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
|
|
146
|
-
const key = `${req.method ?? 'GET'} ${url.pathname}`;
|
|
147
|
-
const handler = handlers[key];
|
|
148
|
-
if (!handler) {
|
|
149
|
-
sendJson(res, 404, { error: 'not_found' });
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
await handler(req, res);
|
|
28
|
+
DEV_SECRET: env.DEV_SECRET?.trim(),
|
|
29
|
+
API_BASE_URL: env.API_BASE_URL?.trim().replace(/\/$/, ''),
|
|
153
30
|
};
|
|
154
31
|
}
|
|
155
32
|
export function bardiocDevAuth(options) {
|
|
156
|
-
let
|
|
33
|
+
let proxyOptions = options.proxy ?? null;
|
|
157
34
|
return {
|
|
158
35
|
name: 'bardioc-dev-auth',
|
|
159
36
|
apply: 'serve',
|
|
160
37
|
enforce: 'pre',
|
|
161
38
|
configureServer(server) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
39
|
+
if (!proxyOptions) {
|
|
40
|
+
const env = readRuntimeEnv(server);
|
|
41
|
+
if (!env.DEV_SECRET || !env.API_BASE_URL) {
|
|
42
|
+
server.config.logger.warn(`[bardioc-dev-auth] DEV_SECRET and API_BASE_URL are required for dev auth — disabled for appName="${options.appName}".`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
proxyOptions = {
|
|
46
|
+
apiBaseUrl: env.API_BASE_URL,
|
|
47
|
+
devSecret: env.DEV_SECRET,
|
|
48
|
+
};
|
|
166
49
|
}
|
|
167
|
-
const
|
|
168
|
-
hostUrl: runtimeConfig.hostUrl,
|
|
169
|
-
devSessionKey: runtimeConfig.devSessionKey,
|
|
170
|
-
sessionStorageKey: runtimeConfig.sessionStorageKey,
|
|
171
|
-
};
|
|
172
|
-
const router = createDevAuthRouter({ appId: runtimeConfig.exchangeAppId, env });
|
|
173
|
-
server.config.logger.info(`[bardioc-dev-auth] enabled for appName="${options.appName}" appId="${runtimeConfig.exchangeAppId}" → ${runtimeConfig.hostUrl}/api/transport`);
|
|
50
|
+
const handler = createDevAuthProxyHandler(proxyOptions);
|
|
174
51
|
const basePath = server.config.base.endsWith('/')
|
|
175
52
|
? server.config.base.slice(0, -1)
|
|
176
53
|
: server.config.base;
|
|
54
|
+
server.config.logger.info(`[bardioc-dev-auth] proxying /rest/* → ${proxyOptions.apiBaseUrl} for appName="${options.appName}"`);
|
|
177
55
|
server.middlewares.use(async (req, res, next) => {
|
|
178
56
|
const originalUrl = req.url ?? '';
|
|
179
|
-
const devAuthReq = req;
|
|
180
57
|
const parsed = new URL(originalUrl, 'http://localhost');
|
|
181
58
|
const pathname = parsed.pathname;
|
|
182
59
|
const routePath = pathname.startsWith(basePath)
|
|
183
60
|
? pathname.slice(basePath.length) || '/'
|
|
184
61
|
: pathname;
|
|
185
|
-
|
|
186
|
-
if (shouldHandle) {
|
|
187
|
-
try {
|
|
188
|
-
devAuthReq.__bardiocOriginalUrl = originalUrl;
|
|
189
|
-
req.url = `${routePath}${parsed.search}`;
|
|
190
|
-
await router(req, res);
|
|
191
|
-
}
|
|
192
|
-
catch (error) {
|
|
193
|
-
next(error);
|
|
194
|
-
}
|
|
195
|
-
finally {
|
|
196
|
-
req.url = originalUrl;
|
|
197
|
-
delete devAuthReq.__bardiocOriginalUrl;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
else {
|
|
62
|
+
if (!routePath.startsWith('/rest/')) {
|
|
201
63
|
next();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
req.url = `${routePath}${parsed.search}`;
|
|
68
|
+
await handler(req, res);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
next(error);
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
req.url = originalUrl;
|
|
202
75
|
}
|
|
203
76
|
});
|
|
204
77
|
},
|
|
205
|
-
transformIndexHtml() {
|
|
206
|
-
if (!runtimeConfig) {
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
return [
|
|
210
|
-
{
|
|
211
|
-
tag: 'script',
|
|
212
|
-
injectTo: 'head',
|
|
213
|
-
children: `window.__BARDIOC_DEV_AUTH__ = ${JSON.stringify({
|
|
214
|
-
hostUrl: runtimeConfig.hostUrl,
|
|
215
|
-
sessionKey: runtimeConfig.devSessionKey,
|
|
216
|
-
})};`,
|
|
217
|
-
},
|
|
218
|
-
];
|
|
219
|
-
},
|
|
220
78
|
};
|
|
221
79
|
}
|
package/dist/dev-proxy.d.ts
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
2
|
export interface DevAuthProxyOptions {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
/** Upstream BFF base URL (e.g. `https://your-bff-host`). */
|
|
4
|
+
apiBaseUrl: string;
|
|
5
|
+
/** Dev secret copied from the backend. */
|
|
6
|
+
devSecret: string;
|
|
7
|
+
/** Optional CORS origin (e.g. `*`). */
|
|
6
8
|
allowOrigin?: string;
|
|
7
9
|
}
|
|
10
|
+
interface SendJsonOptions {
|
|
11
|
+
allowOrigin?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function readRequestBody(req: IncomingMessage): Promise<Buffer>;
|
|
14
|
+
export declare function applyCors(res: ServerResponse, allowOrigin?: string): void;
|
|
15
|
+
export declare function sendJson(res: ServerResponse, status: number, body: unknown, options?: SendJsonOptions): void;
|
|
8
16
|
export declare function createDevAuthProxyHandler(options: DevAuthProxyOptions): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
17
|
+
export {};
|
package/dist/dev-proxy.js
CHANGED
|
@@ -1,40 +1,99 @@
|
|
|
1
|
-
|
|
1
|
+
export async function readRequestBody(req) {
|
|
2
|
+
return new Promise((resolve, reject) => {
|
|
3
|
+
const chunks = [];
|
|
4
|
+
req.on('data', (chunk) => chunks.push(chunk));
|
|
5
|
+
req.on('end', () => resolve(Buffer.concat(chunks)));
|
|
6
|
+
req.on('error', reject);
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
export function applyCors(res, allowOrigin) {
|
|
10
|
+
if (!allowOrigin) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
res.setHeader('Access-Control-Allow-Origin', allowOrigin);
|
|
14
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE,OPTIONS');
|
|
15
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Scope-ID');
|
|
16
|
+
}
|
|
17
|
+
export function sendJson(res, status, body, options = {}) {
|
|
18
|
+
applyCors(res, options.allowOrigin);
|
|
19
|
+
res.statusCode = status;
|
|
20
|
+
res.setHeader('Content-Type', 'application/json');
|
|
21
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
22
|
+
res.end(JSON.stringify(body));
|
|
23
|
+
}
|
|
24
|
+
function normalizeApiBaseUrl(value) {
|
|
25
|
+
return value.trim().replace(/\/$/, '');
|
|
26
|
+
}
|
|
27
|
+
function addDevSecretHeader(req, headers, devSecret) {
|
|
28
|
+
const url = req.url ?? '/';
|
|
29
|
+
if (url.startsWith('/rest/') && !url.startsWith('/rest/auth/local-dev-secret')) {
|
|
30
|
+
headers.set('X-Dev-Secret', devSecret);
|
|
31
|
+
}
|
|
32
|
+
return headers;
|
|
33
|
+
}
|
|
2
34
|
export function createDevAuthProxyHandler(options) {
|
|
35
|
+
const apiBaseUrl = normalizeApiBaseUrl(options.apiBaseUrl);
|
|
36
|
+
const { devSecret, allowOrigin } = options;
|
|
3
37
|
return async (req, res) => {
|
|
4
|
-
const
|
|
38
|
+
const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
|
|
5
39
|
if (req.method === 'OPTIONS') {
|
|
6
|
-
applyCors(res,
|
|
40
|
+
applyCors(res, allowOrigin);
|
|
7
41
|
res.statusCode = 204;
|
|
8
42
|
res.end();
|
|
9
43
|
return;
|
|
10
44
|
}
|
|
11
|
-
if (
|
|
12
|
-
sendJson(res, 404, { error: 'not_found' }, { allowOrigin
|
|
45
|
+
if (!url.pathname.startsWith('/rest/')) {
|
|
46
|
+
sendJson(res, 404, { error: 'not_found' }, { allowOrigin });
|
|
13
47
|
return;
|
|
14
48
|
}
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
49
|
+
const upstreamPath = url.pathname.slice('/rest'.length) + url.search;
|
|
50
|
+
const upstreamUrl = `${apiBaseUrl}${upstreamPath}`;
|
|
51
|
+
const headers = new Headers();
|
|
52
|
+
const contentType = req.headers['content-type'];
|
|
53
|
+
if (contentType) {
|
|
54
|
+
headers.set('Content-Type', contentType);
|
|
55
|
+
}
|
|
56
|
+
addDevSecretHeader(req, headers, devSecret);
|
|
57
|
+
// Forward a few useful headers from the browser.
|
|
58
|
+
const forwardedHeaders = ['accept', 'accept-language', 'x-scope-id'];
|
|
59
|
+
for (const name of forwardedHeaders) {
|
|
60
|
+
const value = req.headers[name];
|
|
61
|
+
if (typeof value === 'string') {
|
|
62
|
+
headers.set(name, value);
|
|
63
|
+
}
|
|
64
|
+
else if (Array.isArray(value)) {
|
|
65
|
+
for (const v of value) {
|
|
66
|
+
headers.append(name, v);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
let body;
|
|
71
|
+
try {
|
|
72
|
+
body = await readRequestBody(req);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
sendJson(res, 400, { error: 'invalid_body' }, { allowOrigin });
|
|
18
76
|
return;
|
|
19
77
|
}
|
|
20
78
|
try {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
79
|
+
const upstreamRes = await fetch(upstreamUrl, {
|
|
80
|
+
method: req.method ?? 'GET',
|
|
81
|
+
headers,
|
|
82
|
+
body: body.length > 0 ? new Uint8Array(body) : undefined,
|
|
83
|
+
cache: 'no-store',
|
|
84
|
+
});
|
|
85
|
+
applyCors(res, allowOrigin);
|
|
86
|
+
res.statusCode = upstreamRes.status;
|
|
25
87
|
res.setHeader('Cache-Control', 'no-store');
|
|
26
|
-
const
|
|
27
|
-
if (
|
|
28
|
-
res.setHeader('Content-Type',
|
|
88
|
+
const upstreamContentType = upstreamRes.headers.get('content-type');
|
|
89
|
+
if (upstreamContentType) {
|
|
90
|
+
res.setHeader('Content-Type', upstreamContentType);
|
|
29
91
|
}
|
|
30
|
-
|
|
92
|
+
const responseBody = Buffer.from(await upstreamRes.arrayBuffer());
|
|
93
|
+
res.end(responseBody);
|
|
31
94
|
}
|
|
32
|
-
catch
|
|
33
|
-
|
|
34
|
-
sendJson(res, 400, { error: 'missing_dev_session' }, { allowOrigin: options.allowOrigin });
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
sendJson(res, 502, { error: 'host_transport_failed' }, { allowOrigin: options.allowOrigin });
|
|
95
|
+
catch {
|
|
96
|
+
sendJson(res, 502, { error: 'upstream_unavailable' }, { allowOrigin });
|
|
38
97
|
}
|
|
39
98
|
};
|
|
40
99
|
}
|
package/dist/dev-session-cli.js
CHANGED
|
File without changes
|
package/dist/dev.d.ts
CHANGED
|
@@ -1,33 +1,8 @@
|
|
|
1
|
-
export interface
|
|
2
|
-
|
|
3
|
-
|
|
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 {
|
|
1
|
+
export interface DevBridgeOptions {
|
|
2
|
+
/** Base URL prepended to SDK transport paths. Defaults to `/rest`. */
|
|
3
|
+
apiBaseUrl?: string;
|
|
22
4
|
debug?: boolean;
|
|
23
|
-
baseUrl?: string;
|
|
24
|
-
proxyPath?: string;
|
|
25
5
|
}
|
|
26
|
-
export declare function getInjectedDevAuthConfig(): InjectedDevAuthConfig | null;
|
|
27
6
|
export declare function isInsideIframe(): boolean;
|
|
28
7
|
export declare function isDevStandalone(): boolean;
|
|
29
|
-
export declare function
|
|
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;
|
|
8
|
+
export declare function installDevBridge(options?: DevBridgeOptions): () => void;
|
package/dist/dev.js
CHANGED
|
@@ -1,47 +1,6 @@
|
|
|
1
1
|
import { SDK_MSG } from './protocol.js';
|
|
2
2
|
function getBaseUrl(baseUrl) {
|
|
3
|
-
|
|
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
|
-
}
|
|
3
|
+
return baseUrl ?? '/rest';
|
|
45
4
|
}
|
|
46
5
|
export function isInsideIframe() {
|
|
47
6
|
const currentWindow = globalThis;
|
|
@@ -51,57 +10,16 @@ export function isDevStandalone() {
|
|
|
51
10
|
const metaEnv = import.meta.env;
|
|
52
11
|
return Boolean(metaEnv?.DEV) && !isInsideIframe();
|
|
53
12
|
}
|
|
54
|
-
|
|
55
|
-
|
|
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;
|
|
13
|
+
function shouldForwardScope(policy, scopeId) {
|
|
14
|
+
if (policy === 'required') {
|
|
15
|
+
return true;
|
|
89
16
|
}
|
|
90
|
-
|
|
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(() => { });
|
|
17
|
+
return policy === 'optional' && scopeId !== undefined;
|
|
99
18
|
}
|
|
100
|
-
export function installDevBridge(options) {
|
|
101
|
-
const
|
|
19
|
+
export function installDevBridge(options = {}) {
|
|
20
|
+
const apiBaseUrl = getBaseUrl(options.apiBaseUrl);
|
|
21
|
+
const debug = options.debug ?? false;
|
|
102
22
|
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
23
|
const log = (...args) => {
|
|
106
24
|
if (debug) {
|
|
107
25
|
console.log('[bardioc-dev-bridge]', ...args);
|
|
@@ -117,71 +35,48 @@ export function installDevBridge(options) {
|
|
|
117
35
|
window.postMessage({ type: SDK_MSG.INIT_ACK, id, payload }, origin);
|
|
118
36
|
}
|
|
119
37
|
async function handleTransport(payload) {
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
38
|
+
const headers = new Headers();
|
|
39
|
+
const body = payload.body == null
|
|
40
|
+
? undefined
|
|
41
|
+
: payload.contentType === 'application/json'
|
|
42
|
+
? JSON.stringify(payload.body)
|
|
43
|
+
: payload.body;
|
|
44
|
+
if (body && payload.contentType) {
|
|
45
|
+
headers.set('Content-Type', payload.contentType);
|
|
46
|
+
}
|
|
47
|
+
if (shouldForwardScope(payload.scopePolicy, payload.scopeId)) {
|
|
48
|
+
headers.set('X-Scope-ID', payload.scopeId ?? '');
|
|
123
49
|
}
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
: null;
|
|
128
|
-
console.info('[bardioc-dev-bridge][1/2] sending dev session to local proxy', {
|
|
129
|
-
proxyUrl,
|
|
130
|
-
appId,
|
|
131
|
-
path: payload.path,
|
|
50
|
+
const url = `${apiBaseUrl}${payload.path}`;
|
|
51
|
+
log('[transport]', payload.method ?? 'GET', url);
|
|
52
|
+
const response = await fetch(url, {
|
|
132
53
|
method: payload.method ?? 'GET',
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
}),
|
|
54
|
+
headers,
|
|
55
|
+
body,
|
|
56
|
+
credentials: 'include',
|
|
151
57
|
cache: 'no-store',
|
|
152
58
|
});
|
|
59
|
+
const contentType = response.headers.get('content-type') ?? '';
|
|
153
60
|
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
61
|
if (!response.ok) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
method: payload.method ?? 'GET',
|
|
168
|
-
status: response.status,
|
|
169
|
-
failureStage: response.headers.get('X-Bardioc-Dev-Auth-Stage'),
|
|
170
|
-
responsePayload: data,
|
|
171
|
-
});
|
|
62
|
+
const responsePayload = contentType.includes('application/json')
|
|
63
|
+
? JSON.parse(text)
|
|
64
|
+
: text;
|
|
172
65
|
const err = new Error(`Transport ${payload.path} failed: ${response.status}`);
|
|
173
|
-
Object.assign(err, { status: response.status, responsePayload
|
|
66
|
+
Object.assign(err, { status: response.status, responsePayload });
|
|
174
67
|
throw err;
|
|
175
68
|
}
|
|
176
|
-
|
|
69
|
+
if (!text) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
return contentType.includes('application/json') ? JSON.parse(text) : text;
|
|
177
73
|
}
|
|
178
74
|
async function handleKernelProxy(payload) {
|
|
179
75
|
if (payload.kernelType === 'PING') {
|
|
180
76
|
return { pong: true };
|
|
181
77
|
}
|
|
182
78
|
if (payload.kernelType === 'GET_SESSION_STATE') {
|
|
183
|
-
|
|
184
|
-
return { authenticated: !!session?.sessionKey };
|
|
79
|
+
return { authenticated: true };
|
|
185
80
|
}
|
|
186
81
|
throw new Error(`Kernel message ${payload.kernelType} not supported in dev bridge`);
|
|
187
82
|
}
|
|
@@ -200,7 +95,7 @@ export function installDevBridge(options) {
|
|
|
200
95
|
try {
|
|
201
96
|
switch (data.type) {
|
|
202
97
|
case SDK_MSG.INIT:
|
|
203
|
-
ack(data.id, { appId, instanceId: null });
|
|
98
|
+
ack(data.id, { appId: null, instanceId: null });
|
|
204
99
|
return;
|
|
205
100
|
case SDK_MSG.NOTIFY:
|
|
206
101
|
return;
|
package/dist/host-bridge.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { getInjectedDevAuthConfig } from './dev.js';
|
|
2
1
|
import { SDK_MSG } from './protocol.js';
|
|
3
2
|
import { GraphTransport } from './transports/graph-transport.js';
|
|
4
3
|
import { OsTransport } from './transports/os-transport.js';
|
|
@@ -36,15 +35,12 @@ export function createHostBridge(config) {
|
|
|
36
35
|
console.log('[app-sdk]', ...args);
|
|
37
36
|
}
|
|
38
37
|
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
38
|
let _connected = false;
|
|
44
39
|
let _context = null;
|
|
45
40
|
let _destroyed = false;
|
|
46
41
|
const commandHandlers = new Map();
|
|
47
42
|
const osConfigUpdateCallbacks = new Set();
|
|
43
|
+
const eventHandlers = new Map();
|
|
48
44
|
const pending = new Map();
|
|
49
45
|
function isAllowedMessageOrigin(origin) {
|
|
50
46
|
return origin === targetOrigin || origin === hostWindow.location.origin;
|
|
@@ -80,6 +76,19 @@ export function createHostBridge(config) {
|
|
|
80
76
|
}
|
|
81
77
|
}
|
|
82
78
|
}
|
|
79
|
+
function handleGraphEvent(data) {
|
|
80
|
+
const { subscriptionId, event } = data.payload;
|
|
81
|
+
const handler = eventHandlers.get(subscriptionId);
|
|
82
|
+
if (!handler) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
handler(event);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
console.error('[app-sdk] graph event handler error:', error);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
83
92
|
function handlePendingResponse(data) {
|
|
84
93
|
const request = pending.get(data.id);
|
|
85
94
|
if (!request) {
|
|
@@ -110,6 +119,9 @@ export function createHostBridge(config) {
|
|
|
110
119
|
case SDK_MSG.OS_CONFIG_UPDATE:
|
|
111
120
|
handleOsConfigUpdate(data);
|
|
112
121
|
return;
|
|
122
|
+
case SDK_MSG.GRAPH_EVENT:
|
|
123
|
+
handleGraphEvent(data);
|
|
124
|
+
return;
|
|
113
125
|
case SDK_MSG.INIT_ACK:
|
|
114
126
|
case SDK_MSG.RESPONSE:
|
|
115
127
|
handlePendingResponse(data);
|
|
@@ -178,6 +190,27 @@ export function createHostBridge(config) {
|
|
|
178
190
|
}),
|
|
179
191
|
};
|
|
180
192
|
}
|
|
193
|
+
const events = {
|
|
194
|
+
subscribe({ types }, handler) {
|
|
195
|
+
const subscriptionId = nextId();
|
|
196
|
+
eventHandlers.set(subscriptionId, handler);
|
|
197
|
+
// Fire-and-forget the ack: the app-generated id removes any race, so a
|
|
198
|
+
// failed ack only means no events will arrive — surface it and clean up.
|
|
199
|
+
request(SDK_MSG.EVENT_SUBSCRIBE, { subscriptionId, types }).catch((error) => {
|
|
200
|
+
eventHandlers.delete(subscriptionId);
|
|
201
|
+
console.error('[app-sdk] events.subscribe failed:', error);
|
|
202
|
+
});
|
|
203
|
+
return () => {
|
|
204
|
+
if (!eventHandlers.delete(subscriptionId)) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (_destroyed) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
send(SDK_MSG.EVENT_UNSUBSCRIBE, { subscriptionId });
|
|
211
|
+
};
|
|
212
|
+
},
|
|
213
|
+
};
|
|
181
214
|
const bridge = {
|
|
182
215
|
get connected() {
|
|
183
216
|
return _connected;
|
|
@@ -199,6 +232,7 @@ export function createHostBridge(config) {
|
|
|
199
232
|
storage,
|
|
200
233
|
transport,
|
|
201
234
|
idb,
|
|
235
|
+
events,
|
|
202
236
|
setCommands(commands) {
|
|
203
237
|
const payload = { commands };
|
|
204
238
|
return request(SDK_MSG.SET_COMMANDS, payload);
|
|
@@ -246,6 +280,16 @@ export function createHostBridge(config) {
|
|
|
246
280
|
if (_destroyed)
|
|
247
281
|
return;
|
|
248
282
|
log('bridge.destroy(), pending requests:', pending.size);
|
|
283
|
+
// Best-effort unsubscribe every open stream before flipping the flag.
|
|
284
|
+
for (const subscriptionId of eventHandlers.keys()) {
|
|
285
|
+
try {
|
|
286
|
+
send(SDK_MSG.EVENT_UNSUBSCRIBE, { subscriptionId });
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
// ignore — host will drop the frame on window close anyway
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
eventHandlers.clear();
|
|
249
293
|
_destroyed = true;
|
|
250
294
|
hostWindow.removeEventListener('message', onMessage);
|
|
251
295
|
for (const [, p] of pending)
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export { createHostBridge } from './host-bridge.js';
|
|
2
2
|
export { DEFAULT_WINDOW_SIZE, MANIFEST_FILENAME, WINDOW_SIZES, validateManifest, } from './manifest.js';
|
|
3
3
|
export type { AppManifest, AppManifestPermission, AppManifestWindowConfig, CustomWindowSize, WindowSizePreset, } from './manifest.js';
|
|
4
|
-
export { useOsConfig } from './react.js';
|
|
4
|
+
export { useGraphEvents, useOsConfig } from './react.js';
|
|
5
5
|
export { SDK_CONTRACT_MATRIX, getRequiredPermissionForSdkMessage } from './contract-matrix.js';
|
|
6
6
|
export { ALLOWED_KERNEL_MESSAGES, SDK_MSG } from './protocol.js';
|
|
7
7
|
export type { SdkMessageType } from './protocol.js';
|
|
8
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';
|
|
9
|
+
export type { Application, BatchResult, 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, TimeseriesOptions, TimeseriesQueryOptions, TimeseriesValue, TransportScopeOptions, UpdateMemberInput, UpdateUnitInput, UserProfile, } from './types.js';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { createHostBridge } from './host-bridge.js';
|
|
2
2
|
export { DEFAULT_WINDOW_SIZE, MANIFEST_FILENAME, WINDOW_SIZES, validateManifest, } from './manifest.js';
|
|
3
|
-
export { useOsConfig } from './react.js';
|
|
3
|
+
export { useGraphEvents, useOsConfig } from './react.js';
|
|
4
4
|
export { SDK_CONTRACT_MATRIX, getRequiredPermissionForSdkMessage } from './contract-matrix.js';
|
|
5
5
|
export { ALLOWED_KERNEL_MESSAGES, SDK_MSG } from './protocol.js';
|
|
6
6
|
export { EntityNotFoundError, NetworkError, PermissionError, SdkError, TimeoutError, ValidationError, } from './errors.js';
|
package/dist/manifest.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
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';
|
|
2
|
+
export type AppManifestPermission = 'notify' | 'storage' | 'indexdb' | 'kernel-proxy' | 'transport' | 'events';
|
|
3
3
|
export interface CustomWindowSize {
|
|
4
4
|
width: number;
|
|
5
5
|
height: number;
|
package/dist/manifest.js
CHANGED
|
@@ -17,6 +17,7 @@ const VALID_PERMISSIONS = new Set([
|
|
|
17
17
|
// Deprecated: legacy/internal only. Do not request for normal hosted apps.
|
|
18
18
|
'kernel-proxy',
|
|
19
19
|
'transport',
|
|
20
|
+
'events',
|
|
20
21
|
]);
|
|
21
22
|
function validateBooleanField(value, fieldPath, errors) {
|
|
22
23
|
if (value !== undefined && typeof value !== 'boolean') {
|
package/dist/protocol.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { SdkCommandDefinition, SdkCommandInvokePayload, SdkIdbDeletePayload, SdkIdbGetPayload, SdkIdbPutPayload, SdkIdbQueryPayload, SdkMenuItem, SdkMenuRoot, SdkMessage, SdkNotifyPayload, SdkResponse, SdkSetActiveTabPayload, SdkSetCommandsPayload, SdkSetMenuPayload, SdkShortcutEventPayload, SdkStorageGetPayload, SdkStorageSetPayload, SdkTransportPayload, } from './types.js';
|
|
1
|
+
export type { EventSubscribeRequest, EventSubscribeResponse, EventUnsubscribeRequest, GraphEvent, GraphEventPushPayload, GraphEventType, SdkCommandDefinition, SdkCommandInvokePayload, SdkIdbDeletePayload, SdkIdbGetPayload, SdkIdbPutPayload, SdkIdbQueryPayload, SdkMenuItem, SdkMenuRoot, SdkMessage, SdkNotifyPayload, SdkResponse, SdkSetActiveTabPayload, SdkSetCommandsPayload, SdkSetMenuPayload, SdkShortcutEventPayload, SdkStorageGetPayload, SdkStorageSetPayload, SdkTransportPayload, } from './types.js';
|
|
2
2
|
export declare const ALLOWED_KERNEL_MESSAGES: ReadonlySet<string>;
|
|
3
3
|
export declare const SDK_MSG: {
|
|
4
4
|
readonly INIT: "SDK_INIT";
|
|
@@ -22,5 +22,8 @@ export declare const SDK_MSG: {
|
|
|
22
22
|
readonly IDB_QUERY: "SDK_IDB_QUERY";
|
|
23
23
|
readonly LAUNCH_APP: "SDK_LAUNCH_APP";
|
|
24
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";
|
|
25
28
|
};
|
|
26
29
|
export type SdkMessageType = (typeof SDK_MSG)[keyof typeof SDK_MSG];
|
package/dist/protocol.js
CHANGED
|
@@ -25,4 +25,7 @@ export const SDK_MSG = {
|
|
|
25
25
|
IDB_QUERY: 'SDK_IDB_QUERY',
|
|
26
26
|
LAUNCH_APP: 'SDK_LAUNCH_APP',
|
|
27
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',
|
|
28
31
|
};
|
package/dist/react.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import type { HostBridge, HostBridgeConfig, NotifyLevel, OsConfig, SdkCommandDefinition, SdkCommandHandler, SdkMenuRoot } from './types.js';
|
|
2
|
+
import type { GraphEvent, HostBridge, HostBridgeConfig, NotifyLevel, OsConfig, SdkCommandDefinition, SdkCommandHandler, SdkMenuRoot } from './types.js';
|
|
4
3
|
export interface AppSdkProviderProps extends HostBridgeConfig {
|
|
5
4
|
children: ReactNode;
|
|
6
5
|
}
|
|
@@ -22,5 +21,11 @@ export declare function useActiveTab(activeTab: string | null): void;
|
|
|
22
21
|
* Automatically subscribes to updates and returns the latest values.
|
|
23
22
|
*/
|
|
24
23
|
export declare function useOsConfig(): OsConfig | null;
|
|
25
|
-
/**
|
|
26
|
-
|
|
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: {
|
|
30
|
+
types: string[];
|
|
31
|
+
}, handler: (event: GraphEvent) => void, deps?: React.DependencyList): void;
|
package/dist/react.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { createContext, useCallback, useContext, useEffect, useRef, useState, } from 'react';
|
|
3
|
-
import { clearDevAuthSession } from './dev.js';
|
|
4
3
|
import { createHostBridge } from './host-bridge.js';
|
|
5
4
|
const SdkContext = createContext(null);
|
|
6
5
|
const SYSTEM_SIZE_CLASS_NAMES = ['text-sm', 'text-base', 'text-lg', 'text-xl'];
|
|
@@ -199,10 +198,19 @@ export function useOsConfig() {
|
|
|
199
198
|
}, [bridge]);
|
|
200
199
|
return osConfig;
|
|
201
200
|
}
|
|
202
|
-
/**
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
201
|
+
/**
|
|
202
|
+
* Subscribes to live graph events for the given OGIT types while mounted.
|
|
203
|
+
* The handler is kept in a ref so its identity may change without resubscribing;
|
|
204
|
+
* the subscription is recreated only when `types` (or extra `deps`) change.
|
|
205
|
+
*/
|
|
206
|
+
export function useGraphEvents(options, handler, deps = []) {
|
|
207
|
+
const bridge = useSdk();
|
|
208
|
+
const handlerRef = useRef(handler);
|
|
209
|
+
handlerRef.current = handler;
|
|
210
|
+
const typesKey = JSON.stringify(options.types);
|
|
211
|
+
useEffect(() => {
|
|
212
|
+
const unsubscribe = bridge.events.subscribe({ types: JSON.parse(typesKey) }, event => handlerRef.current(event));
|
|
213
|
+
return unsubscribe;
|
|
214
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
215
|
+
}, [bridge, typesKey, ...deps]);
|
|
208
216
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -84,6 +84,42 @@ export interface SdkShortcutEventPayload {
|
|
|
84
84
|
altKey: boolean;
|
|
85
85
|
shiftKey: boolean;
|
|
86
86
|
}
|
|
87
|
+
/** Mirrors the BFF `GraphEvent` SSE contract delivered by the host. */
|
|
88
|
+
export type GraphEventType = 'CREATE' | 'REPLACE' | 'UPDATE' | 'CONNECT' | 'DISCONNECT' | 'DELETE' | 'WRITE_TIMESERIES';
|
|
89
|
+
export interface GraphEvent {
|
|
90
|
+
id: string;
|
|
91
|
+
type: GraphEventType;
|
|
92
|
+
timestamp: number;
|
|
93
|
+
nanotime?: number;
|
|
94
|
+
body?: Record<string, unknown>;
|
|
95
|
+
metadata?: Record<string, unknown>;
|
|
96
|
+
}
|
|
97
|
+
export interface EventSubscribeRequest {
|
|
98
|
+
/** App-generated subscription id, echoed by the host and stamped on pushes. */
|
|
99
|
+
subscriptionId: string;
|
|
100
|
+
/** OGIT types to subscribe to, e.g. `['ogit/Documents/Document']`. */
|
|
101
|
+
types: string[];
|
|
102
|
+
}
|
|
103
|
+
export interface EventSubscribeResponse {
|
|
104
|
+
subscriptionId: string;
|
|
105
|
+
}
|
|
106
|
+
export interface EventUnsubscribeRequest {
|
|
107
|
+
subscriptionId: string;
|
|
108
|
+
}
|
|
109
|
+
export interface GraphEventPushPayload {
|
|
110
|
+
subscriptionId: string;
|
|
111
|
+
event: GraphEvent;
|
|
112
|
+
}
|
|
113
|
+
/** Live graph-event subscription surface exposed to hosted apps. */
|
|
114
|
+
export interface EventsApi {
|
|
115
|
+
/**
|
|
116
|
+
* Subscribe to graph mutations for the given OGIT types. Returns an
|
|
117
|
+
* unsubscribe function; the handler fires for each matching event.
|
|
118
|
+
*/
|
|
119
|
+
subscribe(options: {
|
|
120
|
+
types: string[];
|
|
121
|
+
}, handler: (event: GraphEvent) => void): () => void;
|
|
122
|
+
}
|
|
87
123
|
export type NotifyLevel = 'success' | 'error' | 'info' | 'warning';
|
|
88
124
|
export interface SdkNotifyPayload {
|
|
89
125
|
message: string;
|
|
@@ -325,6 +361,7 @@ export interface HostBridge {
|
|
|
325
361
|
storage: SdkStorage;
|
|
326
362
|
idb(storeName: string): SdkIdbStore;
|
|
327
363
|
transport: SdkTransport;
|
|
364
|
+
events: EventsApi;
|
|
328
365
|
setCommands(commands: SdkCommandDefinition[]): Promise<void>;
|
|
329
366
|
setMenu(menu: SdkMenuRoot[] | null): Promise<void>;
|
|
330
367
|
setActiveTab(activeTab: string | null): Promise<void>;
|
package/dist/vite.js
CHANGED
|
@@ -3,7 +3,6 @@ import { execSync, spawn } from 'node:child_process';
|
|
|
3
3
|
import { existsSync, readFileSync } from 'node:fs';
|
|
4
4
|
import { resolve, sep } from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import { readDotEnvValue } from './dot-env.js';
|
|
7
6
|
import { MANIFEST_FILENAME, validateManifest } from './manifest.js';
|
|
8
7
|
export { bardiocDevAuth } from './dev-auth-vite.js';
|
|
9
8
|
function hasCommand(cmd) {
|
|
@@ -91,8 +90,6 @@ function devProxy(opts) {
|
|
|
91
90
|
}
|
|
92
91
|
server.httpServer?.on('listening', async () => {
|
|
93
92
|
const manifestPath = resolve(process.cwd(), 'public', MANIFEST_FILENAME);
|
|
94
|
-
const envPath = resolve(process.cwd(), '.env');
|
|
95
|
-
const envAppId = existsSync(envPath) ? readDotEnvValue(envPath, 'VITE_APP_ID') : null;
|
|
96
93
|
if (!existsSync(manifestPath)) {
|
|
97
94
|
console.warn(`[bardioc] public/${MANIFEST_FILENAME} not found`);
|
|
98
95
|
return;
|
|
@@ -114,14 +111,12 @@ function devProxy(opts) {
|
|
|
114
111
|
}
|
|
115
112
|
console.log(`[bardioc] Use this live URL in the host App Store app configuration:`);
|
|
116
113
|
console.log(`[bardioc] App Name: ${manifestId}`);
|
|
117
|
-
console.log(`[bardioc] App ID: ${envAppId || '<from .env VITE_APP_ID>'}`);
|
|
118
114
|
console.log(`[bardioc] Live URL: https://${tunnelHost}`);
|
|
119
115
|
}
|
|
120
116
|
else {
|
|
121
117
|
console.log(`[bardioc] Dev server ready: http://localhost:${port}`);
|
|
122
|
-
console.log(`[bardioc] For standalone
|
|
118
|
+
console.log(`[bardioc] For standalone dev auth, set DEV_SECRET and API_BASE_URL in .env.`);
|
|
123
119
|
console.log(`[bardioc] App Name: ${manifestId}`);
|
|
124
|
-
console.log(`[bardioc] App ID: ${envAppId || '<from .env VITE_APP_ID>'}`);
|
|
125
120
|
console.log(`[bardioc] URL: http://localhost:${port}`);
|
|
126
121
|
}
|
|
127
122
|
});
|
package/package.json
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bardioc/app-sdk",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "SDK for building apps for the Bardioc OS",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
7
7
|
"type": "module",
|
|
8
|
-
"bin": {
|
|
9
|
-
"bardioc-dev-session": "./dist/dev-session-cli.js"
|
|
10
|
-
},
|
|
11
8
|
"publishConfig": {
|
|
12
9
|
"registry": "https://registry.npmjs.org/",
|
|
13
10
|
"access": "public"
|
|
@@ -44,10 +41,6 @@
|
|
|
44
41
|
"types": "./dist/dev-proxy.d.ts",
|
|
45
42
|
"default": "./dist/dev-proxy.js"
|
|
46
43
|
},
|
|
47
|
-
"./dev-session-cli": {
|
|
48
|
-
"types": "./dist/dev-session-cli.d.ts",
|
|
49
|
-
"default": "./dist/dev-session-cli.js"
|
|
50
|
-
},
|
|
51
44
|
"./types": {
|
|
52
45
|
"types": "./dist/types.d.ts"
|
|
53
46
|
}
|