@bardioc/app-sdk 1.0.2 → 1.0.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.
@@ -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
+ }
package/dist/dev-idb.d.ts CHANGED
@@ -2,9 +2,9 @@ import type { AppMethods, IdbEntry, Params } from './contract.js';
2
2
  type In<K extends keyof AppMethods> = Params<AppMethods[K]>;
3
3
  /** The host's app storage, kept in this browser's IndexedDB while running standalone. */
4
4
  export declare function devIdb(): {
5
- get: ({ store, key }: In<"idb.get">) => Promise<IdbEntry<unknown> | null>;
6
- put: ({ store, key, value }: In<"idb.put">) => Promise<void>;
7
- delete: ({ store, key }: In<"idb.delete">) => Promise<void>;
8
- query: ({ store, prefix, limit }: In<"idb.query">) => Promise<IdbEntry<unknown>[]>;
5
+ get: ({ store, key }: In<'idb.get'>) => Promise<IdbEntry<unknown> | null>;
6
+ put: ({ store, key, value }: In<'idb.put'>) => Promise<void>;
7
+ delete: ({ store, key }: In<'idb.delete'>) => Promise<void>;
8
+ query: ({ store, prefix, limit }: In<'idb.query'>) => Promise<IdbEntry<unknown>[]>;
9
9
  };
10
10
  export {};
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ import type { HostBridge, HostBridgeConfig } from './types.js';
2
+ /** Create a bridge between an app and its host OS, over `postMessage` unless `config.channel` says otherwise. */
3
+ export declare function createHostBridge(config: HostBridgeConfig): HostBridge;