@graffiti-garden/implementation-decentralized 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/1-services/1-authorization.d.ts +1 -1
- package/dist/1-services/1-authorization.d.ts.map +1 -1
- package/dist/3-protocol/1-sessions.d.ts +3 -2
- package/dist/3-protocol/1-sessions.d.ts.map +1 -1
- package/dist/3-protocol/4-graffiti.d.ts +2 -0
- package/dist/3-protocol/4-graffiti.d.ts.map +1 -1
- package/dist/browser/index.js +6 -6
- package/dist/browser/index.js.map +3 -3
- package/dist/cjs/1-services/1-authorization.js +6 -2
- package/dist/cjs/1-services/1-authorization.js.map +2 -2
- package/dist/cjs/3-protocol/1-sessions.js +9 -6
- package/dist/cjs/3-protocol/1-sessions.js.map +2 -2
- package/dist/cjs/3-protocol/4-graffiti.js +3 -0
- package/dist/cjs/3-protocol/4-graffiti.js.map +2 -2
- package/dist/esm/1-services/1-authorization.js +6 -2
- package/dist/esm/1-services/1-authorization.js.map +2 -2
- package/dist/esm/3-protocol/1-sessions.js +9 -6
- package/dist/esm/3-protocol/1-sessions.js.map +2 -2
- package/dist/esm/3-protocol/4-graffiti.js +3 -0
- package/dist/esm/3-protocol/4-graffiti.js.map +2 -2
- package/package.json +1 -1
- package/src/1-services/1-authorization.ts +6 -1
- package/src/3-protocol/1-sessions.ts +16 -5
- package/src/3-protocol/4-graffiti.ts +5 -0
|
@@ -58,7 +58,7 @@ class Authorization {
|
|
|
58
58
|
this.eventTarget.dispatchEvent(new CustomEvent("login", { detail }));
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
-
async login_(authorizationEndpoint, loginId, serviceEndpoints) {
|
|
61
|
+
async login_(authorizationEndpoint, loginId, serviceEndpoints, navigation) {
|
|
62
62
|
const configuration = await this.getAuthorizationConfiguration(
|
|
63
63
|
authorizationEndpoint
|
|
64
64
|
);
|
|
@@ -165,7 +165,11 @@ class Authorization {
|
|
|
165
165
|
state
|
|
166
166
|
});
|
|
167
167
|
if (typeof window !== "undefined") {
|
|
168
|
-
|
|
168
|
+
if (navigation === "assign") {
|
|
169
|
+
window.location.assign(redirectTo.toString());
|
|
170
|
+
} else {
|
|
171
|
+
window.location.replace(redirectTo.toString());
|
|
172
|
+
}
|
|
169
173
|
} else {
|
|
170
174
|
console.log("Please open the following URL in your browser:");
|
|
171
175
|
console.log(redirectTo.toString());
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/1-services/1-authorization.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n discovery,\n randomState,\n buildAuthorizationUrl,\n authorizationCodeGrant,\n tokenRevocation,\n} from \"openid-client\";\nimport {\n type infer as infer_,\n string,\n url,\n array,\n object,\n optional,\n nullable,\n instanceof as instanceof_,\n undefined as undefined_,\n intersection,\n union,\n} from \"zod/mini\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type { Socket } from \"node:net\";\n\nconst AUTHORIZATION_ENDPOINT_METHOD_PREFIX_OAUTH2 = \"oauth2:\";\nconst LOCAL_STORAGE_OAUTH2_KEY = \"graffiti-auth-oauth2-data\";\n\nexport class Authorization {\n eventTarget: EventTarget = new EventTarget();\n\n constructor() {\n // Extract oauth redirect synchronously so the route\n // can be changed before any SPA routers (e.g. vue router)\n // start messing with things\n const oauthPromise = this.completeOauth();\n\n (async () => {\n // Allow listeners to be added first\n await new Promise((resolve) => setTimeout(resolve, 0));\n\n // Complete the oauth flow\n await oauthPromise;\n\n // Send an initialized event\n const initializedEvent: InitializedEvent = new CustomEvent(\"initialized\");\n this.eventTarget.dispatchEvent(initializedEvent);\n })();\n }\n\n async login(...args: Parameters<typeof this.login_>): Promise<void> {\n try {\n await this.login_(...args);\n } catch (e) {\n const error = e instanceof Error ? e : new Error(\"Unknown error\");\n const detail: LoginEvent[\"detail\"] = { loginId: args[1], error };\n this.eventTarget.dispatchEvent(new CustomEvent(\"login\", { detail }));\n }\n }\n protected async login_(\n authorizationEndpoint: string,\n loginId: string,\n serviceEndpoints: string[],\n ): Promise<void> {\n const configuration = await this.getAuthorizationConfiguration(\n authorizationEndpoint,\n );\n\n const scope = serviceEndpoints.map(encodeURIComponent).join(\" \");\n const state = randomState();\n\n let redirectUri: string;\n let waitForCallback: Promise<void> | undefined = undefined;\n if (typeof window !== \"undefined\") {\n // If in a browser, prepare for a redirect by\n // storing the configuration, expected state,\n // current URL, and endpoints in local storage\n redirectUri = window.location.href;\n const data: infer_<typeof OAuth2LoginDataSchema> = {\n loginId,\n redirectUri,\n authorizationEndpoint,\n state,\n serviceEndpoints,\n };\n window.localStorage.setItem(\n LOCAL_STORAGE_OAUTH2_KEY,\n JSON.stringify(data),\n );\n } else {\n // Otherwise, in node, start a local server to receive the callback\n const http = await import(\"node:http\").catch((e) => {\n throw new Error(\n \"Unrecognized environment: cannot find window or node:http\",\n );\n });\n const server = http.createServer();\n\n try {\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(0, \"::1\", resolve);\n });\n } catch (e) {\n try {\n server.close();\n } catch {}\n throw new Error(\"Failed to start local oauth callback server.\");\n }\n\n const address = server.address();\n if (!address) {\n try {\n server.close();\n } catch {}\n throw new Error(\"Failed to get local oauth callback server address.\");\n }\n redirectUri =\n typeof address === \"string\"\n ? `http://${address}`\n : `http://${address.family === \"IPv6\" ? `[${address.address}]` : address.address}:${address.port}`;\n\n // Wait for a callback request\n waitForCallback = new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(\n () => {\n try {\n server.close();\n } catch {}\n reject(\"Oauth callback timed out.\");\n },\n 5 * 60 * 1000, // 5 minutes\n );\n\n const sockets = new Set<Socket>();\n server.on(\"connection\", (socket: Socket) => {\n sockets.add(socket);\n socket.on(\"close\", () => {\n sockets.delete(socket);\n });\n });\n\n // Set up the actual request handler\n const onRequest = async (req: IncomingMessage, res: ServerResponse) => {\n try {\n const callbackUrl = new URL(req.url ?? \"/\", redirectUri);\n await this.onCallbackUrl({\n loginId,\n callbackUrl,\n configuration,\n expectedState: state,\n serviceEndpoints,\n });\n\n res.statusCode = 200;\n res.setHeader(\"Content-Type\", \"text/plain\");\n res.end(\"You may now close this window.\");\n } catch (e) {\n res.statusCode = 500;\n res.setHeader(\"Content-Type\", \"text/plain\");\n res.end(\"Error processing OAuth callback.\");\n\n throw e;\n } finally {\n clearTimeout(timeout);\n server.off(\"request\", onRequest);\n\n for (const socket of sockets) {\n socket.destroy();\n }\n\n server.close(() => resolve());\n }\n };\n\n server.on(\"request\", onRequest);\n });\n }\n\n // Construct the authorization URL\n const redirectUriStripped = new URL(redirectUri);\n redirectUriStripped.hash = \"\";\n redirectUriStripped.search = \"\";\n\n const redirectTo = buildAuthorizationUrl(configuration, {\n scope,\n redirect_uri: redirectUriStripped.toString(),\n state,\n });\n\n // Either redirect (browser) or print the URL and wait (node)\n if (typeof window !== \"undefined\") {\n window.location.href = redirectTo.toString();\n } else {\n console.log(\"Please open the following URL in your browser:\");\n console.log(redirectTo.toString());\n await waitForCallback;\n }\n }\n\n protected completeOauth() {\n if (typeof window === \"undefined\") return;\n\n // Look in local storage to see if we have a pending login\n const data = window.localStorage.getItem(LOCAL_STORAGE_OAUTH2_KEY);\n if (!data) return;\n\n let json: unknown;\n try {\n json = JSON.parse(data);\n } catch {\n console.error(\"Invalid OAuth2 login data in local storage.\");\n window.localStorage.removeItem(LOCAL_STORAGE_OAUTH2_KEY);\n return;\n }\n\n const parseResult = OAuth2LoginDataSchema.safeParse(json);\n if (!parseResult.success) {\n console.error(\n \"Invalid OAuth2 login data structure in local storage.\",\n parseResult.error,\n );\n window.localStorage.removeItem(LOCAL_STORAGE_OAUTH2_KEY);\n return;\n }\n\n const {\n loginId,\n redirectUri,\n authorizationEndpoint,\n state,\n serviceEndpoints,\n } = parseResult.data;\n\n try {\n // Make sure that we redirected back to the correct page\n const expectedUrl = new URL(redirectUri);\n const callbackUrl = new URL(window.location.href);\n if (expectedUrl.pathname !== callbackUrl.pathname) return;\n // Make sure it is actually an oauth call\n const params = callbackUrl.searchParams;\n if (!params.has(\"code\") && !params.has(\"error\")) return;\n\n // Restore the hash and query parameters to the expected URL,\n // removing the code, state, and error parameters\n window.history.replaceState({}, document.title, expectedUrl.toString());\n window.localStorage.removeItem(LOCAL_STORAGE_OAUTH2_KEY);\n\n return new Promise<void>((resolve) => setTimeout(resolve, 0))\n .then(() => this.getAuthorizationConfiguration(authorizationEndpoint))\n .then((configuration) =>\n this.onCallbackUrl({\n loginId,\n callbackUrl,\n configuration,\n expectedState: state,\n serviceEndpoints,\n }),\n )\n .catch((e) => {\n const error = e instanceof Error ? e : new Error(\"Unknown error\");\n const detail: LoginEvent[\"detail\"] = { loginId, error };\n this.eventTarget.dispatchEvent(new CustomEvent(\"login\", { detail }));\n });\n } catch (e) {\n console.error(e);\n }\n }\n\n protected async onCallbackUrl(args: {\n loginId: string;\n callbackUrl: URL;\n configuration: any;\n expectedState: string;\n serviceEndpoints: string[];\n }) {\n const {\n loginId,\n callbackUrl,\n configuration,\n expectedState,\n serviceEndpoints,\n } = args;\n\n const response = await authorizationCodeGrant(configuration, callbackUrl, {\n expectedState,\n });\n\n const token = response.access_token;\n const scope = response.scope;\n const grantedEndpoints =\n scope?.split(\" \").map(decodeURIComponent) || serviceEndpoints;\n\n // Make sure granted endpoints cover the requested endpoints\n if (\n !serviceEndpoints.every((endpoint) => grantedEndpoints.includes(endpoint))\n ) {\n throw new Error(\"Not all requested service endpoints were granted.\");\n }\n\n // Send a logged in event\n const loginEvent: LoginEvent = new CustomEvent(\"login\", {\n detail: {\n loginId,\n token,\n },\n });\n this.eventTarget.dispatchEvent(loginEvent);\n }\n\n async logout(\n authorizationEndpoint: string,\n logoutId: string,\n token: string,\n ): Promise<void> {\n try {\n await this.logout_(authorizationEndpoint, logoutId, token);\n } catch (e) {\n const error = e instanceof Error ? e : new Error(\"Unknown error\");\n const detail: LogoutEvent[\"detail\"] = { logoutId, error };\n this.eventTarget.dispatchEvent(new CustomEvent(\"logout\", { detail }));\n }\n }\n protected async logout_(\n authorizationEndpoint: string,\n logoutId: string,\n token: string,\n ): Promise<void> {\n const configuration = await this.getAuthorizationConfiguration(\n authorizationEndpoint,\n );\n await tokenRevocation(configuration, token);\n const detail: LogoutEvent[\"detail\"] = { logoutId };\n this.eventTarget.dispatchEvent(new CustomEvent(\"logout\", { detail }));\n }\n\n protected async getAuthorizationConfiguration(\n authorizationEndpoint: string,\n ): Promise<any> {\n // Parse the authorization endpoint\n if (\n !authorizationEndpoint.startsWith(\n AUTHORIZATION_ENDPOINT_METHOD_PREFIX_OAUTH2,\n )\n ) {\n throw new Error(\n `Unrecognized authorization endpoint method: ${authorizationEndpoint}`,\n );\n }\n const issuer = authorizationEndpoint.slice(\n AUTHORIZATION_ENDPOINT_METHOD_PREFIX_OAUTH2.length,\n );\n\n // Look up the oauth configuration\n let issuerUrl: URL;\n try {\n issuerUrl = new URL(issuer);\n } catch (e) {\n throw new Error(\"Invalid issuer URL.\");\n }\n\n return await discovery(issuerUrl, \"graffiti-client\");\n }\n}\n\nexport const LoginEventDetailSchema = intersection(\n object({\n loginId: string(),\n }),\n union([\n object({ token: string(), error: optional(undefined_()) }),\n object({ error: instanceof_(Error) }),\n ]),\n);\n\nexport const LogoutEventDetailSchema = object({\n logoutId: string(),\n error: optional(instanceof_(Error)),\n});\n\nexport const InitializedEventDetailSchema = optional(\n nullable(\n object({\n error: optional(instanceof_(Error)),\n }),\n ),\n);\n\nexport type LoginEvent = CustomEvent<infer_<typeof LoginEventDetailSchema>>;\nexport type LogoutEvent = CustomEvent<infer_<typeof LogoutEventDetailSchema>>;\nexport type InitializedEvent = CustomEvent<\n infer_<typeof InitializedEventDetailSchema>\n>;\n\nconst OAuth2LoginDataSchema = object({\n loginId: string(),\n redirectUri: url(),\n authorizationEndpoint: url(),\n state: string(),\n serviceEndpoints: array(url()),\n});\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,2BAMO;AACP,kBAYO;AAIP,MAAM,8CAA8C;AACpD,MAAM,2BAA2B;AAE1B,MAAM,cAAc;AAAA,EACzB,cAA2B,IAAI,YAAY;AAAA,EAE3C,cAAc;AAIZ,UAAM,eAAe,KAAK,cAAc;AAExC,KAAC,YAAY;AAEX,YAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,CAAC,CAAC;AAGrD,YAAM;AAGN,YAAM,mBAAqC,IAAI,YAAY,aAAa;AACxE,WAAK,YAAY,cAAc,gBAAgB;AAAA,IACjD,GAAG;AAAA,EACL;AAAA,EAEA,MAAM,SAAS,MAAqD;AAClE,QAAI;AACF,YAAM,KAAK,OAAO,GAAG,IAAI;AAAA,IAC3B,SAAS,GAAG;AACV,YAAM,QAAQ,aAAa,QAAQ,IAAI,IAAI,MAAM,eAAe;AAChE,YAAM,SAA+B,EAAE,SAAS,KAAK,CAAC,GAAG,MAAM;AAC/D,WAAK,YAAY,cAAc,IAAI,YAAY,SAAS,EAAE,OAAO,CAAC,CAAC;AAAA,IACrE;AAAA,EACF;AAAA,EACA,MAAgB,OACd,uBACA,SACA,
|
|
4
|
+
"sourcesContent": ["import {\n discovery,\n randomState,\n buildAuthorizationUrl,\n authorizationCodeGrant,\n tokenRevocation,\n} from \"openid-client\";\nimport {\n type infer as infer_,\n string,\n url,\n array,\n object,\n optional,\n nullable,\n instanceof as instanceof_,\n undefined as undefined_,\n intersection,\n union,\n} from \"zod/mini\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type { Socket } from \"node:net\";\n\nconst AUTHORIZATION_ENDPOINT_METHOD_PREFIX_OAUTH2 = \"oauth2:\";\nconst LOCAL_STORAGE_OAUTH2_KEY = \"graffiti-auth-oauth2-data\";\n\nexport class Authorization {\n eventTarget: EventTarget = new EventTarget();\n\n constructor() {\n // Extract oauth redirect synchronously so the route\n // can be changed before any SPA routers (e.g. vue router)\n // start messing with things\n const oauthPromise = this.completeOauth();\n\n (async () => {\n // Allow listeners to be added first\n await new Promise((resolve) => setTimeout(resolve, 0));\n\n // Complete the oauth flow\n await oauthPromise;\n\n // Send an initialized event\n const initializedEvent: InitializedEvent = new CustomEvent(\"initialized\");\n this.eventTarget.dispatchEvent(initializedEvent);\n })();\n }\n\n async login(...args: Parameters<typeof this.login_>): Promise<void> {\n try {\n await this.login_(...args);\n } catch (e) {\n const error = e instanceof Error ? e : new Error(\"Unknown error\");\n const detail: LoginEvent[\"detail\"] = { loginId: args[1], error };\n this.eventTarget.dispatchEvent(new CustomEvent(\"login\", { detail }));\n }\n }\n protected async login_(\n authorizationEndpoint: string,\n loginId: string,\n serviceEndpoints: string[],\n navigation: \"assign\" | \"replace\",\n ): Promise<void> {\n const configuration = await this.getAuthorizationConfiguration(\n authorizationEndpoint,\n );\n\n const scope = serviceEndpoints.map(encodeURIComponent).join(\" \");\n const state = randomState();\n\n let redirectUri: string;\n let waitForCallback: Promise<void> | undefined = undefined;\n if (typeof window !== \"undefined\") {\n // If in a browser, prepare for a redirect by\n // storing the configuration, expected state,\n // current URL, and endpoints in local storage\n redirectUri = window.location.href;\n const data: infer_<typeof OAuth2LoginDataSchema> = {\n loginId,\n redirectUri,\n authorizationEndpoint,\n state,\n serviceEndpoints,\n };\n window.localStorage.setItem(\n LOCAL_STORAGE_OAUTH2_KEY,\n JSON.stringify(data),\n );\n } else {\n // Otherwise, in node, start a local server to receive the callback\n const http = await import(\"node:http\").catch((e) => {\n throw new Error(\n \"Unrecognized environment: cannot find window or node:http\",\n );\n });\n const server = http.createServer();\n\n try {\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(0, \"::1\", resolve);\n });\n } catch (e) {\n try {\n server.close();\n } catch {}\n throw new Error(\"Failed to start local oauth callback server.\");\n }\n\n const address = server.address();\n if (!address) {\n try {\n server.close();\n } catch {}\n throw new Error(\"Failed to get local oauth callback server address.\");\n }\n redirectUri =\n typeof address === \"string\"\n ? `http://${address}`\n : `http://${address.family === \"IPv6\" ? `[${address.address}]` : address.address}:${address.port}`;\n\n // Wait for a callback request\n waitForCallback = new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(\n () => {\n try {\n server.close();\n } catch {}\n reject(\"Oauth callback timed out.\");\n },\n 5 * 60 * 1000, // 5 minutes\n );\n\n const sockets = new Set<Socket>();\n server.on(\"connection\", (socket: Socket) => {\n sockets.add(socket);\n socket.on(\"close\", () => {\n sockets.delete(socket);\n });\n });\n\n // Set up the actual request handler\n const onRequest = async (req: IncomingMessage, res: ServerResponse) => {\n try {\n const callbackUrl = new URL(req.url ?? \"/\", redirectUri);\n await this.onCallbackUrl({\n loginId,\n callbackUrl,\n configuration,\n expectedState: state,\n serviceEndpoints,\n });\n\n res.statusCode = 200;\n res.setHeader(\"Content-Type\", \"text/plain\");\n res.end(\"You may now close this window.\");\n } catch (e) {\n res.statusCode = 500;\n res.setHeader(\"Content-Type\", \"text/plain\");\n res.end(\"Error processing OAuth callback.\");\n\n throw e;\n } finally {\n clearTimeout(timeout);\n server.off(\"request\", onRequest);\n\n for (const socket of sockets) {\n socket.destroy();\n }\n\n server.close(() => resolve());\n }\n };\n\n server.on(\"request\", onRequest);\n });\n }\n\n // Construct the authorization URL\n const redirectUriStripped = new URL(redirectUri);\n redirectUriStripped.hash = \"\";\n redirectUriStripped.search = \"\";\n\n const redirectTo = buildAuthorizationUrl(configuration, {\n scope,\n redirect_uri: redirectUriStripped.toString(),\n state,\n });\n\n // Either redirect (browser) or print the URL and wait (node)\n if (typeof window !== \"undefined\") {\n if (navigation === \"assign\") {\n window.location.assign(redirectTo.toString());\n } else {\n window.location.replace(redirectTo.toString());\n }\n } else {\n console.log(\"Please open the following URL in your browser:\");\n console.log(redirectTo.toString());\n await waitForCallback;\n }\n }\n\n protected completeOauth() {\n if (typeof window === \"undefined\") return;\n\n // Look in local storage to see if we have a pending login\n const data = window.localStorage.getItem(LOCAL_STORAGE_OAUTH2_KEY);\n if (!data) return;\n\n let json: unknown;\n try {\n json = JSON.parse(data);\n } catch {\n console.error(\"Invalid OAuth2 login data in local storage.\");\n window.localStorage.removeItem(LOCAL_STORAGE_OAUTH2_KEY);\n return;\n }\n\n const parseResult = OAuth2LoginDataSchema.safeParse(json);\n if (!parseResult.success) {\n console.error(\n \"Invalid OAuth2 login data structure in local storage.\",\n parseResult.error,\n );\n window.localStorage.removeItem(LOCAL_STORAGE_OAUTH2_KEY);\n return;\n }\n\n const {\n loginId,\n redirectUri,\n authorizationEndpoint,\n state,\n serviceEndpoints,\n } = parseResult.data;\n\n try {\n // Make sure that we redirected back to the correct page\n const expectedUrl = new URL(redirectUri);\n const callbackUrl = new URL(window.location.href);\n if (expectedUrl.pathname !== callbackUrl.pathname) return;\n // Make sure it is actually an oauth call\n const params = callbackUrl.searchParams;\n if (!params.has(\"code\") && !params.has(\"error\")) return;\n\n // Restore the hash and query parameters to the expected URL,\n // removing the code, state, and error parameters\n window.history.replaceState({}, document.title, expectedUrl.toString());\n window.localStorage.removeItem(LOCAL_STORAGE_OAUTH2_KEY);\n\n return new Promise<void>((resolve) => setTimeout(resolve, 0))\n .then(() => this.getAuthorizationConfiguration(authorizationEndpoint))\n .then((configuration) =>\n this.onCallbackUrl({\n loginId,\n callbackUrl,\n configuration,\n expectedState: state,\n serviceEndpoints,\n }),\n )\n .catch((e) => {\n const error = e instanceof Error ? e : new Error(\"Unknown error\");\n const detail: LoginEvent[\"detail\"] = { loginId, error };\n this.eventTarget.dispatchEvent(new CustomEvent(\"login\", { detail }));\n });\n } catch (e) {\n console.error(e);\n }\n }\n\n protected async onCallbackUrl(args: {\n loginId: string;\n callbackUrl: URL;\n configuration: any;\n expectedState: string;\n serviceEndpoints: string[];\n }) {\n const {\n loginId,\n callbackUrl,\n configuration,\n expectedState,\n serviceEndpoints,\n } = args;\n\n const response = await authorizationCodeGrant(configuration, callbackUrl, {\n expectedState,\n });\n\n const token = response.access_token;\n const scope = response.scope;\n const grantedEndpoints =\n scope?.split(\" \").map(decodeURIComponent) || serviceEndpoints;\n\n // Make sure granted endpoints cover the requested endpoints\n if (\n !serviceEndpoints.every((endpoint) => grantedEndpoints.includes(endpoint))\n ) {\n throw new Error(\"Not all requested service endpoints were granted.\");\n }\n\n // Send a logged in event\n const loginEvent: LoginEvent = new CustomEvent(\"login\", {\n detail: {\n loginId,\n token,\n },\n });\n this.eventTarget.dispatchEvent(loginEvent);\n }\n\n async logout(\n authorizationEndpoint: string,\n logoutId: string,\n token: string,\n ): Promise<void> {\n try {\n await this.logout_(authorizationEndpoint, logoutId, token);\n } catch (e) {\n const error = e instanceof Error ? e : new Error(\"Unknown error\");\n const detail: LogoutEvent[\"detail\"] = { logoutId, error };\n this.eventTarget.dispatchEvent(new CustomEvent(\"logout\", { detail }));\n }\n }\n protected async logout_(\n authorizationEndpoint: string,\n logoutId: string,\n token: string,\n ): Promise<void> {\n const configuration = await this.getAuthorizationConfiguration(\n authorizationEndpoint,\n );\n await tokenRevocation(configuration, token);\n const detail: LogoutEvent[\"detail\"] = { logoutId };\n this.eventTarget.dispatchEvent(new CustomEvent(\"logout\", { detail }));\n }\n\n protected async getAuthorizationConfiguration(\n authorizationEndpoint: string,\n ): Promise<any> {\n // Parse the authorization endpoint\n if (\n !authorizationEndpoint.startsWith(\n AUTHORIZATION_ENDPOINT_METHOD_PREFIX_OAUTH2,\n )\n ) {\n throw new Error(\n `Unrecognized authorization endpoint method: ${authorizationEndpoint}`,\n );\n }\n const issuer = authorizationEndpoint.slice(\n AUTHORIZATION_ENDPOINT_METHOD_PREFIX_OAUTH2.length,\n );\n\n // Look up the oauth configuration\n let issuerUrl: URL;\n try {\n issuerUrl = new URL(issuer);\n } catch (e) {\n throw new Error(\"Invalid issuer URL.\");\n }\n\n return await discovery(issuerUrl, \"graffiti-client\");\n }\n}\n\nexport const LoginEventDetailSchema = intersection(\n object({\n loginId: string(),\n }),\n union([\n object({ token: string(), error: optional(undefined_()) }),\n object({ error: instanceof_(Error) }),\n ]),\n);\n\nexport const LogoutEventDetailSchema = object({\n logoutId: string(),\n error: optional(instanceof_(Error)),\n});\n\nexport const InitializedEventDetailSchema = optional(\n nullable(\n object({\n error: optional(instanceof_(Error)),\n }),\n ),\n);\n\nexport type LoginEvent = CustomEvent<infer_<typeof LoginEventDetailSchema>>;\nexport type LogoutEvent = CustomEvent<infer_<typeof LogoutEventDetailSchema>>;\nexport type InitializedEvent = CustomEvent<\n infer_<typeof InitializedEventDetailSchema>\n>;\n\nconst OAuth2LoginDataSchema = object({\n loginId: string(),\n redirectUri: url(),\n authorizationEndpoint: url(),\n state: string(),\n serviceEndpoints: array(url()),\n});\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["undefined_", "instanceof_"]
|
|
7
7
|
}
|
|
@@ -72,7 +72,7 @@ class Sessions {
|
|
|
72
72
|
const url2 = new URL(window.location.toString());
|
|
73
73
|
url2.searchParams.delete("actor");
|
|
74
74
|
window.history.replaceState({}, "", url2.toString());
|
|
75
|
-
loginPromise = this.login(actor);
|
|
75
|
+
loginPromise = this.login(actor, "replace");
|
|
76
76
|
} catch (error) {
|
|
77
77
|
console.error("Error decoding actor:", error);
|
|
78
78
|
}
|
|
@@ -96,11 +96,12 @@ class Sessions {
|
|
|
96
96
|
}
|
|
97
97
|
services;
|
|
98
98
|
sessionEvents = new EventTarget();
|
|
99
|
+
initialAuthorizationNavigation = "assign";
|
|
99
100
|
inProgressLogin = void 0;
|
|
100
101
|
inProgressLogout = void 0;
|
|
101
|
-
async login(actor) {
|
|
102
|
+
async login(actor, initialAuthorizationNavigation = this.initialAuthorizationNavigation) {
|
|
102
103
|
try {
|
|
103
|
-
await this.login_(actor);
|
|
104
|
+
await this.login_(actor, initialAuthorizationNavigation);
|
|
104
105
|
} catch (e) {
|
|
105
106
|
const loginEvent = new CustomEvent("login", {
|
|
106
107
|
detail: {
|
|
@@ -111,7 +112,7 @@ class Sessions {
|
|
|
111
112
|
this.sessionEvents.dispatchEvent(loginEvent);
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
|
-
async login_(actor) {
|
|
115
|
+
async login_(actor, initialAuthorizationNavigation) {
|
|
115
116
|
const existingSession = this.loggedInSessions.find(
|
|
116
117
|
(session2) => session2.actor === actor
|
|
117
118
|
);
|
|
@@ -189,7 +190,8 @@ class Sessions {
|
|
|
189
190
|
await this.services.authorization.login(
|
|
190
191
|
firstAuthorizationEndpoint,
|
|
191
192
|
actor,
|
|
192
|
-
firstServices.map((s) => s.endpoint)
|
|
193
|
+
firstServices.map((s) => s.endpoint),
|
|
194
|
+
initialAuthorizationNavigation
|
|
193
195
|
);
|
|
194
196
|
}
|
|
195
197
|
async onLogin(event) {
|
|
@@ -273,7 +275,8 @@ class Sessions {
|
|
|
273
275
|
await this.services.authorization.login(
|
|
274
276
|
authorizationEndpoint,
|
|
275
277
|
actor,
|
|
276
|
-
services.map((s) => s.endpoint)
|
|
278
|
+
services.map((s) => s.endpoint),
|
|
279
|
+
"replace"
|
|
277
280
|
);
|
|
278
281
|
}
|
|
279
282
|
}
|
|
@@ -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 getBrowserStorageItem,\n removeBrowserStorageItem,\n setBrowserStorageItem,\n} from \"./browser-storage\";\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 = getBrowserStorageItem(LOCAL_STORAGE_LOGGED_IN_SESSIONS_KEY);\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 removeBrowserStorageItem(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 removeBrowserStorageItem(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 setBrowserStorageItem(\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": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,kBAAyC;AACzC,2BAKO;AACP,6BAA+B;AAG/B,6BAIO;AACP,kBASO;AACP,uBAA0C;AAEnC,MAAM,kCAAkC;AACxC,MAAM,2CAA2C;AACjD,MAAM,yCAAyC;AAC/C,MAAM,yCAAyC;AAC/C,MAAM,8CACX;AAEK,MAAM,SAAS;AAAA,
|
|
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 getBrowserStorageItem,\n removeBrowserStorageItem,\n setBrowserStorageItem,\n} from \"./browser-storage\";\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 initialAuthorizationNavigation: \"assign\" | \"replace\" = \"assign\";\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 // The original app entry was kept when opening account creation or\n // recovery, so OAuth reuses this callback entry.\n loginPromise = this.login(actor, \"replace\");\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(\n actor: string,\n initialAuthorizationNavigation: \"assign\" | \"replace\" =\n this.initialAuthorizationNavigation,\n ) {\n try {\n await this.login_(actor, initialAuthorizationNavigation);\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_(\n actor: string,\n initialAuthorizationNavigation: \"assign\" | \"replace\",\n ) {\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 initialAuthorizationNavigation,\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 \"replace\",\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 = getBrowserStorageItem(LOCAL_STORAGE_LOGGED_IN_SESSIONS_KEY);\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 removeBrowserStorageItem(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 removeBrowserStorageItem(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 setBrowserStorageItem(\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
|
}
|
|
@@ -127,6 +127,9 @@ class GraffitiDecentralized {
|
|
|
127
127
|
defaultInboxEndpoints;
|
|
128
128
|
identityCreatorEndpoint;
|
|
129
129
|
constructor(options) {
|
|
130
|
+
if (options?.replaceInitialLoginHistoryEntry) {
|
|
131
|
+
this.sessions.initialAuthorizationNavigation = "replace";
|
|
132
|
+
}
|
|
130
133
|
this.defaultInboxEndpoints = options?.defaultInboxEndpoints ?? [
|
|
131
134
|
"https://graffiti.actor/i/shared"
|
|
132
135
|
];
|