@checkcourt/sdk 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -1
- package/dist/extensions.d.ts +9 -4
- package/dist/extensions.js +16 -10
- package/dist/ui.d.ts +29 -1
- package/dist/ui.js +6 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -178,6 +178,21 @@ ui.form({
|
|
|
178
178
|
});
|
|
179
179
|
```
|
|
180
180
|
|
|
181
|
+
`ui.field.date` and `ui.field.time` render native date and time pickers. `default`, `min` and
|
|
182
|
+
`max` are `YYYY-MM-DD` strings for dates and `HH:MM` (24-hour) strings for times; `time` takes an
|
|
183
|
+
optional `step` in minutes. Both submit their value as a string:
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
ui.form({
|
|
187
|
+
actionId: "block",
|
|
188
|
+
submitLabel: "Sperren",
|
|
189
|
+
fields: [
|
|
190
|
+
ui.field.date("day", "Tag", { min: "2026-01-01", required: true }),
|
|
191
|
+
ui.field.time("from", "Von", { min: "07:00", max: "22:00", step: 30, required: true }),
|
|
192
|
+
],
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
181
196
|
Compare `context.installation_id` and `context.tenant_id` with what you stored from
|
|
182
197
|
`app.installed` before you act on a request. For the context token alone (for example in
|
|
183
198
|
the backend of an iframe extension), use `verifyExtensionContext(token, secret)`.
|
|
@@ -192,7 +207,8 @@ with the matching builder; each throws when the answer would break CheckCourt's
|
|
|
192
207
|
| `court.annotation` | `ext.date`, `ext.courts` | `ui.annotations([{ court_id, label, variant? }])`, label up to 24 characters, one per court |
|
|
193
208
|
| `member.list.column` | `ext.members` | `ui.column({ title, values: [{ member_id, text, variant? }] })`, title up to 20, text up to 24 characters |
|
|
194
209
|
| `booking.hint` | `ext.draft` | `ui.hint([...])` with up to 6 text, badge, key_value or link blocks; answer within 1 second |
|
|
195
|
-
| `booking_plan.action`, `
|
|
210
|
+
| `booking_plan.action` | `ext.date`, `ext.courts` (and the day as subject) | `ui.doc([...])`, shown in a dialog after a click; `ui.hidden()` closes it |
|
|
211
|
+
| `sidebar.action` | nothing extra | `ui.doc([...])`, shown in a dialog after a click; `ui.hidden()` closes it |
|
|
196
212
|
| `member.settings.section` | nothing extra | `ui.doc([...])`, a card on the member's own settings page |
|
|
197
213
|
|
|
198
214
|
```ts
|
package/dist/extensions.d.ts
CHANGED
|
@@ -115,7 +115,7 @@ export type ExtensionContextClaims = {
|
|
|
115
115
|
export declare function verifyExtensionContext(token: string, secret: string, options?: {
|
|
116
116
|
now?: Date | number;
|
|
117
117
|
}): Promise<ExtensionContextClaims>;
|
|
118
|
-
/** A court of the plan a `court.annotation` request covers. */
|
|
118
|
+
/** A court of the plan a `court.annotation` or `booking_plan.action` request covers. */
|
|
119
119
|
export interface AnnotationCourt {
|
|
120
120
|
id: number;
|
|
121
121
|
name: string;
|
|
@@ -145,9 +145,9 @@ export interface ExtensionRenderRequest {
|
|
|
145
145
|
context: ExtensionContextClaims;
|
|
146
146
|
point: ExtensionPoint;
|
|
147
147
|
subject: ExtensionSubject | null;
|
|
148
|
-
/** `court.annotation`: the plan's day, YYYY-MM-DD. */
|
|
148
|
+
/** `court.annotation` and `booking_plan.action`: the plan's day, YYYY-MM-DD. */
|
|
149
149
|
date?: string;
|
|
150
|
-
/** `court.annotation`: every court of the plan
|
|
150
|
+
/** `court.annotation` (one document for all) and `booking_plan.action`: every court of the plan. */
|
|
151
151
|
courts?: AnnotationCourt[];
|
|
152
152
|
/** `member.list.column`: the members on the visible page. */
|
|
153
153
|
members?: ColumnMember[];
|
|
@@ -162,6 +162,10 @@ export interface ExtensionActionRequest {
|
|
|
162
162
|
subject: ExtensionSubject | null;
|
|
163
163
|
actionId: string;
|
|
164
164
|
values: UiFormValues;
|
|
165
|
+
/** `booking_plan.action`: the plan's day, YYYY-MM-DD. */
|
|
166
|
+
date?: string;
|
|
167
|
+
/** `booking_plan.action`: every court of the plan, so a form can offer a court select. */
|
|
168
|
+
courts?: AnnotationCourt[];
|
|
165
169
|
}
|
|
166
170
|
export type ExtensionRequest = ExtensionRenderRequest | ExtensionActionRequest;
|
|
167
171
|
type HeaderSource = Headers | Record<string, string | string[] | undefined>;
|
|
@@ -169,7 +173,8 @@ type HeaderSource = Headers | Record<string, string | string[] | undefined>;
|
|
|
169
173
|
* Verifies a declarative extension POST: the `CheckCourt-Signature` over the raw body (it binds
|
|
170
174
|
* `action_id` and `values` to the token), the context token, and that body, header token and
|
|
171
175
|
* claims agree. Renders at `court.annotation`, `member.list.column` and `booking.hint` also
|
|
172
|
-
* carry `date` and `courts`, `members` or `draft
|
|
176
|
+
* carry `date` and `courts`, `members` or `draft`; `booking_plan.action` renders and actions
|
|
177
|
+
* carry the plan's `date` and `courts`. Throws `ExtensionVerificationError`.
|
|
173
178
|
*/
|
|
174
179
|
export declare function verifyExtensionRequest(options: {
|
|
175
180
|
secret: string;
|
package/dist/extensions.js
CHANGED
|
@@ -84,17 +84,21 @@ function sameSubject(a, b) {
|
|
|
84
84
|
const DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
85
85
|
const TIME = /^\d{2}:\d{2}$/;
|
|
86
86
|
const isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
87
|
+
/** Parses the `date` and `courts` a court.annotation or booking_plan.action request carries. */
|
|
88
|
+
function planCourts(body) {
|
|
89
|
+
const { date, courts } = body;
|
|
90
|
+
if (typeof date !== "string" || !DATE.test(date))
|
|
91
|
+
throw fail("invalid_body", "date is not YYYY-MM-DD");
|
|
92
|
+
if (!Array.isArray(courts) || !courts.every((c) => isObject(c) && typeof c.id === "number" && typeof c.name === "string")) {
|
|
93
|
+
throw fail("invalid_body", "courts is not a list of { id, name }");
|
|
94
|
+
}
|
|
95
|
+
return { date, courts: courts.map((c) => ({ id: c.id, name: c.name })) };
|
|
96
|
+
}
|
|
87
97
|
function surfaceFields(point, body) {
|
|
88
98
|
switch (point) {
|
|
89
|
-
case "court.annotation":
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
throw fail("invalid_body", "date is not YYYY-MM-DD");
|
|
93
|
-
if (!Array.isArray(courts) || !courts.every((c) => isObject(c) && typeof c.id === "number" && typeof c.name === "string")) {
|
|
94
|
-
throw fail("invalid_body", "courts is not a list of { id, name }");
|
|
95
|
-
}
|
|
96
|
-
return { date, courts: courts.map((c) => ({ id: c.id, name: c.name })) };
|
|
97
|
-
}
|
|
99
|
+
case "court.annotation":
|
|
100
|
+
case "booking_plan.action":
|
|
101
|
+
return planCourts(body);
|
|
98
102
|
case "member.list.column": {
|
|
99
103
|
const { members } = body;
|
|
100
104
|
if (!Array.isArray(members) ||
|
|
@@ -134,7 +138,8 @@ function surfaceFields(point, body) {
|
|
|
134
138
|
* Verifies a declarative extension POST: the `CheckCourt-Signature` over the raw body (it binds
|
|
135
139
|
* `action_id` and `values` to the token), the context token, and that body, header token and
|
|
136
140
|
* claims agree. Renders at `court.annotation`, `member.list.column` and `booking.hint` also
|
|
137
|
-
* carry `date` and `courts`, `members` or `draft
|
|
141
|
+
* carry `date` and `courts`, `members` or `draft`; `booking_plan.action` renders and actions
|
|
142
|
+
* carry the plan's `date` and `courts`. Throws `ExtensionVerificationError`.
|
|
138
143
|
*/
|
|
139
144
|
export async function verifyExtensionRequest(options) {
|
|
140
145
|
try {
|
|
@@ -185,5 +190,6 @@ export async function verifyExtensionRequest(options) {
|
|
|
185
190
|
subject,
|
|
186
191
|
actionId: body.action_id,
|
|
187
192
|
values: values,
|
|
193
|
+
...(context.point === "booking_plan.action" ? planCourts(body) : {}),
|
|
188
194
|
};
|
|
189
195
|
}
|
package/dist/ui.d.ts
CHANGED
|
@@ -93,7 +93,33 @@ export type UiSwitchField = {
|
|
|
93
93
|
label: string;
|
|
94
94
|
default?: boolean;
|
|
95
95
|
};
|
|
96
|
-
export type
|
|
96
|
+
export type UiDateField = {
|
|
97
|
+
type: "date";
|
|
98
|
+
name: string;
|
|
99
|
+
label: string;
|
|
100
|
+
/** YYYY-MM-DD. */
|
|
101
|
+
default?: string;
|
|
102
|
+
/** YYYY-MM-DD. */
|
|
103
|
+
min?: string;
|
|
104
|
+
/** YYYY-MM-DD. */
|
|
105
|
+
max?: string;
|
|
106
|
+
required?: boolean;
|
|
107
|
+
};
|
|
108
|
+
export type UiTimeField = {
|
|
109
|
+
type: "time";
|
|
110
|
+
name: string;
|
|
111
|
+
label: string;
|
|
112
|
+
/** HH:MM, 24-hour. */
|
|
113
|
+
default?: string;
|
|
114
|
+
/** HH:MM, 24-hour. */
|
|
115
|
+
min?: string;
|
|
116
|
+
/** HH:MM, 24-hour. */
|
|
117
|
+
max?: string;
|
|
118
|
+
required?: boolean;
|
|
119
|
+
/** Granularity of the time picker in minutes. */
|
|
120
|
+
step?: number;
|
|
121
|
+
};
|
|
122
|
+
export type UiFormField = UiTextField | UiNumberField | UiSelectField | UiSwitchField | UiDateField | UiTimeField;
|
|
97
123
|
export type UiFormBlock = {
|
|
98
124
|
type: "form";
|
|
99
125
|
fields: UiFormField[];
|
|
@@ -263,6 +289,8 @@ export declare const ui: {
|
|
|
263
289
|
number(name: string, label: string, options?: Opt<Omit<UiNumberField, "type" | "name" | "label">>): UiNumberField;
|
|
264
290
|
select(name: string, label: string, options: UiSelectField["options"], extra?: Opt<Pick<UiSelectField, "default" | "required">>): UiSelectField;
|
|
265
291
|
switch(name: string, label: string, options?: Opt<Pick<UiSwitchField, "default">>): UiSwitchField;
|
|
292
|
+
date(name: string, label: string, options?: Opt<Omit<UiDateField, "type" | "name" | "label">>): UiDateField;
|
|
293
|
+
time(name: string, label: string, options?: Opt<Omit<UiTimeField, "type" | "name" | "label">>): UiTimeField;
|
|
266
294
|
};
|
|
267
295
|
readonly divider: () => UiDividerBlock;
|
|
268
296
|
readonly stack: (children: UiBlock[]) => UiStackBlock;
|
package/dist/ui.js
CHANGED
|
@@ -40,6 +40,12 @@ const field = {
|
|
|
40
40
|
switch(name, label, options = {}) {
|
|
41
41
|
return { type: "switch", name, label, ...options };
|
|
42
42
|
},
|
|
43
|
+
date(name, label, options = {}) {
|
|
44
|
+
return compact({ type: "date", name, label, ...options });
|
|
45
|
+
},
|
|
46
|
+
time(name, label, options = {}) {
|
|
47
|
+
return compact({ type: "time", name, label, ...options });
|
|
48
|
+
},
|
|
43
49
|
};
|
|
44
50
|
function compact(value) {
|
|
45
51
|
return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== undefined));
|