@happyview/oauth-client 1.1.1 → 1.2.0-dev.2
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/index.cjs +90 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +64 -24
- package/dist/index.d.ts +64 -24
- package/dist/index.js +87 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -15,6 +15,7 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
15
|
}
|
|
16
16
|
return to;
|
|
17
17
|
};
|
|
18
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
18
19
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
20
|
|
|
20
21
|
// src/index.ts
|
|
@@ -28,11 +29,14 @@ __export(index_exports, {
|
|
|
28
29
|
InvalidStateError: () => InvalidStateError,
|
|
29
30
|
LAST_ACTIVE_KEY: () => LAST_ACTIVE_KEY,
|
|
30
31
|
MemoryStorage: () => MemoryStorage,
|
|
32
|
+
OAuthCallbackError: () => OAuthCallbackError,
|
|
31
33
|
ResolutionError: () => ResolutionError,
|
|
32
34
|
TokenExchangeError: () => TokenExchangeError,
|
|
33
35
|
importJwk: () => importJwk
|
|
34
36
|
});
|
|
35
37
|
module.exports = __toCommonJS(index_exports);
|
|
38
|
+
__reExport(index_exports, require("@atproto/jwk"), module.exports);
|
|
39
|
+
__reExport(index_exports, require("@atproto/jwk-webcrypto"), module.exports);
|
|
36
40
|
|
|
37
41
|
// src/errors.ts
|
|
38
42
|
var HappyViewError = class extends Error {
|
|
@@ -81,6 +85,26 @@ var ResolutionError = class extends HappyViewError {
|
|
|
81
85
|
this.name = "ResolutionError";
|
|
82
86
|
}
|
|
83
87
|
};
|
|
88
|
+
var OAuthCallbackError = class _OAuthCallbackError extends HappyViewError {
|
|
89
|
+
params;
|
|
90
|
+
state;
|
|
91
|
+
static from(err, params, state) {
|
|
92
|
+
if (err instanceof _OAuthCallbackError) return err;
|
|
93
|
+
const message = err instanceof Error ? err.message : void 0;
|
|
94
|
+
return new _OAuthCallbackError(params, message, state, err);
|
|
95
|
+
}
|
|
96
|
+
constructor(params, message, state, cause) {
|
|
97
|
+
super(
|
|
98
|
+
message ?? params.get("error_description") ?? "OAuth callback error"
|
|
99
|
+
);
|
|
100
|
+
this.name = "OAuthCallbackError";
|
|
101
|
+
this.params = params;
|
|
102
|
+
this.state = state;
|
|
103
|
+
if (cause !== void 0) {
|
|
104
|
+
this.cause = cause;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
84
108
|
|
|
85
109
|
// src/import-jwk.ts
|
|
86
110
|
var import_jwk_webcrypto = require("@atproto/jwk-webcrypto");
|
|
@@ -123,16 +147,40 @@ var HappyViewSession = class {
|
|
|
123
147
|
accessToken;
|
|
124
148
|
clientKey;
|
|
125
149
|
instanceUrl;
|
|
150
|
+
_scopes;
|
|
151
|
+
_pdsUrl;
|
|
152
|
+
_issuer;
|
|
126
153
|
_fetch;
|
|
154
|
+
_onSignOut;
|
|
127
155
|
dpopNonce;
|
|
128
156
|
constructor(options) {
|
|
129
157
|
this.did = options.did;
|
|
130
|
-
this.scopes = options.scopes;
|
|
158
|
+
this.scopes = options.scopes ? options.scopes.split(" ") : [];
|
|
131
159
|
this.dpopKey = options.dpopKey;
|
|
132
160
|
this.accessToken = options.accessToken;
|
|
133
161
|
this.clientKey = options.clientKey;
|
|
134
162
|
this.instanceUrl = options.instanceUrl.replace(/\/+$/, "");
|
|
163
|
+
this._scopes = options.scopes;
|
|
164
|
+
this._pdsUrl = options.pdsUrl;
|
|
165
|
+
this._issuer = options.issuer;
|
|
135
166
|
this._fetch = options.fetch ?? ((input, init) => fetch(input, init));
|
|
167
|
+
this._onSignOut = options.onSignOut;
|
|
168
|
+
}
|
|
169
|
+
get sub() {
|
|
170
|
+
return this.did;
|
|
171
|
+
}
|
|
172
|
+
getTokenInfo() {
|
|
173
|
+
return {
|
|
174
|
+
scope: this._scopes,
|
|
175
|
+
iss: this._issuer,
|
|
176
|
+
aud: this._pdsUrl,
|
|
177
|
+
sub: this.did
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
async signOut() {
|
|
181
|
+
if (this._onSignOut) {
|
|
182
|
+
await this._onSignOut();
|
|
183
|
+
}
|
|
136
184
|
}
|
|
137
185
|
async fetchHandler(url, init) {
|
|
138
186
|
const fullUrl = /^https?:\/\//i.test(url) ? url : `${this.instanceUrl}${url}`;
|
|
@@ -211,17 +259,37 @@ var MemoryStorage = class {
|
|
|
211
259
|
var STORAGE_PREFIX = "happyview:session:";
|
|
212
260
|
var LAST_ACTIVE_KEY = "happyview:last-active-did";
|
|
213
261
|
var HappyViewOAuthClient = class {
|
|
262
|
+
static async fetchMetadata({
|
|
263
|
+
clientId,
|
|
264
|
+
fetch: fetchFn = globalThis.fetch,
|
|
265
|
+
signal
|
|
266
|
+
}) {
|
|
267
|
+
signal?.throwIfAborted();
|
|
268
|
+
const resp = await fetchFn(clientId, { redirect: "error", signal });
|
|
269
|
+
if (resp.status !== 200) {
|
|
270
|
+
resp.body?.cancel?.();
|
|
271
|
+
throw new Error(`Failed to fetch client metadata: ${resp.status}`);
|
|
272
|
+
}
|
|
273
|
+
const mime = resp.headers.get("content-type")?.split(";")[0].trim();
|
|
274
|
+
if (mime !== "application/json") {
|
|
275
|
+
resp.body?.cancel?.();
|
|
276
|
+
throw new Error(`Invalid client metadata content type: ${mime}`);
|
|
277
|
+
}
|
|
278
|
+
return resp.json();
|
|
279
|
+
}
|
|
214
280
|
instanceUrl;
|
|
215
281
|
clientKey;
|
|
216
282
|
storage;
|
|
217
283
|
clientSecret;
|
|
218
284
|
_fetch;
|
|
285
|
+
sessionHooks;
|
|
219
286
|
constructor(options) {
|
|
220
287
|
this.instanceUrl = options.instanceUrl.replace(/\/+$/, "");
|
|
221
288
|
this.clientKey = options.clientKey;
|
|
222
289
|
this.clientSecret = options.clientSecret;
|
|
223
290
|
this.storage = options.storage ?? new MemoryStorage();
|
|
224
291
|
this._fetch = options.fetch ?? ((input, init) => fetch(input, init));
|
|
292
|
+
this.sessionHooks = options.sessionHooks ?? {};
|
|
225
293
|
}
|
|
226
294
|
get isConfidential() {
|
|
227
295
|
return this.clientSecret !== void 0;
|
|
@@ -304,22 +372,29 @@ var HappyViewOAuthClient = class {
|
|
|
304
372
|
accessToken: params.accessToken,
|
|
305
373
|
clientKey: this.clientKey,
|
|
306
374
|
instanceUrl: this.instanceUrl,
|
|
307
|
-
scopes:
|
|
375
|
+
scopes: params.scopes,
|
|
376
|
+
pdsUrl: params.pdsUrl,
|
|
377
|
+
issuer: params.issuer
|
|
308
378
|
};
|
|
309
379
|
await this.storage.set(
|
|
310
380
|
`${STORAGE_PREFIX}${data.did}`,
|
|
311
381
|
JSON.stringify(storedSession)
|
|
312
382
|
);
|
|
313
383
|
await this.storage.set(LAST_ACTIVE_KEY, data.did);
|
|
314
|
-
|
|
384
|
+
const session = new HappyViewSession({
|
|
315
385
|
did: data.did,
|
|
316
386
|
dpopKey,
|
|
317
387
|
accessToken: params.accessToken,
|
|
318
388
|
clientKey: this.clientKey,
|
|
319
389
|
instanceUrl: this.instanceUrl,
|
|
320
|
-
scopes:
|
|
321
|
-
|
|
390
|
+
scopes: params.scopes,
|
|
391
|
+
pdsUrl: params.pdsUrl,
|
|
392
|
+
issuer: params.issuer,
|
|
393
|
+
fetch: this._fetch,
|
|
394
|
+
onSignOut: () => this.deleteSession(data.did)
|
|
322
395
|
});
|
|
396
|
+
this.sessionHooks.onSessionUpdate?.(data.did);
|
|
397
|
+
return session;
|
|
323
398
|
}
|
|
324
399
|
async deleteSession(did) {
|
|
325
400
|
const session = await this.restoreSession(did);
|
|
@@ -342,6 +417,7 @@ var HappyViewOAuthClient = class {
|
|
|
342
417
|
if (lastActive === did) {
|
|
343
418
|
await this.storage.delete(LAST_ACTIVE_KEY);
|
|
344
419
|
}
|
|
420
|
+
this.sessionHooks.onSessionDelete?.(did);
|
|
345
421
|
}
|
|
346
422
|
async restoreSession(did) {
|
|
347
423
|
const stored = await this.storage.get(`${STORAGE_PREFIX}${did}`);
|
|
@@ -354,8 +430,11 @@ var HappyViewOAuthClient = class {
|
|
|
354
430
|
accessToken: data.accessToken,
|
|
355
431
|
clientKey: data.clientKey,
|
|
356
432
|
instanceUrl: data.instanceUrl,
|
|
357
|
-
scopes: data.scopes
|
|
358
|
-
|
|
433
|
+
scopes: data.scopes,
|
|
434
|
+
pdsUrl: data.pdsUrl,
|
|
435
|
+
issuer: data.issuer,
|
|
436
|
+
fetch: this._fetch,
|
|
437
|
+
onSignOut: () => this.deleteSession(data.did)
|
|
359
438
|
});
|
|
360
439
|
}
|
|
361
440
|
async getSession(did) {
|
|
@@ -413,8 +492,11 @@ async function computePkceChallenge(verifier) {
|
|
|
413
492
|
InvalidStateError,
|
|
414
493
|
LAST_ACTIVE_KEY,
|
|
415
494
|
MemoryStorage,
|
|
495
|
+
OAuthCallbackError,
|
|
416
496
|
ResolutionError,
|
|
417
497
|
TokenExchangeError,
|
|
418
|
-
importJwk
|
|
498
|
+
importJwk,
|
|
499
|
+
...require("@atproto/jwk"),
|
|
500
|
+
...require("@atproto/jwk-webcrypto")
|
|
419
501
|
});
|
|
420
502
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/import-jwk.ts","../src/session.ts","../src/storage.ts","../src/client.ts"],"sourcesContent":["export { HappyViewOAuthClient, LAST_ACTIVE_KEY } from \"./client\";\nexport { importJwk } from \"./import-jwk\";\nexport {\n ApiError,\n AuthenticationError,\n HappyViewError,\n InvalidStateError,\n ResolutionError,\n TokenExchangeError,\n} from \"./errors\";\nexport { HappyViewSession } from \"./session\";\nexport type { HappyViewSessionOptions } from \"./session\";\nexport { MemoryStorage } from \"./storage\";\nexport type {\n DpopProvision,\n GetSessionResponse,\n HappyViewOAuthClientOptions,\n ProvisionKeyResponse,\n RegisterSessionParams,\n RegisterSessionResponse,\n StorageAdapter,\n StoredSession,\n} from \"./types\";\n","export class HappyViewError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"HappyViewError\";\n }\n}\n\nexport class ApiError extends HappyViewError {\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body?: unknown) {\n super(message);\n this.name = \"ApiError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport class AuthenticationError extends HappyViewError {\n readonly status: number;\n\n constructor(message: string, status: number = 401) {\n super(message);\n this.name = \"AuthenticationError\";\n this.status = status;\n }\n}\n\nexport class InvalidStateError extends HappyViewError {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidStateError\";\n }\n}\n\nexport class TokenExchangeError extends HappyViewError {\n readonly status: number;\n readonly body: string;\n\n constructor(message: string, status: number, body: string) {\n super(message);\n this.name = \"TokenExchangeError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport class ResolutionError extends HappyViewError {\n constructor(message: string) {\n super(message);\n this.name = \"ResolutionError\";\n }\n}\n","import { WebcryptoKey } from \"@atproto/jwk-webcrypto\";\n\n/**\n * Import a raw ES256 JWK (as returned by HappyView's /oauth/dpop-keys)\n * into a WebcryptoKey that can sign JWTs.\n */\nexport async function importJwk(jwk: JsonWebKey): Promise<WebcryptoKey> {\n const privateKey = await crypto.subtle.importKey(\n \"jwk\",\n jwk,\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n true,\n [\"sign\"],\n );\n\n // Derive public key by stripping the private component\n const { d: _, ...publicJwkFields } = jwk;\n const publicKey = await crypto.subtle.importKey(\n \"jwk\",\n publicJwkFields,\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n true,\n [\"verify\"],\n );\n\n return WebcryptoKey.fromKeypair({ privateKey, publicKey });\n}\n","import type { Key } from \"@atproto/jwk\";\n\nexport interface HappyViewSessionOptions {\n did: string;\n dpopKey: Key;\n accessToken: string;\n clientKey: string;\n instanceUrl: string;\n scopes: string[];\n fetch?: typeof globalThis.fetch;\n}\n\nfunction randomHex(byteLength: number): string {\n const bytes = crypto.getRandomValues(new Uint8Array(byteLength));\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nfunction base64url(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nexport class HappyViewSession {\n readonly did: string;\n readonly scopes: string[];\n\n private readonly dpopKey: Key;\n private readonly accessToken: string;\n private readonly clientKey: string;\n private readonly instanceUrl: string;\n private readonly _fetch: typeof globalThis.fetch;\n private dpopNonce: string | undefined;\n\n constructor(options: HappyViewSessionOptions) {\n this.did = options.did;\n this.scopes = options.scopes;\n this.dpopKey = options.dpopKey;\n this.accessToken = options.accessToken;\n this.clientKey = options.clientKey;\n this.instanceUrl = options.instanceUrl.replace(/\\/+$/, \"\");\n this._fetch = options.fetch ?? ((input: RequestInfo | URL, init?: RequestInit) => fetch(input, init)) as typeof globalThis.fetch;\n }\n\n async fetchHandler(url: string, init: RequestInit): Promise<Response> {\n const fullUrl = /^https?:\\/\\//i.test(url)\n ? url\n : `${this.instanceUrl}${url}`;\n const method = (init.method ?? \"GET\").toUpperCase();\n const htu = fullUrl.split(\"?\")[0];\n\n const sendWithProof = async (): Promise<Response> => {\n const tokenHash = await crypto.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(this.accessToken),\n );\n const ath = base64url(tokenHash);\n\n const proof = await this.dpopKey.createJwt(\n {\n alg: \"ES256\",\n typ: \"dpop+jwt\",\n jwk: this.dpopKey.publicJwk!,\n },\n {\n htm: method,\n htu,\n iat: Math.floor(Date.now() / 1000),\n jti: randomHex(16),\n ath,\n nonce: this.dpopNonce,\n },\n );\n\n const headers = new Headers(init.headers);\n headers.set(\"authorization\", `DPoP ${this.accessToken}`);\n headers.set(\"dpop\", proof);\n headers.set(\"x-client-key\", this.clientKey);\n\n return this._fetch(fullUrl, { ...init, headers });\n };\n\n let response: Response;\n try {\n response = await sendWithProof();\n } catch (e) {\n const info = `_fetch type: ${typeof this._fetch}, toString: ${String(this._fetch).slice(0, 120)}, url: ${fullUrl}`;\n throw new Error(`fetchHandler failed: ${(e as Error).message}\\n\\nDEBUG: ${info}`);\n }\n\n // Retry once if the server requires a DPoP nonce we didn't have\n const nonce = response.headers.get(\"dpop-nonce\");\n if (nonce && nonce !== this.dpopNonce && (response.status === 401 || response.status === 400)) {\n this.dpopNonce = nonce;\n try {\n response = await sendWithProof();\n } catch (e) {\n throw new Error(`fetchHandler retry failed: ${(e as Error).message}`);\n }\n const retryNonce = response.headers.get(\"dpop-nonce\");\n if (retryNonce) {\n this.dpopNonce = retryNonce;\n }\n } else if (nonce) {\n this.dpopNonce = nonce;\n }\n\n return response;\n }\n}\n","import type { StorageAdapter } from \"./types\";\n\nexport class MemoryStorage implements StorageAdapter {\n private data = new Map<string, string>();\n\n async get(key: string): Promise<string | null> {\n return this.data.get(key) ?? null;\n }\n\n async set(key: string, value: string): Promise<void> {\n this.data.set(key, value);\n }\n\n async delete(key: string): Promise<void> {\n this.data.delete(key);\n }\n}\n","import type { Key } from \"@atproto/jwk\";\nimport { ApiError } from \"./errors\";\nimport { importJwk } from \"./import-jwk\";\nimport { HappyViewSession } from \"./session\";\nimport { MemoryStorage } from \"./storage\";\nimport type {\n GetSessionResponse,\n HappyViewOAuthClientOptions,\n ProvisionKeyResponse,\n RegisterSessionParams,\n RegisterSessionResponse,\n StorageAdapter,\n StoredSession,\n} from \"./types\";\n\nconst STORAGE_PREFIX = \"happyview:session:\";\nexport const LAST_ACTIVE_KEY = \"happyview:last-active-did\";\n\nexport class HappyViewOAuthClient {\n protected readonly instanceUrl: string;\n protected readonly clientKey: string;\n protected readonly storage: StorageAdapter;\n private readonly clientSecret: string | undefined;\n protected readonly _fetch: typeof globalThis.fetch;\n\n constructor(\n options: HappyViewOAuthClientOptions & {\n fetch?: typeof globalThis.fetch;\n },\n ) {\n this.instanceUrl = options.instanceUrl.replace(/\\/+$/, \"\");\n this.clientKey = options.clientKey;\n this.clientSecret = options.clientSecret;\n this.storage = options.storage ?? new MemoryStorage();\n this._fetch = options.fetch ?? ((input: RequestInfo | URL, init?: RequestInit) => fetch(input, init)) as typeof globalThis.fetch;\n }\n\n get isConfidential(): boolean {\n return this.clientSecret !== undefined;\n }\n\n async provisionDpopKey(): Promise<{\n provisionId: string;\n dpopKey: Key;\n rawJwk: JsonWebKey;\n pkceVerifier?: string;\n }> {\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const body: Record<string, unknown> = {};\n let pkceVerifier: string | undefined;\n\n if (!this.isConfidential) {\n pkceVerifier = generatePkceVerifier();\n const challenge = await computePkceChallenge(pkceVerifier);\n body.pkce_challenge = challenge;\n }\n\n const resp = await this._fetch(`${this.instanceUrl}/oauth/dpop-keys`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to provision DPoP key: ${resp.status} ${(body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n const data: ProvisionKeyResponse = await resp.json();\n const dpopKey = await importJwk(data.dpop_key);\n return {\n provisionId: data.provision_id,\n dpopKey,\n rawJwk: data.dpop_key,\n pkceVerifier,\n };\n }\n\n async registerSession(\n params: RegisterSessionParams,\n ): Promise<HappyViewSession> {\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const body: Record<string, unknown> = {\n provision_id: params.provisionId,\n did: params.did,\n access_token: params.accessToken,\n refresh_token: params.refreshToken,\n scopes: params.scopes,\n pds_url: params.pdsUrl,\n issuer: params.issuer,\n };\n\n if (!this.isConfidential && params.pkceVerifier) {\n body.pkce_verifier = params.pkceVerifier;\n }\n\n const resp = await this._fetch(`${this.instanceUrl}/oauth/sessions`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to register session: ${resp.status} ${(body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n const data: RegisterSessionResponse = await resp.json();\n const dpopKey = await importJwk(params.dpopKey);\n\n const storedSession: StoredSession = {\n did: data.did,\n dpopKey: params.dpopKey,\n accessToken: params.accessToken,\n clientKey: this.clientKey,\n instanceUrl: this.instanceUrl,\n scopes: data.scopes,\n };\n await this.storage.set(\n `${STORAGE_PREFIX}${data.did}`,\n JSON.stringify(storedSession),\n );\n await this.storage.set(LAST_ACTIVE_KEY, data.did);\n\n return new HappyViewSession({\n did: data.did,\n dpopKey,\n accessToken: params.accessToken,\n clientKey: this.clientKey,\n instanceUrl: this.instanceUrl,\n scopes: data.scopes,\n fetch: this._fetch,\n });\n }\n\n async deleteSession(did: string): Promise<void> {\n const session = await this.restoreSession(did);\n if (session) {\n const resp = await session.fetchHandler(\n `${this.instanceUrl}/oauth/sessions/${did}`,\n { method: \"DELETE\" },\n );\n\n if (!resp.ok && resp.status !== 404) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to delete session: ${resp.status} ${(body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n }\n\n await this.storage.delete(`${STORAGE_PREFIX}${did}`);\n\n const lastActive = await this.storage.get(LAST_ACTIVE_KEY);\n if (lastActive === did) {\n await this.storage.delete(LAST_ACTIVE_KEY);\n }\n }\n\n async restoreSession(did: string): Promise<HappyViewSession | null> {\n const stored = await this.storage.get(`${STORAGE_PREFIX}${did}`);\n if (!stored) return null;\n\n const data: StoredSession = JSON.parse(stored);\n const dpopKey = await importJwk(data.dpopKey);\n return new HappyViewSession({\n did: data.did,\n dpopKey,\n accessToken: data.accessToken,\n clientKey: data.clientKey,\n instanceUrl: data.instanceUrl,\n scopes: data.scopes ?? [],\n fetch: this._fetch,\n });\n }\n\n async getSession(did: string): Promise<GetSessionResponse> {\n const session = await this.restoreSession(did);\n if (!session) {\n throw new ApiError(\"No stored session for this DID\", 404, {});\n }\n\n const resp = await session.fetchHandler(\n `${this.instanceUrl}/oauth/sessions/${did}`,\n { method: \"GET\" },\n );\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to get session: ${resp.status} ${(body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n return resp.json();\n }\n\n async restore(): Promise<HappyViewSession | null> {\n const did = await this.storage.get(LAST_ACTIVE_KEY);\n if (!did) return null;\n return this.restoreSession(did);\n }\n}\n\n// PKCE helpers — inline since they're just a few lines of Web Crypto\nfunction generatePkceVerifier(): string {\n const bytes = crypto.getRandomValues(new Uint8Array(32));\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nasync function computePkceChallenge(verifier: string): Promise<string> {\n const hash = await crypto.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(verifier),\n );\n const bytes = new Uint8Array(hash);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n"],"mappings":"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","names":["body"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/import-jwk.ts","../src/session.ts","../src/storage.ts","../src/client.ts"],"sourcesContent":["export * from \"@atproto/jwk\";\nexport * from \"@atproto/jwk-webcrypto\";\n\nexport { HappyViewOAuthClient, LAST_ACTIVE_KEY } from \"./client\";\nexport type { FetchMetadataOptions } from \"./client\";\nexport { importJwk } from \"./import-jwk\";\nexport {\n ApiError,\n AuthenticationError,\n HappyViewError,\n InvalidStateError,\n OAuthCallbackError,\n ResolutionError,\n TokenExchangeError,\n} from \"./errors\";\nexport { HappyViewSession } from \"./session\";\nexport type { HappyViewSessionOptions } from \"./session\";\nexport { MemoryStorage } from \"./storage\";\nexport type {\n DpopProvision,\n GetSessionResponse,\n HappyViewOAuthClientOptions,\n ProvisionKeyResponse,\n RegisterSessionParams,\n RegisterSessionResponse,\n SessionEventHooks,\n StorageAdapter,\n StoredSession,\n TokenInfo,\n} from \"./types\";\n","export class HappyViewError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"HappyViewError\";\n }\n}\n\nexport class ApiError extends HappyViewError {\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body?: unknown) {\n super(message);\n this.name = \"ApiError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport class AuthenticationError extends HappyViewError {\n readonly status: number;\n\n constructor(message: string, status: number = 401) {\n super(message);\n this.name = \"AuthenticationError\";\n this.status = status;\n }\n}\n\nexport class InvalidStateError extends HappyViewError {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidStateError\";\n }\n}\n\nexport class TokenExchangeError extends HappyViewError {\n readonly status: number;\n readonly body: string;\n\n constructor(message: string, status: number, body: string) {\n super(message);\n this.name = \"TokenExchangeError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport class ResolutionError extends HappyViewError {\n constructor(message: string) {\n super(message);\n this.name = \"ResolutionError\";\n }\n}\n\nexport class OAuthCallbackError extends HappyViewError {\n readonly params: URLSearchParams;\n readonly state: string | undefined;\n\n static from(\n err: unknown,\n params: URLSearchParams,\n state?: string,\n ): OAuthCallbackError {\n if (err instanceof OAuthCallbackError) return err;\n const message = err instanceof Error ? err.message : undefined;\n return new OAuthCallbackError(params, message, state, err);\n }\n\n constructor(\n params: URLSearchParams,\n message?: string,\n state?: string,\n cause?: unknown,\n ) {\n super(\n message ??\n params.get(\"error_description\") ??\n \"OAuth callback error\",\n );\n this.name = \"OAuthCallbackError\";\n this.params = params;\n this.state = state;\n if (cause !== undefined) {\n this.cause = cause;\n }\n }\n}\n","import { WebcryptoKey } from \"@atproto/jwk-webcrypto\";\n\n/**\n * Import a raw ES256 JWK (as returned by HappyView's /oauth/dpop-keys)\n * into a WebcryptoKey that can sign JWTs.\n */\nexport async function importJwk(jwk: JsonWebKey): Promise<WebcryptoKey> {\n const privateKey = await crypto.subtle.importKey(\n \"jwk\",\n jwk,\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n true,\n [\"sign\"],\n );\n\n // Derive public key by stripping the private component\n const { d: _, ...publicJwkFields } = jwk;\n const publicKey = await crypto.subtle.importKey(\n \"jwk\",\n publicJwkFields,\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n true,\n [\"verify\"],\n );\n\n return WebcryptoKey.fromKeypair({ privateKey, publicKey });\n}\n","import type { Key } from \"@atproto/jwk\";\nimport type { TokenInfo } from \"./types\";\n\nexport interface HappyViewSessionOptions {\n did: string;\n dpopKey: Key;\n accessToken: string;\n clientKey: string;\n instanceUrl: string;\n scopes?: string;\n pdsUrl?: string;\n issuer?: string;\n fetch?: typeof globalThis.fetch;\n onSignOut?: () => Promise<void>;\n}\n\nfunction randomHex(byteLength: number): string {\n const bytes = crypto.getRandomValues(new Uint8Array(byteLength));\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nfunction base64url(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nexport class HappyViewSession {\n readonly did: string;\n readonly scopes: string[];\n\n private readonly dpopKey: Key;\n private readonly accessToken: string;\n private readonly clientKey: string;\n private readonly instanceUrl: string;\n private readonly _scopes: string | undefined;\n private readonly _pdsUrl: string | undefined;\n private readonly _issuer: string | undefined;\n private readonly _fetch: typeof globalThis.fetch;\n private readonly _onSignOut: (() => Promise<void>) | undefined;\n private dpopNonce: string | undefined;\n\n constructor(options: HappyViewSessionOptions) {\n this.did = options.did;\n this.scopes = options.scopes ? options.scopes.split(\" \") : [];\n this.dpopKey = options.dpopKey;\n this.accessToken = options.accessToken;\n this.clientKey = options.clientKey;\n this.instanceUrl = options.instanceUrl.replace(/\\/+$/, \"\");\n this._scopes = options.scopes;\n this._pdsUrl = options.pdsUrl;\n this._issuer = options.issuer;\n this._fetch = options.fetch ?? ((input: RequestInfo | URL, init?: RequestInit) => fetch(input, init)) as typeof globalThis.fetch;\n this._onSignOut = options.onSignOut;\n }\n\n get sub(): string {\n return this.did;\n }\n\n getTokenInfo(): TokenInfo {\n return {\n scope: this._scopes,\n iss: this._issuer,\n aud: this._pdsUrl,\n sub: this.did,\n };\n }\n\n async signOut(): Promise<void> {\n if (this._onSignOut) {\n await this._onSignOut();\n }\n }\n\n async fetchHandler(url: string, init: RequestInit): Promise<Response> {\n const fullUrl = /^https?:\\/\\//i.test(url)\n ? url\n : `${this.instanceUrl}${url}`;\n const method = (init.method ?? \"GET\").toUpperCase();\n const htu = fullUrl.split(\"?\")[0];\n\n const sendWithProof = async (): Promise<Response> => {\n const tokenHash = await crypto.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(this.accessToken),\n );\n const ath = base64url(tokenHash);\n\n const proof = await this.dpopKey.createJwt(\n {\n alg: \"ES256\",\n typ: \"dpop+jwt\",\n jwk: this.dpopKey.publicJwk!,\n },\n {\n htm: method,\n htu,\n iat: Math.floor(Date.now() / 1000),\n jti: randomHex(16),\n ath,\n nonce: this.dpopNonce,\n },\n );\n\n const headers = new Headers(init.headers);\n headers.set(\"authorization\", `DPoP ${this.accessToken}`);\n headers.set(\"dpop\", proof);\n headers.set(\"x-client-key\", this.clientKey);\n\n return this._fetch(fullUrl, { ...init, headers });\n };\n\n let response: Response;\n try {\n response = await sendWithProof();\n } catch (e) {\n const info = `_fetch type: ${typeof this._fetch}, toString: ${String(this._fetch).slice(0, 120)}, url: ${fullUrl}`;\n throw new Error(`fetchHandler failed: ${(e as Error).message}\\n\\nDEBUG: ${info}`);\n }\n\n // Retry once if the server requires a DPoP nonce we didn't have\n const nonce = response.headers.get(\"dpop-nonce\");\n if (nonce && nonce !== this.dpopNonce && (response.status === 401 || response.status === 400)) {\n this.dpopNonce = nonce;\n try {\n response = await sendWithProof();\n } catch (e) {\n throw new Error(`fetchHandler retry failed: ${(e as Error).message}`);\n }\n const retryNonce = response.headers.get(\"dpop-nonce\");\n if (retryNonce) {\n this.dpopNonce = retryNonce;\n }\n } else if (nonce) {\n this.dpopNonce = nonce;\n }\n\n return response;\n }\n}\n","import type { StorageAdapter } from \"./types\";\n\nexport class MemoryStorage implements StorageAdapter {\n private data = new Map<string, string>();\n\n async get(key: string): Promise<string | null> {\n return this.data.get(key) ?? null;\n }\n\n async set(key: string, value: string): Promise<void> {\n this.data.set(key, value);\n }\n\n async delete(key: string): Promise<void> {\n this.data.delete(key);\n }\n}\n","import type { Key } from \"@atproto/jwk\";\nimport { ApiError } from \"./errors\";\nimport { importJwk } from \"./import-jwk\";\nimport { HappyViewSession } from \"./session\";\nimport { MemoryStorage } from \"./storage\";\nimport type {\n GetSessionResponse,\n HappyViewOAuthClientOptions,\n ProvisionKeyResponse,\n RegisterSessionParams,\n RegisterSessionResponse,\n SessionEventHooks,\n StorageAdapter,\n StoredSession,\n} from \"./types\";\n\nconst STORAGE_PREFIX = \"happyview:session:\";\nexport const LAST_ACTIVE_KEY = \"happyview:last-active-did\";\n\nexport interface FetchMetadataOptions {\n clientId: string;\n fetch?: typeof globalThis.fetch;\n signal?: AbortSignal;\n}\n\nexport class HappyViewOAuthClient {\n static async fetchMetadata({\n clientId,\n fetch: fetchFn = globalThis.fetch,\n signal,\n }: FetchMetadataOptions): Promise<Record<string, unknown>> {\n signal?.throwIfAborted();\n const resp = await fetchFn(clientId, { redirect: \"error\", signal });\n if (resp.status !== 200) {\n resp.body?.cancel?.();\n throw new Error(`Failed to fetch client metadata: ${resp.status}`);\n }\n const mime = resp.headers.get(\"content-type\")?.split(\";\")[0].trim();\n if (mime !== \"application/json\") {\n resp.body?.cancel?.();\n throw new Error(`Invalid client metadata content type: ${mime}`);\n }\n return resp.json() as Promise<Record<string, unknown>>;\n }\n\n protected readonly instanceUrl: string;\n protected readonly clientKey: string;\n protected readonly storage: StorageAdapter;\n private readonly clientSecret: string | undefined;\n protected readonly _fetch: typeof globalThis.fetch;\n private readonly sessionHooks: SessionEventHooks;\n\n constructor(\n options: HappyViewOAuthClientOptions & {\n fetch?: typeof globalThis.fetch;\n },\n ) {\n this.instanceUrl = options.instanceUrl.replace(/\\/+$/, \"\");\n this.clientKey = options.clientKey;\n this.clientSecret = options.clientSecret;\n this.storage = options.storage ?? new MemoryStorage();\n this._fetch = options.fetch ?? ((input: RequestInfo | URL, init?: RequestInit) => fetch(input, init)) as typeof globalThis.fetch;\n this.sessionHooks = options.sessionHooks ?? {};\n }\n\n get isConfidential(): boolean {\n return this.clientSecret !== undefined;\n }\n\n async provisionDpopKey(): Promise<{\n provisionId: string;\n dpopKey: Key;\n rawJwk: JsonWebKey;\n pkceVerifier?: string;\n }> {\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const body: Record<string, unknown> = {};\n let pkceVerifier: string | undefined;\n\n if (!this.isConfidential) {\n pkceVerifier = generatePkceVerifier();\n const challenge = await computePkceChallenge(pkceVerifier);\n body.pkce_challenge = challenge;\n }\n\n const resp = await this._fetch(`${this.instanceUrl}/oauth/dpop-keys`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to provision DPoP key: ${resp.status} ${(body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n const data: ProvisionKeyResponse = await resp.json();\n const dpopKey = await importJwk(data.dpop_key);\n return {\n provisionId: data.provision_id,\n dpopKey,\n rawJwk: data.dpop_key,\n pkceVerifier,\n };\n }\n\n async registerSession(\n params: RegisterSessionParams,\n ): Promise<HappyViewSession> {\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const body: Record<string, unknown> = {\n provision_id: params.provisionId,\n did: params.did,\n access_token: params.accessToken,\n refresh_token: params.refreshToken,\n scopes: params.scopes,\n pds_url: params.pdsUrl,\n issuer: params.issuer,\n };\n\n if (!this.isConfidential && params.pkceVerifier) {\n body.pkce_verifier = params.pkceVerifier;\n }\n\n const resp = await this._fetch(`${this.instanceUrl}/oauth/sessions`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to register session: ${resp.status} ${(body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n const data: RegisterSessionResponse = await resp.json();\n const dpopKey = await importJwk(params.dpopKey);\n\n const storedSession: StoredSession = {\n did: data.did,\n dpopKey: params.dpopKey,\n accessToken: params.accessToken,\n clientKey: this.clientKey,\n instanceUrl: this.instanceUrl,\n scopes: params.scopes,\n pdsUrl: params.pdsUrl,\n issuer: params.issuer,\n };\n await this.storage.set(\n `${STORAGE_PREFIX}${data.did}`,\n JSON.stringify(storedSession),\n );\n await this.storage.set(LAST_ACTIVE_KEY, data.did);\n\n const session = new HappyViewSession({\n did: data.did,\n dpopKey,\n accessToken: params.accessToken,\n clientKey: this.clientKey,\n instanceUrl: this.instanceUrl,\n scopes: params.scopes,\n pdsUrl: params.pdsUrl,\n issuer: params.issuer,\n fetch: this._fetch,\n onSignOut: () => this.deleteSession(data.did),\n });\n\n this.sessionHooks.onSessionUpdate?.(data.did);\n\n return session;\n }\n\n async deleteSession(did: string): Promise<void> {\n const session = await this.restoreSession(did);\n if (session) {\n const resp = await session.fetchHandler(\n `${this.instanceUrl}/oauth/sessions/${did}`,\n { method: \"DELETE\" },\n );\n\n if (!resp.ok && resp.status !== 404) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to delete session: ${resp.status} ${(body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n }\n\n await this.storage.delete(`${STORAGE_PREFIX}${did}`);\n\n const lastActive = await this.storage.get(LAST_ACTIVE_KEY);\n if (lastActive === did) {\n await this.storage.delete(LAST_ACTIVE_KEY);\n }\n\n this.sessionHooks.onSessionDelete?.(did);\n }\n\n async restoreSession(did: string): Promise<HappyViewSession | null> {\n const stored = await this.storage.get(`${STORAGE_PREFIX}${did}`);\n if (!stored) return null;\n\n const data: StoredSession = JSON.parse(stored);\n const dpopKey = await importJwk(data.dpopKey);\n return new HappyViewSession({\n did: data.did,\n dpopKey,\n accessToken: data.accessToken,\n clientKey: data.clientKey,\n instanceUrl: data.instanceUrl,\n scopes: data.scopes,\n pdsUrl: data.pdsUrl,\n issuer: data.issuer,\n fetch: this._fetch,\n onSignOut: () => this.deleteSession(data.did),\n });\n }\n\n async getSession(did: string): Promise<GetSessionResponse> {\n const session = await this.restoreSession(did);\n if (!session) {\n throw new ApiError(\"No stored session for this DID\", 404, {});\n }\n\n const resp = await session.fetchHandler(\n `${this.instanceUrl}/oauth/sessions/${did}`,\n { method: \"GET\" },\n );\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to get session: ${resp.status} ${(body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n return resp.json();\n }\n\n async restore(): Promise<HappyViewSession | null> {\n const did = await this.storage.get(LAST_ACTIVE_KEY);\n if (!did) return null;\n return this.restoreSession(did);\n }\n}\n\n// PKCE helpers — inline since they're just a few lines of Web Crypto\nfunction generatePkceVerifier(): string {\n const bytes = crypto.getRandomValues(new Uint8Array(32));\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nasync function computePkceChallenge(verifier: string): Promise<string> {\n const hash = await crypto.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(verifier),\n );\n const bytes = new Uint8Array(hash);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n"],"mappings":"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","names":["body"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,38 +1,23 @@
|
|
|
1
1
|
import { Key } from '@atproto/jwk';
|
|
2
|
+
export * from '@atproto/jwk';
|
|
2
3
|
import { WebcryptoKey } from '@atproto/jwk-webcrypto';
|
|
3
|
-
|
|
4
|
-
interface HappyViewSessionOptions {
|
|
5
|
-
did: string;
|
|
6
|
-
dpopKey: Key;
|
|
7
|
-
accessToken: string;
|
|
8
|
-
clientKey: string;
|
|
9
|
-
instanceUrl: string;
|
|
10
|
-
scopes: string[];
|
|
11
|
-
fetch?: typeof globalThis.fetch;
|
|
12
|
-
}
|
|
13
|
-
declare class HappyViewSession {
|
|
14
|
-
readonly did: string;
|
|
15
|
-
readonly scopes: string[];
|
|
16
|
-
private readonly dpopKey;
|
|
17
|
-
private readonly accessToken;
|
|
18
|
-
private readonly clientKey;
|
|
19
|
-
private readonly instanceUrl;
|
|
20
|
-
private readonly _fetch;
|
|
21
|
-
private dpopNonce;
|
|
22
|
-
constructor(options: HappyViewSessionOptions);
|
|
23
|
-
fetchHandler(url: string, init: RequestInit): Promise<Response>;
|
|
24
|
-
}
|
|
4
|
+
export * from '@atproto/jwk-webcrypto';
|
|
25
5
|
|
|
26
6
|
interface StorageAdapter {
|
|
27
7
|
get(key: string): Promise<string | null>;
|
|
28
8
|
set(key: string, value: string): Promise<void>;
|
|
29
9
|
delete(key: string): Promise<void>;
|
|
30
10
|
}
|
|
11
|
+
interface SessionEventHooks {
|
|
12
|
+
onSessionUpdate?: (did: string) => void;
|
|
13
|
+
onSessionDelete?: (did: string, cause?: unknown) => void;
|
|
14
|
+
}
|
|
31
15
|
interface HappyViewOAuthClientOptions {
|
|
32
16
|
instanceUrl: string;
|
|
33
17
|
clientKey: string;
|
|
34
18
|
clientSecret?: string;
|
|
35
19
|
storage?: StorageAdapter;
|
|
20
|
+
sessionHooks?: SessionEventHooks;
|
|
36
21
|
}
|
|
37
22
|
interface DpopProvision {
|
|
38
23
|
provisionId: string;
|
|
@@ -61,7 +46,17 @@ interface StoredSession {
|
|
|
61
46
|
accessToken: string;
|
|
62
47
|
clientKey: string;
|
|
63
48
|
instanceUrl: string;
|
|
64
|
-
scopes
|
|
49
|
+
scopes?: string;
|
|
50
|
+
pdsUrl?: string;
|
|
51
|
+
issuer?: string;
|
|
52
|
+
}
|
|
53
|
+
interface TokenInfo {
|
|
54
|
+
expiresAt?: Date;
|
|
55
|
+
expired?: boolean;
|
|
56
|
+
scope?: string;
|
|
57
|
+
iss?: string;
|
|
58
|
+
aud?: string;
|
|
59
|
+
sub: string;
|
|
65
60
|
}
|
|
66
61
|
interface ProvisionKeyResponse {
|
|
67
62
|
provision_id: string;
|
|
@@ -77,13 +72,52 @@ interface GetSessionResponse {
|
|
|
77
72
|
scopes: string[];
|
|
78
73
|
}
|
|
79
74
|
|
|
75
|
+
interface HappyViewSessionOptions {
|
|
76
|
+
did: string;
|
|
77
|
+
dpopKey: Key;
|
|
78
|
+
accessToken: string;
|
|
79
|
+
clientKey: string;
|
|
80
|
+
instanceUrl: string;
|
|
81
|
+
scopes?: string;
|
|
82
|
+
pdsUrl?: string;
|
|
83
|
+
issuer?: string;
|
|
84
|
+
fetch?: typeof globalThis.fetch;
|
|
85
|
+
onSignOut?: () => Promise<void>;
|
|
86
|
+
}
|
|
87
|
+
declare class HappyViewSession {
|
|
88
|
+
readonly did: string;
|
|
89
|
+
readonly scopes: string[];
|
|
90
|
+
private readonly dpopKey;
|
|
91
|
+
private readonly accessToken;
|
|
92
|
+
private readonly clientKey;
|
|
93
|
+
private readonly instanceUrl;
|
|
94
|
+
private readonly _scopes;
|
|
95
|
+
private readonly _pdsUrl;
|
|
96
|
+
private readonly _issuer;
|
|
97
|
+
private readonly _fetch;
|
|
98
|
+
private readonly _onSignOut;
|
|
99
|
+
private dpopNonce;
|
|
100
|
+
constructor(options: HappyViewSessionOptions);
|
|
101
|
+
get sub(): string;
|
|
102
|
+
getTokenInfo(): TokenInfo;
|
|
103
|
+
signOut(): Promise<void>;
|
|
104
|
+
fetchHandler(url: string, init: RequestInit): Promise<Response>;
|
|
105
|
+
}
|
|
106
|
+
|
|
80
107
|
declare const LAST_ACTIVE_KEY = "happyview:last-active-did";
|
|
108
|
+
interface FetchMetadataOptions {
|
|
109
|
+
clientId: string;
|
|
110
|
+
fetch?: typeof globalThis.fetch;
|
|
111
|
+
signal?: AbortSignal;
|
|
112
|
+
}
|
|
81
113
|
declare class HappyViewOAuthClient {
|
|
114
|
+
static fetchMetadata({ clientId, fetch: fetchFn, signal, }: FetchMetadataOptions): Promise<Record<string, unknown>>;
|
|
82
115
|
protected readonly instanceUrl: string;
|
|
83
116
|
protected readonly clientKey: string;
|
|
84
117
|
protected readonly storage: StorageAdapter;
|
|
85
118
|
private readonly clientSecret;
|
|
86
119
|
protected readonly _fetch: typeof globalThis.fetch;
|
|
120
|
+
private readonly sessionHooks;
|
|
87
121
|
constructor(options: HappyViewOAuthClientOptions & {
|
|
88
122
|
fetch?: typeof globalThis.fetch;
|
|
89
123
|
});
|
|
@@ -130,6 +164,12 @@ declare class TokenExchangeError extends HappyViewError {
|
|
|
130
164
|
declare class ResolutionError extends HappyViewError {
|
|
131
165
|
constructor(message: string);
|
|
132
166
|
}
|
|
167
|
+
declare class OAuthCallbackError extends HappyViewError {
|
|
168
|
+
readonly params: URLSearchParams;
|
|
169
|
+
readonly state: string | undefined;
|
|
170
|
+
static from(err: unknown, params: URLSearchParams, state?: string): OAuthCallbackError;
|
|
171
|
+
constructor(params: URLSearchParams, message?: string, state?: string, cause?: unknown);
|
|
172
|
+
}
|
|
133
173
|
|
|
134
174
|
declare class MemoryStorage implements StorageAdapter {
|
|
135
175
|
private data;
|
|
@@ -138,4 +178,4 @@ declare class MemoryStorage implements StorageAdapter {
|
|
|
138
178
|
delete(key: string): Promise<void>;
|
|
139
179
|
}
|
|
140
180
|
|
|
141
|
-
export { ApiError, AuthenticationError, type DpopProvision, type GetSessionResponse, HappyViewError, HappyViewOAuthClient, type HappyViewOAuthClientOptions, HappyViewSession, type HappyViewSessionOptions, InvalidStateError, LAST_ACTIVE_KEY, MemoryStorage, type ProvisionKeyResponse, type RegisterSessionParams, type RegisterSessionResponse, ResolutionError, type StorageAdapter, type StoredSession, TokenExchangeError, importJwk };
|
|
181
|
+
export { ApiError, AuthenticationError, type DpopProvision, type FetchMetadataOptions, type GetSessionResponse, HappyViewError, HappyViewOAuthClient, type HappyViewOAuthClientOptions, HappyViewSession, type HappyViewSessionOptions, InvalidStateError, LAST_ACTIVE_KEY, MemoryStorage, OAuthCallbackError, type ProvisionKeyResponse, type RegisterSessionParams, type RegisterSessionResponse, ResolutionError, type SessionEventHooks, type StorageAdapter, type StoredSession, TokenExchangeError, type TokenInfo, importJwk };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,38 +1,23 @@
|
|
|
1
1
|
import { Key } from '@atproto/jwk';
|
|
2
|
+
export * from '@atproto/jwk';
|
|
2
3
|
import { WebcryptoKey } from '@atproto/jwk-webcrypto';
|
|
3
|
-
|
|
4
|
-
interface HappyViewSessionOptions {
|
|
5
|
-
did: string;
|
|
6
|
-
dpopKey: Key;
|
|
7
|
-
accessToken: string;
|
|
8
|
-
clientKey: string;
|
|
9
|
-
instanceUrl: string;
|
|
10
|
-
scopes: string[];
|
|
11
|
-
fetch?: typeof globalThis.fetch;
|
|
12
|
-
}
|
|
13
|
-
declare class HappyViewSession {
|
|
14
|
-
readonly did: string;
|
|
15
|
-
readonly scopes: string[];
|
|
16
|
-
private readonly dpopKey;
|
|
17
|
-
private readonly accessToken;
|
|
18
|
-
private readonly clientKey;
|
|
19
|
-
private readonly instanceUrl;
|
|
20
|
-
private readonly _fetch;
|
|
21
|
-
private dpopNonce;
|
|
22
|
-
constructor(options: HappyViewSessionOptions);
|
|
23
|
-
fetchHandler(url: string, init: RequestInit): Promise<Response>;
|
|
24
|
-
}
|
|
4
|
+
export * from '@atproto/jwk-webcrypto';
|
|
25
5
|
|
|
26
6
|
interface StorageAdapter {
|
|
27
7
|
get(key: string): Promise<string | null>;
|
|
28
8
|
set(key: string, value: string): Promise<void>;
|
|
29
9
|
delete(key: string): Promise<void>;
|
|
30
10
|
}
|
|
11
|
+
interface SessionEventHooks {
|
|
12
|
+
onSessionUpdate?: (did: string) => void;
|
|
13
|
+
onSessionDelete?: (did: string, cause?: unknown) => void;
|
|
14
|
+
}
|
|
31
15
|
interface HappyViewOAuthClientOptions {
|
|
32
16
|
instanceUrl: string;
|
|
33
17
|
clientKey: string;
|
|
34
18
|
clientSecret?: string;
|
|
35
19
|
storage?: StorageAdapter;
|
|
20
|
+
sessionHooks?: SessionEventHooks;
|
|
36
21
|
}
|
|
37
22
|
interface DpopProvision {
|
|
38
23
|
provisionId: string;
|
|
@@ -61,7 +46,17 @@ interface StoredSession {
|
|
|
61
46
|
accessToken: string;
|
|
62
47
|
clientKey: string;
|
|
63
48
|
instanceUrl: string;
|
|
64
|
-
scopes
|
|
49
|
+
scopes?: string;
|
|
50
|
+
pdsUrl?: string;
|
|
51
|
+
issuer?: string;
|
|
52
|
+
}
|
|
53
|
+
interface TokenInfo {
|
|
54
|
+
expiresAt?: Date;
|
|
55
|
+
expired?: boolean;
|
|
56
|
+
scope?: string;
|
|
57
|
+
iss?: string;
|
|
58
|
+
aud?: string;
|
|
59
|
+
sub: string;
|
|
65
60
|
}
|
|
66
61
|
interface ProvisionKeyResponse {
|
|
67
62
|
provision_id: string;
|
|
@@ -77,13 +72,52 @@ interface GetSessionResponse {
|
|
|
77
72
|
scopes: string[];
|
|
78
73
|
}
|
|
79
74
|
|
|
75
|
+
interface HappyViewSessionOptions {
|
|
76
|
+
did: string;
|
|
77
|
+
dpopKey: Key;
|
|
78
|
+
accessToken: string;
|
|
79
|
+
clientKey: string;
|
|
80
|
+
instanceUrl: string;
|
|
81
|
+
scopes?: string;
|
|
82
|
+
pdsUrl?: string;
|
|
83
|
+
issuer?: string;
|
|
84
|
+
fetch?: typeof globalThis.fetch;
|
|
85
|
+
onSignOut?: () => Promise<void>;
|
|
86
|
+
}
|
|
87
|
+
declare class HappyViewSession {
|
|
88
|
+
readonly did: string;
|
|
89
|
+
readonly scopes: string[];
|
|
90
|
+
private readonly dpopKey;
|
|
91
|
+
private readonly accessToken;
|
|
92
|
+
private readonly clientKey;
|
|
93
|
+
private readonly instanceUrl;
|
|
94
|
+
private readonly _scopes;
|
|
95
|
+
private readonly _pdsUrl;
|
|
96
|
+
private readonly _issuer;
|
|
97
|
+
private readonly _fetch;
|
|
98
|
+
private readonly _onSignOut;
|
|
99
|
+
private dpopNonce;
|
|
100
|
+
constructor(options: HappyViewSessionOptions);
|
|
101
|
+
get sub(): string;
|
|
102
|
+
getTokenInfo(): TokenInfo;
|
|
103
|
+
signOut(): Promise<void>;
|
|
104
|
+
fetchHandler(url: string, init: RequestInit): Promise<Response>;
|
|
105
|
+
}
|
|
106
|
+
|
|
80
107
|
declare const LAST_ACTIVE_KEY = "happyview:last-active-did";
|
|
108
|
+
interface FetchMetadataOptions {
|
|
109
|
+
clientId: string;
|
|
110
|
+
fetch?: typeof globalThis.fetch;
|
|
111
|
+
signal?: AbortSignal;
|
|
112
|
+
}
|
|
81
113
|
declare class HappyViewOAuthClient {
|
|
114
|
+
static fetchMetadata({ clientId, fetch: fetchFn, signal, }: FetchMetadataOptions): Promise<Record<string, unknown>>;
|
|
82
115
|
protected readonly instanceUrl: string;
|
|
83
116
|
protected readonly clientKey: string;
|
|
84
117
|
protected readonly storage: StorageAdapter;
|
|
85
118
|
private readonly clientSecret;
|
|
86
119
|
protected readonly _fetch: typeof globalThis.fetch;
|
|
120
|
+
private readonly sessionHooks;
|
|
87
121
|
constructor(options: HappyViewOAuthClientOptions & {
|
|
88
122
|
fetch?: typeof globalThis.fetch;
|
|
89
123
|
});
|
|
@@ -130,6 +164,12 @@ declare class TokenExchangeError extends HappyViewError {
|
|
|
130
164
|
declare class ResolutionError extends HappyViewError {
|
|
131
165
|
constructor(message: string);
|
|
132
166
|
}
|
|
167
|
+
declare class OAuthCallbackError extends HappyViewError {
|
|
168
|
+
readonly params: URLSearchParams;
|
|
169
|
+
readonly state: string | undefined;
|
|
170
|
+
static from(err: unknown, params: URLSearchParams, state?: string): OAuthCallbackError;
|
|
171
|
+
constructor(params: URLSearchParams, message?: string, state?: string, cause?: unknown);
|
|
172
|
+
}
|
|
133
173
|
|
|
134
174
|
declare class MemoryStorage implements StorageAdapter {
|
|
135
175
|
private data;
|
|
@@ -138,4 +178,4 @@ declare class MemoryStorage implements StorageAdapter {
|
|
|
138
178
|
delete(key: string): Promise<void>;
|
|
139
179
|
}
|
|
140
180
|
|
|
141
|
-
export { ApiError, AuthenticationError, type DpopProvision, type GetSessionResponse, HappyViewError, HappyViewOAuthClient, type HappyViewOAuthClientOptions, HappyViewSession, type HappyViewSessionOptions, InvalidStateError, LAST_ACTIVE_KEY, MemoryStorage, type ProvisionKeyResponse, type RegisterSessionParams, type RegisterSessionResponse, ResolutionError, type StorageAdapter, type StoredSession, TokenExchangeError, importJwk };
|
|
181
|
+
export { ApiError, AuthenticationError, type DpopProvision, type FetchMetadataOptions, type GetSessionResponse, HappyViewError, HappyViewOAuthClient, type HappyViewOAuthClientOptions, HappyViewSession, type HappyViewSessionOptions, InvalidStateError, LAST_ACTIVE_KEY, MemoryStorage, OAuthCallbackError, type ProvisionKeyResponse, type RegisterSessionParams, type RegisterSessionResponse, ResolutionError, type SessionEventHooks, type StorageAdapter, type StoredSession, TokenExchangeError, type TokenInfo, importJwk };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
export * from "@atproto/jwk";
|
|
3
|
+
export * from "@atproto/jwk-webcrypto";
|
|
4
|
+
|
|
1
5
|
// src/errors.ts
|
|
2
6
|
var HappyViewError = class extends Error {
|
|
3
7
|
constructor(message) {
|
|
@@ -45,6 +49,26 @@ var ResolutionError = class extends HappyViewError {
|
|
|
45
49
|
this.name = "ResolutionError";
|
|
46
50
|
}
|
|
47
51
|
};
|
|
52
|
+
var OAuthCallbackError = class _OAuthCallbackError extends HappyViewError {
|
|
53
|
+
params;
|
|
54
|
+
state;
|
|
55
|
+
static from(err, params, state) {
|
|
56
|
+
if (err instanceof _OAuthCallbackError) return err;
|
|
57
|
+
const message = err instanceof Error ? err.message : void 0;
|
|
58
|
+
return new _OAuthCallbackError(params, message, state, err);
|
|
59
|
+
}
|
|
60
|
+
constructor(params, message, state, cause) {
|
|
61
|
+
super(
|
|
62
|
+
message ?? params.get("error_description") ?? "OAuth callback error"
|
|
63
|
+
);
|
|
64
|
+
this.name = "OAuthCallbackError";
|
|
65
|
+
this.params = params;
|
|
66
|
+
this.state = state;
|
|
67
|
+
if (cause !== void 0) {
|
|
68
|
+
this.cause = cause;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
48
72
|
|
|
49
73
|
// src/import-jwk.ts
|
|
50
74
|
import { WebcryptoKey } from "@atproto/jwk-webcrypto";
|
|
@@ -87,16 +111,40 @@ var HappyViewSession = class {
|
|
|
87
111
|
accessToken;
|
|
88
112
|
clientKey;
|
|
89
113
|
instanceUrl;
|
|
114
|
+
_scopes;
|
|
115
|
+
_pdsUrl;
|
|
116
|
+
_issuer;
|
|
90
117
|
_fetch;
|
|
118
|
+
_onSignOut;
|
|
91
119
|
dpopNonce;
|
|
92
120
|
constructor(options) {
|
|
93
121
|
this.did = options.did;
|
|
94
|
-
this.scopes = options.scopes;
|
|
122
|
+
this.scopes = options.scopes ? options.scopes.split(" ") : [];
|
|
95
123
|
this.dpopKey = options.dpopKey;
|
|
96
124
|
this.accessToken = options.accessToken;
|
|
97
125
|
this.clientKey = options.clientKey;
|
|
98
126
|
this.instanceUrl = options.instanceUrl.replace(/\/+$/, "");
|
|
127
|
+
this._scopes = options.scopes;
|
|
128
|
+
this._pdsUrl = options.pdsUrl;
|
|
129
|
+
this._issuer = options.issuer;
|
|
99
130
|
this._fetch = options.fetch ?? ((input, init) => fetch(input, init));
|
|
131
|
+
this._onSignOut = options.onSignOut;
|
|
132
|
+
}
|
|
133
|
+
get sub() {
|
|
134
|
+
return this.did;
|
|
135
|
+
}
|
|
136
|
+
getTokenInfo() {
|
|
137
|
+
return {
|
|
138
|
+
scope: this._scopes,
|
|
139
|
+
iss: this._issuer,
|
|
140
|
+
aud: this._pdsUrl,
|
|
141
|
+
sub: this.did
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
async signOut() {
|
|
145
|
+
if (this._onSignOut) {
|
|
146
|
+
await this._onSignOut();
|
|
147
|
+
}
|
|
100
148
|
}
|
|
101
149
|
async fetchHandler(url, init) {
|
|
102
150
|
const fullUrl = /^https?:\/\//i.test(url) ? url : `${this.instanceUrl}${url}`;
|
|
@@ -175,17 +223,37 @@ var MemoryStorage = class {
|
|
|
175
223
|
var STORAGE_PREFIX = "happyview:session:";
|
|
176
224
|
var LAST_ACTIVE_KEY = "happyview:last-active-did";
|
|
177
225
|
var HappyViewOAuthClient = class {
|
|
226
|
+
static async fetchMetadata({
|
|
227
|
+
clientId,
|
|
228
|
+
fetch: fetchFn = globalThis.fetch,
|
|
229
|
+
signal
|
|
230
|
+
}) {
|
|
231
|
+
signal?.throwIfAborted();
|
|
232
|
+
const resp = await fetchFn(clientId, { redirect: "error", signal });
|
|
233
|
+
if (resp.status !== 200) {
|
|
234
|
+
resp.body?.cancel?.();
|
|
235
|
+
throw new Error(`Failed to fetch client metadata: ${resp.status}`);
|
|
236
|
+
}
|
|
237
|
+
const mime = resp.headers.get("content-type")?.split(";")[0].trim();
|
|
238
|
+
if (mime !== "application/json") {
|
|
239
|
+
resp.body?.cancel?.();
|
|
240
|
+
throw new Error(`Invalid client metadata content type: ${mime}`);
|
|
241
|
+
}
|
|
242
|
+
return resp.json();
|
|
243
|
+
}
|
|
178
244
|
instanceUrl;
|
|
179
245
|
clientKey;
|
|
180
246
|
storage;
|
|
181
247
|
clientSecret;
|
|
182
248
|
_fetch;
|
|
249
|
+
sessionHooks;
|
|
183
250
|
constructor(options) {
|
|
184
251
|
this.instanceUrl = options.instanceUrl.replace(/\/+$/, "");
|
|
185
252
|
this.clientKey = options.clientKey;
|
|
186
253
|
this.clientSecret = options.clientSecret;
|
|
187
254
|
this.storage = options.storage ?? new MemoryStorage();
|
|
188
255
|
this._fetch = options.fetch ?? ((input, init) => fetch(input, init));
|
|
256
|
+
this.sessionHooks = options.sessionHooks ?? {};
|
|
189
257
|
}
|
|
190
258
|
get isConfidential() {
|
|
191
259
|
return this.clientSecret !== void 0;
|
|
@@ -268,22 +336,29 @@ var HappyViewOAuthClient = class {
|
|
|
268
336
|
accessToken: params.accessToken,
|
|
269
337
|
clientKey: this.clientKey,
|
|
270
338
|
instanceUrl: this.instanceUrl,
|
|
271
|
-
scopes:
|
|
339
|
+
scopes: params.scopes,
|
|
340
|
+
pdsUrl: params.pdsUrl,
|
|
341
|
+
issuer: params.issuer
|
|
272
342
|
};
|
|
273
343
|
await this.storage.set(
|
|
274
344
|
`${STORAGE_PREFIX}${data.did}`,
|
|
275
345
|
JSON.stringify(storedSession)
|
|
276
346
|
);
|
|
277
347
|
await this.storage.set(LAST_ACTIVE_KEY, data.did);
|
|
278
|
-
|
|
348
|
+
const session = new HappyViewSession({
|
|
279
349
|
did: data.did,
|
|
280
350
|
dpopKey,
|
|
281
351
|
accessToken: params.accessToken,
|
|
282
352
|
clientKey: this.clientKey,
|
|
283
353
|
instanceUrl: this.instanceUrl,
|
|
284
|
-
scopes:
|
|
285
|
-
|
|
354
|
+
scopes: params.scopes,
|
|
355
|
+
pdsUrl: params.pdsUrl,
|
|
356
|
+
issuer: params.issuer,
|
|
357
|
+
fetch: this._fetch,
|
|
358
|
+
onSignOut: () => this.deleteSession(data.did)
|
|
286
359
|
});
|
|
360
|
+
this.sessionHooks.onSessionUpdate?.(data.did);
|
|
361
|
+
return session;
|
|
287
362
|
}
|
|
288
363
|
async deleteSession(did) {
|
|
289
364
|
const session = await this.restoreSession(did);
|
|
@@ -306,6 +381,7 @@ var HappyViewOAuthClient = class {
|
|
|
306
381
|
if (lastActive === did) {
|
|
307
382
|
await this.storage.delete(LAST_ACTIVE_KEY);
|
|
308
383
|
}
|
|
384
|
+
this.sessionHooks.onSessionDelete?.(did);
|
|
309
385
|
}
|
|
310
386
|
async restoreSession(did) {
|
|
311
387
|
const stored = await this.storage.get(`${STORAGE_PREFIX}${did}`);
|
|
@@ -318,8 +394,11 @@ var HappyViewOAuthClient = class {
|
|
|
318
394
|
accessToken: data.accessToken,
|
|
319
395
|
clientKey: data.clientKey,
|
|
320
396
|
instanceUrl: data.instanceUrl,
|
|
321
|
-
scopes: data.scopes
|
|
322
|
-
|
|
397
|
+
scopes: data.scopes,
|
|
398
|
+
pdsUrl: data.pdsUrl,
|
|
399
|
+
issuer: data.issuer,
|
|
400
|
+
fetch: this._fetch,
|
|
401
|
+
onSignOut: () => this.deleteSession(data.did)
|
|
323
402
|
});
|
|
324
403
|
}
|
|
325
404
|
async getSession(did) {
|
|
@@ -376,6 +455,7 @@ export {
|
|
|
376
455
|
InvalidStateError,
|
|
377
456
|
LAST_ACTIVE_KEY,
|
|
378
457
|
MemoryStorage,
|
|
458
|
+
OAuthCallbackError,
|
|
379
459
|
ResolutionError,
|
|
380
460
|
TokenExchangeError,
|
|
381
461
|
importJwk
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/import-jwk.ts","../src/session.ts","../src/storage.ts","../src/client.ts"],"sourcesContent":["export class HappyViewError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"HappyViewError\";\n }\n}\n\nexport class ApiError extends HappyViewError {\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body?: unknown) {\n super(message);\n this.name = \"ApiError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport class AuthenticationError extends HappyViewError {\n readonly status: number;\n\n constructor(message: string, status: number = 401) {\n super(message);\n this.name = \"AuthenticationError\";\n this.status = status;\n }\n}\n\nexport class InvalidStateError extends HappyViewError {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidStateError\";\n }\n}\n\nexport class TokenExchangeError extends HappyViewError {\n readonly status: number;\n readonly body: string;\n\n constructor(message: string, status: number, body: string) {\n super(message);\n this.name = \"TokenExchangeError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport class ResolutionError extends HappyViewError {\n constructor(message: string) {\n super(message);\n this.name = \"ResolutionError\";\n }\n}\n","import { WebcryptoKey } from \"@atproto/jwk-webcrypto\";\n\n/**\n * Import a raw ES256 JWK (as returned by HappyView's /oauth/dpop-keys)\n * into a WebcryptoKey that can sign JWTs.\n */\nexport async function importJwk(jwk: JsonWebKey): Promise<WebcryptoKey> {\n const privateKey = await crypto.subtle.importKey(\n \"jwk\",\n jwk,\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n true,\n [\"sign\"],\n );\n\n // Derive public key by stripping the private component\n const { d: _, ...publicJwkFields } = jwk;\n const publicKey = await crypto.subtle.importKey(\n \"jwk\",\n publicJwkFields,\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n true,\n [\"verify\"],\n );\n\n return WebcryptoKey.fromKeypair({ privateKey, publicKey });\n}\n","import type { Key } from \"@atproto/jwk\";\n\nexport interface HappyViewSessionOptions {\n did: string;\n dpopKey: Key;\n accessToken: string;\n clientKey: string;\n instanceUrl: string;\n scopes: string[];\n fetch?: typeof globalThis.fetch;\n}\n\nfunction randomHex(byteLength: number): string {\n const bytes = crypto.getRandomValues(new Uint8Array(byteLength));\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nfunction base64url(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nexport class HappyViewSession {\n readonly did: string;\n readonly scopes: string[];\n\n private readonly dpopKey: Key;\n private readonly accessToken: string;\n private readonly clientKey: string;\n private readonly instanceUrl: string;\n private readonly _fetch: typeof globalThis.fetch;\n private dpopNonce: string | undefined;\n\n constructor(options: HappyViewSessionOptions) {\n this.did = options.did;\n this.scopes = options.scopes;\n this.dpopKey = options.dpopKey;\n this.accessToken = options.accessToken;\n this.clientKey = options.clientKey;\n this.instanceUrl = options.instanceUrl.replace(/\\/+$/, \"\");\n this._fetch = options.fetch ?? ((input: RequestInfo | URL, init?: RequestInit) => fetch(input, init)) as typeof globalThis.fetch;\n }\n\n async fetchHandler(url: string, init: RequestInit): Promise<Response> {\n const fullUrl = /^https?:\\/\\//i.test(url)\n ? url\n : `${this.instanceUrl}${url}`;\n const method = (init.method ?? \"GET\").toUpperCase();\n const htu = fullUrl.split(\"?\")[0];\n\n const sendWithProof = async (): Promise<Response> => {\n const tokenHash = await crypto.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(this.accessToken),\n );\n const ath = base64url(tokenHash);\n\n const proof = await this.dpopKey.createJwt(\n {\n alg: \"ES256\",\n typ: \"dpop+jwt\",\n jwk: this.dpopKey.publicJwk!,\n },\n {\n htm: method,\n htu,\n iat: Math.floor(Date.now() / 1000),\n jti: randomHex(16),\n ath,\n nonce: this.dpopNonce,\n },\n );\n\n const headers = new Headers(init.headers);\n headers.set(\"authorization\", `DPoP ${this.accessToken}`);\n headers.set(\"dpop\", proof);\n headers.set(\"x-client-key\", this.clientKey);\n\n return this._fetch(fullUrl, { ...init, headers });\n };\n\n let response: Response;\n try {\n response = await sendWithProof();\n } catch (e) {\n const info = `_fetch type: ${typeof this._fetch}, toString: ${String(this._fetch).slice(0, 120)}, url: ${fullUrl}`;\n throw new Error(`fetchHandler failed: ${(e as Error).message}\\n\\nDEBUG: ${info}`);\n }\n\n // Retry once if the server requires a DPoP nonce we didn't have\n const nonce = response.headers.get(\"dpop-nonce\");\n if (nonce && nonce !== this.dpopNonce && (response.status === 401 || response.status === 400)) {\n this.dpopNonce = nonce;\n try {\n response = await sendWithProof();\n } catch (e) {\n throw new Error(`fetchHandler retry failed: ${(e as Error).message}`);\n }\n const retryNonce = response.headers.get(\"dpop-nonce\");\n if (retryNonce) {\n this.dpopNonce = retryNonce;\n }\n } else if (nonce) {\n this.dpopNonce = nonce;\n }\n\n return response;\n }\n}\n","import type { StorageAdapter } from \"./types\";\n\nexport class MemoryStorage implements StorageAdapter {\n private data = new Map<string, string>();\n\n async get(key: string): Promise<string | null> {\n return this.data.get(key) ?? null;\n }\n\n async set(key: string, value: string): Promise<void> {\n this.data.set(key, value);\n }\n\n async delete(key: string): Promise<void> {\n this.data.delete(key);\n }\n}\n","import type { Key } from \"@atproto/jwk\";\nimport { ApiError } from \"./errors\";\nimport { importJwk } from \"./import-jwk\";\nimport { HappyViewSession } from \"./session\";\nimport { MemoryStorage } from \"./storage\";\nimport type {\n GetSessionResponse,\n HappyViewOAuthClientOptions,\n ProvisionKeyResponse,\n RegisterSessionParams,\n RegisterSessionResponse,\n StorageAdapter,\n StoredSession,\n} from \"./types\";\n\nconst STORAGE_PREFIX = \"happyview:session:\";\nexport const LAST_ACTIVE_KEY = \"happyview:last-active-did\";\n\nexport class HappyViewOAuthClient {\n protected readonly instanceUrl: string;\n protected readonly clientKey: string;\n protected readonly storage: StorageAdapter;\n private readonly clientSecret: string | undefined;\n protected readonly _fetch: typeof globalThis.fetch;\n\n constructor(\n options: HappyViewOAuthClientOptions & {\n fetch?: typeof globalThis.fetch;\n },\n ) {\n this.instanceUrl = options.instanceUrl.replace(/\\/+$/, \"\");\n this.clientKey = options.clientKey;\n this.clientSecret = options.clientSecret;\n this.storage = options.storage ?? new MemoryStorage();\n this._fetch = options.fetch ?? ((input: RequestInfo | URL, init?: RequestInit) => fetch(input, init)) as typeof globalThis.fetch;\n }\n\n get isConfidential(): boolean {\n return this.clientSecret !== undefined;\n }\n\n async provisionDpopKey(): Promise<{\n provisionId: string;\n dpopKey: Key;\n rawJwk: JsonWebKey;\n pkceVerifier?: string;\n }> {\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const body: Record<string, unknown> = {};\n let pkceVerifier: string | undefined;\n\n if (!this.isConfidential) {\n pkceVerifier = generatePkceVerifier();\n const challenge = await computePkceChallenge(pkceVerifier);\n body.pkce_challenge = challenge;\n }\n\n const resp = await this._fetch(`${this.instanceUrl}/oauth/dpop-keys`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to provision DPoP key: ${resp.status} ${(body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n const data: ProvisionKeyResponse = await resp.json();\n const dpopKey = await importJwk(data.dpop_key);\n return {\n provisionId: data.provision_id,\n dpopKey,\n rawJwk: data.dpop_key,\n pkceVerifier,\n };\n }\n\n async registerSession(\n params: RegisterSessionParams,\n ): Promise<HappyViewSession> {\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const body: Record<string, unknown> = {\n provision_id: params.provisionId,\n did: params.did,\n access_token: params.accessToken,\n refresh_token: params.refreshToken,\n scopes: params.scopes,\n pds_url: params.pdsUrl,\n issuer: params.issuer,\n };\n\n if (!this.isConfidential && params.pkceVerifier) {\n body.pkce_verifier = params.pkceVerifier;\n }\n\n const resp = await this._fetch(`${this.instanceUrl}/oauth/sessions`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to register session: ${resp.status} ${(body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n const data: RegisterSessionResponse = await resp.json();\n const dpopKey = await importJwk(params.dpopKey);\n\n const storedSession: StoredSession = {\n did: data.did,\n dpopKey: params.dpopKey,\n accessToken: params.accessToken,\n clientKey: this.clientKey,\n instanceUrl: this.instanceUrl,\n scopes: data.scopes,\n };\n await this.storage.set(\n `${STORAGE_PREFIX}${data.did}`,\n JSON.stringify(storedSession),\n );\n await this.storage.set(LAST_ACTIVE_KEY, data.did);\n\n return new HappyViewSession({\n did: data.did,\n dpopKey,\n accessToken: params.accessToken,\n clientKey: this.clientKey,\n instanceUrl: this.instanceUrl,\n scopes: data.scopes,\n fetch: this._fetch,\n });\n }\n\n async deleteSession(did: string): Promise<void> {\n const session = await this.restoreSession(did);\n if (session) {\n const resp = await session.fetchHandler(\n `${this.instanceUrl}/oauth/sessions/${did}`,\n { method: \"DELETE\" },\n );\n\n if (!resp.ok && resp.status !== 404) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to delete session: ${resp.status} ${(body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n }\n\n await this.storage.delete(`${STORAGE_PREFIX}${did}`);\n\n const lastActive = await this.storage.get(LAST_ACTIVE_KEY);\n if (lastActive === did) {\n await this.storage.delete(LAST_ACTIVE_KEY);\n }\n }\n\n async restoreSession(did: string): Promise<HappyViewSession | null> {\n const stored = await this.storage.get(`${STORAGE_PREFIX}${did}`);\n if (!stored) return null;\n\n const data: StoredSession = JSON.parse(stored);\n const dpopKey = await importJwk(data.dpopKey);\n return new HappyViewSession({\n did: data.did,\n dpopKey,\n accessToken: data.accessToken,\n clientKey: data.clientKey,\n instanceUrl: data.instanceUrl,\n scopes: data.scopes ?? [],\n fetch: this._fetch,\n });\n }\n\n async getSession(did: string): Promise<GetSessionResponse> {\n const session = await this.restoreSession(did);\n if (!session) {\n throw new ApiError(\"No stored session for this DID\", 404, {});\n }\n\n const resp = await session.fetchHandler(\n `${this.instanceUrl}/oauth/sessions/${did}`,\n { method: \"GET\" },\n );\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to get session: ${resp.status} ${(body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n return resp.json();\n }\n\n async restore(): Promise<HappyViewSession | null> {\n const did = await this.storage.get(LAST_ACTIVE_KEY);\n if (!did) return null;\n return this.restoreSession(did);\n }\n}\n\n// PKCE helpers — inline since they're just a few lines of Web Crypto\nfunction generatePkceVerifier(): string {\n const bytes = crypto.getRandomValues(new Uint8Array(32));\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nasync function computePkceChallenge(verifier: string): Promise<string> {\n const hash = await crypto.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(verifier),\n );\n const bytes = new Uint8Array(hash);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n"],"mappings":"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","names":["body"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/import-jwk.ts","../src/session.ts","../src/storage.ts","../src/client.ts"],"sourcesContent":["export * from \"@atproto/jwk\";\nexport * from \"@atproto/jwk-webcrypto\";\n\nexport { HappyViewOAuthClient, LAST_ACTIVE_KEY } from \"./client\";\nexport type { FetchMetadataOptions } from \"./client\";\nexport { importJwk } from \"./import-jwk\";\nexport {\n ApiError,\n AuthenticationError,\n HappyViewError,\n InvalidStateError,\n OAuthCallbackError,\n ResolutionError,\n TokenExchangeError,\n} from \"./errors\";\nexport { HappyViewSession } from \"./session\";\nexport type { HappyViewSessionOptions } from \"./session\";\nexport { MemoryStorage } from \"./storage\";\nexport type {\n DpopProvision,\n GetSessionResponse,\n HappyViewOAuthClientOptions,\n ProvisionKeyResponse,\n RegisterSessionParams,\n RegisterSessionResponse,\n SessionEventHooks,\n StorageAdapter,\n StoredSession,\n TokenInfo,\n} from \"./types\";\n","export class HappyViewError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"HappyViewError\";\n }\n}\n\nexport class ApiError extends HappyViewError {\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body?: unknown) {\n super(message);\n this.name = \"ApiError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport class AuthenticationError extends HappyViewError {\n readonly status: number;\n\n constructor(message: string, status: number = 401) {\n super(message);\n this.name = \"AuthenticationError\";\n this.status = status;\n }\n}\n\nexport class InvalidStateError extends HappyViewError {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidStateError\";\n }\n}\n\nexport class TokenExchangeError extends HappyViewError {\n readonly status: number;\n readonly body: string;\n\n constructor(message: string, status: number, body: string) {\n super(message);\n this.name = \"TokenExchangeError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport class ResolutionError extends HappyViewError {\n constructor(message: string) {\n super(message);\n this.name = \"ResolutionError\";\n }\n}\n\nexport class OAuthCallbackError extends HappyViewError {\n readonly params: URLSearchParams;\n readonly state: string | undefined;\n\n static from(\n err: unknown,\n params: URLSearchParams,\n state?: string,\n ): OAuthCallbackError {\n if (err instanceof OAuthCallbackError) return err;\n const message = err instanceof Error ? err.message : undefined;\n return new OAuthCallbackError(params, message, state, err);\n }\n\n constructor(\n params: URLSearchParams,\n message?: string,\n state?: string,\n cause?: unknown,\n ) {\n super(\n message ??\n params.get(\"error_description\") ??\n \"OAuth callback error\",\n );\n this.name = \"OAuthCallbackError\";\n this.params = params;\n this.state = state;\n if (cause !== undefined) {\n this.cause = cause;\n }\n }\n}\n","import { WebcryptoKey } from \"@atproto/jwk-webcrypto\";\n\n/**\n * Import a raw ES256 JWK (as returned by HappyView's /oauth/dpop-keys)\n * into a WebcryptoKey that can sign JWTs.\n */\nexport async function importJwk(jwk: JsonWebKey): Promise<WebcryptoKey> {\n const privateKey = await crypto.subtle.importKey(\n \"jwk\",\n jwk,\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n true,\n [\"sign\"],\n );\n\n // Derive public key by stripping the private component\n const { d: _, ...publicJwkFields } = jwk;\n const publicKey = await crypto.subtle.importKey(\n \"jwk\",\n publicJwkFields,\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n true,\n [\"verify\"],\n );\n\n return WebcryptoKey.fromKeypair({ privateKey, publicKey });\n}\n","import type { Key } from \"@atproto/jwk\";\nimport type { TokenInfo } from \"./types\";\n\nexport interface HappyViewSessionOptions {\n did: string;\n dpopKey: Key;\n accessToken: string;\n clientKey: string;\n instanceUrl: string;\n scopes?: string;\n pdsUrl?: string;\n issuer?: string;\n fetch?: typeof globalThis.fetch;\n onSignOut?: () => Promise<void>;\n}\n\nfunction randomHex(byteLength: number): string {\n const bytes = crypto.getRandomValues(new Uint8Array(byteLength));\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nfunction base64url(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nexport class HappyViewSession {\n readonly did: string;\n readonly scopes: string[];\n\n private readonly dpopKey: Key;\n private readonly accessToken: string;\n private readonly clientKey: string;\n private readonly instanceUrl: string;\n private readonly _scopes: string | undefined;\n private readonly _pdsUrl: string | undefined;\n private readonly _issuer: string | undefined;\n private readonly _fetch: typeof globalThis.fetch;\n private readonly _onSignOut: (() => Promise<void>) | undefined;\n private dpopNonce: string | undefined;\n\n constructor(options: HappyViewSessionOptions) {\n this.did = options.did;\n this.scopes = options.scopes ? options.scopes.split(\" \") : [];\n this.dpopKey = options.dpopKey;\n this.accessToken = options.accessToken;\n this.clientKey = options.clientKey;\n this.instanceUrl = options.instanceUrl.replace(/\\/+$/, \"\");\n this._scopes = options.scopes;\n this._pdsUrl = options.pdsUrl;\n this._issuer = options.issuer;\n this._fetch = options.fetch ?? ((input: RequestInfo | URL, init?: RequestInit) => fetch(input, init)) as typeof globalThis.fetch;\n this._onSignOut = options.onSignOut;\n }\n\n get sub(): string {\n return this.did;\n }\n\n getTokenInfo(): TokenInfo {\n return {\n scope: this._scopes,\n iss: this._issuer,\n aud: this._pdsUrl,\n sub: this.did,\n };\n }\n\n async signOut(): Promise<void> {\n if (this._onSignOut) {\n await this._onSignOut();\n }\n }\n\n async fetchHandler(url: string, init: RequestInit): Promise<Response> {\n const fullUrl = /^https?:\\/\\//i.test(url)\n ? url\n : `${this.instanceUrl}${url}`;\n const method = (init.method ?? \"GET\").toUpperCase();\n const htu = fullUrl.split(\"?\")[0];\n\n const sendWithProof = async (): Promise<Response> => {\n const tokenHash = await crypto.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(this.accessToken),\n );\n const ath = base64url(tokenHash);\n\n const proof = await this.dpopKey.createJwt(\n {\n alg: \"ES256\",\n typ: \"dpop+jwt\",\n jwk: this.dpopKey.publicJwk!,\n },\n {\n htm: method,\n htu,\n iat: Math.floor(Date.now() / 1000),\n jti: randomHex(16),\n ath,\n nonce: this.dpopNonce,\n },\n );\n\n const headers = new Headers(init.headers);\n headers.set(\"authorization\", `DPoP ${this.accessToken}`);\n headers.set(\"dpop\", proof);\n headers.set(\"x-client-key\", this.clientKey);\n\n return this._fetch(fullUrl, { ...init, headers });\n };\n\n let response: Response;\n try {\n response = await sendWithProof();\n } catch (e) {\n const info = `_fetch type: ${typeof this._fetch}, toString: ${String(this._fetch).slice(0, 120)}, url: ${fullUrl}`;\n throw new Error(`fetchHandler failed: ${(e as Error).message}\\n\\nDEBUG: ${info}`);\n }\n\n // Retry once if the server requires a DPoP nonce we didn't have\n const nonce = response.headers.get(\"dpop-nonce\");\n if (nonce && nonce !== this.dpopNonce && (response.status === 401 || response.status === 400)) {\n this.dpopNonce = nonce;\n try {\n response = await sendWithProof();\n } catch (e) {\n throw new Error(`fetchHandler retry failed: ${(e as Error).message}`);\n }\n const retryNonce = response.headers.get(\"dpop-nonce\");\n if (retryNonce) {\n this.dpopNonce = retryNonce;\n }\n } else if (nonce) {\n this.dpopNonce = nonce;\n }\n\n return response;\n }\n}\n","import type { StorageAdapter } from \"./types\";\n\nexport class MemoryStorage implements StorageAdapter {\n private data = new Map<string, string>();\n\n async get(key: string): Promise<string | null> {\n return this.data.get(key) ?? null;\n }\n\n async set(key: string, value: string): Promise<void> {\n this.data.set(key, value);\n }\n\n async delete(key: string): Promise<void> {\n this.data.delete(key);\n }\n}\n","import type { Key } from \"@atproto/jwk\";\nimport { ApiError } from \"./errors\";\nimport { importJwk } from \"./import-jwk\";\nimport { HappyViewSession } from \"./session\";\nimport { MemoryStorage } from \"./storage\";\nimport type {\n GetSessionResponse,\n HappyViewOAuthClientOptions,\n ProvisionKeyResponse,\n RegisterSessionParams,\n RegisterSessionResponse,\n SessionEventHooks,\n StorageAdapter,\n StoredSession,\n} from \"./types\";\n\nconst STORAGE_PREFIX = \"happyview:session:\";\nexport const LAST_ACTIVE_KEY = \"happyview:last-active-did\";\n\nexport interface FetchMetadataOptions {\n clientId: string;\n fetch?: typeof globalThis.fetch;\n signal?: AbortSignal;\n}\n\nexport class HappyViewOAuthClient {\n static async fetchMetadata({\n clientId,\n fetch: fetchFn = globalThis.fetch,\n signal,\n }: FetchMetadataOptions): Promise<Record<string, unknown>> {\n signal?.throwIfAborted();\n const resp = await fetchFn(clientId, { redirect: \"error\", signal });\n if (resp.status !== 200) {\n resp.body?.cancel?.();\n throw new Error(`Failed to fetch client metadata: ${resp.status}`);\n }\n const mime = resp.headers.get(\"content-type\")?.split(\";\")[0].trim();\n if (mime !== \"application/json\") {\n resp.body?.cancel?.();\n throw new Error(`Invalid client metadata content type: ${mime}`);\n }\n return resp.json() as Promise<Record<string, unknown>>;\n }\n\n protected readonly instanceUrl: string;\n protected readonly clientKey: string;\n protected readonly storage: StorageAdapter;\n private readonly clientSecret: string | undefined;\n protected readonly _fetch: typeof globalThis.fetch;\n private readonly sessionHooks: SessionEventHooks;\n\n constructor(\n options: HappyViewOAuthClientOptions & {\n fetch?: typeof globalThis.fetch;\n },\n ) {\n this.instanceUrl = options.instanceUrl.replace(/\\/+$/, \"\");\n this.clientKey = options.clientKey;\n this.clientSecret = options.clientSecret;\n this.storage = options.storage ?? new MemoryStorage();\n this._fetch = options.fetch ?? ((input: RequestInfo | URL, init?: RequestInit) => fetch(input, init)) as typeof globalThis.fetch;\n this.sessionHooks = options.sessionHooks ?? {};\n }\n\n get isConfidential(): boolean {\n return this.clientSecret !== undefined;\n }\n\n async provisionDpopKey(): Promise<{\n provisionId: string;\n dpopKey: Key;\n rawJwk: JsonWebKey;\n pkceVerifier?: string;\n }> {\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const body: Record<string, unknown> = {};\n let pkceVerifier: string | undefined;\n\n if (!this.isConfidential) {\n pkceVerifier = generatePkceVerifier();\n const challenge = await computePkceChallenge(pkceVerifier);\n body.pkce_challenge = challenge;\n }\n\n const resp = await this._fetch(`${this.instanceUrl}/oauth/dpop-keys`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to provision DPoP key: ${resp.status} ${(body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n const data: ProvisionKeyResponse = await resp.json();\n const dpopKey = await importJwk(data.dpop_key);\n return {\n provisionId: data.provision_id,\n dpopKey,\n rawJwk: data.dpop_key,\n pkceVerifier,\n };\n }\n\n async registerSession(\n params: RegisterSessionParams,\n ): Promise<HappyViewSession> {\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const body: Record<string, unknown> = {\n provision_id: params.provisionId,\n did: params.did,\n access_token: params.accessToken,\n refresh_token: params.refreshToken,\n scopes: params.scopes,\n pds_url: params.pdsUrl,\n issuer: params.issuer,\n };\n\n if (!this.isConfidential && params.pkceVerifier) {\n body.pkce_verifier = params.pkceVerifier;\n }\n\n const resp = await this._fetch(`${this.instanceUrl}/oauth/sessions`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to register session: ${resp.status} ${(body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n const data: RegisterSessionResponse = await resp.json();\n const dpopKey = await importJwk(params.dpopKey);\n\n const storedSession: StoredSession = {\n did: data.did,\n dpopKey: params.dpopKey,\n accessToken: params.accessToken,\n clientKey: this.clientKey,\n instanceUrl: this.instanceUrl,\n scopes: params.scopes,\n pdsUrl: params.pdsUrl,\n issuer: params.issuer,\n };\n await this.storage.set(\n `${STORAGE_PREFIX}${data.did}`,\n JSON.stringify(storedSession),\n );\n await this.storage.set(LAST_ACTIVE_KEY, data.did);\n\n const session = new HappyViewSession({\n did: data.did,\n dpopKey,\n accessToken: params.accessToken,\n clientKey: this.clientKey,\n instanceUrl: this.instanceUrl,\n scopes: params.scopes,\n pdsUrl: params.pdsUrl,\n issuer: params.issuer,\n fetch: this._fetch,\n onSignOut: () => this.deleteSession(data.did),\n });\n\n this.sessionHooks.onSessionUpdate?.(data.did);\n\n return session;\n }\n\n async deleteSession(did: string): Promise<void> {\n const session = await this.restoreSession(did);\n if (session) {\n const resp = await session.fetchHandler(\n `${this.instanceUrl}/oauth/sessions/${did}`,\n { method: \"DELETE\" },\n );\n\n if (!resp.ok && resp.status !== 404) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to delete session: ${resp.status} ${(body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n }\n\n await this.storage.delete(`${STORAGE_PREFIX}${did}`);\n\n const lastActive = await this.storage.get(LAST_ACTIVE_KEY);\n if (lastActive === did) {\n await this.storage.delete(LAST_ACTIVE_KEY);\n }\n\n this.sessionHooks.onSessionDelete?.(did);\n }\n\n async restoreSession(did: string): Promise<HappyViewSession | null> {\n const stored = await this.storage.get(`${STORAGE_PREFIX}${did}`);\n if (!stored) return null;\n\n const data: StoredSession = JSON.parse(stored);\n const dpopKey = await importJwk(data.dpopKey);\n return new HappyViewSession({\n did: data.did,\n dpopKey,\n accessToken: data.accessToken,\n clientKey: data.clientKey,\n instanceUrl: data.instanceUrl,\n scopes: data.scopes,\n pdsUrl: data.pdsUrl,\n issuer: data.issuer,\n fetch: this._fetch,\n onSignOut: () => this.deleteSession(data.did),\n });\n }\n\n async getSession(did: string): Promise<GetSessionResponse> {\n const session = await this.restoreSession(did);\n if (!session) {\n throw new ApiError(\"No stored session for this DID\", 404, {});\n }\n\n const resp = await session.fetchHandler(\n `${this.instanceUrl}/oauth/sessions/${did}`,\n { method: \"GET\" },\n );\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to get session: ${resp.status} ${(body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n return resp.json();\n }\n\n async restore(): Promise<HappyViewSession | null> {\n const did = await this.storage.get(LAST_ACTIVE_KEY);\n if (!did) return null;\n return this.restoreSession(did);\n }\n}\n\n// PKCE helpers — inline since they're just a few lines of Web Crypto\nfunction generatePkceVerifier(): string {\n const bytes = crypto.getRandomValues(new Uint8Array(32));\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nasync function computePkceChallenge(verifier: string): Promise<string> {\n const hash = await crypto.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(verifier),\n );\n const bytes = new Uint8Array(hash);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n"],"mappings":"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","names":["body"]}
|