@omelhorsite/sdk 0.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.
Files changed (36) hide show
  1. package/README.md +321 -0
  2. package/dist/index.js +11589 -0
  3. package/dist/types/auth/device.d.ts +156 -0
  4. package/dist/types/auth/index.d.ts +127 -0
  5. package/dist/types/auth/tokens.d.ts +356 -0
  6. package/dist/types/client.d.ts +133 -0
  7. package/dist/types/errors.d.ts +202 -0
  8. package/dist/types/http.d.ts +204 -0
  9. package/dist/types/index.d.ts +33 -0
  10. package/dist/types/local/index.d.ts +42 -0
  11. package/dist/types/local/password.d.ts +169 -0
  12. package/dist/types/local/qr.d.ts +127 -0
  13. package/dist/types/local/wordlist.d.ts +26 -0
  14. package/dist/types/resources/account.d.ts +296 -0
  15. package/dist/types/resources/chests.d.ts +194 -0
  16. package/dist/types/resources/dynamicQrs.d.ts +172 -0
  17. package/dist/types/resources/forms.d.ts +331 -0
  18. package/dist/types/resources/index.d.ts +30 -0
  19. package/dist/types/resources/ipLookup.d.ts +63 -0
  20. package/dist/types/resources/jobs.d.ts +233 -0
  21. package/dist/types/resources/linkTrees.d.ts +249 -0
  22. package/dist/types/resources/notepads.d.ts +96 -0
  23. package/dist/types/resources/shortLinks.d.ts +248 -0
  24. package/dist/types/resources/storage/upload.d.ts +459 -0
  25. package/dist/types/resources/storage.d.ts +527 -0
  26. package/dist/types/resources/tickets.d.ts +236 -0
  27. package/dist/types/resources/tools/backgroundRemoval.d.ts +99 -0
  28. package/dist/types/resources/tools/captions.d.ts +318 -0
  29. package/dist/types/resources/tools/downloader.d.ts +397 -0
  30. package/dist/types/resources/tools/index.d.ts +215 -0
  31. package/dist/types/resources/tools/jumpstyle.d.ts +194 -0
  32. package/dist/types/resources/tools/transcription.d.ts +178 -0
  33. package/dist/types/resources/tools/upscale.d.ts +94 -0
  34. package/dist/types/resources/tools/vocalSeparation.d.ts +183 -0
  35. package/dist/types/types.d.ts +245 -0
  36. package/package.json +37 -0
@@ -0,0 +1,156 @@
1
+ /**
2
+ * OAuth 2.0 Device Authorization Grant (RFC 8628).
3
+ *
4
+ * This is how a CLI or an MCP server signs a person in without ever handling
5
+ * their password: the client asks for a code, the person opens a URL in a real
6
+ * browser and approves, and the client polls the token endpoint until the
7
+ * approval lands.
8
+ *
9
+ * The approval page is rendered by RAILS at `backend.omelhorsite.pt`, not by
10
+ * the Next.js frontend - the browser already holds the host-only `oms_session`
11
+ * cookie for that origin, so the person is usually already signed in when they
12
+ * arrive.
13
+ *
14
+ * The core never opens a browser and never prints a URL. It returns the
15
+ * verification URI and lets the host decide what to do with it.
16
+ */
17
+ import { Resource } from "../http";
18
+ import type { RequestOptions } from "../types";
19
+ import { OmsOAuthError, type TokenSet } from "./tokens";
20
+ /** The RFC 8628 `grant_type` URN. Sent literally; `URLSearchParams` escapes it. */
21
+ export declare const DEVICE_CODE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
22
+ /** Poll interval to use when the server names none. Matches the backend default. */
23
+ export declare const DEFAULT_DEVICE_INTERVAL_MS = 5000;
24
+ /** Grant lifetime to assume when the caller passes no `expiresAt`. */
25
+ export declare const DEFAULT_DEVICE_EXPIRY_MS = 600000;
26
+ /** How much `slow_down` adds to the interval, per RFC 8628 §3.5. Permanent. */
27
+ export declare const DEVICE_SLOW_DOWN_STEP_MS = 5000;
28
+ /** What the device authorization endpoint answers. */
29
+ export interface DeviceAuthorization {
30
+ /** Opaque code the client polls with. Never show it to the person. */
31
+ readonly deviceCode: string;
32
+ /** Short code the person types, e.g. `"WDJB-MJHT"`. */
33
+ readonly userCode: string;
34
+ /** URL the person opens to approve. */
35
+ readonly verificationUri: string;
36
+ /** Same URL with the code pre-filled, when the server provides it. Prefer it. */
37
+ readonly verificationUriComplete?: string;
38
+ /** Absolute epoch milliseconds after which `deviceCode` is dead. */
39
+ readonly expiresAt: number;
40
+ /** Minimum milliseconds between polls, as the server asked. Honour it. */
41
+ readonly intervalMs: number;
42
+ }
43
+ /** Arguments for starting a device flow. */
44
+ export interface StartDeviceFlowInput {
45
+ /** The registered doorkeeper application id. */
46
+ readonly clientId: string;
47
+ /** Space-separated scopes to request. */
48
+ readonly scope?: string;
49
+ }
50
+ /** Arguments for polling a device flow to completion. */
51
+ export interface WaitForDeviceApprovalInput extends RequestOptions {
52
+ readonly clientId: string;
53
+ readonly deviceCode: string;
54
+ /** Starting poll interval. RFC 8628 `slow_down` raises it as the server asks. */
55
+ readonly intervalMs?: number;
56
+ /** Absolute epoch milliseconds to give up at. Defaults to the grant's own expiry. */
57
+ readonly expiresAt?: number;
58
+ /** Called on each poll so a host can keep a spinner honest. Never receives the code. */
59
+ readonly onPoll?: (state: "pending" | "slow_down") => void;
60
+ }
61
+ /**
62
+ * A device flow that ended for a reason the client cannot argue with.
63
+ *
64
+ * Branch on the subclasses, or on {@link OmsOAuthError.error} for the exact
65
+ * wire code. Never on the message.
66
+ */
67
+ export declare class OmsDeviceFlowError extends OmsOAuthError {
68
+ /** Nothing about a terminal grant outcome improves by being tried again. */
69
+ get retryable(): boolean;
70
+ }
71
+ /**
72
+ * The `device_code` outlived `expires_in` and the grant row is gone.
73
+ *
74
+ * The person took too long, or never opened the URL. Recover by calling
75
+ * {@link DeviceFlow.start} again for a fresh code.
76
+ */
77
+ export declare class OmsDeviceExpiredError extends OmsDeviceFlowError {
78
+ }
79
+ /**
80
+ * The person refused, or the grant no longer exists.
81
+ *
82
+ * Both `access_denied` and `invalid_grant` land here on purpose. This backend
83
+ * represents a refusal by destroying the grant row, so the next poll finds
84
+ * nothing and answers `invalid_grant`; `access_denied` is handled identically
85
+ * so that emitting it later is not a breaking change. Read
86
+ * {@link OmsOAuthError.error} when the difference matters.
87
+ */
88
+ export declare class OmsDeviceDeniedError extends OmsDeviceFlowError {
89
+ }
90
+ /**
91
+ * The device grant, exposed as `oms.auth.device`.
92
+ *
93
+ * Typical use:
94
+ * ```ts
95
+ * const grant = await oms.auth.device.start({ clientId });
96
+ * // host shows grant.verificationUriComplete ?? grant.verificationUri
97
+ * const tokens = await oms.auth.device.wait({ clientId, deviceCode: grant.deviceCode });
98
+ * ```
99
+ *
100
+ * Call it through a client with NO credential. Both endpoints authenticate
101
+ * with `client_id` in the body and want no `Authorization` header.
102
+ */
103
+ export declare class DeviceFlow extends Resource {
104
+ /**
105
+ * `POST /oauth/authorize_device` - asks for a device code and a user code.
106
+ *
107
+ * `scope` is omitted from the request when the caller passes none, and the
108
+ * server then grants `openid` alone. An unknown scope is rejected here
109
+ * rather than at first use, so a typo fails fast.
110
+ *
111
+ * @throws {OmsDeviceFlowError} when the server named an OAuth error.
112
+ * `invalid_client` (HTTP 401) means the `clientId` is wrong: a build bug,
113
+ * not something the person can fix.
114
+ */
115
+ start(input: StartDeviceFlowInput, options?: RequestOptions): Promise<DeviceAuthorization>;
116
+ /**
117
+ * Polls `POST /oauth/token` with `grant_type=urn:ietf:params:oauth:grant-type:device_code`
118
+ * until the person approves.
119
+ *
120
+ * Timing, in order:
121
+ * 1. sleep the interval BEFORE the first poll - the server would accept an
122
+ * immediate one, but the person has not opened a browser yet;
123
+ * 2. `authorization_pending` sleeps the interval and goes again;
124
+ * 3. `slow_down` adds {@link DEVICE_SLOW_DOWN_STEP_MS} to the interval
125
+ * permanently, then goes again;
126
+ * 4. HTTP 429 is rack-attack, not OAuth: wait what `Retry-After` said and go
127
+ * again, without touching the interval and without counting it against
128
+ * the flow;
129
+ * 5. a network fault or a 5xx waits one interval and goes again.
130
+ *
131
+ * The flow ends only at `expiresAt`, on a terminal OAuth error, or when the
132
+ * caller's `signal` aborts.
133
+ *
134
+ * @throws {OmsDeviceExpiredError} at `expiresAt`, and on `expired_token`.
135
+ * @throws {OmsDeviceDeniedError} when the person refused.
136
+ * @throws {OmsTimeoutError} with `code === "aborted"` when `signal` fired.
137
+ */
138
+ wait(input: WaitForDeviceApprovalInput): Promise<TokenSet>;
139
+ /**
140
+ * One poll of the token endpoint. Returns the tokens once approved, or
141
+ * `null` while the person has not answered yet.
142
+ *
143
+ * Does not sleep and does not respect the interval: {@link wait} owns the
144
+ * timing. `slow_down` reads as `null` here, so a caller driving its own loop
145
+ * must raise its interval on its own - or use {@link wait}.
146
+ *
147
+ * @throws {OmsDeviceExpiredError} / {@link OmsDeviceDeniedError} / any other
148
+ * terminal OAuth failure.
149
+ */
150
+ poll(input: {
151
+ clientId: string;
152
+ deviceCode: string;
153
+ }, options?: RequestOptions): Promise<TokenSet | null>;
154
+ /** The bare token request. Every caller above adds the policy around it. */
155
+ private exchange;
156
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * The `auth` namespace: signing in, refreshing, signing out, and knowing who
3
+ * the current credential belongs to.
4
+ *
5
+ * Re-exports every sibling module so nobody has to touch this file again.
6
+ *
7
+ * Two of these methods want a credential and the rest want none, which is the
8
+ * one thing to get right when wiring a host:
9
+ *
10
+ * - `device.start`, `device.wait`, `device.poll`, `refresh` and `revoke` carry
11
+ * `client_id` in a form body and must NOT send `Authorization`. Call them on
12
+ * an `Oms` built with no token.
13
+ * - `whoami` and `userinfo` are ordinary bearer-authenticated calls. Call them
14
+ * on the credentialed `Oms`.
15
+ *
16
+ * ```ts
17
+ * const anon = new Oms({ baseUrl, fetch });
18
+ * const grant = await anon.auth.device.start({ clientId, scope: "openid storage:read" });
19
+ * // host shows grant.verificationUriComplete ?? grant.verificationUri
20
+ * const set = await anon.auth.device.wait({ clientId, deviceCode: grant.deviceCode, ...grant });
21
+ *
22
+ * const tokens = new OAuthTokenProvider({
23
+ * store,
24
+ * refresh: (refreshToken) => anon.auth.refresh(refreshToken, { clientId }),
25
+ * });
26
+ * await tokens.set(set);
27
+ * const oms = new Oms({ baseUrl, fetch, tokens });
28
+ * const me = await oms.auth.whoami();
29
+ * ```
30
+ */
31
+ import { ApiClient, Resource } from "../http";
32
+ import type { RequestOptions } from "../types";
33
+ import { DeviceFlow } from "./device";
34
+ import type { IdentityClaims, TokenSet } from "./tokens";
35
+ export * from "./device";
36
+ export * from "./tokens";
37
+ /** Who the current credential belongs to. */
38
+ export interface WhoAmI {
39
+ /** `users.id`. The stable identifier; matches the OIDC `sub` claim. */
40
+ readonly id: string;
41
+ /** Current handle. Mutable - never key anything on it. */
42
+ readonly handle: string;
43
+ /** Current email. Mutable. */
44
+ readonly email?: string;
45
+ /** Scopes the credential carries, or `undefined` for a legacy session token. */
46
+ readonly scopes?: string[];
47
+ /** Whether the credential is an OAuth token rather than a legacy session token. */
48
+ readonly oauth: boolean;
49
+ }
50
+ /**
51
+ * The subset of the OIDC discovery document worth naming. Everything else the
52
+ * server publishes is still there under the index signature.
53
+ */
54
+ export interface DiscoveryDocument {
55
+ readonly issuer: string;
56
+ readonly token_endpoint: string;
57
+ readonly device_authorization_endpoint?: string;
58
+ readonly userinfo_endpoint?: string;
59
+ readonly jwks_uri?: string;
60
+ readonly scopes_supported?: string[];
61
+ readonly grant_types_supported?: string[];
62
+ readonly [member: string]: unknown;
63
+ }
64
+ /** The `auth` namespace, reachable as `oms.auth`. */
65
+ export declare class AuthNamespace extends Resource {
66
+ /** RFC 8628 device grant, for CLIs and headless clients. */
67
+ readonly device: DeviceFlow;
68
+ constructor(http: ApiClient);
69
+ /**
70
+ * `GET /account` with the current credential: resolves who is signed in.
71
+ *
72
+ * The cheapest way to check that a stored token is still alive. Needs the
73
+ * credential, so call it on the credentialed client.
74
+ *
75
+ * `email` comes back only when the `email` scope was granted, and `scopes` is
76
+ * `undefined` for a legacy opaque session token - which has no scopes because
77
+ * it carries full account authority.
78
+ */
79
+ whoami(options?: RequestOptions): Promise<WhoAmI>;
80
+ /**
81
+ * Exchanges a refresh token for a fresh {@link TokenSet}.
82
+ *
83
+ * The answer carries a NEW refresh token and a freshly built id token;
84
+ * discard the old refresh token the moment this resolves. Not retried: a
85
+ * replay after a lost response would spend a refresh token the server has
86
+ * already rotated and sign the user out.
87
+ *
88
+ * A 4xx here is a normal end state, not a fault - the grant was revoked, or
89
+ * it sat unused past the server's absolute lifetime. Clear the stored set
90
+ * and ask the person to sign in again. {@link OAuthTokenProvider} does that
91
+ * on its own.
92
+ */
93
+ refresh(refreshToken: string, input: {
94
+ clientId: string;
95
+ }, options?: RequestOptions): Promise<TokenSet>;
96
+ /**
97
+ * Revokes a token at `POST /oauth/revoke` (RFC 7009). Revoking a refresh
98
+ * token kills the whole grant.
99
+ *
100
+ * Answers `200` both for a real revocation and for a token the server has
101
+ * never seen, which is what RFC 7009 asks for: a client signing out must not
102
+ * be able to tell the two apart. A `403` means the token belongs to another
103
+ * application - a bug in the caller, not a state to recover from.
104
+ */
105
+ revoke(token: string, input: {
106
+ clientId: string;
107
+ }, options?: RequestOptions): Promise<void>;
108
+ /**
109
+ * Reads the OIDC claims of the current credential from
110
+ * `GET /oauth/userinfo`. `sub` is `users.id`.
111
+ *
112
+ * Needs the `openid` scope; without it the answer is 403. Members whose
113
+ * value is null or empty are omitted from the response, so read defensively.
114
+ *
115
+ * `email_verified: false` means the server holds no proof for that address -
116
+ * NOT that the address is wrong. Do not present it as an invalid email.
117
+ */
118
+ userinfo(options?: RequestOptions): Promise<IdentityClaims>;
119
+ /**
120
+ * `GET /.well-known/openid-configuration`, for diagnostics only.
121
+ *
122
+ * Never call this on the hot path. Every path this SDK uses is stable and
123
+ * same-origin with `baseUrl`; spending a round trip to rediscover them - in
124
+ * an isolate that may only ever handle one request - is pure waste.
125
+ */
126
+ discover(options?: RequestOptions): Promise<DiscoveryDocument>;
127
+ }
@@ -0,0 +1,356 @@
1
+ /**
2
+ * Token providers: where the transport gets a bearer token from.
3
+ *
4
+ * The core has no storage. A provider is either a constant, or a thing the
5
+ * host wired to its own store (the CLI's config file, a Worker KV namespace, a
6
+ * browser's memory). Nothing here reads a file or an environment variable.
7
+ *
8
+ * Two credential shapes exist today and both are just bearer tokens on the
9
+ * wire:
10
+ * - a legacy opaque session token (a UUID minted by `Session`), which never
11
+ * expires and carries no scopes;
12
+ * - an OAuth 2 / OIDC access token from doorkeeper, which does expire and does
13
+ * carry scopes, and which comes with a refresh token.
14
+ *
15
+ * {@link TokenSet} models the second. The first is just a string.
16
+ *
17
+ * This module also owns the OAuth *wire* layer - the form encoding every
18
+ * `/oauth/*` endpoint wants, and the error shape they all answer with - because
19
+ * that is the same layer that turns a token response into a {@link TokenSet}.
20
+ * `device.ts` and `index.ts` are built on top of it.
21
+ */
22
+ import { OmsError } from "../errors";
23
+ import { type ApiClient, type TokenProvider } from "../http";
24
+ import type { RequestOptions } from "../types";
25
+ /**
26
+ * An OAuth 2 token response, as doorkeeper returns it.
27
+ *
28
+ * `expiresAt` is absolute epoch milliseconds, not the `expires_in` seconds the
29
+ * server sends, so a stored set stays correct across a restart.
30
+ */
31
+ export interface TokenSet {
32
+ /** Bearer token sent as `Authorization: Bearer <accessToken>`. */
33
+ readonly accessToken: string;
34
+ /** Refresh token, when the grant issued one. */
35
+ readonly refreshToken?: string;
36
+ /** OIDC identity token, when `openid` was among the scopes. */
37
+ readonly idToken?: string;
38
+ /** Always `"Bearer"` for this API. */
39
+ readonly tokenType: string;
40
+ /** Absolute expiry, epoch milliseconds. `undefined` means it does not expire. */
41
+ readonly expiresAt?: number;
42
+ /** Granted scopes, space-separated as the server sent them. */
43
+ readonly scope?: string;
44
+ }
45
+ /** Claims the SDK reads out of an OIDC id token. */
46
+ export interface IdentityClaims {
47
+ /**
48
+ * Stable user identifier: `users.id`. Never the handle and never the email,
49
+ * both of which the user can change.
50
+ */
51
+ readonly sub: string;
52
+ readonly iss?: string;
53
+ readonly aud?: string | string[];
54
+ readonly exp?: number;
55
+ readonly iat?: number;
56
+ /** Present only when the `profile` scope was granted. Mutable, display only. */
57
+ readonly preferred_username?: string;
58
+ /** Present only when the `email` scope was granted. Mutable, display only. */
59
+ readonly email?: string;
60
+ readonly [claim: string]: unknown;
61
+ }
62
+ /**
63
+ * Persistence hook the host supplies so a refreshed token survives the
64
+ * process. Both methods may be async. The core never calls anything else.
65
+ */
66
+ export interface TokenStore {
67
+ /** Loads the stored set, or `null` when the caller has never signed in. */
68
+ load(): TokenSet | null | Promise<TokenSet | null>;
69
+ /** Persists a set after a login or a refresh. */
70
+ save(tokens: TokenSet): void | Promise<void>;
71
+ /** Removes the stored set on logout or on an unrecoverable refresh failure. */
72
+ clear(): void | Promise<void>;
73
+ }
74
+ /**
75
+ * Every scope this authorization server knows about.
76
+ *
77
+ * An unknown scope is rejected at the device authorization request, so a typo
78
+ * fails fast at `start()` rather than silently at first use. Ask for the
79
+ * narrowest set the product actually uses: every extra scope makes the
80
+ * approval page scarier for no benefit.
81
+ */
82
+ export declare const OMS_SCOPES: readonly ["openid", "profile", "email", "tools:read", "tools:write", "storage:read", "storage:write", "tickets:write"];
83
+ /** One of {@link OMS_SCOPES}. */
84
+ export type OmsScope = (typeof OMS_SCOPES)[number];
85
+ /** Refresh this long before the real expiry unless the host says otherwise. */
86
+ export declare const DEFAULT_REFRESH_SKEW_MS = 60000;
87
+ /**
88
+ * Wraps a constant token. This is what `new Oms({ token })` builds.
89
+ *
90
+ * Accepts both credential kinds: an opaque session UUID and a doorkeeper
91
+ * access token look identical on the wire.
92
+ */
93
+ export declare function staticToken(token: string | null): TokenProvider;
94
+ /**
95
+ * Wraps a plain function as a provider, for a host that already has its own
96
+ * lookup and does not want to build an object.
97
+ */
98
+ export declare function tokenFromFunction(fn: () => string | null | Promise<string | null>): TokenProvider;
99
+ /** Options for {@link OAuthTokenProvider} and {@link refreshingTokenProvider}. */
100
+ export interface RefreshingTokenOptions {
101
+ /**
102
+ * Where the set is read from and written back to. Omit it to keep the set in
103
+ * memory only, seeded from {@link RefreshingTokenOptions.tokens}.
104
+ */
105
+ readonly store?: TokenStore;
106
+ /**
107
+ * Initial set, when there is no {@link TokenStore}. Mutually exclusive with
108
+ * `store`; passing both throws.
109
+ */
110
+ readonly tokens?: TokenSet | null;
111
+ /**
112
+ * Exchanges a refresh token for a new set. Usually `auth.refresh` - but see
113
+ * the warning on {@link OAuthTokenProvider}: it must come from a client that
114
+ * does NOT carry this provider.
115
+ */
116
+ readonly refresh: (refreshToken: string) => Promise<TokenSet>;
117
+ /**
118
+ * Refresh this many milliseconds before the real expiry, so a token does not
119
+ * die mid-flight. Defaults to {@link DEFAULT_REFRESH_SKEW_MS}.
120
+ */
121
+ readonly skewMs?: number;
122
+ }
123
+ /**
124
+ * A {@link TokenProvider} that holds an OAuth {@link TokenSet} and renews it on
125
+ * its own, shortly before it expires.
126
+ *
127
+ * Concurrency is the whole point. Ten in-flight requests that all notice the
128
+ * same expiry share ONE refresh call: every caller awaits a single in-flight
129
+ * promise. Without that, nine of them burn a rotated refresh token and the
130
+ * user is signed out.
131
+ *
132
+ * ## Wire the `refresh` callback to a client WITHOUT this provider
133
+ *
134
+ * `POST /oauth/token` authenticates with `client_id` in the body and must not
135
+ * carry an `Authorization` header at all. More importantly, a refresh callback
136
+ * that talks through the same client this provider feeds is a deadlock: the
137
+ * refresh request asks the provider for a token, the provider is mid-refresh,
138
+ * and the two wait for each other forever.
139
+ *
140
+ * Build two clients - one anonymous for the OAuth endpoints, one credentialed
141
+ * for everything else:
142
+ *
143
+ * ```ts
144
+ * const anon = new Oms({ baseUrl, fetch });
145
+ * const tokens = new OAuthTokenProvider({
146
+ * store,
147
+ * refresh: (refreshToken) => anon.auth.refresh(refreshToken, { clientId }),
148
+ * });
149
+ * const oms = new Oms({ baseUrl, fetch, tokens });
150
+ * ```
151
+ *
152
+ * Wiring it the other way is caught: the provider notices the re-entrant call
153
+ * and throws an {@link OmsError} explaining this, rather than hanging.
154
+ */
155
+ export declare class OAuthTokenProvider implements TokenProvider {
156
+ private readonly store;
157
+ private readonly refreshFn;
158
+ private readonly skewMs;
159
+ /** Last set we know about. `undefined` means "never read the store yet". */
160
+ private cached;
161
+ /** The one refresh every concurrent caller waits on. */
162
+ private inFlight;
163
+ /** When the last successful refresh landed, epoch milliseconds. */
164
+ private lastRefreshAt;
165
+ /**
166
+ * True only for the synchronous instant in which the refresh callback is
167
+ * being invoked. A `getToken` that arrives inside that instant can only be
168
+ * the refresh request asking for a credential, which is the deadlock this
169
+ * class refuses to enter. Nothing else can observe it: no `await` runs
170
+ * between setting and clearing it.
171
+ */
172
+ private invokingRefresh;
173
+ constructor(options: RefreshingTokenOptions);
174
+ /**
175
+ * The token the transport should send, refreshing first when the stored one
176
+ * is expired or about to be.
177
+ *
178
+ * Returns `null` when nobody is signed in. Returns the stored access token
179
+ * unchanged when there is no refresh token to renew it with: letting the API
180
+ * answer 401 says more than sending nothing at all.
181
+ */
182
+ getToken(): Promise<string | null>;
183
+ /**
184
+ * The API rejected the token we just handed out. Renew once and tell the
185
+ * transport whether the retry is worth making.
186
+ *
187
+ * Never throws: a failed renewal returns `false` so the original
188
+ * `OmsAuthError` reaches the caller, which is the error that says "sign in
189
+ * again". The store is cleared on the way out.
190
+ */
191
+ onUnauthorized(): Promise<boolean>;
192
+ /**
193
+ * The set as last seen, without touching the store or the network.
194
+ *
195
+ * `null` before the first {@link getToken} and after a {@link clear}. Read it
196
+ * for display (the granted scopes, the expiry); never to decide whether a
197
+ * request may go out.
198
+ */
199
+ peek(): TokenSet | null;
200
+ /** Adopts a set - after a device flow completes - and persists it. */
201
+ set(tokens: TokenSet): Promise<void>;
202
+ /** Forgets the set here and in the store. Sign-out, or a dead grant. */
203
+ clear(): Promise<void>;
204
+ /**
205
+ * Reads the store, using the cached set while it is still good.
206
+ *
207
+ * Re-reading whenever the cached set is expired is what lets a second
208
+ * process pick up a refresh the first one already performed.
209
+ */
210
+ private read;
211
+ /**
212
+ * The single in-flight refresh every concurrent caller shares.
213
+ *
214
+ * `force` skips the "somebody already refreshed" shortcut, for the one case
215
+ * where a live-looking set is known to be dead: the API answered 401 with it.
216
+ */
217
+ private renew;
218
+ private performRefresh;
219
+ private forget;
220
+ }
221
+ /**
222
+ * Builds a provider that refreshes an expiring {@link TokenSet} on its own.
223
+ *
224
+ * Sugar over `new OAuthTokenProvider(options)`; read that class's notes before
225
+ * wiring the `refresh` callback.
226
+ */
227
+ export declare function refreshingTokenProvider(options: RefreshingTokenOptions): OAuthTokenProvider;
228
+ /**
229
+ * Decodes an OIDC id token's claims WITHOUT verifying its signature.
230
+ *
231
+ * Only safe for reading `sub` out of a token this client just received over
232
+ * TLS from the issuer. Never use it to authorise anything.
233
+ *
234
+ * The device grant's id token carries no `nonce` - there is nowhere to put one
235
+ * in RFC 8628 - and it expires 120 seconds after issue. Read `sub` on arrival
236
+ * and never send it anywhere.
237
+ *
238
+ * @throws {OmsError} when the token is not three base64url segments, when the
239
+ * payload is not JSON, or when `sub` is missing. A token with no `sub` is
240
+ * useless: it is the only identifier that is safe to key anything on.
241
+ */
242
+ export declare function decodeIdToken(idToken: string): IdentityClaims;
243
+ /**
244
+ * Whether a set is expired, or will be within `skewMs`.
245
+ *
246
+ * A set with no `expiresAt` never expires - that is how a legacy opaque
247
+ * session token behaves when it is carried in this shape.
248
+ */
249
+ export declare function isExpired(tokens: TokenSet, skewMs?: number, now?: number): boolean;
250
+ /**
251
+ * Builds a {@link TokenSet} from a raw OAuth token endpoint response,
252
+ * converting `expires_in` seconds into an absolute `expiresAt`.
253
+ *
254
+ * This is the ONLY place that touches `expires_in`. `now` defaults to the
255
+ * moment of the call, which makes `expiresAt` slightly conservative - the
256
+ * server measured the lifetime from issue, and that is the right direction to
257
+ * be wrong in.
258
+ *
259
+ * @throws {OmsError} when the body carries no `access_token`.
260
+ */
261
+ export declare function tokenSetFromResponse(body: unknown, now?: number): TokenSet;
262
+ /** The scopes a {@link TokenSet} carries, split out of its space-separated `scope`. */
263
+ export declare function scopesOf(tokens: TokenSet): string[];
264
+ /** Splits a scope value the server sent, in either shape, into a list. */
265
+ export declare function parseScopes(value: unknown): string[];
266
+ /**
267
+ * An in-memory {@link TokenStore}. Useful for tests and for a Worker that
268
+ * holds a token for the length of one request.
269
+ */
270
+ export declare function memoryTokenStore(initial?: TokenSet | null): TokenStore;
271
+ /**
272
+ * An error the authorization server named itself, in the RFC 6749 §5.2 shape:
273
+ * `{ "error": "...", "error_description": "...", "error_uri": "..." }`.
274
+ *
275
+ * Branch on {@link OmsOAuthError.error}. NEVER on the message: the
276
+ * descriptions are I18n strings and they change.
277
+ */
278
+ export declare class OmsOAuthError extends OmsError {
279
+ /** The OAuth error code, e.g. `"invalid_grant"`. The only field to branch on. */
280
+ readonly error: string;
281
+ /** The server's human sentence, when it sent one. Display only. */
282
+ readonly description: string | undefined;
283
+ /** `error_uri`, when the server sent one. */
284
+ readonly errorUri: string | undefined;
285
+ /** HTTP status it arrived with. `invalid_client` is 401; everything else 400. */
286
+ readonly status: number;
287
+ constructor(input: {
288
+ error: string;
289
+ description?: string;
290
+ errorUri?: string;
291
+ status: number;
292
+ method?: string;
293
+ url?: string;
294
+ cause?: unknown;
295
+ });
296
+ /**
297
+ * OAuth errors are decisions, not faults. Only the two the spec reserves for
298
+ * a struggling server are worth trying again.
299
+ */
300
+ get retryable(): boolean;
301
+ toJSON(): Record<string, unknown>;
302
+ }
303
+ /**
304
+ * Recognises an OAuth error inside whatever the transport threw.
305
+ *
306
+ * Deliberately narrow: only 400 and 401 bodies are read as OAuth errors. A 429
307
+ * comes from rack-attack with the body `{"error":"rate_limited"}`, and that
308
+ * `error` key is NOT an OAuth code - reading it as one abandons a perfectly
309
+ * live device flow. It stays an {@link OmsQuotaError} and the caller handles
310
+ * it as a rate limit.
311
+ *
312
+ * @returns The typed error, or `undefined` when this was not an OAuth failure.
313
+ */
314
+ export declare function oauthErrorFrom(thrown: unknown): OmsOAuthError | undefined;
315
+ /** What a 403 `insufficient_scope` challenge said was missing. */
316
+ export interface InsufficientScope {
317
+ /**
318
+ * The scopes the endpoint needed. EMPTY when the server named none, which
319
+ * means the endpoint has not been opened to OAuth clients at all - a backend
320
+ * gap, not a client bug. The two cases must read differently to the user.
321
+ */
322
+ readonly required: string[];
323
+ /** The `realm` parameter, when present. */
324
+ readonly realm: string | undefined;
325
+ }
326
+ /**
327
+ * Reads the `WWW-Authenticate` challenge of a 403 that rejected an OAuth token
328
+ * for want of a scope.
329
+ *
330
+ * The fix for this error is a fresh authorization with a wider scope set, not
331
+ * a change to the account's permissions, and the message shown to the user
332
+ * should say so.
333
+ *
334
+ * Note for browser hosts: `WWW-Authenticate` is not a CORS-safelisted response
335
+ * header, so a cross-origin `fetch` will not expose it unless the server lists
336
+ * it in `Access-Control-Expose-Headers`. This returns `undefined` then.
337
+ */
338
+ export declare function readInsufficientScope(error: unknown): InsufficientScope | undefined;
339
+ /**
340
+ * `POST`s an `application/x-www-form-urlencoded` body to an `/oauth/*` endpoint
341
+ * and returns the parsed JSON.
342
+ *
343
+ * The OAuth endpoints do not take JSON, which is why this exists next to
344
+ * `ApiClient.post` rather than using it. Blank values are dropped rather than
345
+ * sent empty, because doorkeeper reads `""` as a present-but-invalid parameter.
346
+ *
347
+ * Retries are OFF and stay off. Replaying `POST /oauth/token` after a lost
348
+ * response is not safe: the server may have rotated the refresh token already,
349
+ * and the replay would spend the old one and sign the user out. Callers that
350
+ * want to try again own the timing - `DeviceFlow.wait` does exactly that.
351
+ *
352
+ * These endpoints authenticate with `client_id` in the body and want no
353
+ * `Authorization` header. Call them through a client with no credential; see
354
+ * the note on {@link OAuthTokenProvider}.
355
+ */
356
+ export declare function oauthPost(http: ApiClient, path: string, params: Record<string, string | undefined>, options?: RequestOptions): Promise<unknown>;