@acms-subir/client 0.1.19 → 0.1.21

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 CHANGED
@@ -49,13 +49,16 @@ language. Update the site before shipping an app build with a newer client.
49
49
  | `calendars()` | Public calendars |
50
50
  | `resources(opts)`, `resource(slug, opts)` | Public resources; slug lookup scans list pages |
51
51
  | `availability(opts)` | Required `resource`, `link`; optional `from`, `days`, `minutes` |
52
+ | `roomFree({ room, start, end })` | Free or busy for a room the site lets people check, with the busy times and no names; never cached (core 0.3.45+) |
52
53
  | `rsvpStatus(slug, { start })` | One event date's RSVP status: `open` or `reason` (`disabled`, `ended`, `closed` past the event's deadline, `cancelled`, `full`), `closesAt` (when RSVPs to the date close, or `null`: open until it ends), live `going`/`left` (held waitlist spots count as taken), `maxSeats` for one RSVP right now, `autoConfirm`, `waitlist` (`enabled`: a full date takes places in line; `waiting` people for `seats` seats; `maxSeats` for one place in line), and `form` (questions and `version`, or `null`). `start` is the occurrence's start exactly as `events()` returns it (a UTC ISO string such as `2026-10-05T14:00:00.000Z`). Never cached |
53
54
  | `book(opts)` | Resource booking or RSVP input (`RsvpInput` for an RSVP; `waitlist: true` joins a full date's waitlist, status `waitlisted`), optional `idempotencyKey`; sends the client's `locale` (or `opts.locale`) for its emails |
54
55
  | `openapi()` | Raw OpenAPI document |
55
56
  | `registerDevice({ token, platform, locale?, topics?, channels? })` | Register or refresh an Expo push token (call on every app start); `{ created, device }`. `channels`: Android channels the app created (e.g. `['urgent']`) |
56
57
  | `updateDevice(token, { topics?, locale?, channels? })` | Change a device's topics, language or channels |
57
- | `unregisterDevice(token)` | Opt out (idempotent) |
58
+ | `unregisterDevice(token)` | Opt out (idempotent); also unlinks a staff test phone |
58
59
  | `pushTopics()` | Topics to offer as notification toggles, named in the client's language (built-in and the site's own, `kind: 'site'`) |
60
+ | `linkStaffPhone(token, code)` | Staff only: link this registered device with the 6-digit code from the admin's Test phones, so it gets that person's test notifications; `{ linked, staff: { name } }`. A wrong, used or expired code is a 400, too many tries a 429; never retried |
61
+ | `unlinkStaffPhone(token)` | Unlink it (idempotent); `{ linked: false, staff: null }` |
59
62
  | `links()` | The links page: profile, socials and visible blocks (`link`, `email`, `phone`, `contact`, `heading`, `text`, `spacer`, `live`, `embed`) |
60
63
  | `live()` | Live video: the current broadcast (`snapshot` or `null`), `serverNow` and the owner's `display` choices. Never cached; 404 while `site.features.live` is false |
61
64
  | `announcements({ kind?, pinned?, page?, limit? })`, `announcement(id)`, `paginate.announcements()` | Announcements up right now in the client's language, newest first (`pinned: true`: the banners, urgent first); 404 while `site.features.announcements` is false |
@@ -181,6 +184,18 @@ await client.updateDevice(token, { topics: ['events'] });
181
184
  await client.unregisterDevice(token);
182
185
  ```
183
186
 
187
+ Staff can link their own phone for test notifications ("Send test to my
188
+ phone" in the admin): the admin's Test phones panel shows a one-time 6-digit
189
+ code (10 minutes, single use) that the app sends with this phone's token.
190
+ `device.staff` (from `registerDevice` and `updateDevice`) and the link
191
+ response name who it's linked to (their name only), or `null`. Test pushes
192
+ carry `data.test: true`.
193
+
194
+ ```ts
195
+ const { staff } = await client.linkStaffPhone(token, '123456'); // { name: 'Ana López' }; 400 for a wrong, used or expired code
196
+ await client.unlinkStaffPhone(token); // turning notifications off unlinks it too
197
+ ```
198
+
184
199
  ## Live video
185
200
 
186
201
  `site.features.live` is true while the site's live video (Integrations → Live
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Site, Theme, Page, Content, Post, ContentList, SearchResults, Links, Form, FormReceipt, FormResponseInput, ManagedBooking, RescheduleInput, ManageAvailabilityQuery, GuestCodeSent, GuestToken, GuestBookings, GuestBookingsQuery, GuestSignedOut, Events, Event, Calendars, Resources, Resource, Availability, BookingInput, BookingResult, RsvpStatus, Device, DeviceInput, DeviceUpdate, DeviceRegistration, DeviceRemoved, PushTopics, LiveStatus, Announcements, AnnouncementDetail, EventDetail } from './types.js';
1
+ import type { Site, Theme, Page, Content, Post, ContentList, SearchResults, Links, Form, FormReceipt, FormResponseInput, ManagedBooking, RescheduleInput, ManageAvailabilityQuery, GuestCodeSent, GuestToken, GuestBookings, GuestBookingsQuery, GuestSignedOut, Events, Event, Calendars, Resources, Resource, Availability, BookingInput, BookingResult, RsvpStatus, Device, DeviceInput, DeviceUpdate, DeviceRegistration, DeviceRemoved, StaffLink, PushTopics, LiveStatus, Announcements, AnnouncementDetail, EventDetail, RoomFree } from './types.js';
2
2
  export { AcmsError, isAcmsError } from './errors.js';
3
3
  export type * from './types.js';
4
4
  export * from './editor.js';
@@ -60,6 +60,12 @@ export interface AvailabilityOptions extends RequestOptions {
60
60
  export interface RsvpStatusOptions extends RequestOptions {
61
61
  start: string;
62
62
  }
63
+ /** A room's slug and a span: each bound a site-local day (its midnight) or an ISO date-time with a zone; at most 31 days. */
64
+ export interface RoomFreeOptions extends RequestOptions {
65
+ room: string;
66
+ start: EventBound | (string & {});
67
+ end: EventBound | (string & {});
68
+ }
63
69
  export type BookOptions = BookingInput & RequestOptions & {
64
70
  idempotencyKey?: string;
65
71
  };
@@ -145,6 +151,10 @@ export declare const endpoints: {
145
151
  readonly method: "GET";
146
152
  readonly path: "/availability";
147
153
  };
154
+ readonly roomFree: {
155
+ readonly method: "GET";
156
+ readonly path: "/rooms/free";
157
+ };
148
158
  readonly book: {
149
159
  readonly method: "POST";
150
160
  readonly path: "/bookings";
@@ -169,6 +179,14 @@ export declare const endpoints: {
169
179
  readonly method: "GET";
170
180
  readonly path: "/push/topics";
171
181
  };
182
+ readonly linkStaffPhone: {
183
+ readonly method: "POST";
184
+ readonly path: "/devices/{token}/staff-link";
185
+ };
186
+ readonly unlinkStaffPhone: {
187
+ readonly method: "DELETE";
188
+ readonly path: "/devices/{token}/staff-link";
189
+ };
172
190
  readonly links: {
173
191
  readonly method: "GET";
174
192
  readonly path: "/links";
@@ -268,6 +286,11 @@ export interface AcmsClient {
268
286
  * RSVP with `book({ event, start, seats, name, email, answers, formVersion: form.version })`.
269
287
  */
270
288
  rsvpStatus(slug: string, o: RsvpStatusOptions): Promise<RsvpStatus>;
289
+ /**
290
+ * Whether a room is free over a span (`free`), and its taken times (`busy`), never what or who. Only rooms the site
291
+ * lets people check answer; others are a 404. Never cached; 30 checks a minute. Requires bookings and core 0.3.45.
292
+ */
293
+ roomFree(o: RoomFreeOptions): Promise<RoomFree>;
271
294
  /** A room booking or an RSVP. Sends the client's `locale` (or `o.locale`), so its emails and links speak it. */
272
295
  book(o: BookOptions): Promise<BookingResult>;
273
296
  openapi(o?: RequestOptions): Promise<OpenApiDocument>;
@@ -279,6 +302,14 @@ export interface AcmsClient {
279
302
  unregisterDevice(token: string, o?: RequestOptions): Promise<DeviceRemoved>;
280
303
  /** Topics the app can offer as notification toggles. */
281
304
  pushTopics(o?: RequestOptions): Promise<PushTopics>;
305
+ /**
306
+ * Staff only: link this registered device to the staff member whose one-time code (Admin → Test phones) it sends,
307
+ * so it gets their test notifications. `{ linked: true, staff: { name } }`. A wrong, used or expired code is a 400;
308
+ * too many tries a 429 (never retried). Sites without it (or with push off) answer 404.
309
+ */
310
+ linkStaffPhone(token: string, code: string, o?: RequestOptions): Promise<StaffLink>;
311
+ /** Unlink it (idempotent). Turning notifications off (`unregisterDevice`) unlinks it too. */
312
+ unlinkStaffPhone(token: string, o?: RequestOptions): Promise<StaffLink>;
282
313
  /** The links page: profile, socials and visible blocks. Requires `site.features.links`. */
283
314
  links(o?: RequestOptions): Promise<Links>;
284
315
  /**
package/dist/index.js CHANGED
@@ -11,10 +11,14 @@ export const endpoints = {
11
11
  // an event date's RSVP status: live seats and questions (never cached); RSVP with book()
12
12
  rsvpStatus: { method: 'GET', path: '/events/{slug}/rsvp' },
13
13
  resources: { method: 'GET', path: '/resources' }, availability: { method: 'GET', path: '/availability' },
14
+ // free or busy for a room people may request (no names; never cached)
15
+ roomFree: { method: 'GET', path: '/rooms/free' },
14
16
  book: { method: 'POST', path: '/bookings' }, openapi: { method: 'GET', path: '/openapi.json' },
15
17
  // push notifications (only when the site enables push; 404 otherwise)
16
18
  registerDevice: { method: 'POST', path: '/devices' }, updateDevice: { method: 'PUT', path: '/devices/{token}' },
17
19
  unregisterDevice: { method: 'DELETE', path: '/devices/{token}' }, pushTopics: { method: 'GET', path: '/push/topics' },
20
+ // a staff member's own test phone (a one-time code from the admin's Test phones)
21
+ linkStaffPhone: { method: 'POST', path: '/devices/{token}/staff-link' }, unlinkStaffPhone: { method: 'DELETE', path: '/devices/{token}/staff-link' },
18
22
  // links page and forms (owner switches in Admin → Mobile app)
19
23
  links: { method: 'GET', path: '/links' }, form: { method: 'GET', path: '/forms/{id}' }, submitForm: { method: 'POST', path: '/forms/{id}/responses' },
20
24
  // live video status (404 while the site's live video is off)
@@ -117,7 +121,7 @@ export function createClient(options) {
117
121
  const timer = setTimeout(() => controller.abort(new AcmsError(0, 'timeout', 'Request timed out.', undefined, undefined, key)), timeoutMs);
118
122
  const signal = controller.signal;
119
123
  // availability, live status and RSVP seats are never served from the cache
120
- const cacheable = endpoint.method === 'GET' && name !== 'availability' && name !== 'live' && name !== 'rsvpStatus' && !extra.headers && cache;
124
+ const cacheable = endpoint.method === 'GET' && name !== 'availability' && name !== 'live' && name !== 'rsvpStatus' && name !== 'roomFree' && !extra.headers && cache;
121
125
  // only replay what the server treats as repeatable: a lost reply to a code
122
126
  // exchange or a move must not run it twice
123
127
  const replayable = endpoint.method !== 'POST' || !!key || name === 'registerDevice';
@@ -291,6 +295,7 @@ export function createClient(options) {
291
295
  calendars: (o = {}) => request('calendars', o),
292
296
  availability: (o) => request('availability', o, { resource: o.resource, link: o.link, from: o.from, days: o.days, minutes: o.minutes }),
293
297
  rsvpStatus: (slug, o) => request('rsvpStatus', o, { start: o.start }, slug),
298
+ roomFree: (o) => request('roomFree', o, { room: o.room, start: o.start, end: o.end }),
294
299
  async book(o) {
295
300
  const { locale = options.locale, signal, idempotencyKey, ...body } = o;
296
301
  const key = idempotencyKey ?? defaultHeaders.get('Idempotency-Key') ?? uuid();
@@ -313,6 +318,8 @@ export function createClient(options) {
313
318
  updateDevice: (token, input, o = {}) => request('updateDevice', o, {}, token, input),
314
319
  unregisterDevice: (token, o = {}) => request('unregisterDevice', o, {}, token),
315
320
  pushTopics: (o = {}) => request('pushTopics', o),
321
+ linkStaffPhone: (token, code, o = {}) => request('linkStaffPhone', o, {}, token, { code }),
322
+ unlinkStaffPhone: (token, o = {}) => request('unlinkStaffPhone', o, {}, token),
316
323
  links: (o = {}) => request('links', o),
317
324
  live: (o = {}) => request('live', o),
318
325
  form: (id, o = {}) => request('form', o, {}, id),
package/dist/types.d.ts CHANGED
@@ -181,6 +181,11 @@ export type Page = {
181
181
  };
182
182
  }>;
183
183
  };
184
+ export type EventRoom = {
185
+ "slug": string;
186
+ "name": string;
187
+ "color": string | null;
188
+ };
184
189
  export type Event = {
185
190
  "id": string;
186
191
  "slug": string;
@@ -254,6 +259,11 @@ export type Event = {
254
259
  "name": string;
255
260
  "color": string;
256
261
  }>;
262
+ "rooms": Array<{
263
+ "slug": string;
264
+ "name": string;
265
+ "color": string | null;
266
+ }>;
257
267
  "notify": {
258
268
  "changes": Array<string>;
259
269
  };
@@ -274,6 +284,11 @@ export type Events = {
274
284
  "end": string;
275
285
  "allDay": boolean;
276
286
  "cancelled": boolean;
287
+ "rooms": Array<{
288
+ "slug": string;
289
+ "name": string;
290
+ "color": string | null;
291
+ }>;
277
292
  "event": {
278
293
  "id": string;
279
294
  "slug": string;
@@ -347,6 +362,11 @@ export type Events = {
347
362
  "name": string;
348
363
  "color": string;
349
364
  }>;
365
+ "rooms": Array<{
366
+ "slug": string;
367
+ "name": string;
368
+ "color": string | null;
369
+ }>;
350
370
  "notify": {
351
371
  "changes": Array<string>;
352
372
  };
@@ -361,6 +381,11 @@ export type EventDetail = {
361
381
  "end": string;
362
382
  "allDay": boolean;
363
383
  "cancelled": boolean;
384
+ "rooms": Array<{
385
+ "slug": string;
386
+ "name": string;
387
+ "color": string | null;
388
+ }>;
364
389
  }) | (null);
365
390
  "event": {
366
391
  "id": string;
@@ -435,6 +460,11 @@ export type EventDetail = {
435
460
  "name": string;
436
461
  "color": string;
437
462
  }>;
463
+ "rooms": Array<{
464
+ "slug": string;
465
+ "name": string;
466
+ "color": string | null;
467
+ }>;
438
468
  "notify": {
439
469
  "changes": Array<string>;
440
470
  };
@@ -502,6 +532,11 @@ export type SearchResults = {
502
532
  "end": string;
503
533
  "allDay": boolean;
504
534
  "cancelled": boolean;
535
+ "rooms": Array<{
536
+ "slug": string;
537
+ "name": string;
538
+ "color": string | null;
539
+ }>;
505
540
  }) | (null);
506
541
  "event": {
507
542
  "id": string;
@@ -576,6 +611,11 @@ export type SearchResults = {
576
611
  "name": string;
577
612
  "color": string;
578
613
  }>;
614
+ "rooms": Array<{
615
+ "slug": string;
616
+ "name": string;
617
+ "color": string | null;
618
+ }>;
579
619
  "notify": {
580
620
  "changes": Array<string>;
581
621
  };
@@ -876,6 +916,19 @@ export type Resources = {
876
916
  }>;
877
917
  }>;
878
918
  };
919
+ export type RoomFree = {
920
+ "room": {
921
+ "slug": string;
922
+ "name": string;
923
+ };
924
+ "start": string;
925
+ "end": string;
926
+ "free": boolean;
927
+ "busy": Array<{
928
+ "start": string;
929
+ "end": string;
930
+ }>;
931
+ };
879
932
  export type Availability = {
880
933
  "timezone": string;
881
934
  "mode": "slots" | "day" | "range";
@@ -943,6 +996,9 @@ export type Device = {
943
996
  "channels": Array<string>;
944
997
  "registeredAt": string;
945
998
  "lastSeenAt": string;
999
+ "staff": ({
1000
+ "name": string;
1001
+ }) | (null);
946
1002
  };
947
1003
  export type DeviceRegistration = {
948
1004
  "created": boolean;
@@ -954,12 +1010,21 @@ export type DeviceRegistration = {
954
1010
  "channels": Array<string>;
955
1011
  "registeredAt": string;
956
1012
  "lastSeenAt": string;
1013
+ "staff": ({
1014
+ "name": string;
1015
+ }) | (null);
957
1016
  };
958
1017
  };
959
1018
  export type DeviceRemoved = {
960
1019
  "token": string;
961
1020
  "deleted": true;
962
1021
  };
1022
+ export type StaffLink = {
1023
+ "linked": boolean;
1024
+ "staff": ({
1025
+ "name": string;
1026
+ }) | (null);
1027
+ };
963
1028
  export type PushTopics = {
964
1029
  "locale": string | null;
965
1030
  "topics": Array<{
@@ -1727,6 +1792,9 @@ export type EditorResource = {
1727
1792
  "notifyEmails": Array<string>;
1728
1793
  "visibility": "public" | "unlisted";
1729
1794
  "showOnCalendar": boolean;
1795
+ "color": string;
1796
+ "showOnEvents": boolean;
1797
+ "requestable": boolean;
1730
1798
  "placeId": string;
1731
1799
  "order": number;
1732
1800
  "createdAt": string;
@@ -1861,6 +1929,9 @@ export type EditorResourceList = {
1861
1929
  "notifyEmails": Array<string>;
1862
1930
  "visibility": "public" | "unlisted";
1863
1931
  "showOnCalendar": boolean;
1932
+ "color": string;
1933
+ "showOnEvents": boolean;
1934
+ "requestable": boolean;
1864
1935
  "placeId": string;
1865
1936
  "order": number;
1866
1937
  "createdAt": string;
@@ -2324,6 +2395,9 @@ export type EditorResourceInput = {
2324
2395
  "visibility"?: "public" | "unlisted";
2325
2396
  "showOnCalendar"?: boolean;
2326
2397
  "placeId"?: string;
2398
+ "color"?: ("") | (string);
2399
+ "showOnEvents"?: boolean;
2400
+ "requestable"?: boolean;
2327
2401
  "busyFeeds"?: Array<string>;
2328
2402
  "notifyEmails"?: Array<string>;
2329
2403
  };
@@ -2378,6 +2452,9 @@ export type EditorResourcePatch = {
2378
2452
  "visibility"?: "public" | "unlisted";
2379
2453
  "showOnCalendar"?: boolean;
2380
2454
  "placeId"?: string;
2455
+ "color"?: ("") | (string);
2456
+ "showOnEvents"?: boolean;
2457
+ "requestable"?: boolean;
2381
2458
  "busyFeeds"?: Array<string>;
2382
2459
  "notifyEmails"?: Array<string>;
2383
2460
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acms-subir/client",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "description": "Typed, dependency-free client for the ACMS public mobile API.",
5
5
  "type": "module",
6
6
  "license": "MIT",