@happyview/oauth-client 1.1.0-dev.2 → 1.1.0

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 CHANGED
@@ -15,7 +15,6 @@ 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"));
19
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
19
 
21
20
  // src/index.ts
@@ -29,14 +28,11 @@ __export(index_exports, {
29
28
  InvalidStateError: () => InvalidStateError,
30
29
  LAST_ACTIVE_KEY: () => LAST_ACTIVE_KEY,
31
30
  MemoryStorage: () => MemoryStorage,
32
- OAuthCallbackError: () => OAuthCallbackError,
33
31
  ResolutionError: () => ResolutionError,
34
32
  TokenExchangeError: () => TokenExchangeError,
35
33
  importJwk: () => importJwk
36
34
  });
37
35
  module.exports = __toCommonJS(index_exports);
38
- __reExport(index_exports, require("@atproto/jwk"), module.exports);
39
- __reExport(index_exports, require("@atproto/jwk-webcrypto"), module.exports);
40
36
 
41
37
  // src/errors.ts
42
38
  var HappyViewError = class extends Error {
@@ -85,26 +81,6 @@ var ResolutionError = class extends HappyViewError {
85
81
  this.name = "ResolutionError";
86
82
  }
87
83
  };
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
- };
108
84
 
109
85
  // src/import-jwk.ts
110
86
  var import_jwk_webcrypto = require("@atproto/jwk-webcrypto");
@@ -146,11 +122,7 @@ var HappyViewSession = class {
146
122
  accessToken;
147
123
  clientKey;
148
124
  instanceUrl;
149
- _scopes;
150
- _pdsUrl;
151
- _issuer;
152
125
  _fetch;
153
- _onSignOut;
154
126
  dpopNonce;
155
127
  constructor(options) {
156
128
  this.did = options.did;
@@ -158,27 +130,7 @@ var HappyViewSession = class {
158
130
  this.accessToken = options.accessToken;
159
131
  this.clientKey = options.clientKey;
160
132
  this.instanceUrl = options.instanceUrl.replace(/\/+$/, "");
161
- this._scopes = options.scopes;
162
- this._pdsUrl = options.pdsUrl;
163
- this._issuer = options.issuer;
164
133
  this._fetch = options.fetch ?? ((input, init) => fetch(input, init));
165
- this._onSignOut = options.onSignOut;
166
- }
167
- get sub() {
168
- return this.did;
169
- }
170
- getTokenInfo() {
171
- return {
172
- scope: this._scopes,
173
- iss: this._issuer,
174
- aud: this._pdsUrl,
175
- sub: this.did
176
- };
177
- }
178
- async signOut() {
179
- if (this._onSignOut) {
180
- await this._onSignOut();
181
- }
182
134
  }
183
135
  async fetchHandler(url, init) {
184
136
  const fullUrl = /^https?:\/\//i.test(url) ? url : `${this.instanceUrl}${url}`;
@@ -257,37 +209,17 @@ var MemoryStorage = class {
257
209
  var STORAGE_PREFIX = "happyview:session:";
258
210
  var LAST_ACTIVE_KEY = "happyview:last-active-did";
259
211
  var HappyViewOAuthClient = class {
260
- static async fetchMetadata({
261
- clientId,
262
- fetch: fetchFn = globalThis.fetch,
263
- signal
264
- }) {
265
- signal?.throwIfAborted();
266
- const resp = await fetchFn(clientId, { redirect: "error", signal });
267
- if (resp.status !== 200) {
268
- resp.body?.cancel?.();
269
- throw new Error(`Failed to fetch client metadata: ${resp.status}`);
270
- }
271
- const mime = resp.headers.get("content-type")?.split(";")[0].trim();
272
- if (mime !== "application/json") {
273
- resp.body?.cancel?.();
274
- throw new Error(`Invalid client metadata content type: ${mime}`);
275
- }
276
- return resp.json();
277
- }
278
212
  instanceUrl;
279
213
  clientKey;
280
214
  storage;
281
215
  clientSecret;
282
216
  _fetch;
283
- sessionHooks;
284
217
  constructor(options) {
285
218
  this.instanceUrl = options.instanceUrl.replace(/\/+$/, "");
286
219
  this.clientKey = options.clientKey;
287
220
  this.clientSecret = options.clientSecret;
288
221
  this.storage = options.storage ?? new MemoryStorage();
289
222
  this._fetch = options.fetch ?? ((input, init) => fetch(input, init));
290
- this.sessionHooks = options.sessionHooks ?? {};
291
223
  }
292
224
  get isConfidential() {
293
225
  return this.clientSecret !== void 0;
@@ -369,30 +301,21 @@ var HappyViewOAuthClient = class {
369
301
  dpopKey: params.dpopKey,
370
302
  accessToken: params.accessToken,
371
303
  clientKey: this.clientKey,
372
- instanceUrl: this.instanceUrl,
373
- scopes: params.scopes,
374
- pdsUrl: params.pdsUrl,
375
- issuer: params.issuer
304
+ instanceUrl: this.instanceUrl
376
305
  };
377
306
  await this.storage.set(
378
307
  `${STORAGE_PREFIX}${data.did}`,
379
308
  JSON.stringify(storedSession)
380
309
  );
381
310
  await this.storage.set(LAST_ACTIVE_KEY, data.did);
382
- const session = new HappyViewSession({
311
+ return new HappyViewSession({
383
312
  did: data.did,
384
313
  dpopKey,
385
314
  accessToken: params.accessToken,
386
315
  clientKey: this.clientKey,
387
316
  instanceUrl: this.instanceUrl,
388
- scopes: params.scopes,
389
- pdsUrl: params.pdsUrl,
390
- issuer: params.issuer,
391
- fetch: this._fetch,
392
- onSignOut: () => this.deleteSession(data.did)
317
+ fetch: this._fetch
393
318
  });
394
- this.sessionHooks.onSessionUpdate?.(data.did);
395
- return session;
396
319
  }
397
320
  async deleteSession(did) {
398
321
  const session = await this.restoreSession(did);
@@ -415,7 +338,6 @@ var HappyViewOAuthClient = class {
415
338
  if (lastActive === did) {
416
339
  await this.storage.delete(LAST_ACTIVE_KEY);
417
340
  }
418
- this.sessionHooks.onSessionDelete?.(did);
419
341
  }
420
342
  async restoreSession(did) {
421
343
  const stored = await this.storage.get(`${STORAGE_PREFIX}${did}`);
@@ -428,11 +350,7 @@ var HappyViewOAuthClient = class {
428
350
  accessToken: data.accessToken,
429
351
  clientKey: data.clientKey,
430
352
  instanceUrl: data.instanceUrl,
431
- scopes: data.scopes,
432
- pdsUrl: data.pdsUrl,
433
- issuer: data.issuer,
434
- fetch: this._fetch,
435
- onSignOut: () => this.deleteSession(data.did)
353
+ fetch: this._fetch
436
354
  });
437
355
  }
438
356
  async restore() {
@@ -471,11 +389,8 @@ async function computePkceChallenge(verifier) {
471
389
  InvalidStateError,
472
390
  LAST_ACTIVE_KEY,
473
391
  MemoryStorage,
474
- OAuthCallbackError,
475
392
  ResolutionError,
476
393
  TokenExchangeError,
477
- importJwk,
478
- ...require("@atproto/jwk"),
479
- ...require("@atproto/jwk-webcrypto")
394
+ importJwk
480
395
  });
481
396
  //# sourceMappingURL=index.cjs.map
@@ -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 * 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 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\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.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 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 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 { 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 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 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\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.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 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 };\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 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 fetch: this._fetch,\n });\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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAO,IAAM,iBAAN,cAA6B,MAAM;AAAA,EACxC,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,WAAN,cAAuB,eAAe;AAAA,EAClC;AAAA,EACA;AAAA,EAET,YAAY,SAAiB,QAAgB,MAAgB;AAC3D,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,sBAAN,cAAkC,eAAe;AAAA,EAC7C;AAAA,EAET,YAAY,SAAiB,SAAiB,KAAK;AACjD,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AAAA,EAChB;AACF;AAEO,IAAM,oBAAN,cAAgC,eAAe;AAAA,EACpD,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,qBAAN,cAAiC,eAAe;AAAA,EAC5C;AAAA,EACA;AAAA,EAET,YAAY,SAAiB,QAAgB,MAAc;AACzD,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,kBAAN,cAA8B,eAAe;AAAA,EAClD,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;;;ACrDA,2BAA6B;AAM7B,eAAsB,UAAU,KAAwC;AACtE,QAAM,aAAa,MAAM,OAAO,OAAO;AAAA,IACrC;AAAA,IACA;AAAA,IACA,EAAE,MAAM,SAAS,YAAY,QAAQ;AAAA,IACrC;AAAA,IACA,CAAC,MAAM;AAAA,EACT;AAGA,QAAM,EAAE,GAAG,GAAG,GAAG,gBAAgB,IAAI;AACrC,QAAM,YAAY,MAAM,OAAO,OAAO;AAAA,IACpC;AAAA,IACA;AAAA,IACA,EAAE,MAAM,SAAS,YAAY,QAAQ;AAAA,IACrC;AAAA,IACA,CAAC,QAAQ;AAAA,EACX;AAEA,SAAO,kCAAa,YAAY,EAAE,YAAY,UAAU,CAAC;AAC3D;;;ACfA,SAAS,UAAU,YAA4B;AAC7C,QAAM,QAAQ,OAAO,gBAAgB,IAAI,WAAW,UAAU,CAAC;AAC/D,SAAO,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AAC1E;AAEA,SAAS,UAAU,QAA6B;AAC9C,QAAM,QAAQ,IAAI,WAAW,MAAM;AACnC,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,cAAU,OAAO,aAAa,MAAM,CAAC,CAAC;AAAA,EACxC;AACA,SAAO,KAAK,MAAM,EACf,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,EAAE;AACtB;AAEO,IAAM,mBAAN,MAAuB;AAAA,EACnB;AAAA,EAEQ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACT;AAAA,EAER,YAAY,SAAkC;AAC5C,SAAK,MAAM,QAAQ;AACnB,SAAK,UAAU,QAAQ;AACvB,SAAK,cAAc,QAAQ;AAC3B,SAAK,YAAY,QAAQ;AACzB,SAAK,cAAc,QAAQ,YAAY,QAAQ,QAAQ,EAAE;AACzD,SAAK,SAAS,QAAQ,UAAU,CAAC,OAA0B,SAAuB,MAAM,OAAO,IAAI;AAAA,EACrG;AAAA,EAEA,MAAM,aAAa,KAAa,MAAsC;AACpE,UAAM,UAAU,gBAAgB,KAAK,GAAG,IACpC,MACA,GAAG,KAAK,WAAW,GAAG,GAAG;AAC7B,UAAM,UAAU,KAAK,UAAU,OAAO,YAAY;AAClD,UAAM,MAAM,QAAQ,MAAM,GAAG,EAAE,CAAC;AAEhC,UAAM,gBAAgB,YAA+B;AACnD,YAAM,YAAY,MAAM,OAAO,OAAO;AAAA,QACpC;AAAA,QACA,IAAI,YAAY,EAAE,OAAO,KAAK,WAAW;AAAA,MAC3C;AACA,YAAM,MAAM,UAAU,SAAS;AAE/B,YAAM,QAAQ,MAAM,KAAK,QAAQ;AAAA,QAC/B;AAAA,UACE,KAAK;AAAA,UACL,KAAK;AAAA,UACL,KAAK,KAAK,QAAQ;AAAA,QACpB;AAAA,QACA;AAAA,UACE,KAAK;AAAA,UACL;AAAA,UACA,KAAK,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAAA,UACjC,KAAK,UAAU,EAAE;AAAA,UACjB;AAAA,UACA,OAAO,KAAK;AAAA,QACd;AAAA,MACF;AAEA,YAAM,UAAU,IAAI,QAAQ,KAAK,OAAO;AACxC,cAAQ,IAAI,iBAAiB,QAAQ,KAAK,WAAW,EAAE;AACvD,cAAQ,IAAI,QAAQ,KAAK;AACzB,cAAQ,IAAI,gBAAgB,KAAK,SAAS;AAE1C,aAAO,KAAK,OAAO,SAAS,EAAE,GAAG,MAAM,QAAQ,CAAC;AAAA,IAClD;AAEA,QAAI;AACJ,QAAI;AACF,iBAAW,MAAM,cAAc;AAAA,IACjC,SAAS,GAAG;AACV,YAAM,OAAO,gBAAgB,OAAO,KAAK,MAAM,eAAe,OAAO,KAAK,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,UAAU,OAAO;AAChH,YAAM,IAAI,MAAM,wBAAyB,EAAY,OAAO;AAAA;AAAA,SAAc,IAAI,EAAE;AAAA,IAClF;AAGA,UAAM,QAAQ,SAAS,QAAQ,IAAI,YAAY;AAC/C,QAAI,SAAS,UAAU,KAAK,cAAc,SAAS,WAAW,OAAO,SAAS,WAAW,MAAM;AAC7F,WAAK,YAAY;AACjB,UAAI;AACF,mBAAW,MAAM,cAAc;AAAA,MACjC,SAAS,GAAG;AACV,cAAM,IAAI,MAAM,8BAA+B,EAAY,OAAO,EAAE;AAAA,MACtE;AACA,YAAM,aAAa,SAAS,QAAQ,IAAI,YAAY;AACpD,UAAI,YAAY;AACd,aAAK,YAAY;AAAA,MACnB;AAAA,IACF,WAAW,OAAO;AAChB,WAAK,YAAY;AAAA,IACnB;AAEA,WAAO;AAAA,EACT;AACF;;;AC9GO,IAAM,gBAAN,MAA8C;AAAA,EAC3C,OAAO,oBAAI,IAAoB;AAAA,EAEvC,MAAM,IAAI,KAAqC;AAC7C,WAAO,KAAK,KAAK,IAAI,GAAG,KAAK;AAAA,EAC/B;AAAA,EAEA,MAAM,IAAI,KAAa,OAA8B;AACnD,SAAK,KAAK,IAAI,KAAK,KAAK;AAAA,EAC1B;AAAA,EAEA,MAAM,OAAO,KAA4B;AACvC,SAAK,KAAK,OAAO,GAAG;AAAA,EACtB;AACF;;;ACFA,IAAM,iBAAiB;AAChB,IAAM,kBAAkB;AAExB,IAAM,uBAAN,MAA2B;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACF;AAAA,EACE;AAAA,EAEnB,YACE,SAGA;AACA,SAAK,cAAc,QAAQ,YAAY,QAAQ,QAAQ,EAAE;AACzD,SAAK,YAAY,QAAQ;AACzB,SAAK,eAAe,QAAQ;AAC5B,SAAK,UAAU,QAAQ,WAAW,IAAI,cAAc;AACpD,SAAK,SAAS,QAAQ,UAAU,CAAC,OAA0B,SAAuB,MAAM,OAAO,IAAI;AAAA,EACrG;AAAA,EAEA,IAAI,iBAA0B;AAC5B,WAAO,KAAK,iBAAiB;AAAA,EAC/B;AAAA,EAEA,MAAM,mBAKH;AACD,UAAM,UAAkC;AAAA,MACtC,gBAAgB;AAAA,MAChB,gBAAgB,KAAK;AAAA,IACvB;AACA,QAAI,KAAK,cAAc;AACrB,cAAQ,iBAAiB,IAAI,KAAK;AAAA,IACpC;AAEA,UAAM,OAAgC,CAAC;AACvC,QAAI;AAEJ,QAAI,CAAC,KAAK,gBAAgB;AACxB,qBAAe,qBAAqB;AACpC,YAAM,YAAY,MAAM,qBAAqB,YAAY;AACzD,WAAK,iBAAiB;AAAA,IACxB;AAEA,UAAM,OAAO,MAAM,KAAK,OAAO,GAAG,KAAK,WAAW,oBAAoB;AAAA,MACpE,QAAQ;AAAA,MACR;AAAA,MACA,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B,CAAC;AAED,QAAI,CAAC,KAAK,IAAI;AACZ,YAAMA,QAAO,MAAM,KAAK,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC/C,YAAM,IAAI;AAAA,QACR,iCAAiC,KAAK,MAAM,IAAKA,MAAa,WAAW,KAAK,UAAU;AAAA,QACxF,KAAK;AAAA,QACLA;AAAA,MACF;AAAA,IACF;AAEA,UAAM,OAA6B,MAAM,KAAK,KAAK;AACnD,UAAM,UAAU,MAAM,UAAU,KAAK,QAAQ;AAC7C,WAAO;AAAA,MACL,aAAa,KAAK;AAAA,MAClB;AAAA,MACA,QAAQ,KAAK;AAAA,MACb;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,gBACJ,QAC2B;AAC3B,UAAM,UAAkC;AAAA,MACtC,gBAAgB;AAAA,MAChB,gBAAgB,KAAK;AAAA,IACvB;AACA,QAAI,KAAK,cAAc;AACrB,cAAQ,iBAAiB,IAAI,KAAK;AAAA,IACpC;AAEA,UAAM,OAAgC;AAAA,MACpC,cAAc,OAAO;AAAA,MACrB,KAAK,OAAO;AAAA,MACZ,cAAc,OAAO;AAAA,MACrB,eAAe,OAAO;AAAA,MACtB,QAAQ,OAAO;AAAA,MACf,SAAS,OAAO;AAAA,MAChB,QAAQ,OAAO;AAAA,IACjB;AAEA,QAAI,CAAC,KAAK,kBAAkB,OAAO,cAAc;AAC/C,WAAK,gBAAgB,OAAO;AAAA,IAC9B;AAEA,UAAM,OAAO,MAAM,KAAK,OAAO,GAAG,KAAK,WAAW,mBAAmB;AAAA,MACnE,QAAQ;AAAA,MACR;AAAA,MACA,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B,CAAC;AAED,QAAI,CAAC,KAAK,IAAI;AACZ,YAAMA,QAAO,MAAM,KAAK,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC/C,YAAM,IAAI;AAAA,QACR,+BAA+B,KAAK,MAAM,IAAKA,MAAa,WAAW,KAAK,UAAU;AAAA,QACtF,KAAK;AAAA,QACLA;AAAA,MACF;AAAA,IACF;AAEA,UAAM,OAAgC,MAAM,KAAK,KAAK;AACtD,UAAM,UAAU,MAAM,UAAU,OAAO,OAAO;AAE9C,UAAM,gBAA+B;AAAA,MACnC,KAAK,KAAK;AAAA,MACV,SAAS,OAAO;AAAA,MAChB,aAAa,OAAO;AAAA,MACpB,WAAW,KAAK;AAAA,MAChB,aAAa,KAAK;AAAA,IACpB;AACA,UAAM,KAAK,QAAQ;AAAA,MACjB,GAAG,cAAc,GAAG,KAAK,GAAG;AAAA,MAC5B,KAAK,UAAU,aAAa;AAAA,IAC9B;AACA,UAAM,KAAK,QAAQ,IAAI,iBAAiB,KAAK,GAAG;AAEhD,WAAO,IAAI,iBAAiB;AAAA,MAC1B,KAAK,KAAK;AAAA,MACV;AAAA,MACA,aAAa,OAAO;AAAA,MACpB,WAAW,KAAK;AAAA,MAChB,aAAa,KAAK;AAAA,MAClB,OAAO,KAAK;AAAA,IACd,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,cAAc,KAA4B;AAC9C,UAAM,UAAU,MAAM,KAAK,eAAe,GAAG;AAC7C,QAAI,SAAS;AACX,YAAM,OAAO,MAAM,QAAQ;AAAA,QACzB,GAAG,KAAK,WAAW,mBAAmB,GAAG;AAAA,QACzC,EAAE,QAAQ,SAAS;AAAA,MACrB;AAEA,UAAI,CAAC,KAAK,MAAM,KAAK,WAAW,KAAK;AACnC,cAAM,OAAO,MAAM,KAAK,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC/C,cAAM,IAAI;AAAA,UACR,6BAA6B,KAAK,MAAM,IAAK,KAAa,WAAW,KAAK,UAAU;AAAA,UACpF,KAAK;AAAA,UACL;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,UAAM,KAAK,QAAQ,OAAO,GAAG,cAAc,GAAG,GAAG,EAAE;AAEnD,UAAM,aAAa,MAAM,KAAK,QAAQ,IAAI,eAAe;AACzD,QAAI,eAAe,KAAK;AACtB,YAAM,KAAK,QAAQ,OAAO,eAAe;AAAA,IAC3C;AAAA,EACF;AAAA,EAEA,MAAM,eAAe,KAA+C;AAClE,UAAM,SAAS,MAAM,KAAK,QAAQ,IAAI,GAAG,cAAc,GAAG,GAAG,EAAE;AAC/D,QAAI,CAAC,OAAQ,QAAO;AAEpB,UAAM,OAAsB,KAAK,MAAM,MAAM;AAC7C,UAAM,UAAU,MAAM,UAAU,KAAK,OAAO;AAC5C,WAAO,IAAI,iBAAiB;AAAA,MAC1B,KAAK,KAAK;AAAA,MACV;AAAA,MACA,aAAa,KAAK;AAAA,MAClB,WAAW,KAAK;AAAA,MAChB,aAAa,KAAK;AAAA,MAClB,OAAO,KAAK;AAAA,IACd,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,UAA4C;AAChD,UAAM,MAAM,MAAM,KAAK,QAAQ,IAAI,eAAe;AAClD,QAAI,CAAC,IAAK,QAAO;AACjB,WAAO,KAAK,eAAe,GAAG;AAAA,EAChC;AACF;AAGA,SAAS,uBAA+B;AACtC,QAAM,QAAQ,OAAO,gBAAgB,IAAI,WAAW,EAAE,CAAC;AACvD,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,cAAU,OAAO,aAAa,MAAM,CAAC,CAAC;AAAA,EACxC;AACA,SAAO,KAAK,MAAM,EACf,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,EAAE;AACtB;AAEA,eAAe,qBAAqB,UAAmC;AACrE,QAAM,OAAO,MAAM,OAAO,OAAO;AAAA,IAC/B;AAAA,IACA,IAAI,YAAY,EAAE,OAAO,QAAQ;AAAA,EACnC;AACA,QAAM,QAAQ,IAAI,WAAW,IAAI;AACjC,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,cAAU,OAAO,aAAa,MAAM,CAAC,CAAC;AAAA,EACxC;AACA,SAAO,KAAK,MAAM,EACf,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,EAAE;AACtB;","names":["body"]}
package/dist/index.d.cts CHANGED
@@ -1,23 +1,36 @@
1
1
  import { Key } from '@atproto/jwk';
2
- export * from '@atproto/jwk';
3
2
  import { WebcryptoKey } from '@atproto/jwk-webcrypto';
4
- export * from '@atproto/jwk-webcrypto';
3
+
4
+ interface HappyViewSessionOptions {
5
+ did: string;
6
+ dpopKey: Key;
7
+ accessToken: string;
8
+ clientKey: string;
9
+ instanceUrl: string;
10
+ fetch?: typeof globalThis.fetch;
11
+ }
12
+ declare class HappyViewSession {
13
+ readonly did: string;
14
+ private readonly dpopKey;
15
+ private readonly accessToken;
16
+ private readonly clientKey;
17
+ private readonly instanceUrl;
18
+ private readonly _fetch;
19
+ private dpopNonce;
20
+ constructor(options: HappyViewSessionOptions);
21
+ fetchHandler(url: string, init: RequestInit): Promise<Response>;
22
+ }
5
23
 
6
24
  interface StorageAdapter {
7
25
  get(key: string): Promise<string | null>;
8
26
  set(key: string, value: string): Promise<void>;
9
27
  delete(key: string): Promise<void>;
10
28
  }
11
- interface SessionEventHooks {
12
- onSessionUpdate?: (did: string) => void;
13
- onSessionDelete?: (did: string, cause?: unknown) => void;
14
- }
15
29
  interface HappyViewOAuthClientOptions {
16
30
  instanceUrl: string;
17
31
  clientKey: string;
18
32
  clientSecret?: string;
19
33
  storage?: StorageAdapter;
20
- sessionHooks?: SessionEventHooks;
21
34
  }
22
35
  interface DpopProvision {
23
36
  provisionId: string;
@@ -46,17 +59,6 @@ interface StoredSession {
46
59
  accessToken: string;
47
60
  clientKey: string;
48
61
  instanceUrl: string;
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;
60
62
  }
61
63
  interface ProvisionKeyResponse {
62
64
  provision_id: string;
@@ -67,51 +69,13 @@ interface RegisterSessionResponse {
67
69
  did: string;
68
70
  }
69
71
 
70
- interface HappyViewSessionOptions {
71
- did: string;
72
- dpopKey: Key;
73
- accessToken: string;
74
- clientKey: string;
75
- instanceUrl: string;
76
- scopes?: string;
77
- pdsUrl?: string;
78
- issuer?: string;
79
- fetch?: typeof globalThis.fetch;
80
- onSignOut?: () => Promise<void>;
81
- }
82
- declare class HappyViewSession {
83
- readonly did: string;
84
- private readonly dpopKey;
85
- private readonly accessToken;
86
- private readonly clientKey;
87
- private readonly instanceUrl;
88
- private readonly _scopes;
89
- private readonly _pdsUrl;
90
- private readonly _issuer;
91
- private readonly _fetch;
92
- private readonly _onSignOut;
93
- private dpopNonce;
94
- constructor(options: HappyViewSessionOptions);
95
- get sub(): string;
96
- getTokenInfo(): TokenInfo;
97
- signOut(): Promise<void>;
98
- fetchHandler(url: string, init: RequestInit): Promise<Response>;
99
- }
100
-
101
72
  declare const LAST_ACTIVE_KEY = "happyview:last-active-did";
102
- interface FetchMetadataOptions {
103
- clientId: string;
104
- fetch?: typeof globalThis.fetch;
105
- signal?: AbortSignal;
106
- }
107
73
  declare class HappyViewOAuthClient {
108
- static fetchMetadata({ clientId, fetch: fetchFn, signal, }: FetchMetadataOptions): Promise<Record<string, unknown>>;
109
74
  protected readonly instanceUrl: string;
110
75
  protected readonly clientKey: string;
111
76
  protected readonly storage: StorageAdapter;
112
77
  private readonly clientSecret;
113
78
  protected readonly _fetch: typeof globalThis.fetch;
114
- private readonly sessionHooks;
115
79
  constructor(options: HappyViewOAuthClientOptions & {
116
80
  fetch?: typeof globalThis.fetch;
117
81
  });
@@ -157,12 +121,6 @@ declare class TokenExchangeError extends HappyViewError {
157
121
  declare class ResolutionError extends HappyViewError {
158
122
  constructor(message: string);
159
123
  }
160
- declare class OAuthCallbackError extends HappyViewError {
161
- readonly params: URLSearchParams;
162
- readonly state: string | undefined;
163
- static from(err: unknown, params: URLSearchParams, state?: string): OAuthCallbackError;
164
- constructor(params: URLSearchParams, message?: string, state?: string, cause?: unknown);
165
- }
166
124
 
167
125
  declare class MemoryStorage implements StorageAdapter {
168
126
  private data;
@@ -171,4 +129,4 @@ declare class MemoryStorage implements StorageAdapter {
171
129
  delete(key: string): Promise<void>;
172
130
  }
173
131
 
174
- export { ApiError, AuthenticationError, type DpopProvision, type FetchMetadataOptions, 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 };
132
+ export { ApiError, AuthenticationError, type DpopProvision, 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 };
package/dist/index.d.ts CHANGED
@@ -1,23 +1,36 @@
1
1
  import { Key } from '@atproto/jwk';
2
- export * from '@atproto/jwk';
3
2
  import { WebcryptoKey } from '@atproto/jwk-webcrypto';
4
- export * from '@atproto/jwk-webcrypto';
3
+
4
+ interface HappyViewSessionOptions {
5
+ did: string;
6
+ dpopKey: Key;
7
+ accessToken: string;
8
+ clientKey: string;
9
+ instanceUrl: string;
10
+ fetch?: typeof globalThis.fetch;
11
+ }
12
+ declare class HappyViewSession {
13
+ readonly did: string;
14
+ private readonly dpopKey;
15
+ private readonly accessToken;
16
+ private readonly clientKey;
17
+ private readonly instanceUrl;
18
+ private readonly _fetch;
19
+ private dpopNonce;
20
+ constructor(options: HappyViewSessionOptions);
21
+ fetchHandler(url: string, init: RequestInit): Promise<Response>;
22
+ }
5
23
 
6
24
  interface StorageAdapter {
7
25
  get(key: string): Promise<string | null>;
8
26
  set(key: string, value: string): Promise<void>;
9
27
  delete(key: string): Promise<void>;
10
28
  }
11
- interface SessionEventHooks {
12
- onSessionUpdate?: (did: string) => void;
13
- onSessionDelete?: (did: string, cause?: unknown) => void;
14
- }
15
29
  interface HappyViewOAuthClientOptions {
16
30
  instanceUrl: string;
17
31
  clientKey: string;
18
32
  clientSecret?: string;
19
33
  storage?: StorageAdapter;
20
- sessionHooks?: SessionEventHooks;
21
34
  }
22
35
  interface DpopProvision {
23
36
  provisionId: string;
@@ -46,17 +59,6 @@ interface StoredSession {
46
59
  accessToken: string;
47
60
  clientKey: string;
48
61
  instanceUrl: string;
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;
60
62
  }
61
63
  interface ProvisionKeyResponse {
62
64
  provision_id: string;
@@ -67,51 +69,13 @@ interface RegisterSessionResponse {
67
69
  did: string;
68
70
  }
69
71
 
70
- interface HappyViewSessionOptions {
71
- did: string;
72
- dpopKey: Key;
73
- accessToken: string;
74
- clientKey: string;
75
- instanceUrl: string;
76
- scopes?: string;
77
- pdsUrl?: string;
78
- issuer?: string;
79
- fetch?: typeof globalThis.fetch;
80
- onSignOut?: () => Promise<void>;
81
- }
82
- declare class HappyViewSession {
83
- readonly did: string;
84
- private readonly dpopKey;
85
- private readonly accessToken;
86
- private readonly clientKey;
87
- private readonly instanceUrl;
88
- private readonly _scopes;
89
- private readonly _pdsUrl;
90
- private readonly _issuer;
91
- private readonly _fetch;
92
- private readonly _onSignOut;
93
- private dpopNonce;
94
- constructor(options: HappyViewSessionOptions);
95
- get sub(): string;
96
- getTokenInfo(): TokenInfo;
97
- signOut(): Promise<void>;
98
- fetchHandler(url: string, init: RequestInit): Promise<Response>;
99
- }
100
-
101
72
  declare const LAST_ACTIVE_KEY = "happyview:last-active-did";
102
- interface FetchMetadataOptions {
103
- clientId: string;
104
- fetch?: typeof globalThis.fetch;
105
- signal?: AbortSignal;
106
- }
107
73
  declare class HappyViewOAuthClient {
108
- static fetchMetadata({ clientId, fetch: fetchFn, signal, }: FetchMetadataOptions): Promise<Record<string, unknown>>;
109
74
  protected readonly instanceUrl: string;
110
75
  protected readonly clientKey: string;
111
76
  protected readonly storage: StorageAdapter;
112
77
  private readonly clientSecret;
113
78
  protected readonly _fetch: typeof globalThis.fetch;
114
- private readonly sessionHooks;
115
79
  constructor(options: HappyViewOAuthClientOptions & {
116
80
  fetch?: typeof globalThis.fetch;
117
81
  });
@@ -157,12 +121,6 @@ declare class TokenExchangeError extends HappyViewError {
157
121
  declare class ResolutionError extends HappyViewError {
158
122
  constructor(message: string);
159
123
  }
160
- declare class OAuthCallbackError extends HappyViewError {
161
- readonly params: URLSearchParams;
162
- readonly state: string | undefined;
163
- static from(err: unknown, params: URLSearchParams, state?: string): OAuthCallbackError;
164
- constructor(params: URLSearchParams, message?: string, state?: string, cause?: unknown);
165
- }
166
124
 
167
125
  declare class MemoryStorage implements StorageAdapter {
168
126
  private data;
@@ -171,4 +129,4 @@ declare class MemoryStorage implements StorageAdapter {
171
129
  delete(key: string): Promise<void>;
172
130
  }
173
131
 
174
- export { ApiError, AuthenticationError, type DpopProvision, type FetchMetadataOptions, 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 };
132
+ export { ApiError, AuthenticationError, type DpopProvision, 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 };
package/dist/index.js CHANGED
@@ -1,7 +1,3 @@
1
- // src/index.ts
2
- export * from "@atproto/jwk";
3
- export * from "@atproto/jwk-webcrypto";
4
-
5
1
  // src/errors.ts
6
2
  var HappyViewError = class extends Error {
7
3
  constructor(message) {
@@ -49,26 +45,6 @@ var ResolutionError = class extends HappyViewError {
49
45
  this.name = "ResolutionError";
50
46
  }
51
47
  };
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
- };
72
48
 
73
49
  // src/import-jwk.ts
74
50
  import { WebcryptoKey } from "@atproto/jwk-webcrypto";
@@ -110,11 +86,7 @@ var HappyViewSession = class {
110
86
  accessToken;
111
87
  clientKey;
112
88
  instanceUrl;
113
- _scopes;
114
- _pdsUrl;
115
- _issuer;
116
89
  _fetch;
117
- _onSignOut;
118
90
  dpopNonce;
119
91
  constructor(options) {
120
92
  this.did = options.did;
@@ -122,27 +94,7 @@ var HappyViewSession = class {
122
94
  this.accessToken = options.accessToken;
123
95
  this.clientKey = options.clientKey;
124
96
  this.instanceUrl = options.instanceUrl.replace(/\/+$/, "");
125
- this._scopes = options.scopes;
126
- this._pdsUrl = options.pdsUrl;
127
- this._issuer = options.issuer;
128
97
  this._fetch = options.fetch ?? ((input, init) => fetch(input, init));
129
- this._onSignOut = options.onSignOut;
130
- }
131
- get sub() {
132
- return this.did;
133
- }
134
- getTokenInfo() {
135
- return {
136
- scope: this._scopes,
137
- iss: this._issuer,
138
- aud: this._pdsUrl,
139
- sub: this.did
140
- };
141
- }
142
- async signOut() {
143
- if (this._onSignOut) {
144
- await this._onSignOut();
145
- }
146
98
  }
147
99
  async fetchHandler(url, init) {
148
100
  const fullUrl = /^https?:\/\//i.test(url) ? url : `${this.instanceUrl}${url}`;
@@ -221,37 +173,17 @@ var MemoryStorage = class {
221
173
  var STORAGE_PREFIX = "happyview:session:";
222
174
  var LAST_ACTIVE_KEY = "happyview:last-active-did";
223
175
  var HappyViewOAuthClient = class {
224
- static async fetchMetadata({
225
- clientId,
226
- fetch: fetchFn = globalThis.fetch,
227
- signal
228
- }) {
229
- signal?.throwIfAborted();
230
- const resp = await fetchFn(clientId, { redirect: "error", signal });
231
- if (resp.status !== 200) {
232
- resp.body?.cancel?.();
233
- throw new Error(`Failed to fetch client metadata: ${resp.status}`);
234
- }
235
- const mime = resp.headers.get("content-type")?.split(";")[0].trim();
236
- if (mime !== "application/json") {
237
- resp.body?.cancel?.();
238
- throw new Error(`Invalid client metadata content type: ${mime}`);
239
- }
240
- return resp.json();
241
- }
242
176
  instanceUrl;
243
177
  clientKey;
244
178
  storage;
245
179
  clientSecret;
246
180
  _fetch;
247
- sessionHooks;
248
181
  constructor(options) {
249
182
  this.instanceUrl = options.instanceUrl.replace(/\/+$/, "");
250
183
  this.clientKey = options.clientKey;
251
184
  this.clientSecret = options.clientSecret;
252
185
  this.storage = options.storage ?? new MemoryStorage();
253
186
  this._fetch = options.fetch ?? ((input, init) => fetch(input, init));
254
- this.sessionHooks = options.sessionHooks ?? {};
255
187
  }
256
188
  get isConfidential() {
257
189
  return this.clientSecret !== void 0;
@@ -333,30 +265,21 @@ var HappyViewOAuthClient = class {
333
265
  dpopKey: params.dpopKey,
334
266
  accessToken: params.accessToken,
335
267
  clientKey: this.clientKey,
336
- instanceUrl: this.instanceUrl,
337
- scopes: params.scopes,
338
- pdsUrl: params.pdsUrl,
339
- issuer: params.issuer
268
+ instanceUrl: this.instanceUrl
340
269
  };
341
270
  await this.storage.set(
342
271
  `${STORAGE_PREFIX}${data.did}`,
343
272
  JSON.stringify(storedSession)
344
273
  );
345
274
  await this.storage.set(LAST_ACTIVE_KEY, data.did);
346
- const session = new HappyViewSession({
275
+ return new HappyViewSession({
347
276
  did: data.did,
348
277
  dpopKey,
349
278
  accessToken: params.accessToken,
350
279
  clientKey: this.clientKey,
351
280
  instanceUrl: this.instanceUrl,
352
- scopes: params.scopes,
353
- pdsUrl: params.pdsUrl,
354
- issuer: params.issuer,
355
- fetch: this._fetch,
356
- onSignOut: () => this.deleteSession(data.did)
281
+ fetch: this._fetch
357
282
  });
358
- this.sessionHooks.onSessionUpdate?.(data.did);
359
- return session;
360
283
  }
361
284
  async deleteSession(did) {
362
285
  const session = await this.restoreSession(did);
@@ -379,7 +302,6 @@ var HappyViewOAuthClient = class {
379
302
  if (lastActive === did) {
380
303
  await this.storage.delete(LAST_ACTIVE_KEY);
381
304
  }
382
- this.sessionHooks.onSessionDelete?.(did);
383
305
  }
384
306
  async restoreSession(did) {
385
307
  const stored = await this.storage.get(`${STORAGE_PREFIX}${did}`);
@@ -392,11 +314,7 @@ var HappyViewOAuthClient = class {
392
314
  accessToken: data.accessToken,
393
315
  clientKey: data.clientKey,
394
316
  instanceUrl: data.instanceUrl,
395
- scopes: data.scopes,
396
- pdsUrl: data.pdsUrl,
397
- issuer: data.issuer,
398
- fetch: this._fetch,
399
- onSignOut: () => this.deleteSession(data.did)
317
+ fetch: this._fetch
400
318
  });
401
319
  }
402
320
  async restore() {
@@ -434,7 +352,6 @@ export {
434
352
  InvalidStateError,
435
353
  LAST_ACTIVE_KEY,
436
354
  MemoryStorage,
437
- OAuthCallbackError,
438
355
  ResolutionError,
439
356
  TokenExchangeError,
440
357
  importJwk
package/dist/index.js.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 * 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 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\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.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 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 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/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 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\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.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 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 };\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 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 fetch: this._fetch,\n });\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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyview/oauth-client",
3
- "version": "1.1.0-dev.2",
3
+ "version": "1.1.0",
4
4
  "description": "HappyView OAuth client for ATProto DPoP authentication",
5
5
  "license": "MIT",
6
6
  "repository": {