@graffiti-garden/implementation-decentralized 0.0.7 → 0.0.9

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.
@@ -30,6 +30,7 @@ var import_dids = require("../1-services/2-dids");
30
30
  var import_authorization = require("../1-services/1-authorization");
31
31
  var import_storage_buckets = require("../1-services/3-storage-buckets");
32
32
  var import_mini = require("zod/mini");
33
+ var import_utilities = require("../1-services/utilities");
33
34
  const DID_SERVICE_TYPE_GRAFFITI_INBOX = "GraffitiInbox";
34
35
  const DID_SERVICE_TYPE_GRAFFITI_STORAGE_BUCKET = "GraffitiStorageBucket";
35
36
  const DID_SERVICE_ID_GRAFFITI_PERSONAL_INBOX = "#graffitiPersonalInbox";
@@ -280,7 +281,10 @@ class Sessions {
280
281
  } catch (e) {
281
282
  const logoutEvent = new CustomEvent("logout", {
282
283
  detail: {
283
- error: e instanceof Error ? e : new Error(String(e)),
284
+ error: e instanceof import_utilities.GraffitiErrorUnauthorized ? (
285
+ // Unauthorized errors can be treated as successful logouts
286
+ void 0
287
+ ) : e instanceof Error ? e : new Error(String(e)),
284
288
  actor
285
289
  }
286
290
  });
@@ -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.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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",
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\";\nimport { GraffitiErrorUnauthorized } from \"../1-services/utilities\";\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:\n e instanceof GraffitiErrorUnauthorized\n ? // Unauthorized errors can be treated as successful logouts\n undefined\n : e instanceof Error\n ? e\n : 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
  }
@@ -38,6 +38,7 @@ var import_dids = require("../1-services/2-dids");
38
38
  var import_authorization = require("../1-services/1-authorization");
39
39
  var import_storage_buckets = require("../1-services/3-storage-buckets");
40
40
  var import_inboxes = require("../1-services/4-inboxes");
41
+ var import_utilities = require("../1-services/utilities");
41
42
  var import_string_encoding = require("../2-primitives/1-string-encoding");
42
43
  var import_content_addresses = require("../2-primitives/2-content-addresses");
43
44
  var import_channel_attestations = require("../2-primitives/3-channel-attestations");
@@ -88,6 +89,7 @@ const MESSAGE_LABEL_VALID = 1;
88
89
  const MESSAGE_LABEL_TRASH = 2;
89
90
  const MESSAGE_LABEL_INVALID = 3;
90
91
  const CONCURRENCY = 16;
92
+ const LOCAL_STORAGE_REMEMBERED_HANDLE_KEY = "graffiti-login-remembered-handle";
91
93
  class GraffitiDecentralized {
92
94
  dids = new import_dids.DecentralizedIdentifiers();
93
95
  authorization = new import_authorization.Authorization();
@@ -132,20 +134,51 @@ class GraffitiDecentralized {
132
134
  this.sessionEvents.addEventListener("login", async (event) => {
133
135
  if (!(event instanceof CustomEvent)) return;
134
136
  const detail = event.detail;
135
- if (detail.error !== void 0 && !("manual" in detail && detail.manual)) {
137
+ if (detail.error !== void 0) {
138
+ if ("manual" in detail && detail.manual) return;
136
139
  alert("Login failed: " + detail.error.message);
137
- const actor = detail.session?.actor;
138
- let handle;
139
- if (actor) {
140
+ const actor2 = detail.session?.actor;
141
+ let handle2;
142
+ if (actor2) {
140
143
  try {
141
- handle = await this.actorToHandle(actor);
144
+ handle2 = await this.actorToHandle(actor2);
142
145
  } catch (error) {
143
146
  console.error("Failed to handle actor:", error);
144
147
  }
145
148
  }
146
- this.login_(handle);
149
+ this.login_(handle2);
150
+ return;
151
+ }
152
+ const actor = detail.session.actor;
153
+ let handle;
154
+ try {
155
+ handle = await this.actorToHandle(actor);
156
+ } catch (error) {
157
+ console.error("Failed to load handle from actor:", error);
158
+ }
159
+ if (handle) {
160
+ await this.rememberHandleInPasswordManager(handle, actor);
147
161
  }
148
162
  });
163
+ for (const method of [
164
+ "post",
165
+ "get",
166
+ "delete",
167
+ "postMedia",
168
+ "getMedia",
169
+ "deleteMedia"
170
+ ]) {
171
+ const fn = this[method];
172
+ this[method] = async (...args) => {
173
+ try {
174
+ return await fn(...args);
175
+ } catch (e) {
176
+ const session = args[args.length - 1];
177
+ await this.tryLogoutIfUnauthorized(e, session);
178
+ throw e;
179
+ }
180
+ };
181
+ }
149
182
  }
150
183
  actorToHandle = this.handles.actorToHandle.bind(this.handles);
151
184
  handleToActor = this.handles.handleToActor.bind(this.handles);
@@ -176,7 +209,8 @@ class GraffitiDecentralized {
176
209
  const input = template?.querySelector(
177
210
  "#username"
178
211
  );
179
- input?.setAttribute("value", proposedHandle);
212
+ const rememberedHandle = proposedHandle || await this.getRememberedHandleFromPasswordManager();
213
+ if (input) input.value = rememberedHandle ?? "";
180
214
  input?.addEventListener("focus", () => input?.select());
181
215
  setTimeout(() => input?.focus(), 0);
182
216
  template?.querySelector("#graffiti-login-handle-form")?.addEventListener("submit", async (e) => {
@@ -263,6 +297,66 @@ class GraffitiDecentralized {
263
297
  await this.sessions.login(actor);
264
298
  }
265
299
  }
300
+ async rememberHandleInPasswordManager(handle, actor) {
301
+ if (typeof window === "undefined") return;
302
+ const credentials = window.navigator.credentials;
303
+ const PasswordCredentialConstructor = window.PasswordCredential;
304
+ if (!credentials?.store || !PasswordCredentialConstructor) {
305
+ this.rememberHandleInBrowserStorage(handle);
306
+ return;
307
+ }
308
+ try {
309
+ const credential = new PasswordCredentialConstructor({
310
+ id: handle,
311
+ // Graffiti logins use external authorization; this stable placeholder
312
+ // allows browsers/password managers to persist the handle.
313
+ password: "no-password",
314
+ name: handle
315
+ });
316
+ await credentials.store(credential);
317
+ } catch (error) {
318
+ console.error("Failed to remember handle in password manager:", error);
319
+ this.rememberHandleInBrowserStorage(handle);
320
+ }
321
+ }
322
+ async getRememberedHandleFromPasswordManager() {
323
+ if (typeof window === "undefined") return void 0;
324
+ const credentials = window.navigator.credentials;
325
+ const PasswordCredentialConstructor = window.PasswordCredential;
326
+ if (!credentials?.get || !PasswordCredentialConstructor) {
327
+ return this.getRememberedHandleFromBrowserStorage();
328
+ }
329
+ try {
330
+ const credential = await credentials.get({
331
+ password: true,
332
+ mediation: "optional"
333
+ });
334
+ if (credential && "id" in credential && typeof credential.id === "string") {
335
+ return credential.id;
336
+ }
337
+ } catch (error) {
338
+ console.error("Failed to load handle from password manager:", error);
339
+ return this.getRememberedHandleFromBrowserStorage();
340
+ }
341
+ return void 0;
342
+ }
343
+ rememberHandleInBrowserStorage(handle) {
344
+ if (typeof window === "undefined") return;
345
+ try {
346
+ window.localStorage.setItem(LOCAL_STORAGE_REMEMBERED_HANDLE_KEY, handle);
347
+ } catch (error) {
348
+ console.error("Failed to remember handle in browser storage:", error);
349
+ }
350
+ }
351
+ getRememberedHandleFromBrowserStorage() {
352
+ if (typeof window === "undefined") return void 0;
353
+ try {
354
+ return window.localStorage.getItem(LOCAL_STORAGE_REMEMBERED_HANDLE_KEY) ?? void 0;
355
+ } catch (error) {
356
+ console.error("Failed to load handle from browser storage:", error);
357
+ return void 0;
358
+ }
359
+ }
266
360
  logout = async (session) => {
267
361
  await this.sessions.logout(session.actor);
268
362
  };
@@ -541,6 +635,7 @@ class GraffitiDecentralized {
541
635
  error: next.error,
542
636
  origin: allInboxes[next.index].serviceEndpoint
543
637
  };
638
+ this.tryLogoutIfUnauthorized(next.error, session);
544
639
  } else if (next.result.done) {
545
640
  const inbox = allInboxes[next.index];
546
641
  cursors[inbox.serviceEndpoint] = next.result.value;
@@ -595,6 +690,9 @@ class GraffitiDecentralized {
595
690
  }
596
691
  }
597
692
  }
693
+ } catch (e) {
694
+ this.tryLogoutIfUnauthorized(e, session);
695
+ throw e;
598
696
  } finally {
599
697
  await Promise.all(
600
698
  iterators.map(async (it) => {
@@ -630,6 +728,11 @@ class GraffitiDecentralized {
630
728
  }
631
729
  return this.discoverMeta(channels, {}, cursors, session);
632
730
  };
731
+ async tryLogoutIfUnauthorized(e, session) {
732
+ if (e instanceof import_utilities.GraffitiErrorUnauthorized && session) {
733
+ await this.sessions.logout(session.actor);
734
+ }
735
+ }
633
736
  async announceObject(object, tags, allowedTickets, storageBucketKey, session, priorAnnouncements) {
634
737
  const resolvedSession = this.sessions.resolveSession(session);
635
738
  const metadataBase = {