@atproto/oauth-client 0.7.2 → 0.7.4
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/CHANGELOG.md +24 -0
- package/dist/errors/token-invalid-error.d.ts.map +1 -1
- package/dist/errors/token-invalid-error.js.map +1 -1
- package/dist/errors/token-refresh-error.d.ts.map +1 -1
- package/dist/errors/token-refresh-error.js.map +1 -1
- package/dist/errors/token-revoked-error.d.ts.map +1 -1
- package/dist/errors/token-revoked-error.js.map +1 -1
- package/dist/oauth-authorization-server-metadata-resolver.d.ts.map +1 -1
- package/dist/oauth-callback-error.d.ts.map +1 -1
- package/dist/oauth-callback-error.js.map +1 -1
- package/dist/oauth-client.d.ts +78 -78
- package/dist/oauth-client.d.ts.map +1 -1
- package/dist/oauth-client.js.map +1 -1
- package/dist/oauth-protected-resource-metadata-resolver.d.ts.map +1 -1
- package/dist/oauth-protected-resource-metadata-resolver.js.map +1 -1
- package/dist/oauth-resolver-error.d.ts.map +1 -1
- package/dist/oauth-resolver.d.ts +13 -13
- package/dist/oauth-resolver.d.ts.map +1 -1
- package/dist/oauth-resolver.js.map +1 -1
- package/dist/oauth-response-error.d.ts.map +1 -1
- package/dist/oauth-response-error.js.map +1 -1
- package/dist/oauth-server-agent.d.ts +1 -1
- package/dist/oauth-server-agent.d.ts.map +1 -1
- package/dist/oauth-server-agent.js +2 -1
- package/dist/oauth-server-agent.js.map +1 -1
- package/dist/oauth-server-factory.d.ts.map +1 -1
- package/dist/oauth-server-factory.js.map +1 -1
- package/dist/oauth-session.d.ts.map +1 -1
- package/dist/oauth-session.js.map +1 -1
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js.map +1 -1
- package/dist/session-getter.d.ts +1 -1
- package/dist/session-getter.d.ts.map +1 -1
- package/dist/session-getter.js +2 -2
- package/dist/session-getter.js.map +1 -1
- package/dist/types.d.ts +131 -131
- package/dist/types.d.ts.map +1 -1
- package/dist/util.d.ts +12 -0
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +32 -0
- package/dist/util.js.map +1 -1
- package/dist/validate-client-metadata.js.map +1 -1
- package/package.json +14 -13
- package/src/oauth-server-agent.ts +2 -1
- package/src/session-getter.ts +2 -2
- package/src/util.test.ts +86 -0
- package/src/util.ts +35 -0
- package/tsconfig.build.json +3 -2
- package/tsconfig.build.tsbuildinfo +1 -1
- package/tsconfig.json +4 -1
- package/tsconfig.tests.json +8 -0
- package/vitest.config.ts +5 -0
package/dist/util.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAI,CAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;AAE5E,MAAM,UAAU,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,MAAM,UAAU,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"]}
|
|
1
|
+
{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAI,CAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;AAE5E,MAAM,UAAU,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;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa,CAAC,EAAU;IACtC,IAAI,OAAO,WAAW,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;QAC9C,OAAO,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IAChC,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAA;IACxC,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;IACxD,OAAO,UAAU,CAAC,MAAM,CAAA;AAC1B,CAAC;AAED;;;;;GAKG;AACH,SAAS,YAAY,CAAC,EAAU;IAC9B,MAAM,OAAO,GAAG,iCAAiC,EAAE,KAAK,CAAA;IACxD,IAAI,OAAO,YAAY,KAAK,UAAU,EAAE,CAAC;QACvC,OAAO,IAAI,YAAY,CAAC,OAAO,EAAE,cAAc,CAAC,CAAA;IAClD,CAAC;IACD,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,CAAA;AAC3B,CAAC;AAED,MAAM,UAAU,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\n/**\n * Returns an {@link AbortSignal} that aborts after `ms` milliseconds.\n *\n * Uses the native {@link AbortSignal.timeout} when available, and otherwise\n * falls back to an {@link AbortController} + `setTimeout`. The static\n * `AbortSignal.timeout` method is not implemented in every runtime this package\n * targets (notably React Native / Expo), so relying on it directly throws a\n * `TypeError: AbortSignal.timeout is not a function` at runtime.\n *\n * @see {@link https://github.com/facebook/react-native/issues/42042}\n */\nexport function timeoutSignal(ms: number): AbortSignal {\n if (typeof AbortSignal.timeout === 'function') {\n return AbortSignal.timeout(ms)\n }\n\n const controller = new AbortController()\n setTimeout(() => controller.abort(timeoutError(ms)), ms)\n return controller.signal\n}\n\n/**\n * Builds the reason used to abort a {@link timeoutSignal} fallback. Mirrors the\n * native `AbortSignal.timeout` behaviour (a `TimeoutError` `DOMException`) when\n * `DOMException` is available, and degrades to a plain `Error` in runtimes that\n * lack it.\n */\nfunction timeoutError(ms: number): unknown {\n const message = `The operation timed out after ${ms} ms`\n if (typeof DOMException === 'function') {\n return new DOMException(message, 'TimeoutError')\n }\n return new Error(message)\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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-client-metadata.js","sourceRoot":"","sources":["../src/validate-client-metadata.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,+BAA+B,EAC/B,2BAA2B,GAC5B,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7C,OAAO,EAAkB,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAEjE,MAAM,UAAU,sBAAsB,CACpC,KAA+B,EAC/B,MAAe;IAEf,+DAA+D;IAC/D,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,MAAM,EAAE,IAAI,EAAE,CAAC;QACnD,KAAK,GAAG,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,EAAE,CAAA;IAC7C,CAAC;IAED,MAAM,QAAQ,GAAG,oBAAoB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IAElD,qBAAqB;IACrB,IAAI,QAAQ,CAAC,SAAS,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3C,2BAA2B,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAA;IACjD,CAAC;SAAM,CAAC;QACN,+BAA+B,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAA;IACrD,CAAC;IAED,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAA;IACzC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,SAAS,CAAC,kDAAkD,CAAC,CAAA;IACzE,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,SAAS,CAAC,sCAAsC,CAAC,CAAA;IAC7D,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,CAAC;QACzD,MAAM,IAAI,SAAS,CAAC,iDAAiD,CAAC,CAAA;IACxE,CAAC;IAED,MAAM,MAAM,GAAG,QAAQ,CAAC,0BAA0B,CAAA;IAClD,MAAM,SAAS,GAAG,QAAQ,CAAC,+BAA+B,CAAA;IAC1D,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,MAAM;YACT,IAAI,SAAS,EAAE,CAAC;gBACd,MAAM,IAAI,SAAS,CACjB,gGAAgG,MAAM,GAAG,CAC1G,CAAA;YACH,CAAC;YACD,MAAK;QAEP,KAAK,iBAAiB,
|
|
1
|
+
{"version":3,"file":"validate-client-metadata.js","sourceRoot":"","sources":["../src/validate-client-metadata.ts"],"names":[],"mappings":"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","sourcesContent":["import { Keyset } from '@atproto/jwk'\nimport {\n OAuthClientMetadataInput,\n assertOAuthDiscoverableClientId,\n assertOAuthLoopbackClientId,\n} from '@atproto/oauth-types'\nimport { FALLBACK_ALG } from './constants.js'\nimport { ClientMetadata, clientMetadataSchema } from './types.js'\n\nexport function validateClientMetadata(\n input: OAuthClientMetadataInput,\n keyset?: Keyset,\n): ClientMetadata {\n // Allow to pass a keyset and omit the jwks/jwks_uri properties\n if (!input.jwks && !input.jwks_uri && keyset?.size) {\n input = { ...input, jwks: keyset.toJSON() }\n }\n\n const metadata = clientMetadataSchema.parse(input)\n\n // Validate client ID\n if (metadata.client_id.startsWith('http:')) {\n assertOAuthLoopbackClientId(metadata.client_id)\n } else {\n assertOAuthDiscoverableClientId(metadata.client_id)\n }\n\n const scopes = metadata.scope?.split(' ')\n if (!scopes?.includes('atproto')) {\n throw new TypeError(`Client metadata must include the \"atproto\" scope`)\n }\n\n if (!metadata.response_types.includes('code')) {\n throw new TypeError(`\"response_types\" must include \"code\"`)\n }\n\n if (!metadata.grant_types.includes('authorization_code')) {\n throw new TypeError(`\"grant_types\" must include \"authorization_code\"`)\n }\n\n const method = metadata.token_endpoint_auth_method\n const methodAlg = metadata.token_endpoint_auth_signing_alg\n switch (method) {\n case 'none':\n if (methodAlg) {\n throw new TypeError(\n `\"token_endpoint_auth_signing_alg\" must not be provided when \"token_endpoint_auth_method\" is \"${method}\"`,\n )\n }\n break\n\n case 'private_key_jwt': {\n if (!methodAlg) {\n throw new TypeError(\n `\"token_endpoint_auth_signing_alg\" must be provided when \"token_endpoint_auth_method\" is \"${method}\"`,\n )\n }\n\n if (!keyset) {\n throw new TypeError(\n `Client authentication method \"${method}\" requires a keyset`,\n )\n }\n\n // @NOTE This reproduces the logic from `negotiateClientAuthMethod` at\n // initialization time to ensure that every key that might end-up being\n // used is indeed valid & advertised in the metadata.\n const signingKeys = Array.from(keyset.list({ usage: 'sign' })).filter(\n (key) => key.kid,\n )\n\n if (!signingKeys.length) {\n throw new TypeError(\n `Client authentication method \"${method}\" requires at least one active signing key with a \"kid\" property`,\n )\n }\n\n if (!signingKeys.some((key) => key.algorithms.includes(FALLBACK_ALG))) {\n throw new TypeError(\n `Client authentication method \"${method}\" requires at least one active \"${FALLBACK_ALG}\" signing key`,\n )\n }\n\n if (metadata.jwks) {\n // Ensure that all the signing keys that could end-up being used are\n // advertised in the JWKS.\n for (const key of signingKeys) {\n if (\n !metadata.jwks.keys.some((k) => k.kid === key.kid && !k.revoked)\n ) {\n throw new TypeError(\n `Missing or inactive key \"${key.kid}\" in jwks. Make sure that every signing key of the Keyset is declared as an active key in the Metadata's JWKS.`,\n )\n }\n }\n } else if (metadata.jwks_uri) {\n // @NOTE we only ensure that all the signing keys are referenced in JWKS\n // when it is available (see previous \"if\") as we don't want to download\n // that file here (for efficiency reasons).\n } else {\n throw new TypeError(\n `Client authentication method \"${method}\" requires a JWKS`,\n )\n }\n\n break\n }\n\n default:\n throw new TypeError(\n `Unsupported \"token_endpoint_auth_method\" value: ${method}`,\n )\n }\n\n return metadata\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atproto/oauth-client",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": ">=22"
|
|
6
6
|
},
|
|
@@ -29,21 +29,22 @@
|
|
|
29
29
|
"core-js": "^3",
|
|
30
30
|
"multiformats": "^13.0.0",
|
|
31
31
|
"zod": "^3.23.8",
|
|
32
|
-
"@atproto-labs/
|
|
33
|
-
"@atproto-labs/
|
|
34
|
-
"@atproto-labs/simple-store": "^0.4.
|
|
35
|
-
"@atproto-labs/
|
|
36
|
-
"@atproto-labs/
|
|
37
|
-
"@atproto
|
|
38
|
-
"@atproto/
|
|
39
|
-
"@atproto/
|
|
40
|
-
"@atproto/
|
|
41
|
-
"@atproto/xrpc": "^0.8.
|
|
32
|
+
"@atproto-labs/fetch": "^0.3.1",
|
|
33
|
+
"@atproto-labs/did-resolver": "^0.3.2",
|
|
34
|
+
"@atproto-labs/simple-store": "^0.4.1",
|
|
35
|
+
"@atproto-labs/simple-store-memory": "^0.2.1",
|
|
36
|
+
"@atproto-labs/handle-resolver": "^0.4.2",
|
|
37
|
+
"@atproto/did": "^0.5.1",
|
|
38
|
+
"@atproto-labs/identity-resolver": "^0.4.1",
|
|
39
|
+
"@atproto/jwk": "^0.7.1",
|
|
40
|
+
"@atproto/oauth-types": "^0.7.2",
|
|
41
|
+
"@atproto/xrpc": "^0.8.1"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"
|
|
44
|
+
"vitest": "^4.0.16"
|
|
45
45
|
},
|
|
46
46
|
"scripts": {
|
|
47
|
-
"build": "
|
|
47
|
+
"build": "tsgo --build tsconfig.build.json",
|
|
48
|
+
"test": "vitest run"
|
|
48
49
|
}
|
|
49
50
|
}
|
|
@@ -25,6 +25,7 @@ import { OAuthResolver } from './oauth-resolver.js'
|
|
|
25
25
|
import { OAuthResponseError } from './oauth-response-error.js'
|
|
26
26
|
import { Runtime } from './runtime.js'
|
|
27
27
|
import { ClientMetadata } from './types.js'
|
|
28
|
+
import { timeoutSignal } from './util.js'
|
|
28
29
|
|
|
29
30
|
export type { AtprotoOAuthScope, AtprotoOAuthTokenResponse }
|
|
30
31
|
|
|
@@ -189,7 +190,7 @@ export class OAuthServerAgent {
|
|
|
189
190
|
const resolved = await this.oauthResolver.resolveFromIdentity(sub, {
|
|
190
191
|
noCache: true,
|
|
191
192
|
allowStale: false,
|
|
192
|
-
signal:
|
|
193
|
+
signal: timeoutSignal(10e3),
|
|
193
194
|
})
|
|
194
195
|
|
|
195
196
|
if (this.issuer !== resolved.metadata.issuer) {
|
package/src/session-getter.ts
CHANGED
|
@@ -15,7 +15,7 @@ import { OAuthResponseError } from './oauth-response-error.js'
|
|
|
15
15
|
import { TokenSet } from './oauth-server-agent.js'
|
|
16
16
|
import { OAuthServerFactory } from './oauth-server-factory.js'
|
|
17
17
|
import { Runtime } from './runtime.js'
|
|
18
|
-
import { combineSignals } from './util.js'
|
|
18
|
+
import { combineSignals, timeoutSignal } from './util.js'
|
|
19
19
|
|
|
20
20
|
export type Session = {
|
|
21
21
|
dpopKey: Key
|
|
@@ -247,7 +247,7 @@ export class SessionGetter extends CachedGetter<AtprotoDid, Session> {
|
|
|
247
247
|
async () => {
|
|
248
248
|
// Make sure, even if there is no signal in the options, that the
|
|
249
249
|
// request will be cancelled after at most 30 seconds.
|
|
250
|
-
const signal =
|
|
250
|
+
const signal = timeoutSignal(30e3)
|
|
251
251
|
|
|
252
252
|
using abortController = combineSignals([options?.signal, signal])
|
|
253
253
|
|
package/src/util.test.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { timeoutSignal } from './util.js'
|
|
3
|
+
|
|
4
|
+
describe(timeoutSignal, () => {
|
|
5
|
+
describe('with native AbortSignal.timeout', () => {
|
|
6
|
+
it('delegates to the native implementation when available', () => {
|
|
7
|
+
using spy = vi.spyOn(AbortSignal, 'timeout')
|
|
8
|
+
|
|
9
|
+
const signal = timeoutSignal(1000)
|
|
10
|
+
|
|
11
|
+
expect(spy).toHaveBeenCalledOnce()
|
|
12
|
+
expect(spy).toHaveBeenCalledWith(1000)
|
|
13
|
+
expect(signal).toBeInstanceOf(AbortSignal)
|
|
14
|
+
expect(signal.aborted).toBe(false)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('returns a signal that actually aborts after the timeout', async () => {
|
|
18
|
+
const signal = timeoutSignal(5)
|
|
19
|
+
expect(signal.aborted).toBe(false)
|
|
20
|
+
|
|
21
|
+
await new Promise((resolve) => setTimeout(resolve, 20))
|
|
22
|
+
expect(signal.aborted).toBe(true)
|
|
23
|
+
})
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
describe('without native AbortSignal.timeout (e.g. React Native)', () => {
|
|
27
|
+
let original: typeof AbortSignal.timeout
|
|
28
|
+
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
vi.useFakeTimers()
|
|
31
|
+
original = AbortSignal.timeout
|
|
32
|
+
// Simulate a runtime that does not implement the static method.
|
|
33
|
+
// @ts-expect-error intentionally removing a built-in to emulate RN
|
|
34
|
+
AbortSignal.timeout = undefined
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
AbortSignal.timeout = original
|
|
39
|
+
vi.useRealTimers()
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('does not throw and returns a usable AbortSignal', () => {
|
|
43
|
+
const signal = timeoutSignal(1000)
|
|
44
|
+
expect(signal).toBeInstanceOf(AbortSignal)
|
|
45
|
+
expect(signal.aborted).toBe(false)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('aborts the signal once the timeout elapses', () => {
|
|
49
|
+
const signal = timeoutSignal(1000)
|
|
50
|
+
const onAbort = vi.fn()
|
|
51
|
+
signal.addEventListener('abort', onAbort)
|
|
52
|
+
|
|
53
|
+
vi.advanceTimersByTime(999)
|
|
54
|
+
expect(signal.aborted).toBe(false)
|
|
55
|
+
expect(onAbort).not.toHaveBeenCalled()
|
|
56
|
+
|
|
57
|
+
vi.advanceTimersByTime(1)
|
|
58
|
+
expect(signal.aborted).toBe(true)
|
|
59
|
+
expect(onAbort).toHaveBeenCalledOnce()
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('aborts with a TimeoutError reason', () => {
|
|
63
|
+
const signal = timeoutSignal(1000)
|
|
64
|
+
vi.advanceTimersByTime(1000)
|
|
65
|
+
|
|
66
|
+
expect(signal.reason).toBeInstanceOf(DOMException)
|
|
67
|
+
expect(signal.reason.name).toBe('TimeoutError')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('falls back to a plain Error when DOMException is unavailable', () => {
|
|
71
|
+
const originalDomException = globalThis.DOMException
|
|
72
|
+
try {
|
|
73
|
+
// @ts-expect-error intentionally removing a built-in to emulate RN
|
|
74
|
+
globalThis.DOMException = undefined
|
|
75
|
+
|
|
76
|
+
const signal = timeoutSignal(1000)
|
|
77
|
+
vi.advanceTimersByTime(1000)
|
|
78
|
+
|
|
79
|
+
expect(signal.aborted).toBe(true)
|
|
80
|
+
expect(signal.reason).toBeInstanceOf(Error)
|
|
81
|
+
} finally {
|
|
82
|
+
globalThis.DOMException = originalDomException
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
})
|
package/src/util.ts
CHANGED
|
@@ -7,6 +7,41 @@ export function contentMime(headers: Headers): string | undefined {
|
|
|
7
7
|
return headers.get('content-type')?.split(';')[0]!.trim()
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Returns an {@link AbortSignal} that aborts after `ms` milliseconds.
|
|
12
|
+
*
|
|
13
|
+
* Uses the native {@link AbortSignal.timeout} when available, and otherwise
|
|
14
|
+
* falls back to an {@link AbortController} + `setTimeout`. The static
|
|
15
|
+
* `AbortSignal.timeout` method is not implemented in every runtime this package
|
|
16
|
+
* targets (notably React Native / Expo), so relying on it directly throws a
|
|
17
|
+
* `TypeError: AbortSignal.timeout is not a function` at runtime.
|
|
18
|
+
*
|
|
19
|
+
* @see {@link https://github.com/facebook/react-native/issues/42042}
|
|
20
|
+
*/
|
|
21
|
+
export function timeoutSignal(ms: number): AbortSignal {
|
|
22
|
+
if (typeof AbortSignal.timeout === 'function') {
|
|
23
|
+
return AbortSignal.timeout(ms)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const controller = new AbortController()
|
|
27
|
+
setTimeout(() => controller.abort(timeoutError(ms)), ms)
|
|
28
|
+
return controller.signal
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Builds the reason used to abort a {@link timeoutSignal} fallback. Mirrors the
|
|
33
|
+
* native `AbortSignal.timeout` behaviour (a `TimeoutError` `DOMException`) when
|
|
34
|
+
* `DOMException` is available, and degrades to a plain `Error` in runtimes that
|
|
35
|
+
* lack it.
|
|
36
|
+
*/
|
|
37
|
+
function timeoutError(ms: number): unknown {
|
|
38
|
+
const message = `The operation timed out after ${ms} ms`
|
|
39
|
+
if (typeof DOMException === 'function') {
|
|
40
|
+
return new DOMException(message, 'TimeoutError')
|
|
41
|
+
}
|
|
42
|
+
return new Error(message)
|
|
43
|
+
}
|
|
44
|
+
|
|
10
45
|
export function combineSignals(
|
|
11
46
|
signals: readonly (AbortSignal | undefined)[],
|
|
12
47
|
): AbortController & Disposable {
|
package/tsconfig.build.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/constants.ts","./src/core-js.d.ts","./src/fetch-dpop.ts","./src/identity-resolver.ts","./src/index.ts","./src/lock.ts","./src/oauth-authorization-server-metadata-resolver.ts","./src/oauth-callback-error.ts","./src/oauth-client-auth.ts","./src/oauth-client.ts","./src/oauth-protected-resource-metadata-resolver.ts","./src/oauth-resolver-error.ts","./src/oauth-resolver.ts","./src/oauth-response-error.ts","./src/oauth-server-agent.ts","./src/oauth-server-factory.ts","./src/oauth-session.ts","./src/runtime-implementation.ts","./src/runtime.ts","./src/session-getter.ts","./src/state-store.ts","./src/types.ts","./src/util.ts","./src/validate-client-metadata.ts","./src/errors/auth-method-unsatisfiable-error.ts","./src/errors/token-invalid-error.ts","./src/errors/token-refresh-error.ts","./src/errors/token-revoked-error.ts"]
|
|
1
|
+
{"version":"7.0.0-dev.20260614.1","root":["./src/constants.ts","./src/core-js.d.ts","./src/fetch-dpop.ts","./src/identity-resolver.ts","./src/index.ts","./src/lock.ts","./src/oauth-authorization-server-metadata-resolver.ts","./src/oauth-callback-error.ts","./src/oauth-client-auth.ts","./src/oauth-client.ts","./src/oauth-protected-resource-metadata-resolver.ts","./src/oauth-resolver-error.ts","./src/oauth-resolver.ts","./src/oauth-response-error.ts","./src/oauth-server-agent.ts","./src/oauth-server-factory.ts","./src/oauth-session.ts","./src/runtime-implementation.ts","./src/runtime.ts","./src/session-getter.ts","./src/state-store.ts","./src/types.ts","./src/util.ts","./src/validate-client-metadata.ts","./src/errors/auth-method-unsatisfiable-error.ts","./src/errors/token-invalid-error.ts","./src/errors/token-refresh-error.ts","./src/errors/token-revoked-error.ts"]}
|
package/tsconfig.json
CHANGED