@bardioc/app-sdk 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +5 -0
- package/README.md +368 -0
- package/assets/fonts/README.md +11 -0
- package/assets/fonts/bardioc-fonts.css +55 -0
- package/assets/fonts/v1/geist-mono-latin-wght-normal.woff2 +0 -0
- package/assets/fonts/v1/nunito-sans-latin-wght-italic.woff2 +0 -0
- package/assets/fonts/v1/nunito-sans-latin-wght-normal.woff2 +0 -0
- package/dist/contract-matrix.d.ts +130 -0
- package/dist/contract-matrix.js +132 -0
- package/dist/dev-auth-proxy-core.d.ts +24 -0
- package/dist/dev-auth-proxy-core.js +59 -0
- package/dist/dev-auth-vite.d.ts +34 -0
- package/dist/dev-auth-vite.js +221 -0
- package/dist/dev-proxy.d.ts +8 -0
- package/dist/dev-proxy.js +40 -0
- package/dist/dev-session-cli.d.ts +34 -0
- package/dist/dev-session-cli.js +125 -0
- package/dist/dev.d.ts +33 -0
- package/dist/dev.js +223 -0
- package/dist/dot-env.d.ts +2 -0
- package/dist/dot-env.js +22 -0
- package/dist/errors.d.ts +27 -0
- package/dist/errors.js +57 -0
- package/dist/host-bridge.d.ts +3 -0
- package/dist/host-bridge.js +260 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +6 -0
- package/dist/manifest.d.ts +78 -0
- package/dist/manifest.js +169 -0
- package/dist/protocol.d.ts +26 -0
- package/dist/protocol.js +28 -0
- package/dist/react.d.ts +26 -0
- package/dist/react.js +208 -0
- package/dist/transports/graph-transport.d.ts +224 -0
- package/dist/transports/graph-transport.js +584 -0
- package/dist/transports/os-transport.d.ts +189 -0
- package/dist/transports/os-transport.js +444 -0
- package/dist/types.d.ts +343 -0
- package/dist/vite.d.ts +9 -0
- package/dist/vite.js +262 -0
- package/package.json +101 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/** Wire-level envelope sent from iframe → host. */
|
|
2
|
+
export interface SdkMessage<T extends string = string, P = unknown> {
|
|
3
|
+
type: T;
|
|
4
|
+
id: string;
|
|
5
|
+
payload: P;
|
|
6
|
+
}
|
|
7
|
+
/** Wire-level envelope sent from host → iframe as a response. */
|
|
8
|
+
export interface SdkResponse<P = unknown> {
|
|
9
|
+
type: string;
|
|
10
|
+
id: string;
|
|
11
|
+
payload: P;
|
|
12
|
+
error?: string;
|
|
13
|
+
}
|
|
14
|
+
/** OS configuration provided to apps. */
|
|
15
|
+
export interface OsConfig {
|
|
16
|
+
theme: string;
|
|
17
|
+
language: string;
|
|
18
|
+
tabId: string;
|
|
19
|
+
windowId: string;
|
|
20
|
+
instanceId: string | null;
|
|
21
|
+
systemSize?: string;
|
|
22
|
+
}
|
|
23
|
+
/** Context returned by the host after a successful handshake. */
|
|
24
|
+
export interface SdkInitResponse {
|
|
25
|
+
appId: string;
|
|
26
|
+
instanceId: string | null;
|
|
27
|
+
osConfig?: OsConfig;
|
|
28
|
+
}
|
|
29
|
+
export type SdkCommandHandler = () => void | Promise<void>;
|
|
30
|
+
export interface SdkCommandDefinition {
|
|
31
|
+
id: string;
|
|
32
|
+
title?: string;
|
|
33
|
+
disabled?: boolean;
|
|
34
|
+
checked?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export interface SdkMenuCommandItem {
|
|
37
|
+
type: 'command';
|
|
38
|
+
commandId: string;
|
|
39
|
+
label?: string;
|
|
40
|
+
leftIcon?: string;
|
|
41
|
+
separatorAbove?: boolean;
|
|
42
|
+
shortcut?: string;
|
|
43
|
+
variant?: 'default' | 'destructive';
|
|
44
|
+
}
|
|
45
|
+
export interface SdkMenuSubmenuItem {
|
|
46
|
+
type: 'submenu';
|
|
47
|
+
label: string;
|
|
48
|
+
leftIcon?: string;
|
|
49
|
+
separatorAbove?: boolean;
|
|
50
|
+
items: SdkMenuItem[];
|
|
51
|
+
contentWidth?: string;
|
|
52
|
+
}
|
|
53
|
+
export type SdkMenuItem = SdkMenuCommandItem | SdkMenuSubmenuItem;
|
|
54
|
+
export type SdkMenuRootKind = 'app' | 'menu';
|
|
55
|
+
export interface SdkMenuRoot {
|
|
56
|
+
label: string;
|
|
57
|
+
kind?: SdkMenuRootKind;
|
|
58
|
+
leftIcon?: string;
|
|
59
|
+
items: SdkMenuItem[];
|
|
60
|
+
contentWidth?: string;
|
|
61
|
+
}
|
|
62
|
+
export interface SdkSetCommandsPayload {
|
|
63
|
+
commands: SdkCommandDefinition[];
|
|
64
|
+
}
|
|
65
|
+
export interface SdkSetMenuPayload {
|
|
66
|
+
menu: SdkMenuRoot[] | null;
|
|
67
|
+
}
|
|
68
|
+
export interface SdkSetActiveTabPayload {
|
|
69
|
+
activeTab: string | null;
|
|
70
|
+
}
|
|
71
|
+
export interface SdkCommandInvokePayload {
|
|
72
|
+
commandId: string;
|
|
73
|
+
}
|
|
74
|
+
export interface SdkShortcutEventPayload {
|
|
75
|
+
/**
|
|
76
|
+
* Physical-key identifier (`KeyboardEvent.code`), e.g. `"KeyS"`, `"Digit1"`,
|
|
77
|
+
* `"Comma"`. Layout- and modifier-independent. Hosts that match shortcuts
|
|
78
|
+
* by physical key (recommended for cross-platform / cross-layout
|
|
79
|
+
* correctness, especially macOS Option combinations) use this field.
|
|
80
|
+
*/
|
|
81
|
+
code: string;
|
|
82
|
+
ctrlKey: boolean;
|
|
83
|
+
metaKey: boolean;
|
|
84
|
+
altKey: boolean;
|
|
85
|
+
shiftKey: boolean;
|
|
86
|
+
}
|
|
87
|
+
export type NotifyLevel = 'success' | 'error' | 'info' | 'warning';
|
|
88
|
+
export interface SdkNotifyPayload {
|
|
89
|
+
message: string;
|
|
90
|
+
level: NotifyLevel;
|
|
91
|
+
}
|
|
92
|
+
export interface SdkStorageGetPayload {
|
|
93
|
+
key: string;
|
|
94
|
+
}
|
|
95
|
+
export interface SdkStorageSetPayload {
|
|
96
|
+
key: string;
|
|
97
|
+
value: unknown;
|
|
98
|
+
}
|
|
99
|
+
export interface SdkStorageDeletePayload {
|
|
100
|
+
key: string;
|
|
101
|
+
}
|
|
102
|
+
export interface SdkKernelProxyPayload {
|
|
103
|
+
kernelType: string;
|
|
104
|
+
kernelPayload?: unknown;
|
|
105
|
+
}
|
|
106
|
+
export interface SdkTransportPayload {
|
|
107
|
+
path: string;
|
|
108
|
+
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
109
|
+
body?: unknown;
|
|
110
|
+
contentType?: string;
|
|
111
|
+
scopeId?: string;
|
|
112
|
+
scopePolicy?: 'none' | 'optional' | 'required';
|
|
113
|
+
}
|
|
114
|
+
export interface TransportScopeOptions {
|
|
115
|
+
scopeId?: string;
|
|
116
|
+
}
|
|
117
|
+
export interface SdkIdbGetPayload {
|
|
118
|
+
storeName: string;
|
|
119
|
+
key: string;
|
|
120
|
+
}
|
|
121
|
+
export interface SdkIdbPutPayload {
|
|
122
|
+
storeName: string;
|
|
123
|
+
key: string;
|
|
124
|
+
value: unknown;
|
|
125
|
+
}
|
|
126
|
+
export interface SdkIdbDeletePayload {
|
|
127
|
+
storeName: string;
|
|
128
|
+
key: string;
|
|
129
|
+
}
|
|
130
|
+
export interface SdkIdbQueryPayload {
|
|
131
|
+
storeName: string;
|
|
132
|
+
prefix?: string;
|
|
133
|
+
limit?: number;
|
|
134
|
+
}
|
|
135
|
+
/** Configuration passed to {@link createHostBridge}. */
|
|
136
|
+
export interface HostBridgeConfig {
|
|
137
|
+
appId?: string;
|
|
138
|
+
/** Override the host origin for `postMessage`. Inferred from `document.referrer` by default. */
|
|
139
|
+
targetOrigin?: string;
|
|
140
|
+
/** Handshake timeout in ms. Default: `5000`. */
|
|
141
|
+
connectTimeout?: number;
|
|
142
|
+
/** Enable verbose `[app-sdk]` console output. Default: `false`. */
|
|
143
|
+
debug?: boolean;
|
|
144
|
+
}
|
|
145
|
+
/** Key-value storage sandboxed to this app by the host. */
|
|
146
|
+
export interface SdkStorage {
|
|
147
|
+
get<T = unknown>(key: string): Promise<T | null>;
|
|
148
|
+
set(key: string, value: unknown): Promise<void>;
|
|
149
|
+
delete(key: string): Promise<void>;
|
|
150
|
+
keys(): Promise<string[]>;
|
|
151
|
+
}
|
|
152
|
+
export interface SdkIdbEntry<T = unknown> {
|
|
153
|
+
key: string;
|
|
154
|
+
value: T;
|
|
155
|
+
updatedAt: number;
|
|
156
|
+
}
|
|
157
|
+
export interface SdkIdbStore {
|
|
158
|
+
get<T = unknown>(key: string): Promise<T | null>;
|
|
159
|
+
put(key: string, value: unknown): Promise<void>;
|
|
160
|
+
delete(key: string): Promise<void>;
|
|
161
|
+
query<T = unknown>(options?: {
|
|
162
|
+
prefix?: string;
|
|
163
|
+
limit?: number;
|
|
164
|
+
}): Promise<SdkIdbEntry<T>[]>;
|
|
165
|
+
}
|
|
166
|
+
export interface SdkTransport {
|
|
167
|
+
graph: GraphTransport;
|
|
168
|
+
os: OsTransport;
|
|
169
|
+
}
|
|
170
|
+
/** Graph node in raw backend format (ogit/* fields) */
|
|
171
|
+
export interface GraphNodeRaw {
|
|
172
|
+
'ogit/_id': string;
|
|
173
|
+
'ogit/_type': string;
|
|
174
|
+
[key: string]: unknown;
|
|
175
|
+
}
|
|
176
|
+
/** Graph edge in raw backend format */
|
|
177
|
+
export interface GraphEdgeRaw {
|
|
178
|
+
'ogit/_id': string;
|
|
179
|
+
'ogit/_type': string;
|
|
180
|
+
'ogit/_in-id': string;
|
|
181
|
+
'ogit/_out-id': string;
|
|
182
|
+
[key: string]: unknown;
|
|
183
|
+
}
|
|
184
|
+
export interface GetNodeOptions extends TransportScopeOptions {
|
|
185
|
+
fields?: string;
|
|
186
|
+
includeDeleted?: boolean;
|
|
187
|
+
listMeta?: boolean;
|
|
188
|
+
vid?: number;
|
|
189
|
+
}
|
|
190
|
+
export interface QueryOptions extends TransportScopeOptions {
|
|
191
|
+
limit?: number;
|
|
192
|
+
offset?: number;
|
|
193
|
+
fields?: string;
|
|
194
|
+
includeDeleted?: boolean;
|
|
195
|
+
listMeta?: boolean;
|
|
196
|
+
order?: string;
|
|
197
|
+
}
|
|
198
|
+
export interface GremlinOptions extends TransportScopeOptions {
|
|
199
|
+
fields?: string;
|
|
200
|
+
includeDeleted?: boolean;
|
|
201
|
+
listMeta?: boolean;
|
|
202
|
+
}
|
|
203
|
+
export interface MutationOptions extends TransportScopeOptions {
|
|
204
|
+
fullResponse?: boolean;
|
|
205
|
+
listMeta?: boolean;
|
|
206
|
+
}
|
|
207
|
+
export interface DeleteOptions extends TransportScopeOptions {
|
|
208
|
+
hard?: boolean;
|
|
209
|
+
}
|
|
210
|
+
export interface HistoryOptions extends TransportScopeOptions {
|
|
211
|
+
from?: number | string;
|
|
212
|
+
to?: number | string;
|
|
213
|
+
limit?: number;
|
|
214
|
+
offset?: number;
|
|
215
|
+
includeDeleted?: boolean;
|
|
216
|
+
}
|
|
217
|
+
export interface HistoryEntry {
|
|
218
|
+
version: number;
|
|
219
|
+
timestamp: number;
|
|
220
|
+
data: Record<string, unknown>;
|
|
221
|
+
}
|
|
222
|
+
export interface TimeseriesValue {
|
|
223
|
+
timestamp: number;
|
|
224
|
+
value: unknown;
|
|
225
|
+
}
|
|
226
|
+
export interface TimeseriesOptions extends TransportScopeOptions {
|
|
227
|
+
from?: number | string;
|
|
228
|
+
to?: number | string;
|
|
229
|
+
limit?: number;
|
|
230
|
+
order?: 'asc' | 'desc';
|
|
231
|
+
}
|
|
232
|
+
export interface TimeseriesQueryOptions extends TimeseriesOptions {
|
|
233
|
+
ids?: string[];
|
|
234
|
+
aggregate?: string;
|
|
235
|
+
}
|
|
236
|
+
export interface BatchResult {
|
|
237
|
+
succeeded: string[];
|
|
238
|
+
failed: Array<{
|
|
239
|
+
id: string;
|
|
240
|
+
error: string;
|
|
241
|
+
}>;
|
|
242
|
+
}
|
|
243
|
+
export interface UserProfile {
|
|
244
|
+
id: string;
|
|
245
|
+
email: string;
|
|
246
|
+
name?: string;
|
|
247
|
+
[key: string]: unknown;
|
|
248
|
+
}
|
|
249
|
+
export interface OrgUnit {
|
|
250
|
+
id: string;
|
|
251
|
+
name: string;
|
|
252
|
+
parentId?: string;
|
|
253
|
+
[key: string]: unknown;
|
|
254
|
+
}
|
|
255
|
+
export interface OrgMember {
|
|
256
|
+
id: string;
|
|
257
|
+
mailAddress: string;
|
|
258
|
+
firstName?: string;
|
|
259
|
+
lastName?: string;
|
|
260
|
+
[key: string]: unknown;
|
|
261
|
+
}
|
|
262
|
+
export interface OrgStructure {
|
|
263
|
+
units: OrgUnit[];
|
|
264
|
+
members: OrgMember[];
|
|
265
|
+
}
|
|
266
|
+
export interface Application {
|
|
267
|
+
id: string;
|
|
268
|
+
name: string;
|
|
269
|
+
version?: string;
|
|
270
|
+
[key: string]: unknown;
|
|
271
|
+
}
|
|
272
|
+
export interface Configuration {
|
|
273
|
+
key: string;
|
|
274
|
+
value: unknown;
|
|
275
|
+
}
|
|
276
|
+
export interface FileEntry {
|
|
277
|
+
path: string;
|
|
278
|
+
size: number;
|
|
279
|
+
mimeType?: string;
|
|
280
|
+
}
|
|
281
|
+
export interface InstanceList {
|
|
282
|
+
instances: Array<{
|
|
283
|
+
id: string;
|
|
284
|
+
name: string;
|
|
285
|
+
}>;
|
|
286
|
+
}
|
|
287
|
+
export interface CreateUnitInput {
|
|
288
|
+
name: string;
|
|
289
|
+
parentId?: string;
|
|
290
|
+
[key: string]: unknown;
|
|
291
|
+
}
|
|
292
|
+
export interface UpdateUnitInput {
|
|
293
|
+
name?: string;
|
|
294
|
+
parentId?: string;
|
|
295
|
+
[key: string]: unknown;
|
|
296
|
+
}
|
|
297
|
+
export interface CreateMemberInput {
|
|
298
|
+
mailAddress: string;
|
|
299
|
+
firstName?: string;
|
|
300
|
+
lastName?: string;
|
|
301
|
+
[key: string]: unknown;
|
|
302
|
+
}
|
|
303
|
+
export interface UpdateMemberInput {
|
|
304
|
+
mailAddress?: string;
|
|
305
|
+
firstName?: string;
|
|
306
|
+
lastName?: string;
|
|
307
|
+
[key: string]: unknown;
|
|
308
|
+
}
|
|
309
|
+
export interface OsRequestOptions extends TransportScopeOptions {
|
|
310
|
+
path: string;
|
|
311
|
+
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
312
|
+
body?: unknown;
|
|
313
|
+
contentType?: string;
|
|
314
|
+
scopePolicy?: 'none' | 'optional' | 'required';
|
|
315
|
+
}
|
|
316
|
+
/** Live connection between an iframe app and its host OS. */
|
|
317
|
+
export interface HostBridge {
|
|
318
|
+
/** Perform the SDK handshake. Resolves with host context. Cached after first call. */
|
|
319
|
+
connect(): Promise<SdkInitResponse>;
|
|
320
|
+
/** Tear down the bridge and reject all pending requests. */
|
|
321
|
+
destroy(): void;
|
|
322
|
+
readonly connected: boolean;
|
|
323
|
+
readonly context: SdkInitResponse | null;
|
|
324
|
+
notify(message: string, level?: NotifyLevel): void;
|
|
325
|
+
storage: SdkStorage;
|
|
326
|
+
idb(storeName: string): SdkIdbStore;
|
|
327
|
+
transport: SdkTransport;
|
|
328
|
+
setCommands(commands: SdkCommandDefinition[]): Promise<void>;
|
|
329
|
+
setMenu(menu: SdkMenuRoot[] | null): Promise<void>;
|
|
330
|
+
setActiveTab(activeTab: string | null): Promise<void>;
|
|
331
|
+
sendShortcutEvent(payload: SdkShortcutEventPayload): void;
|
|
332
|
+
registerCommandHandlers(handlers: Record<string, SdkCommandHandler>): () => void;
|
|
333
|
+
onOsConfigUpdate(callback: (config: OsConfig) => void): () => void;
|
|
334
|
+
/** Forward a whitelisted message to the OS service worker. */
|
|
335
|
+
sendToKernel<R = unknown>(kernelType: string, kernelPayload?: unknown): Promise<R>;
|
|
336
|
+
/** Launch an app by ID in the OS. Returns the window ID if successful. */
|
|
337
|
+
launchApp(appId: string): Promise<{
|
|
338
|
+
windowId: string | null;
|
|
339
|
+
}>;
|
|
340
|
+
}
|
|
341
|
+
/** Forward declarations for transport classes */
|
|
342
|
+
export type GraphTransport = import('./transports/graph-transport.js').GraphTransport;
|
|
343
|
+
export type OsTransport = import('./transports/os-transport.js').OsTransport;
|
package/dist/vite.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Vite plugin for Bardioc iframe apps. Usage: `bardiocApp({ appName, port })` */
|
|
2
|
+
export { bardiocDevAuth, type BardiocDevAuthOptions } from './dev-auth-vite.js';
|
|
3
|
+
export interface BardiocAppOptions {
|
|
4
|
+
appName?: string;
|
|
5
|
+
port: number;
|
|
6
|
+
previewPort?: number;
|
|
7
|
+
}
|
|
8
|
+
/** Returns an array of Vite plugins for a Bardioc iframe app. */
|
|
9
|
+
export declare function bardiocApp(opts: BardiocAppOptions): any[];
|
package/dist/vite.js
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/** Vite plugin for Bardioc iframe apps. Usage: `bardiocApp({ appName, port })` */
|
|
2
|
+
import { execSync, spawn } from 'node:child_process';
|
|
3
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { resolve, sep } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { readDotEnvValue } from './dot-env.js';
|
|
7
|
+
import { MANIFEST_FILENAME, validateManifest } from './manifest.js';
|
|
8
|
+
export { bardiocDevAuth } from './dev-auth-vite.js';
|
|
9
|
+
function hasCommand(cmd) {
|
|
10
|
+
try {
|
|
11
|
+
execSync(`which ${cmd}`, { stdio: 'ignore' });
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function startTunnel(port) {
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
if (!hasCommand('cloudflared')) {
|
|
21
|
+
reject(new Error('cloudflared is not installed.\n' +
|
|
22
|
+
' macOS: brew install cloudflared\n' +
|
|
23
|
+
' Linux: apt install cloudflared\n' +
|
|
24
|
+
' Other: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/'));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const proc = spawn('cloudflared', ['tunnel', '--url', `http://localhost:${port}`], {
|
|
28
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
29
|
+
});
|
|
30
|
+
let found = false;
|
|
31
|
+
const timeout = setTimeout(() => {
|
|
32
|
+
if (!found) {
|
|
33
|
+
proc.kill();
|
|
34
|
+
reject(new Error('Tunnel timed out waiting for URL'));
|
|
35
|
+
}
|
|
36
|
+
}, 30_000);
|
|
37
|
+
function onData(data) {
|
|
38
|
+
const text = data.toString();
|
|
39
|
+
const match = text.match(/(https:\/\/[a-z0-9-]+\.trycloudflare\.com)/);
|
|
40
|
+
if (match?.[1] && !found) {
|
|
41
|
+
found = true;
|
|
42
|
+
clearTimeout(timeout);
|
|
43
|
+
resolve({ url: match[1], process: proc });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
proc.stdout?.on('data', onData);
|
|
47
|
+
proc.stderr?.on('data', onData);
|
|
48
|
+
proc.on('close', code => {
|
|
49
|
+
if (!found) {
|
|
50
|
+
clearTimeout(timeout);
|
|
51
|
+
reject(new Error(`cloudflared exited with code ${code} before providing a URL`));
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function devProxy(opts) {
|
|
57
|
+
const { port } = opts;
|
|
58
|
+
const wantTunnel = !!process.env.BARDIOC_TUNNEL;
|
|
59
|
+
let tunnelProcess = null;
|
|
60
|
+
let tunnelHost = null;
|
|
61
|
+
let tunnelReady = null;
|
|
62
|
+
return {
|
|
63
|
+
name: 'bardioc-dev-proxy',
|
|
64
|
+
apply: 'serve',
|
|
65
|
+
configureServer(server) {
|
|
66
|
+
if (wantTunnel) {
|
|
67
|
+
tunnelReady = (async () => {
|
|
68
|
+
try {
|
|
69
|
+
const manifestPath = resolve(process.cwd(), 'public', MANIFEST_FILENAME);
|
|
70
|
+
let manifestId = opts.appName;
|
|
71
|
+
if (existsSync(manifestPath)) {
|
|
72
|
+
try {
|
|
73
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
74
|
+
manifestId = manifest.id || manifestId;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// ignore
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
console.log(`[bardioc] Starting tunnel for "${manifestId}" on port ${port}...`);
|
|
81
|
+
const tunnel = await startTunnel(port);
|
|
82
|
+
tunnelProcess = tunnel.process;
|
|
83
|
+
tunnelHost = new URL(tunnel.url).host;
|
|
84
|
+
console.log(`[bardioc] Tunnel ready: ${tunnel.url}`);
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
console.error(`[bardioc] Tunnel failed: ${err.message}`);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
})();
|
|
91
|
+
}
|
|
92
|
+
server.httpServer?.on('listening', async () => {
|
|
93
|
+
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
|
+
if (!existsSync(manifestPath)) {
|
|
97
|
+
console.warn(`[bardioc] public/${MANIFEST_FILENAME} not found`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
let manifest;
|
|
101
|
+
try {
|
|
102
|
+
manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
console.error(`[bardioc] Failed to read manifest: ${err}`);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const manifestId = opts.appName || manifest.id;
|
|
109
|
+
if (wantTunnel) {
|
|
110
|
+
await tunnelReady;
|
|
111
|
+
if (!tunnelHost) {
|
|
112
|
+
console.error(`[bardioc] Tunnel was requested but failed to start`);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
console.log(`[bardioc] Use this live URL in the host App Store app configuration:`);
|
|
116
|
+
console.log(`[bardioc] App Name: ${manifestId}`);
|
|
117
|
+
console.log(`[bardioc] App ID: ${envAppId || '<from .env VITE_APP_ID>'}`);
|
|
118
|
+
console.log(`[bardioc] Live URL: https://${tunnelHost}`);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
console.log(`[bardioc] Dev server ready: http://localhost:${port}`);
|
|
122
|
+
console.log(`[bardioc] For standalone host-backed auth, open this app in the host App Store and use "Copy Dev Session".`);
|
|
123
|
+
console.log(`[bardioc] App Name: ${manifestId}`);
|
|
124
|
+
console.log(`[bardioc] App ID: ${envAppId || '<from .env VITE_APP_ID>'}`);
|
|
125
|
+
console.log(`[bardioc] URL: http://localhost:${port}`);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
server.httpServer?.on('close', () => {
|
|
129
|
+
tunnelProcess?.kill();
|
|
130
|
+
});
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
const FONTS_URL_PREFIX = '/fonts/';
|
|
135
|
+
const FONT_CONTENT_TYPES = {
|
|
136
|
+
css: 'text/css; charset=utf-8',
|
|
137
|
+
woff2: 'font/woff2',
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* Serves the platform font contract (`/fonts/bardioc-fonts.css` + `/fonts/v1/*`)
|
|
141
|
+
* from assets shipped inside the SDK package, so the same absolute URL apps use
|
|
142
|
+
* embedded in the OS also resolves in standalone dev mode (and tunnel mode —
|
|
143
|
+
* the tunnel terminates at this server). Dev-server only; the assets are never
|
|
144
|
+
* bundled into apps.
|
|
145
|
+
*
|
|
146
|
+
* Vite's dev HTML transform rewrites the root-absolute contract <link> to a
|
|
147
|
+
* base-prefixed URL (/_apps/<id>/fonts/...), so like the dev-auth middleware
|
|
148
|
+
* this strips the configured base before matching. The reserved /fonts/ path
|
|
149
|
+
* intentionally shadows an app's own public/fonts/ in dev.
|
|
150
|
+
*/
|
|
151
|
+
function fontsMiddleware() {
|
|
152
|
+
// import.meta.url must not be inlined into new URL(): Vite statically rewrites
|
|
153
|
+
// the `new URL('...', import.meta.url)` pattern (asset URL detection), which
|
|
154
|
+
// breaks the path under vite-based tooling such as vitest.
|
|
155
|
+
const moduleUrl = import.meta.url;
|
|
156
|
+
const fontsDir = fileURLToPath(new URL('../assets/fonts/', moduleUrl));
|
|
157
|
+
return {
|
|
158
|
+
name: 'bardioc-fonts',
|
|
159
|
+
apply: 'serve',
|
|
160
|
+
configureServer(server) {
|
|
161
|
+
const basePath = server.config.base.endsWith('/')
|
|
162
|
+
? server.config.base.slice(0, -1)
|
|
163
|
+
: server.config.base;
|
|
164
|
+
server.middlewares.use((req, res, next) => {
|
|
165
|
+
const pathname = new URL(req.url ?? '/', 'http://localhost').pathname;
|
|
166
|
+
const routePath = pathname.startsWith(basePath)
|
|
167
|
+
? pathname.slice(basePath.length) || '/'
|
|
168
|
+
: pathname;
|
|
169
|
+
const isReadMethod = req.method === 'GET' || req.method === 'HEAD';
|
|
170
|
+
if (!routePath.startsWith(FONTS_URL_PREFIX) || !isReadMethod) {
|
|
171
|
+
next();
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const contentType = FONT_CONTENT_TYPES[routePath.split('.').pop() ?? ''];
|
|
175
|
+
const filePath = resolve(fontsDir, routePath.slice(FONTS_URL_PREFIX.length));
|
|
176
|
+
// Traversal guard: only files inside the packaged fonts directory.
|
|
177
|
+
if (!contentType || !filePath.startsWith(fontsDir.replace(/[/\\]$/, '') + sep)) {
|
|
178
|
+
next();
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (!existsSync(filePath)) {
|
|
182
|
+
res.statusCode = 404;
|
|
183
|
+
res.end('Not found');
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
res.setHeader('Content-Type', contentType);
|
|
187
|
+
// Mirror the host's semantics: versioned binaries are immutable, the
|
|
188
|
+
// entry stylesheet must always be revalidated so edits show up in dev.
|
|
189
|
+
res.setHeader('Cache-Control', routePath.startsWith(`${FONTS_URL_PREFIX}v1/`)
|
|
190
|
+
? 'public, max-age=31536000, immutable'
|
|
191
|
+
: 'no-cache');
|
|
192
|
+
res.end(req.method === 'HEAD' ? undefined : readFileSync(filePath));
|
|
193
|
+
});
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function manifestCheck() {
|
|
198
|
+
return {
|
|
199
|
+
name: 'bardioc-manifest-check',
|
|
200
|
+
apply: 'build',
|
|
201
|
+
enforce: 'post',
|
|
202
|
+
closeBundle() {
|
|
203
|
+
const manifestPath = resolve(process.cwd(), 'public', MANIFEST_FILENAME);
|
|
204
|
+
if (!existsSync(manifestPath)) {
|
|
205
|
+
throw new Error(`[bardioc] public/${MANIFEST_FILENAME} not found`);
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
const raw = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
209
|
+
const { valid, errors } = validateManifest(raw);
|
|
210
|
+
if (!valid) {
|
|
211
|
+
throw new Error(`[bardioc] Manifest validation errors:\n${errors.map(error => ` - ${error}`).join('\n')}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
if (error instanceof Error && error.message.startsWith('[bardioc]')) {
|
|
216
|
+
throw error;
|
|
217
|
+
}
|
|
218
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
219
|
+
throw new Error(`[bardioc] Could not parse ${MANIFEST_FILENAME}: ${message}`);
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
/** Returns an array of Vite plugins for a Bardioc iframe app. */
|
|
225
|
+
export function bardiocApp(opts) {
|
|
226
|
+
const { port, previewPort } = opts;
|
|
227
|
+
// Read manifest to get app ID
|
|
228
|
+
const manifestPath = resolve(process.cwd(), 'public', MANIFEST_FILENAME);
|
|
229
|
+
let manifestId = opts.appName;
|
|
230
|
+
if (existsSync(manifestPath)) {
|
|
231
|
+
try {
|
|
232
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
233
|
+
manifestId = manifest.id || manifestId;
|
|
234
|
+
}
|
|
235
|
+
catch (err) {
|
|
236
|
+
console.warn(`[bardioc] Could not read manifest, using appName from options`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (!manifestId) {
|
|
240
|
+
throw new Error('[bardioc] No appName found in options or manifest. Please provide appName or create public/app-manifest.json');
|
|
241
|
+
}
|
|
242
|
+
const baseConfig = {
|
|
243
|
+
name: 'bardioc-base-config',
|
|
244
|
+
config(_config, env) {
|
|
245
|
+
const isDev = env.command === 'serve';
|
|
246
|
+
return {
|
|
247
|
+
base: isDev ? `/_apps/${manifestId}/` : './',
|
|
248
|
+
server: {
|
|
249
|
+
port,
|
|
250
|
+
allowedHosts: true,
|
|
251
|
+
},
|
|
252
|
+
preview: {
|
|
253
|
+
port: previewPort ?? port + 1000,
|
|
254
|
+
},
|
|
255
|
+
build: {
|
|
256
|
+
outDir: 'dist',
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
return [baseConfig, devProxy(opts), fontsMiddleware(), manifestCheck()];
|
|
262
|
+
}
|