@graffiti-garden/implementation-decentralized 0.1.3 → 0.1.5

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.
Files changed (46) hide show
  1. package/dist/1-services/4-inboxes/cache.d.ts +34 -0
  2. package/dist/1-services/4-inboxes/cache.d.ts.map +1 -0
  3. package/dist/1-services/4-inboxes/messages.d.ts +70 -0
  4. package/dist/1-services/4-inboxes/messages.d.ts.map +1 -0
  5. package/dist/1-services/4-inboxes.d.ts +28 -67
  6. package/dist/1-services/4-inboxes.d.ts.map +1 -1
  7. package/dist/3-protocol/4-graffiti.d.ts +1 -0
  8. package/dist/3-protocol/4-graffiti.d.ts.map +1 -1
  9. package/dist/browser/index.js +7 -7
  10. package/dist/browser/index.js.map +4 -4
  11. package/dist/cjs/1-services/1-authorization.js +1 -1
  12. package/dist/cjs/1-services/1-authorization.js.map +2 -2
  13. package/dist/cjs/1-services/4-inboxes/cache.js +186 -0
  14. package/dist/cjs/1-services/4-inboxes/cache.js.map +7 -0
  15. package/dist/cjs/1-services/4-inboxes/messages.js +69 -0
  16. package/dist/cjs/1-services/4-inboxes/messages.js.map +7 -0
  17. package/dist/cjs/1-services/4-inboxes-tests.js +339 -9
  18. package/dist/cjs/1-services/4-inboxes-tests.js.map +3 -3
  19. package/dist/cjs/1-services/4-inboxes.js +210 -399
  20. package/dist/cjs/1-services/4-inboxes.js.map +3 -3
  21. package/dist/cjs/3-protocol/4-graffiti.js +19 -10
  22. package/dist/cjs/3-protocol/4-graffiti.js.map +2 -2
  23. package/dist/cjs/index.spec.js +1 -0
  24. package/dist/cjs/index.spec.js.map +2 -2
  25. package/dist/esm/1-services/1-authorization.js +1 -1
  26. package/dist/esm/1-services/1-authorization.js.map +2 -2
  27. package/dist/esm/1-services/4-inboxes/cache.js +156 -0
  28. package/dist/esm/1-services/4-inboxes/cache.js.map +7 -0
  29. package/dist/esm/1-services/4-inboxes/messages.js +60 -0
  30. package/dist/esm/1-services/4-inboxes/messages.js.map +7 -0
  31. package/dist/esm/1-services/4-inboxes-tests.js +340 -10
  32. package/dist/esm/1-services/4-inboxes-tests.js.map +3 -3
  33. package/dist/esm/1-services/4-inboxes.js +222 -383
  34. package/dist/esm/1-services/4-inboxes.js.map +3 -3
  35. package/dist/esm/3-protocol/4-graffiti.js +19 -10
  36. package/dist/esm/3-protocol/4-graffiti.js.map +2 -2
  37. package/dist/esm/index.spec.js +2 -1
  38. package/dist/esm/index.spec.js.map +2 -2
  39. package/package.json +1 -1
  40. package/src/1-services/1-authorization.ts +1 -1
  41. package/src/1-services/4-inboxes/cache.ts +227 -0
  42. package/src/1-services/4-inboxes/messages.ts +63 -0
  43. package/src/1-services/4-inboxes-tests.ts +387 -2
  44. package/src/1-services/4-inboxes.ts +297 -512
  45. package/src/3-protocol/4-graffiti.ts +26 -10
  46. package/src/index.spec.ts +3 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/index.spec.ts"],
4
- "sourcesContent": ["import type {\n GraffitiLoginEvent,\n GraffitiLogoutEvent,\n GraffitiSession,\n} from \"@graffiti-garden/api\";\nimport {\n graffitiCRUDTests,\n graffitiDiscoverTests,\n graffitiMediaTests,\n} from \"@graffiti-garden/api/tests\";\nimport { DecentralizedIdentifiers } from \"./1-services/2-dids\";\nimport { Authorization } from \"./1-services/1-authorization\";\nimport { StorageBuckets } from \"./1-services/3-storage-buckets\";\nimport { Inboxes } from \"./1-services/4-inboxes\";\nimport { Sessions } from \"./3-protocol/1-sessions\";\nimport { afterAll, beforeAll, describe, expect, test } from \"vitest\";\nimport { decode as dagCborDecode, encode as dagCborEncode } from \"@ipld/dag-cbor\";\nimport { Handles } from \"./3-protocol/2-handles\";\nimport { didTests } from \"./1-services/2-dids-tests\";\nimport { storageBucketTests } from \"./1-services/3-storage-buckets-tests\";\nimport { inboxTests } from \"./1-services/4-inboxes-tests\";\nimport { stringEncodingTests } from \"./2-primitives/1-string-encoding-tests\";\nimport { contentAddressesTests } from \"./2-primitives/2-content-addresses-tests\";\nimport { channelAttestationTests } from \"./2-primitives/3-channel-attestations-tests\";\nimport { allowedAttestationTests } from \"./2-primitives/4-allowed-attestations-tests\";\nimport { handleTests } from \"./3-protocol/2-handles-tests\";\nimport { objectEncodingTests } from \"./3-protocol/3-object-encoding-tests\";\nimport { GraffitiDecentralized } from \"./3-protocol/4-graffiti\";\nimport {\n DecentralizedTestEnvironment,\n decentralizedSharedInbox,\n decentralizedTestUsers,\n} from \"../test/environment\";\n\ndescribe(\"GraffitiDecentralized Tests\", () => {\n const environment = new DecentralizedTestEnvironment();\n const handles = decentralizedTestUsers.map((user) => user.handle);\n let sessions: GraffitiSession[] = [];\n let sessionMethods: Sessions;\n let handleMethods: Handles;\n\n beforeAll(async () => {\n await environment.start();\n\n const dids = new DecentralizedIdentifiers();\n sessionMethods = new Sessions({\n dids,\n authorization: new Authorization(),\n inboxes: new Inboxes(),\n storageBuckets: new StorageBuckets(),\n });\n handleMethods = new Handles({ dids });\n\n for (const handle of handles) {\n sessions.push(await login(handle));\n }\n });\n\n afterAll(async () => {\n try {\n for (const session of sessions) {\n await logout(session.actor);\n }\n } finally {\n await environment.stop();\n }\n });\n\n // Service tests\n didTests();\n storageBucketTests(\n decentralizedTestUsers[0].bucketEndpoint,\n decentralizedTestUsers[0].token,\n );\n inboxTests(\n decentralizedTestUsers[0].inboxEndpoint,\n decentralizedTestUsers[0].token,\n );\n\n // Primitive tests\n stringEncodingTests();\n contentAddressesTests();\n channelAttestationTests();\n allowedAttestationTests();\n\n // Protocol tests\n handleTests(handles[0]);\n objectEncodingTests();\n\n const useGraffiti = () =>\n new GraffitiDecentralized({\n defaultInboxEndpoints: [decentralizedSharedInbox],\n });\n graffitiCRUDTests(\n useGraffiti,\n () => sessions[0],\n () => sessions[1],\n );\n graffitiMediaTests(\n useGraffiti,\n () => sessions[0],\n () => sessions[1],\n );\n graffitiDiscoverTests(\n useGraffiti,\n () => sessions[0],\n () => sessions[1],\n );\n\n test(\"OAuth codes require approval from the selected account\", async () => {\n const account = decentralizedTestUsers[0];\n const redirectUri = \"https://client.example/callback\";\n const endpoint = \"https://localhost:5173/app/oauth/authorize\";\n const cookie = `account_${account.accountId}=${account.token}`;\n const headers = {\n Cookie: cookie,\n \"X-Graffiti-Account\": String(account.accountId),\n \"Content-Type\": \"application/json\",\n Origin: \"https://localhost:5173\",\n };\n const request = (scope: string[], requestHeaders: HeadersInit = headers) =>\n fetch(endpoint, {\n method: \"POST\",\n headers: requestHeaders,\n body: JSON.stringify({ redirect_uri: redirectUri, state: \"test-state\", scope }),\n });\n\n const navigation = await fetch(\n `${endpoint}?account=${account.accountId}&redirect_uri=${encodeURIComponent(redirectUri)}`,\n { headers: { Cookie: cookie } },\n );\n expect(navigation.status).toBe(404);\n\n expect((await request([account.bucketEndpoint], { Cookie: cookie })).status).toBe(401);\n expect((await request([account.bucketEndpoint], {\n ...headers,\n Origin: \"https://attacker.example\",\n })).status).toBe(403);\n expect((await request([decentralizedTestUsers[1].bucketEndpoint])).status).toBe(403);\n\n const approval = await request([account.bucketEndpoint]);\n expect(approval.status).toBe(200);\n const { redirectUri: callback } = await approval.json() as { redirectUri: string };\n const code = new URL(callback).searchParams.get(\"code\");\n expect(code).toBeTruthy();\n expect(new URL(callback).searchParams.get(\"state\")).toBe(\"test-state\");\n\n const exchange = await fetch(\"https://localhost:5173/app/oauth/token\", {\n method: \"POST\",\n body: new URLSearchParams({ code: code!, redirect_uri: redirectUri }),\n });\n expect(exchange.status).toBe(200);\n expect((await exchange.json()).access_token).toBeTypeOf(\"string\");\n });\n\n test(\"deleting a bucket removes its values and rejects requests to its old URL\", async () => {\n const account = decentralizedTestUsers[0];\n const smallKey = crypto.randomUUID();\n const largeKey = crypto.randomUUID();\n const largeValue = new Uint8Array(32 * 1024 + 1).fill(7);\n const authorization = { Authorization: `Bearer ${account.token}` };\n\n for (const [key, value] of [\n [smallKey, new Uint8Array([1, 2, 3])],\n [largeKey, largeValue],\n ] as const) {\n const response = await fetch(`${account.bucketEndpoint}/value/${key}`, {\n method: \"PUT\",\n headers: authorization,\n body: value,\n });\n expect(response.status).toBe(201);\n }\n\n const r2Key = `${account.bucketId}/${largeKey}`;\n expect(await environment.storedR2ObjectExists(r2Key)).toBe(true);\n\n const deletion = await fetch(\n `https://localhost:5173/app/service-instances/bucket/service/${account.bucketId}`,\n {\n method: \"DELETE\",\n headers: {\n Cookie: `account_${account.accountId}=${account.token}`,\n \"X-Graffiti-Account\": String(account.accountId),\n },\n },\n );\n expect(deletion.status).toBe(200);\n\n expect(await environment.storedR2ObjectExists(r2Key)).toBe(false);\n for (const key of [smallKey, largeKey]) {\n const response = await fetch(`${account.bucketEndpoint}/value/${key}`);\n expect(response.status).toBe(404);\n }\n const batch = await fetch(`${account.bucketEndpoint}/values`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/cbor\" },\n body: dagCborEncode({ keys: [smallKey, largeKey], maxValueBytes: largeValue.length }).slice(),\n });\n expect((dagCborDecode(await batch.arrayBuffer()) as { results: { status: number }[] }).results)\n .toEqual([{ status: 404 }, { status: 404 }]);\n\n const putAfterDeletion = await fetch(`${account.bucketEndpoint}/value/${smallKey}`, {\n method: \"PUT\",\n headers: authorization,\n body: new Uint8Array([4]),\n });\n expect(putAfterDeletion.status).toBe(404);\n });\n\n // How to log in/out vvv\n async function login(handle: string) {\n const actor = await handleMethods.handleToActor(handle);\n\n return await new Promise<GraffitiSession>((resolve, reject) => {\n let timeout: ReturnType<typeof setTimeout>;\n const cleanup = () => {\n clearTimeout(timeout);\n sessionMethods.sessionEvents.removeEventListener(\"login\", listener);\n };\n const listener = (e: unknown) => {\n if (!(e instanceof CustomEvent)) return;\n const detail = e.detail as GraffitiLoginEvent[\"detail\"];\n if (detail.session?.actor !== actor) return;\n cleanup();\n if (detail.error) {\n reject(detail.error);\n } else {\n resolve(detail.session);\n }\n };\n sessionMethods.sessionEvents.addEventListener(\"login\", listener);\n\n timeout = setTimeout(\n () => {\n cleanup();\n reject(new Error(\"Authorization timed out\"));\n },\n 30 * 1000,\n );\n\n void sessionMethods.login(actor);\n });\n }\n\n async function logout(actor: string) {\n return await new Promise<void>((resolve, reject) => {\n let timeout: ReturnType<typeof setTimeout>;\n const cleanup = () => {\n clearTimeout(timeout);\n sessionMethods.sessionEvents.removeEventListener(\"logout\", listener);\n };\n const listener = (e: unknown) => {\n if (!(e instanceof CustomEvent)) return;\n const detail = e.detail as GraffitiLogoutEvent[\"detail\"];\n if (detail.actor !== actor) return;\n cleanup();\n\n if (detail.error) {\n reject(detail.error);\n } else {\n resolve();\n }\n };\n sessionMethods.sessionEvents.addEventListener(\"logout\", listener);\n\n timeout = setTimeout(\n () => {\n cleanup();\n reject(new Error(\"Logout timed out\"));\n },\n 30 * 1000,\n );\n\n void sessionMethods.logout(actor);\n });\n }\n});\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type {\n GraffitiLoginEvent,\n GraffitiLogoutEvent,\n GraffitiSession,\n} from \"@graffiti-garden/api\";\nimport {\n graffitiCRUDTests,\n graffitiDiscoverTests,\n graffitiMediaTests,\n} from \"@graffiti-garden/api/tests\";\nimport { DecentralizedIdentifiers } from \"./1-services/2-dids\";\nimport { Authorization } from \"./1-services/1-authorization\";\nimport { StorageBuckets } from \"./1-services/3-storage-buckets\";\nimport { Inboxes } from \"./1-services/4-inboxes\";\nimport { Sessions } from \"./3-protocol/1-sessions\";\nimport { afterAll, beforeAll, describe, expect, test } from \"vitest\";\nimport { decode as dagCborDecode, encode as dagCborEncode } from \"@ipld/dag-cbor\";\nimport { Handles } from \"./3-protocol/2-handles\";\nimport { didTests } from \"./1-services/2-dids-tests\";\nimport { storageBucketTests } from \"./1-services/3-storage-buckets-tests\";\nimport { inboxTests, inboxUnitTests } from \"./1-services/4-inboxes-tests\";\nimport { stringEncodingTests } from \"./2-primitives/1-string-encoding-tests\";\nimport { contentAddressesTests } from \"./2-primitives/2-content-addresses-tests\";\nimport { channelAttestationTests } from \"./2-primitives/3-channel-attestations-tests\";\nimport { allowedAttestationTests } from \"./2-primitives/4-allowed-attestations-tests\";\nimport { handleTests } from \"./3-protocol/2-handles-tests\";\nimport { objectEncodingTests } from \"./3-protocol/3-object-encoding-tests\";\nimport { GraffitiDecentralized } from \"./3-protocol/4-graffiti\";\nimport {\n DecentralizedTestEnvironment,\n decentralizedSharedInbox,\n decentralizedTestUsers,\n} from \"../test/environment\";\n\ndescribe(\"GraffitiDecentralized Tests\", () => {\n const environment = new DecentralizedTestEnvironment();\n const handles = decentralizedTestUsers.map((user) => user.handle);\n let sessions: GraffitiSession[] = [];\n let sessionMethods: Sessions;\n let handleMethods: Handles;\n\n beforeAll(async () => {\n await environment.start();\n\n const dids = new DecentralizedIdentifiers();\n sessionMethods = new Sessions({\n dids,\n authorization: new Authorization(),\n inboxes: new Inboxes(),\n storageBuckets: new StorageBuckets(),\n });\n handleMethods = new Handles({ dids });\n\n for (const handle of handles) {\n sessions.push(await login(handle));\n }\n });\n\n afterAll(async () => {\n try {\n for (const session of sessions) {\n await logout(session.actor);\n }\n } finally {\n await environment.stop();\n }\n });\n\n // Service tests\n didTests();\n storageBucketTests(\n decentralizedTestUsers[0].bucketEndpoint,\n decentralizedTestUsers[0].token,\n );\n inboxTests(\n decentralizedTestUsers[0].inboxEndpoint,\n decentralizedTestUsers[0].token,\n );\n\n // Primitive tests\n stringEncodingTests();\n contentAddressesTests();\n channelAttestationTests();\n allowedAttestationTests();\n\n // Protocol tests\n handleTests(handles[0]);\n objectEncodingTests();\n\n const useGraffiti = () =>\n new GraffitiDecentralized({\n defaultInboxEndpoints: [decentralizedSharedInbox],\n });\n graffitiCRUDTests(\n useGraffiti,\n () => sessions[0],\n () => sessions[1],\n );\n graffitiMediaTests(\n useGraffiti,\n () => sessions[0],\n () => sessions[1],\n );\n graffitiDiscoverTests(\n useGraffiti,\n () => sessions[0],\n () => sessions[1],\n );\n\n test(\"OAuth codes require approval from the selected account\", async () => {\n const account = decentralizedTestUsers[0];\n const redirectUri = \"https://client.example/callback\";\n const endpoint = \"https://localhost:5173/app/oauth/authorize\";\n const cookie = `account_${account.accountId}=${account.token}`;\n const headers = {\n Cookie: cookie,\n \"X-Graffiti-Account\": String(account.accountId),\n \"Content-Type\": \"application/json\",\n Origin: \"https://localhost:5173\",\n };\n const request = (scope: string[], requestHeaders: HeadersInit = headers) =>\n fetch(endpoint, {\n method: \"POST\",\n headers: requestHeaders,\n body: JSON.stringify({ redirect_uri: redirectUri, state: \"test-state\", scope }),\n });\n\n const navigation = await fetch(\n `${endpoint}?account=${account.accountId}&redirect_uri=${encodeURIComponent(redirectUri)}`,\n { headers: { Cookie: cookie } },\n );\n expect(navigation.status).toBe(404);\n\n expect((await request([account.bucketEndpoint], { Cookie: cookie })).status).toBe(401);\n expect((await request([account.bucketEndpoint], {\n ...headers,\n Origin: \"https://attacker.example\",\n })).status).toBe(403);\n expect((await request([decentralizedTestUsers[1].bucketEndpoint])).status).toBe(403);\n\n const approval = await request([account.bucketEndpoint]);\n expect(approval.status).toBe(200);\n const { redirectUri: callback } = await approval.json() as { redirectUri: string };\n const code = new URL(callback).searchParams.get(\"code\");\n expect(code).toBeTruthy();\n expect(new URL(callback).searchParams.get(\"state\")).toBe(\"test-state\");\n\n const exchange = await fetch(\"https://localhost:5173/app/oauth/token\", {\n method: \"POST\",\n body: new URLSearchParams({ code: code!, redirect_uri: redirectUri }),\n });\n expect(exchange.status).toBe(200);\n expect((await exchange.json()).access_token).toBeTypeOf(\"string\");\n });\n\n test(\"deleting a bucket removes its values and rejects requests to its old URL\", async () => {\n const account = decentralizedTestUsers[0];\n const smallKey = crypto.randomUUID();\n const largeKey = crypto.randomUUID();\n const largeValue = new Uint8Array(32 * 1024 + 1).fill(7);\n const authorization = { Authorization: `Bearer ${account.token}` };\n\n for (const [key, value] of [\n [smallKey, new Uint8Array([1, 2, 3])],\n [largeKey, largeValue],\n ] as const) {\n const response = await fetch(`${account.bucketEndpoint}/value/${key}`, {\n method: \"PUT\",\n headers: authorization,\n body: value,\n });\n expect(response.status).toBe(201);\n }\n\n const r2Key = `${account.bucketId}/${largeKey}`;\n expect(await environment.storedR2ObjectExists(r2Key)).toBe(true);\n\n const deletion = await fetch(\n `https://localhost:5173/app/service-instances/bucket/service/${account.bucketId}`,\n {\n method: \"DELETE\",\n headers: {\n Cookie: `account_${account.accountId}=${account.token}`,\n \"X-Graffiti-Account\": String(account.accountId),\n },\n },\n );\n expect(deletion.status).toBe(200);\n\n expect(await environment.storedR2ObjectExists(r2Key)).toBe(false);\n for (const key of [smallKey, largeKey]) {\n const response = await fetch(`${account.bucketEndpoint}/value/${key}`);\n expect(response.status).toBe(404);\n }\n const batch = await fetch(`${account.bucketEndpoint}/values`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/cbor\" },\n body: dagCborEncode({ keys: [smallKey, largeKey], maxValueBytes: largeValue.length }).slice(),\n });\n expect((dagCborDecode(await batch.arrayBuffer()) as { results: { status: number }[] }).results)\n .toEqual([{ status: 404 }, { status: 404 }]);\n\n const putAfterDeletion = await fetch(`${account.bucketEndpoint}/value/${smallKey}`, {\n method: \"PUT\",\n headers: authorization,\n body: new Uint8Array([4]),\n });\n expect(putAfterDeletion.status).toBe(404);\n });\n\n // How to log in/out vvv\n async function login(handle: string) {\n const actor = await handleMethods.handleToActor(handle);\n\n return await new Promise<GraffitiSession>((resolve, reject) => {\n let timeout: ReturnType<typeof setTimeout>;\n const cleanup = () => {\n clearTimeout(timeout);\n sessionMethods.sessionEvents.removeEventListener(\"login\", listener);\n };\n const listener = (e: unknown) => {\n if (!(e instanceof CustomEvent)) return;\n const detail = e.detail as GraffitiLoginEvent[\"detail\"];\n if (detail.session?.actor !== actor) return;\n cleanup();\n if (detail.error) {\n reject(detail.error);\n } else {\n resolve(detail.session);\n }\n };\n sessionMethods.sessionEvents.addEventListener(\"login\", listener);\n\n timeout = setTimeout(\n () => {\n cleanup();\n reject(new Error(\"Authorization timed out\"));\n },\n 30 * 1000,\n );\n\n void sessionMethods.login(actor);\n });\n }\n\n async function logout(actor: string) {\n return await new Promise<void>((resolve, reject) => {\n let timeout: ReturnType<typeof setTimeout>;\n const cleanup = () => {\n clearTimeout(timeout);\n sessionMethods.sessionEvents.removeEventListener(\"logout\", listener);\n };\n const listener = (e: unknown) => {\n if (!(e instanceof CustomEvent)) return;\n const detail = e.detail as GraffitiLogoutEvent[\"detail\"];\n if (detail.actor !== actor) return;\n cleanup();\n\n if (detail.error) {\n reject(detail.error);\n } else {\n resolve();\n }\n };\n sessionMethods.sessionEvents.addEventListener(\"logout\", listener);\n\n timeout = setTimeout(\n () => {\n cleanup();\n reject(new Error(\"Logout timed out\"));\n },\n 30 * 1000,\n );\n\n void sessionMethods.logout(actor);\n });\n }\n});\n\ninboxUnitTests();\n"],
5
+ "mappings": "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",
6
6
  "names": ["dagCborEncode", "dagCborDecode"]
7
7
  }
@@ -146,7 +146,7 @@ class Authorization {
146
146
  state
147
147
  });
148
148
  if (typeof window !== "undefined") {
149
- window.location.href = redirectTo.toString();
149
+ window.location.replace(redirectTo.toString());
150
150
  } else {
151
151
  console.log("Please open the following URL in your browser:");
152
152
  console.log(redirectTo.toString());
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/1-services/1-authorization.ts"],
4
- "sourcesContent": ["import {\n discovery,\n randomState,\n buildAuthorizationUrl,\n authorizationCodeGrant,\n tokenRevocation,\n} from \"openid-client\";\nimport {\n type infer as infer_,\n string,\n url,\n array,\n object,\n optional,\n nullable,\n instanceof as instanceof_,\n undefined as undefined_,\n intersection,\n union,\n} from \"zod/mini\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type { Socket } from \"node:net\";\n\nconst AUTHORIZATION_ENDPOINT_METHOD_PREFIX_OAUTH2 = \"oauth2:\";\nconst LOCAL_STORAGE_OAUTH2_KEY = \"graffiti-auth-oauth2-data\";\n\nexport class Authorization {\n eventTarget: EventTarget = new EventTarget();\n\n constructor() {\n // Extract oauth redirect synchronously so the route\n // can be changed before any SPA routers (e.g. vue router)\n // start messing with things\n const oauthPromise = this.completeOauth();\n\n (async () => {\n // Allow listeners to be added first\n await new Promise((resolve) => setTimeout(resolve, 0));\n\n // Complete the oauth flow\n await oauthPromise;\n\n // Send an initialized event\n const initializedEvent: InitializedEvent = new CustomEvent(\"initialized\");\n this.eventTarget.dispatchEvent(initializedEvent);\n })();\n }\n\n async login(...args: Parameters<typeof this.login_>): Promise<void> {\n try {\n await this.login_(...args);\n } catch (e) {\n const error = e instanceof Error ? e : new Error(\"Unknown error\");\n const detail: LoginEvent[\"detail\"] = { loginId: args[1], error };\n this.eventTarget.dispatchEvent(new CustomEvent(\"login\", { detail }));\n }\n }\n protected async login_(\n authorizationEndpoint: string,\n loginId: string,\n serviceEndpoints: string[],\n ): Promise<void> {\n const configuration = await this.getAuthorizationConfiguration(\n authorizationEndpoint,\n );\n\n const scope = serviceEndpoints.map(encodeURIComponent).join(\" \");\n const state = randomState();\n\n let redirectUri: string;\n let waitForCallback: Promise<void> | undefined = undefined;\n if (typeof window !== \"undefined\") {\n // If in a browser, prepare for a redirect by\n // storing the configuration, expected state,\n // current URL, and endpoints in local storage\n redirectUri = window.location.href;\n const data: infer_<typeof OAuth2LoginDataSchema> = {\n loginId,\n redirectUri,\n authorizationEndpoint,\n state,\n serviceEndpoints,\n };\n window.localStorage.setItem(\n LOCAL_STORAGE_OAUTH2_KEY,\n JSON.stringify(data),\n );\n } else {\n // Otherwise, in node, start a local server to receive the callback\n const http = await import(\"node:http\").catch((e) => {\n throw new Error(\n \"Unrecognized environment: cannot find window or node:http\",\n );\n });\n const server = http.createServer();\n\n try {\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(0, \"::1\", resolve);\n });\n } catch (e) {\n try {\n server.close();\n } catch {}\n throw new Error(\"Failed to start local oauth callback server.\");\n }\n\n const address = server.address();\n if (!address) {\n try {\n server.close();\n } catch {}\n throw new Error(\"Failed to get local oauth callback server address.\");\n }\n redirectUri =\n typeof address === \"string\"\n ? `http://${address}`\n : `http://${address.family === \"IPv6\" ? `[${address.address}]` : address.address}:${address.port}`;\n\n // Wait for a callback request\n waitForCallback = new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(\n () => {\n try {\n server.close();\n } catch {}\n reject(\"Oauth callback timed out.\");\n },\n 5 * 60 * 1000, // 5 minutes\n );\n\n const sockets = new Set<Socket>();\n server.on(\"connection\", (socket: Socket) => {\n sockets.add(socket);\n socket.on(\"close\", () => {\n sockets.delete(socket);\n });\n });\n\n // Set up the actual request handler\n const onRequest = async (req: IncomingMessage, res: ServerResponse) => {\n try {\n const callbackUrl = new URL(req.url ?? \"/\", redirectUri);\n await this.onCallbackUrl({\n loginId,\n callbackUrl,\n configuration,\n expectedState: state,\n serviceEndpoints,\n });\n\n res.statusCode = 200;\n res.setHeader(\"Content-Type\", \"text/plain\");\n res.end(\"You may now close this window.\");\n } catch (e) {\n res.statusCode = 500;\n res.setHeader(\"Content-Type\", \"text/plain\");\n res.end(\"Error processing OAuth callback.\");\n\n throw e;\n } finally {\n clearTimeout(timeout);\n server.off(\"request\", onRequest);\n\n for (const socket of sockets) {\n socket.destroy();\n }\n\n server.close(() => resolve());\n }\n };\n\n server.on(\"request\", onRequest);\n });\n }\n\n // Construct the authorization URL\n const redirectUriStripped = new URL(redirectUri);\n redirectUriStripped.hash = \"\";\n redirectUriStripped.search = \"\";\n\n const redirectTo = buildAuthorizationUrl(configuration, {\n scope,\n redirect_uri: redirectUriStripped.toString(),\n state,\n });\n\n // Either redirect (browser) or print the URL and wait (node)\n if (typeof window !== \"undefined\") {\n window.location.href = redirectTo.toString();\n } else {\n console.log(\"Please open the following URL in your browser:\");\n console.log(redirectTo.toString());\n await waitForCallback;\n }\n }\n\n protected completeOauth() {\n if (typeof window === \"undefined\") return;\n\n // Look in local storage to see if we have a pending login\n const data = window.localStorage.getItem(LOCAL_STORAGE_OAUTH2_KEY);\n if (!data) return;\n\n let json: unknown;\n try {\n json = JSON.parse(data);\n } catch {\n console.error(\"Invalid OAuth2 login data in local storage.\");\n window.localStorage.removeItem(LOCAL_STORAGE_OAUTH2_KEY);\n return;\n }\n\n const parseResult = OAuth2LoginDataSchema.safeParse(json);\n if (!parseResult.success) {\n console.error(\n \"Invalid OAuth2 login data structure in local storage.\",\n parseResult.error,\n );\n window.localStorage.removeItem(LOCAL_STORAGE_OAUTH2_KEY);\n return;\n }\n\n const {\n loginId,\n redirectUri,\n authorizationEndpoint,\n state,\n serviceEndpoints,\n } = parseResult.data;\n\n try {\n // Make sure that we redirected back to the correct page\n const expectedUrl = new URL(redirectUri);\n const callbackUrl = new URL(window.location.href);\n if (expectedUrl.pathname !== callbackUrl.pathname) return;\n // Make sure it is actually an oauth call\n const params = callbackUrl.searchParams;\n if (!params.has(\"code\") && !params.has(\"error\")) return;\n\n // Restore the hash and query parameters to the expected URL,\n // removing the code, state, and error parameters\n window.history.replaceState({}, document.title, expectedUrl.toString());\n window.localStorage.removeItem(LOCAL_STORAGE_OAUTH2_KEY);\n\n return new Promise<void>((resolve) => setTimeout(resolve, 0))\n .then(() => this.getAuthorizationConfiguration(authorizationEndpoint))\n .then((configuration) =>\n this.onCallbackUrl({\n loginId,\n callbackUrl,\n configuration,\n expectedState: state,\n serviceEndpoints,\n }),\n )\n .catch((e) => {\n const error = e instanceof Error ? e : new Error(\"Unknown error\");\n const detail: LoginEvent[\"detail\"] = { loginId, error };\n this.eventTarget.dispatchEvent(new CustomEvent(\"login\", { detail }));\n });\n } catch (e) {\n console.error(e);\n }\n }\n\n protected async onCallbackUrl(args: {\n loginId: string;\n callbackUrl: URL;\n configuration: any;\n expectedState: string;\n serviceEndpoints: string[];\n }) {\n const {\n loginId,\n callbackUrl,\n configuration,\n expectedState,\n serviceEndpoints,\n } = args;\n\n const response = await authorizationCodeGrant(configuration, callbackUrl, {\n expectedState,\n });\n\n const token = response.access_token;\n const scope = response.scope;\n const grantedEndpoints =\n scope?.split(\" \").map(decodeURIComponent) || serviceEndpoints;\n\n // Make sure granted endpoints cover the requested endpoints\n if (\n !serviceEndpoints.every((endpoint) => grantedEndpoints.includes(endpoint))\n ) {\n throw new Error(\"Not all requested service endpoints were granted.\");\n }\n\n // Send a logged in event\n const loginEvent: LoginEvent = new CustomEvent(\"login\", {\n detail: {\n loginId,\n token,\n },\n });\n this.eventTarget.dispatchEvent(loginEvent);\n }\n\n async logout(\n authorizationEndpoint: string,\n logoutId: string,\n token: string,\n ): Promise<void> {\n try {\n await this.logout_(authorizationEndpoint, logoutId, token);\n } catch (e) {\n const error = e instanceof Error ? e : new Error(\"Unknown error\");\n const detail: LogoutEvent[\"detail\"] = { logoutId, error };\n this.eventTarget.dispatchEvent(new CustomEvent(\"logout\", { detail }));\n }\n }\n protected async logout_(\n authorizationEndpoint: string,\n logoutId: string,\n token: string,\n ): Promise<void> {\n const configuration = await this.getAuthorizationConfiguration(\n authorizationEndpoint,\n );\n await tokenRevocation(configuration, token);\n const detail: LogoutEvent[\"detail\"] = { logoutId };\n this.eventTarget.dispatchEvent(new CustomEvent(\"logout\", { detail }));\n }\n\n protected async getAuthorizationConfiguration(\n authorizationEndpoint: string,\n ): Promise<any> {\n // Parse the authorization endpoint\n if (\n !authorizationEndpoint.startsWith(\n AUTHORIZATION_ENDPOINT_METHOD_PREFIX_OAUTH2,\n )\n ) {\n throw new Error(\n `Unrecognized authorization endpoint method: ${authorizationEndpoint}`,\n );\n }\n const issuer = authorizationEndpoint.slice(\n AUTHORIZATION_ENDPOINT_METHOD_PREFIX_OAUTH2.length,\n );\n\n // Look up the oauth configuration\n let issuerUrl: URL;\n try {\n issuerUrl = new URL(issuer);\n } catch (e) {\n throw new Error(\"Invalid issuer URL.\");\n }\n\n return await discovery(issuerUrl, \"graffiti-client\");\n }\n}\n\nexport const LoginEventDetailSchema = intersection(\n object({\n loginId: string(),\n }),\n union([\n object({ token: string(), error: optional(undefined_()) }),\n object({ error: instanceof_(Error) }),\n ]),\n);\n\nexport const LogoutEventDetailSchema = object({\n logoutId: string(),\n error: optional(instanceof_(Error)),\n});\n\nexport const InitializedEventDetailSchema = optional(\n nullable(\n object({\n error: optional(instanceof_(Error)),\n }),\n ),\n);\n\nexport type LoginEvent = CustomEvent<infer_<typeof LoginEventDetailSchema>>;\nexport type LogoutEvent = CustomEvent<infer_<typeof LogoutEventDetailSchema>>;\nexport type InitializedEvent = CustomEvent<\n infer_<typeof InitializedEventDetailSchema>\n>;\n\nconst OAuth2LoginDataSchema = object({\n loginId: string(),\n redirectUri: url(),\n authorizationEndpoint: url(),\n state: string(),\n serviceEndpoints: array(url()),\n});\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import {\n discovery,\n randomState,\n buildAuthorizationUrl,\n authorizationCodeGrant,\n tokenRevocation,\n} from \"openid-client\";\nimport {\n type infer as infer_,\n string,\n url,\n array,\n object,\n optional,\n nullable,\n instanceof as instanceof_,\n undefined as undefined_,\n intersection,\n union,\n} from \"zod/mini\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type { Socket } from \"node:net\";\n\nconst AUTHORIZATION_ENDPOINT_METHOD_PREFIX_OAUTH2 = \"oauth2:\";\nconst LOCAL_STORAGE_OAUTH2_KEY = \"graffiti-auth-oauth2-data\";\n\nexport class Authorization {\n eventTarget: EventTarget = new EventTarget();\n\n constructor() {\n // Extract oauth redirect synchronously so the route\n // can be changed before any SPA routers (e.g. vue router)\n // start messing with things\n const oauthPromise = this.completeOauth();\n\n (async () => {\n // Allow listeners to be added first\n await new Promise((resolve) => setTimeout(resolve, 0));\n\n // Complete the oauth flow\n await oauthPromise;\n\n // Send an initialized event\n const initializedEvent: InitializedEvent = new CustomEvent(\"initialized\");\n this.eventTarget.dispatchEvent(initializedEvent);\n })();\n }\n\n async login(...args: Parameters<typeof this.login_>): Promise<void> {\n try {\n await this.login_(...args);\n } catch (e) {\n const error = e instanceof Error ? e : new Error(\"Unknown error\");\n const detail: LoginEvent[\"detail\"] = { loginId: args[1], error };\n this.eventTarget.dispatchEvent(new CustomEvent(\"login\", { detail }));\n }\n }\n protected async login_(\n authorizationEndpoint: string,\n loginId: string,\n serviceEndpoints: string[],\n ): Promise<void> {\n const configuration = await this.getAuthorizationConfiguration(\n authorizationEndpoint,\n );\n\n const scope = serviceEndpoints.map(encodeURIComponent).join(\" \");\n const state = randomState();\n\n let redirectUri: string;\n let waitForCallback: Promise<void> | undefined = undefined;\n if (typeof window !== \"undefined\") {\n // If in a browser, prepare for a redirect by\n // storing the configuration, expected state,\n // current URL, and endpoints in local storage\n redirectUri = window.location.href;\n const data: infer_<typeof OAuth2LoginDataSchema> = {\n loginId,\n redirectUri,\n authorizationEndpoint,\n state,\n serviceEndpoints,\n };\n window.localStorage.setItem(\n LOCAL_STORAGE_OAUTH2_KEY,\n JSON.stringify(data),\n );\n } else {\n // Otherwise, in node, start a local server to receive the callback\n const http = await import(\"node:http\").catch((e) => {\n throw new Error(\n \"Unrecognized environment: cannot find window or node:http\",\n );\n });\n const server = http.createServer();\n\n try {\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(0, \"::1\", resolve);\n });\n } catch (e) {\n try {\n server.close();\n } catch {}\n throw new Error(\"Failed to start local oauth callback server.\");\n }\n\n const address = server.address();\n if (!address) {\n try {\n server.close();\n } catch {}\n throw new Error(\"Failed to get local oauth callback server address.\");\n }\n redirectUri =\n typeof address === \"string\"\n ? `http://${address}`\n : `http://${address.family === \"IPv6\" ? `[${address.address}]` : address.address}:${address.port}`;\n\n // Wait for a callback request\n waitForCallback = new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(\n () => {\n try {\n server.close();\n } catch {}\n reject(\"Oauth callback timed out.\");\n },\n 5 * 60 * 1000, // 5 minutes\n );\n\n const sockets = new Set<Socket>();\n server.on(\"connection\", (socket: Socket) => {\n sockets.add(socket);\n socket.on(\"close\", () => {\n sockets.delete(socket);\n });\n });\n\n // Set up the actual request handler\n const onRequest = async (req: IncomingMessage, res: ServerResponse) => {\n try {\n const callbackUrl = new URL(req.url ?? \"/\", redirectUri);\n await this.onCallbackUrl({\n loginId,\n callbackUrl,\n configuration,\n expectedState: state,\n serviceEndpoints,\n });\n\n res.statusCode = 200;\n res.setHeader(\"Content-Type\", \"text/plain\");\n res.end(\"You may now close this window.\");\n } catch (e) {\n res.statusCode = 500;\n res.setHeader(\"Content-Type\", \"text/plain\");\n res.end(\"Error processing OAuth callback.\");\n\n throw e;\n } finally {\n clearTimeout(timeout);\n server.off(\"request\", onRequest);\n\n for (const socket of sockets) {\n socket.destroy();\n }\n\n server.close(() => resolve());\n }\n };\n\n server.on(\"request\", onRequest);\n });\n }\n\n // Construct the authorization URL\n const redirectUriStripped = new URL(redirectUri);\n redirectUriStripped.hash = \"\";\n redirectUriStripped.search = \"\";\n\n const redirectTo = buildAuthorizationUrl(configuration, {\n scope,\n redirect_uri: redirectUriStripped.toString(),\n state,\n });\n\n // Either redirect (browser) or print the URL and wait (node)\n if (typeof window !== \"undefined\") {\n window.location.replace(redirectTo.toString());\n } else {\n console.log(\"Please open the following URL in your browser:\");\n console.log(redirectTo.toString());\n await waitForCallback;\n }\n }\n\n protected completeOauth() {\n if (typeof window === \"undefined\") return;\n\n // Look in local storage to see if we have a pending login\n const data = window.localStorage.getItem(LOCAL_STORAGE_OAUTH2_KEY);\n if (!data) return;\n\n let json: unknown;\n try {\n json = JSON.parse(data);\n } catch {\n console.error(\"Invalid OAuth2 login data in local storage.\");\n window.localStorage.removeItem(LOCAL_STORAGE_OAUTH2_KEY);\n return;\n }\n\n const parseResult = OAuth2LoginDataSchema.safeParse(json);\n if (!parseResult.success) {\n console.error(\n \"Invalid OAuth2 login data structure in local storage.\",\n parseResult.error,\n );\n window.localStorage.removeItem(LOCAL_STORAGE_OAUTH2_KEY);\n return;\n }\n\n const {\n loginId,\n redirectUri,\n authorizationEndpoint,\n state,\n serviceEndpoints,\n } = parseResult.data;\n\n try {\n // Make sure that we redirected back to the correct page\n const expectedUrl = new URL(redirectUri);\n const callbackUrl = new URL(window.location.href);\n if (expectedUrl.pathname !== callbackUrl.pathname) return;\n // Make sure it is actually an oauth call\n const params = callbackUrl.searchParams;\n if (!params.has(\"code\") && !params.has(\"error\")) return;\n\n // Restore the hash and query parameters to the expected URL,\n // removing the code, state, and error parameters\n window.history.replaceState({}, document.title, expectedUrl.toString());\n window.localStorage.removeItem(LOCAL_STORAGE_OAUTH2_KEY);\n\n return new Promise<void>((resolve) => setTimeout(resolve, 0))\n .then(() => this.getAuthorizationConfiguration(authorizationEndpoint))\n .then((configuration) =>\n this.onCallbackUrl({\n loginId,\n callbackUrl,\n configuration,\n expectedState: state,\n serviceEndpoints,\n }),\n )\n .catch((e) => {\n const error = e instanceof Error ? e : new Error(\"Unknown error\");\n const detail: LoginEvent[\"detail\"] = { loginId, error };\n this.eventTarget.dispatchEvent(new CustomEvent(\"login\", { detail }));\n });\n } catch (e) {\n console.error(e);\n }\n }\n\n protected async onCallbackUrl(args: {\n loginId: string;\n callbackUrl: URL;\n configuration: any;\n expectedState: string;\n serviceEndpoints: string[];\n }) {\n const {\n loginId,\n callbackUrl,\n configuration,\n expectedState,\n serviceEndpoints,\n } = args;\n\n const response = await authorizationCodeGrant(configuration, callbackUrl, {\n expectedState,\n });\n\n const token = response.access_token;\n const scope = response.scope;\n const grantedEndpoints =\n scope?.split(\" \").map(decodeURIComponent) || serviceEndpoints;\n\n // Make sure granted endpoints cover the requested endpoints\n if (\n !serviceEndpoints.every((endpoint) => grantedEndpoints.includes(endpoint))\n ) {\n throw new Error(\"Not all requested service endpoints were granted.\");\n }\n\n // Send a logged in event\n const loginEvent: LoginEvent = new CustomEvent(\"login\", {\n detail: {\n loginId,\n token,\n },\n });\n this.eventTarget.dispatchEvent(loginEvent);\n }\n\n async logout(\n authorizationEndpoint: string,\n logoutId: string,\n token: string,\n ): Promise<void> {\n try {\n await this.logout_(authorizationEndpoint, logoutId, token);\n } catch (e) {\n const error = e instanceof Error ? e : new Error(\"Unknown error\");\n const detail: LogoutEvent[\"detail\"] = { logoutId, error };\n this.eventTarget.dispatchEvent(new CustomEvent(\"logout\", { detail }));\n }\n }\n protected async logout_(\n authorizationEndpoint: string,\n logoutId: string,\n token: string,\n ): Promise<void> {\n const configuration = await this.getAuthorizationConfiguration(\n authorizationEndpoint,\n );\n await tokenRevocation(configuration, token);\n const detail: LogoutEvent[\"detail\"] = { logoutId };\n this.eventTarget.dispatchEvent(new CustomEvent(\"logout\", { detail }));\n }\n\n protected async getAuthorizationConfiguration(\n authorizationEndpoint: string,\n ): Promise<any> {\n // Parse the authorization endpoint\n if (\n !authorizationEndpoint.startsWith(\n AUTHORIZATION_ENDPOINT_METHOD_PREFIX_OAUTH2,\n )\n ) {\n throw new Error(\n `Unrecognized authorization endpoint method: ${authorizationEndpoint}`,\n );\n }\n const issuer = authorizationEndpoint.slice(\n AUTHORIZATION_ENDPOINT_METHOD_PREFIX_OAUTH2.length,\n );\n\n // Look up the oauth configuration\n let issuerUrl: URL;\n try {\n issuerUrl = new URL(issuer);\n } catch (e) {\n throw new Error(\"Invalid issuer URL.\");\n }\n\n return await discovery(issuerUrl, \"graffiti-client\");\n }\n}\n\nexport const LoginEventDetailSchema = intersection(\n object({\n loginId: string(),\n }),\n union([\n object({ token: string(), error: optional(undefined_()) }),\n object({ error: instanceof_(Error) }),\n ]),\n);\n\nexport const LogoutEventDetailSchema = object({\n logoutId: string(),\n error: optional(instanceof_(Error)),\n});\n\nexport const InitializedEventDetailSchema = optional(\n nullable(\n object({\n error: optional(instanceof_(Error)),\n }),\n ),\n);\n\nexport type LoginEvent = CustomEvent<infer_<typeof LoginEventDetailSchema>>;\nexport type LogoutEvent = CustomEvent<infer_<typeof LogoutEventDetailSchema>>;\nexport type InitializedEvent = CustomEvent<\n infer_<typeof InitializedEventDetailSchema>\n>;\n\nconst OAuth2LoginDataSchema = object({\n loginId: string(),\n redirectUri: url(),\n authorizationEndpoint: url(),\n state: string(),\n serviceEndpoints: array(url()),\n});\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,156 @@
1
+ import { encode as dagCborEncode } from "@ipld/dag-cbor";
2
+ import { LABELED_MESSAGE_ID_KEY } from "./messages";
3
+ async function canUseIDB() {
4
+ try {
5
+ if (!globalThis.indexedDB) return false;
6
+ await new Promise((resolve, reject) => {
7
+ const req = indexedDB.open("__idb_probe__", 1);
8
+ req.onupgradeneeded = () => req.result.createObjectStore("k");
9
+ req.onsuccess = () => {
10
+ req.result.close();
11
+ resolve();
12
+ };
13
+ req.onerror = () => reject(req.error);
14
+ });
15
+ return true;
16
+ } catch {
17
+ return false;
18
+ }
19
+ }
20
+ function getMessageCacheKey(inboxUrl, messageId) {
21
+ return `${encodeURIComponent(inboxUrl)}:${encodeURIComponent(messageId)}`;
22
+ }
23
+ async function replayQuery(cache, inboxUrl, messageIdsCacheKey, query, numSeen = 0) {
24
+ const messageIds = query.messageIds.slice(numSeen);
25
+ return Promise.all(
26
+ messageIds.map(async (id) => {
27
+ const message = await cache.messages.get(getMessageCacheKey(inboxUrl, id));
28
+ if (!message) {
29
+ try {
30
+ await cache.messageIds.del(messageIdsCacheKey);
31
+ } catch {
32
+ }
33
+ throw new Error("Cache out of sync - perhaps clear browser storage");
34
+ }
35
+ return message;
36
+ })
37
+ );
38
+ }
39
+ async function storeQueryPage(cache, inboxUrl, messageIdsCacheKey, previous, page) {
40
+ await cache.messages.setMany(
41
+ page.messages.map((message) => [
42
+ getMessageCacheKey(inboxUrl, message[LABELED_MESSAGE_ID_KEY]),
43
+ message
44
+ ])
45
+ );
46
+ const query = {
47
+ cursor: page.cursor,
48
+ version: previous?.version ?? crypto.randomUUID(),
49
+ messageIds: [
50
+ ...previous?.messageIds ?? [],
51
+ ...page.messages.map((message) => message[LABELED_MESSAGE_ID_KEY])
52
+ ],
53
+ generation: crypto.randomUUID(),
54
+ hasMore: page.hasMore,
55
+ waitTil: page.waitTil
56
+ };
57
+ await cache.messageIds.set(messageIdsCacheKey, query);
58
+ return query;
59
+ }
60
+ async function getMessageIdsCacheKey(inboxUrl, type, body) {
61
+ const cacheIdData = dagCborEncode({
62
+ inboxUrl,
63
+ type,
64
+ body: body ?? null
65
+ });
66
+ return crypto.subtle.digest("SHA-256", new Uint8Array(cacheIdData)).then(
67
+ (bytes) => Array.from(new Uint8Array(bytes)).map((byte) => byte.toString(16).padStart(2, "0")).join("")
68
+ );
69
+ }
70
+ async function resetCacheDB() {
71
+ await new Promise((resolve) => {
72
+ const req = indexedDB.deleteDatabase("graffiti-inbox-cache");
73
+ req.onsuccess = () => resolve();
74
+ req.onerror = () => resolve();
75
+ req.onblocked = () => resolve();
76
+ });
77
+ }
78
+ async function createCache() {
79
+ if (await canUseIDB()) {
80
+ const { openDB } = await import("idb");
81
+ const db = await openDB("graffiti-inbox-cache", 1, {
82
+ upgrade(db2) {
83
+ if (!db2.objectStoreNames.contains("m")) db2.createObjectStore("m");
84
+ if (!db2.objectStoreNames.contains("q")) db2.createObjectStore("q");
85
+ }
86
+ });
87
+ return {
88
+ messages: {
89
+ get: async (k) => {
90
+ try {
91
+ return db.get("m", k);
92
+ } catch (error) {
93
+ console.error("Error getting message from cache:", error);
94
+ console.error("resetting cache...");
95
+ await resetCacheDB();
96
+ return void 0;
97
+ }
98
+ },
99
+ set: async (k, v) => {
100
+ await db.put("m", v, k);
101
+ },
102
+ setMany: async (entries) => {
103
+ if (entries.length === 0) return;
104
+ const tx = db.transaction("m", "readwrite");
105
+ await Promise.all([
106
+ ...entries.map(([k, v]) => tx.store.put(v, k)),
107
+ tx.done
108
+ ]);
109
+ },
110
+ del: (k) => db.delete("m", k)
111
+ },
112
+ messageIds: {
113
+ get: async (k) => {
114
+ try {
115
+ return await db.get("q", k);
116
+ } catch (error) {
117
+ console.error("Error getting message IDs from cache:", error);
118
+ console.error("resetting cache...");
119
+ await resetCacheDB();
120
+ return void 0;
121
+ }
122
+ },
123
+ set: async (k, v) => {
124
+ await db.put("q", v, k);
125
+ },
126
+ del: (k) => db.delete("q", k)
127
+ }
128
+ };
129
+ }
130
+ const m = /* @__PURE__ */ new Map();
131
+ const q = /* @__PURE__ */ new Map();
132
+ return {
133
+ messages: {
134
+ get: async (k) => m.get(k),
135
+ set: async (k, v) => void m.set(k, v),
136
+ setMany: async (entries) => {
137
+ for (const [k, v] of entries) m.set(k, v);
138
+ },
139
+ del: async (k) => void m.delete(k)
140
+ },
141
+ messageIds: {
142
+ get: async (k) => q.get(k),
143
+ set: async (k, v) => void q.set(k, v),
144
+ del: async (k) => void q.delete(k)
145
+ }
146
+ };
147
+ }
148
+ export {
149
+ canUseIDB,
150
+ createCache,
151
+ getMessageCacheKey,
152
+ getMessageIdsCacheKey,
153
+ replayQuery,
154
+ storeQueryPage
155
+ };
156
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/1-services/4-inboxes/cache.ts"],
4
+ "sourcesContent": ["import { encode as dagCborEncode } from \"@ipld/dag-cbor\";\nimport { LABELED_MESSAGE_ID_KEY, type LabeledMessageBase } from \"./messages\";\n\n// One record per inbox query. Messages live in the separate message cache;\n// this record tracks their IDs and where to continue fetching from the server.\nexport type CacheQueryValue = {\n // Server continuation state for the next page.\n cursor: string;\n // Identifies this local query history, not a server version. It stays the\n // same as pages arrive; a reset creates a new one so old caller cursors expire.\n version: string;\n // All message IDs fetched for this query so far, in order. A caller's\n // numSeen cursor indexes into this list to replay only unseen messages.\n messageIds: string[];\n waitTil?: number;\n // Changes on every cached page write, even if the page is empty and neither\n // messageIds nor cursor changes. A tab waiting on the query lock can use this\n // to detect and reuse a page fetched by another tab. Older entries lack it.\n generation?: string;\n // Whether the server has another page; needed when reusing another tab's\n // cached page. Older cache entries lack this and are refreshed on next query.\n hasMore?: boolean;\n};\n\nexport async function canUseIDB(): Promise<boolean> {\n try {\n if (!globalThis.indexedDB) return false;\n\n // Small probe database\n await new Promise<void>((resolve, reject) => {\n const req = indexedDB.open(\"__idb_probe__\", 1);\n req.onupgradeneeded = () => req.result.createObjectStore(\"k\");\n req.onsuccess = () => {\n req.result.close();\n resolve();\n };\n req.onerror = () => reject(req.error);\n });\n\n return true;\n } catch {\n return false;\n }\n}\n\nexport type Cache = {\n messages: {\n get(k: string): Promise<LabeledMessageBase | null | undefined>;\n set(k: string, value: LabeledMessageBase | null): Promise<void>;\n setMany(entries: [string, LabeledMessageBase][]): Promise<void>;\n del(k: string): Promise<void>;\n };\n messageIds: {\n get(k: string): Promise<CacheQueryValue | undefined>;\n set(k: string, value: CacheQueryValue): Promise<void>;\n del(k: string): Promise<void>;\n };\n};\n\nexport function getMessageCacheKey(inboxUrl: string, messageId: string) {\n return `${encodeURIComponent(inboxUrl)}:${encodeURIComponent(messageId)}`;\n}\n\nexport async function replayQuery(\n cache: Cache,\n inboxUrl: string,\n messageIdsCacheKey: string,\n query: CacheQueryValue,\n numSeen = 0,\n): Promise<LabeledMessageBase[]> {\n // Filter out all messageIds before\n // the number already seen\n const messageIds = query.messageIds.slice(numSeen);\n\n // Get all the messages pointed to in the cache\n return Promise.all(\n messageIds.map(async (id) => {\n const message = await cache.messages.get(getMessageCacheKey(inboxUrl, id));\n if (!message) {\n // Something is very wrong with the cache,\n // it refers to message IDs that are not cached\n try {\n await cache.messageIds.del(messageIdsCacheKey);\n } catch {}\n throw new Error(\"Cache out of sync - perhaps clear browser storage\");\n }\n return message;\n }),\n );\n}\n\nexport async function storeQueryPage(\n cache: Cache,\n inboxUrl: string,\n messageIdsCacheKey: string,\n previous: CacheQueryValue | undefined,\n page: {\n messages: LabeledMessageBase[];\n cursor: string;\n hasMore: boolean;\n waitTil?: number;\n },\n): Promise<CacheQueryValue> {\n // First cache the messages with their labels\n await cache.messages.setMany(\n page.messages.map((message) => [\n getMessageCacheKey(inboxUrl, message[LABELED_MESSAGE_ID_KEY]),\n message,\n ]),\n );\n // Then store all the messageids\n const query = {\n cursor: page.cursor,\n version: previous?.version ?? crypto.randomUUID(),\n messageIds: [\n ...(previous?.messageIds ?? []),\n ...page.messages.map((message) => message[LABELED_MESSAGE_ID_KEY]),\n ],\n generation: crypto.randomUUID(),\n hasMore: page.hasMore,\n waitTil: page.waitTil,\n };\n await cache.messageIds.set(messageIdsCacheKey, query);\n return query;\n}\n\nexport async function getMessageIdsCacheKey(\n inboxUrl: string,\n type: \"query\" | \"export\",\n body?: Uint8Array,\n): Promise<string> {\n const cacheIdData = dagCborEncode({\n inboxUrl,\n type,\n body: body ?? null,\n });\n return crypto.subtle\n .digest(\"SHA-256\", new Uint8Array(cacheIdData))\n .then((bytes) =>\n Array.from(new Uint8Array(bytes))\n .map((byte) => byte.toString(16).padStart(2, \"0\"))\n .join(\"\"),\n );\n}\n\nasync function resetCacheDB() {\n await new Promise<void>((resolve) => {\n const req = indexedDB.deleteDatabase(\"graffiti-inbox-cache\");\n req.onsuccess = () => resolve();\n req.onerror = () => resolve(); // best effort\n req.onblocked = () => resolve(); // best effort\n });\n}\n\nexport async function createCache(): Promise<Cache> {\n if (await canUseIDB()) {\n const { openDB } = await import(\"idb\");\n const db = await openDB(\"graffiti-inbox-cache\", 1, {\n upgrade(db) {\n if (!db.objectStoreNames.contains(\"m\")) db.createObjectStore(\"m\");\n if (!db.objectStoreNames.contains(\"q\")) db.createObjectStore(\"q\");\n },\n });\n\n return {\n messages: {\n get: async (k) => {\n try {\n return db.get(\"m\", k);\n } catch (error) {\n console.error(\"Error getting message from cache:\", error);\n console.error(\"resetting cache...\");\n await resetCacheDB();\n return undefined;\n }\n },\n set: async (k, v) => {\n await db.put(\"m\", v, k);\n },\n setMany: async (entries) => {\n if (entries.length === 0) return;\n const tx = db.transaction(\"m\", \"readwrite\");\n await Promise.all([\n ...entries.map(([k, v]) => tx.store.put(v, k)),\n tx.done,\n ]);\n },\n del: (k) => db.delete(\"m\", k),\n },\n messageIds: {\n get: async (k) => {\n try {\n return await db.get(\"q\", k);\n } catch (error) {\n console.error(\"Error getting message IDs from cache:\", error);\n console.error(\"resetting cache...\");\n await resetCacheDB();\n return undefined;\n }\n },\n set: async (k, v) => {\n await db.put(\"q\", v, k);\n },\n del: (k) => db.delete(\"q\", k),\n },\n };\n }\n\n const m = new Map<string, LabeledMessageBase | null>();\n const q = new Map<string, CacheQueryValue>();\n\n return {\n messages: {\n get: async (k) => m.get(k),\n set: async (k, v) => void m.set(k, v),\n setMany: async (entries) => {\n for (const [k, v] of entries) m.set(k, v);\n },\n del: async (k) => void m.delete(k),\n },\n messageIds: {\n get: async (k) => q.get(k),\n set: async (k, v) => void q.set(k, v),\n del: async (k) => void q.delete(k),\n },\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["db"]
7
+ }
@@ -0,0 +1,60 @@
1
+ import {
2
+ string,
3
+ url,
4
+ array,
5
+ optional,
6
+ nullable,
7
+ strictObject,
8
+ looseObject,
9
+ custom,
10
+ number,
11
+ boolean
12
+ } from "zod/mini";
13
+ const GraffitiObjectSchema = strictObject({
14
+ value: looseObject({}),
15
+ channels: array(string()),
16
+ allowed: optional(nullable(array(url()))),
17
+ url: url(),
18
+ actor: url()
19
+ });
20
+ const Uint8ArraySchema = custom(
21
+ (v) => v instanceof Uint8Array
22
+ );
23
+ const TagsSchema = array(Uint8ArraySchema);
24
+ const MESSAGE_TAGS_KEY = "t";
25
+ const MESSAGE_OBJECT_KEY = "o";
26
+ const MESSAGE_METADATA_KEY = "m";
27
+ const MessageBaseSchema = strictObject({
28
+ [MESSAGE_TAGS_KEY]: TagsSchema,
29
+ [MESSAGE_OBJECT_KEY]: GraffitiObjectSchema,
30
+ [MESSAGE_METADATA_KEY]: Uint8ArraySchema
31
+ });
32
+ const LABELED_MESSAGE_ID_KEY = "id";
33
+ const LABELED_MESSAGE_MESSAGE_KEY = "m";
34
+ const LABELED_MESSAGE_LABEL_KEY = "l";
35
+ const LabeledMessageBaseSchema = strictObject({
36
+ [LABELED_MESSAGE_ID_KEY]: string(),
37
+ [LABELED_MESSAGE_MESSAGE_KEY]: MessageBaseSchema,
38
+ [LABELED_MESSAGE_LABEL_KEY]: number()
39
+ });
40
+ const SendResponseSchema = strictObject({ id: string() });
41
+ const MessageResultSchema = strictObject({
42
+ results: array(LabeledMessageBaseSchema),
43
+ hasMore: boolean(),
44
+ cursor: string()
45
+ });
46
+ export {
47
+ LABELED_MESSAGE_ID_KEY,
48
+ LABELED_MESSAGE_LABEL_KEY,
49
+ LABELED_MESSAGE_MESSAGE_KEY,
50
+ LabeledMessageBaseSchema,
51
+ MESSAGE_METADATA_KEY,
52
+ MESSAGE_OBJECT_KEY,
53
+ MESSAGE_TAGS_KEY,
54
+ MessageBaseSchema,
55
+ MessageResultSchema,
56
+ SendResponseSchema,
57
+ TagsSchema,
58
+ Uint8ArraySchema
59
+ };
60
+ //# sourceMappingURL=messages.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/1-services/4-inboxes/messages.ts"],
4
+ "sourcesContent": ["import type { JSONSchema, GraffitiObject } from \"@graffiti-garden/api\";\nimport {\n type infer as infer_,\n string,\n url,\n array,\n optional,\n nullable,\n strictObject,\n looseObject,\n custom,\n number,\n boolean,\n} from \"zod/mini\";\n\nconst GraffitiObjectSchema = strictObject({\n value: looseObject({}),\n channels: array(string()),\n allowed: optional(nullable(array(url()))),\n url: url(),\n actor: url(),\n});\nexport const Uint8ArraySchema = custom<Uint8Array>(\n (v): v is Uint8Array => v instanceof Uint8Array,\n);\nexport const TagsSchema = array(Uint8ArraySchema);\n\nexport const MESSAGE_TAGS_KEY = \"t\";\nexport const MESSAGE_OBJECT_KEY = \"o\";\nexport const MESSAGE_METADATA_KEY = \"m\";\nexport const MessageBaseSchema = strictObject({\n [MESSAGE_TAGS_KEY]: TagsSchema,\n [MESSAGE_OBJECT_KEY]: GraffitiObjectSchema,\n [MESSAGE_METADATA_KEY]: Uint8ArraySchema,\n});\ntype MessageBase = infer_<typeof MessageBaseSchema>;\n\nexport const LABELED_MESSAGE_ID_KEY = \"id\";\nexport const LABELED_MESSAGE_MESSAGE_KEY = \"m\";\nexport const LABELED_MESSAGE_LABEL_KEY = \"l\";\nexport const LabeledMessageBaseSchema = strictObject({\n [LABELED_MESSAGE_ID_KEY]: string(),\n [LABELED_MESSAGE_MESSAGE_KEY]: MessageBaseSchema,\n [LABELED_MESSAGE_LABEL_KEY]: number(),\n});\nexport type LabeledMessageBase = infer_<typeof LabeledMessageBaseSchema>;\n\nexport type Message<Schema extends JSONSchema> = MessageBase & {\n [MESSAGE_OBJECT_KEY]: GraffitiObject<Schema>;\n};\nexport type LabeledMessage<Schema extends JSONSchema> = LabeledMessageBase & {\n [LABELED_MESSAGE_MESSAGE_KEY]: {\n [MESSAGE_OBJECT_KEY]: GraffitiObject<Schema>;\n };\n};\n\nexport const SendResponseSchema = strictObject({ id: string() });\n\nexport const MessageResultSchema = strictObject({\n results: array(LabeledMessageBaseSchema),\n hasMore: boolean(),\n cursor: string(),\n});\n"],
5
+ "mappings": "AACA;AAAA,EAEE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,MAAM,uBAAuB,aAAa;AAAA,EACxC,OAAO,YAAY,CAAC,CAAC;AAAA,EACrB,UAAU,MAAM,OAAO,CAAC;AAAA,EACxB,SAAS,SAAS,SAAS,MAAM,IAAI,CAAC,CAAC,CAAC;AAAA,EACxC,KAAK,IAAI;AAAA,EACT,OAAO,IAAI;AACb,CAAC;AACM,MAAM,mBAAmB;AAAA,EAC9B,CAAC,MAAuB,aAAa;AACvC;AACO,MAAM,aAAa,MAAM,gBAAgB;AAEzC,MAAM,mBAAmB;AACzB,MAAM,qBAAqB;AAC3B,MAAM,uBAAuB;AAC7B,MAAM,oBAAoB,aAAa;AAAA,EAC5C,CAAC,gBAAgB,GAAG;AAAA,EACpB,CAAC,kBAAkB,GAAG;AAAA,EACtB,CAAC,oBAAoB,GAAG;AAC1B,CAAC;AAGM,MAAM,yBAAyB;AAC/B,MAAM,8BAA8B;AACpC,MAAM,4BAA4B;AAClC,MAAM,2BAA2B,aAAa;AAAA,EACnD,CAAC,sBAAsB,GAAG,OAAO;AAAA,EACjC,CAAC,2BAA2B,GAAG;AAAA,EAC/B,CAAC,yBAAyB,GAAG,OAAO;AACtC,CAAC;AAYM,MAAM,qBAAqB,aAAa,EAAE,IAAI,OAAO,EAAE,CAAC;AAExD,MAAM,sBAAsB,aAAa;AAAA,EAC9C,SAAS,MAAM,wBAAwB;AAAA,EACvC,SAAS,QAAQ;AAAA,EACjB,QAAQ,OAAO;AACjB,CAAC;",
6
+ "names": []
7
+ }