@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.
Files changed (41) hide show
  1. package/LICENSE +5 -0
  2. package/README.md +368 -0
  3. package/assets/fonts/README.md +11 -0
  4. package/assets/fonts/bardioc-fonts.css +55 -0
  5. package/assets/fonts/v1/geist-mono-latin-wght-normal.woff2 +0 -0
  6. package/assets/fonts/v1/nunito-sans-latin-wght-italic.woff2 +0 -0
  7. package/assets/fonts/v1/nunito-sans-latin-wght-normal.woff2 +0 -0
  8. package/dist/contract-matrix.d.ts +130 -0
  9. package/dist/contract-matrix.js +132 -0
  10. package/dist/dev-auth-proxy-core.d.ts +24 -0
  11. package/dist/dev-auth-proxy-core.js +59 -0
  12. package/dist/dev-auth-vite.d.ts +34 -0
  13. package/dist/dev-auth-vite.js +221 -0
  14. package/dist/dev-proxy.d.ts +8 -0
  15. package/dist/dev-proxy.js +40 -0
  16. package/dist/dev-session-cli.d.ts +34 -0
  17. package/dist/dev-session-cli.js +125 -0
  18. package/dist/dev.d.ts +33 -0
  19. package/dist/dev.js +223 -0
  20. package/dist/dot-env.d.ts +2 -0
  21. package/dist/dot-env.js +22 -0
  22. package/dist/errors.d.ts +27 -0
  23. package/dist/errors.js +57 -0
  24. package/dist/host-bridge.d.ts +3 -0
  25. package/dist/host-bridge.js +260 -0
  26. package/dist/index.d.ts +9 -0
  27. package/dist/index.js +6 -0
  28. package/dist/manifest.d.ts +78 -0
  29. package/dist/manifest.js +169 -0
  30. package/dist/protocol.d.ts +26 -0
  31. package/dist/protocol.js +28 -0
  32. package/dist/react.d.ts +26 -0
  33. package/dist/react.js +208 -0
  34. package/dist/transports/graph-transport.d.ts +224 -0
  35. package/dist/transports/graph-transport.js +584 -0
  36. package/dist/transports/os-transport.d.ts +189 -0
  37. package/dist/transports/os-transport.js +444 -0
  38. package/dist/types.d.ts +343 -0
  39. package/dist/vite.d.ts +9 -0
  40. package/dist/vite.js +262 -0
  41. package/package.json +101 -0
@@ -0,0 +1,132 @@
1
+ import { SDK_MSG } from './protocol.js';
2
+ export const SDK_CONTRACT_MATRIX = {
3
+ [SDK_MSG.INIT]: {
4
+ requestPayload: '{ appId?: string }',
5
+ responsePayload: 'SdkInitResponse',
6
+ permission: null,
7
+ expectedHostBehavior: 'Handshake the iframe app and return SDK-owned host context.',
8
+ },
9
+ [SDK_MSG.NOTIFY]: {
10
+ requestPayload: 'SdkNotifyPayload',
11
+ responsePayload: '{ ok: true }',
12
+ permission: 'notify',
13
+ expectedHostBehavior: 'Surface a host-level notification and acknowledge delivery.',
14
+ },
15
+ [SDK_MSG.KERNEL_PROXY]: {
16
+ requestPayload: 'SdkKernelProxyPayload',
17
+ responsePayload: 'KernelProtocol response',
18
+ permission: 'kernel-proxy',
19
+ expectedHostBehavior: 'Forward only allow-listed kernel messages through the host.',
20
+ },
21
+ [SDK_MSG.TRANSPORT]: {
22
+ requestPayload: 'SdkTransportPayload',
23
+ responsePayload: 'Upstream transport response payload',
24
+ permission: 'transport',
25
+ expectedHostBehavior: 'Relay app-scoped transport requests with scope-aware host mediation.',
26
+ },
27
+ [SDK_MSG.SET_COMMANDS]: {
28
+ requestPayload: 'SdkSetCommandsPayload',
29
+ responsePayload: '{ ok: true }',
30
+ permission: null,
31
+ expectedHostBehavior: 'Store the app command registry for host command invocation.',
32
+ },
33
+ [SDK_MSG.SET_MENU]: {
34
+ requestPayload: 'SdkSetMenuPayload',
35
+ responsePayload: '{ ok: true }',
36
+ permission: null,
37
+ expectedHostBehavior: 'Store the app menu tree without degrading supported SDK menu kinds.',
38
+ },
39
+ [SDK_MSG.SET_ACTIVE_TAB]: {
40
+ requestPayload: 'SdkSetActiveTabPayload',
41
+ responsePayload: '{ ok: true }',
42
+ permission: null,
43
+ expectedHostBehavior: 'Persist the app-reported active tab label in host window state.',
44
+ },
45
+ [SDK_MSG.SHORTCUT_EVENT]: {
46
+ requestPayload: 'SdkShortcutEventPayload',
47
+ responsePayload: null,
48
+ permission: null,
49
+ expectedHostBehavior: 'Route a focused iframe shortcut event through host shortcut handling.',
50
+ },
51
+ [SDK_MSG.STORAGE_GET]: {
52
+ requestPayload: 'SdkStorageGetPayload',
53
+ responsePayload: 'unknown',
54
+ permission: 'storage',
55
+ expectedHostBehavior: 'Host-owned storage bridge; currently deferred in bdf.',
56
+ },
57
+ [SDK_MSG.STORAGE_SET]: {
58
+ requestPayload: 'SdkStorageSetPayload',
59
+ responsePayload: 'void',
60
+ permission: 'storage',
61
+ expectedHostBehavior: 'Host-owned storage bridge; currently deferred in bdf.',
62
+ },
63
+ [SDK_MSG.STORAGE_DELETE]: {
64
+ requestPayload: 'SdkStorageDeletePayload',
65
+ responsePayload: 'void',
66
+ permission: 'storage',
67
+ expectedHostBehavior: 'Host-owned storage bridge; currently deferred in bdf.',
68
+ },
69
+ [SDK_MSG.STORAGE_KEYS]: {
70
+ requestPayload: '{}',
71
+ responsePayload: 'string[]',
72
+ permission: 'storage',
73
+ expectedHostBehavior: 'Host-owned storage bridge; currently deferred in bdf.',
74
+ },
75
+ [SDK_MSG.IDB_GET]: {
76
+ requestPayload: 'SdkIdbGetPayload',
77
+ responsePayload: 'SdkIdbEntry | null',
78
+ permission: 'indexdb',
79
+ expectedHostBehavior: 'Perform app-scoped IndexedDB reads in host-owned storage.',
80
+ },
81
+ [SDK_MSG.IDB_PUT]: {
82
+ requestPayload: 'SdkIdbPutPayload',
83
+ responsePayload: 'void',
84
+ permission: 'indexdb',
85
+ expectedHostBehavior: 'Perform app-scoped IndexedDB writes in host-owned storage.',
86
+ },
87
+ [SDK_MSG.IDB_DELETE]: {
88
+ requestPayload: 'SdkIdbDeletePayload',
89
+ responsePayload: 'void',
90
+ permission: 'indexdb',
91
+ expectedHostBehavior: 'Perform app-scoped IndexedDB deletes in host-owned storage.',
92
+ },
93
+ [SDK_MSG.IDB_QUERY]: {
94
+ requestPayload: 'SdkIdbQueryPayload',
95
+ responsePayload: 'SdkIdbEntry[]',
96
+ permission: 'indexdb',
97
+ expectedHostBehavior: 'Perform app-scoped IndexedDB queries in host-owned storage.',
98
+ },
99
+ [SDK_MSG.LAUNCH_APP]: {
100
+ requestPayload: '{ appId: string }',
101
+ responsePayload: '{ windowId: string | null }',
102
+ permission: null,
103
+ expectedHostBehavior: 'Launch another registered app through the host shell.',
104
+ },
105
+ [SDK_MSG.COMMAND_INVOKE]: {
106
+ requestPayload: 'SdkCommandInvokePayload',
107
+ responsePayload: '{ ok: true }',
108
+ permission: null,
109
+ expectedHostBehavior: 'Host-to-app command callback delivery.',
110
+ },
111
+ [SDK_MSG.OS_CONFIG_UPDATE]: {
112
+ requestPayload: 'OsConfig',
113
+ responsePayload: null,
114
+ permission: null,
115
+ expectedHostBehavior: 'Host-to-app runtime OS configuration update delivery.',
116
+ },
117
+ [SDK_MSG.RESPONSE]: {
118
+ requestPayload: 'SdkResponse',
119
+ responsePayload: null,
120
+ permission: null,
121
+ expectedHostBehavior: 'Request/response envelope used by the bridge runtime.',
122
+ },
123
+ [SDK_MSG.INIT_ACK]: {
124
+ requestPayload: 'SdkInitResponse',
125
+ responsePayload: null,
126
+ permission: null,
127
+ expectedHostBehavior: 'Handshake acknowledgement used by the bridge runtime.',
128
+ },
129
+ };
130
+ export function getRequiredPermissionForSdkMessage(messageType) {
131
+ return SDK_CONTRACT_MATRIX[messageType]?.permission ?? null;
132
+ }
@@ -0,0 +1,24 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ export type ProxyPayload = {
3
+ sessionKey?: string;
4
+ hostUrl?: string;
5
+ scopeId?: string | null;
6
+ scopePolicy?: 'none' | 'optional' | 'required';
7
+ path: string;
8
+ method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
9
+ body?: unknown;
10
+ contentType?: string;
11
+ };
12
+ interface ProxyTransportOptions {
13
+ appId: string;
14
+ hostUrl: string;
15
+ devSessionKey: string;
16
+ }
17
+ interface SendJsonOptions {
18
+ allowOrigin?: string;
19
+ }
20
+ export declare function readJsonBody<T = unknown>(req: IncomingMessage): Promise<T | null>;
21
+ export declare function applyCors(res: ServerResponse, allowOrigin?: string): void;
22
+ export declare function sendJson(res: ServerResponse, status: number, body: unknown, options?: SendJsonOptions): void;
23
+ export declare function forwardDevAuthProxyRequest(payload: ProxyPayload, options: ProxyTransportOptions): Promise<Response>;
24
+ export {};
@@ -0,0 +1,59 @@
1
+ export async function readJsonBody(req) {
2
+ return new Promise(resolveBody => {
3
+ const chunks = [];
4
+ req.on('data', (chunk) => chunks.push(chunk));
5
+ req.on('end', () => {
6
+ const buffer = Buffer.concat(chunks);
7
+ if (buffer.length === 0) {
8
+ resolveBody(null);
9
+ return;
10
+ }
11
+ try {
12
+ resolveBody(JSON.parse(buffer.toString('utf8')));
13
+ }
14
+ catch {
15
+ resolveBody(null);
16
+ }
17
+ });
18
+ req.on('error', () => resolveBody(null));
19
+ });
20
+ }
21
+ export function applyCors(res, allowOrigin) {
22
+ if (!allowOrigin) {
23
+ return;
24
+ }
25
+ res.setHeader('Access-Control-Allow-Origin', allowOrigin);
26
+ res.setHeader('Access-Control-Allow-Methods', 'POST,OPTIONS');
27
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
28
+ }
29
+ export function sendJson(res, status, body, options = {}) {
30
+ applyCors(res, options.allowOrigin);
31
+ res.statusCode = status;
32
+ res.setHeader('Content-Type', 'application/json');
33
+ res.setHeader('Cache-Control', 'no-store');
34
+ res.end(JSON.stringify(body));
35
+ }
36
+ export async function forwardDevAuthProxyRequest(payload, options) {
37
+ const hostUrl = payload.hostUrl ?? options.hostUrl;
38
+ const sessionKey = payload.sessionKey ?? options.devSessionKey;
39
+ if (!hostUrl || !sessionKey) {
40
+ throw new Error('missing_dev_session');
41
+ }
42
+ return fetch(`${hostUrl.replace(/\/$/, '')}/api/transport`, {
43
+ method: 'POST',
44
+ headers: {
45
+ 'Content-Type': 'application/json',
46
+ 'X-Dev-Session-Key': sessionKey,
47
+ },
48
+ body: JSON.stringify({
49
+ appId: options.appId,
50
+ path: payload.path,
51
+ method: payload.method ?? 'GET',
52
+ body: payload.body,
53
+ contentType: payload.contentType,
54
+ scopePolicy: payload.scopePolicy,
55
+ scopeId: payload.scopeId ?? null,
56
+ }),
57
+ cache: 'no-store',
58
+ });
59
+ }
@@ -0,0 +1,34 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ type VitePlugin = {
3
+ name: string;
4
+ apply?: 'serve' | 'build';
5
+ enforce?: 'pre' | 'post';
6
+ 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
+ };
14
+ interface ViteDevServerLike {
15
+ config: {
16
+ root: string;
17
+ envDir?: string;
18
+ mode?: string;
19
+ base: string;
20
+ logger: {
21
+ warn: (message: string) => void;
22
+ info: (message: string) => void;
23
+ };
24
+ };
25
+ middlewares: {
26
+ use: (handler: (req: IncomingMessage, res: ServerResponse, next: (error?: unknown) => void) => void) => void;
27
+ };
28
+ }
29
+ export interface BardiocDevAuthOptions {
30
+ appName: string;
31
+ sessionStorageKey?: string;
32
+ }
33
+ export declare function bardiocDevAuth(options: BardiocDevAuthOptions): VitePlugin;
34
+ export {};
@@ -0,0 +1,221 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { forwardDevAuthProxyRequest, readJsonBody, sendJson, } from './dev-auth-proxy-core.js';
4
+ import { readDotEnvFile } from './dot-env.js';
5
+ function resolveSessionStorageKey(options) {
6
+ return options.sessionStorageKey ?? `bardioc.dev.auth.${options.appName}`;
7
+ }
8
+ function readRuntimeEnv(server) {
9
+ const envDir = server.config.envDir ?? server.config.root;
10
+ const mode = server.config.mode?.trim();
11
+ const envFileNames = [
12
+ '.env',
13
+ '.env.local',
14
+ mode ? `.env.${mode}` : null,
15
+ mode ? `.env.${mode}.local` : null,
16
+ ];
17
+ const env = {};
18
+ for (const fileName of envFileNames) {
19
+ if (!fileName) {
20
+ continue;
21
+ }
22
+ const filePath = resolve(envDir, fileName);
23
+ if (!existsSync(filePath)) {
24
+ continue;
25
+ }
26
+ Object.assign(env, readDotEnvFile(filePath));
27
+ }
28
+ for (const [key, value] of Object.entries(process.env)) {
29
+ if (typeof value === 'string') {
30
+ env[key] = value;
31
+ }
32
+ }
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
+ return {
44
+ exchangeAppId,
45
+ hostUrl,
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);
153
+ };
154
+ }
155
+ export function bardiocDevAuth(options) {
156
+ let runtimeConfig = null;
157
+ return {
158
+ name: 'bardioc-dev-auth',
159
+ apply: 'serve',
160
+ enforce: 'pre',
161
+ configureServer(server) {
162
+ runtimeConfig = readDevAuthEnv(server, options);
163
+ if (!runtimeConfig) {
164
+ server.config.logger.warn(`[bardioc-dev-auth] DEV_AUTH_HOST_URL + DEV_SESSION_KEY, plus VITE_APP_ID, are required in env files — dev auth disabled for appName="${options.appName}".`);
165
+ return;
166
+ }
167
+ const env = {
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`);
174
+ const basePath = server.config.base.endsWith('/')
175
+ ? server.config.base.slice(0, -1)
176
+ : server.config.base;
177
+ server.middlewares.use(async (req, res, next) => {
178
+ const originalUrl = req.url ?? '';
179
+ const devAuthReq = req;
180
+ const parsed = new URL(originalUrl, 'http://localhost');
181
+ const pathname = parsed.pathname;
182
+ const routePath = pathname.startsWith(basePath)
183
+ ? pathname.slice(basePath.length) || '/'
184
+ : pathname;
185
+ const shouldHandle = routePath === '/login' || routePath === '/proxy';
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 {
201
+ next();
202
+ }
203
+ });
204
+ },
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
+ };
221
+ }
@@ -0,0 +1,8 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ export interface DevAuthProxyOptions {
3
+ appId: string;
4
+ hostUrl: string;
5
+ devSessionKey: string;
6
+ allowOrigin?: string;
7
+ }
8
+ export declare function createDevAuthProxyHandler(options: DevAuthProxyOptions): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
@@ -0,0 +1,40 @@
1
+ import { applyCors, forwardDevAuthProxyRequest, readJsonBody, sendJson, } from './dev-auth-proxy-core.js';
2
+ export function createDevAuthProxyHandler(options) {
3
+ return async (req, res) => {
4
+ const pathname = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`).pathname;
5
+ if (req.method === 'OPTIONS') {
6
+ applyCors(res, options.allowOrigin);
7
+ res.statusCode = 204;
8
+ res.end();
9
+ return;
10
+ }
11
+ if (req.method !== 'POST' || pathname !== '/proxy') {
12
+ sendJson(res, 404, { error: 'not_found' }, { allowOrigin: options.allowOrigin });
13
+ return;
14
+ }
15
+ const payload = await readJsonBody(req);
16
+ if (!payload?.path) {
17
+ sendJson(res, 400, { error: 'invalid_payload' }, { allowOrigin: options.allowOrigin });
18
+ return;
19
+ }
20
+ try {
21
+ const transportResponse = await forwardDevAuthProxyRequest(payload, options);
22
+ const responseText = await transportResponse.text();
23
+ applyCors(res, options.allowOrigin);
24
+ res.statusCode = transportResponse.status;
25
+ res.setHeader('Cache-Control', 'no-store');
26
+ const contentType = transportResponse.headers.get('content-type');
27
+ if (contentType) {
28
+ res.setHeader('Content-Type', contentType);
29
+ }
30
+ res.end(responseText);
31
+ }
32
+ catch (error) {
33
+ if (error instanceof Error && error.message === 'missing_dev_session') {
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 });
38
+ }
39
+ };
40
+ }
@@ -0,0 +1,34 @@
1
+ import { spawn } from 'node:child_process';
2
+ interface CliConfig {
3
+ hostUrl?: string;
4
+ cliAccessToken?: string;
5
+ cliAccessTokenExpiresAt?: number;
6
+ }
7
+ interface RuntimeEnv {
8
+ VITE_ENABLE_DEV_AUTH?: string;
9
+ VITE_APP_ID?: string;
10
+ DEV_AUTH_HOST_URL?: string;
11
+ }
12
+ interface DevSessionResponse {
13
+ sessionKey: string;
14
+ hostUrl: string;
15
+ appId: string;
16
+ }
17
+ interface CliMainRuntime {
18
+ argv?: string[];
19
+ cwd?: () => string;
20
+ env?: NodeJS.ProcessEnv;
21
+ platform?: NodeJS.Platform;
22
+ spawnCommand?: typeof spawn;
23
+ stdout?: Pick<NodeJS.WriteStream, 'write'>;
24
+ stderr?: Pick<NodeJS.WriteStream, 'write'>;
25
+ exit?: (code: number) => void;
26
+ readCliConfig?: () => CliConfig | null;
27
+ }
28
+ export declare function readLayeredEnv(cwd: string, runtimeEnv?: NodeJS.ProcessEnv): RuntimeEnv;
29
+ export declare function readCliConfigFromFile(configPath: string): CliConfig | null;
30
+ export declare function normalizeHostUrl(hostUrl: string): string;
31
+ export declare function assertCliConfig(config: CliConfig | null, hostUrl: string): asserts config is CliConfig;
32
+ export declare function fetchDevSession(hostUrl: string, cliAccessToken: string, appId: string): Promise<DevSessionResponse>;
33
+ export declare function main(runtime?: CliMainRuntime): Promise<void>;
34
+ export {};
@@ -0,0 +1,125 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { existsSync, readFileSync, realpathSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { resolve } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { readDotEnvFile } from './dot-env.js';
7
+ export function readLayeredEnv(cwd, runtimeEnv = process.env) {
8
+ const env = {};
9
+ for (const fileName of ['.env', '.env.local']) {
10
+ const filePath = resolve(cwd, fileName);
11
+ if (!existsSync(filePath)) {
12
+ continue;
13
+ }
14
+ Object.assign(env, readDotEnvFile(filePath));
15
+ }
16
+ for (const [key, value] of Object.entries(runtimeEnv)) {
17
+ if (typeof value === 'string') {
18
+ env[key] = value;
19
+ }
20
+ }
21
+ return env;
22
+ }
23
+ export function readCliConfigFromFile(configPath) {
24
+ if (!existsSync(configPath)) {
25
+ return null;
26
+ }
27
+ const parsed = JSON.parse(readFileSync(configPath, 'utf8'));
28
+ return parsed;
29
+ }
30
+ function readCliConfig() {
31
+ return readCliConfigFromFile(resolve(homedir(), '.bardioc', 'config.json'));
32
+ }
33
+ export function normalizeHostUrl(hostUrl) {
34
+ return hostUrl.trim().replace(/\/$/, '');
35
+ }
36
+ export function assertCliConfig(config, hostUrl) {
37
+ if (!config?.hostUrl || !config.cliAccessToken) {
38
+ throw new Error('Missing CLI login. Run `bardioc login` first.');
39
+ }
40
+ if (normalizeHostUrl(config.hostUrl) !== normalizeHostUrl(hostUrl)) {
41
+ throw new Error(`CLI login host ${normalizeHostUrl(config.hostUrl)} does not match DEV_AUTH_HOST_URL ${normalizeHostUrl(hostUrl)}.`);
42
+ }
43
+ if (typeof config.cliAccessTokenExpiresAt === 'number' &&
44
+ config.cliAccessTokenExpiresAt > 0 &&
45
+ config.cliAccessTokenExpiresAt * 1000 <= Date.now()) {
46
+ throw new Error('Stored CLI login is expired. Run `bardioc login` again.');
47
+ }
48
+ }
49
+ export async function fetchDevSession(hostUrl, cliAccessToken, appId) {
50
+ const response = await fetch(`${normalizeHostUrl(hostUrl)}/api/auth/dev-session`, {
51
+ method: 'POST',
52
+ headers: {
53
+ 'Content-Type': 'application/json',
54
+ Authorization: `Bearer ${cliAccessToken}`,
55
+ },
56
+ body: JSON.stringify({ appId }),
57
+ cache: 'no-store',
58
+ });
59
+ if (!response.ok) {
60
+ const data = (await response.json().catch(() => ({})));
61
+ throw new Error(data.error ?? `Dev session failed (${response.status})`);
62
+ }
63
+ return (await response.json());
64
+ }
65
+ export async function main(runtime = {}) {
66
+ const argv = runtime.argv ?? process.argv;
67
+ const currentEnv = runtime.env ?? process.env;
68
+ const currentCwd = runtime.cwd ?? (() => process.cwd());
69
+ const spawnCommand = runtime.spawnCommand ?? spawn;
70
+ const stdout = runtime.stdout ?? process.stdout;
71
+ const stderr = runtime.stderr ?? process.stderr;
72
+ const exit = runtime.exit ?? process.exit;
73
+ const separatorIndex = argv.indexOf('--');
74
+ const commandArgs = separatorIndex >= 0 ? argv.slice(separatorIndex + 1) : [];
75
+ if (commandArgs.length === 0) {
76
+ throw new Error('Missing command after `--`. Example: bardioc-dev-session -- vite');
77
+ }
78
+ const cwd = currentCwd();
79
+ const env = readLayeredEnv(cwd, currentEnv);
80
+ const devAuthEnabled = env.VITE_ENABLE_DEV_AUTH === 'true';
81
+ const childEnv = { ...currentEnv };
82
+ const [command, ...commandRestArgs] = commandArgs;
83
+ if (!command) {
84
+ throw new Error('Missing command after `--`. Example: bardioc-dev-session -- vite');
85
+ }
86
+ if (devAuthEnabled) {
87
+ const appId = env.VITE_APP_ID?.trim();
88
+ const hostUrl = env.DEV_AUTH_HOST_URL?.trim();
89
+ if (!appId) {
90
+ throw new Error('Missing VITE_APP_ID for standalone dev auth.');
91
+ }
92
+ if (!hostUrl) {
93
+ throw new Error('Missing DEV_AUTH_HOST_URL for standalone dev auth.');
94
+ }
95
+ const cliConfig = (runtime.readCliConfig ?? readCliConfig)();
96
+ assertCliConfig(cliConfig, hostUrl);
97
+ const devSession = await fetchDevSession(hostUrl, cliConfig.cliAccessToken, appId);
98
+ childEnv.DEV_AUTH_HOST_URL = normalizeHostUrl(devSession.hostUrl);
99
+ childEnv.DEV_SESSION_KEY = devSession.sessionKey;
100
+ stdout.write(`[bardioc-dev-session] Refreshed dev session for appId="${devSession.appId}" via ${childEnv.DEV_AUTH_HOST_URL}\n`);
101
+ }
102
+ const child = spawnCommand(command, commandRestArgs, {
103
+ stdio: 'inherit',
104
+ env: childEnv,
105
+ shell: (runtime.platform ?? process.platform) === 'win32',
106
+ });
107
+ child.on('exit', (code) => {
108
+ exit(code ?? 0);
109
+ });
110
+ child.on('error', (error) => {
111
+ stderr.write(`${error.message}\n`);
112
+ exit(1);
113
+ });
114
+ }
115
+ const isDirectRun = process.argv[1]
116
+ ? realpathSync.native(resolve(process.argv[1])) ===
117
+ realpathSync.native(fileURLToPath(import.meta.url))
118
+ : false;
119
+ if (isDirectRun) {
120
+ void main().catch(error => {
121
+ const message = error instanceof Error ? error.message : String(error);
122
+ process.stderr.write(`[bardioc-dev-session] ${message}\n`);
123
+ process.exit(1);
124
+ });
125
+ }