@blazeo.com/calendar-client 1.0.23 → 1.0.26

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
@@ -22,12 +22,26 @@ cd your-app && npm link @blazeo.com/calendar-client
22
22
  Configure once at app startup, then use models and their methods:
23
23
 
24
24
  ```js
25
- import { configure, setBaseUrl, setConsumer, CalendarModel, createRootStore } from '@blazeo.com/calendar-client';
25
+ import {
26
+ configure,
27
+ setBaseUrl,
28
+ setConsumer,
29
+ setApiCredentials,
30
+ fetchAccessToken,
31
+ CalendarModel,
32
+ createRootStore,
33
+ } from '@blazeo.com/calendar-client';
26
34
 
27
35
  configure({ baseUrl: 'https://your-appointment-api.example.com' });
28
36
  // or: setBaseUrl('https://localhost:7051');
29
37
  setConsumer('my-app'); // optional: sent as Consumer header (e.g. for lead source)
30
38
 
39
+ // API JWT (for Authorized routes such as lead export)
40
+ setApiCredentials('your-api-key', 'your-api-secret');
41
+ await fetchAccessToken();
42
+ // Or set a token you obtained elsewhere:
43
+ // setAccessToken('eyJ...', '2026-05-19T12:00:00Z');
44
+
31
45
  // Calendar static methods (no store needed)
32
46
  const timezones = await CalendarModel.getTimeZones();
33
47
  const calendar = await CalendarModel.get('calendar-guid');
@@ -38,14 +52,80 @@ const cal = store.addCalendar({ calendarId: 'my-cal', name: 'My Calendar' });
38
52
  await cal.create(); // POST to backend
39
53
  ```
40
54
 
55
+ ### API JWT authentication
56
+
57
+ The backend issues JWTs via `POST Api/Auth/Token` (`api_key` + `api_secret`). There is no refresh endpoint; when the token expires (~1 hour), call `fetchAccessToken()` again.
58
+
59
+ | Function | Purpose |
60
+ |----------|---------|
61
+ | `setApiCredentials(apiKey, apiSecret)` | Store credentials for token exchange |
62
+ | `fetchAccessToken()` | Get JWT from API and store on config |
63
+ | `setAccessToken(token, expiresAtUtc?)` | Use a token you already have |
64
+ | `ensureValidAccessToken()` | Fetch only if missing or near expiry |
65
+ | `clearAuth()` | Clear token and credentials |
66
+
67
+ All `reqGet` / `reqPost` calls automatically attach `Authorization: Bearer …` and re-issue the token before requests when credentials are configured.
68
+
69
+ ```js
70
+ configure({ baseUrl: 'https://your-api', apiKey: 'key', apiSecret: 'secret' });
71
+ await fetchAccessToken();
72
+ await LeadModel.requestExport('company-key');
73
+ ```
74
+
41
75
  ## API overview
42
76
 
43
77
  - **CalendarModel (static):** `get`, `getByCompany`, `getTimeZones`, `getTimeZone`, `getParticipants`, `getMonth`, `getEvents`, etc.
44
78
  - **EventModel (instance):** `get`, `create`, `cancel`, `getCancellable`, `getAvailability`, `setReminder`
45
79
  - **FlowModel:** Same pattern as Calendar — `FlowModel.create({}, { env })` with no fields; static `get`, `getRaw`, `list`, `createFlow`, `updateFlow`, `delete`, `duplicate`, appearance/embed/public/preview helpers; instance methods mirror those using `flowId` on the snapshot
46
80
  - **LeadModel:** `LeadModel.create({}, { env })`; static `get`, `getRaw`, `getByEmail`, `getByCompany`; instance `get`, `getByEmail`, `getByCompany` (uses `leadId` / `email` / `companyKey` on the snapshot)
81
+ - **ParticipantModel:** static `get`, `getByEmail`, `getByIds`, `getAll`, …; instance `getByEmail` uses `email` + `companyKey` on the snapshot (see `GET /participant/getbyemail`)
82
+ - **AuthModel (calendar OAuth / Connect Calendar):** `getCalendarProviders`, `getAuthorizationUrl`, `getAuthorizationStatus`, `openOAuthPopup`, `onCalendarAuthMessage` — see [Calendar authorization flow](#calendar-authorization-direct-ui)
47
83
  - **RootStore:** `addCalendar`, `addEvent`
48
84
 
85
+ ### Calendar authorization (direct UI)
86
+
87
+ When the user connects Google or Outlook from the Scheduling modal:
88
+
89
+ ```js
90
+ import {
91
+ configure,
92
+ AuthModel,
93
+ CalendarEmailProvider,
94
+ CALENDAR_AUTH_MESSAGE_TYPE,
95
+ } from '@blazeo.com/calendar-client';
96
+
97
+ configure({ baseUrl: 'https://your-appointment-api.example.com' });
98
+
99
+ const participantId = '...'; // logged-in participant GUID
100
+
101
+ // Optional: already connected?
102
+ const statusRes = await AuthModel.getAuthorizationStatus(participantId);
103
+ if (statusRes.status === 'success' && statusRes.data?.isAuthorized) {
104
+ // show connected UI
105
+ }
106
+
107
+ // Load provider cards (Google / Gmail, Microsoft Outlook)
108
+ const providersRes = await AuthModel.getCalendarProviders();
109
+ const providers = providersRes.data; // [{ key: 'google', displayName: 'Google / Gmail', ... }, ...]
110
+
111
+ // User clicks Google
112
+ const urlRes = await AuthModel.getAuthorizationUrl(participantId, 'google');
113
+ const popup = AuthModel.openOAuthPopup(urlRes.data.authorizationUrl);
114
+
115
+ const unsubscribe = AuthModel.onCalendarAuthMessage(async (payload) => {
116
+ if (payload.status === 'success') {
117
+ const check = await AuthModel.getAuthorizationStatus(participantId);
118
+ if (check.data?.isAuthorized) {
119
+ // close modal, show success
120
+ }
121
+ }
122
+ });
123
+
124
+ // On modal unmount: unsubscribe(); popup?.close();
125
+ ```
126
+
127
+ `email_provider` for `getAuthorizationUrl`: `google`, `gmail`, `1`, `outlook`, `microsoft`, or `2`.
128
+
49
129
  All methods return `Promise<{ status, data?, message? }>`. Check `response.status === 'success'` and use `response.data`.
50
130
 
51
131
  ## Samples
package/dist/index.d.ts CHANGED
@@ -1,9 +1,59 @@
1
1
  /** @blazeo.com/calendar-client - type declarations */
2
2
 
3
- export function configure(env: { baseUrl?: string; consumer?: string; fetch?: typeof fetch; getDefaultOffset?: () => number }): void;
4
- export function getConfig(): { baseUrl?: string; consumer?: string; fetch?: typeof fetch; getDefaultOffset?: () => number } | null;
3
+ export type AccessTokenResult = {
4
+ status: string;
5
+ message?: string;
6
+ accessToken?: string;
7
+ expiresAtUtc?: string | null;
8
+ tokenType?: string;
9
+ };
10
+
11
+ export type AuthState = {
12
+ accessToken?: string;
13
+ tokenExpiresAt?: string;
14
+ hasApiCredentials: boolean;
15
+ };
16
+
17
+ export function configure(env: {
18
+ baseUrl?: string;
19
+ consumer?: string;
20
+ fetch?: typeof fetch;
21
+ getDefaultOffset?: () => number;
22
+ accessToken?: string;
23
+ expiresAtUtc?: string;
24
+ tokenExpiresAt?: string;
25
+ expires_at_utc?: string;
26
+ apiKey?: string;
27
+ api_key?: string;
28
+ apiSecret?: string;
29
+ api_secret?: string;
30
+ }): void;
31
+
32
+ export function getConfig(): {
33
+ baseUrl?: string;
34
+ consumer?: string;
35
+ fetch?: typeof fetch;
36
+ getDefaultOffset?: () => number;
37
+ accessToken?: string;
38
+ tokenExpiresAt?: string;
39
+ hasApiCredentials?: boolean;
40
+ } | null;
41
+
5
42
  export function setBaseUrl(baseUrl: string): void;
6
43
  export function setConsumer(consumer: string): void;
44
+ export function setAccessToken(accessToken: string, expiresAtUtc?: string): void;
45
+ export function clearAccessToken(): void;
46
+ export function setApiCredentials(apiKey: string, apiSecret: string): void;
47
+ export function clearApiCredentials(): void;
48
+ export function clearAuth(): void;
49
+ export function getAuth(): AuthState;
50
+ export function fetchAccessToken(apiKey?: string, apiSecret?: string): Promise<AccessTokenResult>;
51
+ export function ensureValidAccessToken(): Promise<string | undefined>;
52
+ export function requestAccessToken(apiKey: string, apiSecret: string, opts?: { baseUrl?: string; fetch?: typeof fetch }): Promise<AccessTokenResult>;
53
+ export function isAccessTokenExpired(expiresAtUtc?: string | number | Date | null, skewMs?: number): boolean;
54
+ export function buildAuthHeaders(extra?: Record<string, string>): Record<string, string>;
55
+ export const TOKEN_PATH: '/Api/Auth/Token';
56
+ export const DEFAULT_TOKEN_REFRESH_SKEW_MS: number;
7
57
  export function getConfigStore(): unknown;
8
58
 
9
59
  export const ConfigModel: unknown;
@@ -79,6 +129,7 @@ export const CalendarParticipantModel: {
79
129
  };
80
130
  export const ParticipantModel: {
81
131
  get(participantId: string): Promise<unknown>;
132
+ getByEmail(email: string, companyKey: string): Promise<unknown>;
82
133
  getByIds(participantIds: string[] | string): Promise<unknown[] | null>;
83
134
  getAll(companyKey: string): Promise<unknown[] | null>;
84
135
  add(payload: object, calendarId?: string): Promise<unknown>;
@@ -246,6 +297,86 @@ export const LeadModel: {
246
297
  create(snapshot?: object, options?: { env?: object }): unknown;
247
298
  };
248
299
 
300
+ export type CalendarProviderOption = {
301
+ emailProvider: number;
302
+ key: string;
303
+ name: string;
304
+ displayName: string;
305
+ description: string;
306
+ scope: string;
307
+ redirectUrl: string;
308
+ };
309
+
310
+ export type ParticipantAuthorizationUrl = {
311
+ participantId: string;
312
+ emailProvider: number;
313
+ providerKey: string;
314
+ authorizationUrl: string;
315
+ redirectUrl: string;
316
+ };
317
+
318
+ export type CalendarProviderAuthorizationState = {
319
+ emailProvider: number;
320
+ key: string;
321
+ name: string;
322
+ hasCredentials: boolean;
323
+ isAuthorized: boolean;
324
+ isSelected: boolean;
325
+ };
326
+
327
+ export type ParticipantAuthorizationStatus = {
328
+ participantId: string;
329
+ email: string;
330
+ emailProvider: number;
331
+ providerKey: string;
332
+ hasCredentials: boolean;
333
+ isAuthorized: boolean;
334
+ providers: CalendarProviderAuthorizationState[];
335
+ };
336
+
337
+ export const CALENDAR_AUTH_MESSAGE_TYPE: 'calendar-authorization';
338
+
339
+ export const CalendarEmailProvider: {
340
+ Google: 1;
341
+ Microsoft: 2;
342
+ Default: 3;
343
+ };
344
+
345
+ export const AuthModel: {
346
+ CALENDAR_AUTH_MESSAGE_TYPE: typeof CALENDAR_AUTH_MESSAGE_TYPE;
347
+ EmailProvider: typeof CalendarEmailProvider;
348
+ getCalendarProviders(opts?: { host?: string }): Promise<{
349
+ status: string;
350
+ data?: CalendarProviderOption[];
351
+ message?: string;
352
+ }>;
353
+ getAuthorizationUrl(
354
+ participantId: string,
355
+ emailProvider: string | number,
356
+ opts?: { host?: string }
357
+ ): Promise<{
358
+ status: string;
359
+ data?: ParticipantAuthorizationUrl;
360
+ message?: string;
361
+ }>;
362
+ getAuthorizationStatus(participantId: string): Promise<{
363
+ status: string;
364
+ data?: ParticipantAuthorizationStatus;
365
+ message?: string;
366
+ }>;
367
+ openOAuthPopup(
368
+ authorizationUrl: string,
369
+ opts?: { width?: number; height?: number }
370
+ ): Window | null;
371
+ onCalendarAuthMessage(
372
+ handler: (payload: {
373
+ type: string;
374
+ status: 'success' | 'error';
375
+ message?: string;
376
+ }) => void
377
+ ): () => void;
378
+ };
379
+
249
380
  export const CustomFieldModel: {
250
381
  getAll(calendarId: string): Promise<unknown[] | null>;
251
382
  getFieldType(fieldType: string): Promise<unknown>;
@@ -270,3 +401,4 @@ export const AttendeeStatus: Record<string, number>;
270
401
  export const RecurringFrequency: Record<string, number>;
271
402
  export const DayOfWeek: Record<string, number>;
272
403
  export const LocationType: Record<string, number>;
404
+ export const EmailProvider: Record<string, number>;