mirror of
https://github.com/HeyPuter/node-worker.git
synced 2026-11-13 18:34:31 -10:00
362 lines
13 KiB
JavaScript
362 lines
13 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { pathToFileURL } from "node:url";
|
|
import { rollup } from "rollup";
|
|
import typescript from "@rollup/plugin-typescript";
|
|
|
|
const root = path.resolve(import.meta.dirname, "..");
|
|
const mockDir = fs.mkdtempSync(path.join(os.tmpdir(), "nodeworker-epoxy-"));
|
|
fs.writeFileSync(path.join(mockDir, "package.json"), '{"type":"module"}');
|
|
fs.writeFileSync(
|
|
path.join(mockDir, "full.js"),
|
|
`
|
|
export async function init() {}
|
|
export class JsProtocolExtension { constructor() {} }
|
|
export class JsProtocolExtensionBuilder { constructor() {} }
|
|
export class JsProvider { constructor(connect) { this.connect = connect; } }
|
|
export class WispSocketProvider { constructor() { globalThis.__networkMock.providers++; } }
|
|
export class JsSocketProvider { constructor(connect) { this.connect = connect; } }
|
|
export class EitherSocketProvider { constructor() {} }
|
|
export class EpoxyClient {
|
|
constructor() {}
|
|
async fetch(url, init) {
|
|
const body = init.body ? await new Response(init.body).text() : "";
|
|
globalThis.__networkMock.requests.push({ url, method: init.method, body, headers: init.headers });
|
|
const response = new Response(new ReadableStream({ start(c) {
|
|
c.enqueue(new TextEncoder().encode("reply:" + body)); c.close();
|
|
} }), { status: 201, headers: { "x-test": "ok" } });
|
|
response.rawHeaders = { "x-test": ["ok"] };
|
|
return response;
|
|
}
|
|
async websocket() {
|
|
let resolve;
|
|
const closed = new Promise((r) => { resolve = r; });
|
|
return {
|
|
protocol: "test", headers: new Headers({ "x-ws": "ok" }), rawHeaders: { "x-ws": ["ok"] },
|
|
readable: new ReadableStream({ start(c) { c.enqueue("message"); c.close(); } }),
|
|
writable: new WritableStream(), closed,
|
|
close(info) { resolve(info ?? {}); },
|
|
};
|
|
}
|
|
async connect() { return this.stream(); }
|
|
async connectTls() {
|
|
return Object.assign(this.stream(), { negotiatedProtocol: "h2", protocolVersion: "TLSv1.3",
|
|
cipherSuite: "TLS_AES_128_GCM_SHA256", peerCertificates: [new Uint8Array([1, 2, 3])] });
|
|
}
|
|
stream() {
|
|
return {
|
|
read: new ReadableStream({ start(c) { c.enqueue(new Uint8Array([4, 5])); c.close(); } }),
|
|
write: new WritableStream({ write(c) { globalThis.__networkMock.writes.push([...c]); } }),
|
|
};
|
|
}
|
|
}
|
|
`
|
|
);
|
|
|
|
const entry = "\0network-test-entry";
|
|
const bundle = await rollup({
|
|
input: "network-test-entry",
|
|
plugins: [
|
|
{
|
|
name: "network-test-entry",
|
|
resolveId(id) {
|
|
return id === "network-test-entry" ? entry : null;
|
|
},
|
|
load(id) {
|
|
return id === entry
|
|
? `export { NetworkService } from ${JSON.stringify(path.join(root, "src/lib/network.ts"))};
|
|
export { PuterCredential } from ${JSON.stringify(path.join(root, "src/lib/credential.ts"))};`
|
|
: null;
|
|
},
|
|
},
|
|
typescript({
|
|
tsconfig: path.join(root, "tsconfig.main.json"),
|
|
declaration: false,
|
|
}),
|
|
],
|
|
});
|
|
const { output } = await bundle.generate({ format: "es" });
|
|
await bundle.close();
|
|
const { NetworkService, PuterCredential } = await import(
|
|
`data:text/javascript;base64,${Buffer.from(output[0].code).toString("base64")}`
|
|
);
|
|
|
|
const proxyEntry = "\0network-proxy-test-entry";
|
|
const proxyBundle = await rollup({
|
|
input: "network-proxy-test-entry",
|
|
plugins: [
|
|
{
|
|
name: "network-proxy-test-entry",
|
|
resolveId(id, importer) {
|
|
if (id === "network-proxy-test-entry") return proxyEntry;
|
|
if (!importer?.endsWith("/src/worker/epoxy/index.ts")) return null;
|
|
if (id === "../wire") return "\0mock-wire";
|
|
if (id === "./globals") return "\0mock-globals";
|
|
if (id === "../node-primordials") return "\0mock-primordials";
|
|
return null;
|
|
},
|
|
load(id) {
|
|
if (id === proxyEntry)
|
|
return `export { getClient } from ${JSON.stringify(path.join(root, "src/worker/epoxy/index.ts"))};`;
|
|
if (id === "\0mock-wire")
|
|
return "export const call = (kind, request, options) => globalThis.__networkCall(kind, request, options);";
|
|
if (id === "\0mock-globals")
|
|
return "export const FETCH = globalThis.fetch; export const WebSocket = globalThis.WebSocket; export const WebSocketStream = null;";
|
|
if (id === "\0mock-primordials")
|
|
return "export const nodePrimordials = { ObjectDefineProperties: Object.defineProperties };";
|
|
return null;
|
|
},
|
|
},
|
|
typescript({
|
|
tsconfig: path.join(root, "tsconfig.worker.json"),
|
|
declaration: false,
|
|
}),
|
|
],
|
|
});
|
|
const proxyOutput = await proxyBundle.generate({ format: "es" });
|
|
await proxyBundle.close();
|
|
const { getClient } = await import(
|
|
`data:text/javascript;base64,${Buffer.from(proxyOutput.output[0].code).toString("base64")}`
|
|
);
|
|
|
|
const nativeFetch = globalThis.fetch;
|
|
globalThis.fetch = async (url) => {
|
|
assert.equal(url, `${pathToFileURL(mockDir).href}/full.wasm`);
|
|
return new Response(new Uint8Array());
|
|
};
|
|
globalThis.__networkMock = { providers: 0, requests: [], writes: [] };
|
|
try {
|
|
const base = pathToFileURL(mockDir).href;
|
|
const first = new NetworkService(
|
|
undefined,
|
|
{ wispUrl: "wss://relay.invalid/" },
|
|
undefined,
|
|
base,
|
|
(resource) => resource
|
|
);
|
|
const second = new NetworkService(
|
|
undefined,
|
|
{ wispUrl: "wss://relay.invalid/" },
|
|
undefined,
|
|
base,
|
|
(resource) => resource
|
|
);
|
|
globalThis.__networkCall = async (kind, request, options) => {
|
|
assert.equal(kind, 7);
|
|
const answered = await first.handle(request, options?.transfer ?? []);
|
|
return { value: answered.value, attachments: answered.transfer ?? [] };
|
|
};
|
|
const channel = new MessageChannel();
|
|
const body = new ReadableStream({
|
|
start(c) {
|
|
c.enqueue(new TextEncoder().encode("body"));
|
|
c.close();
|
|
},
|
|
});
|
|
const fetched = await first.handle(
|
|
{
|
|
op: "net.fetch",
|
|
url: "https://example.test/",
|
|
method: "POST",
|
|
headers: [["x-request", "yes"]],
|
|
hasBody: true,
|
|
},
|
|
[body, channel.port2]
|
|
);
|
|
assert.equal(fetched.value.status, 201);
|
|
assert.equal(await new Response(fetched.transfer[0]).text(), "reply:body");
|
|
assert.deepEqual(globalThis.__networkMock.requests, [
|
|
{
|
|
url: "https://example.test/",
|
|
method: "POST",
|
|
body: "body",
|
|
headers: [["x-request", "yes"]],
|
|
},
|
|
]);
|
|
channel.port1.close();
|
|
|
|
const ws = await first.handle(
|
|
{ op: "net.websocket", url: "wss://example.test/" },
|
|
[]
|
|
);
|
|
assert.equal(ws.value.protocol, "test");
|
|
const control = ws.transfer[2];
|
|
const closed = new Promise((resolve) => {
|
|
control.onmessage = (event) => resolve(event.data);
|
|
});
|
|
control.postMessage({ closeCode: 1000, reason: "done" });
|
|
assert.deepEqual(await closed, { info: { closeCode: 1000, reason: "done" } });
|
|
control.close();
|
|
|
|
const tcp = await second.handle(
|
|
{ op: "net.tcp", host: "example.test", port: 80 },
|
|
[]
|
|
);
|
|
const tcpReader = tcp.transfer[0].getReader();
|
|
assert.deepEqual([...(await tcpReader.read()).value], [4, 5]);
|
|
assert.equal((await tcpReader.read()).done, true);
|
|
const tcpWriter = tcp.transfer[1].getWriter();
|
|
await tcpWriter.write(new Uint8Array([9]));
|
|
await tcpWriter.close();
|
|
assert.deepEqual(globalThis.__networkMock.writes, [[9]]);
|
|
|
|
const tls = await second.handle(
|
|
{ op: "net.tls", host: "example.test", port: 443, alpn: ["h2"] },
|
|
[]
|
|
);
|
|
assert.equal(tls.value.negotiatedProtocol, "h2");
|
|
assert.deepEqual(tls.value.peerCertificates, [[1, 2, 3]]);
|
|
assert.equal(globalThis.__networkMock.providers, 1);
|
|
const proxied = await getClient();
|
|
const proxiedResponse = await proxied.fetch("https://example.test/upload", {
|
|
method: "POST",
|
|
headers: { "x-proxy": "yes" },
|
|
body: new Blob(["blob-data"], { type: "text/plain" }),
|
|
});
|
|
assert.equal(proxiedResponse.status, 201);
|
|
assert.equal(await proxiedResponse.text(), "reply:blob-data");
|
|
assert.deepEqual(proxiedResponse.rawHeaders, { "x-test": ["ok"] });
|
|
assert.deepEqual(globalThis.__networkMock.requests.at(-1), {
|
|
url: "https://example.test/upload",
|
|
method: "POST",
|
|
body: "blob-data",
|
|
headers: [
|
|
["content-type", "text/plain"],
|
|
["x-proxy", "yes"],
|
|
],
|
|
});
|
|
// Firefox's native Request has body consumers but no .body getter.
|
|
class FirefoxRequest extends Request {
|
|
get body() { return undefined; }
|
|
}
|
|
const firefoxRequest = new FirefoxRequest("https://example.test/firefox", {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json", "x-firefox": "yes" },
|
|
body: '{"message":"hello ☃"}',
|
|
});
|
|
const firefoxResponse = await proxied.fetch(firefoxRequest);
|
|
assert.equal(await firefoxResponse.text(), 'reply:{"message":"hello ☃"}');
|
|
assert.equal(firefoxRequest.bodyUsed, true);
|
|
assert.deepEqual(globalThis.__networkMock.requests.at(-1), {
|
|
url: "https://example.test/firefox",
|
|
method: "POST",
|
|
body: '{"message":"hello ☃"}',
|
|
headers: [["content-type", "application/json"], ["x-firefox", "yes"]],
|
|
});
|
|
await assert.rejects(proxied.fetch(firefoxRequest), TypeError);
|
|
for (const body of ["replacement", null]) {
|
|
const input = new FirefoxRequest("https://example.test/override", {
|
|
method: "POST", body: "original",
|
|
});
|
|
const response = await proxied.fetch(input, { body });
|
|
assert.equal(await response.text(), "reply:" + (body ?? ""));
|
|
assert.equal(input.bodyUsed, false, "an explicit body must override the Request body");
|
|
}
|
|
const aborted = new AbortController();
|
|
aborted.abort();
|
|
await assert.rejects(
|
|
proxied.fetch("https://example.test/abort", { signal: aborted.signal }),
|
|
{ name: "AbortError" }
|
|
);
|
|
const proxiedWs = await proxied.websocket("wss://example.test/");
|
|
proxiedWs.close({ closeCode: 1000, reason: "done" });
|
|
assert.deepEqual(await proxiedWs.closed, { closeCode: 1000, reason: "done" });
|
|
const proxiedTls = await proxied.connectTls("example.test", 443, {
|
|
alpn: ["h2"],
|
|
});
|
|
assert.equal(proxiedTls.negotiatedProtocol, "h2");
|
|
assert.deepEqual([...proxiedTls.peerCertificates[0]], [1, 2, 3]);
|
|
first.close();
|
|
second.close();
|
|
|
|
const customOrigin = "https://api.custom.test";
|
|
const requests = [];
|
|
globalThis.fetch = async (input, init) => {
|
|
const url = new URL(input);
|
|
assert.equal(url.origin, customOrigin);
|
|
requests.push({
|
|
path: url.pathname,
|
|
queryToken: url.searchParams.get("auth_token"),
|
|
headerToken: init?.headers?.Authorization,
|
|
});
|
|
const body =
|
|
url.pathname === "/whoami"
|
|
? { username: "tester", uuid: "user-id", email: "[email protected]" }
|
|
: url.pathname === "/wisp/relay-token/create"
|
|
? { server: "wss://relay.custom.test", token: "relay-secret" }
|
|
: url.pathname === "/peer/signaller-info"
|
|
? {
|
|
url: "wss://signal.custom.test",
|
|
fallbackIce: [{ urls: "stun:stun.custom.test" }],
|
|
}
|
|
: { iceServers: [{ urls: "stun:stun.custom.test" }], ttl: 60 };
|
|
return new Response(JSON.stringify(body), { status: 200 });
|
|
};
|
|
const credential = new PuterCredential("page-secret");
|
|
const authenticated = new NetworkService(
|
|
credential,
|
|
undefined,
|
|
`${customOrigin}/`,
|
|
base,
|
|
(resource) => resource
|
|
);
|
|
assert.equal((await authenticated.auth.user()).username, "tester");
|
|
assert.deepEqual(await authenticated.auth.relay(), {
|
|
server: "wss://relay.custom.test",
|
|
token: "relay-secret",
|
|
});
|
|
assert.equal((await authenticated.auth.peerOptions()).token, "page-secret");
|
|
assert.deepEqual(requests, [
|
|
{ path: "/whoami", queryToken: "page-secret", headerToken: undefined },
|
|
{
|
|
path: "/wisp/relay-token/create",
|
|
queryToken: null,
|
|
headerToken: "Bearer page-secret",
|
|
},
|
|
{ path: "/peer/signaller-info", queryToken: null, headerToken: undefined },
|
|
{
|
|
path: "/peer/generate-turn",
|
|
queryToken: null,
|
|
headerToken: "Bearer page-secret",
|
|
},
|
|
]);
|
|
|
|
// A renewal: the next request carries the new token, TURN credentials are asked for
|
|
// again, and the next connection mints a relay token through a fresh provider.
|
|
const providersBefore = globalThis.__networkMock.providers;
|
|
await authenticated.handle({ op: "net.tcp", host: "example.test", port: 80 }, []);
|
|
assert.equal(globalThis.__networkMock.providers, providersBefore + 1);
|
|
credential.set("renewed-secret");
|
|
requests.length = 0;
|
|
assert.equal(authenticated.auth.token, "renewed-secret");
|
|
assert.equal((await authenticated.auth.peerOptions()).token, "renewed-secret");
|
|
await authenticated.handle({ op: "net.tcp", host: "example.test", port: 80 }, []);
|
|
assert.equal(globalThis.__networkMock.providers, providersBefore + 2);
|
|
assert.deepEqual(requests, [
|
|
{
|
|
path: "/peer/generate-turn",
|
|
queryToken: null,
|
|
headerToken: "Bearer renewed-secret",
|
|
},
|
|
{
|
|
path: "/wisp/relay-token/create",
|
|
queryToken: null,
|
|
headerToken: "Bearer renewed-secret",
|
|
},
|
|
]);
|
|
// A cleared SDK token is not a sign-out.
|
|
credential.set("");
|
|
assert.equal(authenticated.auth.token, "renewed-secret");
|
|
authenticated.close();
|
|
console.log(
|
|
"page network proxy streams fetch, WebSocket, TCP and TLS; Wisp provider shared; custom Puter API origin honored; renewed token picked up"
|
|
);
|
|
} finally {
|
|
globalThis.fetch = nativeFetch;
|
|
delete globalThis.__networkMock;
|
|
delete globalThis.__networkCall;
|
|
fs.rmSync(mockDir, { recursive: true, force: true });
|
|
}
|