@checkcourt/sdk 0.3.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -11
- package/dist/connections.d.ts +28 -0
- package/dist/connections.js +55 -0
- package/dist/events.d.ts +41 -1
- package/dist/events.js +17 -0
- package/dist/extensions.d.ts +59 -3
- package/dist/extensions.js +54 -3
- package/dist/generated/schema.d.ts +670 -0
- package/dist/generated/spec-hash.d.ts +1 -1
- package/dist/generated/spec-hash.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/manifest.d.ts +131 -3
- package/dist/manifest.js +68 -0
- package/dist/notifications.d.ts +29 -0
- package/dist/notifications.js +14 -0
- package/dist/ui.d.ts +101 -7
- package/dist/ui.js +119 -2
- package/dist/webhooks.d.ts +2 -2
- package/dist/webhooks.js +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ export { createCheckCourtClient, unwrap, retryAfterMs, TENANT_HEADER } from "./c
|
|
|
2
2
|
export type { CheckCourtClient, CheckCourtClientOptions, RetryOptions } from "./client.js";
|
|
3
3
|
export { apiKeyAuth, installationAuth, userAuth } from "./auth.js";
|
|
4
4
|
export { getInstallation, getMe, type AppInstallation, type Me } from "./installation.js";
|
|
5
|
+
export { deleteObjectMetadata, getObjectMetadata, publishAppEvent, putObjectMetadata, type ObjectMetadata, type ObjectMetadataEntry, type ObjectMetadataWritten, type PublishAppEventRequest, type PublishedAppEvent, } from "./connections.js";
|
|
6
|
+
export { sendNotification, NOTIFICATION_TITLE_MAX, NOTIFICATION_BODY_MAX, type SendNotificationInput, type SendNotificationResponse, } from "./notifications.js";
|
|
5
7
|
export type { AuthContext, AuthStrategy, InstallationAuth, StoredUserTokens, UserAuth } from "./auth.js";
|
|
6
8
|
export * from "./errors.js";
|
|
7
9
|
export * from "./oauth.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { createCheckCourtClient, unwrap, retryAfterMs, TENANT_HEADER } from "./client.js";
|
|
2
2
|
export { apiKeyAuth, installationAuth, userAuth } from "./auth.js";
|
|
3
3
|
export { getInstallation, getMe } from "./installation.js";
|
|
4
|
+
export { deleteObjectMetadata, getObjectMetadata, publishAppEvent, putObjectMetadata, } from "./connections.js";
|
|
5
|
+
export { sendNotification, NOTIFICATION_TITLE_MAX, NOTIFICATION_BODY_MAX, } from "./notifications.js";
|
|
4
6
|
export * from "./errors.js";
|
|
5
7
|
export * from "./oauth.js";
|
|
6
8
|
export * from "./webhooks.js";
|
package/dist/manifest.d.ts
CHANGED
|
@@ -32,6 +32,30 @@ export declare const EXTENSION_POINTS: {
|
|
|
32
32
|
readonly targets: readonly ["tenant"];
|
|
33
33
|
readonly kinds: readonly ["declarative"];
|
|
34
34
|
};
|
|
35
|
+
readonly "court.annotation": {
|
|
36
|
+
readonly targets: readonly ["tenant"];
|
|
37
|
+
readonly kinds: readonly ["declarative"];
|
|
38
|
+
};
|
|
39
|
+
readonly "member.list.column": {
|
|
40
|
+
readonly targets: readonly ["tenant"];
|
|
41
|
+
readonly kinds: readonly ["declarative"];
|
|
42
|
+
};
|
|
43
|
+
readonly "member.settings.section": {
|
|
44
|
+
readonly targets: readonly ["user"];
|
|
45
|
+
readonly kinds: readonly ["declarative", "iframe"];
|
|
46
|
+
};
|
|
47
|
+
readonly "booking.hint": {
|
|
48
|
+
readonly targets: readonly ["tenant"];
|
|
49
|
+
readonly kinds: readonly ["declarative"];
|
|
50
|
+
};
|
|
51
|
+
readonly "booking_plan.action": {
|
|
52
|
+
readonly targets: readonly ["tenant"];
|
|
53
|
+
readonly kinds: readonly ["declarative"];
|
|
54
|
+
};
|
|
55
|
+
readonly "sidebar.action": {
|
|
56
|
+
readonly targets: readonly ["tenant", "user"];
|
|
57
|
+
readonly kinds: readonly ["declarative"];
|
|
58
|
+
};
|
|
35
59
|
};
|
|
36
60
|
export type ExtensionPoint = keyof typeof EXTENSION_POINTS;
|
|
37
61
|
/** Scope the installation needs (in `tenantScopes` or `userScopes`) before the point hands it a subject id. */
|
|
@@ -42,21 +66,47 @@ export declare const EXTENSION_POINT_SCOPE: {
|
|
|
42
66
|
readonly "member.profile.section": "members:read";
|
|
43
67
|
readonly "dashboard.widget": null;
|
|
44
68
|
readonly "kiosk.tile": null;
|
|
69
|
+
readonly "court.annotation": "courts:read";
|
|
70
|
+
readonly "member.list.column": "members:read";
|
|
71
|
+
readonly "member.settings.section": null;
|
|
72
|
+
readonly "booking.hint": "bookings:read";
|
|
73
|
+
readonly "booking_plan.action": "courts:read";
|
|
74
|
+
readonly "sidebar.action": null;
|
|
45
75
|
};
|
|
76
|
+
/** Buttons CheckCourt draws from the manifest alone, before the app is ever called. */
|
|
77
|
+
export declare const STATIC_ACTION_POINTS: readonly ["booking_plan.action", "sidebar.action"];
|
|
78
|
+
export type StaticActionPoint = (typeof STATIC_ACTION_POINTS)[number];
|
|
79
|
+
/** Longest `label` of a static action. */
|
|
80
|
+
export declare const STATIC_ACTION_LABEL_MAX = 24;
|
|
81
|
+
/** lucide icon names a static action may use as `icon`. */
|
|
82
|
+
export declare const APP_ACTION_ICONS: readonly ["bell", "calendar", "calendar-check", "camera", "chart-column", "circle-help", "clipboard-list", "clock", "cloud-rain", "door-open", "file-text", "flag", "heart-pulse", "info", "key-round", "lightbulb", "link", "list-checks", "lock-open", "map-pin", "megaphone", "message-square", "receipt", "send", "sparkles", "star", "sun", "thermometer", "ticket", "triangle-alert", "trophy", "user-round", "users", "wallet", "wrench"];
|
|
83
|
+
export type AppActionIcon = (typeof APP_ACTION_ICONS)[number];
|
|
46
84
|
/** Every scope an app may request. Role, app, webhook, key, billing and AVV management are reserved for people. */
|
|
47
|
-
export declare const GRANTABLE_SCOPES: readonly ["courts:read", "courts:read_confidential", "courts:write", "bookings:read", "bookings:read_confidential", "bookings:write", "bookings:cancel", "bookings:edit", "bookings:export", "members:read", "members:read_confidential", "members:invite", "members:write", "members:delete", "teams:read", "teams:write", "policies:read", "policies:read_confidential", "policies:write", "categories:read", "categories:write", "invites:read", "invites:write", "settings:read", "settings:read_confidential", "settings:write", "guest_fees:read", "guest_fees:write", "audit:read", "announcements:read", "announcements:write", "events:read", "events:write", "posts:read", "posts:write", "posts:moderate", "court_layout:read", "court_layout:write", "work_hours:read", "work_hours:write", "work_hours:manage", "compliance:read", "kiosk:manage", "trainer_blocks:write", "analytics:read", "embeds:manage", "webhooks:read"];
|
|
85
|
+
export declare const GRANTABLE_SCOPES: readonly ["courts:read", "courts:read_confidential", "courts:write", "bookings:read", "bookings:read_confidential", "bookings:write", "bookings:cancel", "bookings:edit", "bookings:export", "members:read", "members:read_confidential", "members:invite", "members:write", "members:delete", "teams:read", "teams:write", "policies:read", "policies:read_confidential", "policies:write", "categories:read", "categories:write", "invites:read", "invites:write", "settings:read", "settings:read_confidential", "settings:write", "guest_fees:read", "guest_fees:write", "audit:read", "announcements:read", "announcements:write", "events:read", "events:write", "posts:read", "posts:write", "posts:moderate", "court_layout:read", "court_layout:write", "work_hours:read", "work_hours:write", "work_hours:manage", "compliance:read", "kiosk:manage", "trainer_blocks:write", "analytics:read", "embeds:manage", "webhooks:read", "notifications:send"];
|
|
48
86
|
export type GrantableScope = (typeof GRANTABLE_SCOPES)[number];
|
|
49
87
|
type ExtensionOf<P extends ExtensionPoint> = {
|
|
50
88
|
point: P;
|
|
51
89
|
kind: (typeof EXTENSION_POINTS)[P]["kinds"][number];
|
|
52
90
|
/** https; http://localhost is accepted outside production. */
|
|
53
91
|
url: string;
|
|
54
|
-
} & (P extends
|
|
92
|
+
} & (P extends StaticActionPoint ? {
|
|
93
|
+
/** Button text, 1 to 24 characters (`STATIC_ACTION_LABEL_MAX`). */
|
|
94
|
+
label: string;
|
|
95
|
+
icon?: AppActionIcon;
|
|
96
|
+
} : P extends "booking.action" ? {
|
|
55
97
|
label: string;
|
|
98
|
+
icon?: never;
|
|
99
|
+
} : P extends "member.list.column" ? {
|
|
100
|
+
label?: string;
|
|
101
|
+
icon?: never;
|
|
56
102
|
} : {
|
|
57
103
|
label?: string;
|
|
104
|
+
icon?: never;
|
|
58
105
|
});
|
|
59
|
-
/**
|
|
106
|
+
/**
|
|
107
|
+
* One entry of `extensions`; `kind`, `label` and `icon` are checked per point at compile time.
|
|
108
|
+
* Every point may appear once, except `booking.action`.
|
|
109
|
+
*/
|
|
60
110
|
export type ManifestExtension = {
|
|
61
111
|
[P in ExtensionPoint]: ExtensionOf<P>;
|
|
62
112
|
}[ExtensionPoint];
|
|
@@ -97,6 +147,72 @@ export interface DataProcessing {
|
|
|
97
147
|
storageLocation: "EU" | "non-EU";
|
|
98
148
|
avvRequired: boolean;
|
|
99
149
|
}
|
|
150
|
+
/** Club objects apps can attach metadata to. */
|
|
151
|
+
export declare const SHARED_OBJECT_TYPES: readonly ["booking", "court", "member"];
|
|
152
|
+
export type SharedObjectType = (typeof SHARED_OBJECT_TYPES)[number];
|
|
153
|
+
/** Read scope both the sharing and the reading app need in `tenantScopes` for an object type. */
|
|
154
|
+
export declare const SHARED_OBJECT_SCOPE: {
|
|
155
|
+
readonly booking: "bookings:read";
|
|
156
|
+
readonly court: "courts:read";
|
|
157
|
+
readonly member: "members:read";
|
|
158
|
+
};
|
|
159
|
+
/** Metadata keys and event names: `^[a-z][a-z0-9_]{0,39}$`. */
|
|
160
|
+
export declare const CONNECTION_NAME_PATTERN: RegExp;
|
|
161
|
+
/** Serialized size limit of a metadata value and of event data. */
|
|
162
|
+
export declare const MAX_SHARED_VALUE_BYTES = 4096;
|
|
163
|
+
/** A metadata key the app writes on club objects and lets other apps read once the club approves. */
|
|
164
|
+
export interface SharedMetadata {
|
|
165
|
+
key: string;
|
|
166
|
+
object: SharedObjectType;
|
|
167
|
+
/** Shown to the club when it approves a connection, 1 to 200 characters, e.g. "Videolink". */
|
|
168
|
+
description: string;
|
|
169
|
+
/** 1 to 10 categories, shown in the approval dialog, e.g. ["Videoaufzeichnung"]. */
|
|
170
|
+
data_categories: readonly string[];
|
|
171
|
+
}
|
|
172
|
+
/** Metadata of another app this app wants to read: that app's slug, the key and the object type. */
|
|
173
|
+
export interface ReadMetadata {
|
|
174
|
+
app: string;
|
|
175
|
+
key: string;
|
|
176
|
+
object: SharedObjectType;
|
|
177
|
+
}
|
|
178
|
+
export type AppEventProperty = {
|
|
179
|
+
type: "string";
|
|
180
|
+
description?: string;
|
|
181
|
+
/** Excludes `maxLength`. */
|
|
182
|
+
enum?: readonly string[];
|
|
183
|
+
maxLength?: number;
|
|
184
|
+
} | {
|
|
185
|
+
type: "number" | "integer";
|
|
186
|
+
description?: string;
|
|
187
|
+
minimum?: number;
|
|
188
|
+
maximum?: number;
|
|
189
|
+
} | {
|
|
190
|
+
type: "boolean";
|
|
191
|
+
description?: string;
|
|
192
|
+
};
|
|
193
|
+
/** JSON-Schema subset for event data: flat objects of strings, numbers, integers and booleans (at most 30 properties). */
|
|
194
|
+
export interface AppEventSchema {
|
|
195
|
+
type: "object";
|
|
196
|
+
/** Keys match `^[a-zA-Z][a-zA-Z0-9_]{0,39}$`. */
|
|
197
|
+
properties: Record<string, AppEventProperty>;
|
|
198
|
+
required?: readonly string[];
|
|
199
|
+
/** `false` rejects unknown keys. */
|
|
200
|
+
additionalProperties?: boolean;
|
|
201
|
+
}
|
|
202
|
+
/** An event the app publishes with `publishAppEvent`; subscribers receive it as `app.<your slug>.<name>`. */
|
|
203
|
+
export interface EmittedEvent {
|
|
204
|
+
name: string;
|
|
205
|
+
/** 1 to 200 characters, shown in the approval dialog. */
|
|
206
|
+
description: string;
|
|
207
|
+
data_categories: readonly string[];
|
|
208
|
+
/** Published data is validated against it. */
|
|
209
|
+
schema?: AppEventSchema;
|
|
210
|
+
}
|
|
211
|
+
/** An event of another app this app wants to receive. */
|
|
212
|
+
export interface SubscribedEvent {
|
|
213
|
+
app: string;
|
|
214
|
+
event: string;
|
|
215
|
+
}
|
|
100
216
|
export interface Manifest {
|
|
101
217
|
manifestVersion?: typeof MANIFEST_VERSION;
|
|
102
218
|
installTargets: readonly InstallTarget[];
|
|
@@ -110,6 +226,18 @@ export interface Manifest {
|
|
|
110
226
|
extensions?: readonly ManifestExtension[];
|
|
111
227
|
settingsSchema?: SettingsSchema;
|
|
112
228
|
dataProcessing: DataProcessing;
|
|
229
|
+
/** Metadata other apps may read (at most 20). Club installations only. */
|
|
230
|
+
shares?: {
|
|
231
|
+
metadata: readonly SharedMetadata[];
|
|
232
|
+
};
|
|
233
|
+
/** Metadata of other apps this app reads (at most 50). Club installations only. */
|
|
234
|
+
reads?: {
|
|
235
|
+
metadata: readonly ReadMetadata[];
|
|
236
|
+
};
|
|
237
|
+
/** Events this app publishes to connected apps (at most 20). Club installations only. */
|
|
238
|
+
emits?: readonly EmittedEvent[];
|
|
239
|
+
/** Events of other apps this app receives (at most 50). Club installations only. */
|
|
240
|
+
subscribes?: readonly SubscribedEvent[];
|
|
113
241
|
}
|
|
114
242
|
/**
|
|
115
243
|
* Identity function that gives editor completion and compile-time checks for a manifest.
|
package/dist/manifest.js
CHANGED
|
@@ -10,6 +10,12 @@ export const EXTENSION_POINTS = {
|
|
|
10
10
|
"dashboard.widget": { targets: ["tenant", "user"], kinds: ["declarative"] },
|
|
11
11
|
"booking.action": { targets: ["tenant", "user"], kinds: ["declarative"] },
|
|
12
12
|
"kiosk.tile": { targets: ["tenant"], kinds: ["declarative"] },
|
|
13
|
+
"court.annotation": { targets: ["tenant"], kinds: ["declarative"] },
|
|
14
|
+
"member.list.column": { targets: ["tenant"], kinds: ["declarative"] },
|
|
15
|
+
"member.settings.section": { targets: ["user"], kinds: ["declarative", "iframe"] },
|
|
16
|
+
"booking.hint": { targets: ["tenant"], kinds: ["declarative"] },
|
|
17
|
+
"booking_plan.action": { targets: ["tenant"], kinds: ["declarative"] },
|
|
18
|
+
"sidebar.action": { targets: ["tenant", "user"], kinds: ["declarative"] },
|
|
13
19
|
};
|
|
14
20
|
/** Scope the installation needs (in `tenantScopes` or `userScopes`) before the point hands it a subject id. */
|
|
15
21
|
export const EXTENSION_POINT_SCOPE = {
|
|
@@ -19,7 +25,55 @@ export const EXTENSION_POINT_SCOPE = {
|
|
|
19
25
|
"member.profile.section": "members:read",
|
|
20
26
|
"dashboard.widget": null,
|
|
21
27
|
"kiosk.tile": null,
|
|
28
|
+
"court.annotation": "courts:read",
|
|
29
|
+
"member.list.column": "members:read",
|
|
30
|
+
"member.settings.section": null,
|
|
31
|
+
"booking.hint": "bookings:read",
|
|
32
|
+
"booking_plan.action": "courts:read",
|
|
33
|
+
"sidebar.action": null,
|
|
22
34
|
};
|
|
35
|
+
/** Buttons CheckCourt draws from the manifest alone, before the app is ever called. */
|
|
36
|
+
export const STATIC_ACTION_POINTS = ["booking_plan.action", "sidebar.action"];
|
|
37
|
+
/** Longest `label` of a static action. */
|
|
38
|
+
export const STATIC_ACTION_LABEL_MAX = 24;
|
|
39
|
+
/** lucide icon names a static action may use as `icon`. */
|
|
40
|
+
export const APP_ACTION_ICONS = [
|
|
41
|
+
"bell",
|
|
42
|
+
"calendar",
|
|
43
|
+
"calendar-check",
|
|
44
|
+
"camera",
|
|
45
|
+
"chart-column",
|
|
46
|
+
"circle-help",
|
|
47
|
+
"clipboard-list",
|
|
48
|
+
"clock",
|
|
49
|
+
"cloud-rain",
|
|
50
|
+
"door-open",
|
|
51
|
+
"file-text",
|
|
52
|
+
"flag",
|
|
53
|
+
"heart-pulse",
|
|
54
|
+
"info",
|
|
55
|
+
"key-round",
|
|
56
|
+
"lightbulb",
|
|
57
|
+
"link",
|
|
58
|
+
"list-checks",
|
|
59
|
+
"lock-open",
|
|
60
|
+
"map-pin",
|
|
61
|
+
"megaphone",
|
|
62
|
+
"message-square",
|
|
63
|
+
"receipt",
|
|
64
|
+
"send",
|
|
65
|
+
"sparkles",
|
|
66
|
+
"star",
|
|
67
|
+
"sun",
|
|
68
|
+
"thermometer",
|
|
69
|
+
"ticket",
|
|
70
|
+
"triangle-alert",
|
|
71
|
+
"trophy",
|
|
72
|
+
"user-round",
|
|
73
|
+
"users",
|
|
74
|
+
"wallet",
|
|
75
|
+
"wrench",
|
|
76
|
+
];
|
|
23
77
|
/** Every scope an app may request. Role, app, webhook, key, billing and AVV management are reserved for people. */
|
|
24
78
|
export const GRANTABLE_SCOPES = [
|
|
25
79
|
"courts:read",
|
|
@@ -69,7 +123,21 @@ export const GRANTABLE_SCOPES = [
|
|
|
69
123
|
"analytics:read",
|
|
70
124
|
"embeds:manage",
|
|
71
125
|
"webhooks:read",
|
|
126
|
+
// App-only: no club role holds it; a member app gets it from the member's consent.
|
|
127
|
+
"notifications:send",
|
|
72
128
|
];
|
|
129
|
+
/** Club objects apps can attach metadata to. */
|
|
130
|
+
export const SHARED_OBJECT_TYPES = ["booking", "court", "member"];
|
|
131
|
+
/** Read scope both the sharing and the reading app need in `tenantScopes` for an object type. */
|
|
132
|
+
export const SHARED_OBJECT_SCOPE = {
|
|
133
|
+
booking: "bookings:read",
|
|
134
|
+
court: "courts:read",
|
|
135
|
+
member: "members:read",
|
|
136
|
+
};
|
|
137
|
+
/** Metadata keys and event names: `^[a-z][a-z0-9_]{0,39}$`. */
|
|
138
|
+
export const CONNECTION_NAME_PATTERN = /^[a-z][a-z0-9_]{0,39}$/;
|
|
139
|
+
/** Serialized size limit of a metadata value and of event data. */
|
|
140
|
+
export const MAX_SHARED_VALUE_BYTES = 4096;
|
|
73
141
|
/**
|
|
74
142
|
* Identity function that gives editor completion and compile-time checks for a manifest.
|
|
75
143
|
* CheckCourt validates the rest on upload (scope and event pairing, URL rules, lengths).
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type CheckCourtClient } from "./client.js";
|
|
2
|
+
import type { components } from "./generated/schema.js";
|
|
3
|
+
export type SendNotificationResponse = components["schemas"]["SendNotificationResponse"];
|
|
4
|
+
export declare const NOTIFICATION_TITLE_MAX = 80;
|
|
5
|
+
export declare const NOTIFICATION_BODY_MAX = 500;
|
|
6
|
+
export interface SendNotificationInput {
|
|
7
|
+
/**
|
|
8
|
+
* Who gets it: the CheckCourt user id (club apps with `members:read`), the `psn_…` pseudonym
|
|
9
|
+
* from an extension context, or the pairwise `usr_…` id from `getMe`. A member app may only
|
|
10
|
+
* notify its own member. Anything else answers 404.
|
|
11
|
+
*/
|
|
12
|
+
recipient: string;
|
|
13
|
+
/** One line of plain text, 1 to 80 characters. */
|
|
14
|
+
title: string;
|
|
15
|
+
/** Plain text, 1 to 500 characters; line breaks are kept. */
|
|
16
|
+
body: string;
|
|
17
|
+
/** A path inside CheckCourt (`/booking?date=…`) or an https link on an origin your app registered. */
|
|
18
|
+
url?: string;
|
|
19
|
+
/** Your own label for the kind of message, e.g. `reminder`. */
|
|
20
|
+
category?: string;
|
|
21
|
+
/** The same key within 24 hours returns the first notification instead of sending again. */
|
|
22
|
+
idempotencyKey?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* `POST /api/v1/app/notifications`: CheckCourt delivers a message to one member, in CheckCourt and
|
|
26
|
+
* by email if they allow it. Needs `notifications:send`. The answer never says whether the member
|
|
27
|
+
* muted your app.
|
|
28
|
+
*/
|
|
29
|
+
export declare function sendNotification(client: CheckCourtClient, input: SendNotificationInput): Promise<SendNotificationResponse>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { unwrap } from "./client.js";
|
|
2
|
+
export const NOTIFICATION_TITLE_MAX = 80;
|
|
3
|
+
export const NOTIFICATION_BODY_MAX = 500;
|
|
4
|
+
/**
|
|
5
|
+
* `POST /api/v1/app/notifications`: CheckCourt delivers a message to one member, in CheckCourt and
|
|
6
|
+
* by email if they allow it. Needs `notifications:send`. The answer never says whether the member
|
|
7
|
+
* muted your app.
|
|
8
|
+
*/
|
|
9
|
+
export async function sendNotification(client, input) {
|
|
10
|
+
const { idempotencyKey, ...rest } = input;
|
|
11
|
+
return unwrap(client.POST("/app/notifications", {
|
|
12
|
+
body: { ...rest, ...(idempotencyKey !== undefined ? { idempotency_key: idempotencyKey } : {}) },
|
|
13
|
+
}));
|
|
14
|
+
}
|
package/dist/ui.d.ts
CHANGED
|
@@ -62,6 +62,10 @@ export type UiTextField = {
|
|
|
62
62
|
default?: string;
|
|
63
63
|
required?: boolean;
|
|
64
64
|
max_length?: number;
|
|
65
|
+
/** Renders a multi-line text box instead of a single-line input. The value stays a string. */
|
|
66
|
+
multiline?: boolean;
|
|
67
|
+
/** Visible rows of the text box when `multiline` is set; 1 to 12. */
|
|
68
|
+
rows?: number;
|
|
65
69
|
};
|
|
66
70
|
export type UiNumberField = {
|
|
67
71
|
type: "number";
|
|
@@ -112,37 +116,127 @@ export type UiToast = {
|
|
|
112
116
|
kind: "success" | "error";
|
|
113
117
|
message: string;
|
|
114
118
|
};
|
|
115
|
-
/**
|
|
119
|
+
/** CheckCourt caps every render lifetime at this many seconds. */
|
|
120
|
+
export declare const UI_CACHE_MAX_AGE_LIMIT = 300;
|
|
121
|
+
/**
|
|
122
|
+
* How long CheckCourt may reuse a render, in whole seconds. `0` means never. Without it
|
|
123
|
+
* CheckCourt follows the response's `Cache-Control` header, else keeps a render for 30 seconds.
|
|
124
|
+
*/
|
|
125
|
+
export type UiCache = {
|
|
126
|
+
max_age: number;
|
|
127
|
+
};
|
|
128
|
+
/** The response of a declarative extension: `{ ui: "v1", blocks, toast?, cache? }`. */
|
|
116
129
|
export interface UiDocument {
|
|
117
130
|
ui: typeof UI_VERSION;
|
|
118
131
|
blocks: UiBlock[];
|
|
119
132
|
toast?: UiToast;
|
|
133
|
+
cache?: UiCache;
|
|
120
134
|
}
|
|
121
135
|
/** Tells CheckCourt to show no card for this subject and viewer; on an action, removes the panel. */
|
|
122
136
|
export interface UiHiddenDocument {
|
|
123
137
|
ui: typeof UI_VERSION;
|
|
124
138
|
hidden: true;
|
|
125
139
|
toast?: UiToast;
|
|
140
|
+
cache?: UiCache;
|
|
126
141
|
}
|
|
127
142
|
/** Anything a declarative extension may answer with. */
|
|
128
143
|
export type UiResponse = UiDocument | UiHiddenDocument;
|
|
144
|
+
/** Longest badge label of a `court.annotation`. */
|
|
145
|
+
export declare const ANNOTATION_LABEL_MAX = 24;
|
|
146
|
+
/** Badges CheckCourt shows per court across all apps, by app name; the rest is not shown. */
|
|
147
|
+
export declare const MAX_ANNOTATIONS_PER_COURT = 2;
|
|
148
|
+
/** Longest `column.title` of a `member.list.column`. */
|
|
149
|
+
export declare const COLUMN_TITLE_MAX = 20;
|
|
150
|
+
/** Longest cell text of a `member.list.column`. */
|
|
151
|
+
export declare const COLUMN_TEXT_MAX = 24;
|
|
152
|
+
/** App columns CheckCourt shows on the member list, by app name. */
|
|
153
|
+
export declare const MAX_APP_COLUMNS = 2;
|
|
154
|
+
/** App entries CheckCourt shows in the sidebar, by app name. */
|
|
155
|
+
export declare const MAX_SIDEBAR_ACTIONS = 2;
|
|
156
|
+
/** CheckCourt waits this long for a `booking.hint` answer, then shows nothing. */
|
|
157
|
+
export declare const BOOKING_HINT_TIMEOUT_MS = 1000;
|
|
158
|
+
/** Block types a `booking.hint` document may contain, top level only. */
|
|
159
|
+
export declare const BOOKING_HINT_BLOCKS: readonly ["text", "badge", "key_value", "link"];
|
|
160
|
+
export declare const MAX_HINT_BLOCKS = 6;
|
|
161
|
+
/** Apps whose hints CheckCourt shows in the booking dialog, by app name. */
|
|
162
|
+
export declare const MAX_BOOKING_HINTS = 2;
|
|
163
|
+
/** One badge in a court header of the booking plan. */
|
|
164
|
+
export interface CourtAnnotation {
|
|
165
|
+
court_id: number;
|
|
166
|
+
/** 1 to 24 characters. */
|
|
167
|
+
label: string;
|
|
168
|
+
variant?: BadgeVariant;
|
|
169
|
+
}
|
|
170
|
+
/** The answer to `court.annotation`: at most one annotation per court. */
|
|
171
|
+
export interface AnnotationsDocument {
|
|
172
|
+
ui: typeof UI_VERSION;
|
|
173
|
+
annotations: CourtAnnotation[];
|
|
174
|
+
cache?: UiCache;
|
|
175
|
+
}
|
|
176
|
+
/** One cell of an app column on the member list; a badge when `variant` is set, plain text otherwise. */
|
|
177
|
+
export interface ColumnValue {
|
|
178
|
+
member_id: string;
|
|
179
|
+
/** 1 to 24 characters. */
|
|
180
|
+
text: string;
|
|
181
|
+
variant?: BadgeVariant;
|
|
182
|
+
}
|
|
183
|
+
/** The answer to `member.list.column`: at most one value per member. */
|
|
184
|
+
export interface ColumnDocument {
|
|
185
|
+
ui: typeof UI_VERSION;
|
|
186
|
+
column: {
|
|
187
|
+
title: string;
|
|
188
|
+
values: ColumnValue[];
|
|
189
|
+
};
|
|
190
|
+
cache?: UiCache;
|
|
191
|
+
}
|
|
192
|
+
export type UiHintBlock = UiTextBlock | UiBadgeBlock | UiKeyValueBlock | UiLinkBlock;
|
|
193
|
+
/** The answer to `booking.hint`: display-only blocks; toasts are ignored. */
|
|
194
|
+
export interface UiHintDocument extends UiDocument {
|
|
195
|
+
blocks: UiHintBlock[];
|
|
196
|
+
toast?: never;
|
|
197
|
+
}
|
|
198
|
+
/** Options of `ui.annotations`, `ui.column` and `ui.hint`. */
|
|
199
|
+
export interface UiSurfaceOptions {
|
|
200
|
+
/** Seconds CheckCourt may reuse this render, sent as `cache.max_age`; `0` disables caching. Capped at 300. */
|
|
201
|
+
maxAge?: number;
|
|
202
|
+
}
|
|
129
203
|
/** Values a submitted form sends back, keyed by field name. Empty optional fields are omitted. */
|
|
130
204
|
export type UiFormValues = Record<string, string | number | boolean>;
|
|
131
205
|
type Opt<T> = {
|
|
132
206
|
[K in keyof T]?: T[K];
|
|
133
207
|
};
|
|
208
|
+
/** Options shared by `ui.doc` and `ui.hidden`. */
|
|
209
|
+
export interface UiDocumentOptions {
|
|
210
|
+
toast?: UiToast;
|
|
211
|
+
/** Seconds CheckCourt may reuse this render, sent as `cache.max_age`; `0` disables caching. Capped at 300. */
|
|
212
|
+
maxAge?: number;
|
|
213
|
+
}
|
|
134
214
|
/**
|
|
135
215
|
* Builds `ui: "v1"` documents. Strings are plain text (no HTML, no Markdown); CheckCourt
|
|
136
216
|
* renders them with its own design system.
|
|
137
217
|
*/
|
|
138
218
|
export declare const ui: {
|
|
139
|
-
readonly doc: (blocks: UiBlock[], options?:
|
|
140
|
-
|
|
141
|
-
|
|
219
|
+
readonly doc: (blocks: UiBlock[], options?: UiDocumentOptions) => UiDocument;
|
|
220
|
+
/**
|
|
221
|
+
* The answer to `court.annotation`: badges for some of the requested courts, at most one per
|
|
222
|
+
* court. Throws on a label outside 1 to 24 characters or a court listed twice.
|
|
223
|
+
*/
|
|
224
|
+
readonly annotations: (annotations: CourtAnnotation[], options?: UiSurfaceOptions) => AnnotationsDocument;
|
|
225
|
+
/**
|
|
226
|
+
* The answer to `member.list.column`: a title and one value per member you have something for.
|
|
227
|
+
* Throws on a title over 20 or a text over 24 characters, or a member listed twice.
|
|
228
|
+
*/
|
|
229
|
+
readonly column: (column: {
|
|
230
|
+
title: string;
|
|
231
|
+
values: ColumnValue[];
|
|
232
|
+
}, options?: UiSurfaceOptions) => ColumnDocument;
|
|
233
|
+
/**
|
|
234
|
+
* The answer to `booking.hint`: at most 6 text, badge, key_value or link blocks. Throws on any
|
|
235
|
+
* other block, which CheckCourt would reject together with the whole hint.
|
|
236
|
+
*/
|
|
237
|
+
readonly hint: (blocks: UiHintBlock[], options?: UiSurfaceOptions) => UiHintDocument;
|
|
142
238
|
/** Nothing relevant here: no card at all. Answering `204 No Content` does the same. */
|
|
143
|
-
readonly hidden: (options?:
|
|
144
|
-
toast?: UiToast;
|
|
145
|
-
}) => UiHiddenDocument;
|
|
239
|
+
readonly hidden: (options?: UiDocumentOptions) => UiHiddenDocument;
|
|
146
240
|
readonly text: (text: string, options?: {
|
|
147
241
|
tone?: "muted";
|
|
148
242
|
}) => UiTextBlock;
|
package/dist/ui.js
CHANGED
|
@@ -4,6 +4,29 @@ export const MAX_UI_BLOCKS = 50;
|
|
|
4
4
|
export const MAX_UI_DEPTH = 3;
|
|
5
5
|
export const BADGE_VARIANTS = ["default", "secondary", "outline", "destructive"];
|
|
6
6
|
export const BUTTON_VARIANTS = ["default", "secondary", "outline", "destructive"];
|
|
7
|
+
/** CheckCourt caps every render lifetime at this many seconds. */
|
|
8
|
+
export const UI_CACHE_MAX_AGE_LIMIT = 300;
|
|
9
|
+
/** Longest badge label of a `court.annotation`. */
|
|
10
|
+
export const ANNOTATION_LABEL_MAX = 24;
|
|
11
|
+
/** Badges CheckCourt shows per court across all apps, by app name; the rest is not shown. */
|
|
12
|
+
export const MAX_ANNOTATIONS_PER_COURT = 2;
|
|
13
|
+
/** Longest `column.title` of a `member.list.column`. */
|
|
14
|
+
export const COLUMN_TITLE_MAX = 20;
|
|
15
|
+
/** Longest cell text of a `member.list.column`. */
|
|
16
|
+
export const COLUMN_TEXT_MAX = 24;
|
|
17
|
+
/** App columns CheckCourt shows on the member list, by app name. */
|
|
18
|
+
export const MAX_APP_COLUMNS = 2;
|
|
19
|
+
/** App entries CheckCourt shows in the sidebar, by app name. */
|
|
20
|
+
export const MAX_SIDEBAR_ACTIONS = 2;
|
|
21
|
+
/** CheckCourt waits this long for a `booking.hint` answer, then shows nothing. */
|
|
22
|
+
export const BOOKING_HINT_TIMEOUT_MS = 1000;
|
|
23
|
+
/** Block types a `booking.hint` document may contain, top level only. */
|
|
24
|
+
export const BOOKING_HINT_BLOCKS = ["text", "badge", "key_value", "link"];
|
|
25
|
+
export const MAX_HINT_BLOCKS = 6;
|
|
26
|
+
/** Apps whose hints CheckCourt shows in the booking dialog, by app name. */
|
|
27
|
+
export const MAX_BOOKING_HINTS = 2;
|
|
28
|
+
/** Annotations or column values one document may carry. */
|
|
29
|
+
const MAX_SURFACE_ENTRIES = 200;
|
|
7
30
|
const field = {
|
|
8
31
|
text(name, label, options = {}) {
|
|
9
32
|
return { type: "text", name, label, ...options };
|
|
@@ -21,17 +44,111 @@ const field = {
|
|
|
21
44
|
function compact(value) {
|
|
22
45
|
return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== undefined));
|
|
23
46
|
}
|
|
47
|
+
function cacheOf(maxAge) {
|
|
48
|
+
if (maxAge === undefined)
|
|
49
|
+
return undefined;
|
|
50
|
+
if (!Number.isInteger(maxAge) || maxAge < 0) {
|
|
51
|
+
throw new RangeError(`maxAge must be a whole number of seconds >= 0, got ${maxAge}`);
|
|
52
|
+
}
|
|
53
|
+
return { max_age: maxAge };
|
|
54
|
+
}
|
|
55
|
+
function shortText(value, max, what) {
|
|
56
|
+
if (typeof value !== "string")
|
|
57
|
+
throw new TypeError(`${what} must be a string`);
|
|
58
|
+
const text = value.trim();
|
|
59
|
+
if (text.length < 1 || text.length > max) {
|
|
60
|
+
throw new RangeError(`${what} must be 1 to ${max} characters, got ${text.length}`);
|
|
61
|
+
}
|
|
62
|
+
return text;
|
|
63
|
+
}
|
|
64
|
+
function variantOf(variant, what) {
|
|
65
|
+
if (variant === undefined)
|
|
66
|
+
return undefined;
|
|
67
|
+
if (!BADGE_VARIANTS.includes(variant)) {
|
|
68
|
+
throw new TypeError(`${what} must be one of ${BADGE_VARIANTS.join(", ")}`);
|
|
69
|
+
}
|
|
70
|
+
return variant;
|
|
71
|
+
}
|
|
72
|
+
function entriesOf(list, what) {
|
|
73
|
+
if (!Array.isArray(list))
|
|
74
|
+
throw new TypeError(`${what} must be an array`);
|
|
75
|
+
if (list.length > MAX_SURFACE_ENTRIES)
|
|
76
|
+
throw new RangeError(`${what}: at most ${MAX_SURFACE_ENTRIES} entries`);
|
|
77
|
+
return list;
|
|
78
|
+
}
|
|
24
79
|
/**
|
|
25
80
|
* Builds `ui: "v1"` documents. Strings are plain text (no HTML, no Markdown); CheckCourt
|
|
26
81
|
* renders them with its own design system.
|
|
27
82
|
*/
|
|
28
83
|
export const ui = {
|
|
29
84
|
doc(blocks, options = {}) {
|
|
30
|
-
return compact({ ui: UI_VERSION, blocks, toast: options.toast });
|
|
85
|
+
return compact({ ui: UI_VERSION, blocks, toast: options.toast, cache: cacheOf(options.maxAge) });
|
|
86
|
+
},
|
|
87
|
+
/**
|
|
88
|
+
* The answer to `court.annotation`: badges for some of the requested courts, at most one per
|
|
89
|
+
* court. Throws on a label outside 1 to 24 characters or a court listed twice.
|
|
90
|
+
*/
|
|
91
|
+
annotations(annotations, options = {}) {
|
|
92
|
+
const seen = new Set();
|
|
93
|
+
const list = entriesOf(annotations, "annotations").map((a, i) => {
|
|
94
|
+
if (!Number.isInteger(a.court_id) || a.court_id <= 0) {
|
|
95
|
+
throw new TypeError(`annotations[${i}].court_id must be a positive integer`);
|
|
96
|
+
}
|
|
97
|
+
if (seen.has(a.court_id))
|
|
98
|
+
throw new RangeError(`annotations: court ${a.court_id} appears twice`);
|
|
99
|
+
seen.add(a.court_id);
|
|
100
|
+
return compact({
|
|
101
|
+
court_id: a.court_id,
|
|
102
|
+
label: shortText(a.label, ANNOTATION_LABEL_MAX, `annotations[${i}].label`),
|
|
103
|
+
variant: variantOf(a.variant, `annotations[${i}].variant`),
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
return compact({ ui: UI_VERSION, annotations: list, cache: cacheOf(options.maxAge) });
|
|
107
|
+
},
|
|
108
|
+
/**
|
|
109
|
+
* The answer to `member.list.column`: a title and one value per member you have something for.
|
|
110
|
+
* Throws on a title over 20 or a text over 24 characters, or a member listed twice.
|
|
111
|
+
*/
|
|
112
|
+
column(column, options = {}) {
|
|
113
|
+
const seen = new Set();
|
|
114
|
+
const values = entriesOf(column.values, "column.values").map((v, i) => {
|
|
115
|
+
if (typeof v.member_id !== "string" || v.member_id.length < 1 || v.member_id.length > 100) {
|
|
116
|
+
throw new TypeError(`column.values[${i}].member_id must be a member id`);
|
|
117
|
+
}
|
|
118
|
+
if (seen.has(v.member_id))
|
|
119
|
+
throw new RangeError(`column.values: member ${v.member_id} appears twice`);
|
|
120
|
+
seen.add(v.member_id);
|
|
121
|
+
return compact({
|
|
122
|
+
member_id: v.member_id,
|
|
123
|
+
text: shortText(v.text, COLUMN_TEXT_MAX, `column.values[${i}].text`),
|
|
124
|
+
variant: variantOf(v.variant, `column.values[${i}].variant`),
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
return compact({
|
|
128
|
+
ui: UI_VERSION,
|
|
129
|
+
column: { title: shortText(column.title, COLUMN_TITLE_MAX, "column.title"), values },
|
|
130
|
+
cache: cacheOf(options.maxAge),
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
/**
|
|
134
|
+
* The answer to `booking.hint`: at most 6 text, badge, key_value or link blocks. Throws on any
|
|
135
|
+
* other block, which CheckCourt would reject together with the whole hint.
|
|
136
|
+
*/
|
|
137
|
+
hint(blocks, options = {}) {
|
|
138
|
+
if (!Array.isArray(blocks))
|
|
139
|
+
throw new TypeError("blocks must be an array");
|
|
140
|
+
if (blocks.length > MAX_HINT_BLOCKS)
|
|
141
|
+
throw new RangeError(`booking.hint: at most ${MAX_HINT_BLOCKS} blocks`);
|
|
142
|
+
for (const [i, block] of blocks.entries()) {
|
|
143
|
+
if (!BOOKING_HINT_BLOCKS.includes(block.type)) {
|
|
144
|
+
throw new TypeError(`blocks[${i}]: ${block.type} is not allowed in booking.hint`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return compact({ ui: UI_VERSION, blocks, cache: cacheOf(options.maxAge) });
|
|
31
148
|
},
|
|
32
149
|
/** Nothing relevant here: no card at all. Answering `204 No Content` does the same. */
|
|
33
150
|
hidden(options = {}) {
|
|
34
|
-
return compact({ ui: UI_VERSION, hidden: true, toast: options.toast });
|
|
151
|
+
return compact({ ui: UI_VERSION, hidden: true, toast: options.toast, cache: cacheOf(options.maxAge) });
|
|
35
152
|
},
|
|
36
153
|
text(text, options = {}) {
|
|
37
154
|
return compact({ type: "text", text, tone: options.tone });
|
package/dist/webhooks.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { WebhookEvent } from "./events.js";
|
|
2
|
-
export type { AppLifecycleEventData, BookingEventData, CourtLockEventData, EventDataMap, EventObjectType, EventType, MemberEventData, SubscribableEventType, AppLifecycleEventType, WebhookEvent, WebhookEventOf, } from "./events.js";
|
|
3
|
-
export { APP_LIFECYCLE_EVENT_TYPES, EVENT_TYPES, SUBSCRIBABLE_EVENT_TYPES } from "./events.js";
|
|
2
|
+
export type { AppEvent, AppEventType, AppMetadataChangedEvent, AppMetadataChangedEventData, ConnectionEvent, AppLifecycleEventData, BookingEventData, CourtLockEventData, EventDataMap, EventObjectType, EventType, MemberEventData, SubscribableEventType, AppLifecycleEventType, WebhookEvent, WebhookEventOf, } from "./events.js";
|
|
3
|
+
export { APP_LIFECYCLE_EVENT_TYPES, EVENT_TYPES, METADATA_CHANGED_EVENT_TYPE, SUBSCRIBABLE_EVENT_TYPES, appEventType, isAppEvent, isMetadataChangedEvent, } from "./events.js";
|
|
4
4
|
export { WebhookSignatureError, type WebhookSignatureFailure } from "./errors.js";
|
|
5
5
|
export declare const SIGNATURE_HEADER = "CheckCourt-Signature";
|
|
6
6
|
export declare const EVENT_ID_HEADER = "CheckCourt-Event-Id";
|
package/dist/webhooks.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { WebhookSignatureError } from "./errors.js";
|
|
2
2
|
import { decodeUtf8, hexDecode, hexEncode, toBytes, utf8 } from "./internal/encoding.js";
|
|
3
3
|
import { hmacSha256, hmacSha256Verify } from "./internal/hmac.js";
|
|
4
|
-
export { APP_LIFECYCLE_EVENT_TYPES, EVENT_TYPES, SUBSCRIBABLE_EVENT_TYPES } from "./events.js";
|
|
4
|
+
export { APP_LIFECYCLE_EVENT_TYPES, EVENT_TYPES, METADATA_CHANGED_EVENT_TYPE, SUBSCRIBABLE_EVENT_TYPES, appEventType, isAppEvent, isMetadataChangedEvent, } from "./events.js";
|
|
5
5
|
export { WebhookSignatureError } from "./errors.js";
|
|
6
6
|
export const SIGNATURE_HEADER = "CheckCourt-Signature";
|
|
7
7
|
export const EVENT_ID_HEADER = "CheckCourt-Event-Id";
|