@atproto/oauth-client 0.5.13 → 0.6.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.
@@ -53,6 +53,7 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
53
53
  });
54
54
  Object.defineProperty(exports, "__esModule", { value: true });
55
55
  exports.SessionGetter = void 0;
56
+ exports.isExpectedSessionError = isExpectedSessionError;
56
57
  const simple_store_1 = require("@atproto-labs/simple-store");
57
58
  const auth_method_unsatisfiable_error_js_1 = require("./errors/auth-method-unsatisfiable-error.js");
58
59
  const token_invalid_error_js_1 = require("./errors/token-invalid-error.js");
@@ -60,6 +61,15 @@ const token_refresh_error_js_1 = require("./errors/token-refresh-error.js");
60
61
  const token_revoked_error_js_1 = require("./errors/token-revoked-error.js");
61
62
  const oauth_response_error_js_1 = require("./oauth-response-error.js");
62
63
  const util_js_1 = require("./util.js");
64
+ function isExpectedSessionError(err) {
65
+ return (err instanceof token_refresh_error_js_1.TokenRefreshError ||
66
+ err instanceof token_revoked_error_js_1.TokenRevokedError ||
67
+ err instanceof token_invalid_error_js_1.TokenInvalidError ||
68
+ err instanceof auth_method_unsatisfiable_error_js_1.AuthMethodUnsatisfiableError ||
69
+ // The stored session is invalid (e.g. missing properties) and cannot
70
+ // be used properly
71
+ err instanceof TypeError);
72
+ }
63
73
  /**
64
74
  * There are several advantages to wrapping the sessionStore in a (single)
65
75
  * CachedGetter, the main of which is that the cached getter will ensure that at
@@ -68,8 +78,8 @@ const util_js_1 = require("./util.js");
68
78
  * localStorage/indexedDB, will sync across multiple tabs (for a given sub).
69
79
  */
70
80
  class SessionGetter extends simple_store_1.CachedGetter {
71
- constructor(sessionStore, serverFactory, runtime) {
72
- super(async (sub, options, storedSession) => {
81
+ constructor(sessionStore, serverFactory, runtime, hooks = {}) {
82
+ super(async (sub, { signal }, storedSession) => {
73
83
  // There needs to be a previous session to be able to refresh. If
74
84
  // storedSession is undefined, it means that the store does not contain
75
85
  // a session for the given sub.
@@ -81,15 +91,13 @@ class SessionGetter extends simple_store_1.CachedGetter {
81
91
  // make sure an event is dispatched here if this occurs.
82
92
  const msg = 'The session was deleted by another process';
83
93
  const cause = new token_refresh_error_js_1.TokenRefreshError(sub, msg);
84
- this.dispatchEvent('deleted', { sub, cause });
94
+ await hooks.onDelete?.call(null, sub, cause);
85
95
  throw cause;
86
96
  }
87
- // From this point forward, throwing a TokenRefreshError will result in
88
- // this.delStored() being called, resulting in an event being
89
- // dispatched, even if the session was removed from the store through a
90
- // concurrent access (which, normally, should not happen if a proper
91
- // runtime lock was provided).
92
- const { dpopKey, authMethod = 'legacy', tokenSet } = storedSession;
97
+ // @NOTE Throwing a TokenRefreshError (or any other error class defined
98
+ // in the deleteOnError options) will result in this.delStored() being
99
+ // called.
100
+ const { dpopKey, authMethod, tokenSet } = storedSession;
93
101
  if (sub !== tokenSet.sub) {
94
102
  // Fool-proofing (e.g. against invalid session storage)
95
103
  throw new token_refresh_error_js_1.TokenRefreshError(sub, 'Stored session sub mismatch');
@@ -97,22 +105,13 @@ class SessionGetter extends simple_store_1.CachedGetter {
97
105
  if (!tokenSet.refresh_token) {
98
106
  throw new token_refresh_error_js_1.TokenRefreshError(sub, 'No refresh token available');
99
107
  }
100
- // Since refresh tokens can only be used once, we might run into
101
- // concurrency issues if multiple instances (e.g. browser tabs) are
102
- // trying to refresh the same token simultaneously. The chances of this
103
- // happening when multiple instances are started simultaneously is
104
- // reduced by randomizing the expiry time (see isStale() below). The
105
- // best solution is to use a mutex/lock to ensure that only one instance
106
- // is refreshing the token at a time (runtime.usingLock) but that is not
107
- // always possible. If no lock implementation is provided, we will use
108
- // the store to check if a concurrent refresh occurred.
109
108
  const server = await serverFactory.fromIssuer(tokenSet.iss, authMethod, dpopKey);
110
109
  // Because refresh tokens can only be used once, we must not use the
111
110
  // "signal" to abort the refresh, or throw any abort error beyond this
112
111
  // point. Any thrown error beyond this point will prevent the
113
112
  // TokenGetter from obtaining, and storing, the new token set,
114
113
  // effectively rendering the currently saved session unusable.
115
- options?.signal?.throwIfAborted();
114
+ signal?.throwIfAborted();
116
115
  try {
117
116
  const newTokenSet = await server.refresh(tokenSet);
118
117
  if (sub !== newTokenSet.sub) {
@@ -126,9 +125,16 @@ class SessionGetter extends simple_store_1.CachedGetter {
126
125
  };
127
126
  }
128
127
  catch (cause) {
129
- // If the refresh token is invalid, let's try to recover from
130
- // concurrency issues, or make sure the session is deleted by throwing
131
- // a TokenRefreshError.
128
+ // Since refresh tokens can only be used once, we might run into
129
+ // concurrency issues if multiple instances (e.g. browser tabs) are
130
+ // trying to refresh the same token simultaneously. The chances of
131
+ // this happening when multiple instances are started simultaneously
132
+ // is reduced by randomizing the expiry time (see isStale() below).
133
+ // The best solution is to use a mutex/lock to ensure that only one
134
+ // instance is refreshing the token at a time (runtime.usingLock) but
135
+ // that is not always possible. Let's try to recover from concurrency
136
+ // issues, or force the session to be deleted by throwing a
137
+ // TokenRefreshError.
132
138
  if (cause instanceof oauth_response_error_js_1.OAuthResponseError &&
133
139
  cause.status === 400 &&
134
140
  cause.error === 'invalid_grant') {
@@ -176,25 +182,26 @@ class SessionGetter extends simple_store_1.CachedGetter {
176
182
  // instances trying to refresh the token at the same.
177
183
  30e3 * Math.random());
178
184
  },
179
- onStoreError: async (err, sub, { tokenSet, dpopKey, authMethod = 'legacy' }) => {
180
- if (!(err instanceof auth_method_unsatisfiable_error_js_1.AuthMethodUnsatisfiableError)) {
181
- // If the error was an AuthMethodUnsatisfiableError, there is no
182
- // point in trying to call `fromIssuer`.
183
- try {
184
- // If the token data cannot be stored, let's revoke it
185
- const server = await serverFactory.fromIssuer(tokenSet.iss, authMethod, dpopKey);
186
- await server.revoke(tokenSet.refresh_token ?? tokenSet.access_token);
187
- }
188
- catch {
189
- // Let the original error propagate
190
- }
185
+ onStoreError: async (err, sub, { tokenSet, dpopKey, authMethod }) => {
186
+ // If the token data cannot be stored, let's revoke it
187
+ try {
188
+ const server = await serverFactory.fromIssuer(tokenSet.iss, authMethod, dpopKey);
189
+ await server.revoke(tokenSet.refresh_token ?? tokenSet.access_token);
190
+ }
191
+ catch {
192
+ // At least we tried...
193
+ }
194
+ // Attempt to delete the session from the store. Note that this might
195
+ // fail if the store is not available, which is fine.
196
+ try {
197
+ await this.delStored(sub, err);
198
+ }
199
+ catch {
200
+ // Ignore (better to propagate the original storage error)
191
201
  }
192
202
  throw err;
193
203
  },
194
- deleteOnError: async (err) => err instanceof token_refresh_error_js_1.TokenRefreshError ||
195
- err instanceof token_revoked_error_js_1.TokenRevokedError ||
196
- err instanceof token_invalid_error_js_1.TokenInvalidError ||
197
- err instanceof auth_method_unsatisfiable_error_js_1.AuthMethodUnsatisfiableError,
204
+ deleteOnError: isExpectedSessionError,
198
205
  });
199
206
  Object.defineProperty(this, "runtime", {
200
207
  enumerable: true,
@@ -202,21 +209,15 @@ class SessionGetter extends simple_store_1.CachedGetter {
202
209
  writable: true,
203
210
  value: runtime
204
211
  });
205
- Object.defineProperty(this, "eventTarget", {
212
+ Object.defineProperty(this, "hooks", {
206
213
  enumerable: true,
207
214
  configurable: true,
208
215
  writable: true,
209
- value: new util_js_1.CustomEventTarget()
216
+ value: hooks
210
217
  });
211
218
  }
212
- addEventListener(type, callback, options) {
213
- this.eventTarget.addEventListener(type, callback, options);
214
- }
215
- removeEventListener(type, callback, options) {
216
- this.eventTarget.removeEventListener(type, callback, options);
217
- }
218
- dispatchEvent(type, detail) {
219
- return this.eventTarget.dispatchCustomEvent(type, detail);
219
+ async getStored(sub, options) {
220
+ return super.getStored(sub, options);
220
221
  }
221
222
  async setStored(sub, session) {
222
223
  // Prevent tampering with the stored value
@@ -224,24 +225,16 @@ class SessionGetter extends simple_store_1.CachedGetter {
224
225
  throw new TypeError('Token set does not match the expected sub');
225
226
  }
226
227
  await super.setStored(sub, session);
227
- this.dispatchEvent('updated', { sub, ...session });
228
+ await this.hooks.onUpdate?.call(null, sub, session);
228
229
  }
229
230
  async delStored(sub, cause) {
230
231
  await super.delStored(sub, cause);
231
- this.dispatchEvent('deleted', { sub, cause });
232
+ await this.hooks.onDelete?.call(null, sub, cause);
232
233
  }
233
234
  /**
234
- * @param refresh When `true`, the credentials will be refreshed even if they
235
- * are not expired. When `false`, the credentials will not be refreshed even
236
- * if they are expired. When `undefined`, the credentials will be refreshed
237
- * if, and only if, they are (about to be) expired. Defaults to `undefined`.
235
+ * @deprecated Use {@link getSession} instead
236
+ * @internal (not really deprecated)
238
237
  */
239
- async getSession(sub, refresh = 'auto') {
240
- return this.get(sub, {
241
- noCache: refresh === true,
242
- allowStale: refresh === false,
243
- });
244
- }
245
238
  async get(sub, options) {
246
239
  const session = await this.runtime.usingLock(`@atproto-oauth-client-${sub}`, async () => {
247
240
  const env_1 = { stack: [], error: void 0, hasError: false };
@@ -269,6 +262,18 @@ class SessionGetter extends simple_store_1.CachedGetter {
269
262
  }
270
263
  return session;
271
264
  }
265
+ /**
266
+ * @param refresh When `true`, the credentials will be refreshed even if they
267
+ * are not expired. When `false`, the credentials will not be refreshed even
268
+ * if they are expired. When `undefined`, the credentials will be refreshed
269
+ * if, and only if, they are (about to be) expired. Defaults to `undefined`.
270
+ */
271
+ async getSession(sub, refresh = 'auto') {
272
+ return this.get(sub, {
273
+ noCache: refresh === true,
274
+ allowStale: refresh === false,
275
+ });
276
+ }
272
277
  }
273
278
  exports.SessionGetter = SessionGetter;
274
279
  //# sourceMappingURL=session-getter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"session-getter.js","sourceRoot":"","sources":["../src/session-getter.ts"],"names":[],"mappings":"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","sourcesContent":["import { AtprotoDid } from '@atproto/did'\nimport { Key } from '@atproto/jwk'\nimport {\n CachedGetter,\n GetCachedOptions,\n SimpleStore,\n} from '@atproto-labs/simple-store'\nimport { AuthMethodUnsatisfiableError } from './errors/auth-method-unsatisfiable-error.js'\nimport { TokenInvalidError } from './errors/token-invalid-error.js'\nimport { TokenRefreshError } from './errors/token-refresh-error.js'\nimport { TokenRevokedError } from './errors/token-revoked-error.js'\nimport { ClientAuthMethod } from './oauth-client-auth.js'\nimport { OAuthResponseError } from './oauth-response-error.js'\nimport { TokenSet } from './oauth-server-agent.js'\nimport { OAuthServerFactory } from './oauth-server-factory.js'\nimport { Runtime } from './runtime.js'\nimport { CustomEventTarget, combineSignals } from './util.js'\n\nexport type Session = {\n dpopKey: Key\n /**\n * Previous implementation of this lib did not define an `authMethod`\n */\n authMethod?: ClientAuthMethod\n tokenSet: TokenSet\n}\n\nexport type SessionStore = SimpleStore<string, Session>\n\nexport type SessionEventMap = {\n updated: {\n sub: string\n } & Session\n deleted: {\n sub: string\n cause: TokenRefreshError | TokenRevokedError | TokenInvalidError | unknown\n }\n}\n\nexport type SessionEventListener<\n T extends keyof SessionEventMap = keyof SessionEventMap,\n> = (event: CustomEvent<SessionEventMap[T]>) => void\n\n/**\n * There are several advantages to wrapping the sessionStore in a (single)\n * CachedGetter, the main of which is that the cached getter will ensure that at\n * most one fresh call is ever being made. Another advantage, is that it\n * contains the logic for reading from the cache which, if the cache is based on\n * localStorage/indexedDB, will sync across multiple tabs (for a given sub).\n */\nexport class SessionGetter extends CachedGetter<AtprotoDid, Session> {\n private readonly eventTarget = new CustomEventTarget<SessionEventMap>()\n\n constructor(\n sessionStore: SessionStore,\n serverFactory: OAuthServerFactory,\n private readonly runtime: Runtime,\n ) {\n super(\n async (sub, options, storedSession) => {\n // There needs to be a previous session to be able to refresh. If\n // storedSession is undefined, it means that the store does not contain\n // a session for the given sub.\n if (storedSession === undefined) {\n // Because the session is not in the store, this.delStored() method\n // will not be called by the CachedGetter class (because there is\n // nothing to delete). This would typically happen if there is no\n // synchronization mechanism between instances of this class. Let's\n // make sure an event is dispatched here if this occurs.\n const msg = 'The session was deleted by another process'\n const cause = new TokenRefreshError(sub, msg)\n this.dispatchEvent('deleted', { sub, cause })\n throw cause\n }\n\n // From this point forward, throwing a TokenRefreshError will result in\n // this.delStored() being called, resulting in an event being\n // dispatched, even if the session was removed from the store through a\n // concurrent access (which, normally, should not happen if a proper\n // runtime lock was provided).\n\n const { dpopKey, authMethod = 'legacy', tokenSet } = storedSession\n\n if (sub !== tokenSet.sub) {\n // Fool-proofing (e.g. against invalid session storage)\n throw new TokenRefreshError(sub, 'Stored session sub mismatch')\n }\n\n if (!tokenSet.refresh_token) {\n throw new TokenRefreshError(sub, 'No refresh token available')\n }\n\n // Since refresh tokens can only be used once, we might run into\n // concurrency issues if multiple instances (e.g. browser tabs) are\n // trying to refresh the same token simultaneously. The chances of this\n // happening when multiple instances are started simultaneously is\n // reduced by randomizing the expiry time (see isStale() below). The\n // best solution is to use a mutex/lock to ensure that only one instance\n // is refreshing the token at a time (runtime.usingLock) but that is not\n // always possible. If no lock implementation is provided, we will use\n // the store to check if a concurrent refresh occurred.\n\n const server = await serverFactory.fromIssuer(\n tokenSet.iss,\n authMethod,\n dpopKey,\n )\n\n // Because refresh tokens can only be used once, we must not use the\n // \"signal\" to abort the refresh, or throw any abort error beyond this\n // point. Any thrown error beyond this point will prevent the\n // TokenGetter from obtaining, and storing, the new token set,\n // effectively rendering the currently saved session unusable.\n options?.signal?.throwIfAborted()\n\n try {\n const newTokenSet = await server.refresh(tokenSet)\n\n if (sub !== newTokenSet.sub) {\n // The server returned another sub. Was the tokenSet manipulated?\n throw new TokenRefreshError(sub, 'Token set sub mismatch')\n }\n\n return {\n dpopKey,\n tokenSet: newTokenSet,\n authMethod: server.authMethod,\n }\n } catch (cause) {\n // If the refresh token is invalid, let's try to recover from\n // concurrency issues, or make sure the session is deleted by throwing\n // a TokenRefreshError.\n if (\n cause instanceof OAuthResponseError &&\n cause.status === 400 &&\n cause.error === 'invalid_grant'\n ) {\n // In case there is no lock implementation in the runtime, we will\n // wait for a short time to give the other concurrent instances a\n // chance to finish their refreshing of the token. If a concurrent\n // refresh did occur, we will pretend that this one succeeded.\n if (!runtime.hasImplementationLock) {\n await new Promise((r) => setTimeout(r, 1000))\n\n const stored = await this.getStored(sub)\n if (stored === undefined) {\n // A concurrent refresh occurred and caused the session to be\n // deleted (for a reason we can't know at this point).\n\n // Using a distinct error message mainly for debugging\n // purposes. Also, throwing a TokenRefreshError to trigger\n // deletion through the deleteOnError callback.\n const msg = 'The session was deleted by another process'\n throw new TokenRefreshError(sub, msg, { cause })\n } else if (\n stored.tokenSet.access_token !== tokenSet.access_token ||\n stored.tokenSet.refresh_token !== tokenSet.refresh_token\n ) {\n // A concurrent refresh occurred. Pretend this one succeeded.\n return stored\n } else {\n // There were no concurrent refresh. The token is (likely)\n // simply no longer valid.\n }\n }\n\n // Make sure the session gets deleted from the store\n const msg = cause.errorDescription ?? 'The session was revoked'\n throw new TokenRefreshError(sub, msg, { cause })\n }\n\n throw cause\n }\n },\n sessionStore,\n {\n isStale: (sub, { tokenSet }) => {\n return (\n tokenSet.expires_at != null &&\n new Date(tokenSet.expires_at).getTime() <\n Date.now() +\n // Add some lee way to ensure the token is not expired when it\n // reaches the server.\n 10e3 +\n // Add some randomness to reduce the chances of multiple\n // instances trying to refresh the token at the same.\n 30e3 * Math.random()\n )\n },\n onStoreError: async (\n err,\n sub,\n { tokenSet, dpopKey, authMethod = 'legacy' as const },\n ) => {\n if (!(err instanceof AuthMethodUnsatisfiableError)) {\n // If the error was an AuthMethodUnsatisfiableError, there is no\n // point in trying to call `fromIssuer`.\n try {\n // If the token data cannot be stored, let's revoke it\n const server = await serverFactory.fromIssuer(\n tokenSet.iss,\n authMethod,\n dpopKey,\n )\n await server.revoke(\n tokenSet.refresh_token ?? tokenSet.access_token,\n )\n } catch {\n // Let the original error propagate\n }\n }\n\n throw err\n },\n deleteOnError: async (err) =>\n err instanceof TokenRefreshError ||\n err instanceof TokenRevokedError ||\n err instanceof TokenInvalidError ||\n err instanceof AuthMethodUnsatisfiableError,\n },\n )\n }\n\n addEventListener<T extends keyof SessionEventMap>(\n type: T,\n callback: SessionEventListener<T>,\n options?: AddEventListenerOptions | boolean,\n ) {\n this.eventTarget.addEventListener(type, callback, options)\n }\n\n removeEventListener<T extends keyof SessionEventMap>(\n type: T,\n callback: SessionEventListener<T>,\n options?: EventListenerOptions | boolean,\n ) {\n this.eventTarget.removeEventListener(type, callback, options)\n }\n\n dispatchEvent<T extends keyof SessionEventMap>(\n type: T,\n detail: SessionEventMap[T],\n ): boolean {\n return this.eventTarget.dispatchCustomEvent(type, detail)\n }\n\n async setStored(sub: string, session: Session) {\n // Prevent tampering with the stored value\n if (sub !== session.tokenSet.sub) {\n throw new TypeError('Token set does not match the expected sub')\n }\n await super.setStored(sub, session)\n this.dispatchEvent('updated', { sub, ...session })\n }\n\n override async delStored(sub: AtprotoDid, cause?: unknown): Promise<void> {\n await super.delStored(sub, cause)\n this.dispatchEvent('deleted', { sub, cause })\n }\n\n /**\n * @param refresh When `true`, the credentials will be refreshed even if they\n * are not expired. When `false`, the credentials will not be refreshed even\n * if they are expired. When `undefined`, the credentials will be refreshed\n * if, and only if, they are (about to be) expired. Defaults to `undefined`.\n */\n async getSession(sub: AtprotoDid, refresh: boolean | 'auto' = 'auto') {\n return this.get(sub, {\n noCache: refresh === true,\n allowStale: refresh === false,\n })\n }\n\n async get(sub: AtprotoDid, options?: GetCachedOptions): Promise<Session> {\n const session = await this.runtime.usingLock(\n `@atproto-oauth-client-${sub}`,\n async () => {\n // Make sure, even if there is no signal in the options, that the\n // request will be cancelled after at most 30 seconds.\n const signal = AbortSignal.timeout(30e3)\n\n using abortController = combineSignals([options?.signal, signal])\n\n return await super.get(sub, {\n ...options,\n signal: abortController.signal,\n })\n },\n )\n\n if (sub !== session.tokenSet.sub) {\n // Fool-proofing (e.g. against invalid session storage)\n throw new Error('Token set does not match the expected sub')\n }\n\n return session\n }\n}\n"]}
1
+ {"version":3,"file":"session-getter.js","sourceRoot":"","sources":["../src/session-getter.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,wDAUC;AA3CD,6DAKmC;AACnC,oGAA0F;AAC1F,4EAAmE;AACnE,4EAAmE;AACnE,4EAAmE;AAEnE,uEAA8D;AAI9D,uCAA0C;AAkB1C,SAAgB,sBAAsB,CAAC,GAAY;IACjD,OAAO,CACL,GAAG,YAAY,0CAAiB;QAChC,GAAG,YAAY,0CAAiB;QAChC,GAAG,YAAY,0CAAiB;QAChC,GAAG,YAAY,iEAA4B;QAC3C,qEAAqE;QACrE,mBAAmB;QACnB,GAAG,YAAY,SAAS,CACzB,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAa,aAAc,SAAQ,2BAAiC;IAClE,YACE,YAA0B,EAC1B,aAAiC,EAChB,OAAgB,EAChB,QAAsB,EAAE;QAEzC,KAAK,CACH,KAAK,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,EAAE,aAAa,EAAE,EAAE;YACvC,iEAAiE;YACjE,uEAAuE;YACvE,+BAA+B;YAC/B,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;gBAChC,mEAAmE;gBACnE,iEAAiE;gBACjE,iEAAiE;gBACjE,mEAAmE;gBACnE,wDAAwD;gBACxD,MAAM,GAAG,GAAG,4CAA4C,CAAA;gBACxD,MAAM,KAAK,GAAG,IAAI,0CAAiB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;gBAC7C,MAAM,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAA;gBAC5C,MAAM,KAAK,CAAA;YACb,CAAC;YAED,uEAAuE;YACvE,sEAAsE;YACtE,UAAU;YAEV,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,aAAa,CAAA;YAEvD,IAAI,GAAG,KAAK,QAAQ,CAAC,GAAG,EAAE,CAAC;gBACzB,uDAAuD;gBACvD,MAAM,IAAI,0CAAiB,CAAC,GAAG,EAAE,6BAA6B,CAAC,CAAA;YACjE,CAAC;YAED,IAAI,CAAC,QAAQ,CAAC,aAAa,EAAE,CAAC;gBAC5B,MAAM,IAAI,0CAAiB,CAAC,GAAG,EAAE,4BAA4B,CAAC,CAAA;YAChE,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,UAAU,CAC3C,QAAQ,CAAC,GAAG,EACZ,UAAU,EACV,OAAO,CACR,CAAA;YAED,oEAAoE;YACpE,sEAAsE;YACtE,6DAA6D;YAC7D,8DAA8D;YAC9D,8DAA8D;YAC9D,MAAM,EAAE,cAAc,EAAE,CAAA;YAExB,IAAI,CAAC;gBACH,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;gBAElD,IAAI,GAAG,KAAK,WAAW,CAAC,GAAG,EAAE,CAAC;oBAC5B,iEAAiE;oBACjE,MAAM,IAAI,0CAAiB,CAAC,GAAG,EAAE,wBAAwB,CAAC,CAAA;gBAC5D,CAAC;gBAED,OAAO;oBACL,OAAO;oBACP,QAAQ,EAAE,WAAW;oBACrB,UAAU,EAAE,MAAM,CAAC,UAAU;iBAC9B,CAAA;YACH,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,gEAAgE;gBAChE,mEAAmE;gBACnE,kEAAkE;gBAClE,oEAAoE;gBACpE,mEAAmE;gBACnE,mEAAmE;gBACnE,qEAAqE;gBACrE,qEAAqE;gBACrE,2DAA2D;gBAC3D,qBAAqB;gBACrB,IACE,KAAK,YAAY,4CAAkB;oBACnC,KAAK,CAAC,MAAM,KAAK,GAAG;oBACpB,KAAK,CAAC,KAAK,KAAK,eAAe,EAC/B,CAAC;oBACD,kEAAkE;oBAClE,iEAAiE;oBACjE,kEAAkE;oBAClE,8DAA8D;oBAC9D,IAAI,CAAC,OAAO,CAAC,qBAAqB,EAAE,CAAC;wBACnC,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAA;wBAE7C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAA;wBACxC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;4BACzB,6DAA6D;4BAC7D,sDAAsD;4BAEtD,sDAAsD;4BACtD,0DAA0D;4BAC1D,+CAA+C;4BAC/C,MAAM,GAAG,GAAG,4CAA4C,CAAA;4BACxD,MAAM,IAAI,0CAAiB,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,CAAA;wBAClD,CAAC;6BAAM,IACL,MAAM,CAAC,QAAQ,CAAC,YAAY,KAAK,QAAQ,CAAC,YAAY;4BACtD,MAAM,CAAC,QAAQ,CAAC,aAAa,KAAK,QAAQ,CAAC,aAAa,EACxD,CAAC;4BACD,6DAA6D;4BAC7D,OAAO,MAAM,CAAA;wBACf,CAAC;6BAAM,CAAC;4BACN,0DAA0D;4BAC1D,0BAA0B;wBAC5B,CAAC;oBACH,CAAC;oBAED,oDAAoD;oBACpD,MAAM,GAAG,GAAG,KAAK,CAAC,gBAAgB,IAAI,yBAAyB,CAAA;oBAC/D,MAAM,IAAI,0CAAiB,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,CAAA;gBAClD,CAAC;gBAED,MAAM,KAAK,CAAA;YACb,CAAC;QACH,CAAC,EACD,YAAY,EACZ;YACE,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE;gBAC7B,OAAO,CACL,QAAQ,CAAC,UAAU,IAAI,IAAI;oBAC3B,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE;wBACrC,IAAI,CAAC,GAAG,EAAE;4BACR,8DAA8D;4BAC9D,sBAAsB;4BACtB,IAAI;4BACJ,wDAAwD;4BACxD,qDAAqD;4BACrD,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,CACzB,CAAA;YACH,CAAC;YACD,YAAY,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE;gBAClE,sDAAsD;gBACtD,IAAI,CAAC;oBACH,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,UAAU,CAC3C,QAAQ,CAAC,GAAG,EACZ,UAAU,EACV,OAAO,CACR,CAAA;oBACD,MAAM,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,IAAI,QAAQ,CAAC,YAAY,CAAC,CAAA;gBACtE,CAAC;gBAAC,MAAM,CAAC;oBACP,uBAAuB;gBACzB,CAAC;gBAED,qEAAqE;gBACrE,qDAAqD;gBACrD,IAAI,CAAC;oBACH,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;gBAChC,CAAC;gBAAC,MAAM,CAAC;oBACP,0DAA0D;gBAC5D,CAAC;gBAED,MAAM,GAAG,CAAA;YACX,CAAC;YACD,aAAa,EAAE,sBAAsB;SACtC,CACF,CAAA;QA1JD;;;;mBAAiB,OAAO;WAAS;QACjC;;;;mBAAiB,KAAK;WAAmB;IA0J3C,CAAC;IAEQ,KAAK,CAAC,SAAS,CACtB,GAAe,EACf,OAAoB;QAEpB,OAAO,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IACtC,CAAC;IAEQ,KAAK,CAAC,SAAS,CAAC,GAAe,EAAE,OAAgB;QACxD,0CAA0C;QAC1C,IAAI,GAAG,KAAK,OAAO,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC;YACjC,MAAM,IAAI,SAAS,CAAC,2CAA2C,CAAC,CAAA;QAClE,CAAC;QACD,MAAM,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;QACnC,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO,CAAC,CAAA;IACrD,CAAC;IAEQ,KAAK,CAAC,SAAS,CAAC,GAAe,EAAE,KAAe;QACvD,MAAM,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;QACjC,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAA;IACnD,CAAC;IAED;;;OAGG;IACM,KAAK,CAAC,GAAG,CAChB,GAAe,EACf,OAA0B;QAE1B,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,CAC1C,yBAAyB,GAAG,EAAE,EAC9B,KAAK,IAAI,EAAE;;;gBACT,iEAAiE;gBACjE,sDAAsD;gBACtD,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;gBAExC,MAAM,eAAe,kCAAG,IAAA,wBAAc,EAAC,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,QAAA,CAAA;gBAEjE,OAAO,MAAM,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE;oBAC1B,GAAG,OAAO;oBACV,MAAM,EAAE,eAAe,CAAC,MAAM;iBAC/B,CAAC,CAAA;;;;;;;;;SACH,CACF,CAAA;QAED,IAAI,GAAG,KAAK,OAAO,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC;YACjC,uDAAuD;YACvD,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAA;QAC9D,CAAC;QAED,OAAO,OAAO,CAAA;IAChB,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,UAAU,CAAC,GAAe,EAAE,UAA4B,MAAM;QAClE,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE;YACnB,OAAO,EAAE,OAAO,KAAK,IAAI;YACzB,UAAU,EAAE,OAAO,KAAK,KAAK;SAC9B,CAAC,CAAA;IACJ,CAAC;CACF;AAlOD,sCAkOC","sourcesContent":["import { AtprotoDid } from '@atproto/did'\nimport { Key } from '@atproto/jwk'\nimport {\n CachedGetter,\n GetCachedOptions,\n GetOptions,\n SimpleStore,\n} from '@atproto-labs/simple-store'\nimport { AuthMethodUnsatisfiableError } from './errors/auth-method-unsatisfiable-error.js'\nimport { TokenInvalidError } from './errors/token-invalid-error.js'\nimport { TokenRefreshError } from './errors/token-refresh-error.js'\nimport { TokenRevokedError } from './errors/token-revoked-error.js'\nimport { ClientAuthMethod } from './oauth-client-auth.js'\nimport { OAuthResponseError } from './oauth-response-error.js'\nimport { TokenSet } from './oauth-server-agent.js'\nimport { OAuthServerFactory } from './oauth-server-factory.js'\nimport { Runtime } from './runtime.js'\nimport { combineSignals } from './util.js'\n\nexport type Session = {\n dpopKey: Key\n authMethod: ClientAuthMethod\n tokenSet: TokenSet\n}\n\nexport type SessionStore = SimpleStore<string, Session>\n\nexport type SessionHooks = {\n onUpdate?: (sub: AtprotoDid, session: Session) => void\n onDelete?: (\n sub: AtprotoDid,\n cause: TokenRefreshError | TokenRevokedError | TokenInvalidError | unknown,\n ) => void\n}\n\nexport function isExpectedSessionError(err: unknown) {\n return (\n err instanceof TokenRefreshError ||\n err instanceof TokenRevokedError ||\n err instanceof TokenInvalidError ||\n err instanceof AuthMethodUnsatisfiableError ||\n // The stored session is invalid (e.g. missing properties) and cannot\n // be used properly\n err instanceof TypeError\n )\n}\n\n/**\n * There are several advantages to wrapping the sessionStore in a (single)\n * CachedGetter, the main of which is that the cached getter will ensure that at\n * most one fresh call is ever being made. Another advantage, is that it\n * contains the logic for reading from the cache which, if the cache is based on\n * localStorage/indexedDB, will sync across multiple tabs (for a given sub).\n */\nexport class SessionGetter extends CachedGetter<AtprotoDid, Session> {\n constructor(\n sessionStore: SessionStore,\n serverFactory: OAuthServerFactory,\n private readonly runtime: Runtime,\n private readonly hooks: SessionHooks = {},\n ) {\n super(\n async (sub, { signal }, storedSession) => {\n // There needs to be a previous session to be able to refresh. If\n // storedSession is undefined, it means that the store does not contain\n // a session for the given sub.\n if (storedSession === undefined) {\n // Because the session is not in the store, this.delStored() method\n // will not be called by the CachedGetter class (because there is\n // nothing to delete). This would typically happen if there is no\n // synchronization mechanism between instances of this class. Let's\n // make sure an event is dispatched here if this occurs.\n const msg = 'The session was deleted by another process'\n const cause = new TokenRefreshError(sub, msg)\n await hooks.onDelete?.call(null, sub, cause)\n throw cause\n }\n\n // @NOTE Throwing a TokenRefreshError (or any other error class defined\n // in the deleteOnError options) will result in this.delStored() being\n // called.\n\n const { dpopKey, authMethod, tokenSet } = storedSession\n\n if (sub !== tokenSet.sub) {\n // Fool-proofing (e.g. against invalid session storage)\n throw new TokenRefreshError(sub, 'Stored session sub mismatch')\n }\n\n if (!tokenSet.refresh_token) {\n throw new TokenRefreshError(sub, 'No refresh token available')\n }\n\n const server = await serverFactory.fromIssuer(\n tokenSet.iss,\n authMethod,\n dpopKey,\n )\n\n // Because refresh tokens can only be used once, we must not use the\n // \"signal\" to abort the refresh, or throw any abort error beyond this\n // point. Any thrown error beyond this point will prevent the\n // TokenGetter from obtaining, and storing, the new token set,\n // effectively rendering the currently saved session unusable.\n signal?.throwIfAborted()\n\n try {\n const newTokenSet = await server.refresh(tokenSet)\n\n if (sub !== newTokenSet.sub) {\n // The server returned another sub. Was the tokenSet manipulated?\n throw new TokenRefreshError(sub, 'Token set sub mismatch')\n }\n\n return {\n dpopKey,\n tokenSet: newTokenSet,\n authMethod: server.authMethod,\n }\n } catch (cause) {\n // Since refresh tokens can only be used once, we might run into\n // concurrency issues if multiple instances (e.g. browser tabs) are\n // trying to refresh the same token simultaneously. The chances of\n // this happening when multiple instances are started simultaneously\n // is reduced by randomizing the expiry time (see isStale() below).\n // The best solution is to use a mutex/lock to ensure that only one\n // instance is refreshing the token at a time (runtime.usingLock) but\n // that is not always possible. Let's try to recover from concurrency\n // issues, or force the session to be deleted by throwing a\n // TokenRefreshError.\n if (\n cause instanceof OAuthResponseError &&\n cause.status === 400 &&\n cause.error === 'invalid_grant'\n ) {\n // In case there is no lock implementation in the runtime, we will\n // wait for a short time to give the other concurrent instances a\n // chance to finish their refreshing of the token. If a concurrent\n // refresh did occur, we will pretend that this one succeeded.\n if (!runtime.hasImplementationLock) {\n await new Promise((r) => setTimeout(r, 1000))\n\n const stored = await this.getStored(sub)\n if (stored === undefined) {\n // A concurrent refresh occurred and caused the session to be\n // deleted (for a reason we can't know at this point).\n\n // Using a distinct error message mainly for debugging\n // purposes. Also, throwing a TokenRefreshError to trigger\n // deletion through the deleteOnError callback.\n const msg = 'The session was deleted by another process'\n throw new TokenRefreshError(sub, msg, { cause })\n } else if (\n stored.tokenSet.access_token !== tokenSet.access_token ||\n stored.tokenSet.refresh_token !== tokenSet.refresh_token\n ) {\n // A concurrent refresh occurred. Pretend this one succeeded.\n return stored\n } else {\n // There were no concurrent refresh. The token is (likely)\n // simply no longer valid.\n }\n }\n\n // Make sure the session gets deleted from the store\n const msg = cause.errorDescription ?? 'The session was revoked'\n throw new TokenRefreshError(sub, msg, { cause })\n }\n\n throw cause\n }\n },\n sessionStore,\n {\n isStale: (sub, { tokenSet }) => {\n return (\n tokenSet.expires_at != null &&\n new Date(tokenSet.expires_at).getTime() <\n Date.now() +\n // Add some lee way to ensure the token is not expired when it\n // reaches the server.\n 10e3 +\n // Add some randomness to reduce the chances of multiple\n // instances trying to refresh the token at the same.\n 30e3 * Math.random()\n )\n },\n onStoreError: async (err, sub, { tokenSet, dpopKey, authMethod }) => {\n // If the token data cannot be stored, let's revoke it\n try {\n const server = await serverFactory.fromIssuer(\n tokenSet.iss,\n authMethod,\n dpopKey,\n )\n await server.revoke(tokenSet.refresh_token ?? tokenSet.access_token)\n } catch {\n // At least we tried...\n }\n\n // Attempt to delete the session from the store. Note that this might\n // fail if the store is not available, which is fine.\n try {\n await this.delStored(sub, err)\n } catch {\n // Ignore (better to propagate the original storage error)\n }\n\n throw err\n },\n deleteOnError: isExpectedSessionError,\n },\n )\n }\n\n override async getStored(\n sub: AtprotoDid,\n options?: GetOptions,\n ): Promise<Session | undefined> {\n return super.getStored(sub, options)\n }\n\n override async setStored(sub: AtprotoDid, session: Session) {\n // Prevent tampering with the stored value\n if (sub !== session.tokenSet.sub) {\n throw new TypeError('Token set does not match the expected sub')\n }\n await super.setStored(sub, session)\n await this.hooks.onUpdate?.call(null, sub, session)\n }\n\n override async delStored(sub: AtprotoDid, cause?: unknown): Promise<void> {\n await super.delStored(sub, cause)\n await this.hooks.onDelete?.call(null, sub, cause)\n }\n\n /**\n * @deprecated Use {@link getSession} instead\n * @internal (not really deprecated)\n */\n override async get(\n sub: AtprotoDid,\n options?: GetCachedOptions,\n ): Promise<Session> {\n const session = await this.runtime.usingLock(\n `@atproto-oauth-client-${sub}`,\n async () => {\n // Make sure, even if there is no signal in the options, that the\n // request will be cancelled after at most 30 seconds.\n const signal = AbortSignal.timeout(30e3)\n\n using abortController = combineSignals([options?.signal, signal])\n\n return await super.get(sub, {\n ...options,\n signal: abortController.signal,\n })\n },\n )\n\n if (sub !== session.tokenSet.sub) {\n // Fool-proofing (e.g. against invalid session storage)\n throw new Error('Token set does not match the expected sub')\n }\n\n return session\n }\n\n /**\n * @param refresh When `true`, the credentials will be refreshed even if they\n * are not expired. When `false`, the credentials will not be refreshed even\n * if they are expired. When `undefined`, the credentials will be refreshed\n * if, and only if, they are (about to be) expired. Defaults to `undefined`.\n */\n async getSession(sub: AtprotoDid, refresh: boolean | 'auto' = 'auto') {\n return this.get(sub, {\n noCache: refresh === true,\n allowStale: refresh === false,\n })\n }\n}\n"]}
@@ -4,10 +4,20 @@ import { ClientAuthMethod } from './oauth-client-auth.js';
4
4
  export type InternalStateData = {
5
5
  iss: string;
6
6
  dpopKey: Key;
7
- /** @note optional for legacy reasons */
8
- authMethod?: ClientAuthMethod;
9
- verifier?: string;
7
+ authMethod: ClientAuthMethod;
8
+ verifier: string;
10
9
  appState?: string;
11
10
  };
11
+ /**
12
+ * A store pending oauth authorization flows. The key is the "state" parameter
13
+ * used in the authorization request, and the value is an object containing the
14
+ * necessary information to complete the flow once the user is redirected back
15
+ * to the client.
16
+ *
17
+ * @note The data stored in this store is typically short-lived. It should be
18
+ * automatically cleared after a certain period of time (e.g. 1 hour) to prevent
19
+ * the store from growing indefinitely. It is up to the implementation to
20
+ * implement this cleanup mechanism.
21
+ */
12
22
  export type StateStore = SimpleStore<string, InternalStateData>;
13
23
  //# sourceMappingURL=state-store.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"state-store.d.ts","sourceRoot":"","sources":["../src/state-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAClC,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAA;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAEzD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,GAAG,CAAA;IACZ,wCAAwC;IACxC,UAAU,CAAC,EAAE,gBAAgB,CAAA;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG,WAAW,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAA"}
1
+ {"version":3,"file":"state-store.d.ts","sourceRoot":"","sources":["../src/state-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAClC,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAA;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAEzD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,GAAG,CAAA;IACZ,UAAU,EAAE,gBAAgB,CAAA;IAC5B,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,CAAA;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,UAAU,GAAG,WAAW,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"state-store.js","sourceRoot":"","sources":["../src/state-store.ts"],"names":[],"mappings":"","sourcesContent":["import { Key } from '@atproto/jwk'\nimport { SimpleStore } from '@atproto-labs/simple-store'\nimport { ClientAuthMethod } from './oauth-client-auth.js'\n\nexport type InternalStateData = {\n iss: string\n dpopKey: Key\n /** @note optional for legacy reasons */\n authMethod?: ClientAuthMethod\n verifier?: string\n appState?: string\n}\n\nexport type StateStore = SimpleStore<string, InternalStateData>\n"]}
1
+ {"version":3,"file":"state-store.js","sourceRoot":"","sources":["../src/state-store.ts"],"names":[],"mappings":"","sourcesContent":["import { Key } from '@atproto/jwk'\nimport { SimpleStore } from '@atproto-labs/simple-store'\nimport { ClientAuthMethod } from './oauth-client-auth.js'\n\nexport type InternalStateData = {\n iss: string\n dpopKey: Key\n authMethod: ClientAuthMethod\n verifier: string\n appState?: string\n}\n\n/**\n * A store pending oauth authorization flows. The key is the \"state\" parameter\n * used in the authorization request, and the value is an object containing the\n * necessary information to complete the flow once the user is redirected back\n * to the client.\n *\n * @note The data stored in this store is typically short-lived. It should be\n * automatically cleared after a certain period of time (e.g. 1 hour) to prevent\n * the store from growing indefinitely. It is up to the implementation to\n * implement this cleanup mechanism.\n */\nexport type StateStore = SimpleStore<string, InternalStateData>\n"]}
package/dist/util.d.ts CHANGED
@@ -4,15 +4,5 @@ export type Simplify<T> = {
4
4
  } & NonNullable<unknown>;
5
5
  export declare const ifString: <V>(v: V) => (V & string) | undefined;
6
6
  export declare function contentMime(headers: Headers): string | undefined;
7
- /**
8
- * Ponyfill for `CustomEvent` constructor.
9
- */
10
- export declare const CustomEvent: typeof globalThis.CustomEvent;
11
- export declare class CustomEventTarget<EventDetailMap extends Record<string, unknown>> {
12
- readonly eventTarget: EventTarget;
13
- addEventListener<T extends Extract<keyof EventDetailMap, string>>(type: T, callback: (event: CustomEvent<EventDetailMap[T]>) => void, options?: AddEventListenerOptions | boolean): void;
14
- removeEventListener<T extends Extract<keyof EventDetailMap, string>>(type: T, callback: (event: CustomEvent<EventDetailMap[T]>) => void, options?: EventListenerOptions | boolean): void;
15
- dispatchCustomEvent<T extends Extract<keyof EventDetailMap, string>>(type: T, detail: EventDetailMap[T], init?: EventInit): boolean;
16
- }
17
7
  export declare function combineSignals(signals: readonly (AbortSignal | undefined)[]): AbortController & Disposable;
18
8
  //# sourceMappingURL=util.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;AAC7C,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAE,GAAG,WAAW,CAAC,OAAO,CAAC,CAAA;AAEzE,eAAO,MAAM,QAAQ,GAAI,CAAC,EAAE,GAAG,CAAC,6BAA4C,CAAA;AAE5E,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAEhE;AAED;;GAEG;AACH,eAAO,MAAM,WAAW,EAAE,OAAO,UAAU,CAAC,WA4BtC,CAAA;AAEN,qBAAa,iBAAiB,CAAC,cAAc,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC3E,QAAQ,CAAC,WAAW,cAAoB;IAExC,gBAAgB,CAAC,CAAC,SAAS,OAAO,CAAC,MAAM,cAAc,EAAE,MAAM,CAAC,EAC9D,IAAI,EAAE,CAAC,EACP,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,EACzD,OAAO,CAAC,EAAE,uBAAuB,GAAG,OAAO,GAC1C,IAAI;IAIP,mBAAmB,CAAC,CAAC,SAAS,OAAO,CAAC,MAAM,cAAc,EAAE,MAAM,CAAC,EACjE,IAAI,EAAE,CAAC,EACP,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,EACzD,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,GACvC,IAAI;IAQP,mBAAmB,CAAC,CAAC,SAAS,OAAO,CAAC,MAAM,cAAc,EAAE,MAAM,CAAC,EACjE,IAAI,EAAE,CAAC,EACP,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,EACzB,IAAI,CAAC,EAAE,SAAS,GACf,OAAO;CAKX;AAED,wBAAgB,cAAc,CAC5B,OAAO,EAAE,SAAS,CAAC,WAAW,GAAG,SAAS,CAAC,EAAE,GAC5C,eAAe,GAAG,UAAU,CAwB9B"}
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;AAC7C,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAE,GAAG,WAAW,CAAC,OAAO,CAAC,CAAA;AAEzE,eAAO,MAAM,QAAQ,GAAI,CAAC,EAAE,GAAG,CAAC,6BAA4C,CAAA;AAE5E,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAEhE;AAED,wBAAgB,cAAc,CAC5B,OAAO,EAAE,SAAS,CAAC,WAAW,GAAG,SAAS,CAAC,EAAE,GAC5C,eAAe,GAAG,UAAU,CAwB9B"}
package/dist/util.js CHANGED
@@ -1,17 +1,6 @@
1
1
  "use strict";
2
- var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
3
- if (kind === "m") throw new TypeError("Private method is not writable");
4
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
5
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
6
- return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
7
- };
8
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
9
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
10
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
- };
13
2
  Object.defineProperty(exports, "__esModule", { value: true });
14
- exports.CustomEventTarget = exports.CustomEvent = exports.ifString = void 0;
3
+ exports.ifString = void 0;
15
4
  exports.contentMime = contentMime;
16
5
  exports.combineSignals = combineSignals;
17
6
  const ifString = (v) => (typeof v === 'string' ? v : undefined);
@@ -19,58 +8,6 @@ exports.ifString = ifString;
19
8
  function contentMime(headers) {
20
9
  return headers.get('content-type')?.split(';')[0].trim();
21
10
  }
22
- /**
23
- * Ponyfill for `CustomEvent` constructor.
24
- */
25
- exports.CustomEvent = globalThis.CustomEvent ??
26
- (() => {
27
- var _CustomEvent_detail;
28
- class CustomEvent extends Event {
29
- constructor(type, options) {
30
- if (!arguments.length)
31
- throw new TypeError('type argument is required');
32
- super(type, options);
33
- _CustomEvent_detail.set(this, void 0);
34
- __classPrivateFieldSet(this, _CustomEvent_detail, options?.detail ?? null, "f");
35
- }
36
- get detail() {
37
- return __classPrivateFieldGet(this, _CustomEvent_detail, "f");
38
- }
39
- }
40
- _CustomEvent_detail = new WeakMap();
41
- Object.defineProperties(CustomEvent.prototype, {
42
- [Symbol.toStringTag]: {
43
- writable: false,
44
- enumerable: false,
45
- configurable: true,
46
- value: 'CustomEvent',
47
- },
48
- detail: {
49
- enumerable: true,
50
- },
51
- });
52
- return CustomEvent;
53
- })();
54
- class CustomEventTarget {
55
- constructor() {
56
- Object.defineProperty(this, "eventTarget", {
57
- enumerable: true,
58
- configurable: true,
59
- writable: true,
60
- value: new EventTarget()
61
- });
62
- }
63
- addEventListener(type, callback, options) {
64
- this.eventTarget.addEventListener(type, callback, options);
65
- }
66
- removeEventListener(type, callback, options) {
67
- this.eventTarget.removeEventListener(type, callback, options);
68
- }
69
- dispatchCustomEvent(type, detail, init) {
70
- return this.eventTarget.dispatchEvent(new exports.CustomEvent(type, { ...init, detail }));
71
- }
72
- }
73
- exports.CustomEventTarget = CustomEventTarget;
74
11
  function combineSignals(signals) {
75
12
  const controller = new DisposableAbortController();
76
13
  const onAbort = function (_event) {
package/dist/util.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"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","sourcesContent":["export type Awaitable<T> = T | PromiseLike<T>\nexport type Simplify<T> = { [K in keyof T]: T[K] } & NonNullable<unknown>\n\nexport const ifString = <V>(v: V) => (typeof v === 'string' ? v : undefined)\n\nexport function contentMime(headers: Headers): string | undefined {\n return headers.get('content-type')?.split(';')[0]!.trim()\n}\n\n/**\n * Ponyfill for `CustomEvent` constructor.\n */\nexport const CustomEvent: typeof globalThis.CustomEvent =\n globalThis.CustomEvent ??\n (() => {\n class CustomEvent<T> extends Event {\n #detail: T | null\n constructor(type: string, options?: CustomEventInit<T>) {\n if (!arguments.length) throw new TypeError('type argument is required')\n super(type, options)\n this.#detail = options?.detail ?? null\n }\n get detail() {\n return this.#detail\n }\n }\n\n Object.defineProperties(CustomEvent.prototype, {\n [Symbol.toStringTag]: {\n writable: false,\n enumerable: false,\n configurable: true,\n value: 'CustomEvent',\n },\n detail: {\n enumerable: true,\n },\n })\n\n return CustomEvent\n })()\n\nexport class CustomEventTarget<EventDetailMap extends Record<string, unknown>> {\n readonly eventTarget = new EventTarget()\n\n addEventListener<T extends Extract<keyof EventDetailMap, string>>(\n type: T,\n callback: (event: CustomEvent<EventDetailMap[T]>) => void,\n options?: AddEventListenerOptions | boolean,\n ): void {\n this.eventTarget.addEventListener(type, callback as EventListener, options)\n }\n\n removeEventListener<T extends Extract<keyof EventDetailMap, string>>(\n type: T,\n callback: (event: CustomEvent<EventDetailMap[T]>) => void,\n options?: EventListenerOptions | boolean,\n ): void {\n this.eventTarget.removeEventListener(\n type,\n callback as EventListener,\n options,\n )\n }\n\n dispatchCustomEvent<T extends Extract<keyof EventDetailMap, string>>(\n type: T,\n detail: EventDetailMap[T],\n init?: EventInit,\n ): boolean {\n return this.eventTarget.dispatchEvent(\n new CustomEvent(type, { ...init, detail }),\n )\n }\n}\n\nexport function combineSignals(\n signals: readonly (AbortSignal | undefined)[],\n): AbortController & Disposable {\n const controller = new DisposableAbortController()\n\n const onAbort = function (this: AbortSignal, _event: Event) {\n const reason = new Error('This operation was aborted', {\n cause: this.reason,\n })\n\n controller.abort(reason)\n }\n\n try {\n for (const sig of signals) {\n if (sig) {\n sig.throwIfAborted()\n sig.addEventListener('abort', onAbort, { signal: controller.signal })\n }\n }\n\n return controller\n } catch (err) {\n controller.abort(err)\n throw err\n }\n}\n\n/**\n * Allows using {@link AbortController} with the `using` keyword, in order to\n * automatically abort them once the execution block ends.\n */\nclass DisposableAbortController extends AbortController implements Disposable {\n [Symbol.dispose]() {\n this.abort(new Error('AbortController was disposed'))\n }\n}\n"]}
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":";;;AAKA,kCAEC;AAED,wCA0BC;AAhCM,MAAM,QAAQ,GAAG,CAAI,CAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;AAA/D,QAAA,QAAQ,YAAuD;AAE5E,SAAgB,WAAW,CAAC,OAAgB;IAC1C,OAAO,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,CAAA;AAC3D,CAAC;AAED,SAAgB,cAAc,CAC5B,OAA6C;IAE7C,MAAM,UAAU,GAAG,IAAI,yBAAyB,EAAE,CAAA;IAElD,MAAM,OAAO,GAAG,UAA6B,MAAa;QACxD,MAAM,MAAM,GAAG,IAAI,KAAK,CAAC,4BAA4B,EAAE;YACrD,KAAK,EAAE,IAAI,CAAC,MAAM;SACnB,CAAC,CAAA;QAEF,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;IAC1B,CAAC,CAAA;IAED,IAAI,CAAC;QACH,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;YAC1B,IAAI,GAAG,EAAE,CAAC;gBACR,GAAG,CAAC,cAAc,EAAE,CAAA;gBACpB,GAAG,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,CAAA;YACvE,CAAC;QACH,CAAC;QAED,OAAO,UAAU,CAAA;IACnB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QACrB,MAAM,GAAG,CAAA;IACX,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,yBAA0B,SAAQ,eAAe;IACrD,CAAC,MAAM,CAAC,OAAO,CAAC;QACd,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC,CAAA;IACvD,CAAC;CACF","sourcesContent":["export type Awaitable<T> = T | PromiseLike<T>\nexport type Simplify<T> = { [K in keyof T]: T[K] } & NonNullable<unknown>\n\nexport const ifString = <V>(v: V) => (typeof v === 'string' ? v : undefined)\n\nexport function contentMime(headers: Headers): string | undefined {\n return headers.get('content-type')?.split(';')[0]!.trim()\n}\n\nexport function combineSignals(\n signals: readonly (AbortSignal | undefined)[],\n): AbortController & Disposable {\n const controller = new DisposableAbortController()\n\n const onAbort = function (this: AbortSignal, _event: Event) {\n const reason = new Error('This operation was aborted', {\n cause: this.reason,\n })\n\n controller.abort(reason)\n }\n\n try {\n for (const sig of signals) {\n if (sig) {\n sig.throwIfAborted()\n sig.addEventListener('abort', onAbort, { signal: controller.signal })\n }\n }\n\n return controller\n } catch (err) {\n controller.abort(err)\n throw err\n }\n}\n\n/**\n * Allows using {@link AbortController} with the `using` keyword, in order to\n * automatically abort them once the execution block ends.\n */\nclass DisposableAbortController extends AbortController implements Disposable {\n [Symbol.dispose]() {\n this.abort(new Error('AbortController was disposed'))\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/oauth-client",
3
- "version": "0.5.13",
3
+ "version": "0.6.0",
4
4
  "license": "MIT",
5
5
  "description": "OAuth client for ATPROTO PDS. This package serves as common base for environment-specific implementations (NodeJS, Browser, React-Native).",
6
6
  "keywords": [
@@ -28,16 +28,16 @@
28
28
  "core-js": "^3",
29
29
  "multiformats": "^9.9.0",
30
30
  "zod": "^3.23.8",
31
- "@atproto-labs/did-resolver": "0.2.5",
32
- "@atproto-labs/fetch": "0.2.3",
33
- "@atproto-labs/handle-resolver": "0.3.5",
34
- "@atproto-labs/identity-resolver": "0.3.5",
35
- "@atproto-labs/simple-store": "0.3.0",
36
- "@atproto-labs/simple-store-memory": "0.1.4",
37
- "@atproto/did": "0.2.4",
38
- "@atproto/jwk": "0.6.0",
39
- "@atproto/oauth-types": "0.6.1",
40
- "@atproto/xrpc": "0.7.7"
31
+ "@atproto-labs/did-resolver": "^0.2.6",
32
+ "@atproto-labs/fetch": "^0.2.3",
33
+ "@atproto-labs/handle-resolver": "^0.3.6",
34
+ "@atproto-labs/identity-resolver": "^0.3.6",
35
+ "@atproto-labs/simple-store": "^0.3.0",
36
+ "@atproto-labs/simple-store-memory": "^0.1.4",
37
+ "@atproto/did": "^0.3.0",
38
+ "@atproto/jwk": "^0.6.0",
39
+ "@atproto/oauth-types": "^0.6.3",
40
+ "@atproto/xrpc": "^0.7.7"
41
41
  },
42
42
  "devDependencies": {
43
43
  "typescript": "^5.6.3"