@graffiti-garden/implementation-decentralized 0.0.3 → 0.0.4
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/dist/3-protocol/3-object-encoding.d.ts.map +1 -1
- package/dist/3-protocol/4-graffiti.d.ts +2 -1
- package/dist/3-protocol/4-graffiti.d.ts.map +1 -1
- package/dist/browser/index.js +7 -7
- package/dist/browser/index.js.map +3 -3
- package/dist/browser/{style-3ALLGCD7-QNFKN6AK.js → style-RMTPI5KV-Y5KAOOZR.js} +2 -1
- package/dist/browser/style-RMTPI5KV-Y5KAOOZR.js.map +7 -0
- package/dist/cjs/3-protocol/1-sessions.js +1 -1
- package/dist/cjs/3-protocol/1-sessions.js.map +2 -2
- package/dist/cjs/3-protocol/3-object-encoding.js +3 -2
- package/dist/cjs/3-protocol/3-object-encoding.js.map +2 -2
- package/dist/cjs/3-protocol/4-graffiti.js +182 -152
- package/dist/cjs/3-protocol/4-graffiti.js.map +3 -3
- package/dist/esm/3-protocol/1-sessions.js +1 -1
- package/dist/esm/3-protocol/1-sessions.js.map +2 -2
- package/dist/esm/3-protocol/3-object-encoding.js +3 -2
- package/dist/esm/3-protocol/3-object-encoding.js.map +2 -2
- package/dist/esm/3-protocol/4-graffiti.js +182 -152
- package/dist/esm/3-protocol/4-graffiti.js.map +3 -3
- package/package.json +7 -7
- package/src/3-protocol/1-sessions.ts +1 -1
- package/src/3-protocol/3-object-encoding.ts +4 -2
- package/src/3-protocol/4-graffiti.ts +242 -200
- package/dist/browser/style-3ALLGCD7-QNFKN6AK.js.map +0 -7
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/3-protocol/4-graffiti.ts"],
|
|
4
|
-
"sourcesContent": ["import type { JSONSchema } from \"json-schema-to-ts\";\nimport {\n GraffitiErrorNotFound,\n maskGraffitiObject,\n type Graffiti,\n type GraffitiLoginEvent,\n type GraffitiObjectBase,\n type GraffitiSession,\n type GraffitiObject,\n unpackObjectUrl,\n compileGraffitiObjectSchema,\n GraffitiErrorSchemaMismatch,\n GraffitiErrorForbidden,\n GraffitiErrorTooLarge,\n isMediaAcceptable,\n GraffitiErrorNotAcceptable,\n GraffitiErrorCursorExpired,\n GraffitiErrorInvalidSchema,\n type GraffitiObjectStream,\n} from \"@graffiti-garden/api\";\nimport { randomBytes } from \"@noble/hashes/utils.js\";\nimport {\n encode as dagCborEncode,\n decode as dagCborDecode,\n} from \"@ipld/dag-cbor\";\n\nimport { DecentralizedIdentifiers } from \"../1-services/2-dids\";\nimport { Authorization } from \"../1-services/1-authorization\";\nimport { StorageBuckets } from \"../1-services/3-storage-buckets\";\nimport {\n Inboxes,\n LABELED_MESSAGE_LABEL_KEY,\n LABELED_MESSAGE_MESSAGE_KEY,\n MESSAGE_METADATA_KEY,\n MESSAGE_OBJECT_KEY,\n MESSAGE_TAGS_KEY,\n type MessageStream,\n} from \"../1-services/4-inboxes\";\n\nimport {\n StringEncoder,\n STRING_ENCODER_METHOD_BASE64URL,\n} from \"../2-primitives/1-string-encoding\";\nimport { ContentAddresses } from \"../2-primitives/2-content-addresses\";\nimport {\n CHANNEL_ATTESTATION_METHOD_SHA256_ED25519,\n ChannelAttestations,\n} from \"../2-primitives/3-channel-attestations\";\nimport { AllowedAttestations } from \"../2-primitives/4-allowed-attestations\";\n\nimport { Handles } from \"./2-handles\";\nimport {\n Sessions,\n DID_SERVICE_ID_GRAFFITI_PERSONAL_INBOX,\n DID_SERVICE_TYPE_GRAFFITI_INBOX,\n DID_SERVICE_ID_GRAFFITI_STORAGE_BUCKET,\n DID_SERVICE_TYPE_GRAFFITI_STORAGE_BUCKET,\n} from \"./1-sessions\";\nimport {\n decodeObjectUrl,\n MAX_OBJECT_SIZE_BYTES,\n ObjectEncoding,\n} from \"./3-object-encoding\";\n\nimport { GraffitiModal } from \"@graffiti-garden/modal\";\nimport {\n type infer as infer_,\n custom,\n string,\n boolean,\n strictObject,\n array,\n int,\n nonnegative,\n optional,\n extend,\n union,\n record,\n url,\n} from \"zod/mini\";\n\nconst Uint8ArraySchema = custom<Uint8Array>(\n (v): v is Uint8Array => v instanceof Uint8Array,\n);\nconst MESSAGE_DATA_STORAGE_BUCKET_KEY = \"k\";\nconst MESSAGE_DATA_TOMBSTONED_MESSAGE_ID_KEY = \"t\";\nconst MessageMetadataBaseSchema = strictObject({\n [MESSAGE_DATA_STORAGE_BUCKET_KEY]: string(),\n [MESSAGE_DATA_TOMBSTONED_MESSAGE_ID_KEY]: optional(string()),\n});\nconst MESSAGE_DATA_ANNOUNCEMENT_MESSAGE_ID_KEY = \"id\";\nconst MESSAGE_DATA_ANNOUNCEMENT_ENDPOINT_KEY = \"e\";\nconst MESSAGE_DATA_ANNOUNCEMENT_ACTOR_KEY = \"a\";\nconst MessageMetadataAnnouncementsSchema = array(\n strictObject({\n [MESSAGE_DATA_ANNOUNCEMENT_MESSAGE_ID_KEY]: string(),\n [MESSAGE_DATA_ANNOUNCEMENT_ENDPOINT_KEY]: optional(url()),\n [MESSAGE_DATA_ANNOUNCEMENT_ACTOR_KEY]: optional(url()),\n }),\n);\nconst MESSAGE_DATA_ALLOWED_TICKETS_KEY = \"s\";\nconst MESSAGE_DATA_ANNOUNCEMENTS_KEY = \"n\";\nconst MessageMetaDataSelfSchema = extend(MessageMetadataBaseSchema, {\n [MESSAGE_DATA_ALLOWED_TICKETS_KEY]: optional(array(Uint8ArraySchema)),\n [MESSAGE_DATA_ANNOUNCEMENTS_KEY]: MessageMetadataAnnouncementsSchema,\n});\nconst MESSAGE_DATA_ALLOWED_TICKET_KEY = \"a\";\nconst MESSAGE_DATA_ALLOWED_TICKET_INDEX_KEY = \"i\";\nconst MessageMetadataPrivateSchema = extend(MessageMetadataBaseSchema, {\n [MESSAGE_DATA_ALLOWED_TICKET_KEY]: Uint8ArraySchema,\n [MESSAGE_DATA_ALLOWED_TICKET_INDEX_KEY]: int().check(nonnegative()),\n});\nconst MessageMetadataSchema = union([\n MessageMetadataBaseSchema,\n MessageMetaDataSelfSchema,\n MessageMetadataPrivateSchema,\n]);\ntype MessageMetadataBase = infer_<typeof MessageMetadataBaseSchema>;\ntype MessageMetadata = infer_<typeof MessageMetadataSchema>;\ntype MessageMetadataAnnouncements = infer_<\n typeof MessageMetadataAnnouncementsSchema\n>;\n\nconst MESSAGE_LABEL_UNLABELED = 0;\nconst MESSAGE_LABEL_VALID = 1;\nconst MESSAGE_LABEL_TRASH = 2;\nconst MESSAGE_LABEL_INVALID = 3;\n\nexport interface GraffitiDecentralizedOptions {\n identityCreatorEndpoint?: string;\n defaultInboxEndpoints?: string[];\n}\n\nexport class GraffitiDecentralized implements Graffiti {\n protected readonly dids = new DecentralizedIdentifiers();\n protected readonly authorization = new Authorization();\n protected readonly storageBuckets = new StorageBuckets();\n protected readonly inboxes = new Inboxes();\n\n protected readonly stringEncoder = new StringEncoder();\n protected readonly contentAddresses = new ContentAddresses();\n protected readonly channelAttestations = new ChannelAttestations();\n protected readonly allowedAttestations = new AllowedAttestations();\n\n protected readonly sessions = new Sessions({\n dids: this.dids,\n authorization: this.authorization,\n storageBuckets: this.storageBuckets,\n inboxes: this.inboxes,\n });\n protected readonly handles = new Handles({ dids: this.dids });\n protected readonly objectEncoding = new ObjectEncoding({\n stringEncoder: this.stringEncoder,\n contentAddresses: this.contentAddresses,\n channelAttestations: this.channelAttestations,\n allowedAttestations: this.allowedAttestations,\n });\n\n protected readonly modal: GraffitiModal | undefined =\n typeof window === \"undefined\"\n ? undefined\n : new GraffitiModal({\n useTemplateHTML: () =>\n import(\"./login-dialog.html\").then(({ template }) => template),\n onManualClose: () => {\n const event = new CustomEvent(\"login\", {\n detail: {\n error: new Error(\"User cancelled login\"),\n manual: true,\n },\n });\n this.sessionEvents.dispatchEvent(event);\n },\n });\n\n protected readonly defaultInboxEndpoints: string[];\n protected readonly identityCreatorEndpoint: string;\n constructor(options?: GraffitiDecentralizedOptions) {\n this.defaultInboxEndpoints = options?.defaultInboxEndpoints ?? [\n \"https://graffiti.actor/i/shared\",\n ];\n this.identityCreatorEndpoint =\n options?.identityCreatorEndpoint ?? \"https://graffiti.actor/create\";\n\n this.sessionEvents.addEventListener(\"login\", async (event) => {\n if (!(event instanceof CustomEvent)) return;\n const detail = event.detail as GraffitiLoginEvent[\"detail\"];\n if (\n detail.error !== undefined &&\n !(\"manual\" in detail && detail.manual)\n ) {\n alert(\"Login failed: \" + detail.error.message);\n const actor = detail.session?.actor;\n let handle: string | undefined;\n if (actor) {\n try {\n handle = await this.actorToHandle(actor);\n } catch (error) {\n console.error(\"Failed to handle actor:\", error);\n }\n }\n this.login_(handle);\n }\n });\n }\n\n readonly actorToHandle: Graffiti[\"actorToHandle\"] =\n this.handles.actorToHandle.bind(this.handles);\n readonly handleToActor: Graffiti[\"handleToActor\"] =\n this.handles.handleToActor.bind(this.handles);\n readonly sessionEvents: Graffiti[\"sessionEvents\"] =\n this.sessions.sessionEvents;\n\n login: Graffiti[\"login\"] = async (actor?: string) => {\n try {\n let proposedHandle: string | undefined;\n try {\n proposedHandle = actor ? await this.actorToHandle(actor) : undefined;\n } catch (error) {\n console.error(\"Error fetching handle for actor:\", error);\n }\n\n await this.login_(proposedHandle);\n } catch (e) {\n const loginError: GraffitiLoginEvent = new CustomEvent(\"login\", {\n detail: {\n error: e instanceof Error ? e : new Error(String(e)),\n },\n });\n this.sessionEvents.dispatchEvent(loginError);\n }\n };\n protected async login_(proposedHandle?: string) {\n if (typeof window !== \"undefined\") {\n let template: HTMLElement | undefined;\n if (proposedHandle !== undefined) {\n template = await this.modal?.displayTemplate(\"graffiti-login-handle\");\n const input = template?.querySelector(\n \"#username\",\n ) as HTMLInputElement | null;\n input?.setAttribute(\"value\", proposedHandle);\n input?.addEventListener(\"focus\", () => input?.select());\n setTimeout(() => input?.focus(), 0);\n\n template\n ?.querySelector(\"#graffiti-login-handle-form\")\n ?.addEventListener(\"submit\", async (e) => {\n e.preventDefault();\n input?.setAttribute(\"disabled\", \"true\");\n const submitButton = template?.querySelector(\n \"#graffiti-login-handle-submit\",\n ) as HTMLButtonElement | null;\n submitButton?.setAttribute(\"disabled\", \"true\");\n submitButton && (submitButton.innerHTML = \"Logging in...\");\n\n if (!input?.value) {\n alert(\"No handle provided\");\n this.login_(\"\");\n return;\n }\n\n let handle = input.value;\n if (!handle.includes(\".\") && !handle.startsWith(\"localhost\")) {\n const defaultHost = new URL(this.identityCreatorEndpoint).host;\n handle = `${handle}.${defaultHost}`;\n }\n\n let actor: string;\n try {\n actor = await this.handleToActor(handle);\n } catch (e) {\n alert(\"Could not find an identity associated with that handle.\");\n this.login_(handle);\n return;\n }\n\n try {\n await this.sessions.login(actor);\n } catch (e) {\n alert(\"Error logging in.\");\n console.error(e);\n this.login_(handle);\n }\n });\n } else {\n template = await this.modal?.displayTemplate(\"graffiti-login-welcome\");\n template\n ?.querySelector(\"#graffiti-login-existing\")\n ?.addEventListener(\"click\", (e) => {\n e.preventDefault();\n this.login_(\"\");\n });\n\n setTimeout(\n () =>\n (\n template?.querySelector(\n \"#graffiti-login-new\",\n ) as HTMLAnchorElement\n )?.focus(),\n 0,\n );\n }\n\n const createUrl = new URL(this.identityCreatorEndpoint);\n createUrl.searchParams.set(\n \"redirect_uri\",\n encodeURIComponent(window.location.toString()),\n );\n template\n ?.querySelector(\"#graffiti-login-new\")\n ?.setAttribute(\"href\", createUrl.toString());\n\n await this.modal?.open();\n } else {\n // Node.js environment\n const readline = await import(\"readline\").catch((e) => {\n throw new Error(\n \"Unrecognized environment: neither window nor readline\",\n );\n });\n\n console.log(\n \"If you do not already have a Graffiti handle, you can create one here:\",\n );\n console.log(this.identityCreatorEndpoint);\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n const handle: string | undefined = await new Promise((resolve) => {\n rl.question(\n `Please enter your handle${proposedHandle ? ` (default: ${proposedHandle})` : \"\"}: `,\n (input) => {\n rl.close();\n resolve(input || proposedHandle);\n },\n );\n });\n\n if (!handle) {\n throw new Error(\"No handle provided\");\n }\n\n // Convert the handle to an actor\n const actor = await this.handleToActor(handle);\n\n await this.sessions.login(actor);\n }\n }\n\n logout: Graffiti[\"logout\"] = async (session) => {\n await this.sessions.logout(session.actor);\n };\n\n // @ts-ignore\n post: Graffiti[\"post\"] = async (...args) => {\n const [partialObject, session] = args;\n const resolvedSession = this.sessions.resolveSession(session);\n\n // Encode the object\n const { object, tags, objectBytes, allowedTickets } =\n await this.objectEncoding.encode<{}>(partialObject, session.actor);\n\n // Generate a random key under which to store the object\n // If the object is private, this means no one will be able to\n // fetch the object, even if they know its URL.\n // If the object is public but in some secret channel, the storage\n // location means the object can be moved around or \"rotated\"\n // without changing its URL.\n const storageBucketKeyBytes = randomBytes();\n const storageBucketKey = await this.stringEncoder.encode(\n STRING_ENCODER_METHOD_BASE64URL,\n storageBucketKeyBytes,\n );\n\n // Store the object at the random key\n await this.storageBuckets.put(\n resolvedSession.storageBucket.serviceEndpoint,\n storageBucketKey,\n objectBytes,\n resolvedSession.storageBucket.token,\n );\n\n // Announce the object, its key,\n // and other metadata to appropriate inboxes\n await this.announceObject(\n object,\n tags,\n allowedTickets,\n storageBucketKey,\n session,\n );\n\n return object;\n };\n\n get: Graffiti[\"get\"] = async (...args) => {\n const [url, schema, session] = args;\n let services: { token?: string; serviceEndpoint: string }[];\n const validator = await compileGraffitiObjectSchema(schema);\n\n if (session) {\n // If logged in, first search one's\n // personal inbox, then any shared inboxes\n const resolvedSession = this.sessions.resolveSession(session);\n services = [\n resolvedSession.personalInbox,\n ...resolvedSession.sharedInboxes,\n ];\n } else {\n // Otherwise, search the default inboxes\n services = this.defaultInboxEndpoints.map((s) => ({\n serviceEndpoint: s,\n }));\n }\n\n // Search the inboxes for all objects\n // matching the tag, object.url\n const objectUrl = unpackObjectUrl(url);\n const tags = [new TextEncoder().encode(objectUrl)];\n for (const service of services) {\n let object: GraffitiObjectBase | undefined = undefined;\n\n const iterator = this.querySingleEndpoint<{}>(\n service.serviceEndpoint,\n {\n tags,\n objectSchema: {},\n },\n service.token,\n session?.actor,\n );\n\n for await (const result of iterator) {\n if (result.object.url !== objectUrl) continue;\n if (result.tombstone) {\n object = undefined;\n } else {\n object = result.object;\n }\n }\n\n if (object) {\n if (!validator(object)) {\n throw new GraffitiErrorSchemaMismatch(\n \"Object exists but does not match the supplied schema\",\n );\n }\n\n return object;\n }\n }\n\n throw new GraffitiErrorNotFound(\"Object not found\");\n };\n\n delete: Graffiti[\"delete\"] = async (url, session) => {\n const resolvedSession = this.sessions.resolveSession(session);\n\n const objectUrl = unpackObjectUrl(url);\n\n const { actor } = decodeObjectUrl(objectUrl);\n if (actor !== session.actor) {\n throw new GraffitiErrorForbidden(\"Cannot delete someone else's actor\");\n }\n\n // Look in one's personal inbox for the object\n const iterator = this.querySingleEndpoint<{}>(\n resolvedSession.personalInbox.serviceEndpoint,\n {\n tags: [new TextEncoder().encode(objectUrl)],\n objectSchema: {},\n },\n resolvedSession.personalInbox.token,\n );\n let existing: SingleEndpointQueryResult<{}> | undefined;\n for await (const result of iterator) {\n if (result.object.url !== objectUrl) continue;\n if (result.tombstone) {\n existing = undefined;\n } else {\n existing = result;\n }\n }\n if (!existing) {\n throw new GraffitiErrorNotFound(`Object ${objectUrl} not found`);\n }\n const {\n object,\n storageBucketKey,\n tags,\n allowedTickets,\n announcements,\n messageId,\n } = existing;\n\n // Delete the object from the actor's own storage bucket\n await this.storageBuckets.delete(\n resolvedSession.storageBucket.serviceEndpoint,\n storageBucketKey,\n resolvedSession.storageBucket.token,\n );\n\n // Announce the deletion to all inboxes\n await this.announceObject(\n object,\n tags,\n allowedTickets,\n storageBucketKey,\n session,\n [\n ...(announcements ?? []),\n // Make sure we delete from our own inbox too\n {\n [MESSAGE_DATA_ANNOUNCEMENT_ACTOR_KEY]: session.actor,\n [MESSAGE_DATA_ANNOUNCEMENT_MESSAGE_ID_KEY]: messageId,\n },\n ],\n );\n\n return object;\n };\n\n postMedia: Graffiti[\"postMedia\"] = async (...args) => {\n const [media, session] = args;\n\n const type = media.data.type;\n\n const resolvedSession = this.sessions.resolveSession(session);\n\n // Generate a random storage key\n const keyBytes = randomBytes();\n const key = await this.stringEncoder.encode(\n STRING_ENCODER_METHOD_BASE64URL,\n keyBytes,\n );\n\n // Store the media at that key\n await this.storageBuckets.put(\n resolvedSession.storageBucket.serviceEndpoint,\n key,\n new Uint8Array(await media.data.arrayBuffer()),\n resolvedSession.storageBucket.token,\n );\n\n // Create an object\n const { url } = await this.post<typeof MEDIA_OBJECT_SCHEMA>(\n {\n value: {\n key,\n type,\n size: media.data.size,\n },\n channels: [],\n allowed: media.allowed,\n },\n session,\n );\n\n return url;\n };\n\n getMedia: Graffiti[\"getMedia\"] = async (...args) => {\n const [mediaUrl, accept, session] = args;\n\n const object = await this.get<typeof MEDIA_OBJECT_SCHEMA>(\n mediaUrl,\n MEDIA_OBJECT_SCHEMA,\n session,\n );\n\n const { key, type, size } = object.value;\n\n if (accept?.maxBytes && size > accept.maxBytes) {\n throw new GraffitiErrorTooLarge(\"File size exceeds limit\");\n }\n\n // Make sure it adheres to requirements.accept\n if (accept?.types) {\n if (!isMediaAcceptable(type, accept.types)) {\n throw new GraffitiErrorNotAcceptable(\n `Unacceptable media type, ${type}`,\n );\n }\n }\n\n // Get the actor's storage bucket endpoint\n const actorDocument = await this.dids.resolve(object.actor);\n const storageBucketService = actorDocument?.service?.find(\n (service) =>\n service.id === DID_SERVICE_ID_GRAFFITI_STORAGE_BUCKET &&\n service.type === DID_SERVICE_TYPE_GRAFFITI_STORAGE_BUCKET,\n );\n if (!storageBucketService) {\n throw new GraffitiErrorNotFound(\n `Actor ${object.actor} has no storage bucket service`,\n );\n }\n if (typeof storageBucketService.serviceEndpoint !== \"string\") {\n throw new GraffitiErrorNotFound(\n `Actor ${object.actor} does not have a valid storage bucket endpoint`,\n );\n }\n const storageBucketEndpoint = storageBucketService.serviceEndpoint;\n\n const data = await this.storageBuckets.get(\n storageBucketEndpoint,\n key,\n size,\n );\n\n const blob = new Blob([data.slice()], { type });\n\n return {\n data: blob,\n actor: object.actor,\n allowed: object.allowed,\n };\n };\n\n deleteMedia: Graffiti[\"deleteMedia\"] = async (...args) => {\n const [mediaUrl, session] = args;\n\n const resolvedSession = this.sessions.resolveSession(session);\n\n const result = await this.delete(mediaUrl, session);\n\n if (!(\"key\" in result.value && typeof result.value.key === \"string\"))\n throw new Error(\n \"Deleted object was not media: \" + JSON.stringify(result, null, 2),\n );\n\n await this.storageBuckets.delete(\n resolvedSession.storageBucket.serviceEndpoint,\n result.value.key,\n resolvedSession.storageBucket.token,\n );\n };\n\n async *discoverMeta<Schema extends JSONSchema>(\n channels: string[],\n schema: Schema,\n cursors: {\n [endpoint: string]: string;\n },\n session?: GraffitiSession | null,\n ): GraffitiObjectStream<Schema> {\n const tombstones = new Map<string, boolean>();\n\n let allInboxes: { serviceEndpoint: string; token?: string }[];\n if (session) {\n const resolvedSession = this.sessions.resolveSession(session);\n allInboxes = [\n resolvedSession.personalInbox,\n ...resolvedSession.sharedInboxes,\n ];\n } else {\n allInboxes = this.defaultInboxEndpoints.map((e) => ({\n serviceEndpoint: e,\n }));\n }\n\n // Make sure all cursors are represented by an inbox\n for (const endpoint in cursors) {\n if (!allInboxes.some((i) => i.serviceEndpoint === endpoint)) {\n throw new GraffitiErrorForbidden(\n \"Cursor does not match actor's inboxes\",\n );\n }\n }\n\n // Turn the channels into tags\n const tags = await Promise.all(\n channels.map((c) =>\n this.channelAttestations.register(\n CHANNEL_ATTESTATION_METHOD_SHA256_ED25519,\n c,\n ),\n ),\n );\n\n const iterators: SingleEndpointQueryIterator<Schema>[] = allInboxes.map(\n (i) => {\n const cursor = cursors[i.serviceEndpoint];\n return this.querySingleEndpoint<Schema>(\n i.serviceEndpoint,\n cursor\n ? {\n cursor,\n }\n : {\n tags,\n objectSchema: schema,\n },\n i.token,\n session?.actor,\n );\n },\n );\n\n let indexedIteratorNexts = iterators.map<\n Promise<IndexedSingleEndpointQueryResult<Schema>>\n >(async (it, index) => indexedSingleEndpointQueryNext<Schema>(it, index));\n let active = indexedIteratorNexts.length;\n\n while (active > 0) {\n const next: IndexedSingleEndpointQueryResult<Schema> =\n await Promise.race<any>(indexedIteratorNexts);\n if (next.error !== undefined) {\n // Remove it from the race\n indexedIteratorNexts[next.index] = new Promise(() => {});\n active--;\n yield {\n error: next.error,\n origin: allInboxes[next.index].serviceEndpoint,\n };\n } else if (next.result.done) {\n // Store the cursor for future use\n const inbox = allInboxes[next.index];\n cursors[inbox.serviceEndpoint] = next.result.value;\n // Remove it from the race\n indexedIteratorNexts[next.index] = new Promise(() => {});\n active--;\n } else {\n // Re-arm the iterator\n indexedIteratorNexts[next.index] =\n indexedSingleEndpointQueryNext<Schema>(\n iterators[next.index],\n next.index,\n );\n const { object, tombstone, tags: receivedTags } = next.result.value;\n if (tombstone) {\n if (tombstones.get(object.url) === true) continue;\n tombstones.set(object.url, true);\n yield {\n tombstone,\n object: { url: object.url },\n };\n } else {\n // Filter already seen\n if (tombstones.get(object.url) === false) continue;\n\n // Fill in the matched channels\n const matchedTagIndices = tags.reduce<number[]>(\n (acc, tag, tagIndex) => {\n for (const receivedTag of receivedTags) {\n if (\n tag.length === receivedTag.length &&\n tag.every((b, i) => receivedTag[i] === b)\n ) {\n acc.push(tagIndex);\n break;\n }\n }\n return acc;\n },\n [],\n );\n const matchedChannels = matchedTagIndices.map(\n (index) => channels[index],\n );\n if (matchedChannels.length === 0) {\n yield {\n error: new Error(\n \"Inbox returned object without matching channels\",\n ),\n origin: allInboxes[next.index].serviceEndpoint,\n };\n }\n tombstones.set(object.url, false);\n yield {\n object: {\n ...object,\n channels: matchedChannels,\n },\n };\n }\n }\n }\n\n return {\n cursor: JSON.stringify({\n channels,\n cursors,\n } satisfies infer_<typeof CursorSchema>),\n continue: (session) =>\n this.discoverMeta<Schema>(channels, schema, cursors, session),\n };\n }\n\n discover: Graffiti[\"discover\"] = (...args) => {\n const [channels, schema, session] = args;\n return this.discoverMeta<(typeof args)[1]>(channels, schema, {}, session);\n };\n\n continueDiscover: Graffiti[\"continueDiscover\"] = (...args) => {\n const [cursor, session] = args;\n // Extract the channels from the cursor\n let channels: string[];\n let cursors: { [endpoint: string]: string };\n try {\n const json = JSON.parse(cursor);\n const parsed = CursorSchema.parse(json);\n channels = parsed.channels;\n cursors = parsed.cursors;\n } catch (error) {\n return (async function* () {\n throw new GraffitiErrorCursorExpired(\"Invalid cursor\");\n })();\n }\n return this.discoverMeta<{}>(channels, {}, cursors, session);\n };\n\n async announceObject(\n object: GraffitiObjectBase,\n tags: Uint8Array[],\n allowedTickets: Uint8Array[] | undefined,\n storageBucketKey: string,\n session: GraffitiSession,\n priorAnnouncements?: MessageMetadataAnnouncements,\n ): Promise<void> {\n const resolvedSession = this.sessions.resolveSession(session);\n\n const metadataBase: MessageMetadataBase = {\n [MESSAGE_DATA_STORAGE_BUCKET_KEY]: storageBucketKey,\n };\n\n const announcements: MessageMetadataAnnouncements = [];\n const allowed = object.allowed;\n if (Array.isArray(allowed)) {\n if (!allowedTickets || allowedTickets.length !== allowed.length) {\n throw new Error(\n \"If allowed actors are specified, there must be a corresponding ticket for each allowed actor\",\n );\n }\n\n // Send the object to each allowed recipient's personal inbox\n const results = await Promise.allSettled(\n allowed.map(async (recipient, recipientIndex) => {\n // Mask the object to not include any channels\n // and only include the recipient actor on the allowed list\n const copy = JSON.parse(JSON.stringify(object)) as GraffitiObjectBase;\n const masked = maskGraffitiObject(copy, [], recipient);\n\n // Get the recipient's inbox\n const actorDocument = await this.dids.resolve(recipient);\n const personalInbox = actorDocument.service?.find(\n (service) =>\n service.type === DID_SERVICE_TYPE_GRAFFITI_INBOX &&\n service.id === DID_SERVICE_ID_GRAFFITI_PERSONAL_INBOX,\n );\n if (!personalInbox) {\n throw new Error(\n `Recipient ${recipient} does not have a personal inbox`,\n );\n }\n if (typeof personalInbox.serviceEndpoint !== \"string\") {\n throw new Error(\n `Recipient ${recipient} does not have a valid personal inbox endpoint`,\n );\n }\n\n const tombstonedMessageId = priorAnnouncements\n ? priorAnnouncements.find(\n (a) => a[MESSAGE_DATA_ANNOUNCEMENT_ACTOR_KEY] === recipient,\n )?.[MESSAGE_DATA_ANNOUNCEMENT_MESSAGE_ID_KEY]\n : undefined;\n\n // Announce to the inbox\n const privateMetadata: MessageMetadata = {\n ...metadataBase,\n ...(tombstonedMessageId\n ? {\n [MESSAGE_DATA_TOMBSTONED_MESSAGE_ID_KEY]: tombstonedMessageId,\n }\n : {}),\n [MESSAGE_DATA_ALLOWED_TICKET_KEY]: allowedTickets[recipientIndex],\n [MESSAGE_DATA_ALLOWED_TICKET_INDEX_KEY]: recipientIndex,\n };\n const messageId = await this.inboxes.send(\n personalInbox.serviceEndpoint,\n {\n [MESSAGE_TAGS_KEY]: tags,\n [MESSAGE_OBJECT_KEY]: masked,\n [MESSAGE_METADATA_KEY]: dagCborEncode(privateMetadata),\n },\n );\n\n announcements.push({\n [MESSAGE_DATA_ANNOUNCEMENT_MESSAGE_ID_KEY]: messageId,\n [MESSAGE_DATA_ANNOUNCEMENT_ACTOR_KEY]: recipient,\n });\n }),\n );\n\n for (const [index, result] of results.entries()) {\n if (result.status === \"rejected\") {\n const recipient = allowed[index];\n console.error(\"Error sending to recipient:\", recipient);\n console.error(result.reason);\n }\n }\n } else {\n // Mask the object to not include any channels\n // and only include the recipient actor on the allowed list\n const copy = JSON.parse(JSON.stringify(object)) as GraffitiObjectBase;\n const masked = maskGraffitiObject(copy, []);\n\n // Send the object to each shared inbox\n const sharedInboxes = resolvedSession.sharedInboxes;\n const results = await Promise.allSettled(\n sharedInboxes.map(async (inbox) => {\n const tombstonedMessageId = priorAnnouncements\n ? priorAnnouncements.find(\n (a) =>\n a[MESSAGE_DATA_ANNOUNCEMENT_ENDPOINT_KEY] ===\n inbox.serviceEndpoint,\n )?.[MESSAGE_DATA_ANNOUNCEMENT_MESSAGE_ID_KEY]\n : undefined;\n const metadata: MessageMetadata = {\n ...metadataBase,\n ...(tombstonedMessageId\n ? {\n [MESSAGE_DATA_TOMBSTONED_MESSAGE_ID_KEY]: tombstonedMessageId,\n }\n : {}),\n };\n\n const messageId = await this.inboxes.send(inbox.serviceEndpoint, {\n ...(tombstonedMessageId\n ? {\n [MESSAGE_DATA_TOMBSTONED_MESSAGE_ID_KEY]: tombstonedMessageId,\n }\n : {}),\n [MESSAGE_TAGS_KEY]: tags,\n [MESSAGE_OBJECT_KEY]: masked,\n [MESSAGE_METADATA_KEY]: dagCborEncode(metadata),\n });\n announcements.push({\n [MESSAGE_DATA_ANNOUNCEMENT_MESSAGE_ID_KEY]: messageId,\n [MESSAGE_DATA_ANNOUNCEMENT_ENDPOINT_KEY]: inbox.serviceEndpoint,\n });\n }),\n );\n\n for (const [index, result] of results.entries()) {\n if (result.status === \"rejected\") {\n const inbox = sharedInboxes[index];\n console.error(\"Error sending to inbox:\", inbox);\n console.error(result.reason);\n }\n }\n }\n\n // Send the complete object to my own personal inbox\n // along with its key and allowed tickets\n const tombstonedMessageId = priorAnnouncements\n ? priorAnnouncements.find(\n (a) => a[MESSAGE_DATA_ANNOUNCEMENT_ACTOR_KEY] === session.actor,\n )?.[MESSAGE_DATA_ANNOUNCEMENT_MESSAGE_ID_KEY]\n : undefined;\n const selfMetadata: MessageMetadata = {\n ...metadataBase,\n ...(allowedTickets\n ? {\n [MESSAGE_DATA_ALLOWED_TICKETS_KEY]: allowedTickets,\n }\n : {}),\n ...(tombstonedMessageId\n ? {\n [MESSAGE_DATA_TOMBSTONED_MESSAGE_ID_KEY]: tombstonedMessageId,\n }\n : {}),\n [MESSAGE_DATA_ANNOUNCEMENTS_KEY]: announcements,\n };\n await this.inboxes.send(resolvedSession.personalInbox.serviceEndpoint, {\n [MESSAGE_TAGS_KEY]: tags,\n [MESSAGE_OBJECT_KEY]: object,\n [MESSAGE_METADATA_KEY]: dagCborEncode(selfMetadata),\n });\n }\n\n protected async *querySingleEndpoint<Schema extends JSONSchema>(\n inboxEndpoint: string,\n queryArguments:\n | {\n tags: Uint8Array[];\n objectSchema: Schema;\n }\n | {\n cursor: string;\n },\n inboxToken?: string | null,\n recipient?: string | null,\n ): SingleEndpointQueryIterator<Schema> {\n const iterator: MessageStream<Schema> =\n \"tags\" in queryArguments\n ? this.inboxes.query<Schema>(\n inboxEndpoint,\n queryArguments.tags,\n queryArguments.objectSchema,\n inboxToken,\n )\n : (this.inboxes.continueQuery(\n inboxEndpoint,\n queryArguments.cursor,\n inboxToken,\n ) as unknown as MessageStream<Schema>);\n\n while (true) {\n const itResult = await iterator.next();\n // Return the cursor if done\n if (itResult.done) return itResult.value;\n\n const result = itResult.value;\n\n const label = result.l;\n // Anything invalid or unexpected, we can skip\n if (\n label !== MESSAGE_LABEL_VALID &&\n label !== MESSAGE_LABEL_UNLABELED &&\n label !== MESSAGE_LABEL_TRASH\n )\n continue;\n\n const messageId = result.id;\n const { o: object, m: metadataBytes, t: receivedTags } = result.m;\n\n let metadata: MessageMetadata;\n try {\n const metadataRaw = dagCborDecode(metadataBytes);\n metadata = MessageMetadataSchema.parse(metadataRaw);\n } catch (e) {\n this.inboxes.label(\n inboxEndpoint,\n messageId,\n MESSAGE_LABEL_INVALID,\n inboxToken,\n );\n continue;\n }\n\n const {\n [MESSAGE_DATA_STORAGE_BUCKET_KEY]: storageBucketKey,\n [MESSAGE_DATA_TOMBSTONED_MESSAGE_ID_KEY]: tombstonedMessageId,\n } = metadata;\n\n const allowedTickets =\n MESSAGE_DATA_ALLOWED_TICKETS_KEY in metadata\n ? metadata[MESSAGE_DATA_ALLOWED_TICKETS_KEY]\n : undefined;\n const announcements =\n MESSAGE_DATA_ANNOUNCEMENTS_KEY in metadata\n ? metadata[MESSAGE_DATA_ANNOUNCEMENTS_KEY]\n : undefined;\n\n if (label === MESSAGE_LABEL_VALID) {\n yield {\n messageId,\n object,\n storageBucketKey,\n allowedTickets,\n tags: receivedTags,\n announcements,\n };\n continue;\n } else if (label === MESSAGE_LABEL_TRASH) {\n // If it is simply trash, just continue.\n if (!tombstonedMessageId) continue;\n\n // Make sure the tombstone points to a real message\n const past = await this.inboxes.get(\n inboxEndpoint,\n tombstonedMessageId,\n inboxToken,\n );\n if (\n !past ||\n past[LABELED_MESSAGE_MESSAGE_KEY][MESSAGE_OBJECT_KEY].url !==\n object.url\n )\n continue;\n\n // If the referred to message isn't labeled as trash, trash it\n // This may happen if a trash message is processed on another\n // device and the device cache is out of date.\n if (past[LABELED_MESSAGE_LABEL_KEY] !== MESSAGE_LABEL_TRASH) {\n // Label the message as trash\n this.inboxes.label(\n inboxEndpoint,\n tombstonedMessageId,\n MESSAGE_LABEL_TRASH,\n inboxToken,\n );\n }\n\n // Return the tombstone\n yield {\n messageId,\n tombstone: true,\n object,\n storageBucketKey,\n allowedTickets,\n tags: receivedTags,\n announcements,\n };\n continue;\n }\n\n // Otherwise, unlabeled: try to validate the object\n let validationError: unknown | undefined = undefined;\n try {\n const actor = object.actor;\n const actorDocument = await this.dids.resolve(actor);\n const storageBucketService = actorDocument?.service?.find(\n (service) =>\n service.id === DID_SERVICE_ID_GRAFFITI_STORAGE_BUCKET &&\n service.type === DID_SERVICE_TYPE_GRAFFITI_STORAGE_BUCKET,\n );\n if (!storageBucketService) {\n throw new GraffitiErrorNotFound(\n `Actor ${actor} has no storage bucket service`,\n );\n }\n if (typeof storageBucketService.serviceEndpoint !== \"string\") {\n throw new GraffitiErrorNotFound(\n `Actor ${actor} does not have a valid storage bucket endpoint`,\n );\n }\n const storageBucketEndpoint = storageBucketService.serviceEndpoint;\n\n const objectBytes = await this.storageBuckets.get(\n storageBucketEndpoint,\n storageBucketKey,\n MAX_OBJECT_SIZE_BYTES,\n );\n\n if (MESSAGE_DATA_ALLOWED_TICKET_KEY in metadata && !recipient) {\n throw new GraffitiErrorForbidden(\n `Recipient is required when allowed ticket is present`,\n );\n }\n const privateObjectInfo = allowedTickets\n ? { allowedTickets }\n : MESSAGE_DATA_ALLOWED_TICKET_KEY in metadata\n ? {\n recipient: recipient ?? \"null\",\n allowedTicket: metadata[MESSAGE_DATA_ALLOWED_TICKET_KEY],\n allowedIndex: metadata[MESSAGE_DATA_ALLOWED_TICKET_INDEX_KEY],\n }\n : undefined;\n\n await this.objectEncoding.validate(\n object,\n receivedTags,\n objectBytes,\n privateObjectInfo,\n );\n } catch (e) {\n validationError = e;\n }\n\n if (tombstonedMessageId) {\n if (validationError instanceof GraffitiErrorNotFound) {\n // Not found == The tombstone is correct\n this.inboxes\n // Get the referenced message\n .get(inboxEndpoint, tombstonedMessageId, inboxToken)\n .then((result) => {\n if (\n // Make sure that it actually references the object being deleted\n result &&\n result[LABELED_MESSAGE_MESSAGE_KEY][MESSAGE_OBJECT_KEY].url ===\n object.url &&\n // And that the object is not already marked as trash\n result[LABELED_MESSAGE_LABEL_KEY] !== MESSAGE_LABEL_TRASH\n ) {\n // If valid but not yet trash, label the message as trash\n this.inboxes.label(\n inboxEndpoint,\n tombstonedMessageId,\n MESSAGE_LABEL_TRASH,\n inboxToken,\n );\n }\n\n // Then, label the tombstone message as trash\n this.inboxes.label(\n inboxEndpoint,\n messageId,\n MESSAGE_LABEL_TRASH,\n inboxToken,\n );\n });\n\n yield {\n messageId,\n tombstone: true,\n object,\n storageBucketKey,\n allowedTickets,\n tags: receivedTags,\n announcements,\n };\n } else {\n console.error(\"Recieved an incorrect object\");\n console.error(validationError);\n this.inboxes.label(\n inboxEndpoint,\n messageId,\n MESSAGE_LABEL_INVALID,\n inboxToken,\n );\n }\n } else {\n if (validationError === undefined) {\n this.inboxes.label(\n inboxEndpoint,\n messageId,\n MESSAGE_LABEL_VALID,\n inboxToken,\n );\n yield {\n messageId,\n object,\n storageBucketKey,\n tags: receivedTags,\n allowedTickets,\n announcements,\n };\n } else {\n console.error(\"Recieved an incorrect object\");\n console.error(validationError);\n this.inboxes.label(\n inboxEndpoint,\n messageId,\n MESSAGE_LABEL_INVALID,\n inboxToken,\n );\n }\n }\n }\n }\n}\n\nconst MEDIA_OBJECT_SCHEMA = {\n properties: {\n value: {\n properties: {\n type: { type: \"string\" },\n size: { type: \"number\" },\n key: { type: \"string\" },\n },\n required: [\"type\", \"size\", \"key\"],\n },\n },\n} as const satisfies JSONSchema;\n\nconst CursorSchema = strictObject({\n cursors: record(url(), string()),\n channels: array(string()),\n});\n\ninterface SingleEndpointQueryResult<Schema extends JSONSchema> {\n messageId: string;\n object: GraffitiObject<Schema>;\n storageBucketKey: string;\n tags: Uint8Array[];\n allowedTickets: Uint8Array[] | undefined;\n tombstone?: boolean;\n announcements?: MessageMetadataAnnouncements | undefined;\n}\ninterface SingleEndpointQueryIterator<\n Schema extends JSONSchema,\n> extends AsyncGenerator<SingleEndpointQueryResult<Schema>, string> {}\ntype IndexedSingleEndpointQueryResult<Schema extends JSONSchema> =\n | {\n index: number;\n error?: undefined;\n result: IteratorResult<SingleEndpointQueryResult<Schema>, string>;\n }\n | {\n index: number;\n error: Error;\n result?: undefined;\n };\n\nasync function indexedSingleEndpointQueryNext<Schema extends JSONSchema>(\n it: SingleEndpointQueryIterator<Schema>,\n index: number,\n): Promise<IndexedSingleEndpointQueryResult<Schema>> {\n try {\n return {\n index: index,\n result: await it.next(),\n };\n } catch (e) {\n if (\n e instanceof GraffitiErrorCursorExpired ||\n e instanceof GraffitiErrorInvalidSchema\n ) {\n // Propogate these errors to the root\n throw e;\n }\n // Otherwise, silently pass them in the stream\n return {\n index,\n error: e instanceof Error ? e : new Error(String(e)),\n };\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["e", "url", "
|
|
4
|
+
"sourcesContent": ["import type { JSONSchema } from \"json-schema-to-ts\";\nimport {\n GraffitiErrorNotFound,\n maskGraffitiObject,\n type Graffiti,\n type GraffitiLoginEvent,\n type GraffitiObjectBase,\n type GraffitiSession,\n type GraffitiObject,\n unpackObjectUrl,\n compileGraffitiObjectSchema,\n GraffitiErrorSchemaMismatch,\n GraffitiErrorForbidden,\n GraffitiErrorTooLarge,\n isMediaAcceptable,\n GraffitiErrorNotAcceptable,\n GraffitiErrorCursorExpired,\n GraffitiErrorInvalidSchema,\n type GraffitiObjectStream,\n} from \"@graffiti-garden/api\";\nimport { randomBytes } from \"@noble/hashes/utils.js\";\nimport {\n encode as dagCborEncode,\n decode as dagCborDecode,\n} from \"@ipld/dag-cbor\";\n\nimport { DecentralizedIdentifiers } from \"../1-services/2-dids\";\nimport { Authorization } from \"../1-services/1-authorization\";\nimport { StorageBuckets } from \"../1-services/3-storage-buckets\";\nimport {\n Inboxes,\n LABELED_MESSAGE_LABEL_KEY,\n LABELED_MESSAGE_MESSAGE_KEY,\n MESSAGE_METADATA_KEY,\n MESSAGE_OBJECT_KEY,\n MESSAGE_TAGS_KEY,\n type LabeledMessage,\n type MessageStream,\n} from \"../1-services/4-inboxes\";\n\nimport {\n StringEncoder,\n STRING_ENCODER_METHOD_BASE64URL,\n} from \"../2-primitives/1-string-encoding\";\nimport { ContentAddresses } from \"../2-primitives/2-content-addresses\";\nimport {\n CHANNEL_ATTESTATION_METHOD_SHA256_ED25519,\n ChannelAttestations,\n} from \"../2-primitives/3-channel-attestations\";\nimport { AllowedAttestations } from \"../2-primitives/4-allowed-attestations\";\n\nimport { Handles } from \"./2-handles\";\nimport {\n Sessions,\n DID_SERVICE_ID_GRAFFITI_PERSONAL_INBOX,\n DID_SERVICE_TYPE_GRAFFITI_INBOX,\n DID_SERVICE_ID_GRAFFITI_STORAGE_BUCKET,\n DID_SERVICE_TYPE_GRAFFITI_STORAGE_BUCKET,\n} from \"./1-sessions\";\nimport {\n decodeObjectUrl,\n MAX_OBJECT_SIZE_BYTES,\n ObjectEncoding,\n} from \"./3-object-encoding\";\n\nimport { GraffitiModal } from \"@graffiti-garden/modal\";\nimport {\n type infer as infer_,\n custom,\n string,\n boolean,\n strictObject,\n array,\n int,\n nonnegative,\n optional,\n extend,\n union,\n record,\n url,\n} from \"zod/mini\";\n\nconst Uint8ArraySchema = custom<Uint8Array>(\n (v): v is Uint8Array => v instanceof Uint8Array,\n);\nconst MESSAGE_DATA_STORAGE_BUCKET_KEY = \"k\";\nconst MESSAGE_DATA_TOMBSTONED_MESSAGE_ID_KEY = \"t\";\nconst MessageMetadataBaseSchema = strictObject({\n [MESSAGE_DATA_STORAGE_BUCKET_KEY]: string(),\n [MESSAGE_DATA_TOMBSTONED_MESSAGE_ID_KEY]: optional(string()),\n});\nconst MESSAGE_DATA_ANNOUNCEMENT_MESSAGE_ID_KEY = \"id\";\nconst MESSAGE_DATA_ANNOUNCEMENT_ENDPOINT_KEY = \"e\";\nconst MESSAGE_DATA_ANNOUNCEMENT_ACTOR_KEY = \"a\";\nconst MessageMetadataAnnouncementsSchema = array(\n strictObject({\n [MESSAGE_DATA_ANNOUNCEMENT_MESSAGE_ID_KEY]: string(),\n [MESSAGE_DATA_ANNOUNCEMENT_ENDPOINT_KEY]: optional(url()),\n [MESSAGE_DATA_ANNOUNCEMENT_ACTOR_KEY]: optional(url()),\n }),\n);\nconst MESSAGE_DATA_ALLOWED_TICKETS_KEY = \"s\";\nconst MESSAGE_DATA_ANNOUNCEMENTS_KEY = \"n\";\nconst MessageMetaDataSelfSchema = extend(MessageMetadataBaseSchema, {\n [MESSAGE_DATA_ALLOWED_TICKETS_KEY]: optional(array(Uint8ArraySchema)),\n [MESSAGE_DATA_ANNOUNCEMENTS_KEY]: MessageMetadataAnnouncementsSchema,\n});\nconst MESSAGE_DATA_ALLOWED_TICKET_KEY = \"a\";\nconst MESSAGE_DATA_ALLOWED_TICKET_INDEX_KEY = \"i\";\nconst MessageMetadataPrivateSchema = extend(MessageMetadataBaseSchema, {\n [MESSAGE_DATA_ALLOWED_TICKET_KEY]: Uint8ArraySchema,\n [MESSAGE_DATA_ALLOWED_TICKET_INDEX_KEY]: int().check(nonnegative()),\n});\nconst MessageMetadataSchema = union([\n MessageMetadataBaseSchema,\n MessageMetaDataSelfSchema,\n MessageMetadataPrivateSchema,\n]);\ntype MessageMetadataBase = infer_<typeof MessageMetadataBaseSchema>;\ntype MessageMetadata = infer_<typeof MessageMetadataSchema>;\ntype MessageMetadataAnnouncements = infer_<\n typeof MessageMetadataAnnouncementsSchema\n>;\n\nconst MESSAGE_LABEL_UNLABELED = 0;\nconst MESSAGE_LABEL_VALID = 1;\nconst MESSAGE_LABEL_TRASH = 2;\nconst MESSAGE_LABEL_INVALID = 3;\n\nexport interface GraffitiDecentralizedOptions {\n identityCreatorEndpoint?: string;\n defaultInboxEndpoints?: string[];\n}\n\nconst CONCURRENCY = 16;\n\nexport class GraffitiDecentralized implements Graffiti {\n protected readonly dids = new DecentralizedIdentifiers();\n protected readonly authorization = new Authorization();\n protected readonly storageBuckets = new StorageBuckets();\n protected readonly inboxes = new Inboxes();\n\n protected readonly stringEncoder = new StringEncoder();\n protected readonly contentAddresses = new ContentAddresses();\n protected readonly channelAttestations = new ChannelAttestations();\n protected readonly allowedAttestations = new AllowedAttestations();\n\n protected readonly sessions = new Sessions({\n dids: this.dids,\n authorization: this.authorization,\n storageBuckets: this.storageBuckets,\n inboxes: this.inboxes,\n });\n protected readonly handles = new Handles({ dids: this.dids });\n protected readonly objectEncoding = new ObjectEncoding({\n stringEncoder: this.stringEncoder,\n contentAddresses: this.contentAddresses,\n channelAttestations: this.channelAttestations,\n allowedAttestations: this.allowedAttestations,\n });\n\n protected readonly modal: GraffitiModal | undefined =\n typeof window === \"undefined\"\n ? undefined\n : new GraffitiModal({\n useTemplateHTML: () =>\n import(\"./login-dialog.html\").then(({ template }) => template),\n onManualClose: () => {\n const event = new CustomEvent(\"login\", {\n detail: {\n error: new Error(\"User cancelled login\"),\n manual: true,\n },\n });\n this.sessionEvents.dispatchEvent(event);\n },\n });\n\n protected readonly defaultInboxEndpoints: string[];\n protected readonly identityCreatorEndpoint: string;\n constructor(options?: GraffitiDecentralizedOptions) {\n this.defaultInboxEndpoints = options?.defaultInboxEndpoints ?? [\n \"https://graffiti.actor/i/shared\",\n ];\n this.identityCreatorEndpoint =\n options?.identityCreatorEndpoint ?? \"https://graffiti.actor/create\";\n\n this.sessionEvents.addEventListener(\"login\", async (event) => {\n if (!(event instanceof CustomEvent)) return;\n const detail = event.detail as GraffitiLoginEvent[\"detail\"];\n if (\n detail.error !== undefined &&\n !(\"manual\" in detail && detail.manual)\n ) {\n alert(\"Login failed: \" + detail.error.message);\n const actor = detail.session?.actor;\n let handle: string | undefined;\n if (actor) {\n try {\n handle = await this.actorToHandle(actor);\n } catch (error) {\n console.error(\"Failed to handle actor:\", error);\n }\n }\n this.login_(handle);\n }\n });\n }\n\n readonly actorToHandle: Graffiti[\"actorToHandle\"] =\n this.handles.actorToHandle.bind(this.handles);\n readonly handleToActor: Graffiti[\"handleToActor\"] =\n this.handles.handleToActor.bind(this.handles);\n readonly sessionEvents: Graffiti[\"sessionEvents\"] =\n this.sessions.sessionEvents;\n\n login: Graffiti[\"login\"] = async (actor?: string) => {\n try {\n let proposedHandle: string | undefined;\n try {\n proposedHandle = actor ? await this.actorToHandle(actor) : undefined;\n } catch (error) {\n console.error(\"Error fetching handle for actor:\", error);\n }\n\n await this.login_(proposedHandle);\n } catch (e) {\n const loginError: GraffitiLoginEvent = new CustomEvent(\"login\", {\n detail: {\n error: e instanceof Error ? e : new Error(String(e)),\n },\n });\n this.sessionEvents.dispatchEvent(loginError);\n }\n };\n protected async login_(proposedHandle?: string) {\n if (typeof window !== \"undefined\") {\n let template: HTMLElement | undefined;\n if (proposedHandle !== undefined) {\n template = await this.modal?.displayTemplate(\"graffiti-login-handle\");\n const input = template?.querySelector(\n \"#username\",\n ) as HTMLInputElement | null;\n input?.setAttribute(\"value\", proposedHandle);\n input?.addEventListener(\"focus\", () => input?.select());\n setTimeout(() => input?.focus(), 0);\n\n template\n ?.querySelector(\"#graffiti-login-handle-form\")\n ?.addEventListener(\"submit\", async (e) => {\n e.preventDefault();\n input?.setAttribute(\"disabled\", \"true\");\n const submitButton = template?.querySelector(\n \"#graffiti-login-handle-submit\",\n ) as HTMLButtonElement | null;\n submitButton?.setAttribute(\"disabled\", \"true\");\n submitButton && (submitButton.innerHTML = \"Logging in...\");\n\n if (!input?.value) {\n alert(\"No handle provided\");\n this.login_(\"\");\n return;\n }\n\n let handle = input.value;\n if (!handle.includes(\".\") && !handle.startsWith(\"localhost\")) {\n const defaultHost = new URL(this.identityCreatorEndpoint).host;\n handle = `${handle}.${defaultHost}`;\n }\n\n let actor: string;\n try {\n actor = await this.handleToActor(handle);\n } catch (e) {\n alert(\"Could not find an identity associated with that handle.\");\n this.login_(handle);\n return;\n }\n\n try {\n await this.sessions.login(actor);\n } catch (e) {\n alert(\"Error logging in.\");\n console.error(e);\n this.login_(handle);\n }\n });\n } else {\n template = await this.modal?.displayTemplate(\"graffiti-login-welcome\");\n template\n ?.querySelector(\"#graffiti-login-existing\")\n ?.addEventListener(\"click\", (e) => {\n e.preventDefault();\n this.login_(\"\");\n });\n\n setTimeout(\n () =>\n (\n template?.querySelector(\n \"#graffiti-login-new\",\n ) as HTMLAnchorElement\n )?.focus(),\n 0,\n );\n }\n\n const createUrl = new URL(this.identityCreatorEndpoint);\n createUrl.searchParams.set(\n \"redirect_uri\",\n encodeURIComponent(window.location.toString()),\n );\n template\n ?.querySelector(\"#graffiti-login-new\")\n ?.setAttribute(\"href\", createUrl.toString());\n\n await this.modal?.open();\n } else {\n // Node.js environment\n const readline = await import(\"readline\").catch((e) => {\n throw new Error(\n \"Unrecognized environment: neither window nor readline\",\n );\n });\n\n console.log(\n \"If you do not already have a Graffiti handle, you can create one here:\",\n );\n console.log(this.identityCreatorEndpoint);\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n const handle: string | undefined = await new Promise((resolve) => {\n rl.question(\n `Please enter your handle${proposedHandle ? ` (default: ${proposedHandle})` : \"\"}: `,\n (input) => {\n rl.close();\n resolve(input || proposedHandle);\n },\n );\n });\n\n if (!handle) {\n throw new Error(\"No handle provided\");\n }\n\n // Convert the handle to an actor\n const actor = await this.handleToActor(handle);\n\n await this.sessions.login(actor);\n }\n }\n\n logout: Graffiti[\"logout\"] = async (session) => {\n await this.sessions.logout(session.actor);\n };\n\n // @ts-ignore\n post: Graffiti[\"post\"] = async (...args) => {\n const [partialObject, session] = args;\n const resolvedSession = this.sessions.resolveSession(session);\n\n // Encode the object\n const { object, tags, objectBytes, allowedTickets } =\n await this.objectEncoding.encode<{}>(partialObject, session.actor);\n\n // Generate a random key under which to store the object\n // If the object is private, this means no one will be able to\n // fetch the object, even if they know its URL.\n // If the object is public but in some secret channel, the storage\n // location means the object can be moved around or \"rotated\"\n // without changing its URL.\n const storageBucketKeyBytes = randomBytes();\n const storageBucketKey = await this.stringEncoder.encode(\n STRING_ENCODER_METHOD_BASE64URL,\n storageBucketKeyBytes,\n );\n\n // Store the object at the random key\n await this.storageBuckets.put(\n resolvedSession.storageBucket.serviceEndpoint,\n storageBucketKey,\n objectBytes,\n resolvedSession.storageBucket.token,\n );\n\n // Announce the object, its key,\n // and other metadata to appropriate inboxes\n await this.announceObject(\n object,\n tags,\n allowedTickets,\n storageBucketKey,\n session,\n );\n\n return object;\n };\n\n get: Graffiti[\"get\"] = async (...args) => {\n const [url, schema, session] = args;\n let services: { token?: string; serviceEndpoint: string }[];\n const validator = await compileGraffitiObjectSchema(schema);\n\n if (session) {\n // If logged in, first search one's\n // personal inbox, then any shared inboxes\n const resolvedSession = this.sessions.resolveSession(session);\n services = [\n resolvedSession.personalInbox,\n ...resolvedSession.sharedInboxes,\n ];\n } else {\n // Otherwise, search the default inboxes\n services = this.defaultInboxEndpoints.map((s) => ({\n serviceEndpoint: s,\n }));\n }\n\n // Search the inboxes for all objects\n // matching the tag, object.url\n const objectUrl = unpackObjectUrl(url);\n const tags = [new TextEncoder().encode(objectUrl)];\n for (const service of services) {\n let object: GraffitiObjectBase | undefined = undefined;\n\n const iterator = this.querySingleEndpoint<{}>(\n service.serviceEndpoint,\n {\n tags,\n objectSchema: {},\n },\n service.token,\n session?.actor,\n );\n\n for await (const result of iterator) {\n if (result.object.url !== objectUrl) continue;\n if (result.tombstone) {\n object = undefined;\n } else {\n object = result.object;\n }\n }\n\n if (object) {\n if (!validator(object)) {\n throw new GraffitiErrorSchemaMismatch(\n \"Object exists but does not match the supplied schema\",\n );\n }\n\n return object;\n }\n }\n\n throw new GraffitiErrorNotFound(\"Object not found\");\n };\n\n delete: Graffiti[\"delete\"] = async (url, session) => {\n const resolvedSession = this.sessions.resolveSession(session);\n\n const objectUrl = unpackObjectUrl(url);\n\n const { actor } = decodeObjectUrl(objectUrl);\n if (actor !== session.actor) {\n throw new GraffitiErrorForbidden(\"Cannot delete someone else's actor\");\n }\n\n // Look in one's personal inbox for the object\n const iterator = this.querySingleEndpoint<{}>(\n resolvedSession.personalInbox.serviceEndpoint,\n {\n tags: [new TextEncoder().encode(objectUrl)],\n objectSchema: {},\n },\n resolvedSession.personalInbox.token,\n );\n let existing: SingleEndpointQueryResult<{}> | undefined;\n for await (const result of iterator) {\n if (result.object.url !== objectUrl) continue;\n if (result.tombstone) {\n existing = undefined;\n } else {\n existing = result;\n }\n }\n if (!existing) {\n throw new GraffitiErrorNotFound(`Object ${objectUrl} not found`);\n }\n const {\n object,\n storageBucketKey,\n tags,\n allowedTickets,\n announcements,\n messageId,\n } = existing;\n\n // Delete the object from the actor's own storage bucket\n await this.storageBuckets.delete(\n resolvedSession.storageBucket.serviceEndpoint,\n storageBucketKey,\n resolvedSession.storageBucket.token,\n );\n\n // Announce the deletion to all inboxes\n await this.announceObject(\n object,\n tags,\n allowedTickets,\n storageBucketKey,\n session,\n [\n ...(announcements ?? []),\n // Make sure we delete from our own inbox too\n {\n [MESSAGE_DATA_ANNOUNCEMENT_ACTOR_KEY]: session.actor,\n [MESSAGE_DATA_ANNOUNCEMENT_MESSAGE_ID_KEY]: messageId,\n },\n ],\n );\n\n return object;\n };\n\n postMedia: Graffiti[\"postMedia\"] = async (...args) => {\n const [media, session] = args;\n\n const type = media.data.type;\n\n const resolvedSession = this.sessions.resolveSession(session);\n\n // Generate a random storage key\n const keyBytes = randomBytes();\n const key = await this.stringEncoder.encode(\n STRING_ENCODER_METHOD_BASE64URL,\n keyBytes,\n );\n\n // Store the media at that key\n await this.storageBuckets.put(\n resolvedSession.storageBucket.serviceEndpoint,\n key,\n new Uint8Array(await media.data.arrayBuffer()),\n resolvedSession.storageBucket.token,\n );\n\n // Create an object\n const { url } = await this.post<typeof MEDIA_OBJECT_SCHEMA>(\n {\n value: {\n key,\n type,\n size: media.data.size,\n },\n channels: [],\n allowed: media.allowed,\n },\n session,\n );\n\n return url;\n };\n\n getMedia: Graffiti[\"getMedia\"] = async (...args) => {\n const [mediaUrl, accept, session] = args;\n\n const object = await this.get<typeof MEDIA_OBJECT_SCHEMA>(\n mediaUrl,\n MEDIA_OBJECT_SCHEMA,\n session,\n );\n\n const { key, type, size } = object.value;\n\n if (accept?.maxBytes && size > accept.maxBytes) {\n throw new GraffitiErrorTooLarge(\"File size exceeds limit\");\n }\n\n // Make sure it adheres to requirements.accept\n if (accept?.types) {\n if (!isMediaAcceptable(type, accept.types)) {\n throw new GraffitiErrorNotAcceptable(\n `Unacceptable media type, ${type}`,\n );\n }\n }\n\n // Get the actor's storage bucket endpoint\n const actorDocument = await this.dids.resolve(object.actor);\n const storageBucketService = actorDocument?.service?.find(\n (service) =>\n service.id === DID_SERVICE_ID_GRAFFITI_STORAGE_BUCKET &&\n service.type === DID_SERVICE_TYPE_GRAFFITI_STORAGE_BUCKET,\n );\n if (!storageBucketService) {\n throw new GraffitiErrorNotFound(\n `Actor ${object.actor} has no storage bucket service`,\n );\n }\n if (typeof storageBucketService.serviceEndpoint !== \"string\") {\n throw new GraffitiErrorNotFound(\n `Actor ${object.actor} does not have a valid storage bucket endpoint`,\n );\n }\n const storageBucketEndpoint = storageBucketService.serviceEndpoint;\n\n const data = await this.storageBuckets.get(\n storageBucketEndpoint,\n key,\n size,\n );\n\n const blob = new Blob([data.slice()], { type });\n\n return {\n data: blob,\n actor: object.actor,\n allowed: object.allowed,\n };\n };\n\n deleteMedia: Graffiti[\"deleteMedia\"] = async (...args) => {\n const [mediaUrl, session] = args;\n\n const resolvedSession = this.sessions.resolveSession(session);\n\n const result = await this.delete(mediaUrl, session);\n\n if (!(\"key\" in result.value && typeof result.value.key === \"string\"))\n throw new Error(\n \"Deleted object was not media: \" + JSON.stringify(result, null, 2),\n );\n\n await this.storageBuckets.delete(\n resolvedSession.storageBucket.serviceEndpoint,\n result.value.key,\n resolvedSession.storageBucket.token,\n );\n };\n\n async *discoverMeta<Schema extends JSONSchema>(\n channels: string[],\n schema: Schema,\n cursors: {\n [endpoint: string]: string;\n },\n session?: GraffitiSession | null,\n ): GraffitiObjectStream<Schema> {\n const tombstones = new Map<string, boolean>();\n\n let allInboxes: { serviceEndpoint: string; token?: string }[];\n if (session) {\n const resolvedSession = this.sessions.resolveSession(session);\n allInboxes = [\n resolvedSession.personalInbox,\n ...resolvedSession.sharedInboxes,\n ];\n } else {\n allInboxes = this.defaultInboxEndpoints.map((e) => ({\n serviceEndpoint: e,\n }));\n }\n\n // Make sure all cursors are represented by an inbox\n for (const endpoint in cursors) {\n if (!allInboxes.some((i) => i.serviceEndpoint === endpoint)) {\n throw new GraffitiErrorForbidden(\n \"Cursor does not match actor's inboxes\",\n );\n }\n }\n\n // Turn the channels into tags\n const tags = await Promise.all(\n channels.map((c) =>\n this.channelAttestations.register(\n CHANNEL_ATTESTATION_METHOD_SHA256_ED25519,\n c,\n ),\n ),\n );\n\n const iterators: SingleEndpointQueryIterator<Schema>[] = allInboxes.map(\n (i) => {\n const cursor = cursors[i.serviceEndpoint];\n return this.querySingleEndpoint<Schema>(\n i.serviceEndpoint,\n cursor\n ? {\n cursor,\n }\n : {\n tags,\n objectSchema: schema,\n },\n i.token,\n session?.actor,\n );\n },\n );\n\n let indexedIteratorNexts = iterators.map<\n Promise<IndexedSingleEndpointQueryResult<Schema>>\n >(async (it, index) => indexedSingleEndpointQueryNext<Schema>(it, index));\n let active = indexedIteratorNexts.length;\n\n while (active > 0) {\n const next: IndexedSingleEndpointQueryResult<Schema> =\n await Promise.race<any>(indexedIteratorNexts);\n if (next.error !== undefined) {\n // Remove it from the race\n indexedIteratorNexts[next.index] = new Promise(() => {});\n active--;\n yield {\n error: next.error,\n origin: allInboxes[next.index].serviceEndpoint,\n };\n } else if (next.result.done) {\n // Store the cursor for future use\n const inbox = allInboxes[next.index];\n cursors[inbox.serviceEndpoint] = next.result.value;\n // Remove it from the race\n indexedIteratorNexts[next.index] = new Promise(() => {});\n active--;\n } else {\n // Re-arm the iterator\n indexedIteratorNexts[next.index] =\n indexedSingleEndpointQueryNext<Schema>(\n iterators[next.index],\n next.index,\n );\n const { object, tombstone, tags: receivedTags } = next.result.value;\n if (tombstone) {\n if (tombstones.get(object.url) === true) continue;\n tombstones.set(object.url, true);\n yield {\n tombstone,\n object: { url: object.url },\n };\n } else {\n // Filter already seen\n if (tombstones.get(object.url) === false) continue;\n\n // Fill in the matched channels\n const matchedTagIndices = tags.reduce<number[]>(\n (acc, tag, tagIndex) => {\n for (const receivedTag of receivedTags) {\n if (\n tag.length === receivedTag.length &&\n tag.every((b, i) => receivedTag[i] === b)\n ) {\n acc.push(tagIndex);\n break;\n }\n }\n return acc;\n },\n [],\n );\n const matchedChannels = matchedTagIndices.map(\n (index) => channels[index],\n );\n if (matchedChannels.length === 0) {\n yield {\n error: new Error(\n \"Inbox returned object without matching channels\",\n ),\n origin: allInboxes[next.index].serviceEndpoint,\n };\n }\n tombstones.set(object.url, false);\n yield {\n object: {\n ...object,\n channels: matchedChannels,\n },\n };\n }\n }\n }\n\n return {\n cursor: JSON.stringify({\n channels,\n cursors,\n } satisfies infer_<typeof CursorSchema>),\n };\n }\n\n discover: Graffiti[\"discover\"] = (...args) => {\n const [channels, schema, session] = args;\n return this.discoverMeta<(typeof args)[1]>(channels, schema, {}, session);\n };\n\n // @ts-ignore\n continueDiscover: Graffiti[\"continueDiscover\"] = (...args) => {\n const [cursor, session] = args;\n // Extract the channels from the cursor\n let channels: string[];\n let cursors: { [endpoint: string]: string };\n try {\n const json = JSON.parse(cursor);\n const parsed = CursorSchema.parse(json);\n channels = parsed.channels;\n cursors = parsed.cursors;\n } catch (error) {\n return (async function* () {\n throw new GraffitiErrorCursorExpired(\"Invalid cursor\");\n })();\n }\n return this.discoverMeta<{}>(channels, {}, cursors, session);\n };\n\n async announceObject(\n object: GraffitiObjectBase,\n tags: Uint8Array[],\n allowedTickets: Uint8Array[] | undefined,\n storageBucketKey: string,\n session: GraffitiSession,\n priorAnnouncements?: MessageMetadataAnnouncements,\n ): Promise<void> {\n const resolvedSession = this.sessions.resolveSession(session);\n\n const metadataBase: MessageMetadataBase = {\n [MESSAGE_DATA_STORAGE_BUCKET_KEY]: storageBucketKey,\n };\n\n const announcements: MessageMetadataAnnouncements = [];\n const allowed = object.allowed;\n if (Array.isArray(allowed)) {\n if (!allowedTickets || allowedTickets.length !== allowed.length) {\n throw new Error(\n \"If allowed actors are specified, there must be a corresponding ticket for each allowed actor\",\n );\n }\n\n // Send the object to each allowed recipient's personal inbox\n const results = await Promise.allSettled(\n allowed.map(async (recipient, recipientIndex) => {\n // Mask the object to not include any channels\n // and only include the recipient actor on the allowed list\n const copy = JSON.parse(JSON.stringify(object)) as GraffitiObjectBase;\n const masked = maskGraffitiObject(copy, [], recipient);\n\n // Get the recipient's inbox\n const actorDocument = await this.dids.resolve(recipient);\n const personalInbox = actorDocument.service?.find(\n (service) =>\n service.type === DID_SERVICE_TYPE_GRAFFITI_INBOX &&\n service.id === DID_SERVICE_ID_GRAFFITI_PERSONAL_INBOX,\n );\n if (!personalInbox) {\n throw new Error(\n `Recipient ${recipient} does not have a personal inbox`,\n );\n }\n if (typeof personalInbox.serviceEndpoint !== \"string\") {\n throw new Error(\n `Recipient ${recipient} does not have a valid personal inbox endpoint`,\n );\n }\n\n const tombstonedMessageId = priorAnnouncements\n ? priorAnnouncements.find(\n (a) => a[MESSAGE_DATA_ANNOUNCEMENT_ACTOR_KEY] === recipient,\n )?.[MESSAGE_DATA_ANNOUNCEMENT_MESSAGE_ID_KEY]\n : undefined;\n\n // Announce to the inbox\n const privateMetadata: MessageMetadata = {\n ...metadataBase,\n ...(tombstonedMessageId\n ? {\n [MESSAGE_DATA_TOMBSTONED_MESSAGE_ID_KEY]: tombstonedMessageId,\n }\n : {}),\n [MESSAGE_DATA_ALLOWED_TICKET_KEY]: allowedTickets[recipientIndex],\n [MESSAGE_DATA_ALLOWED_TICKET_INDEX_KEY]: recipientIndex,\n };\n const messageId = await this.inboxes.send(\n personalInbox.serviceEndpoint,\n {\n [MESSAGE_TAGS_KEY]: tags,\n [MESSAGE_OBJECT_KEY]: masked,\n [MESSAGE_METADATA_KEY]: dagCborEncode(privateMetadata),\n },\n );\n\n announcements.push({\n [MESSAGE_DATA_ANNOUNCEMENT_MESSAGE_ID_KEY]: messageId,\n [MESSAGE_DATA_ANNOUNCEMENT_ACTOR_KEY]: recipient,\n });\n }),\n );\n\n for (const [index, result] of results.entries()) {\n if (result.status === \"rejected\") {\n const recipient = allowed[index];\n console.error(\"Error sending to recipient:\", recipient);\n console.error(result.reason);\n }\n }\n } else {\n // Mask the object to not include any channels\n // and only include the recipient actor on the allowed list\n const copy = JSON.parse(JSON.stringify(object)) as GraffitiObjectBase;\n const masked = maskGraffitiObject(copy, []);\n\n // Send the object to each shared inbox\n const sharedInboxes = resolvedSession.sharedInboxes;\n const results = await Promise.allSettled(\n sharedInboxes.map(async (inbox) => {\n const tombstonedMessageId = priorAnnouncements\n ? priorAnnouncements.find(\n (a) =>\n a[MESSAGE_DATA_ANNOUNCEMENT_ENDPOINT_KEY] ===\n inbox.serviceEndpoint,\n )?.[MESSAGE_DATA_ANNOUNCEMENT_MESSAGE_ID_KEY]\n : undefined;\n const metadata: MessageMetadata = {\n ...metadataBase,\n ...(tombstonedMessageId\n ? {\n [MESSAGE_DATA_TOMBSTONED_MESSAGE_ID_KEY]: tombstonedMessageId,\n }\n : {}),\n };\n\n const messageId = await this.inboxes.send(inbox.serviceEndpoint, {\n ...(tombstonedMessageId\n ? {\n [MESSAGE_DATA_TOMBSTONED_MESSAGE_ID_KEY]: tombstonedMessageId,\n }\n : {}),\n [MESSAGE_TAGS_KEY]: tags,\n [MESSAGE_OBJECT_KEY]: masked,\n [MESSAGE_METADATA_KEY]: dagCborEncode(metadata),\n });\n announcements.push({\n [MESSAGE_DATA_ANNOUNCEMENT_MESSAGE_ID_KEY]: messageId,\n [MESSAGE_DATA_ANNOUNCEMENT_ENDPOINT_KEY]: inbox.serviceEndpoint,\n });\n }),\n );\n\n for (const [index, result] of results.entries()) {\n if (result.status === \"rejected\") {\n const inbox = sharedInboxes[index];\n console.error(\"Error sending to inbox:\", inbox);\n console.error(result.reason);\n }\n }\n }\n\n // Send the complete object to my own personal inbox\n // along with its key and allowed tickets\n const tombstonedMessageId = priorAnnouncements\n ? priorAnnouncements.find(\n (a) => a[MESSAGE_DATA_ANNOUNCEMENT_ACTOR_KEY] === session.actor,\n )?.[MESSAGE_DATA_ANNOUNCEMENT_MESSAGE_ID_KEY]\n : undefined;\n const selfMetadata: MessageMetadata = {\n ...metadataBase,\n ...(allowedTickets\n ? {\n [MESSAGE_DATA_ALLOWED_TICKETS_KEY]: allowedTickets,\n }\n : {}),\n ...(tombstonedMessageId\n ? {\n [MESSAGE_DATA_TOMBSTONED_MESSAGE_ID_KEY]: tombstonedMessageId,\n }\n : {}),\n [MESSAGE_DATA_ANNOUNCEMENTS_KEY]: announcements,\n };\n await this.inboxes.send(resolvedSession.personalInbox.serviceEndpoint, {\n [MESSAGE_TAGS_KEY]: tags,\n [MESSAGE_OBJECT_KEY]: object,\n [MESSAGE_METADATA_KEY]: dagCborEncode(selfMetadata),\n });\n }\n\n protected async *querySingleEndpoint<Schema extends JSONSchema>(\n inboxEndpoint: string,\n queryArguments:\n | {\n tags: Uint8Array[];\n objectSchema: Schema;\n }\n | {\n cursor: string;\n },\n inboxToken?: string | null,\n recipient?: string | null,\n ): SingleEndpointQueryIterator<Schema> {\n const iterator: MessageStream<Schema> =\n \"tags\" in queryArguments\n ? this.inboxes.query<Schema>(\n inboxEndpoint,\n queryArguments.tags,\n queryArguments.objectSchema,\n inboxToken,\n )\n : (this.inboxes.continueQuery(\n inboxEndpoint,\n queryArguments.cursor,\n inboxToken,\n ) as unknown as MessageStream<Schema>);\n\n const inFlight: Promise<SingleEndpointQueryResult<Schema> | void>[] = [];\n let doneValue: string | null = null;\n\n while (true) {\n while (doneValue === null && inFlight.length < CONCURRENCY) {\n const itResult = await iterator.next();\n if (itResult.done) {\n doneValue = itResult.value;\n break;\n }\n\n const processPromise = this.processOneLabeledMessage<Schema>(\n inboxEndpoint,\n itResult.value,\n inboxToken,\n recipient,\n ).catch((e) => {\n throw e;\n });\n\n inFlight.push(processPromise);\n }\n\n const nextProcessedPromise = inFlight.shift();\n\n if (!nextProcessedPromise) {\n if (doneValue !== null) return doneValue;\n\n throw new Error(\"Process queue empty but no return value\");\n }\n\n const processed = await nextProcessedPromise;\n if (processed) yield processed;\n }\n }\n\n protected async processOneLabeledMessage<Schema extends JSONSchema>(\n inboxEndpoint: string,\n result: LabeledMessage<Schema>,\n inboxToken?: string | null,\n recipient?: string | null,\n ): Promise<SingleEndpointQueryResult<Schema> | void> {\n const label = result.l;\n // Anything invalid or unexpected, we can skip\n if (\n label !== MESSAGE_LABEL_VALID &&\n label !== MESSAGE_LABEL_UNLABELED &&\n label !== MESSAGE_LABEL_TRASH\n )\n return;\n\n const messageId = result.id;\n const { o: object, m: metadataBytes, t: receivedTags } = result.m;\n\n let metadata: MessageMetadata;\n try {\n const metadataRaw = dagCborDecode(metadataBytes);\n metadata = MessageMetadataSchema.parse(metadataRaw);\n } catch (e) {\n this.inboxes.label(\n inboxEndpoint,\n messageId,\n MESSAGE_LABEL_INVALID,\n inboxToken,\n );\n return;\n }\n\n const {\n [MESSAGE_DATA_STORAGE_BUCKET_KEY]: storageBucketKey,\n [MESSAGE_DATA_TOMBSTONED_MESSAGE_ID_KEY]: tombstonedMessageId,\n } = metadata;\n\n const allowedTickets =\n MESSAGE_DATA_ALLOWED_TICKETS_KEY in metadata\n ? metadata[MESSAGE_DATA_ALLOWED_TICKETS_KEY]\n : undefined;\n const announcements =\n MESSAGE_DATA_ANNOUNCEMENTS_KEY in metadata\n ? metadata[MESSAGE_DATA_ANNOUNCEMENTS_KEY]\n : undefined;\n\n if (label === MESSAGE_LABEL_VALID) {\n return {\n messageId,\n object,\n storageBucketKey,\n allowedTickets,\n tags: receivedTags,\n announcements,\n };\n } else if (label === MESSAGE_LABEL_TRASH) {\n // If it is simply trash, just continue.\n if (!tombstonedMessageId) return;\n\n // Make sure the tombstone points to a real message\n const past = await this.inboxes.get(\n inboxEndpoint,\n tombstonedMessageId,\n inboxToken,\n );\n if (\n !past ||\n past[LABELED_MESSAGE_MESSAGE_KEY][MESSAGE_OBJECT_KEY].url !== object.url\n )\n return;\n\n // If the referred to message isn't labeled as trash, trash it\n // This may happen if a trash message is processed on another\n // device and the device cache is out of date.\n if (past[LABELED_MESSAGE_LABEL_KEY] !== MESSAGE_LABEL_TRASH) {\n // Label the message as trash\n this.inboxes.label(\n inboxEndpoint,\n tombstonedMessageId,\n MESSAGE_LABEL_TRASH,\n inboxToken,\n );\n }\n\n // Return the tombstone\n return {\n messageId,\n tombstone: true,\n object,\n storageBucketKey,\n allowedTickets,\n tags: receivedTags,\n announcements,\n };\n }\n\n // Otherwise, unlabeled: try to validate the object\n let validationError: unknown | undefined = undefined;\n try {\n const actor = object.actor;\n const actorDocument = await this.dids.resolve(actor);\n const storageBucketService = actorDocument?.service?.find(\n (service) =>\n service.id === DID_SERVICE_ID_GRAFFITI_STORAGE_BUCKET &&\n service.type === DID_SERVICE_TYPE_GRAFFITI_STORAGE_BUCKET,\n );\n if (!storageBucketService) {\n throw new GraffitiErrorNotFound(\n `Actor ${actor} has no storage bucket service`,\n );\n }\n if (typeof storageBucketService.serviceEndpoint !== \"string\") {\n throw new GraffitiErrorNotFound(\n `Actor ${actor} does not have a valid storage bucket endpoint`,\n );\n }\n const storageBucketEndpoint = storageBucketService.serviceEndpoint;\n\n const objectBytes = await this.storageBuckets.get(\n storageBucketEndpoint,\n storageBucketKey,\n MAX_OBJECT_SIZE_BYTES,\n );\n\n if (MESSAGE_DATA_ALLOWED_TICKET_KEY in metadata && !recipient) {\n throw new GraffitiErrorForbidden(\n `Recipient is required when allowed ticket is present`,\n );\n }\n const privateObjectInfo = allowedTickets\n ? { allowedTickets }\n : MESSAGE_DATA_ALLOWED_TICKET_KEY in metadata\n ? {\n recipient: recipient ?? \"null\",\n allowedTicket: metadata[MESSAGE_DATA_ALLOWED_TICKET_KEY],\n allowedIndex: metadata[MESSAGE_DATA_ALLOWED_TICKET_INDEX_KEY],\n }\n : undefined;\n\n await this.objectEncoding.validate(\n object,\n receivedTags,\n objectBytes,\n privateObjectInfo,\n );\n } catch (e) {\n validationError = e;\n }\n\n if (tombstonedMessageId) {\n if (validationError instanceof GraffitiErrorNotFound) {\n // Not found == The tombstone is correct\n this.inboxes\n // Get the referenced message\n .get(inboxEndpoint, tombstonedMessageId, inboxToken)\n .then((result) => {\n if (\n // Make sure that it actually references the object being deleted\n result &&\n result[LABELED_MESSAGE_MESSAGE_KEY][MESSAGE_OBJECT_KEY].url ===\n object.url &&\n // And that the object is not already marked as trash\n result[LABELED_MESSAGE_LABEL_KEY] !== MESSAGE_LABEL_TRASH\n ) {\n // If valid but not yet trash, label the message as trash\n this.inboxes.label(\n inboxEndpoint,\n tombstonedMessageId,\n MESSAGE_LABEL_TRASH,\n inboxToken,\n );\n }\n\n // Then, label the tombstone message as trash\n this.inboxes.label(\n inboxEndpoint,\n messageId,\n MESSAGE_LABEL_TRASH,\n inboxToken,\n );\n });\n\n return {\n messageId,\n tombstone: true,\n object,\n storageBucketKey,\n allowedTickets,\n tags: receivedTags,\n announcements,\n };\n } else {\n console.error(\"Recieved an incorrect tombstone object\");\n console.error(validationError);\n this.inboxes.label(\n inboxEndpoint,\n messageId,\n MESSAGE_LABEL_INVALID,\n inboxToken,\n );\n }\n } else {\n if (validationError === undefined) {\n this.inboxes.label(\n inboxEndpoint,\n messageId,\n MESSAGE_LABEL_VALID,\n inboxToken,\n );\n return {\n messageId,\n object,\n storageBucketKey,\n tags: receivedTags,\n allowedTickets,\n announcements,\n };\n } else if (validationError instanceof GraffitiErrorNotFound) {\n // Item was deleted before we got a chance to\n // validate it. Just label the message as trash.\n this.inboxes.label(\n inboxEndpoint,\n messageId,\n MESSAGE_LABEL_TRASH,\n inboxToken,\n );\n } else {\n console.error(\"Recieved an incorrect object\");\n console.error(validationError);\n this.inboxes.label(\n inboxEndpoint,\n messageId,\n MESSAGE_LABEL_INVALID,\n inboxToken,\n );\n }\n }\n }\n}\n\nconst MEDIA_OBJECT_SCHEMA = {\n properties: {\n value: {\n properties: {\n type: { type: \"string\" },\n size: { type: \"number\" },\n key: { type: \"string\" },\n },\n required: [\"type\", \"size\", \"key\"],\n },\n },\n} as const satisfies JSONSchema;\n\nconst CursorSchema = strictObject({\n cursors: record(url(), string()),\n channels: array(string()),\n});\n\ninterface SingleEndpointQueryResult<Schema extends JSONSchema> {\n messageId: string;\n object: GraffitiObject<Schema>;\n storageBucketKey: string;\n tags: Uint8Array[];\n allowedTickets: Uint8Array[] | undefined;\n tombstone?: boolean;\n announcements?: MessageMetadataAnnouncements | undefined;\n}\ninterface SingleEndpointQueryIterator<\n Schema extends JSONSchema,\n> extends AsyncGenerator<SingleEndpointQueryResult<Schema>, string> {}\ntype IndexedSingleEndpointQueryResult<Schema extends JSONSchema> =\n | {\n index: number;\n error?: undefined;\n result: IteratorResult<SingleEndpointQueryResult<Schema>, string>;\n }\n | {\n index: number;\n error: Error;\n result?: undefined;\n };\n\nasync function indexedSingleEndpointQueryNext<Schema extends JSONSchema>(\n it: SingleEndpointQueryIterator<Schema>,\n index: number,\n): Promise<IndexedSingleEndpointQueryResult<Schema>> {\n try {\n return {\n index: index,\n result: await it.next(),\n };\n } catch (e) {\n if (\n e instanceof GraffitiErrorCursorExpired ||\n e instanceof GraffitiErrorInvalidSchema\n ) {\n // Propogate these errors to the root\n throw e;\n }\n // Otherwise, silently pass them in the stream\n return {\n index,\n error: e instanceof Error ? e : new Error(String(e)),\n };\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["e", "url", "tombstonedMessageId", "dagCborEncode", "dagCborDecode", "result"]
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/3-protocol/1-sessions.ts"],
|
|
4
|
-
"sourcesContent": ["import type {\n Graffiti,\n GraffitiLoginEvent,\n GraffitiLogoutEvent,\n GraffitiSession,\n GraffitiSessionInitializedEvent,\n} from \"@graffiti-garden/api\";\nimport { DecentralizedIdentifiers } from \"../1-services/2-dids\";\nimport {\n InitializedEventDetailSchema,\n LoginEventDetailSchema,\n LogoutEventDetailSchema,\n type Authorization,\n} from \"../1-services/1-authorization\";\nimport { StorageBuckets } from \"../1-services/3-storage-buckets\";\nimport type { Inboxes } from \"../1-services/4-inboxes\";\nimport type { Service } from \"did-resolver\";\nimport {\n type infer as infer_,\n extend,\n array,\n string,\n object,\n url,\n tuple,\n enum as enum_,\n} from \"zod/mini\";\n\nexport const DID_SERVICE_TYPE_GRAFFITI_INBOX = \"GraffitiInbox\";\nexport const DID_SERVICE_TYPE_GRAFFITI_STORAGE_BUCKET = \"GraffitiStorageBucket\";\nexport const DID_SERVICE_ID_GRAFFITI_PERSONAL_INBOX = \"#graffitiPersonalInbox\";\nexport const DID_SERVICE_ID_GRAFFITI_STORAGE_BUCKET = \"#graffitiStorageBucket\";\nexport const DID_SERVICE_ID_GRAFFITI_SHARED_INBOX_PREFIX =\n \"#graffitiSharedInbox_\";\n\nexport class Sessions {\n sessionEvents: Graffiti[\"sessionEvents\"] = new EventTarget();\n\n constructor(\n protected readonly services: {\n readonly dids: DecentralizedIdentifiers;\n readonly authorization: Authorization;\n readonly storageBuckets: StorageBuckets;\n readonly inboxes: Inboxes;\n },\n ) {\n const initializedPromise = new Promise<void>((resolve) => {\n this.services.authorization.eventTarget.addEventListener(\n \"initialized\",\n (e) => {\n if (!(e instanceof CustomEvent)) return;\n const parsed = InitializedEventDetailSchema.safeParse(e.detail);\n if (!parsed.success) return;\n const error = parsed.data?.error;\n if (error) console.log(error);\n resolve();\n },\n );\n });\n this.services.authorization.eventTarget.addEventListener(\n \"login\",\n this.onLogin.bind(this),\n );\n this.services.authorization.eventTarget.addEventListener(\n \"logout\",\n this.onLogout.bind(this),\n );\n\n // Handle account registration redirect immediately,\n // to prevent SPA routers from hijacking the URL too soon\n let loginPromise: Promise<void> | undefined;\n if (typeof window !== \"undefined\") {\n const actorEncoded = new URLSearchParams(window.location.search).get(\n \"actor\",\n );\n if (actorEncoded) {\n try {\n // Get the actor\n const actor = decodeURIComponent(actorEncoded);\n // Strip it from the URL\n const url = new URL(window.location.toString());\n url.searchParams.delete(\"actor\");\n window.history.replaceState({}, \"\", url.toString());\n // Complete the login\n loginPromise = this.login(actor);\n } catch (error) {\n console.error(\"Error decoding actor:\", error);\n }\n }\n }\n\n (async () => {\n // Allow listeners to be added before dispatching events\n await new Promise((resolve) => setTimeout(resolve, 0));\n\n // Wait for login to complete, if there\n await loginPromise;\n\n for (const session of this.loggedInSessions) {\n const loginEvent: GraffitiLoginEvent = new CustomEvent(\"login\", {\n detail: { session: { actor: session.actor } },\n });\n this.sessionEvents.dispatchEvent(loginEvent);\n }\n\n await initializedPromise;\n\n // Send own initialized event\n const initializedEvent: GraffitiSessionInitializedEvent = new CustomEvent(\n \"initialized\",\n );\n this.sessionEvents.dispatchEvent(initializedEvent);\n })();\n }\n\n protected inProgressLogin: infer_<typeof InProgressSchema> | undefined =\n undefined;\n protected inProgressLogout: infer_<typeof InProgressSchema> | undefined =\n undefined;\n\n async login(actor: string) {\n try {\n await this.login_(actor);\n } catch (e) {\n const loginEvent: GraffitiLoginEvent = new CustomEvent(\"login\", {\n detail: {\n error: e instanceof Error ? e : new Error(String(e)),\n session: { actor },\n },\n });\n this.sessionEvents.dispatchEvent(loginEvent);\n }\n }\n protected async login_(actor: string) {\n // First look to see if we're already logged in\n const existingSession = this.loggedInSessions.find(\n (session) => session.actor === actor,\n );\n if (existingSession) {\n this.sessionEvents.dispatchEvent(\n new CustomEvent(\"login\", { detail: { session: { actor } } }),\n );\n return;\n }\n\n const actorDocument = await this.services.dids.resolve(actor);\n\n const services = actorDocument.service;\n if (!services) {\n throw new Error(`No services found in actor document for ${actor}`);\n }\n\n const storageBucketService = services.find(\n (service) =>\n service.id === DID_SERVICE_ID_GRAFFITI_STORAGE_BUCKET &&\n service.type === DID_SERVICE_TYPE_GRAFFITI_STORAGE_BUCKET,\n );\n const personalInboxService = services.find(\n (service) =>\n service.id === DID_SERVICE_ID_GRAFFITI_PERSONAL_INBOX &&\n service.type === DID_SERVICE_TYPE_GRAFFITI_INBOX,\n );\n const sharedInboxServices = services.filter(\n (service) =>\n service.id.match(\n new RegExp(`^${DID_SERVICE_ID_GRAFFITI_SHARED_INBOX_PREFIX}\\\\d+$`),\n ) && service.type === DID_SERVICE_TYPE_GRAFFITI_INBOX,\n );\n\n if (\n !personalInboxService ||\n !storageBucketService ||\n sharedInboxServices.length === 0\n ) {\n throw new Error(\n `Required services not found in actor document for ${actor}`,\n );\n }\n\n // Massage the services into a list of endpoints with types\n const storageBucketEndpoint: string =\n serviceToEndpoint(storageBucketService);\n const personalInboxEndpoint: string =\n serviceToEndpoint(personalInboxService);\n const sharedInboxEndpoints: string[] =\n sharedInboxServices.map(serviceToEndpoint);\n const servicesWithTypes = [\n { endpoint: storageBucketEndpoint, type: \"bucket\" } as const,\n { endpoint: personalInboxEndpoint, type: \"personal-inbox\" } as const,\n ...sharedInboxEndpoints.map(\n (endpoint) =>\n ({\n endpoint,\n type: \"shared-inbox\",\n }) as const,\n ),\n ];\n\n // Fetch the authorization endpoints for each service\n const servicesWithAuthorizationEndpoints = await Promise.all(\n servicesWithTypes.map(async ({ endpoint, type }) => {\n const authorizationEndpoint = await (type === \"bucket\"\n ? this.services.storageBuckets.getAuthorizationEndpoint(endpoint)\n : this.services.inboxes.getAuthorizationEndpoint(endpoint));\n return { endpoint, authorizationEndpoint, type };\n }),\n );\n\n // Group the endpoints according to their authorization endpoints\n const servicesByAuthorizationMap: Map<\n string,\n {\n endpoint: string;\n type: \"bucket\" | \"personal-inbox\" | \"shared-inbox\";\n }[]\n > = new Map();\n servicesWithAuthorizationEndpoints.forEach(\n ({ authorizationEndpoint, endpoint, type }) => {\n if (!servicesByAuthorizationMap.has(authorizationEndpoint)) {\n servicesByAuthorizationMap.set(authorizationEndpoint, []);\n }\n servicesByAuthorizationMap\n .get(authorizationEndpoint)!\n .push({ endpoint, type });\n },\n );\n const servicesByAuthorization = [...servicesByAuthorizationMap.entries()];\n\n const session: GraffitiSession = { actor };\n\n const inProgressLogin: infer_<typeof InProgressSchema> = {\n ...session,\n tokens: [],\n servicesByAuthorization,\n };\n\n if (typeof window !== \"undefined\") {\n // Store the in-progress session in localStorage\n window.localStorage.setItem(\n LOCAL_STORAGE_IN_PROGRESS_LOGIN_KEY,\n JSON.stringify(inProgressLogin),\n );\n } else {\n this.inProgressLogin = inProgressLogin;\n }\n\n // Start the login process with the first endpoint\n const [firstAuthorizationEndpoint, firstServices] =\n servicesByAuthorization[0];\n await this.services.authorization.login(\n firstAuthorizationEndpoint,\n actor,\n firstServices.map((s) => s.endpoint),\n );\n }\n\n protected async onLogin(event: unknown) {\n if (!(event instanceof CustomEvent)) return;\n const parsed = LoginEventDetailSchema.safeParse(event.detail);\n if (!parsed.success) return;\n\n const actor = parsed.data.loginId;\n\n try {\n await this.onLogin_(parsed.data);\n } catch (e) {\n const LoginEvent: GraffitiLoginEvent = new CustomEvent(\"login\", {\n detail: {\n error: e instanceof Error ? e : new Error(String(e)),\n session: { actor },\n },\n });\n this.sessionEvents.dispatchEvent(LoginEvent);\n }\n }\n protected async onLogin_(loginDetail: infer_<typeof LoginEventDetailSchema>) {\n if (loginDetail.error) throw loginDetail.error;\n\n const token = loginDetail.token;\n const actor = loginDetail.loginId;\n\n // Lookup the in-progress session\n let inProgressLogin: infer_<typeof InProgressSchema>;\n if (typeof window !== \"undefined\") {\n const inProgressLoginString = window.localStorage.getItem(\n LOCAL_STORAGE_IN_PROGRESS_LOGIN_KEY,\n );\n if (!inProgressLoginString) {\n throw new Error(\"No in-progress login found\");\n }\n\n const json = JSON.parse(inProgressLoginString);\n inProgressLogin = InProgressSchema.parse(json);\n } else {\n if (!this.inProgressLogin) {\n throw new Error(\"No in-progress login found\");\n }\n inProgressLogin = this.inProgressLogin;\n }\n\n if (inProgressLogin.actor !== actor) {\n throw new Error(\"Actor mismatch in login response - concurrent logins?\");\n }\n\n inProgressLogin.tokens.push(token);\n\n if (\n inProgressLogin.tokens.length ===\n inProgressLogin.servicesByAuthorization.length\n ) {\n // Login complete!\n if (typeof window === \"undefined\") {\n this.inProgressLogin = undefined;\n } else {\n window.localStorage.removeItem(LOCAL_STORAGE_IN_PROGRESS_LOGIN_KEY);\n }\n\n // Build the completed session\n const services = inProgressLogin.servicesByAuthorization.flatMap(\n ([authorizationEndpoint, services], index) =>\n services.map((service) => ({\n token: inProgressLogin.tokens[index],\n serviceEndpoint: service.endpoint,\n authorizationEndpoint,\n type: service.type,\n })),\n );\n\n const session: StoredSession = {\n ...inProgressLogin,\n storageBucket: services.find((s) => s.type === \"bucket\")!,\n personalInbox: services.find((s) => s.type === \"personal-inbox\")!,\n sharedInboxes: services.filter((s) => s.type === \"shared-inbox\")!,\n };\n\n // Store the completed session\n const sessions = this.loggedInSessions;\n sessions.push(session);\n this.loggedInSessions = sessions;\n\n // Return the completed session\n const loginEvent: GraffitiLoginEvent = new CustomEvent(\"login\", {\n detail: { session: { actor } },\n });\n this.sessionEvents.dispatchEvent(loginEvent);\n } else {\n // Store the in progress and continue\n if (typeof window !== \"undefined\") {\n window.localStorage.setItem(\n LOCAL_STORAGE_IN_PROGRESS_LOGIN_KEY,\n JSON.stringify(inProgressLogin),\n );\n } else {\n this.inProgressLogin = inProgressLogin;\n }\n\n // Continue to the next authorization endpoint\n const [authorizationEndpoint, services] =\n inProgressLogin.servicesByAuthorization[inProgressLogin.tokens.length];\n await this.services.authorization.login(\n authorizationEndpoint,\n actor,\n services.map((s) => s.endpoint),\n );\n }\n }\n\n async logout(actor: string) {\n try {\n await this.logout_(actor);\n } catch (e) {\n const logoutEvent: GraffitiLogoutEvent = new CustomEvent(\"logout\", {\n detail: {\n error: e instanceof Error ? e : new Error(String(e)),\n actor,\n },\n });\n this.sessionEvents.dispatchEvent(logoutEvent);\n }\n }\n protected async logout_(actor: string) {\n const session = this.loggedInSessions.find(\n (session) => session.actor === actor,\n );\n if (!session) {\n throw new Error(`No session found for actor ${actor}`);\n }\n\n // Remove the session(s)\n this.loggedInSessions = this.loggedInSessions.filter(\n (session) => session.actor !== actor,\n );\n\n // Begin the logout\n const token = session.tokens.pop();\n if (!token) {\n throw new Error(\"No tokens found in session\");\n }\n // Store the in progress logout\n if (typeof window !== \"undefined\") {\n window.localStorage.setItem(\n LOCAL_STORAGE_IN_PROGRESS_LOGOUT_KEY,\n JSON.stringify(session),\n );\n } else {\n this.inProgressLogout = session;\n }\n const [authorizationEndpoint, _] =\n session.servicesByAuthorization[session.tokens.length];\n await this.services.authorization.logout(\n authorizationEndpoint,\n actor,\n token,\n );\n }\n\n protected async onLogout(event: unknown) {\n if (!(event instanceof CustomEvent)) return;\n const parsed = LogoutEventDetailSchema.safeParse(event.detail);\n if (!parsed.success) return;\n\n const actor = parsed.data.logoutId;\n\n try {\n await this.onLogout_(parsed.data);\n } catch (e) {\n const logoutEvent: GraffitiLogoutEvent = new CustomEvent(\"logout\", {\n detail: {\n error: e instanceof Error ? e : new Error(String(e)),\n actor,\n },\n });\n this.sessionEvents.dispatchEvent(logoutEvent);\n }\n }\n protected async onLogout_(\n logoutDetail: infer_<typeof LogoutEventDetailSchema>,\n ) {\n if (logoutDetail.error) throw logoutDetail.error;\n\n const actor = logoutDetail.logoutId;\n\n // Lookup the in-progress session\n let inProgressLogout: infer_<typeof InProgressSchema>;\n if (typeof window !== \"undefined\") {\n const inProgressLogoutString = window.localStorage.getItem(\n LOCAL_STORAGE_IN_PROGRESS_LOGOUT_KEY,\n );\n if (!inProgressLogoutString) {\n throw new Error(\"No in-progress logout found\");\n }\n\n const json = JSON.parse(inProgressLogoutString);\n inProgressLogout = InProgressSchema.parse(json);\n } else {\n if (!this.inProgressLogout) {\n throw new Error(\"No in-progress logout found\");\n }\n inProgressLogout = this.inProgressLogout;\n }\n\n if (inProgressLogout.actor !== actor) {\n throw new Error(\n \"Actor mismatch in logout response - concurrent logouts?\",\n );\n }\n\n const token = inProgressLogout.tokens.pop();\n if (!token) {\n // Logout complete\n if (typeof window === \"undefined\") {\n this.inProgressLogout = undefined;\n } else {\n window.localStorage.removeItem(LOCAL_STORAGE_IN_PROGRESS_LOGOUT_KEY);\n }\n\n const logoutEvent: GraffitiLogoutEvent = new CustomEvent(\"logout\", {\n detail: { actor },\n });\n this.sessionEvents.dispatchEvent(logoutEvent);\n } else {\n // Store the in progress and continue\n if (typeof window !== \"undefined\") {\n window.localStorage.setItem(\n LOCAL_STORAGE_IN_PROGRESS_LOGOUT_KEY,\n JSON.stringify(inProgressLogout),\n );\n } else {\n this.inProgressLogout = inProgressLogout;\n }\n\n // Continue to the next authorization endpoint\n const [authorizationEndpoint, _] =\n inProgressLogout.servicesByAuthorization[\n inProgressLogout.tokens.length\n ];\n await this.services.authorization.logout(\n authorizationEndpoint,\n actor,\n token,\n );\n }\n }\n\n protected get loggedInSessions(): StoredSession[] {\n if (typeof window === \"undefined\") return loggedInSessions_;\n\n const data = window.localStorage.getItem(\n LOCAL_STORAGE_LOGGED_IN_SESSIONS_KEY,\n );\n if (!data) return [];\n\n let json: unknown;\n try {\n json = JSON.parse(data);\n } catch {\n console.error(\"Error parsing stored session data\");\n window.localStorage.removeItem(LOCAL_STORAGE_LOGGED_IN_SESSIONS_KEY);\n return [];\n }\n\n const parsed = array(StoredSessionSchema).safeParse(json);\n if (!parsed.success) {\n console.error(\"Stored session data is invalid\");\n window.localStorage.removeItem(LOCAL_STORAGE_LOGGED_IN_SESSIONS_KEY);\n return [];\n }\n return parsed.data;\n }\n protected set loggedInSessions(sessions: StoredSession[]) {\n if (typeof window === \"undefined\") {\n loggedInSessions_ = sessions;\n return;\n }\n\n window.localStorage.setItem(\n LOCAL_STORAGE_LOGGED_IN_SESSIONS_KEY,\n JSON.stringify(sessions),\n );\n }\n\n resolveSession(session: GraffitiSession): StoredSession {\n const resolvedSession = this.loggedInSessions.find(\n (s) => s.actor === session.actor,\n );\n if (!resolvedSession) {\n const logoutEvent: GraffitiLogoutEvent = new CustomEvent(\"logout\", {\n detail: { actor: session.actor },\n });\n this.sessionEvents.dispatchEvent(logoutEvent);\n throw new Error(\"Not logged in\");\n }\n return resolvedSession;\n }\n}\nlet loggedInSessions_: StoredSession[] = [];\n\nconst LOCAL_STORAGE_IN_PROGRESS_LOGIN_KEY = \"graffiti-login-in-progress\";\nconst LOCAL_STORAGE_IN_PROGRESS_LOGOUT_KEY = \"graffiti-logout-in-progress\";\nconst LOCAL_STORAGE_LOGGED_IN_SESSIONS_KEY = \"graffiti-sessions-logged-in\";\n\nconst GraffitiSessionSchema = object({\n actor: url(),\n});\n\nconst ServiceSessionSchema = object({\n token: string(),\n serviceEndpoint: url(),\n authorizationEndpoint: url(),\n});\n\nconst ServicesByAuthorizationSchema = array(\n tuple([\n url(), // Authorization endpoint\n array(\n object({\n endpoint: url(), // Service endpoint\n type: enum_([\"bucket\", \"personal-inbox\", \"shared-inbox\"]),\n }),\n ),\n ]),\n);\n\nconst InProgressSchema = extend(GraffitiSessionSchema, {\n tokens: array(string()),\n servicesByAuthorization: ServicesByAuthorizationSchema,\n});\n\nconst StoredSessionSchema = extend(InProgressSchema, {\n storageBucket: ServiceSessionSchema,\n personalInbox: ServiceSessionSchema,\n sharedInboxes: array(ServiceSessionSchema),\n});\n\ntype StoredSession = infer_<typeof StoredSessionSchema>;\n\nfunction serviceToEndpoint(service: Service): string {\n if (typeof service.serviceEndpoint === \"string\")\n return service.serviceEndpoint;\n throw new Error(`Service endpoint for ${service.id} is not a string`);\n}\n"],
|
|
5
|
-
"mappings": "AAOA,SAAS,gCAAgC;AACzC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAS,sBAAsB;AAG/B;AAAA,EAEE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,OACH;AAEA,MAAM,kCAAkC;AACxC,MAAM,2CAA2C;AACjD,MAAM,yCAAyC;AAC/C,MAAM,yCAAyC;AAC/C,MAAM,8CACX;AAEK,MAAM,SAAS;AAAA,EAGpB,YACqB,UAMnB;AANmB;AAOnB,UAAM,qBAAqB,IAAI,QAAc,CAAC,YAAY;AACxD,WAAK,SAAS,cAAc,YAAY;AAAA,QACtC;AAAA,QACA,CAAC,MAAM;AACL,cAAI,EAAE,aAAa,aAAc;AACjC,gBAAM,SAAS,6BAA6B,UAAU,EAAE,MAAM;AAC9D,cAAI,CAAC,OAAO,QAAS;AACrB,gBAAM,QAAQ,OAAO,MAAM;AAC3B,cAAI,MAAO,SAAQ,
|
|
4
|
+
"sourcesContent": ["import type {\n Graffiti,\n GraffitiLoginEvent,\n GraffitiLogoutEvent,\n GraffitiSession,\n GraffitiSessionInitializedEvent,\n} from \"@graffiti-garden/api\";\nimport { DecentralizedIdentifiers } from \"../1-services/2-dids\";\nimport {\n InitializedEventDetailSchema,\n LoginEventDetailSchema,\n LogoutEventDetailSchema,\n type Authorization,\n} from \"../1-services/1-authorization\";\nimport { StorageBuckets } from \"../1-services/3-storage-buckets\";\nimport type { Inboxes } from \"../1-services/4-inboxes\";\nimport type { Service } from \"did-resolver\";\nimport {\n type infer as infer_,\n extend,\n array,\n string,\n object,\n url,\n tuple,\n enum as enum_,\n} from \"zod/mini\";\n\nexport const DID_SERVICE_TYPE_GRAFFITI_INBOX = \"GraffitiInbox\";\nexport const DID_SERVICE_TYPE_GRAFFITI_STORAGE_BUCKET = \"GraffitiStorageBucket\";\nexport const DID_SERVICE_ID_GRAFFITI_PERSONAL_INBOX = \"#graffitiPersonalInbox\";\nexport const DID_SERVICE_ID_GRAFFITI_STORAGE_BUCKET = \"#graffitiStorageBucket\";\nexport const DID_SERVICE_ID_GRAFFITI_SHARED_INBOX_PREFIX =\n \"#graffitiSharedInbox_\";\n\nexport class Sessions {\n sessionEvents: Graffiti[\"sessionEvents\"] = new EventTarget();\n\n constructor(\n protected readonly services: {\n readonly dids: DecentralizedIdentifiers;\n readonly authorization: Authorization;\n readonly storageBuckets: StorageBuckets;\n readonly inboxes: Inboxes;\n },\n ) {\n const initializedPromise = new Promise<void>((resolve) => {\n this.services.authorization.eventTarget.addEventListener(\n \"initialized\",\n (e) => {\n if (!(e instanceof CustomEvent)) return;\n const parsed = InitializedEventDetailSchema.safeParse(e.detail);\n if (!parsed.success) return;\n const error = parsed.data?.error;\n if (error) console.error(error);\n resolve();\n },\n );\n });\n this.services.authorization.eventTarget.addEventListener(\n \"login\",\n this.onLogin.bind(this),\n );\n this.services.authorization.eventTarget.addEventListener(\n \"logout\",\n this.onLogout.bind(this),\n );\n\n // Handle account registration redirect immediately,\n // to prevent SPA routers from hijacking the URL too soon\n let loginPromise: Promise<void> | undefined;\n if (typeof window !== \"undefined\") {\n const actorEncoded = new URLSearchParams(window.location.search).get(\n \"actor\",\n );\n if (actorEncoded) {\n try {\n // Get the actor\n const actor = decodeURIComponent(actorEncoded);\n // Strip it from the URL\n const url = new URL(window.location.toString());\n url.searchParams.delete(\"actor\");\n window.history.replaceState({}, \"\", url.toString());\n // Complete the login\n loginPromise = this.login(actor);\n } catch (error) {\n console.error(\"Error decoding actor:\", error);\n }\n }\n }\n\n (async () => {\n // Allow listeners to be added before dispatching events\n await new Promise((resolve) => setTimeout(resolve, 0));\n\n // Wait for login to complete, if there\n await loginPromise;\n\n for (const session of this.loggedInSessions) {\n const loginEvent: GraffitiLoginEvent = new CustomEvent(\"login\", {\n detail: { session: { actor: session.actor } },\n });\n this.sessionEvents.dispatchEvent(loginEvent);\n }\n\n await initializedPromise;\n\n // Send own initialized event\n const initializedEvent: GraffitiSessionInitializedEvent = new CustomEvent(\n \"initialized\",\n );\n this.sessionEvents.dispatchEvent(initializedEvent);\n })();\n }\n\n protected inProgressLogin: infer_<typeof InProgressSchema> | undefined =\n undefined;\n protected inProgressLogout: infer_<typeof InProgressSchema> | undefined =\n undefined;\n\n async login(actor: string) {\n try {\n await this.login_(actor);\n } catch (e) {\n const loginEvent: GraffitiLoginEvent = new CustomEvent(\"login\", {\n detail: {\n error: e instanceof Error ? e : new Error(String(e)),\n session: { actor },\n },\n });\n this.sessionEvents.dispatchEvent(loginEvent);\n }\n }\n protected async login_(actor: string) {\n // First look to see if we're already logged in\n const existingSession = this.loggedInSessions.find(\n (session) => session.actor === actor,\n );\n if (existingSession) {\n this.sessionEvents.dispatchEvent(\n new CustomEvent(\"login\", { detail: { session: { actor } } }),\n );\n return;\n }\n\n const actorDocument = await this.services.dids.resolve(actor);\n\n const services = actorDocument.service;\n if (!services) {\n throw new Error(`No services found in actor document for ${actor}`);\n }\n\n const storageBucketService = services.find(\n (service) =>\n service.id === DID_SERVICE_ID_GRAFFITI_STORAGE_BUCKET &&\n service.type === DID_SERVICE_TYPE_GRAFFITI_STORAGE_BUCKET,\n );\n const personalInboxService = services.find(\n (service) =>\n service.id === DID_SERVICE_ID_GRAFFITI_PERSONAL_INBOX &&\n service.type === DID_SERVICE_TYPE_GRAFFITI_INBOX,\n );\n const sharedInboxServices = services.filter(\n (service) =>\n service.id.match(\n new RegExp(`^${DID_SERVICE_ID_GRAFFITI_SHARED_INBOX_PREFIX}\\\\d+$`),\n ) && service.type === DID_SERVICE_TYPE_GRAFFITI_INBOX,\n );\n\n if (\n !personalInboxService ||\n !storageBucketService ||\n sharedInboxServices.length === 0\n ) {\n throw new Error(\n `Required services not found in actor document for ${actor}`,\n );\n }\n\n // Massage the services into a list of endpoints with types\n const storageBucketEndpoint: string =\n serviceToEndpoint(storageBucketService);\n const personalInboxEndpoint: string =\n serviceToEndpoint(personalInboxService);\n const sharedInboxEndpoints: string[] =\n sharedInboxServices.map(serviceToEndpoint);\n const servicesWithTypes = [\n { endpoint: storageBucketEndpoint, type: \"bucket\" } as const,\n { endpoint: personalInboxEndpoint, type: \"personal-inbox\" } as const,\n ...sharedInboxEndpoints.map(\n (endpoint) =>\n ({\n endpoint,\n type: \"shared-inbox\",\n }) as const,\n ),\n ];\n\n // Fetch the authorization endpoints for each service\n const servicesWithAuthorizationEndpoints = await Promise.all(\n servicesWithTypes.map(async ({ endpoint, type }) => {\n const authorizationEndpoint = await (type === \"bucket\"\n ? this.services.storageBuckets.getAuthorizationEndpoint(endpoint)\n : this.services.inboxes.getAuthorizationEndpoint(endpoint));\n return { endpoint, authorizationEndpoint, type };\n }),\n );\n\n // Group the endpoints according to their authorization endpoints\n const servicesByAuthorizationMap: Map<\n string,\n {\n endpoint: string;\n type: \"bucket\" | \"personal-inbox\" | \"shared-inbox\";\n }[]\n > = new Map();\n servicesWithAuthorizationEndpoints.forEach(\n ({ authorizationEndpoint, endpoint, type }) => {\n if (!servicesByAuthorizationMap.has(authorizationEndpoint)) {\n servicesByAuthorizationMap.set(authorizationEndpoint, []);\n }\n servicesByAuthorizationMap\n .get(authorizationEndpoint)!\n .push({ endpoint, type });\n },\n );\n const servicesByAuthorization = [...servicesByAuthorizationMap.entries()];\n\n const session: GraffitiSession = { actor };\n\n const inProgressLogin: infer_<typeof InProgressSchema> = {\n ...session,\n tokens: [],\n servicesByAuthorization,\n };\n\n if (typeof window !== \"undefined\") {\n // Store the in-progress session in localStorage\n window.localStorage.setItem(\n LOCAL_STORAGE_IN_PROGRESS_LOGIN_KEY,\n JSON.stringify(inProgressLogin),\n );\n } else {\n this.inProgressLogin = inProgressLogin;\n }\n\n // Start the login process with the first endpoint\n const [firstAuthorizationEndpoint, firstServices] =\n servicesByAuthorization[0];\n await this.services.authorization.login(\n firstAuthorizationEndpoint,\n actor,\n firstServices.map((s) => s.endpoint),\n );\n }\n\n protected async onLogin(event: unknown) {\n if (!(event instanceof CustomEvent)) return;\n const parsed = LoginEventDetailSchema.safeParse(event.detail);\n if (!parsed.success) return;\n\n const actor = parsed.data.loginId;\n\n try {\n await this.onLogin_(parsed.data);\n } catch (e) {\n const LoginEvent: GraffitiLoginEvent = new CustomEvent(\"login\", {\n detail: {\n error: e instanceof Error ? e : new Error(String(e)),\n session: { actor },\n },\n });\n this.sessionEvents.dispatchEvent(LoginEvent);\n }\n }\n protected async onLogin_(loginDetail: infer_<typeof LoginEventDetailSchema>) {\n if (loginDetail.error) throw loginDetail.error;\n\n const token = loginDetail.token;\n const actor = loginDetail.loginId;\n\n // Lookup the in-progress session\n let inProgressLogin: infer_<typeof InProgressSchema>;\n if (typeof window !== \"undefined\") {\n const inProgressLoginString = window.localStorage.getItem(\n LOCAL_STORAGE_IN_PROGRESS_LOGIN_KEY,\n );\n if (!inProgressLoginString) {\n throw new Error(\"No in-progress login found\");\n }\n\n const json = JSON.parse(inProgressLoginString);\n inProgressLogin = InProgressSchema.parse(json);\n } else {\n if (!this.inProgressLogin) {\n throw new Error(\"No in-progress login found\");\n }\n inProgressLogin = this.inProgressLogin;\n }\n\n if (inProgressLogin.actor !== actor) {\n throw new Error(\"Actor mismatch in login response - concurrent logins?\");\n }\n\n inProgressLogin.tokens.push(token);\n\n if (\n inProgressLogin.tokens.length ===\n inProgressLogin.servicesByAuthorization.length\n ) {\n // Login complete!\n if (typeof window === \"undefined\") {\n this.inProgressLogin = undefined;\n } else {\n window.localStorage.removeItem(LOCAL_STORAGE_IN_PROGRESS_LOGIN_KEY);\n }\n\n // Build the completed session\n const services = inProgressLogin.servicesByAuthorization.flatMap(\n ([authorizationEndpoint, services], index) =>\n services.map((service) => ({\n token: inProgressLogin.tokens[index],\n serviceEndpoint: service.endpoint,\n authorizationEndpoint,\n type: service.type,\n })),\n );\n\n const session: StoredSession = {\n ...inProgressLogin,\n storageBucket: services.find((s) => s.type === \"bucket\")!,\n personalInbox: services.find((s) => s.type === \"personal-inbox\")!,\n sharedInboxes: services.filter((s) => s.type === \"shared-inbox\")!,\n };\n\n // Store the completed session\n const sessions = this.loggedInSessions;\n sessions.push(session);\n this.loggedInSessions = sessions;\n\n // Return the completed session\n const loginEvent: GraffitiLoginEvent = new CustomEvent(\"login\", {\n detail: { session: { actor } },\n });\n this.sessionEvents.dispatchEvent(loginEvent);\n } else {\n // Store the in progress and continue\n if (typeof window !== \"undefined\") {\n window.localStorage.setItem(\n LOCAL_STORAGE_IN_PROGRESS_LOGIN_KEY,\n JSON.stringify(inProgressLogin),\n );\n } else {\n this.inProgressLogin = inProgressLogin;\n }\n\n // Continue to the next authorization endpoint\n const [authorizationEndpoint, services] =\n inProgressLogin.servicesByAuthorization[inProgressLogin.tokens.length];\n await this.services.authorization.login(\n authorizationEndpoint,\n actor,\n services.map((s) => s.endpoint),\n );\n }\n }\n\n async logout(actor: string) {\n try {\n await this.logout_(actor);\n } catch (e) {\n const logoutEvent: GraffitiLogoutEvent = new CustomEvent(\"logout\", {\n detail: {\n error: e instanceof Error ? e : new Error(String(e)),\n actor,\n },\n });\n this.sessionEvents.dispatchEvent(logoutEvent);\n }\n }\n protected async logout_(actor: string) {\n const session = this.loggedInSessions.find(\n (session) => session.actor === actor,\n );\n if (!session) {\n throw new Error(`No session found for actor ${actor}`);\n }\n\n // Remove the session(s)\n this.loggedInSessions = this.loggedInSessions.filter(\n (session) => session.actor !== actor,\n );\n\n // Begin the logout\n const token = session.tokens.pop();\n if (!token) {\n throw new Error(\"No tokens found in session\");\n }\n // Store the in progress logout\n if (typeof window !== \"undefined\") {\n window.localStorage.setItem(\n LOCAL_STORAGE_IN_PROGRESS_LOGOUT_KEY,\n JSON.stringify(session),\n );\n } else {\n this.inProgressLogout = session;\n }\n const [authorizationEndpoint, _] =\n session.servicesByAuthorization[session.tokens.length];\n await this.services.authorization.logout(\n authorizationEndpoint,\n actor,\n token,\n );\n }\n\n protected async onLogout(event: unknown) {\n if (!(event instanceof CustomEvent)) return;\n const parsed = LogoutEventDetailSchema.safeParse(event.detail);\n if (!parsed.success) return;\n\n const actor = parsed.data.logoutId;\n\n try {\n await this.onLogout_(parsed.data);\n } catch (e) {\n const logoutEvent: GraffitiLogoutEvent = new CustomEvent(\"logout\", {\n detail: {\n error: e instanceof Error ? e : new Error(String(e)),\n actor,\n },\n });\n this.sessionEvents.dispatchEvent(logoutEvent);\n }\n }\n protected async onLogout_(\n logoutDetail: infer_<typeof LogoutEventDetailSchema>,\n ) {\n if (logoutDetail.error) throw logoutDetail.error;\n\n const actor = logoutDetail.logoutId;\n\n // Lookup the in-progress session\n let inProgressLogout: infer_<typeof InProgressSchema>;\n if (typeof window !== \"undefined\") {\n const inProgressLogoutString = window.localStorage.getItem(\n LOCAL_STORAGE_IN_PROGRESS_LOGOUT_KEY,\n );\n if (!inProgressLogoutString) {\n throw new Error(\"No in-progress logout found\");\n }\n\n const json = JSON.parse(inProgressLogoutString);\n inProgressLogout = InProgressSchema.parse(json);\n } else {\n if (!this.inProgressLogout) {\n throw new Error(\"No in-progress logout found\");\n }\n inProgressLogout = this.inProgressLogout;\n }\n\n if (inProgressLogout.actor !== actor) {\n throw new Error(\n \"Actor mismatch in logout response - concurrent logouts?\",\n );\n }\n\n const token = inProgressLogout.tokens.pop();\n if (!token) {\n // Logout complete\n if (typeof window === \"undefined\") {\n this.inProgressLogout = undefined;\n } else {\n window.localStorage.removeItem(LOCAL_STORAGE_IN_PROGRESS_LOGOUT_KEY);\n }\n\n const logoutEvent: GraffitiLogoutEvent = new CustomEvent(\"logout\", {\n detail: { actor },\n });\n this.sessionEvents.dispatchEvent(logoutEvent);\n } else {\n // Store the in progress and continue\n if (typeof window !== \"undefined\") {\n window.localStorage.setItem(\n LOCAL_STORAGE_IN_PROGRESS_LOGOUT_KEY,\n JSON.stringify(inProgressLogout),\n );\n } else {\n this.inProgressLogout = inProgressLogout;\n }\n\n // Continue to the next authorization endpoint\n const [authorizationEndpoint, _] =\n inProgressLogout.servicesByAuthorization[\n inProgressLogout.tokens.length\n ];\n await this.services.authorization.logout(\n authorizationEndpoint,\n actor,\n token,\n );\n }\n }\n\n protected get loggedInSessions(): StoredSession[] {\n if (typeof window === \"undefined\") return loggedInSessions_;\n\n const data = window.localStorage.getItem(\n LOCAL_STORAGE_LOGGED_IN_SESSIONS_KEY,\n );\n if (!data) return [];\n\n let json: unknown;\n try {\n json = JSON.parse(data);\n } catch {\n console.error(\"Error parsing stored session data\");\n window.localStorage.removeItem(LOCAL_STORAGE_LOGGED_IN_SESSIONS_KEY);\n return [];\n }\n\n const parsed = array(StoredSessionSchema).safeParse(json);\n if (!parsed.success) {\n console.error(\"Stored session data is invalid\");\n window.localStorage.removeItem(LOCAL_STORAGE_LOGGED_IN_SESSIONS_KEY);\n return [];\n }\n return parsed.data;\n }\n protected set loggedInSessions(sessions: StoredSession[]) {\n if (typeof window === \"undefined\") {\n loggedInSessions_ = sessions;\n return;\n }\n\n window.localStorage.setItem(\n LOCAL_STORAGE_LOGGED_IN_SESSIONS_KEY,\n JSON.stringify(sessions),\n );\n }\n\n resolveSession(session: GraffitiSession): StoredSession {\n const resolvedSession = this.loggedInSessions.find(\n (s) => s.actor === session.actor,\n );\n if (!resolvedSession) {\n const logoutEvent: GraffitiLogoutEvent = new CustomEvent(\"logout\", {\n detail: { actor: session.actor },\n });\n this.sessionEvents.dispatchEvent(logoutEvent);\n throw new Error(\"Not logged in\");\n }\n return resolvedSession;\n }\n}\nlet loggedInSessions_: StoredSession[] = [];\n\nconst LOCAL_STORAGE_IN_PROGRESS_LOGIN_KEY = \"graffiti-login-in-progress\";\nconst LOCAL_STORAGE_IN_PROGRESS_LOGOUT_KEY = \"graffiti-logout-in-progress\";\nconst LOCAL_STORAGE_LOGGED_IN_SESSIONS_KEY = \"graffiti-sessions-logged-in\";\n\nconst GraffitiSessionSchema = object({\n actor: url(),\n});\n\nconst ServiceSessionSchema = object({\n token: string(),\n serviceEndpoint: url(),\n authorizationEndpoint: url(),\n});\n\nconst ServicesByAuthorizationSchema = array(\n tuple([\n url(), // Authorization endpoint\n array(\n object({\n endpoint: url(), // Service endpoint\n type: enum_([\"bucket\", \"personal-inbox\", \"shared-inbox\"]),\n }),\n ),\n ]),\n);\n\nconst InProgressSchema = extend(GraffitiSessionSchema, {\n tokens: array(string()),\n servicesByAuthorization: ServicesByAuthorizationSchema,\n});\n\nconst StoredSessionSchema = extend(InProgressSchema, {\n storageBucket: ServiceSessionSchema,\n personalInbox: ServiceSessionSchema,\n sharedInboxes: array(ServiceSessionSchema),\n});\n\ntype StoredSession = infer_<typeof StoredSessionSchema>;\n\nfunction serviceToEndpoint(service: Service): string {\n if (typeof service.serviceEndpoint === \"string\")\n return service.serviceEndpoint;\n throw new Error(`Service endpoint for ${service.id} is not a string`);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["url", "session", "services"]
|
|
7
7
|
}
|
|
@@ -25,8 +25,9 @@ class ObjectEncoding {
|
|
|
25
25
|
}
|
|
26
26
|
async encode(partialObject, actor) {
|
|
27
27
|
partialObject = cleanUndefined(partialObject);
|
|
28
|
+
const uniqueChannels = [...new Set(partialObject.channels)];
|
|
28
29
|
const channelAttestationAndPublicIds = await Promise.all(
|
|
29
|
-
|
|
30
|
+
uniqueChannels.map(
|
|
30
31
|
(channel) => this.primitives.channelAttestations.attest(
|
|
31
32
|
// TODO: get this from the DID document of the actor
|
|
32
33
|
CHANNEL_ATTESTATION_METHOD_SHA256_ED25519,
|
|
@@ -79,7 +80,7 @@ class ObjectEncoding {
|
|
|
79
80
|
const tags = [new TextEncoder().encode(objectUrl), ...channelPublicIds];
|
|
80
81
|
const object = {
|
|
81
82
|
value: partialObject.value,
|
|
82
|
-
channels:
|
|
83
|
+
channels: uniqueChannels,
|
|
83
84
|
url: objectUrl,
|
|
84
85
|
actor,
|
|
85
86
|
...partialObject.allowed ? {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/3-protocol/3-object-encoding.ts"],
|
|
4
|
-
"sourcesContent": ["import type { JSONSchema } from \"json-schema-to-ts\";\nimport type {\n GraffitiObject,\n GraffitiObjectBase,\n GraffitiPostObject,\n} from \"@graffiti-garden/api\";\nimport type { ChannelAttestations } from \"../2-primitives/3-channel-attestations\";\nimport type { AllowedAttestations } from \"../2-primitives/4-allowed-attestations\";\nimport {\n CONTENT_ADDRESS_METHOD_SHA256,\n type ContentAddresses,\n} from \"../2-primitives/2-content-addresses\";\nimport { randomBytes } from \"@noble/hashes/utils.js\";\nimport {\n encode as dagCborEncode,\n decode as dagCborDecode,\n} from \"@ipld/dag-cbor\";\nimport {\n type infer as infer_,\n array,\n custom,\n looseObject,\n optional,\n strictObject,\n} from \"zod/mini\";\nimport { CHANNEL_ATTESTATION_METHOD_SHA256_ED25519 } from \"../2-primitives/3-channel-attestations\";\nimport { ALLOWED_ATTESTATION_METHOD_HMAC_SHA256 } from \"../2-primitives/4-allowed-attestations\";\nimport {\n STRING_ENCODER_METHOD_BASE64URL,\n type StringEncoder,\n} from \"../2-primitives/1-string-encoding\";\n\n// Objects have a max size of 32kb\n// If each channel and allowed actor takes 32 bytes\n// of space (i.e. they are hashed with 256 bit security)\n// then this means that the combined number of channels\n// and recipients of object has cannot exceed one thousand.\n// This seems like a reasonable limit and on par with\n// signal's group chat limit of 1000\nexport const MAX_OBJECT_SIZE_BYTES = 32 * 1024;\n\nexport class ObjectEncoding {\n constructor(\n protected readonly primitives: {\n readonly stringEncoder: StringEncoder;\n readonly channelAttestations: ChannelAttestations;\n readonly allowedAttestations: AllowedAttestations;\n readonly contentAddresses: ContentAddresses;\n },\n ) {}\n\n async encode<Schema extends JSONSchema>(\n partialObject: GraffitiPostObject<Schema>,\n actor: string,\n ): Promise<{\n object: GraffitiObject<Schema>;\n tags: Uint8Array[];\n objectBytes: Uint8Array;\n allowedTickets: Uint8Array[] | undefined;\n }> {\n // Clean out any undefineds\n partialObject = cleanUndefined(partialObject);\n\n // Create a verifiable attestation that the actor\n // knows the included channels without\n // directly revealing any channel to anyone who doesn't\n // know the channel already\n const channelAttestationAndPublicIds = await Promise.all(\n partialObject.channels.map((channel) =>\n this.primitives.channelAttestations.attest(\n // TODO: get this from the DID document of the actor\n CHANNEL_ATTESTATION_METHOD_SHA256_ED25519,\n actor,\n channel,\n ),\n ),\n );\n const channelAttestations = channelAttestationAndPublicIds.map(\n (c) => c.attestation,\n );\n const channelPublicIds = channelAttestationAndPublicIds.map(\n (c) => c.channelPublicId,\n );\n\n const objectData: infer_<typeof ObjectDataSchema> = {\n [VALUE_PROPERTY]: partialObject.value,\n [CHANNEL_ATTESTATIONS_PROPERTY]: channelAttestations,\n [NONCE_PROPERTY]: randomBytes(32),\n };\n\n let allowedTickets: Uint8Array[] | undefined = undefined;\n\n // If the object is private...\n if (Array.isArray(partialObject.allowed)) {\n // Create an attestation that the object's allowed list\n // includes the given actors, without revealing the\n // presence of an actor on the list to anyone except\n // that actor themselves. Each actor will receive a\n // \"ticket\" that they can use to verify their own membership\n // on the allowed list.\n const allowedAttestations = await Promise.all(\n partialObject.allowed.map(async (allowedActor) =>\n this.primitives.allowedAttestations.attest(\n // TODO: get this from the DID document of the actor\n ALLOWED_ATTESTATION_METHOD_HMAC_SHA256,\n allowedActor,\n ),\n ),\n );\n objectData[ALLOWED_ATTESTATIONS_PROPERTY] = allowedAttestations.map(\n (a) => a.attestation,\n );\n allowedTickets = allowedAttestations.map((a) => a.ticket);\n }\n\n // Encode the mixed JSON/binary data using CBOR\n const objectBytes = dagCborEncode(objectData);\n if (objectBytes.byteLength > MAX_OBJECT_SIZE_BYTES) {\n throw new Error(\"The object is too large\");\n }\n\n // Compute a public identifier (hash) of the object data\n const objectContentAddressBytes =\n await this.primitives.contentAddresses.register(\n // TODO: get this from the DID document of the actor\n CONTENT_ADDRESS_METHOD_SHA256,\n objectBytes,\n );\n const objectContentAddress = await this.primitives.stringEncoder.encode(\n STRING_ENCODER_METHOD_BASE64URL,\n objectContentAddressBytes,\n );\n // Use it to compute the object's URL\n const objectUrl = encodeObjectUrl(actor, objectContentAddress);\n\n const tags = [new TextEncoder().encode(objectUrl), ...channelPublicIds];\n\n const object: GraffitiObject<Schema> = {\n value: partialObject.value,\n channels: partialObject.channels,\n url: objectUrl,\n actor,\n ...(partialObject.allowed\n ? {\n allowed: partialObject.allowed,\n }\n : {}),\n } as GraffitiObject<Schema>;\n\n // Return object URL and allowed secrets\n return {\n object,\n tags,\n objectBytes,\n allowedTickets,\n };\n }\n\n async validate(\n object: GraffitiObjectBase,\n tags: Uint8Array[],\n objectBytes: Uint8Array,\n privateObjectInfo?:\n | {\n recipient: string;\n allowedTicket: Uint8Array;\n allowedIndex: number;\n }\n | {\n allowedTickets: Uint8Array[];\n },\n ): Promise<void> {\n if (objectBytes.byteLength > MAX_OBJECT_SIZE_BYTES) {\n throw new Error(\"Object is too big\");\n }\n const { actor, contentAddress } = decodeObjectUrl(object.url);\n if (actor !== object.actor) {\n throw new Error(\"Object actor does not match URL actor\");\n }\n\n const objectUrlTag = tags.at(0);\n if (!objectUrlTag) {\n throw new Error(\"No object URL tag\");\n }\n if (new TextDecoder().decode(objectUrlTag) !== object.url) {\n throw new Error(\"Object URL tag does not match object URL\");\n }\n const channelPublicIds = tags.slice(1);\n\n // Make sure the object content address matches the object content\n const contentAddressBytes =\n await this.primitives.stringEncoder.decode(contentAddress);\n const contentAddressMethod =\n await this.primitives.contentAddresses.getMethod(contentAddressBytes);\n const expectedContentAddress =\n await this.primitives.contentAddresses.register(\n contentAddressMethod,\n objectBytes,\n );\n if (\n expectedContentAddress.length !== contentAddressBytes.length ||\n !expectedContentAddress.every((b, i) => b === contentAddressBytes[i])\n ) {\n throw new Error(\"Content address is invalid\");\n }\n\n // Convert the raw object data from CBOR\n // back to a javascript object\n const objectDataUnknown = dagCborDecode(objectBytes);\n const objectData = ObjectDataSchema.parse(objectDataUnknown);\n\n // And extract the values\n const value = objectData[VALUE_PROPERTY];\n const channelAttestations = objectData[CHANNEL_ATTESTATIONS_PROPERTY];\n const allowedAttestations = objectData[ALLOWED_ATTESTATIONS_PROPERTY];\n\n // Validate that the object's value matches\n const valueBytes = dagCborEncode(value);\n const expectedValueBytes = dagCborEncode(object.value);\n if (\n valueBytes.length !== expectedValueBytes.length ||\n !valueBytes.every((b, i) => b === expectedValueBytes[i])\n ) {\n throw new Error(\"Object value does not match storage value\");\n }\n\n // Validate the object's channels\n if (channelAttestations.length !== channelPublicIds.length) {\n throw new Error(\"Not as many channel attestations and public ids\");\n }\n for (const [index, attestation] of channelAttestations.entries()) {\n const channelPublicId = channelPublicIds[index];\n const isValid = await this.primitives.channelAttestations.validate(\n attestation,\n actor,\n channelPublicId,\n );\n if (!isValid) {\n throw new Error(\"Invalid channel attestation\");\n }\n }\n if (object.channels.length) {\n // If any channels are included, they all must be included\n if (object.channels.length !== channelPublicIds.length) {\n throw new Error(\n \"Number of claimed channels does not match attestations/public IDs\",\n );\n }\n const channelAttestationMethod =\n await this.primitives.channelAttestations.getMethod(\n channelPublicIds[0],\n );\n const expectedChannelPublicIds = await Promise.all(\n object.channels.map((channel) =>\n this.primitives.channelAttestations.register(\n channelAttestationMethod,\n channel,\n ),\n ),\n );\n for (const [\n index,\n expectedPublicId,\n ] of expectedChannelPublicIds.entries()) {\n const actualPublicId = channelPublicIds[index];\n if (\n expectedPublicId.length !== actualPublicId.length ||\n !expectedPublicId.every((b, i) => b === actualPublicId[i])\n ) {\n throw new Error(\"Channel public id does not match expected\");\n }\n }\n }\n\n // Validate the recipient\n if (privateObjectInfo) {\n if (!allowedAttestations) {\n throw new Error(\"Object is public but thought to be private\");\n }\n\n let recipients: string[];\n let allowedTickets: Uint8Array[];\n let attestations: Uint8Array[];\n if (\"recipient\" in privateObjectInfo) {\n recipients = [privateObjectInfo.recipient];\n allowedTickets = [privateObjectInfo.allowedTicket];\n attestations = allowedAttestations.filter(\n (_, i) => i === privateObjectInfo.allowedIndex,\n );\n } else {\n recipients = [...(object.allowed ?? [])];\n allowedTickets = privateObjectInfo.allowedTickets;\n attestations = allowedAttestations;\n }\n\n // All recipients must be in the allowed list\n if (recipients.length !== object.allowed?.length) {\n throw new Error(\"Recipient count does not match object allowed list\");\n }\n if (!recipients.every((r) => object.allowed?.includes(r))) {\n throw new Error(\"Recipient not in object allowed list\");\n }\n\n for (const [index, recipient] of recipients.entries()) {\n const allowedTicket = allowedTickets.at(index);\n const allowedAttestation = attestations.at(index);\n if (!allowedTicket) {\n throw new Error(\"Missing allowed ticket for recipient\");\n }\n if (!allowedAttestation) {\n throw new Error(\"Missing allowed attestation for recipient\");\n }\n const isValid = await this.primitives.allowedAttestations.validate(\n allowedAttestation,\n recipient,\n allowedTicket,\n );\n\n if (!isValid) {\n throw new Error(\"Invalid allowed attestation for recipient\");\n }\n }\n } else if (allowedAttestations) {\n throw new Error(\"Object is private but no recipient info provided\");\n }\n }\n}\n\n// A compact data representation of the object data\nconst VALUE_PROPERTY = \"v\";\nconst CHANNEL_ATTESTATIONS_PROPERTY = \"c\";\nconst ALLOWED_ATTESTATIONS_PROPERTY = \"a\";\nconst NONCE_PROPERTY = \"n\";\n\nconst Uint8ArraySchema = custom<Uint8Array>(\n (v): v is Uint8Array => v instanceof Uint8Array,\n);\n\nconst ObjectDataSchema = strictObject({\n [VALUE_PROPERTY]: looseObject({}),\n [CHANNEL_ATTESTATIONS_PROPERTY]: array(Uint8ArraySchema),\n [ALLOWED_ATTESTATIONS_PROPERTY]: optional(array(Uint8ArraySchema)),\n [NONCE_PROPERTY]: Uint8ArraySchema,\n});\n\nexport const GRAFFITI_OBJECT_URL_PREFIX = \"graffiti:\";\n\n// Methods to encode and decode object URLs\nexport function encodeObjectUrlComponent(value: string) {\n const replaced = value.replace(/:/g, \"!\").replace(/\\//g, \"~\");\n return encodeURIComponent(replaced);\n}\nexport function decodeObjectUrlComponent(value: string) {\n const decoded = decodeURIComponent(value);\n return decoded.replace(/!/g, \":\").replace(/~/g, \"/\");\n}\nexport function encodeObjectUrl(actor: string, contentAddress: string) {\n return `${GRAFFITI_OBJECT_URL_PREFIX}${encodeObjectUrlComponent(actor)}:${encodeObjectUrlComponent(contentAddress)}`;\n}\nexport function decodeObjectUrl(objectUrl: string) {\n if (!objectUrl.startsWith(GRAFFITI_OBJECT_URL_PREFIX)) {\n throw new Error(\"Invalid object URL\");\n }\n\n const rest = objectUrl.slice(GRAFFITI_OBJECT_URL_PREFIX.length);\n const parts = rest.split(\":\");\n\n if (parts.length !== 2) {\n throw new Error(\"Invalid object URL format\");\n }\n\n const [actor, contentAddress] = parts;\n\n return {\n actor: decodeObjectUrlComponent(actor),\n contentAddress: decodeObjectUrlComponent(contentAddress),\n };\n}\n\nfunction cleanUndefined(value: any): any {\n if (value === undefined) return null;\n\n if (Array.isArray(value)) {\n return value.map(cleanUndefined);\n }\n\n if (typeof value === \"object\") {\n return Object.fromEntries(\n Object.entries(value)\n .filter(([, v]) => v !== undefined)\n .map(([k, v]) => [k, cleanUndefined(v)]),\n );\n }\n\n return value;\n}\n"],
|
|
5
|
-
"mappings": "AAQA;AAAA,EACE;AAAA,OAEK;AACP,SAAS,mBAAmB;AAC5B;AAAA,EACE,UAAU;AAAA,EACV,UAAU;AAAA,OACL;AACP;AAAA,EAEE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,iDAAiD;AAC1D,SAAS,8CAA8C;AACvD;AAAA,EACE;AAAA,OAEK;AASA,MAAM,wBAAwB,KAAK;AAEnC,MAAM,eAAe;AAAA,EAC1B,YACqB,YAMnB;AANmB;AAAA,EAMlB;AAAA,EAEH,MAAM,OACJ,eACA,OAMC;AAED,oBAAgB,eAAe,aAAa;
|
|
4
|
+
"sourcesContent": ["import type { JSONSchema } from \"json-schema-to-ts\";\nimport type {\n GraffitiObject,\n GraffitiObjectBase,\n GraffitiPostObject,\n} from \"@graffiti-garden/api\";\nimport type { ChannelAttestations } from \"../2-primitives/3-channel-attestations\";\nimport type { AllowedAttestations } from \"../2-primitives/4-allowed-attestations\";\nimport {\n CONTENT_ADDRESS_METHOD_SHA256,\n type ContentAddresses,\n} from \"../2-primitives/2-content-addresses\";\nimport { randomBytes } from \"@noble/hashes/utils.js\";\nimport {\n encode as dagCborEncode,\n decode as dagCborDecode,\n} from \"@ipld/dag-cbor\";\nimport {\n type infer as infer_,\n array,\n custom,\n looseObject,\n optional,\n strictObject,\n} from \"zod/mini\";\nimport { CHANNEL_ATTESTATION_METHOD_SHA256_ED25519 } from \"../2-primitives/3-channel-attestations\";\nimport { ALLOWED_ATTESTATION_METHOD_HMAC_SHA256 } from \"../2-primitives/4-allowed-attestations\";\nimport {\n STRING_ENCODER_METHOD_BASE64URL,\n type StringEncoder,\n} from \"../2-primitives/1-string-encoding\";\n\n// Objects have a max size of 32kb\n// If each channel and allowed actor takes 32 bytes\n// of space (i.e. they are hashed with 256 bit security)\n// then this means that the combined number of channels\n// and recipients of object has cannot exceed one thousand.\n// This seems like a reasonable limit and on par with\n// signal's group chat limit of 1000\nexport const MAX_OBJECT_SIZE_BYTES = 32 * 1024;\n\nexport class ObjectEncoding {\n constructor(\n protected readonly primitives: {\n readonly stringEncoder: StringEncoder;\n readonly channelAttestations: ChannelAttestations;\n readonly allowedAttestations: AllowedAttestations;\n readonly contentAddresses: ContentAddresses;\n },\n ) {}\n\n async encode<Schema extends JSONSchema>(\n partialObject: GraffitiPostObject<Schema>,\n actor: string,\n ): Promise<{\n object: GraffitiObject<Schema>;\n tags: Uint8Array[];\n objectBytes: Uint8Array;\n allowedTickets: Uint8Array[] | undefined;\n }> {\n // Clean out any undefineds\n partialObject = cleanUndefined(partialObject);\n\n const uniqueChannels = [...new Set(partialObject.channels)];\n\n // Create a verifiable attestation that the actor\n // knows the included channels without\n // directly revealing any channel to anyone who doesn't\n // know the channel already\n const channelAttestationAndPublicIds = await Promise.all(\n uniqueChannels.map((channel) =>\n this.primitives.channelAttestations.attest(\n // TODO: get this from the DID document of the actor\n CHANNEL_ATTESTATION_METHOD_SHA256_ED25519,\n actor,\n channel,\n ),\n ),\n );\n const channelAttestations = channelAttestationAndPublicIds.map(\n (c) => c.attestation,\n );\n const channelPublicIds = channelAttestationAndPublicIds.map(\n (c) => c.channelPublicId,\n );\n\n const objectData: infer_<typeof ObjectDataSchema> = {\n [VALUE_PROPERTY]: partialObject.value,\n [CHANNEL_ATTESTATIONS_PROPERTY]: channelAttestations,\n [NONCE_PROPERTY]: randomBytes(32),\n };\n\n let allowedTickets: Uint8Array[] | undefined = undefined;\n\n // If the object is private...\n if (Array.isArray(partialObject.allowed)) {\n // Create an attestation that the object's allowed list\n // includes the given actors, without revealing the\n // presence of an actor on the list to anyone except\n // that actor themselves. Each actor will receive a\n // \"ticket\" that they can use to verify their own membership\n // on the allowed list.\n const allowedAttestations = await Promise.all(\n partialObject.allowed.map(async (allowedActor) =>\n this.primitives.allowedAttestations.attest(\n // TODO: get this from the DID document of the actor\n ALLOWED_ATTESTATION_METHOD_HMAC_SHA256,\n allowedActor,\n ),\n ),\n );\n objectData[ALLOWED_ATTESTATIONS_PROPERTY] = allowedAttestations.map(\n (a) => a.attestation,\n );\n allowedTickets = allowedAttestations.map((a) => a.ticket);\n }\n\n // Encode the mixed JSON/binary data using CBOR\n const objectBytes = dagCborEncode(objectData);\n if (objectBytes.byteLength > MAX_OBJECT_SIZE_BYTES) {\n throw new Error(\"The object is too large\");\n }\n\n // Compute a public identifier (hash) of the object data\n const objectContentAddressBytes =\n await this.primitives.contentAddresses.register(\n // TODO: get this from the DID document of the actor\n CONTENT_ADDRESS_METHOD_SHA256,\n objectBytes,\n );\n const objectContentAddress = await this.primitives.stringEncoder.encode(\n STRING_ENCODER_METHOD_BASE64URL,\n objectContentAddressBytes,\n );\n // Use it to compute the object's URL\n const objectUrl = encodeObjectUrl(actor, objectContentAddress);\n\n const tags = [new TextEncoder().encode(objectUrl), ...channelPublicIds];\n\n const object: GraffitiObject<Schema> = {\n value: partialObject.value,\n channels: uniqueChannels,\n url: objectUrl,\n actor,\n ...(partialObject.allowed\n ? {\n allowed: partialObject.allowed,\n }\n : {}),\n } as GraffitiObject<Schema>;\n\n // Return object URL and allowed secrets\n return {\n object,\n tags,\n objectBytes,\n allowedTickets,\n };\n }\n\n async validate(\n object: GraffitiObjectBase,\n tags: Uint8Array[],\n objectBytes: Uint8Array,\n privateObjectInfo?:\n | {\n recipient: string;\n allowedTicket: Uint8Array;\n allowedIndex: number;\n }\n | {\n allowedTickets: Uint8Array[];\n },\n ): Promise<void> {\n if (objectBytes.byteLength > MAX_OBJECT_SIZE_BYTES) {\n throw new Error(\"Object is too big\");\n }\n const { actor, contentAddress } = decodeObjectUrl(object.url);\n if (actor !== object.actor) {\n throw new Error(\"Object actor does not match URL actor\");\n }\n\n const objectUrlTag = tags.at(0);\n if (!objectUrlTag) {\n throw new Error(\"No object URL tag\");\n }\n if (new TextDecoder().decode(objectUrlTag) !== object.url) {\n throw new Error(\"Object URL tag does not match object URL\");\n }\n const channelPublicIds = tags.slice(1);\n\n // Make sure the object content address matches the object content\n const contentAddressBytes =\n await this.primitives.stringEncoder.decode(contentAddress);\n const contentAddressMethod =\n await this.primitives.contentAddresses.getMethod(contentAddressBytes);\n const expectedContentAddress =\n await this.primitives.contentAddresses.register(\n contentAddressMethod,\n objectBytes,\n );\n if (\n expectedContentAddress.length !== contentAddressBytes.length ||\n !expectedContentAddress.every((b, i) => b === contentAddressBytes[i])\n ) {\n throw new Error(\"Content address is invalid\");\n }\n\n // Convert the raw object data from CBOR\n // back to a javascript object\n const objectDataUnknown = dagCborDecode(objectBytes);\n const objectData = ObjectDataSchema.parse(objectDataUnknown);\n\n // And extract the values\n const value = objectData[VALUE_PROPERTY];\n const channelAttestations = objectData[CHANNEL_ATTESTATIONS_PROPERTY];\n const allowedAttestations = objectData[ALLOWED_ATTESTATIONS_PROPERTY];\n\n // Validate that the object's value matches\n const valueBytes = dagCborEncode(value);\n const expectedValueBytes = dagCborEncode(object.value);\n if (\n valueBytes.length !== expectedValueBytes.length ||\n !valueBytes.every((b, i) => b === expectedValueBytes[i])\n ) {\n throw new Error(\"Object value does not match storage value\");\n }\n\n // Validate the object's channels\n if (channelAttestations.length !== channelPublicIds.length) {\n throw new Error(\"Not as many channel attestations and public ids\");\n }\n for (const [index, attestation] of channelAttestations.entries()) {\n const channelPublicId = channelPublicIds[index];\n const isValid = await this.primitives.channelAttestations.validate(\n attestation,\n actor,\n channelPublicId,\n );\n if (!isValid) {\n throw new Error(\"Invalid channel attestation\");\n }\n }\n if (object.channels.length) {\n // If any channels are included, they all must be included\n if (object.channels.length !== channelPublicIds.length) {\n throw new Error(\n \"Number of claimed channels does not match attestations/public IDs\",\n );\n }\n const channelAttestationMethod =\n await this.primitives.channelAttestations.getMethod(\n channelPublicIds[0],\n );\n const expectedChannelPublicIds = await Promise.all(\n object.channels.map((channel) =>\n this.primitives.channelAttestations.register(\n channelAttestationMethod,\n channel,\n ),\n ),\n );\n for (const [\n index,\n expectedPublicId,\n ] of expectedChannelPublicIds.entries()) {\n const actualPublicId = channelPublicIds[index];\n if (\n expectedPublicId.length !== actualPublicId.length ||\n !expectedPublicId.every((b, i) => b === actualPublicId[i])\n ) {\n throw new Error(\"Channel public id does not match expected\");\n }\n }\n }\n\n // Validate the recipient\n if (privateObjectInfo) {\n if (!allowedAttestations) {\n throw new Error(\"Object is public but thought to be private\");\n }\n\n let recipients: string[];\n let allowedTickets: Uint8Array[];\n let attestations: Uint8Array[];\n if (\"recipient\" in privateObjectInfo) {\n recipients = [privateObjectInfo.recipient];\n allowedTickets = [privateObjectInfo.allowedTicket];\n attestations = allowedAttestations.filter(\n (_, i) => i === privateObjectInfo.allowedIndex,\n );\n } else {\n recipients = [...(object.allowed ?? [])];\n allowedTickets = privateObjectInfo.allowedTickets;\n attestations = allowedAttestations;\n }\n\n // All recipients must be in the allowed list\n if (recipients.length !== object.allowed?.length) {\n throw new Error(\"Recipient count does not match object allowed list\");\n }\n if (!recipients.every((r) => object.allowed?.includes(r))) {\n throw new Error(\"Recipient not in object allowed list\");\n }\n\n for (const [index, recipient] of recipients.entries()) {\n const allowedTicket = allowedTickets.at(index);\n const allowedAttestation = attestations.at(index);\n if (!allowedTicket) {\n throw new Error(\"Missing allowed ticket for recipient\");\n }\n if (!allowedAttestation) {\n throw new Error(\"Missing allowed attestation for recipient\");\n }\n const isValid = await this.primitives.allowedAttestations.validate(\n allowedAttestation,\n recipient,\n allowedTicket,\n );\n\n if (!isValid) {\n throw new Error(\"Invalid allowed attestation for recipient\");\n }\n }\n } else if (allowedAttestations) {\n throw new Error(\"Object is private but no recipient info provided\");\n }\n }\n}\n\n// A compact data representation of the object data\nconst VALUE_PROPERTY = \"v\";\nconst CHANNEL_ATTESTATIONS_PROPERTY = \"c\";\nconst ALLOWED_ATTESTATIONS_PROPERTY = \"a\";\nconst NONCE_PROPERTY = \"n\";\n\nconst Uint8ArraySchema = custom<Uint8Array>(\n (v): v is Uint8Array => v instanceof Uint8Array,\n);\n\nconst ObjectDataSchema = strictObject({\n [VALUE_PROPERTY]: looseObject({}),\n [CHANNEL_ATTESTATIONS_PROPERTY]: array(Uint8ArraySchema),\n [ALLOWED_ATTESTATIONS_PROPERTY]: optional(array(Uint8ArraySchema)),\n [NONCE_PROPERTY]: Uint8ArraySchema,\n});\n\nexport const GRAFFITI_OBJECT_URL_PREFIX = \"graffiti:\";\n\n// Methods to encode and decode object URLs\nexport function encodeObjectUrlComponent(value: string) {\n const replaced = value.replace(/:/g, \"!\").replace(/\\//g, \"~\");\n return encodeURIComponent(replaced);\n}\nexport function decodeObjectUrlComponent(value: string) {\n const decoded = decodeURIComponent(value);\n return decoded.replace(/!/g, \":\").replace(/~/g, \"/\");\n}\nexport function encodeObjectUrl(actor: string, contentAddress: string) {\n return `${GRAFFITI_OBJECT_URL_PREFIX}${encodeObjectUrlComponent(actor)}:${encodeObjectUrlComponent(contentAddress)}`;\n}\nexport function decodeObjectUrl(objectUrl: string) {\n if (!objectUrl.startsWith(GRAFFITI_OBJECT_URL_PREFIX)) {\n throw new Error(\"Invalid object URL\");\n }\n\n const rest = objectUrl.slice(GRAFFITI_OBJECT_URL_PREFIX.length);\n const parts = rest.split(\":\");\n\n if (parts.length !== 2) {\n throw new Error(\"Invalid object URL format\");\n }\n\n const [actor, contentAddress] = parts;\n\n return {\n actor: decodeObjectUrlComponent(actor),\n contentAddress: decodeObjectUrlComponent(contentAddress),\n };\n}\n\nfunction cleanUndefined(value: any): any {\n if (value === undefined) return null;\n\n if (Array.isArray(value)) {\n return value.map(cleanUndefined);\n }\n\n if (typeof value === \"object\") {\n return Object.fromEntries(\n Object.entries(value)\n .filter(([, v]) => v !== undefined)\n .map(([k, v]) => [k, cleanUndefined(v)]),\n );\n }\n\n return value;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|