@graffiti-garden/implementation-decentralized 0.0.2 → 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/1-services/4-inboxes-tests.d.ts.map +1 -1
- package/dist/1-services/4-inboxes.d.ts +3 -3
- package/dist/1-services/4-inboxes.d.ts.map +1 -1
- 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/3-protocol/login-dialog.html.d.ts +1 -1
- package/dist/3-protocol/login-dialog.html.d.ts.map +1 -1
- package/dist/browser/index.js +7 -7
- package/dist/browser/index.js.map +3 -3
- package/dist/browser/login-dialog.html-VTDKJZBG.js +44 -0
- package/dist/browser/login-dialog.html-VTDKJZBG.js.map +7 -0
- package/dist/browser/{style-YUTCEBZV-RWYJV575.js → style-RMTPI5KV-Y5KAOOZR.js} +19 -36
- package/dist/browser/style-RMTPI5KV-Y5KAOOZR.js.map +7 -0
- package/dist/cjs/1-services/4-inboxes-tests.js +2 -0
- package/dist/cjs/1-services/4-inboxes-tests.js.map +2 -2
- package/dist/cjs/1-services/4-inboxes.js +17 -8
- package/dist/cjs/1-services/4-inboxes.js.map +2 -2
- 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 +193 -135
- package/dist/cjs/3-protocol/4-graffiti.js.map +3 -3
- package/dist/cjs/3-protocol/login-dialog.html.js +9 -9
- package/dist/cjs/3-protocol/login-dialog.html.js.map +1 -1
- package/dist/esm/1-services/4-inboxes-tests.js +2 -0
- package/dist/esm/1-services/4-inboxes-tests.js.map +2 -2
- package/dist/esm/1-services/4-inboxes.js +19 -9
- package/dist/esm/1-services/4-inboxes.js.map +2 -2
- 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 +194 -135
- package/dist/esm/3-protocol/4-graffiti.js.map +3 -3
- package/dist/esm/3-protocol/login-dialog.html.js +9 -9
- package/dist/esm/3-protocol/login-dialog.html.js.map +1 -1
- package/package.json +7 -7
- package/src/1-services/4-inboxes-tests.ts +2 -0
- package/src/1-services/4-inboxes.ts +25 -15
- 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 +260 -170
- package/src/3-protocol/login-dialog.html.ts +9 -9
- package/dist/browser/login-dialog.html-XUWYDNNI.js +0 -44
- package/dist/browser/login-dialog.html-XUWYDNNI.js.map +0 -7
- package/dist/browser/style-YUTCEBZV-RWYJV575.js.map +0 -7
|
@@ -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": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQA,+BAGO;AACP,mBAA4B;AAC5B,sBAGO;AACP,kBAOO;AACP,kCAA0D;AAC1D,kCAAuD;AACvD,6BAGO;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": ["dagCborEncode", "dagCborDecode"]
|
|
7
7
|
}
|
|
@@ -87,6 +87,7 @@ const MESSAGE_LABEL_UNLABELED = 0;
|
|
|
87
87
|
const MESSAGE_LABEL_VALID = 1;
|
|
88
88
|
const MESSAGE_LABEL_TRASH = 2;
|
|
89
89
|
const MESSAGE_LABEL_INVALID = 3;
|
|
90
|
+
const CONCURRENCY = 16;
|
|
90
91
|
class GraffitiDecentralized {
|
|
91
92
|
dids = new import_dids.DecentralizedIdentifiers();
|
|
92
93
|
authorization = new import_authorization.Authorization();
|
|
@@ -177,11 +178,7 @@ class GraffitiDecentralized {
|
|
|
177
178
|
);
|
|
178
179
|
input?.setAttribute("value", proposedHandle);
|
|
179
180
|
input?.addEventListener("focus", () => input?.select());
|
|
180
|
-
|
|
181
|
-
setTimeout(() => r(), 0);
|
|
182
|
-
}).then(() => {
|
|
183
|
-
input?.focus();
|
|
184
|
-
});
|
|
181
|
+
setTimeout(() => input?.focus(), 0);
|
|
185
182
|
template?.querySelector("#graffiti-login-handle-form")?.addEventListener("submit", async (e) => {
|
|
186
183
|
e.preventDefault();
|
|
187
184
|
input?.setAttribute("disabled", "true");
|
|
@@ -222,11 +219,12 @@ class GraffitiDecentralized {
|
|
|
222
219
|
e.preventDefault();
|
|
223
220
|
this.login_("");
|
|
224
221
|
});
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
222
|
+
setTimeout(
|
|
223
|
+
() => template?.querySelector(
|
|
224
|
+
"#graffiti-login-new"
|
|
225
|
+
)?.focus(),
|
|
226
|
+
0
|
|
227
|
+
);
|
|
230
228
|
}
|
|
231
229
|
const createUrl = new URL(this.identityCreatorEndpoint);
|
|
232
230
|
createUrl.searchParams.set(
|
|
@@ -600,14 +598,14 @@ class GraffitiDecentralized {
|
|
|
600
598
|
cursor: JSON.stringify({
|
|
601
599
|
channels,
|
|
602
600
|
cursors
|
|
603
|
-
})
|
|
604
|
-
continue: (session2) => this.discoverMeta(channels, schema, cursors, session2)
|
|
601
|
+
})
|
|
605
602
|
};
|
|
606
603
|
}
|
|
607
604
|
discover = (...args) => {
|
|
608
605
|
const [channels, schema, session] = args;
|
|
609
606
|
return this.discoverMeta(channels, schema, {}, session);
|
|
610
607
|
};
|
|
608
|
+
// @ts-ignore
|
|
611
609
|
continueDiscover = (...args) => {
|
|
612
610
|
const [cursor, session] = args;
|
|
613
611
|
let channels;
|
|
@@ -754,150 +752,210 @@ class GraffitiDecentralized {
|
|
|
754
752
|
queryArguments.cursor,
|
|
755
753
|
inboxToken
|
|
756
754
|
);
|
|
755
|
+
const inFlight = [];
|
|
756
|
+
let doneValue = null;
|
|
757
757
|
while (true) {
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
758
|
+
while (doneValue === null && inFlight.length < CONCURRENCY) {
|
|
759
|
+
const itResult = await iterator.next();
|
|
760
|
+
if (itResult.done) {
|
|
761
|
+
doneValue = itResult.value;
|
|
762
|
+
break;
|
|
763
|
+
}
|
|
764
|
+
const processPromise = this.processOneLabeledMessage(
|
|
765
|
+
inboxEndpoint,
|
|
766
|
+
itResult.value,
|
|
767
|
+
inboxToken,
|
|
768
|
+
recipient
|
|
769
|
+
).catch((e) => {
|
|
770
|
+
throw e;
|
|
771
|
+
});
|
|
772
|
+
inFlight.push(processPromise);
|
|
773
|
+
}
|
|
774
|
+
const nextProcessedPromise = inFlight.shift();
|
|
775
|
+
if (!nextProcessedPromise) {
|
|
776
|
+
if (doneValue !== null) return doneValue;
|
|
777
|
+
throw new Error("Process queue empty but no return value");
|
|
778
|
+
}
|
|
779
|
+
const processed = await nextProcessedPromise;
|
|
780
|
+
if (processed) yield processed;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
async processOneLabeledMessage(inboxEndpoint, result, inboxToken, recipient) {
|
|
784
|
+
const label = result.l;
|
|
785
|
+
if (label !== MESSAGE_LABEL_VALID && label !== MESSAGE_LABEL_UNLABELED && label !== MESSAGE_LABEL_TRASH)
|
|
786
|
+
return;
|
|
787
|
+
const messageId = result.id;
|
|
788
|
+
const { o: object, m: metadataBytes, t: receivedTags } = result.m;
|
|
789
|
+
let metadata;
|
|
790
|
+
try {
|
|
791
|
+
const metadataRaw = (0, import_dag_cbor.decode)(metadataBytes);
|
|
792
|
+
metadata = MessageMetadataSchema.parse(metadataRaw);
|
|
793
|
+
} catch (e) {
|
|
794
|
+
this.inboxes.label(
|
|
795
|
+
inboxEndpoint,
|
|
796
|
+
messageId,
|
|
797
|
+
MESSAGE_LABEL_INVALID,
|
|
798
|
+
inboxToken
|
|
799
|
+
);
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
const {
|
|
803
|
+
[MESSAGE_DATA_STORAGE_BUCKET_KEY]: storageBucketKey,
|
|
804
|
+
[MESSAGE_DATA_TOMBSTONED_MESSAGE_ID_KEY]: tombstonedMessageId
|
|
805
|
+
} = metadata;
|
|
806
|
+
const allowedTickets = MESSAGE_DATA_ALLOWED_TICKETS_KEY in metadata ? metadata[MESSAGE_DATA_ALLOWED_TICKETS_KEY] : void 0;
|
|
807
|
+
const announcements = MESSAGE_DATA_ANNOUNCEMENTS_KEY in metadata ? metadata[MESSAGE_DATA_ANNOUNCEMENTS_KEY] : void 0;
|
|
808
|
+
if (label === MESSAGE_LABEL_VALID) {
|
|
809
|
+
return {
|
|
810
|
+
messageId,
|
|
811
|
+
object,
|
|
812
|
+
storageBucketKey,
|
|
813
|
+
allowedTickets,
|
|
814
|
+
tags: receivedTags,
|
|
815
|
+
announcements
|
|
816
|
+
};
|
|
817
|
+
} else if (label === MESSAGE_LABEL_TRASH) {
|
|
818
|
+
if (!tombstonedMessageId) return;
|
|
819
|
+
const past = await this.inboxes.get(
|
|
820
|
+
inboxEndpoint,
|
|
821
|
+
tombstonedMessageId,
|
|
822
|
+
inboxToken
|
|
823
|
+
);
|
|
824
|
+
if (!past || past[import_inboxes.LABELED_MESSAGE_MESSAGE_KEY][import_inboxes.MESSAGE_OBJECT_KEY].url !== object.url)
|
|
825
|
+
return;
|
|
826
|
+
if (past[import_inboxes.LABELED_MESSAGE_LABEL_KEY] !== MESSAGE_LABEL_TRASH) {
|
|
771
827
|
this.inboxes.label(
|
|
772
828
|
inboxEndpoint,
|
|
773
|
-
|
|
774
|
-
|
|
829
|
+
tombstonedMessageId,
|
|
830
|
+
MESSAGE_LABEL_TRASH,
|
|
775
831
|
inboxToken
|
|
776
832
|
);
|
|
777
|
-
continue;
|
|
778
|
-
}
|
|
779
|
-
const {
|
|
780
|
-
[MESSAGE_DATA_STORAGE_BUCKET_KEY]: storageBucketKey,
|
|
781
|
-
[MESSAGE_DATA_TOMBSTONED_MESSAGE_ID_KEY]: tombstonedMessageId
|
|
782
|
-
} = metadata;
|
|
783
|
-
const allowedTickets = MESSAGE_DATA_ALLOWED_TICKETS_KEY in metadata ? metadata[MESSAGE_DATA_ALLOWED_TICKETS_KEY] : void 0;
|
|
784
|
-
const announcements = MESSAGE_DATA_ANNOUNCEMENTS_KEY in metadata ? metadata[MESSAGE_DATA_ANNOUNCEMENTS_KEY] : void 0;
|
|
785
|
-
if (label === MESSAGE_LABEL_VALID) {
|
|
786
|
-
yield {
|
|
787
|
-
messageId,
|
|
788
|
-
object,
|
|
789
|
-
storageBucketKey,
|
|
790
|
-
allowedTickets,
|
|
791
|
-
tags: receivedTags,
|
|
792
|
-
announcements
|
|
793
|
-
};
|
|
794
|
-
continue;
|
|
795
833
|
}
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
834
|
+
return {
|
|
835
|
+
messageId,
|
|
836
|
+
tombstone: true,
|
|
837
|
+
object,
|
|
838
|
+
storageBucketKey,
|
|
839
|
+
allowedTickets,
|
|
840
|
+
tags: receivedTags,
|
|
841
|
+
announcements
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
let validationError = void 0;
|
|
845
|
+
try {
|
|
846
|
+
const actor = object.actor;
|
|
847
|
+
const actorDocument = await this.dids.resolve(actor);
|
|
848
|
+
const storageBucketService = actorDocument?.service?.find(
|
|
849
|
+
(service) => service.id === import_sessions.DID_SERVICE_ID_GRAFFITI_STORAGE_BUCKET && service.type === import_sessions.DID_SERVICE_TYPE_GRAFFITI_STORAGE_BUCKET
|
|
850
|
+
);
|
|
851
|
+
if (!storageBucketService) {
|
|
852
|
+
throw new import_api.GraffitiErrorNotFound(
|
|
853
|
+
`Actor ${actor} has no storage bucket service`
|
|
802
854
|
);
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
}
|
|
808
|
-
if (typeof storageBucketService.serviceEndpoint !== "string") {
|
|
809
|
-
throw new import_api.GraffitiErrorNotFound(
|
|
810
|
-
`Actor ${actor} does not have a valid storage bucket endpoint`
|
|
811
|
-
);
|
|
812
|
-
}
|
|
813
|
-
const storageBucketEndpoint = storageBucketService.serviceEndpoint;
|
|
814
|
-
const objectBytes = await this.storageBuckets.get(
|
|
815
|
-
storageBucketEndpoint,
|
|
816
|
-
storageBucketKey,
|
|
817
|
-
import_object_encoding.MAX_OBJECT_SIZE_BYTES
|
|
855
|
+
}
|
|
856
|
+
if (typeof storageBucketService.serviceEndpoint !== "string") {
|
|
857
|
+
throw new import_api.GraffitiErrorNotFound(
|
|
858
|
+
`Actor ${actor} does not have a valid storage bucket endpoint`
|
|
818
859
|
);
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
await this.objectEncoding.validate(
|
|
830
|
-
object,
|
|
831
|
-
receivedTags,
|
|
832
|
-
objectBytes,
|
|
833
|
-
privateObjectInfo
|
|
860
|
+
}
|
|
861
|
+
const storageBucketEndpoint = storageBucketService.serviceEndpoint;
|
|
862
|
+
const objectBytes = await this.storageBuckets.get(
|
|
863
|
+
storageBucketEndpoint,
|
|
864
|
+
storageBucketKey,
|
|
865
|
+
import_object_encoding.MAX_OBJECT_SIZE_BYTES
|
|
866
|
+
);
|
|
867
|
+
if (MESSAGE_DATA_ALLOWED_TICKET_KEY in metadata && !recipient) {
|
|
868
|
+
throw new import_api.GraffitiErrorForbidden(
|
|
869
|
+
`Recipient is required when allowed ticket is present`
|
|
834
870
|
);
|
|
835
|
-
} catch (e) {
|
|
836
|
-
validationError = e;
|
|
837
871
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
872
|
+
const privateObjectInfo = allowedTickets ? { allowedTickets } : MESSAGE_DATA_ALLOWED_TICKET_KEY in metadata ? {
|
|
873
|
+
recipient: recipient ?? "null",
|
|
874
|
+
allowedTicket: metadata[MESSAGE_DATA_ALLOWED_TICKET_KEY],
|
|
875
|
+
allowedIndex: metadata[MESSAGE_DATA_ALLOWED_TICKET_INDEX_KEY]
|
|
876
|
+
} : void 0;
|
|
877
|
+
await this.objectEncoding.validate(
|
|
878
|
+
object,
|
|
879
|
+
receivedTags,
|
|
880
|
+
objectBytes,
|
|
881
|
+
privateObjectInfo
|
|
882
|
+
);
|
|
883
|
+
} catch (e) {
|
|
884
|
+
validationError = e;
|
|
885
|
+
}
|
|
886
|
+
if (tombstonedMessageId) {
|
|
887
|
+
if (validationError instanceof import_api.GraffitiErrorNotFound) {
|
|
888
|
+
this.inboxes.get(inboxEndpoint, tombstonedMessageId, inboxToken).then((result2) => {
|
|
889
|
+
if (
|
|
890
|
+
// Make sure that it actually references the object being deleted
|
|
891
|
+
result2 && result2[import_inboxes.LABELED_MESSAGE_MESSAGE_KEY][import_inboxes.MESSAGE_OBJECT_KEY].url === object.url && // And that the object is not already marked as trash
|
|
892
|
+
result2[import_inboxes.LABELED_MESSAGE_LABEL_KEY] !== MESSAGE_LABEL_TRASH
|
|
893
|
+
) {
|
|
849
894
|
this.inboxes.label(
|
|
850
895
|
inboxEndpoint,
|
|
851
|
-
|
|
896
|
+
tombstonedMessageId,
|
|
852
897
|
MESSAGE_LABEL_TRASH,
|
|
853
898
|
inboxToken
|
|
854
899
|
);
|
|
855
|
-
}
|
|
856
|
-
yield {
|
|
857
|
-
messageId,
|
|
858
|
-
tombstone: true,
|
|
859
|
-
object,
|
|
860
|
-
storageBucketKey,
|
|
861
|
-
allowedTickets,
|
|
862
|
-
tags: receivedTags,
|
|
863
|
-
announcements
|
|
864
|
-
};
|
|
865
|
-
} else {
|
|
866
|
-
console.error("Recieved an incorrect object");
|
|
867
|
-
console.error(validationError);
|
|
900
|
+
}
|
|
868
901
|
this.inboxes.label(
|
|
869
902
|
inboxEndpoint,
|
|
870
903
|
messageId,
|
|
871
|
-
|
|
904
|
+
MESSAGE_LABEL_TRASH,
|
|
872
905
|
inboxToken
|
|
873
906
|
);
|
|
874
|
-
}
|
|
907
|
+
});
|
|
908
|
+
return {
|
|
909
|
+
messageId,
|
|
910
|
+
tombstone: true,
|
|
911
|
+
object,
|
|
912
|
+
storageBucketKey,
|
|
913
|
+
allowedTickets,
|
|
914
|
+
tags: receivedTags,
|
|
915
|
+
announcements
|
|
916
|
+
};
|
|
875
917
|
} else {
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
}
|
|
918
|
+
console.error("Recieved an incorrect tombstone object");
|
|
919
|
+
console.error(validationError);
|
|
920
|
+
this.inboxes.label(
|
|
921
|
+
inboxEndpoint,
|
|
922
|
+
messageId,
|
|
923
|
+
MESSAGE_LABEL_INVALID,
|
|
924
|
+
inboxToken
|
|
925
|
+
);
|
|
926
|
+
}
|
|
927
|
+
} else {
|
|
928
|
+
if (validationError === void 0) {
|
|
929
|
+
this.inboxes.label(
|
|
930
|
+
inboxEndpoint,
|
|
931
|
+
messageId,
|
|
932
|
+
MESSAGE_LABEL_VALID,
|
|
933
|
+
inboxToken
|
|
934
|
+
);
|
|
935
|
+
return {
|
|
936
|
+
messageId,
|
|
937
|
+
object,
|
|
938
|
+
storageBucketKey,
|
|
939
|
+
tags: receivedTags,
|
|
940
|
+
allowedTickets,
|
|
941
|
+
announcements
|
|
942
|
+
};
|
|
943
|
+
} else if (validationError instanceof import_api.GraffitiErrorNotFound) {
|
|
944
|
+
this.inboxes.label(
|
|
945
|
+
inboxEndpoint,
|
|
946
|
+
messageId,
|
|
947
|
+
MESSAGE_LABEL_TRASH,
|
|
948
|
+
inboxToken
|
|
949
|
+
);
|
|
950
|
+
} else {
|
|
951
|
+
console.error("Recieved an incorrect object");
|
|
952
|
+
console.error(validationError);
|
|
953
|
+
this.inboxes.label(
|
|
954
|
+
inboxEndpoint,
|
|
955
|
+
messageId,
|
|
956
|
+
MESSAGE_LABEL_INVALID,
|
|
957
|
+
inboxToken
|
|
958
|
+
);
|
|
901
959
|
}
|
|
902
960
|
}
|
|
903
961
|
}
|