@kehto/paja 0.1.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/README.md +61 -0
- package/dist/browser-host.js +5674 -0
- package/dist/browser-host.js.map +1 -0
- package/dist/chunk-BM6ROSMJ.js +635 -0
- package/dist/chunk-BM6ROSMJ.js.map +1 -0
- package/dist/cli.d.ts +41 -0
- package/dist/cli.js +314 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +1 -0
- package/dist/options-D3xZz-aS.d.ts +181 -0
- package/package.json +71 -0
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
// src/config-file.ts
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import { resolve } from "path";
|
|
4
|
+
|
|
5
|
+
// src/simulation.ts
|
|
6
|
+
var PajaSimulationError = class extends Error {
|
|
7
|
+
constructor(message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "PajaSimulationError";
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
var PAJA_SIMULATION_DOMAINS = [
|
|
13
|
+
"relay",
|
|
14
|
+
"outbox",
|
|
15
|
+
"storage",
|
|
16
|
+
"identity",
|
|
17
|
+
"keys",
|
|
18
|
+
"config",
|
|
19
|
+
"resource",
|
|
20
|
+
"theme",
|
|
21
|
+
"notify",
|
|
22
|
+
"media",
|
|
23
|
+
"upload",
|
|
24
|
+
"intent",
|
|
25
|
+
"cvm",
|
|
26
|
+
"inc"
|
|
27
|
+
];
|
|
28
|
+
var DEFAULT_RELAY_URLS = ["wss://relay.kehto.dev"];
|
|
29
|
+
var DEFAULT_CONFIG_VALUES = {
|
|
30
|
+
runtime: "kehto paja",
|
|
31
|
+
mode: "development",
|
|
32
|
+
target: "single-window"
|
|
33
|
+
};
|
|
34
|
+
var DEFAULT_THEME_VALUES = {
|
|
35
|
+
title: "Kehto Paja"
|
|
36
|
+
};
|
|
37
|
+
function normalizePajaSimulation(raw) {
|
|
38
|
+
const domainOverrides = raw?.capabilities?.domains ?? {};
|
|
39
|
+
const domains = Object.fromEntries(
|
|
40
|
+
PAJA_SIMULATION_DOMAINS.map((domain) => [domain, domainOverrides[domain] ?? true])
|
|
41
|
+
);
|
|
42
|
+
const relayMode = raw?.relay?.mode ?? (domains.relay ? "memory" : "disabled");
|
|
43
|
+
if (!domains.relay && relayMode !== "disabled") {
|
|
44
|
+
throw new PajaSimulationError('Invalid simulation: relay.mode must be "disabled" when capabilities.domains.relay is false.');
|
|
45
|
+
}
|
|
46
|
+
if (!domains.outbox && relayMode !== "disabled") {
|
|
47
|
+
throw new PajaSimulationError('Invalid simulation: relay.mode must be "disabled" when capabilities.domains.outbox is false.');
|
|
48
|
+
}
|
|
49
|
+
if (relayMode === "disabled") {
|
|
50
|
+
domains.relay = false;
|
|
51
|
+
domains.outbox = false;
|
|
52
|
+
}
|
|
53
|
+
const uploadMode = raw?.upload?.mode ?? (domains.upload ? "memory" : "disabled");
|
|
54
|
+
if (!domains.upload && uploadMode !== "disabled") {
|
|
55
|
+
throw new PajaSimulationError('Invalid simulation: upload.mode must be "disabled" when capabilities.domains.upload is false.');
|
|
56
|
+
}
|
|
57
|
+
if (uploadMode === "disabled") domains.upload = false;
|
|
58
|
+
const intentEnabled = raw?.intent?.enabled ?? domains.intent;
|
|
59
|
+
if (!domains.intent && intentEnabled) {
|
|
60
|
+
throw new PajaSimulationError("Invalid simulation: intent.enabled must be false when capabilities.domains.intent is false.");
|
|
61
|
+
}
|
|
62
|
+
if (!intentEnabled) domains.intent = false;
|
|
63
|
+
const mediaEnabled = raw?.media?.enabled ?? domains.media;
|
|
64
|
+
if (!domains.media && mediaEnabled) {
|
|
65
|
+
throw new PajaSimulationError("Invalid simulation: media.enabled must be false when capabilities.domains.media is false.");
|
|
66
|
+
}
|
|
67
|
+
if (!mediaEnabled) domains.media = false;
|
|
68
|
+
const cvmEnabled = raw?.cvm?.enabled ?? domains.cvm;
|
|
69
|
+
if (!domains.cvm && cvmEnabled) {
|
|
70
|
+
throw new PajaSimulationError("Invalid simulation: cvm.enabled must be false when capabilities.domains.cvm is false.");
|
|
71
|
+
}
|
|
72
|
+
if (!cvmEnabled) domains.cvm = false;
|
|
73
|
+
const notificationsEnabled = raw?.notifications?.enabled ?? domains.notify;
|
|
74
|
+
if (!domains.notify && notificationsEnabled) {
|
|
75
|
+
throw new PajaSimulationError("Invalid simulation: notifications.enabled must be false when capabilities.domains.notify is false.");
|
|
76
|
+
}
|
|
77
|
+
if (!notificationsEnabled) domains.notify = false;
|
|
78
|
+
const storageMode = raw?.storage?.mode ?? (domains.storage ? "local" : "disabled");
|
|
79
|
+
if (!domains.storage && storageMode !== "disabled") {
|
|
80
|
+
throw new PajaSimulationError('Invalid simulation: storage.mode must be "disabled" when capabilities.domains.storage is false.');
|
|
81
|
+
}
|
|
82
|
+
if (storageMode === "disabled") domains.storage = false;
|
|
83
|
+
const identityMode = raw?.identity?.mode ?? "anonymous";
|
|
84
|
+
const pubkey = raw?.identity?.pubkey?.trim() ?? "";
|
|
85
|
+
if (identityMode === "fixed" && !isHexPubkey(pubkey)) {
|
|
86
|
+
throw new PajaSimulationError('Invalid simulation: identity.pubkey must be a 64-character hex string when identity.mode is "fixed".');
|
|
87
|
+
}
|
|
88
|
+
const relayUrls = raw?.relay?.urls ?? DEFAULT_RELAY_URLS;
|
|
89
|
+
if (relayMode === "memory" && relayUrls.length === 0) {
|
|
90
|
+
throw new PajaSimulationError('Invalid simulation: relay.urls must contain at least one URL when relay.mode is "memory".');
|
|
91
|
+
}
|
|
92
|
+
for (const url of relayUrls) {
|
|
93
|
+
if (typeof url !== "string" || url.trim().length === 0) {
|
|
94
|
+
throw new PajaSimulationError("Invalid simulation: relay.urls entries must be non-empty strings.");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const uploadRail = raw?.upload?.rail?.trim() || "dev-memory";
|
|
98
|
+
if (uploadMode === "memory" && uploadRail.length === 0) {
|
|
99
|
+
throw new PajaSimulationError('Invalid simulation: upload.rail must be non-empty when upload.mode is "memory".');
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
capabilities: {
|
|
103
|
+
domains,
|
|
104
|
+
disabledDomains: PAJA_SIMULATION_DOMAINS.filter((domain) => !domains[domain])
|
|
105
|
+
},
|
|
106
|
+
acl: {
|
|
107
|
+
mode: raw?.acl?.mode ?? "allow"
|
|
108
|
+
},
|
|
109
|
+
firewall: {
|
|
110
|
+
mode: raw?.firewall?.mode ?? "observe"
|
|
111
|
+
},
|
|
112
|
+
identity: {
|
|
113
|
+
mode: identityMode,
|
|
114
|
+
pubkey: identityMode === "fixed" ? pubkey : ""
|
|
115
|
+
},
|
|
116
|
+
relay: {
|
|
117
|
+
mode: relayMode,
|
|
118
|
+
urls: relayUrls.map((url) => url.trim()),
|
|
119
|
+
fixtures: raw?.relay?.fixtures ?? []
|
|
120
|
+
},
|
|
121
|
+
storage: {
|
|
122
|
+
mode: storageMode
|
|
123
|
+
},
|
|
124
|
+
cache: {
|
|
125
|
+
mode: raw?.cache?.mode ?? "memory"
|
|
126
|
+
},
|
|
127
|
+
upload: {
|
|
128
|
+
mode: uploadMode,
|
|
129
|
+
rail: uploadRail
|
|
130
|
+
},
|
|
131
|
+
media: {
|
|
132
|
+
enabled: mediaEnabled
|
|
133
|
+
},
|
|
134
|
+
notifications: {
|
|
135
|
+
enabled: notificationsEnabled,
|
|
136
|
+
grant: raw?.notifications?.grant ?? true
|
|
137
|
+
},
|
|
138
|
+
config: {
|
|
139
|
+
values: { ...DEFAULT_CONFIG_VALUES, ...raw?.config?.values }
|
|
140
|
+
},
|
|
141
|
+
theme: {
|
|
142
|
+
mode: raw?.theme?.mode ?? "dark",
|
|
143
|
+
values: { ...DEFAULT_THEME_VALUES, ...raw?.theme?.values }
|
|
144
|
+
},
|
|
145
|
+
intent: {
|
|
146
|
+
enabled: intentEnabled
|
|
147
|
+
},
|
|
148
|
+
cvm: {
|
|
149
|
+
enabled: cvmEnabled
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function summarizePajaSimulation(simulation) {
|
|
154
|
+
const relay = simulation.relay.mode === "memory" ? `relay:${simulation.relay.urls.length}` : "relay:off";
|
|
155
|
+
const identity = simulation.identity.mode === "fixed" ? "identity:fixed" : "identity:anon";
|
|
156
|
+
const storage = `storage:${simulation.storage.mode}`;
|
|
157
|
+
const theme = `theme:${simulation.theme.mode}`;
|
|
158
|
+
const disabled = simulation.capabilities.disabledDomains.length > 0 ? `off:${simulation.capabilities.disabledDomains.join(",")}` : "off:none";
|
|
159
|
+
return `${identity} ${relay} ${storage} ${theme} ${disabled}`;
|
|
160
|
+
}
|
|
161
|
+
function isHexPubkey(value) {
|
|
162
|
+
return /^[0-9a-fA-F]{64}$/.test(value);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/config-file.ts
|
|
166
|
+
function loadPajaConfigFile(configPath) {
|
|
167
|
+
const resolved = resolve(configPath);
|
|
168
|
+
let parsed;
|
|
169
|
+
try {
|
|
170
|
+
parsed = JSON.parse(readFileSync(resolved, "utf8"));
|
|
171
|
+
} catch (error) {
|
|
172
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
173
|
+
throw new PajaSimulationError(`Unable to read --config "${configPath}": ${detail}`);
|
|
174
|
+
}
|
|
175
|
+
if (!isRecord(parsed)) {
|
|
176
|
+
throw new PajaSimulationError(`Invalid --config "${configPath}": expected a JSON object.`);
|
|
177
|
+
}
|
|
178
|
+
return parsed;
|
|
179
|
+
}
|
|
180
|
+
function resolvePajaRawOptions(raw) {
|
|
181
|
+
if (!raw.configPath?.trim()) return raw;
|
|
182
|
+
return mergePajaRawOptions(loadPajaConfigFile(raw.configPath), raw);
|
|
183
|
+
}
|
|
184
|
+
function mergePajaRawOptions(base, override) {
|
|
185
|
+
return {
|
|
186
|
+
targetUrl: override.targetUrl ?? base.targetUrl,
|
|
187
|
+
command: override.command ?? base.command,
|
|
188
|
+
host: override.host ?? base.host,
|
|
189
|
+
port: override.port ?? base.port,
|
|
190
|
+
readyTimeoutMs: override.readyTimeoutMs ?? base.readyTimeoutMs,
|
|
191
|
+
configPath: override.configPath ?? base.configPath,
|
|
192
|
+
simulation: mergeSimulationOptions(base.simulation, override.simulation)
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function mergeSimulationOptions(base, override) {
|
|
196
|
+
if (!base) return override;
|
|
197
|
+
if (!override) return base;
|
|
198
|
+
return {
|
|
199
|
+
capabilities: {
|
|
200
|
+
...base.capabilities,
|
|
201
|
+
...override.capabilities,
|
|
202
|
+
domains: {
|
|
203
|
+
...base.capabilities?.domains,
|
|
204
|
+
...override.capabilities?.domains
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
acl: { ...base.acl, ...override.acl },
|
|
208
|
+
firewall: { ...base.firewall, ...override.firewall },
|
|
209
|
+
identity: { ...base.identity, ...override.identity },
|
|
210
|
+
relay: { ...base.relay, ...override.relay },
|
|
211
|
+
storage: { ...base.storage, ...override.storage },
|
|
212
|
+
cache: { ...base.cache, ...override.cache },
|
|
213
|
+
upload: { ...base.upload, ...override.upload },
|
|
214
|
+
media: { ...base.media, ...override.media },
|
|
215
|
+
notifications: { ...base.notifications, ...override.notifications },
|
|
216
|
+
config: {
|
|
217
|
+
...base.config,
|
|
218
|
+
...override.config,
|
|
219
|
+
values: { ...base.config?.values, ...override.config?.values }
|
|
220
|
+
},
|
|
221
|
+
theme: {
|
|
222
|
+
...base.theme,
|
|
223
|
+
...override.theme,
|
|
224
|
+
values: { ...base.theme?.values, ...override.theme?.values }
|
|
225
|
+
},
|
|
226
|
+
intent: { ...base.intent, ...override.intent },
|
|
227
|
+
cvm: { ...base.cvm, ...override.cvm }
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function isRecord(value) {
|
|
231
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// src/options.ts
|
|
235
|
+
var PajaOptionsError = class extends Error {
|
|
236
|
+
constructor(message) {
|
|
237
|
+
super(message);
|
|
238
|
+
this.name = "PajaOptionsError";
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
var DEFAULT_PAJA_HOST = "127.0.0.1";
|
|
242
|
+
var DEFAULT_PAJA_PORT = 5197;
|
|
243
|
+
var DEFAULT_READY_TIMEOUT_MS = 3e4;
|
|
244
|
+
var DEFAULT_PAJA_WINDOW_ID = "kehto-paja-window";
|
|
245
|
+
var DEFAULT_PAJA_DTAG = "dev-target";
|
|
246
|
+
var DEFAULT_PAJA_AGGREGATE_HASH = "paja";
|
|
247
|
+
function normalizePajaOptions(raw) {
|
|
248
|
+
const targetUrl = normalizeTargetUrl(raw.targetUrl);
|
|
249
|
+
const host = normalizeHost(raw.host);
|
|
250
|
+
const port = normalizePort(raw.port, "port");
|
|
251
|
+
const readyTimeoutMs = normalizePositiveInteger(
|
|
252
|
+
raw.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS,
|
|
253
|
+
"ready-timeout"
|
|
254
|
+
);
|
|
255
|
+
const command = normalizeCommand(raw.command);
|
|
256
|
+
const configPath = raw.configPath?.trim();
|
|
257
|
+
const simulation = normalizePajaSimulation(raw.simulation);
|
|
258
|
+
return {
|
|
259
|
+
targetUrl,
|
|
260
|
+
command,
|
|
261
|
+
host,
|
|
262
|
+
port,
|
|
263
|
+
readyTimeoutMs,
|
|
264
|
+
configPath: configPath && configPath.length > 0 ? configPath : void 0,
|
|
265
|
+
simulation,
|
|
266
|
+
mode: command ? "managed-command" : "external-target"
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
function createPajaHostConfig(options, now = /* @__PURE__ */ new Date()) {
|
|
270
|
+
return {
|
|
271
|
+
version: 1,
|
|
272
|
+
window: {
|
|
273
|
+
id: DEFAULT_PAJA_WINDOW_ID,
|
|
274
|
+
dTag: DEFAULT_PAJA_DTAG,
|
|
275
|
+
aggregateHash: DEFAULT_PAJA_AGGREGATE_HASH
|
|
276
|
+
},
|
|
277
|
+
target: {
|
|
278
|
+
url: options.targetUrl,
|
|
279
|
+
hmrStrategy: "iframe-target-url",
|
|
280
|
+
command: options.command
|
|
281
|
+
},
|
|
282
|
+
runtime: {
|
|
283
|
+
host: options.host,
|
|
284
|
+
port: options.port,
|
|
285
|
+
readyTimeoutMs: options.readyTimeoutMs,
|
|
286
|
+
configPath: options.configPath,
|
|
287
|
+
reloadToken: createReloadToken(now),
|
|
288
|
+
createdAt: now.toISOString()
|
|
289
|
+
},
|
|
290
|
+
chrome: {
|
|
291
|
+
topBar: true,
|
|
292
|
+
bottomBar: true,
|
|
293
|
+
sidePanels: false
|
|
294
|
+
},
|
|
295
|
+
simulation: options.simulation
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
function formatPajaUrl(options) {
|
|
299
|
+
return `http://${options.host}:${options.port}/`;
|
|
300
|
+
}
|
|
301
|
+
function normalizeTargetUrl(value) {
|
|
302
|
+
const raw = value?.trim();
|
|
303
|
+
if (!raw) {
|
|
304
|
+
throw new PajaOptionsError("Missing --target-url. Provide the napplet app URL that the iframe should load.");
|
|
305
|
+
}
|
|
306
|
+
let parsed;
|
|
307
|
+
try {
|
|
308
|
+
parsed = new URL(raw);
|
|
309
|
+
} catch {
|
|
310
|
+
throw new PajaOptionsError(`Invalid --target-url "${raw}". Expected an absolute http(s) URL.`);
|
|
311
|
+
}
|
|
312
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
313
|
+
throw new PajaOptionsError(`Invalid --target-url "${raw}". Only http: and https: URLs are supported.`);
|
|
314
|
+
}
|
|
315
|
+
return parsed.href;
|
|
316
|
+
}
|
|
317
|
+
function normalizeHost(value) {
|
|
318
|
+
const host = value?.trim() || DEFAULT_PAJA_HOST;
|
|
319
|
+
if (host.length === 0 || host.includes("/")) {
|
|
320
|
+
throw new PajaOptionsError(`Invalid --host "${value ?? ""}". Provide a hostname or IP address.`);
|
|
321
|
+
}
|
|
322
|
+
return host;
|
|
323
|
+
}
|
|
324
|
+
function normalizePort(value, label) {
|
|
325
|
+
return normalizeIntegerInRange(value ?? DEFAULT_PAJA_PORT, label, 0, 65535);
|
|
326
|
+
}
|
|
327
|
+
function normalizePositiveInteger(value, label, max = Number.MAX_SAFE_INTEGER) {
|
|
328
|
+
return normalizeIntegerInRange(value, label, 1, max);
|
|
329
|
+
}
|
|
330
|
+
function normalizeIntegerInRange(value, label, min, max) {
|
|
331
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
332
|
+
if (!Number.isInteger(parsed) || parsed < min || parsed > max) {
|
|
333
|
+
throw new PajaOptionsError(`Invalid --${label} "${String(value)}". Expected an integer from ${min} to ${max}.`);
|
|
334
|
+
}
|
|
335
|
+
return parsed;
|
|
336
|
+
}
|
|
337
|
+
function normalizeCommand(command) {
|
|
338
|
+
if (!command) return void 0;
|
|
339
|
+
if (command.mode === "argv") {
|
|
340
|
+
const argv = command.argv.map((part) => part.trim()).filter((part) => part.length > 0);
|
|
341
|
+
if (argv.length === 0) {
|
|
342
|
+
throw new PajaOptionsError("Command mode requires at least one command argument after --.");
|
|
343
|
+
}
|
|
344
|
+
return { mode: "argv", argv };
|
|
345
|
+
}
|
|
346
|
+
const shellCommand = command.command.trim();
|
|
347
|
+
if (shellCommand.length === 0) {
|
|
348
|
+
throw new PajaOptionsError("--command requires a non-empty command string.");
|
|
349
|
+
}
|
|
350
|
+
return { mode: "shell", command: shellCommand };
|
|
351
|
+
}
|
|
352
|
+
function createReloadToken(now) {
|
|
353
|
+
const iso = now.toISOString();
|
|
354
|
+
return `reload-${iso.replaceAll(/[^0-9A-Za-z]/g, "")}`;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// src/host-page.ts
|
|
358
|
+
function renderPajaHtml(config) {
|
|
359
|
+
const configJson = escapeJsonForHtml(JSON.stringify(config));
|
|
360
|
+
const targetUrl = escapeAttribute(config.target.url);
|
|
361
|
+
return `<!doctype html>
|
|
362
|
+
<html lang="en">
|
|
363
|
+
<head>
|
|
364
|
+
<meta charset="utf-8">
|
|
365
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
366
|
+
<title>Kehto Paja</title>
|
|
367
|
+
<style>
|
|
368
|
+
:root {
|
|
369
|
+
color-scheme: dark;
|
|
370
|
+
--bg: #101211;
|
|
371
|
+
--bar: #181b19;
|
|
372
|
+
--line: #30352f;
|
|
373
|
+
--text: #f4f0df;
|
|
374
|
+
--muted: #a9ad9f;
|
|
375
|
+
--accent: #d8c36a;
|
|
376
|
+
}
|
|
377
|
+
* { box-sizing: border-box; }
|
|
378
|
+
html, body { margin: 0; min-height: 100%; background: var(--bg); color: var(--text); font: 13px/1.4 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
|
|
379
|
+
body { height: 100vh; display: grid; grid-template-rows: 38px minmax(0, 1fr) 30px; overflow: hidden; }
|
|
380
|
+
.bar { display: flex; align-items: center; gap: 14px; min-width: 0; padding: 0 12px; background: var(--bar); border-color: var(--line); }
|
|
381
|
+
.top { border-bottom: 1px solid var(--line); }
|
|
382
|
+
.bottom { border-top: 1px solid var(--line); color: var(--muted); font-size: 12px; }
|
|
383
|
+
.brand { font-weight: 700; letter-spacing: 0; color: var(--accent); white-space: nowrap; }
|
|
384
|
+
.target { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); }
|
|
385
|
+
.spacer { flex: 1; min-width: 0; }
|
|
386
|
+
button { border: 1px solid var(--line); color: var(--text); background: #20241f; height: 26px; padding: 0 10px; border-radius: 4px; font: inherit; cursor: pointer; }
|
|
387
|
+
button:hover { border-color: var(--accent); }
|
|
388
|
+
label { display: inline-flex; align-items: center; gap: 6px; color: var(--muted); white-space: nowrap; }
|
|
389
|
+
select { border: 1px solid var(--line); color: var(--text); background: #20241f; height: 26px; border-radius: 4px; font: inherit; }
|
|
390
|
+
iframe { width: 100%; height: 100%; border: 0; background: white; display: block; }
|
|
391
|
+
code { color: var(--text); }
|
|
392
|
+
</style>
|
|
393
|
+
</head>
|
|
394
|
+
<body>
|
|
395
|
+
<header class="bar top">
|
|
396
|
+
<div class="brand">Kehto</div>
|
|
397
|
+
<div class="target" title="${targetUrl}">${targetUrl}</div>
|
|
398
|
+
<div class="spacer"></div>
|
|
399
|
+
<label>theme
|
|
400
|
+
<select id="simulation-theme" aria-label="Simulation theme">
|
|
401
|
+
<option value="dark"${config.simulation.theme.mode === "dark" ? " selected" : ""}>dark</option>
|
|
402
|
+
<option value="light"${config.simulation.theme.mode === "light" ? " selected" : ""}>light</option>
|
|
403
|
+
</select>
|
|
404
|
+
</label>
|
|
405
|
+
<button type="button" id="reload-target">Reload</button>
|
|
406
|
+
</header>
|
|
407
|
+
<main>
|
|
408
|
+
<iframe id="napplet-frame" title="Napplet development target" sandbox="allow-scripts" data-target-url="${targetUrl}"></iframe>
|
|
409
|
+
</main>
|
|
410
|
+
<footer class="bar bottom">
|
|
411
|
+
<span>mode: <code>${config.target.command ? "managed-command" : "external-target"}</code></span>
|
|
412
|
+
<span>hmr: <code>${config.target.hmrStrategy}</code></span>
|
|
413
|
+
<span>runtime: <code>${escapeHtml(config.runtime.host)}:${config.runtime.port}</code></span>
|
|
414
|
+
<span>sim: <code id="simulation-status">${escapeHtml(summarizePajaSimulation(config.simulation))}</code></span>
|
|
415
|
+
<span>state: <code id="lifecycle-status">booting</code></span>
|
|
416
|
+
</footer>
|
|
417
|
+
<script type="application/json" id="kehto-paja-config">${configJson}</script>
|
|
418
|
+
<script type="module" src="/__kehto/browser-host.js"></script>
|
|
419
|
+
</body>
|
|
420
|
+
</html>`;
|
|
421
|
+
}
|
|
422
|
+
function escapeAttribute(value) {
|
|
423
|
+
return escapeHtml(value).replaceAll('"', """);
|
|
424
|
+
}
|
|
425
|
+
function escapeHtml(value) {
|
|
426
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
427
|
+
}
|
|
428
|
+
function escapeJsonForHtml(value) {
|
|
429
|
+
return value.replaceAll("<", "\\u003c");
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// src/parity.ts
|
|
433
|
+
var PAJA_LEGACY_COMPATIBILITY_DOMAIN = `i${"fc"}`;
|
|
434
|
+
var PAJA_UPSTREAM_WEB_DOMAINS = [
|
|
435
|
+
"config",
|
|
436
|
+
"cvm",
|
|
437
|
+
"identity",
|
|
438
|
+
PAJA_LEGACY_COMPATIBILITY_DOMAIN,
|
|
439
|
+
"inc",
|
|
440
|
+
"intent",
|
|
441
|
+
"keys",
|
|
442
|
+
"media",
|
|
443
|
+
"notify",
|
|
444
|
+
"outbox",
|
|
445
|
+
"relay",
|
|
446
|
+
"resource",
|
|
447
|
+
"shell",
|
|
448
|
+
"storage",
|
|
449
|
+
"theme",
|
|
450
|
+
"upload"
|
|
451
|
+
];
|
|
452
|
+
var PAJA_HANDSHAKE_DOMAINS = ["shell"];
|
|
453
|
+
var PAJA_COMPATIBILITY_ALIASES = {
|
|
454
|
+
[PAJA_LEGACY_COMPATIBILITY_DOMAIN]: "inc"
|
|
455
|
+
};
|
|
456
|
+
var PAJA_ADVERTISED_DOMAINS = [
|
|
457
|
+
"relay",
|
|
458
|
+
"outbox",
|
|
459
|
+
"identity",
|
|
460
|
+
"storage",
|
|
461
|
+
"inc",
|
|
462
|
+
"theme",
|
|
463
|
+
"keys",
|
|
464
|
+
"media",
|
|
465
|
+
"notify",
|
|
466
|
+
"config",
|
|
467
|
+
"resource",
|
|
468
|
+
"cvm",
|
|
469
|
+
"upload",
|
|
470
|
+
"intent"
|
|
471
|
+
];
|
|
472
|
+
var PAJA_REQUIRED_SERVICES = [
|
|
473
|
+
"config",
|
|
474
|
+
"cvm",
|
|
475
|
+
"identity",
|
|
476
|
+
"intent",
|
|
477
|
+
"keys",
|
|
478
|
+
"media",
|
|
479
|
+
"notifications",
|
|
480
|
+
"notify",
|
|
481
|
+
"outbox",
|
|
482
|
+
"relay",
|
|
483
|
+
"resource",
|
|
484
|
+
"theme",
|
|
485
|
+
"upload"
|
|
486
|
+
];
|
|
487
|
+
function getMissingAdvertisedDomains(capabilities) {
|
|
488
|
+
const advertised = new Set(capabilities.domains);
|
|
489
|
+
return PAJA_ADVERTISED_DOMAINS.filter((domain) => !advertised.has(domain));
|
|
490
|
+
}
|
|
491
|
+
function getMissingServices(services) {
|
|
492
|
+
const wired = new Set(services);
|
|
493
|
+
return PAJA_REQUIRED_SERVICES.filter((service) => !wired.has(service));
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// src/readiness.ts
|
|
497
|
+
var ReadinessError = class extends Error {
|
|
498
|
+
constructor(message) {
|
|
499
|
+
super(message);
|
|
500
|
+
this.name = "ReadinessError";
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
async function waitForTargetUrl(url, options) {
|
|
504
|
+
const intervalMs = options.intervalMs ?? 250;
|
|
505
|
+
const fetcher = options.fetch ?? defaultFetch;
|
|
506
|
+
const sleep = options.sleep ?? defaultSleep;
|
|
507
|
+
const now = options.now ?? Date.now;
|
|
508
|
+
const deadline = now() + options.timeoutMs;
|
|
509
|
+
let lastError = "no response";
|
|
510
|
+
while (now() <= deadline) {
|
|
511
|
+
try {
|
|
512
|
+
const response = await fetcher(url);
|
|
513
|
+
if (response.status < 500) {
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
lastError = `status ${response.status}`;
|
|
517
|
+
} catch (error) {
|
|
518
|
+
lastError = error instanceof Error ? error.message : String(error);
|
|
519
|
+
}
|
|
520
|
+
await sleep(intervalMs);
|
|
521
|
+
}
|
|
522
|
+
throw new ReadinessError(`Timed out waiting ${options.timeoutMs}ms for target URL ${url}: ${lastError}`);
|
|
523
|
+
}
|
|
524
|
+
async function defaultFetch(url) {
|
|
525
|
+
return fetch(url, { method: "GET", redirect: "follow" });
|
|
526
|
+
}
|
|
527
|
+
function defaultSleep(ms) {
|
|
528
|
+
return new Promise((resolve2) => {
|
|
529
|
+
setTimeout(resolve2, ms);
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// src/server.ts
|
|
534
|
+
import { createServer } from "http";
|
|
535
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
536
|
+
async function startPajaServer(input) {
|
|
537
|
+
const rawOptions = resolvePajaRawOptions(input.options);
|
|
538
|
+
const options = normalizePajaOptions(rawOptions);
|
|
539
|
+
let hostConfig = createPajaHostConfig(options, input.now);
|
|
540
|
+
let html = renderPajaHtml(hostConfig);
|
|
541
|
+
let configJson = JSON.stringify(hostConfig, null, 2);
|
|
542
|
+
const server = createServer((request, response) => {
|
|
543
|
+
const requestUrl = request.url ?? "/";
|
|
544
|
+
if (requestUrl === "/" || requestUrl.startsWith("/?")) {
|
|
545
|
+
response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
546
|
+
response.end(html);
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
if (requestUrl === "/__kehto/config.json") {
|
|
550
|
+
response.writeHead(200, { "content-type": "application/json; charset=utf-8" });
|
|
551
|
+
response.end(configJson);
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
if (requestUrl === "/__kehto/browser-host.js") {
|
|
555
|
+
const browserScript = readBrowserHostScript();
|
|
556
|
+
response.writeHead(200, { "content-type": "text/javascript; charset=utf-8" });
|
|
557
|
+
response.end(browserScript);
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
response.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
|
|
561
|
+
response.end("Not found");
|
|
562
|
+
});
|
|
563
|
+
await listen(server, options.host, options.port);
|
|
564
|
+
const servedOptions = withBoundPort(options, getBoundPort(server, options.port));
|
|
565
|
+
hostConfig = createPajaHostConfig(servedOptions, input.now);
|
|
566
|
+
html = renderPajaHtml(hostConfig);
|
|
567
|
+
configJson = JSON.stringify(hostConfig, null, 2);
|
|
568
|
+
return {
|
|
569
|
+
url: formatPajaUrl(servedOptions),
|
|
570
|
+
hostConfig,
|
|
571
|
+
close: () => close(server)
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
function readBrowserHostScript() {
|
|
575
|
+
return readFileSync2(new URL("./browser-host.js", import.meta.url), "utf8");
|
|
576
|
+
}
|
|
577
|
+
function listen(server, host, port) {
|
|
578
|
+
return new Promise((resolve2, reject) => {
|
|
579
|
+
server.once("error", reject);
|
|
580
|
+
server.listen(port, host, () => {
|
|
581
|
+
server.off("error", reject);
|
|
582
|
+
resolve2();
|
|
583
|
+
});
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
function close(server) {
|
|
587
|
+
return new Promise((resolve2, reject) => {
|
|
588
|
+
server.close((error) => {
|
|
589
|
+
if (error) {
|
|
590
|
+
reject(error);
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
resolve2();
|
|
594
|
+
});
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
function getBoundPort(server, fallback) {
|
|
598
|
+
const address = server.address();
|
|
599
|
+
return typeof address === "object" && address !== null ? address.port : fallback;
|
|
600
|
+
}
|
|
601
|
+
function withBoundPort(options, port) {
|
|
602
|
+
return port === options.port ? options : { ...options, port };
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
export {
|
|
606
|
+
PajaSimulationError,
|
|
607
|
+
PAJA_SIMULATION_DOMAINS,
|
|
608
|
+
normalizePajaSimulation,
|
|
609
|
+
summarizePajaSimulation,
|
|
610
|
+
loadPajaConfigFile,
|
|
611
|
+
resolvePajaRawOptions,
|
|
612
|
+
mergePajaRawOptions,
|
|
613
|
+
PajaOptionsError,
|
|
614
|
+
DEFAULT_PAJA_HOST,
|
|
615
|
+
DEFAULT_PAJA_PORT,
|
|
616
|
+
DEFAULT_READY_TIMEOUT_MS,
|
|
617
|
+
DEFAULT_PAJA_WINDOW_ID,
|
|
618
|
+
DEFAULT_PAJA_DTAG,
|
|
619
|
+
DEFAULT_PAJA_AGGREGATE_HASH,
|
|
620
|
+
normalizePajaOptions,
|
|
621
|
+
createPajaHostConfig,
|
|
622
|
+
formatPajaUrl,
|
|
623
|
+
renderPajaHtml,
|
|
624
|
+
PAJA_UPSTREAM_WEB_DOMAINS,
|
|
625
|
+
PAJA_HANDSHAKE_DOMAINS,
|
|
626
|
+
PAJA_COMPATIBILITY_ALIASES,
|
|
627
|
+
PAJA_ADVERTISED_DOMAINS,
|
|
628
|
+
PAJA_REQUIRED_SERVICES,
|
|
629
|
+
getMissingAdvertisedDomains,
|
|
630
|
+
getMissingServices,
|
|
631
|
+
ReadinessError,
|
|
632
|
+
waitForTargetUrl,
|
|
633
|
+
startPajaServer
|
|
634
|
+
};
|
|
635
|
+
//# sourceMappingURL=chunk-BM6ROSMJ.js.map
|