@anapaya/webscion-poc 0.0.1

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 ADDED
@@ -0,0 +1,107 @@
1
+ # @anapaya/webscion-poc
2
+
3
+ Proof-of-concept npm package that establishes the architecture for the future WebScion SDK,
4
+ the a browser-based SCION networking stack.
5
+
6
+ ## Architecture
7
+
8
+ ```text
9
+ ┌─────────────────────────────────────────────────────┐
10
+ │ Angular App │
11
+ │ ↓ HttpInterceptor │
12
+ │ ScionStack.fetch() │
13
+ │ ↓ postMessage |
14
+ │ ┌────────────────────────────────────────────────┐ │
15
+ │ │ Web Worker │ │
16
+ │ │ │ │
17
+ │ │ Dispatches original request via scion │ │
18
+ │ │ (not yet implemented) │ │
19
+ │ └────────────────────────────────────────────────┘ │
20
+ └─────────────────────────────────────────────────────┘
21
+ ```
22
+
23
+ In a future iteration, the webworker will select an appropriate path for the request and dispatch it
24
+ via the SCION network.
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ npm install @anapaya/webscion-poc
30
+ ```
31
+
32
+ ## Usage
33
+
34
+ ### Explicit Usage
35
+
36
+ ```ts
37
+ import { ScionStack } from "@anapaya/webscion-poc";
38
+
39
+ const stack = new ScionStack();
40
+
41
+ const response = await stack.fetch("https://example.com/api/data", {
42
+ method: "POST",
43
+ headers: { "Content-Type": "application/json" },
44
+ body: JSON.stringify({ key: "value" }),
45
+ });
46
+
47
+ const data = await response.json();
48
+
49
+ ```
50
+
51
+ ### Angular Interceptor
52
+
53
+ ```ts
54
+ import { ScionStack } from "@anapaya/webscion-poc";
55
+ import { provideScionInterceptor } from "@anapaya/webscion-poc/angular";
56
+
57
+ const scionStack = new ScionStack();
58
+
59
+ // In your app config or module providers:
60
+ providers: [
61
+ provideScionInterceptor({
62
+ stack: scionStack,
63
+ match: (req) => req.url.startsWith("https://console.anapaya.net/api/"),
64
+ }),
65
+ ];
66
+ ```
67
+
68
+ ## Worker File Deployment
69
+
70
+ By default, the worker file is resolved relative to the package using `import.meta.url`.
71
+ Modern bundlers including angular cli handle this automatically.
72
+
73
+ If your setup requires a custom worker location, you can override it:
74
+
75
+ ```ts
76
+ new ScionStack({ workerUrl: "/assets/scion-worker.js" });
77
+ ```
78
+
79
+ ## Observability
80
+
81
+ Check the browser console to see which requests are dispatched via the worker.
82
+ The web worker logs diagnostic information for every dispatched request:
83
+
84
+ ```text
85
+ [webscion] #1 POST /api/data | 142 bytes | SHA-256: a1b2c3... | framing: 0.035ms
86
+ ```
87
+
88
+ Each log line includes:
89
+
90
+ - **Sequence number** (`#1`): monotonic counter proving worker statefulness
91
+ - **Method and path**: the HTTP method and URL path
92
+ - **Byte length**: size of the HTTP/1.1 framed representation
93
+ - **SHA-256 hash**: hash of the framed bytes (for verification/debugging)
94
+ - **Framing duration**: time spent serializing the request to HTTP/1.1 bytes
95
+
96
+ ## Development
97
+
98
+ ```bash
99
+ # Install dependencies
100
+ ./pnpm -C pkg/web/webscion install
101
+
102
+ # Run tests
103
+ ./pnpm -C pkg/web/webscion test
104
+
105
+ # Build with Bazel
106
+ bazel build //pkg/web/webscion:pkg
107
+ ```
@@ -0,0 +1,3 @@
1
+ export { provideScionInterceptor, createScionInterceptorFn, } from "./interceptor";
2
+ export type { ScionInterceptorOptions } from "./interceptor";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/angular/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,3 @@
1
+ // Copyright 2026 Anapaya Systems
2
+ export { provideScionInterceptor, createScionInterceptorFn, } from "./interceptor";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/angular/index.ts"],"names":[],"mappings":"AAAA,iCAAiC;AAEjC,OAAO,EACL,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,eAAe,CAAC"}
@@ -0,0 +1,10 @@
1
+ import { HttpRequest, type HttpInterceptorFn } from "@angular/common/http";
2
+ import { Provider } from "@angular/core";
3
+ import type { ScionStack } from "../scion-stack";
4
+ export interface ScionInterceptorOptions {
5
+ stack: ScionStack;
6
+ match?: (req: HttpRequest<unknown>) => boolean;
7
+ }
8
+ export declare function provideScionInterceptor(options: ScionInterceptorOptions): Provider[];
9
+ export declare function createScionInterceptorFn(options: ScionInterceptorOptions): HttpInterceptorFn;
10
+ //# sourceMappingURL=interceptor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interceptor.d.ts","sourceRoot":"","sources":["../../../src/angular/interceptor.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,WAAW,EAEX,KAAK,iBAAiB,EACvB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,UAAU,CAAC;IAClB,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,WAAW,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC;CAChD;AAED,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,uBAAuB,GAC/B,QAAQ,EAAE,CAWZ;AAED,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,uBAAuB,GAC/B,iBAAiB,CAenB"}
@@ -0,0 +1,76 @@
1
+ // Copyright 2026 Anapaya Systems
2
+ import { HttpHeaders, HttpResponse, } from "@angular/common/http";
3
+ import { from, switchMap } from "rxjs";
4
+ export function provideScionInterceptor(options) {
5
+ return [
6
+ {
7
+ provide: "SCION_INTERCEPTOR_OPTIONS",
8
+ useValue: options,
9
+ },
10
+ {
11
+ provide: "HTTP_INTERCEPTORS_FN",
12
+ useValue: createScionInterceptorFn(options),
13
+ },
14
+ ];
15
+ }
16
+ export function createScionInterceptorFn(options) {
17
+ const guardedFetch = options.stack.fetch();
18
+ return (req, next) => {
19
+ if (options.match && !options.match(req)) {
20
+ return next(req);
21
+ }
22
+ return from(dispatchViaGuardedFetch(guardedFetch, req)).pipe(switchMap((httpResponse) => [httpResponse]));
23
+ };
24
+ }
25
+ async function dispatchViaGuardedFetch(guardedFetch, req) {
26
+ const { url, init } = toFetchRequest(req);
27
+ const fetchResponse = await guardedFetch(url, init);
28
+ return toAngularResponse(req.responseType, fetchResponse);
29
+ }
30
+ function toFetchRequest(req) {
31
+ const fetchHeaders = new Headers();
32
+ for (const key of req.headers.keys()) {
33
+ const value = req.headers.get(key);
34
+ if (value !== null) {
35
+ fetchHeaders.set(key, value);
36
+ }
37
+ }
38
+ const init = {
39
+ method: req.method,
40
+ headers: fetchHeaders,
41
+ };
42
+ if (req.body !== null) {
43
+ init.body = req.serializeBody();
44
+ }
45
+ return { url: req.urlWithParams, init };
46
+ }
47
+ async function toAngularResponse(response_type, fetchResponse) {
48
+ const responseHeaders = {};
49
+ fetchResponse.headers.forEach((value, key) => {
50
+ responseHeaders[key] = value;
51
+ });
52
+ const angularHeaders = new HttpHeaders(responseHeaders);
53
+ let body;
54
+ switch (response_type) {
55
+ case "json":
56
+ body = await fetchResponse.json();
57
+ break;
58
+ case "text":
59
+ body = await fetchResponse.text();
60
+ break;
61
+ case "arraybuffer":
62
+ body = await fetchResponse.arrayBuffer();
63
+ break;
64
+ case "blob":
65
+ body = await fetchResponse.blob();
66
+ break;
67
+ }
68
+ return new HttpResponse({
69
+ body,
70
+ headers: angularHeaders,
71
+ status: fetchResponse.status,
72
+ statusText: fetchResponse.statusText,
73
+ url: fetchResponse.url,
74
+ });
75
+ }
76
+ //# sourceMappingURL=interceptor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interceptor.js","sourceRoot":"","sources":["../../../src/angular/interceptor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { ScionStack, DEFAULT_WORKER_URL } from "./scion-stack";
2
+ export type { ScionStackOptions, WorkerRequest, WorkerResponse, WorkerErrorResponse, } from "./types";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAC/D,YAAY,EACV,iBAAiB,EACjB,aAAa,EACb,cAAc,EACd,mBAAmB,GACpB,MAAM,SAAS,CAAC"}
@@ -0,0 +1,3 @@
1
+ // Copyright 2026 Anapaya Systems
2
+ export { ScionStack, DEFAULT_WORKER_URL } from "./scion-stack";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,iCAAiC;AAEjC,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { ScionStackOptions } from "./types";
2
+ export declare const DEFAULT_WORKER_URL: string | URL;
3
+ export declare class ScionStack {
4
+ private worker;
5
+ constructor(options?: ScionStackOptions);
6
+ fetch(): typeof globalThis.fetch;
7
+ }
8
+ //# sourceMappingURL=scion-stack.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scion-stack.d.ts","sourceRoot":"","sources":["../../src/scion-stack.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,iBAAiB,EAGlB,MAAM,SAAS,CAAC;AAGjB,eAAO,MAAM,kBAAkB,EAAE,MAAM,GAAG,GAGzC,CAAC;AAEF,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAS;gBAEX,OAAO,CAAC,EAAE,iBAAiB;IAKvC,KAAK,IAAI,OAAO,UAAU,CAAC,KAAK;CAgEjC"}
@@ -0,0 +1,59 @@
1
+ // Copyright 2026 Anapaya Systems
2
+ import { isWorkerErrorResponse } from "./types";
3
+ export const DEFAULT_WORKER_URL = new URL("./worker/scion-worker.js", import.meta.url);
4
+ export class ScionStack {
5
+ worker;
6
+ constructor(options) {
7
+ const workerUrl = options?.workerUrl ?? DEFAULT_WORKER_URL;
8
+ this.worker = new Worker(workerUrl, { type: "module" });
9
+ }
10
+ fetch() {
11
+ return async (input, init) => {
12
+ const request = input instanceof Request ? input : new Request(input, init);
13
+ const url = request.url;
14
+ const method = request.method;
15
+ const headers = {};
16
+ request.headers.forEach((value, key) => {
17
+ headers[key] = value;
18
+ });
19
+ // Preserve the original body type from init (concrete: string, ArrayBuffer,
20
+ // Blob, etc.) instead of request.body which always returns a ReadableStream.
21
+ // This is required for Firefox which doesn't support ReadableStream as a
22
+ // fetch() body. Prefer init.body over input.body, matching standard fetch()
23
+ // precedence.
24
+ let body = null;
25
+ if (init && init.body !== undefined) {
26
+ body = init.body;
27
+ }
28
+ else if (input instanceof Request) {
29
+ body = input.body;
30
+ }
31
+ const msg = { url, method, headers, body };
32
+ const channel = new MessageChannel();
33
+ return new Promise((resolve, reject) => {
34
+ channel.port1.onmessage = (event) => {
35
+ channel.port1.close();
36
+ const res = event.data;
37
+ if (isWorkerErrorResponse(res)) {
38
+ reject(new Error(res.error));
39
+ return;
40
+ }
41
+ resolve(new Response(res.body, {
42
+ status: res.status,
43
+ statusText: res.statusText,
44
+ headers: res.headers,
45
+ }));
46
+ };
47
+ // ArrayBuffer and ArrayBufferView bodies are structured-cloned (copied),
48
+ // matching native fetch() semantics — the caller retains ownership.
49
+ // ReadableStream cannot be cloned and must be transferred.
50
+ const transferables = [channel.port2];
51
+ if (body instanceof ReadableStream) {
52
+ transferables.push(body);
53
+ }
54
+ this.worker.postMessage(msg, transferables);
55
+ });
56
+ };
57
+ }
58
+ }
59
+ //# sourceMappingURL=scion-stack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scion-stack.js","sourceRoot":"","sources":["../../src/scion-stack.ts"],"names":[],"mappings":"AAAA,iCAAiC;AAOjC,OAAO,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAEhD,MAAM,CAAC,MAAM,kBAAkB,GAAiB,IAAI,GAAG,CACrD,0BAA0B,EAC1B,MAAM,CAAC,IAAI,CAAC,GAAG,CAChB,CAAC;AAEF,MAAM,OAAO,UAAU;IACb,MAAM,CAAS;IAEvB,YAAY,OAA2B;QACrC,MAAM,SAAS,GAAG,OAAO,EAAE,SAAS,IAAI,kBAAkB,CAAC;QAC3D,IAAI,CAAC,MAAM,GAAG,IAAI,MAAM,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC1D,CAAC;IAED,KAAK;QACH,OAAO,KAAK,EACV,KAAwB,EACxB,IAAkB,EACC,EAAE;YACrB,MAAM,OAAO,GACX,KAAK,YAAY,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YAE9D,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;YACxB,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;YAE9B,MAAM,OAAO,GAA2B,EAAE,CAAC;YAC3C,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;gBACrC,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;YACvB,CAAC,CAAC,CAAC;YAEH,4EAA4E;YAC5E,6EAA6E;YAC7E,yEAAyE;YACzE,4EAA4E;YAC5E,cAAc;YACd,IAAI,IAAI,GAAoB,IAAI,CAAC;YACjC,IAAI,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBACpC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;YACnB,CAAC;iBAAM,IAAI,KAAK,YAAY,OAAO,EAAE,CAAC;gBACpC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;YACpB,CAAC;YAED,MAAM,GAAG,GAAkB,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;YAE1D,MAAM,OAAO,GAAG,IAAI,cAAc,EAAE,CAAC;YAErC,OAAO,IAAI,OAAO,CAAW,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBAC/C,OAAO,CAAC,KAAK,CAAC,SAAS,GAAG,CACxB,KAA0C,EAC1C,EAAE;oBACF,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;oBACtB,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC;oBAEvB,IAAI,qBAAqB,CAAC,GAAG,CAAC,EAAE,CAAC;wBAC/B,MAAM,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;wBAC7B,OAAO;oBACT,CAAC;oBAED,OAAO,CACL,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE;wBACrB,MAAM,EAAE,GAAG,CAAC,MAAM;wBAClB,UAAU,EAAE,GAAG,CAAC,UAAU;wBAC1B,OAAO,EAAE,GAAG,CAAC,OAAO;qBACrB,CAAC,CACH,CAAC;gBACJ,CAAC,CAAC;gBAEF,yEAAyE;gBACzE,oEAAoE;gBACpE,2DAA2D;gBAC3D,MAAM,aAAa,GAAmB,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;gBACtD,IAAI,IAAI,YAAY,cAAc,EAAE,CAAC;oBACnC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC3B,CAAC;gBACD,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;YAC9C,CAAC,CAAC,CAAC;QACL,CAAC,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,23 @@
1
+ export interface ScionStackOptions {
2
+ workerUrl?: string | URL;
3
+ logLevel?: "debug" | "info" | "warn" | "error";
4
+ }
5
+ export interface WorkerRequest {
6
+ url: string;
7
+ method: string;
8
+ headers: Record<string, string>;
9
+ body: BodyInit | null;
10
+ }
11
+ export interface WorkerResponse {
12
+ status: number;
13
+ statusText: string;
14
+ headers: Record<string, string>;
15
+ body: ReadableStream<Uint8Array> | null;
16
+ seqNr: number;
17
+ }
18
+ export interface WorkerErrorResponse {
19
+ error: string;
20
+ }
21
+ export type WorkerOutgoingMessage = WorkerResponse | WorkerErrorResponse;
22
+ export declare function isWorkerErrorResponse(msg: WorkerOutgoingMessage): msg is WorkerErrorResponse;
23
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;CAChD;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC;IACxC,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,qBAAqB,GAAG,cAAc,GAAG,mBAAmB,CAAC;AAEzE,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,qBAAqB,GACzB,GAAG,IAAI,mBAAmB,CAE5B"}
@@ -0,0 +1,5 @@
1
+ // Copyright 2026 Anapaya Systems
2
+ export function isWorkerErrorResponse(msg) {
3
+ return "error" in msg;
4
+ }
5
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,iCAAiC;AA4BjC,MAAM,UAAU,qBAAqB,CACnC,GAA0B;IAE1B,OAAO,OAAO,IAAI,GAAG,CAAC;AACxB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=scion-worker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scion-worker.d.ts","sourceRoot":"","sources":["../../../src/worker/scion-worker.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,CAAC"}
@@ -0,0 +1,131 @@
1
+ // Copyright 2026 Anapaya Systems
2
+ const encoder = new TextEncoder();
3
+ let requestCounter = 0;
4
+ self.onmessage = async (event) => {
5
+ const req = event.data;
6
+ const port = event.ports[0];
7
+ requestCounter++;
8
+ const seqNr = requestCounter;
9
+ try {
10
+ // Convert body to bytes for framing and hashing.
11
+ const bodyBytes = await bodyToBytes(req.body);
12
+ // Frame as HTTP/1.1
13
+ const framingStart = performance.now();
14
+ const framedBytes = frameHttp11(req.url, req.method, req.headers, bodyBytes);
15
+ const framingDuration = performance.now() - framingStart;
16
+ // Compute SHA-256 hash
17
+ const hashBuffer = await crypto.subtle.digest("SHA-256", framedBytes);
18
+ const hashHex = Array.from(new Uint8Array(hashBuffer))
19
+ .map((b) => b.toString(16).padStart(2, "0"))
20
+ .join("");
21
+ // Log diagnostics
22
+ const parsedUrl = new URL(req.url);
23
+ console.log(`[webscion] #${seqNr} ${req.method} ${parsedUrl.pathname} | ${framedBytes.byteLength} bytes | SHA-256: ${hashHex} | framing: ${framingDuration.toFixed(3)}ms`);
24
+ // Dispatch original request via native fetch
25
+ const fetchInit = {
26
+ method: req.method,
27
+ headers: req.headers,
28
+ };
29
+ if (req.body != null && req.method !== "GET" && req.method !== "HEAD") {
30
+ fetchInit.body = req.body;
31
+ }
32
+ const response = await fetch(req.url, fetchInit);
33
+ // Forward response back via the message port
34
+ const responseHeaders = {};
35
+ response.headers.forEach((value, key) => {
36
+ responseHeaders[key] = value;
37
+ });
38
+ const responseBody = response.body;
39
+ const msg = {
40
+ status: response.status,
41
+ statusText: response.statusText,
42
+ headers: responseHeaders,
43
+ body: responseBody,
44
+ seqNr,
45
+ };
46
+ const transferables = [];
47
+ if (responseBody) {
48
+ transferables.push(responseBody);
49
+ }
50
+ port.postMessage(msg, transferables);
51
+ port.close();
52
+ }
53
+ catch (err) {
54
+ const msg = {
55
+ error: err instanceof Error ? err.message : String(err),
56
+ };
57
+ port.postMessage(msg);
58
+ port.close();
59
+ }
60
+ };
61
+ async function bodyToBytes(body) {
62
+ if (body == null) {
63
+ return null;
64
+ }
65
+ if (typeof body === "string") {
66
+ return encoder.encode(body);
67
+ }
68
+ if (body instanceof ArrayBuffer) {
69
+ return new Uint8Array(body);
70
+ }
71
+ if (ArrayBuffer.isView(body)) {
72
+ return new Uint8Array(body.buffer, body.byteOffset, body.byteLength);
73
+ }
74
+ if (body instanceof Blob) {
75
+ return new Uint8Array(await body.arrayBuffer());
76
+ }
77
+ if (body instanceof URLSearchParams) {
78
+ return encoder.encode(body.toString());
79
+ }
80
+ if (body instanceof FormData) {
81
+ // Best-effort: serialize as URL-encoded form data.
82
+ const params = new URLSearchParams();
83
+ body.forEach((value, key) => {
84
+ if (typeof value === "string") {
85
+ params.append(key, value);
86
+ }
87
+ });
88
+ return encoder.encode(params.toString());
89
+ }
90
+ // ReadableStream fallback
91
+ if (body instanceof ReadableStream) {
92
+ const reader = body.getReader();
93
+ const chunks = [];
94
+ let totalLength = 0;
95
+ while (true) {
96
+ const { done, value } = await reader.read();
97
+ if (done)
98
+ break;
99
+ chunks.push(value);
100
+ totalLength += value.byteLength;
101
+ }
102
+ const result = new Uint8Array(new ArrayBuffer(totalLength));
103
+ let offset = 0;
104
+ for (const chunk of chunks) {
105
+ result.set(chunk, offset);
106
+ offset += chunk.byteLength;
107
+ }
108
+ return result;
109
+ }
110
+ return null;
111
+ }
112
+ function frameHttp11(url, method, headers, body) {
113
+ const parsedUrl = new URL(url);
114
+ const path = parsedUrl.pathname + parsedUrl.search;
115
+ const host = parsedUrl.host;
116
+ let headerBlock = `${method} ${path} HTTP/1.1\r\nHost: ${host}\r\n`;
117
+ for (const [key, value] of Object.entries(headers)) {
118
+ headerBlock += `${key}: ${value}\r\n`;
119
+ }
120
+ headerBlock += "\r\n";
121
+ const headerBytes = encoder.encode(headerBlock);
122
+ if (!body || body.byteLength === 0) {
123
+ return headerBytes;
124
+ }
125
+ const result = new Uint8Array(new ArrayBuffer(headerBytes.byteLength + body.byteLength));
126
+ result.set(headerBytes, 0);
127
+ result.set(body, headerBytes.byteLength);
128
+ return result;
129
+ }
130
+ export {};
131
+ //# sourceMappingURL=scion-worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scion-worker.js","sourceRoot":"","sources":["../../../src/worker/scion-worker.ts"],"names":[],"mappings":"AAAA,iCAAiC;AA0BjC,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;AAClC,IAAI,cAAc,GAAG,CAAC,CAAC;AAEvB,IAAI,CAAC,SAAS,GAAG,KAAK,EAAE,KAAkC,EAAE,EAAE;IAC5D,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC;IACvB,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC5B,cAAc,EAAE,CAAC;IACjB,MAAM,KAAK,GAAG,cAAc,CAAC;IAE7B,IAAI,CAAC;QACH,iDAAiD;QACjD,MAAM,SAAS,GAAG,MAAM,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAE9C,oBAAoB;QACpB,MAAM,YAAY,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QACvC,MAAM,WAAW,GAAG,WAAW,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAC7E,MAAM,eAAe,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,YAAY,CAAC;QAEzD,uBAAuB;QACvB,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;QACtE,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,UAAU,CAAC,CAAC;aACnD,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;aAC3C,IAAI,CAAC,EAAE,CAAC,CAAC;QAEZ,kBAAkB;QAClB,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnC,OAAO,CAAC,GAAG,CACT,eAAe,KAAK,IAAI,GAAG,CAAC,MAAM,IAAI,SAAS,CAAC,QAAQ,MAAM,WAAW,CAAC,UAAU,qBAAqB,OAAO,eAAe,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAC9J,CAAC;QAEF,6CAA6C;QAC7C,MAAM,SAAS,GAAgB;YAC7B,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,OAAO,EAAE,GAAG,CAAC,OAAO;SACrB,CAAC;QACF,IAAI,GAAG,CAAC,IAAI,IAAI,IAAI,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YACtE,SAAS,CAAC,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;QAC5B,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAEjD,6CAA6C;QAC7C,MAAM,eAAe,GAA2B,EAAE,CAAC;QACnD,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;YACtC,eAAe,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QAC/B,CAAC,CAAC,CAAC;QAEH,MAAM,YAAY,GAAG,QAAQ,CAAC,IAAI,CAAC;QACnC,MAAM,GAAG,GAAmB;YAC1B,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,UAAU,EAAE,QAAQ,CAAC,UAAU;YAC/B,OAAO,EAAE,eAAe;YACxB,IAAI,EAAE,YAAY;YAClB,KAAK;SACN,CAAC;QAEF,MAAM,aAAa,GAAmB,EAAE,CAAC;QACzC,IAAI,YAAY,EAAE,CAAC;YACjB,aAAa,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACnC,CAAC;QACD,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;QACrC,IAAI,CAAC,KAAK,EAAE,CAAC;IACf,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAwB;YAC/B,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;SACxD,CAAC;QACF,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QACtB,IAAI,CAAC,KAAK,EAAE,CAAC;IACf,CAAC;AACH,CAAC,CAAC;AAEF,KAAK,UAAU,WAAW,CAAC,IAAqB;IAC9C,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;QACjB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IACD,IAAI,IAAI,YAAY,WAAW,EAAE,CAAC;QAChC,OAAO,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IACD,IAAI,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7B,OAAO,IAAI,UAAU,CAAC,IAAI,CAAC,MAAqB,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IACtF,CAAC;IACD,IAAI,IAAI,YAAY,IAAI,EAAE,CAAC;QACzB,OAAO,IAAI,UAAU,CAAC,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IAClD,CAAC;IACD,IAAI,IAAI,YAAY,eAAe,EAAE,CAAC;QACpC,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;IACzC,CAAC;IACD,IAAI,IAAI,YAAY,QAAQ,EAAE,CAAC;QAC7B,mDAAmD;QACnD,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;QACrC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;YAC1B,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;gBAC9B,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC,CAAC,CAAC;QACH,OAAO,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC3C,CAAC;IACD,0BAA0B;IAC1B,IAAI,IAAI,YAAY,cAAc,EAAE,CAAC;QACnC,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAChC,MAAM,MAAM,GAA8B,EAAE,CAAC;QAC7C,IAAI,WAAW,GAAG,CAAC,CAAC;QACpB,OAAO,IAAI,EAAE,CAAC;YACZ,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YAC5C,IAAI,IAAI;gBAAE,MAAM;YAChB,MAAM,CAAC,IAAI,CAAC,KAAgC,CAAC,CAAC;YAC9C,WAAW,IAAI,KAAK,CAAC,UAAU,CAAC;QAClC,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,IAAI,WAAW,CAAC,WAAW,CAAC,CAAC,CAAC;QAC5D,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;YAC1B,MAAM,IAAI,KAAK,CAAC,UAAU,CAAC;QAC7B,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,WAAW,CAClB,GAAW,EACX,MAAc,EACd,OAA+B,EAC/B,IAAoC;IAEpC,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IAC/B,MAAM,IAAI,GAAG,SAAS,CAAC,QAAQ,GAAG,SAAS,CAAC,MAAM,CAAC;IACnD,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC;IAE5B,IAAI,WAAW,GAAG,GAAG,MAAM,IAAI,IAAI,sBAAsB,IAAI,MAAM,CAAC;IACpE,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACnD,WAAW,IAAI,GAAG,GAAG,KAAK,KAAK,MAAM,CAAC;IACxC,CAAC;IACD,WAAW,IAAI,MAAM,CAAC;IAEtB,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAChD,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU,KAAK,CAAC,EAAE,CAAC;QACnC,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,IAAI,WAAW,CAAC,WAAW,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;IACzF,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IAC3B,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,WAAW,CAAC,UAAU,CAAC,CAAC;IACzC,OAAO,MAAM,CAAC;AAChB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@anapaya/webscion-poc",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "main": "./bin/src/index.js",
6
+ "types": "./bin/src/index.d.ts",
7
+ "files": [
8
+ "bin/**",
9
+ "README.md",
10
+ "angular.js",
11
+ "angular.d.ts"
12
+ ],
13
+ "exports": {
14
+ ".": {
15
+ "import": "./bin/src/index.js",
16
+ "types": "./bin/src/index.d.ts"
17
+ },
18
+ "./angular": {
19
+ "import": "./bin/src/angular/index.js",
20
+ "types": "./bin/src/angular/index.d.ts"
21
+ },
22
+ "./worker": {
23
+ "default": "./bin/src/worker/scion-worker.js"
24
+ }
25
+ },
26
+ "peerDependencies": {
27
+ "@angular/core": "^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0",
28
+ "@angular/common": "^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0"
29
+ },
30
+ "peerDependenciesMeta": {
31
+ "@angular/core": {
32
+ "optional": true
33
+ },
34
+ "@angular/common": {
35
+ "optional": true
36
+ }
37
+ },
38
+ "devDependencies": {
39
+ "@angular/compiler": "^20.0.0",
40
+ "@angular/core": "^20.0.0",
41
+ "@angular/common": "^20.0.0",
42
+ "rxjs": "^7.8.0",
43
+ "typescript": "5.8.3",
44
+ "vitest": "^3.2.1"
45
+ },
46
+ "publishConfig": {
47
+ "registry": "https://registry.npmjs.org",
48
+ "access": "public"
49
+ },
50
+ "scripts": {
51
+ "build": "rm -rf bin && tsc -p tsconfig.json && tsc -p tsconfig.worker.json",
52
+ "test": "vitest run"
53
+ }
54
+ }