@theaiplatform/miniapp-sdk 0.3.1 → 0.3.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.
package/README.md CHANGED
@@ -25,12 +25,13 @@ Start with the [Miniapp SDK documentation](https://docs.theaiplatform.app/miniap
25
25
  - `@theaiplatform/miniapp-sdk/ui/tailwind.css`
26
26
  - `@theaiplatform/miniapp-sdk/ui-components.json`
27
27
  - `@theaiplatform/miniapp-sdk/surface`
28
+ - `@theaiplatform/miniapp-sdk/vscode-webview`
28
29
  - `@theaiplatform/miniapp-sdk/config`
29
30
  - `@theaiplatform/miniapp-sdk/rspack`
30
31
  - `@theaiplatform/miniapp-sdk/testing/rstest`
31
32
  - `@theaiplatform/miniapp-sdk/config-schema.json`
32
33
 
33
- ## In-app E2E testing in 0.3.1
34
+ ## In-app E2E testing
34
35
 
35
36
  The `/testing/rstest` entry point extends Rstest's Playwright fixtures with a
36
37
  host-provided miniapp surface and exact TAP session provenance. Tests can assert
@@ -60,6 +61,32 @@ requests through host consent instead of browser `fetch`, and
60
61
  secrets remain in the host vault and are injected only by the native request
61
62
  authority.
62
63
 
64
+ ## Converted VS Code webviews in 0.3.3
65
+
66
+ The `/vscode-webview` entry point mounts a converted, static VS Code webview in
67
+ a sandboxed iframe while keeping platform authority in the outer federated
68
+ surface. Declarative message bindings persist document data through
69
+ `sdk.storage`; the synchronous `localStorage` compatibility facade is backed by
70
+ `sdk.session`, so standalone and channel surfaces share sign-in only for the
71
+ same account, workspace, installation, and package; and exact HTTPS origins are
72
+ mediated through `sdk.http`. The bridge does not expose the host transport, and
73
+ undeclared origins fail closed.
74
+
75
+ Conversion tools should emit `tapVsCodeWebviewRuntimeSource` beside the imported
76
+ assets and call `mountVsCodeWebview` from the generated surface. The SDK owns
77
+ this compatibility boundary so conversion recipes describe mappings rather
78
+ than introducing a second miniapp data plane. TAP's package CSP permits only
79
+ same-origin external scripts and forbids `<base>`, so converters must
80
+ externalize executable inline scripts. The bridge resolves document resource
81
+ URLs against the original entry before mounting and exposes the immutable
82
+ `window.__TAP_VSCODE_WEBVIEW_ASSET_BASE_URL__` value for reviewed replacements
83
+ of extension-specific asset-root placeholders.
84
+
85
+ In Surface Test Lab runs, `sdk.http` uses the run's exact-origin policy and the
86
+ same bounded native transport, while stored credentials remain unavailable.
87
+ That origin list governs host-mediated SDK requests; it is not a browser-wide
88
+ network sandbox for trusted Playwright test code.
89
+
63
90
  `sdk.printing` is an optional desktop-only receipt printer API. It discovers
64
91
  bounded machine-local printer metadata and host-supported 58 mm and 80 mm paper
65
92
  profiles, then accepts only bounded semantic version-1 receipt rows with
package/dist/index.d.ts CHANGED
@@ -348,6 +348,7 @@ export declare type MiniAppPlatformApi = {
348
348
  };
349
349
  chat: MiniAppChatApi;
350
350
  storage: MiniAppStorageApi;
351
+ session: MiniAppSessionApi;
351
352
  presence: MiniAppPresenceApi;
352
353
  /** Desktop host capability; feature-detect before use on portable targets. */
353
354
  http?: MiniAppHttpApi;
@@ -507,6 +508,31 @@ export declare type MiniAppReceiptTextAlignment = 'left' | 'center' | 'right';
507
508
 
508
509
  export declare type MiniAppReceiptTextWeight = 'normal' | 'bold';
509
510
 
511
+ /**
512
+ * Secure session storage backed by the operating system credential store.
513
+ *
514
+ * The host derives the signed-in TAP account, active workspace, installation,
515
+ * and package from the authenticated frame. Channel, surface, document, and
516
+ * release are deliberately excluded, so every surface of one installed
517
+ * miniapp shares the same session only within a workspace and package updates
518
+ * retain it. Unlike host-managed HTTP credentials, values returned here enter
519
+ * miniapp JavaScript.
520
+ */
521
+ export declare type MiniAppSessionApi = {
522
+ get(): MiniAppMaybePromise<MiniAppSessionEntry>;
523
+ set(value: MiniAppSessionValue): MiniAppMaybePromise<void>;
524
+ clear(): MiniAppMaybePromise<void>;
525
+ };
526
+
527
+ export declare type MiniAppSessionEntry = {
528
+ /** Null means that no session value currently exists for this installation. */
529
+ value: MiniAppSessionValue | null;
530
+ };
531
+
532
+ export declare type MiniAppSessionValue = {
533
+ [key: string]: MiniAppJsonValue;
534
+ };
535
+
510
536
  export declare type MiniAppSpecialistApi = {
511
537
  joinToChannel(channelId: string, specialistId: string): MiniAppMaybePromise<string>;
512
538
  listWorkspace(workspaceId: string): MiniAppMaybePromise<MiniAppSpecialistSummary[]>;
@@ -1,3 +1,4 @@
1
+ import type { Format } from 'ajv';
1
2
  import type { LibConfig } from '@rslib/core';
2
3
  import { ModuleFederationOptions } from '@module-federation/rsbuild-plugin';
3
4
  import { RsbuildPlugin } from '@rsbuild/core';
@@ -29,8 +30,15 @@ declare type Config = {
29
30
 
30
31
  export declare const pluginTap: (options?: Config) => RsbuildPlugin;
31
32
 
33
+ /** Registers the canonical JSON Schema formats used by TAP manifests. */
34
+ export declare const registerTapManifestAjvFormats: <Registry extends TapManifestAjvFormatRegistry>(ajv: Registry) => Registry;
35
+
32
36
  export declare const tapLib: (options?: Config) => LibConfig;
33
37
 
38
+ declare interface TapManifestAjvFormatRegistry {
39
+ addFormat(name: string, format: Format): unknown;
40
+ }
41
+
34
42
  /** Emitted-package scan used to reject machine-local build artifacts. */
35
43
  export declare type TapPackageArtifactPortabilityOptions = {
36
44
  output: string;
@@ -320,7 +320,7 @@ const TAP_FEDERATED_SURFACE_HTML = `<!doctype html>
320
320
  <head>
321
321
  <meta charset="utf-8">
322
322
  <meta name="viewport" content="width=device-width,initial-scale=1">
323
- <meta http-equiv="Content-Security-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 'self'; worker-src 'self' blob:; object-src 'none'; base-uri 'none'; form-action 'none'">
323
+ <meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-src 'self'; worker-src 'self' blob:; object-src 'none'; base-uri 'none'; form-action 'none'">
324
324
  <title>TAP mini app</title>
325
325
  <style>html,body,#tap-root{height:100%;width:100%;margin:0}body{overflow:hidden;background:transparent}#tap-error{box-sizing:border-box;display:none;min-height:100%;padding:24px;font:14px/1.5 system-ui,sans-serif;color:#b42318;background:#fff}</style>
326
326
  </head>
@@ -452,6 +452,7 @@ const MAX_CHECKPOINT_JSON_BYTES = 32 * 1024;
452
452
  const MAX_STORAGE_NAMESPACE_CHARS = 128;
453
453
  const MAX_STORAGE_KEY_CHARS = 512;
454
454
  const MAX_STORAGE_JSON_BYTES = 5 * 1024 * 1024;
455
+ const MAX_SESSION_JSON_BYTES = 1_800;
455
456
  const MAX_PRESENCE_ROOM_CHARS = 256;
456
457
  const MAX_PRESENCE_JSON_BYTES = 16 * 1024;
457
458
  const MAX_HTTP_REQUEST_JSON_BYTES = 11 * 1024 * 1024;
@@ -736,6 +737,26 @@ const storage = Object.freeze({
736
737
  return invokeSdkHostAction('tap.platform.storage.delete', options, 'app.storage.delete');
737
738
  },
738
739
  });
740
+ const session = Object.freeze({
741
+ get() {
742
+ return invokeSdkHostAction('tap.platform.session.get', {}, 'app.session.get');
743
+ },
744
+ set(value) {
745
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
746
+ throw new Error('app.session.set requires a JSON object.');
747
+ }
748
+ checkedPlatformJson(value, MAX_SESSION_JSON_BYTES, 'app.session.set value');
749
+ return invokeSdkHostAction(
750
+ 'tap.platform.session.set',
751
+ { value },
752
+ 'app.session.set',
753
+ 12000,
754
+ );
755
+ },
756
+ clear() {
757
+ return invokeSdkHostAction('tap.platform.session.clear', {}, 'app.session.clear');
758
+ },
759
+ });
739
760
  const checkedPresenceAddress = (options, label) => {
740
761
  requireOptionsObject(options, label);
741
762
  if (!validPlatformPartition(options.namespace, MAX_STORAGE_NAMESPACE_CHARS)) {
@@ -1692,6 +1713,7 @@ const miniappSdk = Object.freeze({
1692
1713
  navigation,
1693
1714
  chat: miniappChat,
1694
1715
  storage,
1716
+ session,
1695
1717
  presence,
1696
1718
  http,
1697
1719
  credentials,
@@ -1994,27 +2016,30 @@ const isUrl = (value)=>{
1994
2016
  return false;
1995
2017
  }
1996
2018
  };
2019
+ const registerTapManifestAjvFormats = (ajv)=>{
2020
+ ajv.addFormat('uri', {
2021
+ type: 'string',
2022
+ validate: isUrl
2023
+ });
2024
+ ajv.addFormat('uint8', {
2025
+ type: 'number',
2026
+ validate: (value)=>Number.isInteger(value) && value >= 0 && value <= 255
2027
+ });
2028
+ ajv.addFormat('uint16', {
2029
+ type: 'number',
2030
+ validate: (value)=>Number.isInteger(value) && value >= 0 && value <= 65535
2031
+ });
2032
+ ajv.addFormat('uint64', {
2033
+ type: 'number',
2034
+ validate: (value)=>Number.isSafeInteger(value) && value >= 0
2035
+ });
2036
+ return ajv;
2037
+ };
1997
2038
  const getSchemaValidator = async ()=>{
1998
2039
  if (!schemaValidator) {
1999
- const ajv = new __rspack_external_ajv_dist_2020_js_e85899db["default"]({
2040
+ const ajv = registerTapManifestAjvFormats(new __rspack_external_ajv_dist_2020_js_e85899db["default"]({
2000
2041
  allErrors: true
2001
- });
2002
- ajv.addFormat('uri', {
2003
- type: 'string',
2004
- validate: isUrl
2005
- });
2006
- ajv.addFormat('uint8', {
2007
- type: 'number',
2008
- validate: (value)=>Number.isInteger(value) && value >= 0 && value <= 255
2009
- });
2010
- ajv.addFormat('uint16', {
2011
- type: 'number',
2012
- validate: (value)=>Number.isInteger(value) && value >= 0 && value <= 65535
2013
- });
2014
- ajv.addFormat('uint64', {
2015
- type: 'number',
2016
- validate: (value)=>Number.isSafeInteger(value) && value >= 0
2017
- });
2042
+ }));
2018
2043
  schemaValidator = ajv.compile(config_schema_namespaceObject);
2019
2044
  }
2020
2045
  return schemaValidator;
@@ -3512,4 +3537,4 @@ const tapLib = (options = {})=>{
3512
3537
  ]
3513
3538
  };
3514
3539
  };
3515
- export { assembleTapPackage, assertPortableTapPackageArtifacts, pluginTap, tapLib };
3540
+ export { assembleTapPackage, assertPortableTapPackageArtifacts, pluginTap, registerTapManifestAjvFormats, tapLib };
package/dist/sdk.d.ts CHANGED
@@ -319,6 +319,7 @@ export declare type MiniAppPlatformApi = {
319
319
  };
320
320
  chat: MiniAppChatApi;
321
321
  storage: MiniAppStorageApi;
322
+ session: MiniAppSessionApi;
322
323
  presence: MiniAppPresenceApi;
323
324
  /** Desktop host capability; feature-detect before use on portable targets. */
324
325
  http?: MiniAppHttpApi;
@@ -478,6 +479,31 @@ export declare type MiniAppReceiptTextAlignment = 'left' | 'center' | 'right';
478
479
 
479
480
  export declare type MiniAppReceiptTextWeight = 'normal' | 'bold';
480
481
 
482
+ /**
483
+ * Secure session storage backed by the operating system credential store.
484
+ *
485
+ * The host derives the signed-in TAP account, active workspace, installation,
486
+ * and package from the authenticated frame. Channel, surface, document, and
487
+ * release are deliberately excluded, so every surface of one installed
488
+ * miniapp shares the same session only within a workspace and package updates
489
+ * retain it. Unlike host-managed HTTP credentials, values returned here enter
490
+ * miniapp JavaScript.
491
+ */
492
+ export declare type MiniAppSessionApi = {
493
+ get(): MiniAppMaybePromise<MiniAppSessionEntry>;
494
+ set(value: MiniAppSessionValue): MiniAppMaybePromise<void>;
495
+ clear(): MiniAppMaybePromise<void>;
496
+ };
497
+
498
+ export declare type MiniAppSessionEntry = {
499
+ /** Null means that no session value currently exists for this installation. */
500
+ value: MiniAppSessionValue | null;
501
+ };
502
+
503
+ export declare type MiniAppSessionValue = {
504
+ [key: string]: MiniAppJsonValue;
505
+ };
506
+
481
507
  export declare type MiniAppSpecialistApi = {
482
508
  joinToChannel(channelId: string, specialistId: string): MiniAppMaybePromise<string>;
483
509
  listWorkspace(workspaceId: string): MiniAppMaybePromise<MiniAppSpecialistSummary[]>;
@@ -1,31 +1,33 @@
1
1
  import { afterAll } from '@rstest/playwright';
2
- import { afterEach } from '@rstest/playwright';
3
- import { APIRequestContext } from 'playwright';
4
2
  import { beforeAll } from '@rstest/playwright';
5
- import { beforeEach } from '@rstest/playwright';
6
3
  import type { Browser } from 'playwright';
7
4
  import type { BrowserContext } from 'playwright';
8
5
  import { describe } from '@rstest/playwright';
9
6
  import { expect } from '@rstest/playwright';
10
7
  import type { FrameLocator } from 'playwright';
11
8
  import type { Page } from 'playwright';
12
- import { PlaywrightOptions } from '@rstest/playwright';
13
- import { PlaywrightServe } from '@rstest/playwright';
14
- import { PlaywrightTest } from '@rstest/playwright';
9
+ import type { PlaywrightFixtures } from '@rstest/playwright';
10
+ import type { PlaywrightTest } from '@rstest/playwright';
11
+ import type { TestContext } from '@rstest/core';
12
+ import type { TestOptions } from '@rstest/core';
15
13
  import { z } from 'zod';
16
14
 
17
15
  export { afterAll }
18
16
 
19
- export { afterEach }
17
+ export declare const afterEach: TapAfterEachHook;
20
18
 
21
19
  export { beforeAll }
22
20
 
23
- export { beforeEach }
21
+ export declare const beforeEach: TapBeforeEachHook;
24
22
 
25
23
  export { describe }
26
24
 
27
25
  export { expect }
28
26
 
27
+ declare type MergeContext<ExtraContext, FixturesContext> = {
28
+ [Key in keyof FixturesContext | keyof ExtraContext]: Key extends keyof FixturesContext ? FixturesContext[Key] : Key extends keyof ExtraContext ? ExtraContext[Key] : never;
29
+ };
30
+
29
31
  /** Validate a driver session before any endpoint or provenance field is used. */
30
32
  export declare function parseTapMiniappTestSession(value: unknown): TapMiniappTestSession;
31
33
 
@@ -60,6 +62,16 @@ declare const sessionSchema: z.ZodObject<{
60
62
  testBundleDigest: z.ZodString;
61
63
  }, z.core.$strip>;
62
64
 
65
+ declare type TapAfterEachCallback<ExtraContext> = (context: TapHookContext<ExtraContext>) => void | Promise<void>;
66
+
67
+ declare type TapAfterEachHook<ExtraContext = TapRstestFixtures> = (callback: TapAfterEachCallback<ExtraContext>, timeout?: number) => void;
68
+
69
+ declare type TapBeforeEachCallback<ExtraContext> = (context: TapHookContext<ExtraContext>) => void | TapAfterEachCallback<ExtraContext> | Promise<void | TapAfterEachCallback<ExtraContext>>;
70
+
71
+ declare type TapBeforeEachHook<ExtraContext = TapRstestFixtures> = (callback: TapBeforeEachCallback<ExtraContext>, timeout?: number) => void;
72
+
73
+ declare type TapHookContext<ExtraContext> = TestContext & ExtraContext;
74
+
63
75
  export declare type TapMiniappTestControl = {
64
76
  /** Restore the run's declared fixture and permission scenario. */
65
77
  reset: () => Promise<void>;
@@ -68,6 +80,7 @@ export declare type TapMiniappTestControl = {
68
80
  };
69
81
 
70
82
  export declare type TapMiniappTestFixture = Readonly<Pick<TapMiniappTestSession, 'channelId' | 'dataScope' | 'installationId' | 'localGeneration' | 'mode' | 'packageId' | 'permissionScenario' | 'releaseDigest' | 'runId' | 'sourceDigest' | 'surfaceId' | 'surfaceAssetOrigin' | 'testBundleDigest' | 'workspaceId'> & {
83
+ /** Origins authorized for host-mediated SDK HTTP, not browser-wide egress. */
71
84
  readonly allowedNetworkOrigins: readonly string[];
72
85
  readonly credentialAliases: readonly string[];
73
86
  control: TapMiniappTestControl;
@@ -85,47 +98,22 @@ export declare type TapRstestFixtures = {
85
98
  tap: TapMiniappTestFixture;
86
99
  };
87
100
 
101
+ declare type TapRstestTest<ExtraContext = TapRstestFixtures> = Omit<PlaywrightTest<ExtraContext>, 'afterEach' | 'beforeEach' | 'extend'> & {
102
+ (description: string, callback?: (context: TapHookContext<ExtraContext>) => void | Promise<void>, timeout?: number): void;
103
+ (description: string, options: TestOptions, callback?: (context: TapHookContext<ExtraContext>) => void | Promise<void>): void;
104
+ afterEach: TapAfterEachHook<ExtraContext>;
105
+ beforeEach: TapBeforeEachHook<ExtraContext>;
106
+ extend: <FixturesContext extends Record<string, unknown> = Record<never, never>>(fixtures: PlaywrightFixtures<FixturesContext, ExtraContext>) => TapRstestTest<MergeContext<ExtraContext, FixturesContext>>;
107
+ };
108
+
109
+ /** Rstest's per-test context plus TAP's authorized browser and run fixtures. */
110
+ export declare type TapTestContext = TestContext & TapRstestFixtures;
111
+
88
112
  /**
89
113
  * Rstest's Playwright test API with TAP-owned browser, page, iframe, and run
90
114
  * fixtures. The upstream runner still owns scheduling, assertions, hooks, and
91
115
  * reporting; TAP owns the privileged browser/session boundary.
92
116
  */
93
- export declare const test: PlaywrightTest< {
94
- browser: Browser;
95
- context: BrowserContext;
96
- request: APIRequestContext;
97
- surface: FrameLocator;
98
- page: Page;
99
- tap: Readonly<Pick<{
100
- schemaVersion: 1;
101
- runId: string;
102
- workspaceId: string;
103
- channelId: string;
104
- packageId: string;
105
- installationId: string;
106
- mode: "surface" | "live-tap";
107
- dataScope: "fixture" | "selected-channel";
108
- permissionScenario: string;
109
- surfaceId: string;
110
- surfaceSelector: string;
111
- surfaceAssetOrigin: string;
112
- pageUrlPrefix: string;
113
- cdpEndpoint: string;
114
- cdpAuthorization: string;
115
- artifactDirectory: string;
116
- allowedNetworkOrigins: string[];
117
- credentialAliases: string[];
118
- sourceDigest: string;
119
- testBundleDigest: string;
120
- releaseDigest?: string | undefined;
121
- localGeneration?: number | undefined;
122
- }, "packageId" | "installationId" | "workspaceId" | "channelId" | "mode" | "runId" | "dataScope" | "permissionScenario" | "surfaceId" | "surfaceAssetOrigin" | "releaseDigest" | "localGeneration" | "sourceDigest" | "testBundleDigest"> & {
123
- readonly allowedNetworkOrigins: readonly string[];
124
- readonly credentialAliases: readonly string[];
125
- control: TapMiniappTestControl;
126
- }>;
127
- playwright: PlaywrightOptions;
128
- serve: PlaywrightServe;
129
- }>;
117
+ export declare const test: TapRstestTest;
130
118
 
131
119
  export { }
@@ -114,6 +114,97 @@ function fixtureMetadata(session, page) {
114
114
  function safeArtifactSegment(value) {
115
115
  return value.replaceAll(/[^a-zA-Z0-9._-]/g, '-').slice(0, 160);
116
116
  }
117
+ const HTTP_PROTOCOLS = new Set([
118
+ 'http:',
119
+ 'https:'
120
+ ]);
121
+ const SAFE_INTERNAL_PROTOCOLS = new Set([
122
+ 'about:',
123
+ 'blob:',
124
+ 'data:'
125
+ ]);
126
+ function explicitHttpOrigin(value, label) {
127
+ const url = new URL(value);
128
+ if (!HTTP_PROTOCOLS.has(url.protocol)) throw new Error(`${label} must use HTTP or HTTPS.`);
129
+ return url.origin;
130
+ }
131
+ function allowedHttpOrigins(session) {
132
+ return new Set([
133
+ explicitHttpOrigin(session.pageUrlPrefix, 'TAP host page URL prefix'),
134
+ explicitHttpOrigin(session.surfaceAssetOrigin, 'Surface asset origin'),
135
+ ...session.allowedNetworkOrigins.map((value)=>explicitHttpOrigin(value, 'Allowed network origin'))
136
+ ]);
137
+ }
138
+ function classifyRequestUrl(value, allowedOrigins) {
139
+ let url;
140
+ try {
141
+ url = new URL(value);
142
+ } catch {
143
+ return {
144
+ action: 'abort',
145
+ reason: 'malformed-url'
146
+ };
147
+ }
148
+ if (SAFE_INTERNAL_PROTOCOLS.has(url.protocol)) return {
149
+ action: 'continue',
150
+ reason: 'safe-internal'
151
+ };
152
+ if ('file:' === url.protocol) return {
153
+ action: 'abort',
154
+ reason: 'file-scheme',
155
+ target: 'file:'
156
+ };
157
+ if (!HTTP_PROTOCOLS.has(url.protocol)) return {
158
+ action: 'abort',
159
+ reason: 'unsupported-scheme',
160
+ target: url.protocol.slice(0, 32)
161
+ };
162
+ if (!allowedOrigins.has(url.origin)) return {
163
+ action: 'abort',
164
+ reason: 'origin-not-allowed',
165
+ target: url.origin.slice(0, 256)
166
+ };
167
+ return {
168
+ action: 'continue',
169
+ reason: 'explicit-http-origin'
170
+ };
171
+ }
172
+ function classifyWebSocketUrl(value, allowedOrigins) {
173
+ let url;
174
+ try {
175
+ url = new URL(value);
176
+ } catch {
177
+ return {
178
+ action: 'abort',
179
+ reason: 'malformed-url'
180
+ };
181
+ }
182
+ if ('ws:' !== url.protocol && 'wss:' !== url.protocol) return {
183
+ action: 'abort',
184
+ reason: 'file:' === url.protocol ? 'file-scheme' : 'unsupported-scheme',
185
+ target: url.protocol.slice(0, 32)
186
+ };
187
+ const diagnosticOrigin = url.origin.slice(0, 256);
188
+ url.protocol = 'ws:' === url.protocol ? 'http:' : 'https:';
189
+ if (!allowedOrigins.has(url.origin)) return {
190
+ action: 'abort',
191
+ reason: 'origin-not-allowed',
192
+ target: diagnosticOrigin
193
+ };
194
+ return {
195
+ action: 'continue',
196
+ reason: 'explicit-http-origin'
197
+ };
198
+ }
199
+ function reportUnexpectedWebSocket(webSocket, allowedOrigins) {
200
+ const decision = classifyWebSocketUrl(webSocket.url(), allowedOrigins);
201
+ if ('continue' === decision.action) return;
202
+ const target = decision.target ? ` (${decision.target})` : '';
203
+ process.emitWarning(`TAP Test Lab observed a WebSocket outside the run's SDK HTTP origin set: ${decision.reason}${target}. This check is diagnostic only; TAP does not yet provide native browser-wide egress enforcement.`, {
204
+ code: 'TAP_MINIAPP_TEST_NETWORK_DIAGNOSTIC',
205
+ type: 'Warning'
206
+ });
207
+ }
117
208
  const test = __rspack_external__rstest_playwright_5734ff29.test.extend({
118
209
  browser: async (_context, use)=>{
119
210
  const session = await loadSession();
@@ -132,70 +223,80 @@ const test = __rspack_external__rstest_playwright_5734ff29.test.extend({
132
223
  const context = contexts[0];
133
224
  if (!context) throw new Error('TAP Browser Driver context selection failed');
134
225
  const hostPage = selectPage(context, session);
135
- const runtimeOrigins = new Set(hostPage.frames().map((frame)=>originOf(frame.url())).filter((origin)=>void 0 !== origin));
136
- const allowedOrigins = new Set([
137
- ...runtimeOrigins,
138
- new URL(session.surfaceAssetOrigin).origin,
139
- ...session.allowedNetworkOrigins.map((value)=>new URL(value).origin)
140
- ]);
141
- const enforceNetworkPolicy = async (route)=>{
142
- const url = route.request().url();
143
- const origin = originOf(url);
144
- if (void 0 === origin || allowedOrigins.has(origin)) return void await route.continue();
226
+ const allowedOrigins = allowedHttpOrigins(session);
227
+ const enforceDiagnosticNetworkPolicy = async (route)=>{
228
+ const decision = classifyRequestUrl(route.request().url(), allowedOrigins);
229
+ if ('continue' === decision.action) return void await route.continue();
145
230
  await route.abort('blockedbyclient');
146
231
  };
147
- await context.route('**/*', enforceNetworkPolicy);
148
- const traceDirectory = (0, __rspack_external_node_path_806ed179.join)(session.artifactDirectory, 'traces', safeArtifactSegment(task.id));
149
- await (0, __rspack_external_node_fs_promises_3b710708.mkdir)(traceDirectory, {
150
- recursive: true
151
- });
152
- await context.tracing.start({
153
- screenshots: true,
154
- snapshots: true,
155
- sources: true
156
- });
232
+ const diagnoseWebSocket = (webSocket)=>reportUnexpectedWebSocket(webSocket, allowedOrigins);
233
+ await context.route('**/*', enforceDiagnosticNetworkPolicy);
234
+ hostPage.on('websocket', diagnoseWebSocket);
157
235
  try {
158
- await use(context);
159
- } finally{
160
- await context.tracing.stop({
161
- path: (0, __rspack_external_node_path_806ed179.join)(traceDirectory, 'trace.zip')
236
+ const traceDirectory = (0, __rspack_external_node_path_806ed179.join)(session.artifactDirectory, 'traces', safeArtifactSegment(task.id));
237
+ await (0, __rspack_external_node_fs_promises_3b710708.mkdir)(traceDirectory, {
238
+ recursive: true
162
239
  });
163
- const traceSummary = {
164
- schemaVersion: 1,
165
- runId: session.runId,
166
- taskId: task.id,
167
- mode: session.mode,
168
- dataScope: session.dataScope,
169
- packageId: session.packageId,
170
- surfaceId: session.surfaceId,
171
- sourceDigest: session.sourceDigest,
172
- testBundleDigest: session.testBundleDigest,
173
- tracePath: 'trace.zip',
174
- redactions: [
175
- 'authorization headers',
176
- 'cookies',
177
- 'credential values',
178
- 'network bodies'
179
- ]
180
- };
181
- await Promise.all([
182
- (0, __rspack_external_node_fs_promises_3b710708.writeFile)((0, __rspack_external_node_path_806ed179.join)(traceDirectory, 'trace-summary.json'), `${JSON.stringify(traceSummary, void 0, 2)}\n`, 'utf8'),
183
- (0, __rspack_external_node_fs_promises_3b710708.writeFile)((0, __rspack_external_node_path_806ed179.join)(traceDirectory, 'debug.md'), [
184
- '# Miniapp test trace',
185
- '',
186
- `- Run: \`${session.runId}\``,
187
- `- Rstest task: \`${task.id}\``,
188
- `- Mode: \`${session.mode}\``,
189
- `- Data scope: \`${session.dataScope}\``,
190
- `- Surface: \`${session.surfaceId}\``,
191
- `- Source digest: \`${session.sourceDigest}\``,
192
- `- Test bundle digest: \`${session.testBundleDigest}\``,
193
- '',
194
- 'Open `trace.zip` from The AI Platform Test Lab for the DOM, screenshots, console, and network timeline. The final assertion outcome is authoritative in `report.json` and `report.md`.',
195
- ''
196
- ].join('\n'), 'utf8')
197
- ]);
198
- await context.unroute('**/*', enforceNetworkPolicy);
240
+ await context.tracing.start({
241
+ screenshots: true,
242
+ snapshots: true,
243
+ sources: true
244
+ });
245
+ try {
246
+ await use(context);
247
+ } finally{
248
+ await context.tracing.stop({
249
+ path: (0, __rspack_external_node_path_806ed179.join)(traceDirectory, 'trace.zip')
250
+ });
251
+ const traceSummary = {
252
+ schemaVersion: 2,
253
+ runId: session.runId,
254
+ taskId: task.id,
255
+ mode: session.mode,
256
+ dataScope: session.dataScope,
257
+ packageId: session.packageId,
258
+ surfaceId: session.surfaceId,
259
+ sourceDigest: session.sourceDigest,
260
+ testBundleDigest: session.testBundleDigest,
261
+ networkPolicyRole: 'diagnostic-only',
262
+ tracePath: 'trace.zip',
263
+ traceRedacted: false,
264
+ traceSensitivity: 'raw-sensitive',
265
+ traceAccessScope: 'channel-only',
266
+ traceMayContain: [
267
+ 'authorization headers',
268
+ 'cookies',
269
+ 'credential values',
270
+ 'network request and response bodies',
271
+ 'DOM snapshots',
272
+ 'screenshots',
273
+ 'test sources'
274
+ ]
275
+ };
276
+ await Promise.all([
277
+ (0, __rspack_external_node_fs_promises_3b710708.writeFile)((0, __rspack_external_node_path_806ed179.join)(traceDirectory, 'trace-summary.json'), `${JSON.stringify(traceSummary, void 0, 2)}\n`, 'utf8'),
278
+ (0, __rspack_external_node_fs_promises_3b710708.writeFile)((0, __rspack_external_node_path_806ed179.join)(traceDirectory, 'debug.md'), [
279
+ '# Miniapp test trace',
280
+ '',
281
+ `- Run: \`${session.runId}\``,
282
+ `- Rstest task: \`${task.id}\``,
283
+ `- Mode: \`${session.mode}\``,
284
+ `- Data scope: \`${session.dataScope}\``,
285
+ `- Surface: \`${session.surfaceId}\``,
286
+ `- Source digest: \`${session.sourceDigest}\``,
287
+ `- Test bundle digest: \`${session.testBundleDigest}\``,
288
+ '- Browser network routing: diagnostic-only; not native egress enforcement',
289
+ '',
290
+ '`trace.zip` is raw, sensitive, channel-only evidence. It is not redacted and may contain authorization headers, cookies, credential values, network bodies, DOM snapshots, screenshots, and test source.',
291
+ '',
292
+ 'Open the trace from The AI Platform Test Lab for debugging. Do not publish it or attach it to specialist context. The final assertion outcome is authoritative in `report.json` and `report.md`.',
293
+ ''
294
+ ].join('\n'), 'utf8')
295
+ ]);
296
+ }
297
+ } finally{
298
+ hostPage.removeListener('websocket', diagnoseWebSocket);
299
+ await context.unroute('**/*', enforceDiagnosticNetworkPolicy);
199
300
  }
200
301
  },
201
302
  page: async ({ context }, use)=>{
@@ -214,28 +315,10 @@ const test = __rspack_external__rstest_playwright_5734ff29.test.extend({
214
315
  await use(fixtureMetadata(await loadSession(), page));
215
316
  }
216
317
  });
217
- function originOf(value) {
218
- try {
219
- const url = new URL(value);
220
- if (matchesNonNetworkScheme(url.protocol)) return;
221
- return url.origin;
222
- } catch {
223
- return;
224
- }
225
- }
226
- function matchesNonNetworkScheme(protocol) {
227
- return NON_NETWORK_PROTOCOLS.has(protocol);
228
- }
229
- const NON_NETWORK_PROTOCOLS = new Set([
230
- 'about:',
231
- 'blob:',
232
- 'data:',
233
- 'file:'
234
- ]);
318
+ const afterEach = __rspack_external__rstest_playwright_5734ff29.afterEach;
319
+ const beforeEach = __rspack_external__rstest_playwright_5734ff29.beforeEach;
235
320
  var afterAll = __rspack_external__rstest_playwright_5734ff29.afterAll;
236
- var afterEach = __rspack_external__rstest_playwright_5734ff29.afterEach;
237
321
  var beforeAll = __rspack_external__rstest_playwright_5734ff29.beforeAll;
238
- var beforeEach = __rspack_external__rstest_playwright_5734ff29.beforeEach;
239
322
  var describe = __rspack_external__rstest_playwright_5734ff29.describe;
240
323
  var expect = __rspack_external__rstest_playwright_5734ff29.expect;
241
324
  export { afterAll, afterEach, beforeAll, beforeEach, describe, expect, parseTapMiniappTestSession, test };