@theaiplatform/miniapp-sdk 0.3.2 → 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.
@@ -0,0 +1,594 @@
1
+ import { sdk } from "./sdk.js";
2
+ import { resolvePackageAssetUrl } from "./surface.js";
3
+ const BRIDGE_KIND = 'tap-vscode-webview-bridge';
4
+ const BRIDGE_VERSION = 1;
5
+ const BRIDGE_INIT_ELEMENT_ID = 'tap-vscode-webview-bridge-init';
6
+ const FORBIDDEN_PATH_SEGMENTS = new Set([
7
+ '__proto__',
8
+ 'constructor',
9
+ 'prototype'
10
+ ]);
11
+ const CONFLICT_PATTERN = /conflict|revision/iu;
12
+ const HTTP_METHODS = new Set([
13
+ 'DELETE',
14
+ 'GET',
15
+ 'HEAD',
16
+ 'OPTIONS',
17
+ 'PATCH',
18
+ 'POST',
19
+ 'PUT'
20
+ ]);
21
+ function assertCanonicalPath(path, label) {
22
+ if (0 === path.length || path.some((segment)=>0 === segment.length || segment.trim() !== segment || FORBIDDEN_PATH_SEGMENTS.has(segment))) throw new Error(`${label} must be a non-empty canonical JSON path.`);
23
+ }
24
+ function readPath(value, path) {
25
+ let current = value;
26
+ for (const segment of path){
27
+ if (null === current || 'object' != typeof current || !Object.hasOwn(current, segment)) return;
28
+ current = Reflect.get(current, segment);
29
+ }
30
+ return current;
31
+ }
32
+ function writePath(value, path, nextValue) {
33
+ const cloned = cloneJson(value);
34
+ let current;
35
+ if (null === cloned || 'object' != typeof cloned || Array.isArray(cloned)) throw new Error('A bridge state path requires an object root.');
36
+ current = cloned;
37
+ for (const segment of path.slice(0, -1)){
38
+ const child = current[segment];
39
+ if (void 0 === child) {
40
+ const created = {};
41
+ current[segment] = created;
42
+ current = created;
43
+ continue;
44
+ }
45
+ if (null === child || 'object' != typeof child || Array.isArray(child)) throw new Error('A bridge state path crosses a non-object value.');
46
+ current = child;
47
+ }
48
+ current[path.at(-1)] = cloneJson(nextValue);
49
+ return cloned;
50
+ }
51
+ function cloneJson(value) {
52
+ return JSON.parse(JSON.stringify(value));
53
+ }
54
+ function bytesToBase64(value) {
55
+ if (!Array.isArray(value) || value.some((byte)=>!Number.isInteger(byte) || byte < 0 || byte > 255)) throw new Error('Expected an array of unsigned bytes.');
56
+ let binary = '';
57
+ for(let offset = 0; offset < value.length; offset += 32768)binary += String.fromCharCode(...value.slice(offset, offset + 32768));
58
+ return btoa(binary);
59
+ }
60
+ function base64ToBytes(value) {
61
+ if ('string' != typeof value) throw new Error('Expected a base64 string.');
62
+ const binary = atob(value);
63
+ return Array.from(binary, (character)=>character.charCodeAt(0));
64
+ }
65
+ function transformValue(value, transform = 'identity') {
66
+ if ('byte-array-to-base64' === transform) return bytesToBase64(value);
67
+ if ('base64-to-byte-array' === transform) return base64ToBytes(value);
68
+ return cloneJson(value);
69
+ }
70
+ function encodeBase64Json(value) {
71
+ const bytes = new TextEncoder().encode(JSON.stringify(value));
72
+ let binary = '';
73
+ for(let offset = 0; offset < bytes.length; offset += 32768)binary += String.fromCharCode(...bytes.slice(offset, offset + 32768));
74
+ return btoa(binary);
75
+ }
76
+ function rebaseCssUrls(value, baseUrl) {
77
+ return value.replace(/url\(\s*(["']?)([^"')]+)\1\s*\)/giu, (match, quote, rawUrl)=>{
78
+ const candidate = rawUrl.trim();
79
+ if ('' === candidate || candidate.startsWith('#') || candidate.startsWith('data:') || candidate.startsWith('blob:')) return match;
80
+ const resolved = new URL(candidate, baseUrl).href;
81
+ return `url(${quote}${resolved}${quote})`;
82
+ });
83
+ }
84
+ function rebaseDocumentUrls(documentValue, entry) {
85
+ const resourceAttributes = [
86
+ [
87
+ "script[src]",
88
+ 'src'
89
+ ],
90
+ [
91
+ 'link[href]',
92
+ 'href'
93
+ ],
94
+ [
95
+ 'img[src]',
96
+ 'src'
97
+ ],
98
+ [
99
+ 'source[src]',
100
+ 'src'
101
+ ],
102
+ [
103
+ 'video[src]',
104
+ 'src'
105
+ ],
106
+ [
107
+ 'video[poster]',
108
+ 'poster'
109
+ ],
110
+ [
111
+ 'audio[src]',
112
+ 'src'
113
+ ],
114
+ [
115
+ 'track[src]',
116
+ 'src'
117
+ ],
118
+ [
119
+ 'input[src]',
120
+ 'src'
121
+ ]
122
+ ];
123
+ for (const [selector, attribute] of resourceAttributes)for (const element of documentValue.querySelectorAll(selector)){
124
+ const value = element.getAttribute(attribute);
125
+ if (value) element.setAttribute(attribute, new URL(value, entry).href);
126
+ }
127
+ for (const element of documentValue.querySelectorAll('[style]')){
128
+ const value = element.getAttribute('style');
129
+ if (value) element.setAttribute('style', rebaseCssUrls(value, entry));
130
+ }
131
+ for (const element of documentValue.querySelectorAll('style'))element.textContent = rebaseCssUrls(element.textContent ?? '', entry);
132
+ }
133
+ function normalizeOrigins(origins) {
134
+ return (origins ?? []).map((value)=>{
135
+ const url = new URL(value);
136
+ if ('https:' !== url.protocol || url.origin !== value || '' !== url.username || '' !== url.password) throw new Error('VS Code webview network entries must be exact HTTPS origins.');
137
+ return url.origin;
138
+ });
139
+ }
140
+ function normalizeSessionNamespace(value) {
141
+ if (null === value || 'object' != typeof value || Array.isArray(value)) return {};
142
+ return Object.fromEntries(Object.entries(value).filter((entry)=>'string' == typeof entry[1]));
143
+ }
144
+ async function waitForHostAuthority(context) {
145
+ if (context.hostAuthority.getSnapshot()) return;
146
+ await new Promise((resolve)=>{
147
+ const unsubscribe = context.hostAuthority.subscribe(()=>{
148
+ if (!context.hostAuthority.getSnapshot()) return;
149
+ unsubscribe();
150
+ resolve();
151
+ });
152
+ });
153
+ }
154
+ async function loadStoredState(options) {
155
+ if (!options) return null;
156
+ const entry = await sdk.storage.get({
157
+ namespace: options.namespace,
158
+ key: options.key
159
+ });
160
+ return {
161
+ value: null === entry.value ? cloneJson(options.initialValue) : entry.value,
162
+ revision: entry.revision
163
+ };
164
+ }
165
+ function bridgeBootstrapValue(options, state) {
166
+ if (!options.bootstrap) return null;
167
+ let value = cloneJson(options.bootstrap.value);
168
+ for (const binding of options.storage?.bootstrapBindings ?? []){
169
+ assertCanonicalPath(binding.statePath, 'bootstrap statePath');
170
+ assertCanonicalPath(binding.bootstrapPath, 'bootstrap bootstrapPath');
171
+ const stored = readPath(state?.value, binding.statePath);
172
+ if (void 0 !== stored) value = writePath(value, binding.bootstrapPath, transformValue(stored, binding.transform));
173
+ }
174
+ return value;
175
+ }
176
+ function injectBridgeHtml(source, entry, runtime, options, bootstrap, session, vscodeState) {
177
+ const documentValue = new DOMParser().parseFromString(source, 'text/html');
178
+ const head = documentValue.head ?? documentValue.documentElement.insertBefore(documentValue.createElement('head'), documentValue.body);
179
+ documentValue.querySelectorAll('base').forEach((node)=>node.remove());
180
+ documentValue.querySelectorAll('meta[http-equiv]').forEach((node)=>{
181
+ if (node.getAttribute('http-equiv')?.toLowerCase() === 'content-security-policy') node.remove();
182
+ });
183
+ rebaseDocumentUrls(documentValue, entry);
184
+ const policy = documentValue.createElement('meta');
185
+ policy.httpEquiv = 'Content-Security-Policy';
186
+ policy.content = "default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-src 'none'; worker-src 'self' blob:; object-src 'none'; base-uri 'none'; form-action 'none'";
187
+ const init = documentValue.createElement("script");
188
+ init.id = BRIDGE_INIT_ELEMENT_ID;
189
+ init.type = 'application/json';
190
+ init.textContent = JSON.stringify({
191
+ kind: BRIDGE_KIND,
192
+ version: BRIDGE_VERSION,
193
+ parentOrigin: window.location.origin,
194
+ assetOrigin: entry.origin,
195
+ assetBaseUrl: new URL('.', entry).href,
196
+ allowedOrigins: normalizeOrigins(options.network?.allowedOrigins),
197
+ session,
198
+ sessionEnabled: Boolean(options.session),
199
+ vscodeState
200
+ }).replaceAll('<', '\\u003c');
201
+ const runtimeScript = documentValue.createElement("script");
202
+ runtimeScript.src = runtime.href;
203
+ head.prepend(policy, init, runtimeScript);
204
+ if (options.bootstrap) {
205
+ const target = documentValue.querySelector(options.bootstrap.selector);
206
+ if (!target) throw new Error(`The VS Code webview bootstrap selector did not match: ${options.bootstrap.selector}`);
207
+ target.setAttribute(options.bootstrap.attribute, encodeBase64Json(bootstrap ?? null));
208
+ }
209
+ return `<!doctype html>\n${documentValue.documentElement.outerHTML}`;
210
+ }
211
+ function asBridgeEnvelope(value) {
212
+ if (null === value || 'object' != typeof value || Reflect.get(value, 'kind') !== BRIDGE_KIND || Reflect.get(value, 'version') !== BRIDGE_VERSION) return null;
213
+ const type = Reflect.get(value, 'type');
214
+ const method = Reflect.get(value, 'method');
215
+ if ('notification' !== type && 'request' !== type && 'response' !== type || 'string' != typeof method) return null;
216
+ return value;
217
+ }
218
+ function errorMessage(error) {
219
+ return error instanceof Error ? error.message : String(error);
220
+ }
221
+ function webviewHttpRequest(value, allowedOrigins) {
222
+ if (null === value || 'object' != typeof value || Array.isArray(value)) throw new Error('The webview supplied an invalid HTTP request.');
223
+ const method = Reflect.get(value, 'method');
224
+ const urlValue = Reflect.get(value, 'url');
225
+ if ('string' != typeof method || !HTTP_METHODS.has(method) || 'string' != typeof urlValue) throw new Error('The webview supplied an invalid HTTP request.');
226
+ const url = new URL(urlValue);
227
+ if ('https:' !== url.protocol || '' !== url.username || '' !== url.password || !allowedOrigins.has(url.origin)) throw new Error(`The webview HTTP origin is not declared: ${url.origin}`);
228
+ const rawHeaders = Reflect.get(value, 'headers');
229
+ if (void 0 !== rawHeaders && (!Array.isArray(rawHeaders) || rawHeaders.length > 128)) throw new Error('The webview supplied invalid HTTP headers.');
230
+ const headers = (rawHeaders ?? []).map((entry)=>{
231
+ if (null === entry || 'object' != typeof entry || 'string' != typeof Reflect.get(entry, 'name') || 'string' != typeof Reflect.get(entry, 'value')) throw new Error('The webview supplied invalid HTTP headers.');
232
+ const name = Reflect.get(entry, 'name');
233
+ const headerValue = Reflect.get(entry, 'value');
234
+ if (name.length > 128 || headerValue.length > 8192) throw new Error('The webview supplied invalid HTTP headers.');
235
+ new Headers([
236
+ [
237
+ name,
238
+ headerValue
239
+ ]
240
+ ]);
241
+ return {
242
+ name,
243
+ value: headerValue
244
+ };
245
+ });
246
+ const body = Reflect.get(value, 'body');
247
+ if (null != body && ('string' != typeof body || body.length > 10485760)) throw new Error('The webview supplied an invalid HTTP body.');
248
+ if (('GET' === method || 'HEAD' === method) && null != body) throw new Error(`The webview cannot send a body with ${method}.`);
249
+ return {
250
+ method,
251
+ url: url.href,
252
+ ...headers.length > 0 ? {
253
+ headers
254
+ } : {},
255
+ ...void 0 === body ? {} : {
256
+ body
257
+ },
258
+ timeoutMs: 30000,
259
+ responseBodyLimitBytes: 10485760,
260
+ followRedirects: false
261
+ };
262
+ }
263
+ function mountVsCodeWebview(container, context, options) {
264
+ const entry = resolvePackageAssetUrl(context, options.entryPath);
265
+ const runtime = resolvePackageAssetUrl(context, options.runtimePath);
266
+ const allowedOrigins = new Set(normalizeOrigins(options.network?.allowedOrigins));
267
+ const frame = document.createElement('iframe');
268
+ frame.title = options.title;
269
+ frame.loading = 'eager';
270
+ frame.referrerPolicy = 'no-referrer';
271
+ frame.setAttribute('sandbox', "allow-downloads allow-modals allow-scripts allow-same-origin");
272
+ frame.style.border = '0';
273
+ frame.style.display = 'block';
274
+ frame.style.height = '100%';
275
+ frame.style.width = '100%';
276
+ container.replaceChildren(frame);
277
+ let mounted = true;
278
+ let state = null;
279
+ let sessionValue = {};
280
+ let storageQueue = Promise.resolve();
281
+ let sessionQueue = Promise.resolve();
282
+ const reportError = (error)=>{
283
+ const normalized = error instanceof Error ? error : new Error(errorMessage(error));
284
+ if (options.onError) options.onError(normalized);
285
+ else window.dispatchEvent(new ErrorEvent('error', {
286
+ error: normalized,
287
+ message: normalized.message
288
+ }));
289
+ };
290
+ const send = (envelope)=>{
291
+ frame.contentWindow?.postMessage(envelope, window.location.origin);
292
+ };
293
+ const persistStateUpdate = (update)=>{
294
+ const storage = options.storage;
295
+ if (!storage || !state) return;
296
+ storageQueue = storageQueue.then(async ()=>{
297
+ if (!state) return;
298
+ for(let attempt = 0; attempt < 2; attempt += 1){
299
+ const current = state;
300
+ if (!current) return;
301
+ const next = update(current.value);
302
+ try {
303
+ const result = await sdk.storage.set({
304
+ namespace: storage.namespace,
305
+ key: storage.key,
306
+ expectedRevision: current.revision,
307
+ value: next
308
+ });
309
+ state = {
310
+ value: next,
311
+ revision: result.revision
312
+ };
313
+ return;
314
+ } catch (error) {
315
+ if (1 === attempt || !CONFLICT_PATTERN.test(errorMessage(error))) throw error;
316
+ state = await loadStoredState(storage);
317
+ }
318
+ }
319
+ });
320
+ storageQueue.catch(reportError);
321
+ };
322
+ const handleWebviewMessage = async (message)=>{
323
+ const storage = options.storage;
324
+ if (storage) {
325
+ const typePath = storage.messageTypePath ?? [
326
+ 'type'
327
+ ];
328
+ assertCanonicalPath(typePath, 'messageTypePath');
329
+ const messageType = readPath(message, typePath);
330
+ const binding = storage.messageBindings?.find((candidate)=>candidate.messageType === messageType);
331
+ if (binding) {
332
+ assertCanonicalPath(binding.messageValuePath, 'messageValuePath');
333
+ assertCanonicalPath(binding.statePath, 'statePath');
334
+ const incoming = readPath(message, binding.messageValuePath);
335
+ if (void 0 === incoming) throw new Error(`The ${binding.messageType} message did not contain its configured value.`);
336
+ const transformed = transformValue(incoming, binding.transform);
337
+ persistStateUpdate((value)=>writePath(value, binding.statePath, transformed));
338
+ }
339
+ }
340
+ await options.onMessage?.(message);
341
+ };
342
+ const handleSessionChange = (payload)=>{
343
+ const session = options.session;
344
+ if (!session) return;
345
+ const nextNamespace = normalizeSessionNamespace(payload);
346
+ sessionQueue = sessionQueue.then(async ()=>{
347
+ const current = await sdk.session.get();
348
+ sessionValue = current.value ?? {};
349
+ sessionValue = {
350
+ ...sessionValue,
351
+ [session.namespace]: nextNamespace
352
+ };
353
+ await sdk.session.set(sessionValue);
354
+ });
355
+ sessionQueue.catch(reportError);
356
+ };
357
+ const handleHttpRequest = async (envelope)=>{
358
+ if (!envelope.id) return;
359
+ try {
360
+ if (!sdk.http) throw new Error('The host does not provide sdk.http.');
361
+ const input = webviewHttpRequest(envelope.payload, allowedOrigins);
362
+ const response = await sdk.http.request(input);
363
+ send({
364
+ kind: BRIDGE_KIND,
365
+ version: BRIDGE_VERSION,
366
+ id: envelope.id,
367
+ type: 'response',
368
+ method: envelope.method,
369
+ payload: response
370
+ });
371
+ } catch (error) {
372
+ send({
373
+ kind: BRIDGE_KIND,
374
+ version: BRIDGE_VERSION,
375
+ id: envelope.id,
376
+ type: 'response',
377
+ method: envelope.method,
378
+ error: {
379
+ message: errorMessage(error)
380
+ }
381
+ });
382
+ }
383
+ };
384
+ const onWindowMessage = (event)=>{
385
+ if (!mounted || event.source !== frame.contentWindow || event.origin !== window.location.origin) return;
386
+ const envelope = asBridgeEnvelope(event.data);
387
+ if (!envelope) return;
388
+ if ('request' === envelope.type && 'http.request' === envelope.method) return void handleHttpRequest(envelope);
389
+ if ('notification' === envelope.type && 'vscode.postMessage' === envelope.method) return void handleWebviewMessage(envelope.payload).catch(reportError);
390
+ if ('notification' === envelope.type && 'vscode.setState' === envelope.method) {
391
+ const statePath = options.storage?.vscodeStatePath;
392
+ if (statePath) {
393
+ assertCanonicalPath(statePath, 'vscodeStatePath');
394
+ const nextValue = transformValue(envelope.payload);
395
+ persistStateUpdate((value)=>writePath(value, statePath, nextValue));
396
+ }
397
+ return;
398
+ }
399
+ if ('notification' === envelope.type && 'session.replace' === envelope.method) handleSessionChange(envelope.payload);
400
+ };
401
+ window.addEventListener('message', onWindowMessage);
402
+ (async ()=>{
403
+ try {
404
+ await waitForHostAuthority(context);
405
+ if (!mounted) return;
406
+ const [sourceResponse, loadedState, loadedSession] = await Promise.all([
407
+ fetch(entry),
408
+ loadStoredState(options.storage),
409
+ options.session ? sdk.session.get() : Promise.resolve(null)
410
+ ]);
411
+ if (!sourceResponse.ok) throw new Error(`The VS Code webview entry failed to load (${sourceResponse.status}).`);
412
+ state = loadedState;
413
+ sessionValue = loadedSession?.value ?? {};
414
+ const sessionNamespace = options.session ? normalizeSessionNamespace(sessionValue[options.session.namespace]) : {};
415
+ const vscodeState = options.storage?.vscodeStatePath ? readPath(state?.value, options.storage.vscodeStatePath) : null;
416
+ frame.srcdoc = injectBridgeHtml(await sourceResponse.text(), entry, runtime, options, bridgeBootstrapValue(options, state), sessionNamespace, vscodeState);
417
+ } catch (error) {
418
+ reportError(error);
419
+ }
420
+ })();
421
+ return {
422
+ postMessage (message) {
423
+ send({
424
+ kind: BRIDGE_KIND,
425
+ version: BRIDGE_VERSION,
426
+ type: 'notification',
427
+ method: 'host.postMessage',
428
+ payload: message
429
+ });
430
+ },
431
+ unmount () {
432
+ if (!mounted) return;
433
+ mounted = false;
434
+ window.removeEventListener('message', onWindowMessage);
435
+ frame.remove();
436
+ }
437
+ };
438
+ }
439
+ const tapVsCodeWebviewRuntimeSource = String.raw`(() => {
440
+ "use strict";
441
+ const KIND = "tap-vscode-webview-bridge";
442
+ const VERSION = 1;
443
+ const initElement = document.getElementById("tap-vscode-webview-bridge-init");
444
+ if (!initElement) throw new Error("The TAP VS Code webview bridge init is missing.");
445
+ const init = JSON.parse(initElement.textContent || "{}");
446
+ if (init.kind !== KIND || init.version !== VERSION) {
447
+ throw new Error("The TAP VS Code webview bridge init is invalid.");
448
+ }
449
+ Object.defineProperty(window, "__TAP_VSCODE_WEBVIEW_ASSET_BASE_URL__", {
450
+ configurable: false,
451
+ enumerable: false,
452
+ value: init.assetBaseUrl,
453
+ writable: false,
454
+ });
455
+ const pending = new Map();
456
+ let requestCounter = 0;
457
+ let vscodeState = init.vscodeState ?? null;
458
+ const post = (envelope) => window.parent.postMessage(
459
+ { kind: KIND, version: VERSION, ...envelope },
460
+ init.parentOrigin,
461
+ );
462
+ const request = (method, payload) => new Promise((resolve, reject) => {
463
+ const id = "tap-vscode-" + (++requestCounter) + "-" + Date.now();
464
+ const timeout = window.setTimeout(() => {
465
+ pending.delete(id);
466
+ reject(new Error("The TAP VS Code webview request timed out: " + method));
467
+ }, 120000);
468
+ pending.set(id, { resolve, reject, timeout });
469
+ post({ id, type: "request", method, payload });
470
+ });
471
+ const vscodeApi = Object.freeze({
472
+ getState: () => vscodeState,
473
+ setState: (value) => {
474
+ vscodeState = value;
475
+ post({ type: "notification", method: "vscode.setState", payload: value });
476
+ return value;
477
+ },
478
+ postMessage: (message) => {
479
+ post({ type: "notification", method: "vscode.postMessage", payload: message });
480
+ },
481
+ });
482
+ Object.defineProperty(window, "acquireVsCodeApi", {
483
+ configurable: false,
484
+ enumerable: true,
485
+ value: () => vscodeApi,
486
+ writable: false,
487
+ });
488
+
489
+ if (init.sessionEnabled) {
490
+ const values = new Map(Object.entries(init.session || {}));
491
+ const snapshot = () => Object.fromEntries(values);
492
+ const notify = () => post({
493
+ type: "notification",
494
+ method: "session.replace",
495
+ payload: snapshot(),
496
+ });
497
+ const facade = {
498
+ get length() { return values.size; },
499
+ clear() { values.clear(); notify(); },
500
+ getItem(key) {
501
+ const normalized = String(key);
502
+ return values.has(normalized) ? values.get(normalized) : null;
503
+ },
504
+ key(index) { return [...values.keys()][Number(index)] ?? null; },
505
+ removeItem(key) { values.delete(String(key)); notify(); },
506
+ setItem(key, value) {
507
+ values.set(String(key), String(value));
508
+ notify();
509
+ },
510
+ };
511
+ Object.defineProperty(window, "localStorage", {
512
+ configurable: false,
513
+ enumerable: true,
514
+ value: facade,
515
+ writable: false,
516
+ });
517
+ }
518
+
519
+ const nativeFetch = window.fetch.bind(window);
520
+ const allowedOrigins = new Set(init.allowedOrigins || []);
521
+ const decodeBase64 = (value) => {
522
+ const binary = atob(value);
523
+ return Uint8Array.from(binary, (character) => character.charCodeAt(0));
524
+ };
525
+ window.fetch = async (input, requestInit) => {
526
+ const original = new Request(input, requestInit);
527
+ const url = new URL(original.url);
528
+ if (url.origin === init.assetOrigin) return nativeFetch(original);
529
+ if (!allowedOrigins.has(url.origin)) {
530
+ throw new Error("The webview HTTP origin is not declared: " + url.origin);
531
+ }
532
+ const contentType = original.headers.get("content-type") || "";
533
+ if (
534
+ original.body &&
535
+ (contentType.startsWith("multipart/") ||
536
+ contentType === "application/octet-stream")
537
+ ) {
538
+ throw new Error("Binary and multipart webview request bodies are unsupported.");
539
+ }
540
+ const headers = [];
541
+ original.headers.forEach((value, name) => headers.push({ name, value }));
542
+ const response = await request("http.request", {
543
+ method: original.method,
544
+ url: original.url,
545
+ headers,
546
+ ...(original.method === "GET" || original.method === "HEAD"
547
+ ? {}
548
+ : { body: await original.clone().text() }),
549
+ timeoutMs: 30000,
550
+ responseBodyLimitBytes: 10 * 1024 * 1024,
551
+ followRedirects: false,
552
+ });
553
+ const responseHeaders = new Headers();
554
+ for (const header of response.headers || []) {
555
+ responseHeaders.append(header.name, header.value);
556
+ }
557
+ const body = response.bodyKind === "binary" && response.bodyBase64
558
+ ? decodeBase64(response.bodyBase64)
559
+ : response.bodyText;
560
+ return new Response(body, {
561
+ status: response.status,
562
+ statusText: response.statusText,
563
+ headers: responseHeaders,
564
+ });
565
+ };
566
+
567
+ window.addEventListener("message", (event) => {
568
+ if (
569
+ event.source !== window.parent ||
570
+ event.origin !== init.parentOrigin ||
571
+ !event.data ||
572
+ event.data.kind !== KIND ||
573
+ event.data.version !== VERSION
574
+ ) return;
575
+ const envelope = event.data;
576
+ if (envelope.type === "response" && envelope.id) {
577
+ const waiter = pending.get(envelope.id);
578
+ if (!waiter) return;
579
+ pending.delete(envelope.id);
580
+ window.clearTimeout(waiter.timeout);
581
+ if (envelope.error) waiter.reject(new Error(envelope.error.message));
582
+ else waiter.resolve(envelope.payload);
583
+ return;
584
+ }
585
+ if (envelope.type === "notification" && envelope.method === "host.postMessage") {
586
+ window.dispatchEvent(new MessageEvent("message", {
587
+ data: envelope.payload,
588
+ origin: init.parentOrigin,
589
+ source: window.parent,
590
+ }));
591
+ }
592
+ });
593
+ })();`;
594
+ export { mountVsCodeWebview, tapVsCodeWebviewRuntimeSource };
package/dist/web.d.ts CHANGED
@@ -136,6 +136,29 @@ declare type MiniAppAuthApi = {
136
136
  getUserProfile(): MiniAppMaybePromise<MiniAppUserProfile | null>;
137
137
  };
138
138
 
139
+ /**
140
+ * Read-only authorization introspection for custom surface behavior.
141
+ *
142
+ * The host rejects action IDs that are not declared by the exact mounted
143
+ * surface. A declared action resolves to the current dynamic allow/deny
144
+ * decision without executing the action.
145
+ */
146
+ declare type MiniAppAuthorizationApi = {
147
+ check(options: MiniAppAuthorizationCheckOptions): Promise<MiniAppAuthorizationCheckResult>;
148
+ };
149
+
150
+ /** Canonical autonomy requested for one descriptor-declared package action. */
151
+ declare type MiniAppAuthorizationAutonomy = 'listen' | 'plan' | 'do';
152
+
153
+ declare type MiniAppAuthorizationCheckOptions = {
154
+ actionId: string;
155
+ autonomy: MiniAppAuthorizationAutonomy;
156
+ };
157
+
158
+ declare type MiniAppAuthorizationCheckResult = {
159
+ allowed: boolean;
160
+ };
161
+
139
162
  /**
140
163
  * @capability miniapp-platform
141
164
  */
@@ -358,8 +381,10 @@ export declare type MiniAppPlatformApi = {
358
381
  navigation: {
359
382
  open(options: OpenNavigationOptions): void | Promise<void>;
360
383
  };
384
+ authorization: MiniAppAuthorizationApi;
361
385
  chat: MiniAppChatApi;
362
386
  storage: MiniAppStorageApi;
387
+ session: MiniAppSessionApi;
363
388
  presence: MiniAppPresenceApi;
364
389
  /** Desktop host capability; feature-detect before use on portable targets. */
365
390
  http?: MiniAppHttpApi;
@@ -519,6 +544,31 @@ declare type MiniAppReceiptTextAlignment = 'left' | 'center' | 'right';
519
544
 
520
545
  declare type MiniAppReceiptTextWeight = 'normal' | 'bold';
521
546
 
547
+ /**
548
+ * Secure session storage backed by the operating system credential store.
549
+ *
550
+ * The host derives the signed-in TAP account, active workspace, installation,
551
+ * and package from the authenticated frame. Channel, surface, document, and
552
+ * release are deliberately excluded, so every surface of one installed
553
+ * miniapp shares the same session only within a workspace and package updates
554
+ * retain it. Unlike host-managed HTTP credentials, values returned here enter
555
+ * miniapp JavaScript.
556
+ */
557
+ declare type MiniAppSessionApi = {
558
+ get(): MiniAppMaybePromise<MiniAppSessionEntry>;
559
+ set(value: MiniAppSessionValue): MiniAppMaybePromise<void>;
560
+ clear(): MiniAppMaybePromise<void>;
561
+ };
562
+
563
+ declare type MiniAppSessionEntry = {
564
+ /** Null means that no session value currently exists for this installation. */
565
+ value: MiniAppSessionValue | null;
566
+ };
567
+
568
+ declare type MiniAppSessionValue = {
569
+ [key: string]: MiniAppJsonValue;
570
+ };
571
+
522
572
  declare type MiniAppSpecialistApi = {
523
573
  joinToChannel(channelId: string, specialistId: string): MiniAppMaybePromise<string>;
524
574
  listWorkspace(workspaceId: string): MiniAppMaybePromise<MiniAppSpecialistSummary[]>;