@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.
@@ -108,6 +108,7 @@ import"./chunk-RFBBAUMM.js";var e=`.graffiti-modal {
108
108
  letter-spacing: 0.1em;
109
109
  text-align: center;
110
110
  color: var(--front);
111
+ line-height: 1.5em;
111
112
  }
112
113
 
113
114
  h1 a:not([type="button"]) {
@@ -266,4 +267,4 @@ import"./chunk-RFBBAUMM.js";var e=`.graffiti-modal {
266
267
  margin: calc(-1 * var(--blurpix));
267
268
  }
268
269
  `;export{e as default};
269
- //# sourceMappingURL=style-3ALLGCD7-QNFKN6AK.js.map
270
+ //# sourceMappingURL=style-RMTPI5KV-Y5KAOOZR.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../node_modules/@graffiti-garden/modal/src/style.css"],
4
+ "sourcesContent": [".graffiti-modal {\n --back: rgba(26, 26, 26, 0.85);\n --halfback: rgba(80, 80, 80, 0.85);\n --halfback2: rgba(26, 26, 26, 0.85);\n --hover: rgba(202, 122, 204, 0.3);\n --frontfaded: rgba(190, 190, 190);\n --front: rgba(240, 240, 240);\n --emph: rgb(202, 122, 204);\n --blurpix: 3px;\n border-color: var(--emph);\n box-sizing: border-box;\n border-width: 2px;\n padding: 0;\n margin: 0;\n border-radius: 1rem;\n box-shadow: 0 0 2rem black;\n overflow: hidden;\n opacity: 0;\n transition: opacity 0.3s;\n pointer-events: none;\n min-width: 95dvw;\n min-height: 95dvh;\n height: 95dvh;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n display: flex;\n flex-direction: column;\n justify-content: flex-start;\n align-items: center;\n font-family:\n Inter,\n -apple-system,\n BlinkMacSystemFont,\n \"Segoe UI\",\n Roboto,\n Oxygen,\n Ubuntu,\n Cantarell,\n \"Fira Sans\",\n \"Droid Sans\",\n \"Helvetica Neue\",\n sans-serif;\n color: var(--front);\n font-size: 150%;\n\n * {\n box-sizing: border-box;\n padding: 0;\n margin: 0;\n }\n\n ::selection {\n background: rgba(202, 122, 204, 0.3);\n }\n\n :focus {\n outline: 2px solid var(--front);\n }\n\n header {\n width: 100%;\n display: flex;\n justify-content: flex-end;\n }\n\n main {\n flex: 1;\n max-width: 600px;\n width: 100%;\n gap: 2em;\n padding-inline: clamp(1rem, 4dvw, 3rem);\n padding-block: clamp(1rem, 4dvh, 3rem);\n margin-inline: clamp(1rem, 4dvw, 3rem);\n margin-block: clamp(1rem, 4dvh, 3rem);\n background: var(--back);\n border-radius: 1rem;\n display: flex;\n flex-direction: column;\n overflow-y: auto;\n scrollbar-color: var(--emph) rgba(0, 0, 0, 0);\n }\n\n ul {\n list-style-type: none;\n display: flex;\n flex-direction: column;\n gap: 0.5em;\n align-items: stretch;\n justify-content: stretch;\n }\n\n aside {\n color: var(--frontfaded);\n }\n\n .secondary,\n a:not([type=\"button\"]) {\n color: var(--emph);\n }\n\n h1 {\n font-size: 120%;\n font-family:\n Rock Salt,\n cursive,\n sans-serif;\n letter-spacing: 0.1em;\n text-align: center;\n color: var(--front);\n line-height: 1.5em;\n }\n\n h1 a:not([type=\"button\"]) {\n color: inherit;\n }\n\n h1 a:hover {\n background: none;\n color: inherit;\n }\n\n button,\n input[type=\"submit\"],\n input[type=\"text\"],\n a[type=\"button\"] {\n font-size: inherit;\n width: 100%;\n text-align: center;\n display: block;\n border-radius: 1rem;\n border: 2px solid var(--emph);\n padding: 1em;\n padding-top: 0.5em;\n padding-bottom: 0.5em;\n transition: 0.1s;\n text-overflow: ellipsis;\n background: none;\n line-height: 1.2em;\n }\n\n input[type=\"text\"] {\n font-weight: 500;\n background: var(--front);\n text-align: left;\n color: black;\n }\n\n header button {\n border-radius: 0 0 0 1rem;\n border-right: none;\n border-top: none;\n background-color: var(--halfback2);\n width: fit-content;\n position: relative;\n overflow: hidden;\n }\n\n header button::before {\n z-index: -1;\n top: 0;\n left: 0;\n height: 100%;\n position: absolute;\n width: 100%;\n content: \"\";\n background-color: var(--back);\n }\n\n @media (max-height: 600px) {\n main {\n gap: 1em;\n }\n\n h1 {\n font-size: 100%;\n }\n }\n\n @media (max-width: 600px) {\n main {\n border-radius: 0;\n margin: auto;\n overflow: auto;\n }\n\n header button {\n border-radius: 0;\n border-left: none;\n width: 100%;\n }\n\n html {\n justify-content: safe center;\n }\n }\n\n a {\n text-decoration: none;\n }\n\n :is(button, ul a, input[type=\"submit\"]):hover {\n cursor: pointer;\n background: var(--hover);\n color: var(--front);\n }\n\n a:hover {\n text-decoration: underline;\n cursor: pointer;\n }\n\n form {\n display: flex;\n flex-direction: column;\n justify-content: flex-start;\n align-items: stretch;\n gap: 0.5em;\n }\n\n iframe {\n width: 100%;\n height: 100%;\n border: none;\n }\n\n :is(button, a[type=\"button\"], input[type=\"submit\"]).secondary {\n color: rgb(244, 213, 244);\n background: rgba(26, 26, 26, 0.6);\n }\n\n :is(button, a[type=\"button\"], input[type=\"submit\"]):not(.secondary) {\n background: var(--hover);\n color: white;\n }\n\n :is(button, a[type=\"button\"], input[type=\"submit\"]):hover {\n background: rgba(202, 122, 204, 0.6);\n color: white;\n text-decoration: none;\n }\n}\n\n.graffiti-modal[open] {\n pointer-events: inherit;\n opacity: 1;\n}\n\n.graffiti-modal::backdrop {\n background-color: black;\n opacity: 0.5;\n}\n\n.graffiti-modal::before {\n content: \"\";\n position: fixed;\n left: 0;\n right: 0;\n z-index: -1;\n background-image: url(graffiti.jpg);\n background-size: cover;\n background-repeat: no-repeat;\n background-position: 50% 50%;\n height: calc(100% + 2 * var(--blurpix));\n width: calc(100% + 2 * var(--blurpix));\n filter: blur(var(--blurpix));\n margin: calc(-1 * var(--blurpix));\n}\n"],
5
+ "mappings": "4BAAA,IAAAA,EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;",
6
+ "names": ["style_default"]
7
+ }
@@ -46,7 +46,7 @@ class Sessions {
46
46
  const parsed = import_authorization.InitializedEventDetailSchema.safeParse(e.detail);
47
47
  if (!parsed.success) return;
48
48
  const error = parsed.data?.error;
49
- if (error) console.log(error);
49
+ if (error) console.error(error);
50
50
  resolve();
51
51
  }
52
52
  );
@@ -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": "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",
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", "enum_"]
7
7
  }
@@ -41,8 +41,9 @@ class ObjectEncoding {
41
41
  }
42
42
  async encode(partialObject, actor) {
43
43
  partialObject = cleanUndefined(partialObject);
44
+ const uniqueChannels = [...new Set(partialObject.channels)];
44
45
  const channelAttestationAndPublicIds = await Promise.all(
45
- partialObject.channels.map(
46
+ uniqueChannels.map(
46
47
  (channel) => this.primitives.channelAttestations.attest(
47
48
  // TODO: get this from the DID document of the actor
48
49
  import_channel_attestations.CHANNEL_ATTESTATION_METHOD_SHA256_ED25519,
@@ -95,7 +96,7 @@ class ObjectEncoding {
95
96
  const tags = [new TextEncoder().encode(objectUrl), ...channelPublicIds];
96
97
  const object = {
97
98
  value: partialObject.value,
98
- channels: partialObject.channels,
99
+ channels: uniqueChannels,
99
100
  url: objectUrl,
100
101
  actor,
101
102
  ...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": "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",
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
  }