@checkcourt/sdk 0.0.0-stage → 0.3.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 CheckCourt
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,231 @@
1
- # Temporary Holding Version
1
+ # CheckCourt TypeScript SDK
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ `@checkcourt/sdk` is the official TypeScript SDK for the [CheckCourt](https://checkcourt.de)
4
+ app platform. It takes care of the parts of an integration that are easy to get wrong:
5
+
6
+ - a typed client for every endpoint under `/api/v1`, generated from the OpenAPI spec
7
+ - API key, installation token and member token authentication, with token caching and
8
+ safe refresh rotation
9
+ - OAuth 2.1 with PKCE for member apps
10
+ - webhook signature verification with typed events
11
+ - UI extensions: context token and request verification, a builder for declarative UI
12
+ documents, and a browser helper for iframe extensions
13
+ - a fully typed app manifest
14
+
15
+ Everything the SDK does can also be built with plain HTTP. The protocol is described in the
16
+ [developer documentation](https://docs.checkcourt.de/docs/developer).
17
+
18
+ The SDK is an ES module for Node.js 20 or later and ships its own type definitions. It uses
19
+ WebCrypto, so webhook and extension verification also run on edge runtimes such as
20
+ Cloudflare Workers, Vercel Edge or Deno. Its only runtime dependency is `openapi-fetch`.
21
+
22
+ ## Install
23
+
24
+ The package is not on npm yet. Until it is, install a tagged release straight from GitHub:
25
+
26
+ ```bash
27
+ npm install github:CheckCourt/sdk#v0.3.1
28
+ ```
29
+
30
+ npm builds the package on install. Once it is published, the package name will be
31
+ `@checkcourt/sdk`:
32
+
33
+ ```bash
34
+ npm install @checkcourt/sdk # coming soon
35
+ ```
36
+
37
+ ## Quick start
38
+
39
+ ### API client with installation auth (club apps)
40
+
41
+ ```ts
42
+ import { createCheckCourtClient, getInstallation, installationAuth, unwrap } from "@checkcourt/sdk";
43
+
44
+ const client = createCheckCourtClient({
45
+ auth: installationAuth({
46
+ clientId: process.env.CHECKCOURT_CLIENT_ID!, // cca_app_…
47
+ clientSecret: process.env.CHECKCOURT_CLIENT_SECRET!, // ccas_…
48
+ installationId: "inst_123", // from the app.installed event
49
+ }),
50
+ });
51
+
52
+ const installation = await getInstallation(client);
53
+ const { courts } = await unwrap(client.GET("/courts"));
54
+ ```
55
+
56
+ `installationAuth` fetches a short-lived installation token when needed, caches it and
57
+ renews it shortly before it expires. Use `apiKeyAuth(key)` for your own club's API key and
58
+ `userAuth({ … })` for member tokens from OAuth. Calls return `{ data, error, response }`;
59
+ `unwrap()` returns `data` or throws a `CheckCourtApiError`. Requests are retried once
60
+ after a `401` with a fresh token and with backoff after a `429`.
61
+
62
+ ### Verify a webhook
63
+
64
+ ```ts
65
+ import { SIGNATURE_HEADER, isEventType, verifyWebhook } from "@checkcourt/sdk";
66
+
67
+ export async function POST(request: Request) {
68
+ const event = await verifyWebhook({
69
+ secret: process.env.CHECKCOURT_WEBHOOK_SECRET!, // whsec_…
70
+ rawBody: await request.text(),
71
+ signatureHeader: request.headers.get(SIGNATURE_HEADER),
72
+ });
73
+
74
+ if (isEventType(event, "booking.created")) {
75
+ console.log("new booking", event.data.booking_id);
76
+ }
77
+ return new Response(null, { status: 200 });
78
+ }
79
+ ```
80
+
81
+ `verifyWebhook` throws a `WebhookSignatureError` when the signature or timestamp does not
82
+ check out. Deduplicate on `event.id`: retries carry the same id.
83
+
84
+ ### Declarative extensions and the `ui` builder
85
+
86
+ ```ts
87
+ import { ExtensionVerificationError, toast, ui, verifyExtensionRequest } from "@checkcourt/sdk";
88
+
89
+ export async function POST(request: Request) {
90
+ let ext;
91
+ try {
92
+ ext = await verifyExtensionRequest({
93
+ secret: process.env.CHECKCOURT_WEBHOOK_SECRET!,
94
+ rawBody: await request.text(),
95
+ headers: request.headers,
96
+ });
97
+ } catch (err) {
98
+ if (err instanceof ExtensionVerificationError) return new Response(null, { status: 401 });
99
+ throw err;
100
+ }
101
+
102
+ const { context } = ext;
103
+ if (context.point !== "booking.detail.panel") return new Response(null, { status: 404 });
104
+
105
+ if (!(await courtHasDoor(context.subject.id))) {
106
+ // Nothing relevant for this booking: CheckCourt shows no card at all.
107
+ return Response.json(ui.hidden());
108
+ }
109
+
110
+ if (ext.kind === "action" && ext.actionId === "door.open") {
111
+ await openDoor(context.subject.id);
112
+ return Response.json(ui.doc([ui.text("The door is open.")], { toast: toast.success("Door opened") }));
113
+ }
114
+
115
+ return Response.json(
116
+ ui.doc([
117
+ ui.heading("Court door"),
118
+ ui.row([ui.stat("Opened today", "12"), ui.badge("Online", "secondary")]),
119
+ ui.button("Open door", "door.open"),
120
+ ]),
121
+ );
122
+ }
123
+ ```
124
+
125
+ Compare `context.installation_id` and `context.tenant_id` with what you stored from
126
+ `app.installed` before you act on a request. For the context token alone (for example in
127
+ the backend of an iframe extension), use `verifyExtensionContext(token, secret)`.
128
+
129
+ ### OAuth with PKCE (member apps)
130
+
131
+ ```ts
132
+ import { buildAuthorizeUrl, createPkcePair, createState, exchangeCode, parseAuthorizeCallback } from "@checkcourt/sdk";
133
+
134
+ // 1. Redirect the member to CheckCourt
135
+ const pkce = await createPkcePair();
136
+ const state = createState();
137
+ // keep pkce.verifier and state in the member's session
138
+ const authorizeUrl = buildAuthorizeUrl({
139
+ clientId: process.env.CHECKCOURT_CLIENT_ID!,
140
+ redirectUri: "https://calendar.example.com/callback",
141
+ state,
142
+ codeChallenge: pkce.challenge,
143
+ });
144
+
145
+ // 2. In the callback, check state and exchange the code
146
+ const { code } = parseAuthorizeCallback(new URL(callbackUrl), state);
147
+ const tokens = await exchangeCode({
148
+ clientId: process.env.CHECKCOURT_CLIENT_ID!,
149
+ clientSecret: process.env.CHECKCOURT_CLIENT_SECRET!,
150
+ code,
151
+ redirectUri: "https://calendar.example.com/callback",
152
+ codeVerifier: pkce.verifier,
153
+ });
154
+ ```
155
+
156
+ Pass the stored tokens to `userAuth({ tokens, onTokens, … })`; it refreshes them with
157
+ rotation and calls `onTokens` with every new set, which you must persist.
158
+
159
+ ### iframe extensions (browser)
160
+
161
+ ```ts
162
+ import { connectExtensionFrame } from "@checkcourt/sdk/iframe";
163
+
164
+ const frame = connectExtensionFrame(); // keeps the height in sync and applies the theme
165
+
166
+ // Send frame.context to your backend and verify it there, never in the browser.
167
+ await fetch("/api/session", { method: "POST", body: JSON.stringify({ context: frame.context }) });
168
+
169
+ frame.toast("success", "Saved");
170
+ frame.navigate("/booking?date=2026-10-07");
171
+ ```
172
+
173
+ Import the browser entry point `@checkcourt/sdk/iframe` only; it needs no secrets.
174
+
175
+ ## Entry points
176
+
177
+ | Import | Runs in | Contents |
178
+ |---|---|---|
179
+ | `@checkcourt/sdk` | Server | Everything except the iframe part |
180
+ | `@checkcourt/sdk/oauth` | Server | OAuth and installation tokens |
181
+ | `@checkcourt/sdk/webhooks` | Server, edge | Webhook verification and event types |
182
+ | `@checkcourt/sdk/extensions` | Server, edge | Context tokens, request verification, UI builder |
183
+ | `@checkcourt/sdk/manifest` | Anywhere | `defineManifest` and constants |
184
+ | `@checkcourt/sdk/iframe` | Browser | `connectExtensionFrame` and messages |
185
+
186
+ Client secrets, webhook secrets and refresh tokens belong on your server only.
187
+
188
+ ## Documentation
189
+
190
+ Full guides and the API reference: <https://docs.checkcourt.de/docs/developer/sdk>
191
+
192
+ ## Versioning
193
+
194
+ The SDK follows semantic versioning but is still in `0.x`: minor releases may contain
195
+ breaking changes until 1.0. Pin a tag and read the [changelog](CHANGELOG.md) before you
196
+ upgrade.
197
+
198
+ The exported constant `OPENAPI_SPEC_SHA256` is the SHA-256 of the spec the bundled types
199
+ were generated from, in the form served at `https://app.checkcourt.de/api/v1/openapi`.
200
+ Compare it with a hash of that response to see whether the platform has changed since this
201
+ release.
202
+
203
+ ## Security
204
+
205
+ Please do not report security issues in public GitHub issues. Report them privately to
206
+ CheckCourt support at [support@checkcourt.de](mailto:support@checkcourt.de).
207
+
208
+ ## Contributing
209
+
210
+ Bug reports and pull requests are welcome.
211
+
212
+ ```bash
213
+ npm install
214
+ npm run typecheck
215
+ npm test
216
+ npm run build
217
+ ```
218
+
219
+ The API types in `src/generated/` are generated from the live OpenAPI spec:
220
+
221
+ ```bash
222
+ npm run generate # https://app.checkcourt.de/api/v1/openapi
223
+ CHECKCOURT_OPENAPI=./openapi.json npm run generate # a local file or another URL
224
+ ```
225
+
226
+ The generated types follow the platform. A release may ship types for endpoints that are
227
+ about to be deployed, so do not regenerate them in an unrelated pull request.
228
+
229
+ ## License
230
+
231
+ [MIT](LICENSE)
package/dist/auth.d.ts ADDED
@@ -0,0 +1,54 @@
1
+ import { type FetchLike, type InstallationToken, type UserTokenSet } from "./oauth.js";
2
+ export interface AuthContext {
3
+ baseUrl?: string;
4
+ fetch?: FetchLike;
5
+ }
6
+ /**
7
+ * How the client gets its bearer token. Share one strategy object between clients (for example
8
+ * one per club via `tenantId`) to share its token cache and refresh lock.
9
+ */
10
+ export interface AuthStrategy {
11
+ getAccessToken(context?: AuthContext): Promise<string>;
12
+ /** Called after a 401 with the token that failed; true if a retry with a fresh token can help. */
13
+ invalidate?(token: string): boolean;
14
+ }
15
+ /** A management (`ck_mgmt_…`) or personal (`ck_user_…`) API key. */
16
+ export declare function apiKeyAuth(apiKey: string): AuthStrategy;
17
+ interface CredentialOptions {
18
+ clientId: string;
19
+ clientSecret: string;
20
+ baseUrl?: string;
21
+ fetch?: FetchLike;
22
+ /** Renew this long before expiry. Default 60. */
23
+ refreshMarginSeconds?: number;
24
+ }
25
+ export interface InstallationAuth extends AuthStrategy {
26
+ /** The cached token, fetching one if needed. */
27
+ getToken(context?: AuthContext): Promise<InstallationToken>;
28
+ }
29
+ /** Club installation: exchanges client credentials for `cca_` tokens, cached until shortly before expiry. */
30
+ export declare function installationAuth(options: CredentialOptions & {
31
+ installationId: string;
32
+ }): InstallationAuth;
33
+ /** What you need to persist per member; a full `UserTokenSet` fits too. */
34
+ export type StoredUserTokens = Pick<UserTokenSet, "accessToken" | "refreshToken" | "expiresAt"> & Partial<Pick<UserTokenSet, "scope" | "installations">>;
35
+ export interface UserAuth extends AuthStrategy {
36
+ /** The tokens currently in use (after any rotation). */
37
+ getTokens(): StoredUserTokens;
38
+ /** Forces a refresh now; concurrent calls share one request. */
39
+ refresh(context?: AuthContext): Promise<StoredUserTokens>;
40
+ }
41
+ /**
42
+ * Member connection (`ccu_` / `ccr_`). Refreshes shortly before expiry with rotation; concurrent
43
+ * requests share a single refresh so the chain is never reused. `onTokens` runs after every
44
+ * rotation and before the new token is used: persist there, the old refresh token is spent.
45
+ *
46
+ * Errors: `TokenRevokedError` (`invalid_grant`, also after the 180-day chain limit) is final for
47
+ * this strategy. `AppTemporarilyUnavailableError` leaves the tokens untouched; try again later.
48
+ * The lock is per process: refresh one member in one place at a time.
49
+ */
50
+ export declare function userAuth(options: CredentialOptions & {
51
+ tokens: StoredUserTokens;
52
+ onTokens: (tokens: UserTokenSet) => void | Promise<void>;
53
+ }): UserAuth;
54
+ export {};
package/dist/auth.js ADDED
@@ -0,0 +1,96 @@
1
+ import { TokenRevokedError } from "./errors.js";
2
+ import { fetchInstallationToken, refreshTokens, } from "./oauth.js";
3
+ /** A management (`ck_mgmt_…`) or personal (`ck_user_…`) API key. */
4
+ export function apiKeyAuth(apiKey) {
5
+ return { getAccessToken: async () => apiKey };
6
+ }
7
+ /** Club installation: exchanges client credentials for `cca_` tokens, cached until shortly before expiry. */
8
+ export function installationAuth(options) {
9
+ const margin = (options.refreshMarginSeconds ?? 60) * 1000;
10
+ let cached = null;
11
+ let inflight = null;
12
+ const getToken = async (context = {}) => {
13
+ if (cached && cached.expiresAt - margin > Date.now())
14
+ return cached;
15
+ inflight ??= fetchInstallationToken({
16
+ clientId: options.clientId,
17
+ clientSecret: options.clientSecret,
18
+ installationId: options.installationId,
19
+ baseUrl: options.baseUrl ?? context.baseUrl,
20
+ fetch: options.fetch ?? context.fetch,
21
+ })
22
+ .then((token) => (cached = token))
23
+ .finally(() => {
24
+ inflight = null;
25
+ });
26
+ return inflight;
27
+ };
28
+ return {
29
+ getToken,
30
+ getAccessToken: async (context) => (await getToken(context)).accessToken,
31
+ invalidate(token) {
32
+ if (cached?.accessToken === token)
33
+ cached = null;
34
+ return true;
35
+ },
36
+ };
37
+ }
38
+ /**
39
+ * Member connection (`ccu_` / `ccr_`). Refreshes shortly before expiry with rotation; concurrent
40
+ * requests share a single refresh so the chain is never reused. `onTokens` runs after every
41
+ * rotation and before the new token is used: persist there, the old refresh token is spent.
42
+ *
43
+ * Errors: `TokenRevokedError` (`invalid_grant`, also after the 180-day chain limit) is final for
44
+ * this strategy. `AppTemporarilyUnavailableError` leaves the tokens untouched; try again later.
45
+ * The lock is per process: refresh one member in one place at a time.
46
+ */
47
+ export function userAuth(options) {
48
+ const margin = (options.refreshMarginSeconds ?? 60) * 1000;
49
+ let current = { ...options.tokens };
50
+ let revoked = null;
51
+ let inflight = null;
52
+ const refresh = (context = {}) => {
53
+ if (revoked)
54
+ return Promise.reject(revoked);
55
+ inflight ??= (async () => {
56
+ try {
57
+ const next = await refreshTokens({
58
+ clientId: options.clientId,
59
+ clientSecret: options.clientSecret,
60
+ refreshToken: current.refreshToken,
61
+ baseUrl: options.baseUrl ?? context.baseUrl,
62
+ fetch: options.fetch ?? context.fetch,
63
+ });
64
+ current = next;
65
+ await options.onTokens(next);
66
+ return next;
67
+ }
68
+ catch (err) {
69
+ if (err instanceof TokenRevokedError)
70
+ revoked = err;
71
+ throw err;
72
+ }
73
+ })().finally(() => {
74
+ inflight = null;
75
+ });
76
+ return inflight;
77
+ };
78
+ return {
79
+ getTokens: () => current,
80
+ refresh,
81
+ async getAccessToken(context) {
82
+ if (revoked)
83
+ throw revoked;
84
+ if (current.expiresAt - margin > Date.now())
85
+ return current.accessToken;
86
+ return (await refresh(context)).accessToken;
87
+ },
88
+ invalidate(token) {
89
+ if (revoked)
90
+ return false;
91
+ if (current.accessToken === token)
92
+ current = { ...current, expiresAt: 0 };
93
+ return true;
94
+ },
95
+ };
96
+ }
@@ -0,0 +1,38 @@
1
+ import { type Client } from "openapi-fetch";
2
+ import type { AuthStrategy } from "./auth.js";
3
+ import type { FetchLike } from "./oauth.js";
4
+ import type { paths } from "./generated/schema.js";
5
+ export declare const TENANT_HEADER = "X-Tenant-Id";
6
+ export interface RetryOptions {
7
+ /** Retries after a 429. Default 2. */
8
+ maxRetries?: number;
9
+ /** Wait without `Retry-After`: this doubled per attempt. Default 1000. */
10
+ baseDelayMs?: number;
11
+ /** Longer waits are not attempted; the 429 is returned instead. Default 60000. */
12
+ maxDelayMs?: number;
13
+ }
14
+ export interface CheckCourtClientOptions {
15
+ /** Defaults to https://app.checkcourt.de; a trailing `/api/v1` is accepted. */
16
+ baseUrl?: string;
17
+ auth: AuthStrategy;
18
+ /** Sent as `X-Tenant-Id` unless a request sets its own. Needed for member tokens with several clubs and personal keys. */
19
+ tenantId?: string;
20
+ fetch?: FetchLike;
21
+ /** `false` disables retries on 429. */
22
+ retry?: RetryOptions | false;
23
+ headers?: Record<string, string>;
24
+ }
25
+ export type CheckCourtClient = Client<paths>;
26
+ export declare function retryAfterMs(value: string | null, now?: number): number | null;
27
+ /**
28
+ * Typed `/api/v1` client (openapi-fetch). Calls return `{ data, error, response }`; wrap them in
29
+ * `unwrap()` to get `data` or a thrown `CheckCourtApiError`. A 401 triggers one retry with a fresh
30
+ * token; 429 is retried with backoff, honouring `Retry-After`.
31
+ */
32
+ export declare function createCheckCourtClient(options: CheckCourtClientOptions): CheckCourtClient;
33
+ /** Returns `data` of an openapi-fetch call or throws `CheckCourtApiError` with status, code and message. */
34
+ export declare function unwrap<T>(call: Promise<{
35
+ data?: T;
36
+ error?: unknown;
37
+ response: Response;
38
+ }>): Promise<T>;
package/dist/client.js ADDED
@@ -0,0 +1,71 @@
1
+ import createClient, {} from "openapi-fetch";
2
+ import { CheckCourtApiError } from "./errors.js";
3
+ import { normalizeBaseUrl } from "./internal/base-url.js";
4
+ export const TENANT_HEADER = "X-Tenant-Id";
5
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
6
+ export function retryAfterMs(value, now = Date.now()) {
7
+ if (!value)
8
+ return null;
9
+ if (/^\d+$/.test(value.trim()))
10
+ return Number(value.trim()) * 1000;
11
+ const date = Date.parse(value);
12
+ return Number.isNaN(date) ? null : Math.max(0, date - now);
13
+ }
14
+ // Never awaited: under Next.js' patched fetch the body is a tee branch whose cancel() never settles.
15
+ function discard(response) {
16
+ try {
17
+ response.body?.cancel().catch(() => { });
18
+ }
19
+ catch {
20
+ // Already consumed or locked: nothing to free.
21
+ }
22
+ }
23
+ /**
24
+ * Typed `/api/v1` client (openapi-fetch). Calls return `{ data, error, response }`; wrap them in
25
+ * `unwrap()` to get `data` or a thrown `CheckCourtApiError`. A 401 triggers one retry with a fresh
26
+ * token; 429 is retried with backoff, honouring `Retry-After`.
27
+ */
28
+ export function createCheckCourtClient(options) {
29
+ const baseUrl = normalizeBaseUrl(options.baseUrl);
30
+ const doFetch = options.fetch ?? ((request) => fetch(request));
31
+ const retry = options.retry === false ? null : options.retry ?? {};
32
+ const maxRetries = retry?.maxRetries ?? 2;
33
+ const baseDelay = retry?.baseDelayMs ?? 1000;
34
+ const maxDelay = retry?.maxDelayMs ?? 60_000;
35
+ const context = { baseUrl, fetch: doFetch };
36
+ const authedFetch = async (request) => {
37
+ let retries = 0;
38
+ let reauthenticated = false;
39
+ for (;;) {
40
+ const attempt = request.clone();
41
+ const token = await options.auth.getAccessToken(context);
42
+ attempt.headers.set("Authorization", `Bearer ${token}`);
43
+ if (options.tenantId && !attempt.headers.has(TENANT_HEADER))
44
+ attempt.headers.set(TENANT_HEADER, options.tenantId);
45
+ const response = await doFetch(attempt);
46
+ if (response.status === 401 && !reauthenticated && options.auth.invalidate?.(token)) {
47
+ reauthenticated = true;
48
+ discard(response);
49
+ continue;
50
+ }
51
+ if (response.status === 429 && retry && retries < maxRetries) {
52
+ const delay = retryAfterMs(response.headers.get("Retry-After")) ?? baseDelay * 2 ** retries;
53
+ if (delay <= maxDelay) {
54
+ retries += 1;
55
+ discard(response);
56
+ await sleep(delay);
57
+ continue;
58
+ }
59
+ }
60
+ return response;
61
+ }
62
+ };
63
+ return createClient({ baseUrl: `${baseUrl}/api/v1`, fetch: authedFetch, headers: options.headers });
64
+ }
65
+ /** Returns `data` of an openapi-fetch call or throws `CheckCourtApiError` with status, code and message. */
66
+ export async function unwrap(call) {
67
+ const { data, error, response } = await call;
68
+ if (error !== undefined || !response.ok)
69
+ throw CheckCourtApiError.fromBody(response.status, error, response);
70
+ return data;
71
+ }
@@ -0,0 +1,74 @@
1
+ export type CheckCourtErrorKind = "CheckCourtError" | "CheckCourtApiError" | "OAuthError" | "TokenRevokedError" | "AppTemporarilyUnavailableError" | "WebhookSignatureError" | "ExtensionVerificationError";
2
+ declare const KIND: unique symbol;
3
+ /**
4
+ * Base class for every error the SDK throws on purpose. `instanceof` works across copies of the
5
+ * SDK (it checks a brand, not the prototype); `isCheckCourtError(err, kind)` does the same as a function.
6
+ */
7
+ export declare class CheckCourtError extends Error {
8
+ static readonly [KIND]: CheckCourtErrorKind;
9
+ static [Symbol.hasInstance](this: {
10
+ [KIND]?: CheckCourtErrorKind;
11
+ }, value: unknown): boolean;
12
+ constructor(message: string, options?: {
13
+ cause?: unknown;
14
+ });
15
+ }
16
+ export type ApiErrorCode = "UNAUTHORIZED" | "VALIDATION" | "FORBIDDEN" | "NOT_FOUND" | "BUSINESS_RULE" | "RATE_LIMITED" | (string & {});
17
+ /** A non-2xx answer from `/api/v1`. Branch on `code`; `message` is German and may change. */
18
+ export declare class CheckCourtApiError extends CheckCourtError {
19
+ static readonly [KIND]: CheckCourtErrorKind;
20
+ readonly status: number;
21
+ readonly code: ApiErrorCode;
22
+ readonly response?: Response;
23
+ constructor(status: number, code: ApiErrorCode, message: string, response?: Response);
24
+ static fromBody(status: number, body: unknown, response?: Response): CheckCourtApiError;
25
+ }
26
+ /** An RFC 6749 error from `/api/oauth/*`, e.g. `invalid_client` or `slow_down`. */
27
+ export declare class OAuthError extends CheckCourtError {
28
+ static readonly [KIND]: CheckCourtErrorKind;
29
+ readonly status: number;
30
+ readonly error: string;
31
+ readonly description: string;
32
+ constructor(status: number, error: string, description: string);
33
+ }
34
+ /**
35
+ * `invalid_grant`: the refresh token or code is dead (expired, revoked, reused, chain older than
36
+ * 180 days, member disconnected everywhere). Only a new authorization helps.
37
+ */
38
+ export declare class TokenRevokedError extends OAuthError {
39
+ static readonly [KIND]: CheckCourtErrorKind;
40
+ constructor(status: number, error: string, description: string);
41
+ }
42
+ /**
43
+ * `temporarily_unavailable` on refresh: the grant is still valid but nothing may run right now
44
+ * (connection paused, app suspended, account banned). The refresh token was not consumed.
45
+ */
46
+ export declare class AppTemporarilyUnavailableError extends OAuthError {
47
+ static readonly [KIND]: CheckCourtErrorKind;
48
+ constructor(status: number, error: string, description: string);
49
+ }
50
+ export declare function oauthErrorFrom(status: number, body: unknown): OAuthError;
51
+ export type WebhookSignatureFailure = "missing_header" | "malformed_header" | "timestamp_out_of_tolerance" | "signature_mismatch" | "invalid_payload";
52
+ export declare class WebhookSignatureError extends CheckCourtError {
53
+ static readonly [KIND]: CheckCourtErrorKind;
54
+ readonly reason: WebhookSignatureFailure;
55
+ constructor(reason: WebhookSignatureFailure, message: string);
56
+ }
57
+ export type ExtensionVerificationFailure = "malformed_token" | "unsupported_algorithm" | "invalid_signature" | "invalid_claims" | "expired" | "not_yet_valid" | "request_signature" | "invalid_body" | "context_mismatch";
58
+ export declare class ExtensionVerificationError extends CheckCourtError {
59
+ static readonly [KIND]: CheckCourtErrorKind;
60
+ readonly reason: ExtensionVerificationFailure;
61
+ constructor(reason: ExtensionVerificationFailure, message: string);
62
+ }
63
+ interface ErrorKinds {
64
+ CheckCourtError: CheckCourtError;
65
+ CheckCourtApiError: CheckCourtApiError;
66
+ OAuthError: OAuthError;
67
+ TokenRevokedError: TokenRevokedError;
68
+ AppTemporarilyUnavailableError: AppTemporarilyUnavailableError;
69
+ WebhookSignatureError: WebhookSignatureError;
70
+ ExtensionVerificationError: ExtensionVerificationError;
71
+ }
72
+ /** True for errors from any copy of the SDK; with `kind`, also for that class or a subclass of it. */
73
+ export declare function isCheckCourtError<K extends CheckCourtErrorKind = "CheckCourtError">(error: unknown, kind?: K): error is ErrorKinds[K];
74
+ export {};