import { consumeToolboxTransfer, createToolboxTransfer, createToolboxTransferUrl, readToolboxTransferToken, type ToolboxTransfer, type ToolboxTransferStore, } from "../src/index.js"; import { describe, expect, it } from "vitest"; class MemoryStore implements ToolboxTransferStore { readonly values = new Map(); async put(transfer: ToolboxTransfer): Promise { this.values.set(transfer.token, transfer); } async take( token: string, expectedTargetAppId: string, now: number, ): Promise { const value = this.values.get(token); if (value !== undefined && value.targetAppId !== expectedTargetAppId) { throw new Error("Artifact transfer was addressed to another application"); } this.values.delete(token); return value === undefined || value.expiresAt <= now ? undefined : value; } async deleteExpired(now: number): Promise { let count = 0; for (const [token, value] of this.values) { if (value.expiresAt <= now) { this.values.delete(token); count += 1; } } return count; } } const deterministicCrypto = { getRandomValues(array: T): T { if (array instanceof Uint8Array) array.forEach((_value, index) => (array[index] = index)); return array; }, }; describe("one-time artifact transfers", () => { it("stores, addresses and consumes an artifact exactly once", async () => { const store = new MemoryStore(); const blob = new Blob(["hello"], { type: "text/plain" }); const transfer = await createToolboxTransfer( { source: { appId: "de.add-ideas.file-tools", appVersion: "1.0.0" }, targetAppId: "de.add-ideas.text-tools", files: [ { blob, name: "hello.txt", mediaType: blob.type, size: blob.size }, ], }, { store, crypto: deterministicCrypto, now: () => 1_000 }, ); expect(transfer.token).toHaveLength(22); const url = createToolboxTransferUrl( "https://tools.test/apps/text/", transfer.token, { location: "https://tools.test/apps/file/" }, ); expect(readToolboxTransferToken(url)).toBe(transfer.token); const consumed = await consumeToolboxTransfer( transfer.token, "de.add-ideas.text-tools", { store, now: () => 2_000 }, ); expect(await consumed?.files[0]?.blob.text()).toBe("hello"); expect( await consumeToolboxTransfer(transfer.token, "de.add-ideas.text-tools", { store, now: () => 2_000, }), ).toBeUndefined(); }); it("rejects expired, oversized and wrongly addressed transfers", async () => { const store = new MemoryStore(); const blob = new Blob(["hello"]); const transfer = await createToolboxTransfer( { source: { appId: "source" }, targetAppId: "target", ttlMs: 10, files: [{ blob, name: "a.bin", mediaType: "", size: blob.size }], }, { store, crypto: deterministicCrypto, now: () => 10 }, ); await expect( consumeToolboxTransfer(transfer.token, "target", { store, now: () => 20, }), ).resolves.toBeUndefined(); await expect( createToolboxTransfer( { source: { appId: "source" }, targetAppId: "target", maxBytes: 4, files: [{ blob, name: "a.bin", mediaType: "", size: blob.size }], }, { store, crypto: deterministicCrypto }, ), ).rejects.toThrow(/exceeds/u); await expect( createToolboxTransfer( { source: { appId: "source" }, targetAppId: "target", maxBytes: 512 * 1024 * 1024 + 1, files: [{ blob, name: "a.bin", mediaType: "", size: blob.size }], }, { store, crypto: deterministicCrypto }, ), ).rejects.toThrow(/invalid/iu); await expect( createToolboxTransfer( { source: { appId: "source" }, targetAppId: "target", files: [{ blob, name: "a.bin", mediaType: "", size: blob.size }], }, { store, crypto: deterministicCrypto, now: () => Number.NaN }, ), ).rejects.toThrow(/creation time/iu); const addressed = await createToolboxTransfer( { source: { appId: "source" }, targetAppId: "target", files: [{ blob, name: "a.bin", mediaType: "", size: blob.size }], }, { store, crypto: deterministicCrypto }, ); await expect( consumeToolboxTransfer(addressed.token, "another-target", { store }), ).rejects.toThrow(/another application/u); }); it("keeps opaque handoff tokens on-origin and rejects unsafe descriptors", async () => { const store = new MemoryStore(); const blob = new Blob(["hello"]); await expect( createToolboxTransfer( { source: { appId: "source" }, targetAppId: "target", files: [ { blob, name: "../hello.txt", mediaType: "text/plain", size: blob.size, }, ], }, { store, crypto: deterministicCrypto }, ), ).rejects.toThrow(/name is invalid/u); expect(() => createToolboxTransferUrl( "https://other.test/apps/text/", "AAECAwQFBgcICQoLDA0ODw", { location: "https://tools.test/apps/file/" }, ), ).toThrow(/current origin/u); }); it("revalidates same-origin storage records at the consuming trust boundary", async () => { const store = new MemoryStore(); const blob = new Blob(["hello"]); const transfer = await createToolboxTransfer( { source: { appId: "source" }, targetAppId: "target", files: [{ blob, name: "hello.txt", mediaType: "text/plain", size: 5 }], }, { store, crypto: deterministicCrypto, now: () => 100 }, ); store.values.set(transfer.token, { ...transfer, artifactVersion: 99 as 1, }); await expect( consumeToolboxTransfer(transfer.token, "target", { store, now: () => 200, }), ).rejects.toThrow(/identity is invalid/u); expect(store.values.has(transfer.token)).toBe(false); }); });