@blazeo.com/calendar-client 1.0.27 → 1.0.29

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
@@ -24,23 +24,19 @@ Configure once at app startup, then use models and their methods:
24
24
  ```js
25
25
  import {
26
26
  configure,
27
- setBaseUrl,
28
- setConsumer,
29
- setApiCredentials,
30
- fetchAccessToken,
27
+ setAccessToken,
28
+ setGetAccessToken,
31
29
  CalendarModel,
32
30
  createRootStore,
33
31
  } from '@blazeo.com/calendar-client';
34
32
 
35
- configure({ baseUrl: 'https://your-appointment-api.example.com' });
36
- // or: setBaseUrl('https://localhost:7051');
37
- setConsumer('my-app'); // optional: sent as Consumer header (e.g. for lead source)
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');
33
+ configure({
34
+ baseUrl: 'https://your-appointment-api.example.com',
35
+ consumer: 'my-app', // optional Consumer header
36
+ accessToken: yourIdpAccessToken, // JWT from your identity provider
37
+ expiresAtUtc: '2026-05-21T12:00:00Z', // optional used before auto-refresh
38
+ getAccessToken: async () => yourIdp.getAccessToken(), // optional refresh callback
39
+ });
44
40
 
45
41
  // Calendar static methods (no store needed)
46
42
  const timezones = await CalendarModel.getTimeZones();
@@ -52,23 +48,22 @@ const cal = store.addCalendar({ calendarId: 'my-cal', name: 'My Calendar' });
52
48
  await cal.create(); // POST to backend
53
49
  ```
54
50
 
55
- ### API JWT authentication
51
+ ### JWT authentication
56
52
 
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.
53
+ The Appointment API validates Bearer JWTs from your identity provider (`Authentication:Jwt:Authority` on the server). This package does **not** exchange api keys for tokens pass the JWT your app already has.
58
54
 
59
55
  | Function | Purpose |
60
56
  |----------|---------|
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 |
57
+ | `setAccessToken(token, expiresAtUtc?)` | Store JWT from your IdP |
58
+ | `setGetAccessToken(fn)` | Async callback to refresh when missing/near expiry |
59
+ | `ensureValidAccessToken()` | Resolve token before a request (uses callback if needed) |
60
+ | `clearAuth()` | Clear token and refresh callback |
66
61
 
67
- All `reqGet` / `reqPost` calls automatically attach `Authorization: Bearer …` and re-issue the token before requests when credentials are configured.
62
+ All `reqGet` / `reqPost` calls attach `Authorization: Bearer …` when a token is configured.
68
63
 
69
64
  ```js
70
- configure({ baseUrl: 'https://your-api', apiKey: 'key', apiSecret: 'secret' });
71
- await fetchAccessToken();
65
+ setAccessToken(session.accessToken, session.expiresAt);
66
+ setGetAccessToken(() => authService.acquireTokenSilent());
72
67
  await LeadModel.requestExport('company-key');
73
68
  ```
74
69
 
package/dist/index.d.ts CHANGED
@@ -1,19 +1,8 @@
1
1
  /** @blazeo.com/calendar-client - type declarations */
2
2
 
3
- export type AccessTokenResult = {
4
- status: string;
5
- message?: string;
6
- accessToken?: string;
7
- expiresAtUtc?: string | null;
8
- tokenType?: string;
9
- };
10
-
11
3
  export type AuthState = {
12
4
  accessToken?: string;
13
5
  tokenExpiresAt?: string;
14
- apiKey?: string;
15
- apiSecret?: string;
16
- hasApiCredentials: boolean;
17
6
  };
18
7
 
19
8
  export function configure(env: {
@@ -25,10 +14,8 @@ export function configure(env: {
25
14
  expiresAtUtc?: string;
26
15
  tokenExpiresAt?: string;
27
16
  expires_at_utc?: string;
28
- apiKey?: string;
29
- api_key?: string;
30
- apiSecret?: string;
31
- api_secret?: string;
17
+ /** Called when the stored JWT is missing or near expiry; return a fresh token from your IdP. */
18
+ getAccessToken?: () => Promise<string | undefined>;
32
19
  }): void;
33
20
 
34
21
  export function getConfig(): {
@@ -38,23 +25,18 @@ export function getConfig(): {
38
25
  getDefaultOffset?: () => number;
39
26
  accessToken?: string;
40
27
  tokenExpiresAt?: string;
41
- hasApiCredentials?: boolean;
42
28
  } | null;
43
29
 
44
30
  export function setBaseUrl(baseUrl: string): void;
45
31
  export function setConsumer(consumer: string): void;
46
32
  export function setAccessToken(accessToken: string, expiresAtUtc?: string): void;
47
33
  export function clearAccessToken(): void;
48
- export function setApiCredentials(apiKey: string, apiSecret: string): void;
49
- export function clearApiCredentials(): void;
34
+ export function setGetAccessToken(fn: () => Promise<string | undefined>): void;
50
35
  export function clearAuth(): void;
51
36
  export function getAuth(): AuthState;
52
- export function fetchAccessToken(apiKey?: string, apiSecret?: string): Promise<AccessTokenResult>;
53
37
  export function ensureValidAccessToken(): Promise<string | undefined>;
54
- export function requestAccessToken(apiKey: string, apiSecret: string, opts?: { baseUrl?: string; fetch?: typeof fetch }): Promise<AccessTokenResult>;
55
38
  export function isAccessTokenExpired(expiresAtUtc?: string | number | Date | null, skewMs?: number): boolean;
56
39
  export function buildAuthHeaders(extra?: Record<string, string>): Record<string, string>;
57
- export const TOKEN_PATH: '/Api/Auth/Token';
58
40
  export const DEFAULT_TOKEN_REFRESH_SKEW_MS: number;
59
41
  export function getConfigStore(): unknown;
60
42
 
@@ -336,6 +318,20 @@ export type ParticipantAuthorizationStatus = {
336
318
  providers: CalendarProviderAuthorizationState[];
337
319
  };
338
320
 
321
+ export type ParticipantData = {
322
+ id?: number | null;
323
+ participantId: string;
324
+ companyKey?: string | null;
325
+ alias?: string;
326
+ email?: string;
327
+ isApproved: boolean;
328
+ isAvailable: boolean;
329
+ provider: number;
330
+ createdOn?: string | null;
331
+ modifiedOn?: string | null;
332
+ isDeleted?: boolean;
333
+ };
334
+
339
335
  export const CALENDAR_AUTH_MESSAGE_TYPE: 'calendar-authorization';
340
336
 
341
337
  export const CalendarEmailProvider: {
@@ -366,6 +362,11 @@ export const AuthModel: {
366
362
  data?: ParticipantAuthorizationStatus;
367
363
  message?: string;
368
364
  }>;
365
+ IsAuthorized(participantId: string): Promise<{
366
+ status: string;
367
+ data?: ParticipantData;
368
+ message?: string;
369
+ }>;
369
370
  openOAuthPopup(
370
371
  authorizationUrl: string,
371
372
  opts?: { width?: number; height?: number }
package/dist/index.js CHANGED
@@ -49,27 +49,23 @@ __export(index_exports, {
49
49
  RecurringFrequency: () => RecurringFrequency,
50
50
  RootStore: () => RootStore,
51
51
  SettingModel: () => Setting_default,
52
- TOKEN_PATH: () => TOKEN_PATH,
53
52
  TimeFrameModel: () => TimeFrame_default,
54
53
  TimeSlotModel: () => TimeSlot_default,
55
54
  Unit: () => Unit,
56
55
  buildAuthHeaders: () => buildAuthHeaders,
57
56
  clearAccessToken: () => clearAccessToken,
58
- clearApiCredentials: () => clearApiCredentials,
59
57
  clearAuth: () => clearAuth,
60
58
  configure: () => configure,
61
59
  createRootStore: () => createRootStore,
62
60
  ensureValidAccessToken: () => ensureValidAccessToken,
63
- fetchAccessToken: () => fetchAccessToken2,
64
61
  getAuth: () => getAuth,
65
62
  getConfig: () => getConfig,
66
63
  getConfigStore: () => getConfigStore,
67
64
  isAccessTokenExpired: () => isAccessTokenExpired,
68
- requestAccessToken: () => requestAccessToken,
69
65
  setAccessToken: () => setAccessToken,
70
- setApiCredentials: () => setApiCredentials,
71
66
  setBaseUrl: () => setBaseUrl,
72
- setConsumer: () => setConsumer
67
+ setConsumer: () => setConsumer,
68
+ setGetAccessToken: () => setGetAccessToken
73
69
  });
74
70
  module.exports = __toCommonJS(index_exports);
75
71
 
@@ -85,8 +81,8 @@ var ConfigModel = import_mobx_state_tree.types.model("Config", {
85
81
  fetch: void 0,
86
82
  accessToken: void 0,
87
83
  tokenExpiresAt: void 0,
88
- apiKey: void 0,
89
- apiSecret: void 0,
84
+ /** Host app supplies a fresh JWT (e.g. from your IdP) when the stored token is missing/expired. */
85
+ getAccessToken: void 0,
90
86
  getDefaultOffset: () => -(/* @__PURE__ */ new Date()).getTimezoneOffset()
91
87
  })).actions((self) => ({
92
88
  setBaseUrl(url) {
@@ -101,6 +97,9 @@ var ConfigModel = import_mobx_state_tree.types.model("Config", {
101
97
  setGetDefaultOffset(fn) {
102
98
  self.getDefaultOffset = fn;
103
99
  },
100
+ setGetAccessToken(fn) {
101
+ self.getAccessToken = fn;
102
+ },
104
103
  setAccessToken(token, expiresAtUtc = void 0) {
105
104
  self.accessToken = token ? String(token) : void 0;
106
105
  self.tokenExpiresAt = expiresAtUtc != null && expiresAtUtc !== "" ? String(expiresAtUtc) : void 0;
@@ -109,40 +108,24 @@ var ConfigModel = import_mobx_state_tree.types.model("Config", {
109
108
  self.accessToken = void 0;
110
109
  self.tokenExpiresAt = void 0;
111
110
  },
112
- setApiCredentials(apiKey, apiSecret) {
113
- self.apiKey = apiKey != null ? String(apiKey) : void 0;
114
- self.apiSecret = apiSecret != null ? String(apiSecret) : void 0;
115
- },
116
- clearApiCredentials() {
117
- self.apiKey = void 0;
118
- self.apiSecret = void 0;
119
- },
120
111
  clearAuth() {
121
112
  self.clearAccessToken();
122
- self.clearApiCredentials();
113
+ self.getAccessToken = void 0;
123
114
  },
124
115
  configure(env) {
125
116
  if (env.baseUrl != null) self.baseUrl = env.baseUrl;
126
117
  if (env.consumer != null) self.consumer = env.consumer;
127
118
  if (env.fetch != null) self.fetch = env.fetch;
128
119
  if (env.getDefaultOffset != null) self.getDefaultOffset = env.getDefaultOffset;
120
+ if (env.getAccessToken != null) self.getAccessToken = env.getAccessToken;
129
121
  if (env.accessToken != null) {
130
122
  self.setAccessToken(
131
123
  env.accessToken,
132
124
  env.expiresAtUtc ?? env.tokenExpiresAt ?? env.expires_at_utc
133
125
  );
134
126
  }
135
- if (env.apiKey != null || env.api_key != null) {
136
- self.apiKey = String(env.apiKey ?? env.api_key);
137
- }
138
- if (env.apiSecret != null || env.api_secret != null) {
139
- self.apiSecret = String(env.apiSecret ?? env.api_secret);
140
- }
141
127
  }
142
128
  })).views((self) => ({
143
- get hasApiCredentials() {
144
- return Boolean(self.apiKey && self.apiSecret);
145
- },
146
129
  getEnv() {
147
130
  return {
148
131
  baseUrl: self.baseUrl || void 0,
@@ -150,8 +133,7 @@ var ConfigModel = import_mobx_state_tree.types.model("Config", {
150
133
  fetch: self.fetch,
151
134
  getDefaultOffset: self.getDefaultOffset,
152
135
  accessToken: self.accessToken,
153
- tokenExpiresAt: self.tokenExpiresAt,
154
- hasApiCredentials: self.hasApiCredentials
136
+ tokenExpiresAt: self.tokenExpiresAt
155
137
  };
156
138
  }
157
139
  }));
@@ -163,26 +145,7 @@ function getConfigStore() {
163
145
  var ConfigModel_default = ConfigModel;
164
146
 
165
147
  // src/apiAuth.js
166
- var TOKEN_PATH = "/Api/Auth/Token";
167
148
  var DEFAULT_TOKEN_REFRESH_SKEW_MS = 6e4;
168
- function defaultFetch() {
169
- if (typeof fetch === "undefined") {
170
- throw new Error("fetch not available");
171
- }
172
- return fetch;
173
- }
174
- function pickTokenPayload(data) {
175
- if (!data || typeof data !== "object") return null;
176
- const nested = data.data && typeof data.data === "object" ? data.data : data;
177
- const accessToken = nested.access_token ?? nested.accessToken ?? nested.AccessToken ?? null;
178
- if (!accessToken) return null;
179
- const expiresAtUtc = nested.expires_at_utc ?? nested.expiresAtUtc ?? nested.ExpiresAtUtc ?? null;
180
- return {
181
- accessToken: String(accessToken),
182
- expiresAtUtc: expiresAtUtc != null ? String(expiresAtUtc) : null,
183
- tokenType: nested.token_type ?? nested.tokenType ?? "Bearer"
184
- };
185
- }
186
149
  function isAccessTokenExpired(expiresAtUtc, skewMs = DEFAULT_TOKEN_REFRESH_SKEW_MS) {
187
150
  if (expiresAtUtc == null || expiresAtUtc === "") return false;
188
151
  const expMs = new Date(expiresAtUtc).getTime();
@@ -193,77 +156,22 @@ function getAuthState() {
193
156
  const store = getConfigStore();
194
157
  return {
195
158
  accessToken: store.accessToken ?? void 0,
196
- tokenExpiresAt: store.tokenExpiresAt ?? void 0,
197
- apiKey: store.apiKey ?? void 0,
198
- apiSecret: store.apiSecret ?? void 0,
199
- hasApiCredentials: Boolean(store.apiKey && store.apiSecret)
200
- };
201
- }
202
- async function requestAccessToken(apiKey, apiSecret, opts = {}) {
203
- const store = getConfigStore();
204
- const baseUrl = opts.baseUrl ?? store.baseUrl;
205
- if (!baseUrl) {
206
- return { status: "failure", message: "baseUrl required. Call configure({ baseUrl }) first." };
207
- }
208
- const key = apiKey != null ? String(apiKey).trim() : "";
209
- const secret = apiSecret != null ? String(apiSecret).trim() : "";
210
- if (!key || !secret) {
211
- return { status: "failure", message: "api_key and api_secret are required" };
212
- }
213
- const fetchFn = opts.fetch ?? store.fetch ?? defaultFetch();
214
- const url = `${String(baseUrl).replace(/\/+$/, "")}${TOKEN_PATH}`;
215
- const httpRes = await fetchFn(url, {
216
- method: "POST",
217
- headers: { "Content-Type": "application/json" },
218
- body: JSON.stringify({ api_key: key, api_secret: secret })
219
- });
220
- const text = await httpRes.text();
221
- let body;
222
- try {
223
- body = JSON.parse(text);
224
- } catch {
225
- body = { status: "failure", message: text || httpRes.statusText };
226
- }
227
- if (!httpRes.ok && body.status !== "failure") {
228
- body.status = "failure";
229
- body.message = body.message ?? `HTTP ${httpRes.status}`;
230
- }
231
- if (body.status !== "success") {
232
- return {
233
- status: "failure",
234
- message: body.message ?? "Failed to obtain access token"
235
- };
236
- }
237
- const parsed = pickTokenPayload(body);
238
- if (!parsed) {
239
- return { status: "failure", message: "Token response missing access_token" };
240
- }
241
- return {
242
- status: "success",
243
- accessToken: parsed.accessToken,
244
- expiresAtUtc: parsed.expiresAtUtc,
245
- tokenType: parsed.tokenType
159
+ tokenExpiresAt: store.tokenExpiresAt ?? void 0
246
160
  };
247
161
  }
248
- async function fetchAccessToken(apiKey, apiSecret) {
249
- const store = getConfigStore();
250
- const key = apiKey ?? store.apiKey;
251
- const secret = apiSecret ?? store.apiSecret;
252
- const result = await requestAccessToken(key, secret);
253
- if (result.status === "success") {
254
- store.setAccessToken(result.accessToken, result.expiresAtUtc);
255
- }
256
- return result;
257
- }
258
162
  async function ensureAccessToken() {
259
163
  const store = getConfigStore();
260
- const { accessToken, tokenExpiresAt, apiKey, apiSecret } = getAuthState();
164
+ const { accessToken, tokenExpiresAt } = getAuthState();
261
165
  if (accessToken && !isAccessTokenExpired(tokenExpiresAt)) {
262
166
  return accessToken;
263
167
  }
264
- if (apiKey && apiSecret) {
265
- const result = await fetchAccessToken(apiKey, apiSecret);
266
- if (result.status === "success") return result.accessToken;
168
+ const refresh = store.getAccessToken;
169
+ if (typeof refresh === "function") {
170
+ const next = await refresh();
171
+ if (next) {
172
+ store.setAccessToken(next);
173
+ return next;
174
+ }
267
175
  }
268
176
  return accessToken;
269
177
  }
@@ -300,11 +208,8 @@ function setAccessToken(accessToken, expiresAtUtc) {
300
208
  function clearAccessToken() {
301
209
  getConfigStore().clearAccessToken();
302
210
  }
303
- function setApiCredentials(apiKey, apiSecret) {
304
- getConfigStore().setApiCredentials(apiKey, apiSecret);
305
- }
306
- function clearApiCredentials() {
307
- getConfigStore().clearApiCredentials();
211
+ function setGetAccessToken(fn) {
212
+ getConfigStore().setGetAccessToken(fn);
308
213
  }
309
214
  function clearAuth() {
310
215
  getConfigStore().clearAuth();
@@ -312,9 +217,6 @@ function clearAuth() {
312
217
  function getAuth() {
313
218
  return getAuthState();
314
219
  }
315
- function fetchAccessToken2(apiKey, apiSecret) {
316
- return fetchAccessToken(apiKey, apiSecret);
317
- }
318
220
  function ensureValidAccessToken() {
319
221
  return ensureAccessToken();
320
222
  }
@@ -1587,23 +1489,27 @@ var ParticipantModel = import_mobx_state_tree11.types.model("Participant", {
1587
1489
  }
1588
1490
  };
1589
1491
  });
1590
- function mapFromApi2(d) {
1492
+ function mapParticipantFromApi(d) {
1591
1493
  if (!d) return d;
1592
1494
  const pick2 = (...keys) => keys.reduce((v, k) => v ?? d[k], void 0);
1593
1495
  const n = (v) => v != null && v !== "" ? Number(v) : void 0;
1594
1496
  return {
1497
+ id: n(pick2("id", "Id")) ?? null,
1595
1498
  participantId: String(pick2("participantId", "ParticipantId", "participant_id") ?? ""),
1596
1499
  companyKey: pick2("companyKey", "CompanyKey", "company_key") ?? null,
1597
1500
  alias: pick2("alias", "Alias") ?? "",
1598
1501
  email: pick2("email", "Email") ?? "",
1599
1502
  isApproved: Boolean(pick2("isApproved", "IsApproved", "is_approved")),
1600
1503
  isAvailable: Boolean(pick2("isAvailable", "IsAvailable", "is_available")),
1601
- provider: n(pick2("provider", "Provider")) ?? 0,
1504
+ provider: n(pick2("provider", "Provider", "emailProvider", "EmailProvider", "email_provider")) ?? 0,
1602
1505
  createdOn: pick2("createdOn", "CreatedOn", "created_on") ?? null,
1603
1506
  modifiedOn: pick2("modifiedOn", "ModifiedOn", "modified_on") ?? null,
1604
1507
  isDeleted: Boolean(pick2("isDeleted", "IsDeleted", "is_deleted"))
1605
1508
  };
1606
1509
  }
1510
+ function mapFromApi2(d) {
1511
+ return mapParticipantFromApi(d);
1512
+ }
1607
1513
  function toPayload(self) {
1608
1514
  return {
1609
1515
  participantId: self.participantId,
@@ -2944,6 +2850,22 @@ var AuthModel = {
2944
2850
  }
2945
2851
  return res;
2946
2852
  },
2853
+ /**
2854
+ * GET Auth/IsAuthorized — validates OAuth with the provider; clears isApproved when not authorized.
2855
+ * Returns the updated participant record (same shape as participant/get).
2856
+ * @param {string} participantId
2857
+ * @returns {Promise<{ status: string, data?: object, message?: string }>}
2858
+ */
2859
+ async IsAuthorized(participantId) {
2860
+ const id = participantId != null ? String(participantId).trim() : "";
2861
+ if (!id) return { status: "failure", message: "participantId required" };
2862
+ const { reqGet } = createRequestHelpersFromEnv(getConfig());
2863
+ const res = await reqGet("/Auth/IsAuthorized", { participant_id: id });
2864
+ if (res.status === "success" && res.data) {
2865
+ res.data = mapParticipantFromApi(res.data);
2866
+ }
2867
+ return res;
2868
+ },
2947
2869
  /**
2948
2870
  * Open Google/Microsoft OAuth in a centered popup.
2949
2871
  * @param {string} authorizationUrl — from getAuthorizationUrl().data.authorizationUrl
@@ -2964,6 +2886,8 @@ var AuthModel = {
2964
2886
  },
2965
2887
  /**
2966
2888
  * Subscribe to calendar-authorization postMessage from OAuth popup (Authorized.html / Error.html).
2889
+ * Do not use popup.closed to detect completion — Cross-Origin-Opener-Policy on the parent app
2890
+ * blocks that check after the popup navigates to Google/Microsoft.
2967
2891
  * @param {(payload: { type: string, status: 'success' | 'error', message?: string }) => void} handler
2968
2892
  * @returns {() => void} unsubscribe
2969
2893
  */
@@ -3035,25 +2959,21 @@ function createRootStore(initialState = {}) {
3035
2959
  RecurringFrequency,
3036
2960
  RootStore,
3037
2961
  SettingModel,
3038
- TOKEN_PATH,
3039
2962
  TimeFrameModel,
3040
2963
  TimeSlotModel,
3041
2964
  Unit,
3042
2965
  buildAuthHeaders,
3043
2966
  clearAccessToken,
3044
- clearApiCredentials,
3045
2967
  clearAuth,
3046
2968
  configure,
3047
2969
  createRootStore,
3048
2970
  ensureValidAccessToken,
3049
- fetchAccessToken,
3050
2971
  getAuth,
3051
2972
  getConfig,
3052
2973
  getConfigStore,
3053
2974
  isAccessTokenExpired,
3054
- requestAccessToken,
3055
2975
  setAccessToken,
3056
- setApiCredentials,
3057
2976
  setBaseUrl,
3058
- setConsumer
2977
+ setConsumer,
2978
+ setGetAccessToken
3059
2979
  });
package/dist/index.mjs CHANGED
@@ -10,8 +10,8 @@ var ConfigModel = types.model("Config", {
10
10
  fetch: void 0,
11
11
  accessToken: void 0,
12
12
  tokenExpiresAt: void 0,
13
- apiKey: void 0,
14
- apiSecret: void 0,
13
+ /** Host app supplies a fresh JWT (e.g. from your IdP) when the stored token is missing/expired. */
14
+ getAccessToken: void 0,
15
15
  getDefaultOffset: () => -(/* @__PURE__ */ new Date()).getTimezoneOffset()
16
16
  })).actions((self) => ({
17
17
  setBaseUrl(url) {
@@ -26,6 +26,9 @@ var ConfigModel = types.model("Config", {
26
26
  setGetDefaultOffset(fn) {
27
27
  self.getDefaultOffset = fn;
28
28
  },
29
+ setGetAccessToken(fn) {
30
+ self.getAccessToken = fn;
31
+ },
29
32
  setAccessToken(token, expiresAtUtc = void 0) {
30
33
  self.accessToken = token ? String(token) : void 0;
31
34
  self.tokenExpiresAt = expiresAtUtc != null && expiresAtUtc !== "" ? String(expiresAtUtc) : void 0;
@@ -34,40 +37,24 @@ var ConfigModel = types.model("Config", {
34
37
  self.accessToken = void 0;
35
38
  self.tokenExpiresAt = void 0;
36
39
  },
37
- setApiCredentials(apiKey, apiSecret) {
38
- self.apiKey = apiKey != null ? String(apiKey) : void 0;
39
- self.apiSecret = apiSecret != null ? String(apiSecret) : void 0;
40
- },
41
- clearApiCredentials() {
42
- self.apiKey = void 0;
43
- self.apiSecret = void 0;
44
- },
45
40
  clearAuth() {
46
41
  self.clearAccessToken();
47
- self.clearApiCredentials();
42
+ self.getAccessToken = void 0;
48
43
  },
49
44
  configure(env) {
50
45
  if (env.baseUrl != null) self.baseUrl = env.baseUrl;
51
46
  if (env.consumer != null) self.consumer = env.consumer;
52
47
  if (env.fetch != null) self.fetch = env.fetch;
53
48
  if (env.getDefaultOffset != null) self.getDefaultOffset = env.getDefaultOffset;
49
+ if (env.getAccessToken != null) self.getAccessToken = env.getAccessToken;
54
50
  if (env.accessToken != null) {
55
51
  self.setAccessToken(
56
52
  env.accessToken,
57
53
  env.expiresAtUtc ?? env.tokenExpiresAt ?? env.expires_at_utc
58
54
  );
59
55
  }
60
- if (env.apiKey != null || env.api_key != null) {
61
- self.apiKey = String(env.apiKey ?? env.api_key);
62
- }
63
- if (env.apiSecret != null || env.api_secret != null) {
64
- self.apiSecret = String(env.apiSecret ?? env.api_secret);
65
- }
66
56
  }
67
57
  })).views((self) => ({
68
- get hasApiCredentials() {
69
- return Boolean(self.apiKey && self.apiSecret);
70
- },
71
58
  getEnv() {
72
59
  return {
73
60
  baseUrl: self.baseUrl || void 0,
@@ -75,8 +62,7 @@ var ConfigModel = types.model("Config", {
75
62
  fetch: self.fetch,
76
63
  getDefaultOffset: self.getDefaultOffset,
77
64
  accessToken: self.accessToken,
78
- tokenExpiresAt: self.tokenExpiresAt,
79
- hasApiCredentials: self.hasApiCredentials
65
+ tokenExpiresAt: self.tokenExpiresAt
80
66
  };
81
67
  }
82
68
  }));
@@ -88,26 +74,7 @@ function getConfigStore() {
88
74
  var ConfigModel_default = ConfigModel;
89
75
 
90
76
  // src/apiAuth.js
91
- var TOKEN_PATH = "/Api/Auth/Token";
92
77
  var DEFAULT_TOKEN_REFRESH_SKEW_MS = 6e4;
93
- function defaultFetch() {
94
- if (typeof fetch === "undefined") {
95
- throw new Error("fetch not available");
96
- }
97
- return fetch;
98
- }
99
- function pickTokenPayload(data) {
100
- if (!data || typeof data !== "object") return null;
101
- const nested = data.data && typeof data.data === "object" ? data.data : data;
102
- const accessToken = nested.access_token ?? nested.accessToken ?? nested.AccessToken ?? null;
103
- if (!accessToken) return null;
104
- const expiresAtUtc = nested.expires_at_utc ?? nested.expiresAtUtc ?? nested.ExpiresAtUtc ?? null;
105
- return {
106
- accessToken: String(accessToken),
107
- expiresAtUtc: expiresAtUtc != null ? String(expiresAtUtc) : null,
108
- tokenType: nested.token_type ?? nested.tokenType ?? "Bearer"
109
- };
110
- }
111
78
  function isAccessTokenExpired(expiresAtUtc, skewMs = DEFAULT_TOKEN_REFRESH_SKEW_MS) {
112
79
  if (expiresAtUtc == null || expiresAtUtc === "") return false;
113
80
  const expMs = new Date(expiresAtUtc).getTime();
@@ -118,77 +85,22 @@ function getAuthState() {
118
85
  const store = getConfigStore();
119
86
  return {
120
87
  accessToken: store.accessToken ?? void 0,
121
- tokenExpiresAt: store.tokenExpiresAt ?? void 0,
122
- apiKey: store.apiKey ?? void 0,
123
- apiSecret: store.apiSecret ?? void 0,
124
- hasApiCredentials: Boolean(store.apiKey && store.apiSecret)
125
- };
126
- }
127
- async function requestAccessToken(apiKey, apiSecret, opts = {}) {
128
- const store = getConfigStore();
129
- const baseUrl = opts.baseUrl ?? store.baseUrl;
130
- if (!baseUrl) {
131
- return { status: "failure", message: "baseUrl required. Call configure({ baseUrl }) first." };
132
- }
133
- const key = apiKey != null ? String(apiKey).trim() : "";
134
- const secret = apiSecret != null ? String(apiSecret).trim() : "";
135
- if (!key || !secret) {
136
- return { status: "failure", message: "api_key and api_secret are required" };
137
- }
138
- const fetchFn = opts.fetch ?? store.fetch ?? defaultFetch();
139
- const url = `${String(baseUrl).replace(/\/+$/, "")}${TOKEN_PATH}`;
140
- const httpRes = await fetchFn(url, {
141
- method: "POST",
142
- headers: { "Content-Type": "application/json" },
143
- body: JSON.stringify({ api_key: key, api_secret: secret })
144
- });
145
- const text = await httpRes.text();
146
- let body;
147
- try {
148
- body = JSON.parse(text);
149
- } catch {
150
- body = { status: "failure", message: text || httpRes.statusText };
151
- }
152
- if (!httpRes.ok && body.status !== "failure") {
153
- body.status = "failure";
154
- body.message = body.message ?? `HTTP ${httpRes.status}`;
155
- }
156
- if (body.status !== "success") {
157
- return {
158
- status: "failure",
159
- message: body.message ?? "Failed to obtain access token"
160
- };
161
- }
162
- const parsed = pickTokenPayload(body);
163
- if (!parsed) {
164
- return { status: "failure", message: "Token response missing access_token" };
165
- }
166
- return {
167
- status: "success",
168
- accessToken: parsed.accessToken,
169
- expiresAtUtc: parsed.expiresAtUtc,
170
- tokenType: parsed.tokenType
88
+ tokenExpiresAt: store.tokenExpiresAt ?? void 0
171
89
  };
172
90
  }
173
- async function fetchAccessToken(apiKey, apiSecret) {
174
- const store = getConfigStore();
175
- const key = apiKey ?? store.apiKey;
176
- const secret = apiSecret ?? store.apiSecret;
177
- const result = await requestAccessToken(key, secret);
178
- if (result.status === "success") {
179
- store.setAccessToken(result.accessToken, result.expiresAtUtc);
180
- }
181
- return result;
182
- }
183
91
  async function ensureAccessToken() {
184
92
  const store = getConfigStore();
185
- const { accessToken, tokenExpiresAt, apiKey, apiSecret } = getAuthState();
93
+ const { accessToken, tokenExpiresAt } = getAuthState();
186
94
  if (accessToken && !isAccessTokenExpired(tokenExpiresAt)) {
187
95
  return accessToken;
188
96
  }
189
- if (apiKey && apiSecret) {
190
- const result = await fetchAccessToken(apiKey, apiSecret);
191
- if (result.status === "success") return result.accessToken;
97
+ const refresh = store.getAccessToken;
98
+ if (typeof refresh === "function") {
99
+ const next = await refresh();
100
+ if (next) {
101
+ store.setAccessToken(next);
102
+ return next;
103
+ }
192
104
  }
193
105
  return accessToken;
194
106
  }
@@ -225,11 +137,8 @@ function setAccessToken(accessToken, expiresAtUtc) {
225
137
  function clearAccessToken() {
226
138
  getConfigStore().clearAccessToken();
227
139
  }
228
- function setApiCredentials(apiKey, apiSecret) {
229
- getConfigStore().setApiCredentials(apiKey, apiSecret);
230
- }
231
- function clearApiCredentials() {
232
- getConfigStore().clearApiCredentials();
140
+ function setGetAccessToken(fn) {
141
+ getConfigStore().setGetAccessToken(fn);
233
142
  }
234
143
  function clearAuth() {
235
144
  getConfigStore().clearAuth();
@@ -237,9 +146,6 @@ function clearAuth() {
237
146
  function getAuth() {
238
147
  return getAuthState();
239
148
  }
240
- function fetchAccessToken2(apiKey, apiSecret) {
241
- return fetchAccessToken(apiKey, apiSecret);
242
- }
243
149
  function ensureValidAccessToken() {
244
150
  return ensureAccessToken();
245
151
  }
@@ -1512,23 +1418,27 @@ var ParticipantModel = types11.model("Participant", {
1512
1418
  }
1513
1419
  };
1514
1420
  });
1515
- function mapFromApi2(d) {
1421
+ function mapParticipantFromApi(d) {
1516
1422
  if (!d) return d;
1517
1423
  const pick2 = (...keys) => keys.reduce((v, k) => v ?? d[k], void 0);
1518
1424
  const n = (v) => v != null && v !== "" ? Number(v) : void 0;
1519
1425
  return {
1426
+ id: n(pick2("id", "Id")) ?? null,
1520
1427
  participantId: String(pick2("participantId", "ParticipantId", "participant_id") ?? ""),
1521
1428
  companyKey: pick2("companyKey", "CompanyKey", "company_key") ?? null,
1522
1429
  alias: pick2("alias", "Alias") ?? "",
1523
1430
  email: pick2("email", "Email") ?? "",
1524
1431
  isApproved: Boolean(pick2("isApproved", "IsApproved", "is_approved")),
1525
1432
  isAvailable: Boolean(pick2("isAvailable", "IsAvailable", "is_available")),
1526
- provider: n(pick2("provider", "Provider")) ?? 0,
1433
+ provider: n(pick2("provider", "Provider", "emailProvider", "EmailProvider", "email_provider")) ?? 0,
1527
1434
  createdOn: pick2("createdOn", "CreatedOn", "created_on") ?? null,
1528
1435
  modifiedOn: pick2("modifiedOn", "ModifiedOn", "modified_on") ?? null,
1529
1436
  isDeleted: Boolean(pick2("isDeleted", "IsDeleted", "is_deleted"))
1530
1437
  };
1531
1438
  }
1439
+ function mapFromApi2(d) {
1440
+ return mapParticipantFromApi(d);
1441
+ }
1532
1442
  function toPayload(self) {
1533
1443
  return {
1534
1444
  participantId: self.participantId,
@@ -2869,6 +2779,22 @@ var AuthModel = {
2869
2779
  }
2870
2780
  return res;
2871
2781
  },
2782
+ /**
2783
+ * GET Auth/IsAuthorized — validates OAuth with the provider; clears isApproved when not authorized.
2784
+ * Returns the updated participant record (same shape as participant/get).
2785
+ * @param {string} participantId
2786
+ * @returns {Promise<{ status: string, data?: object, message?: string }>}
2787
+ */
2788
+ async IsAuthorized(participantId) {
2789
+ const id = participantId != null ? String(participantId).trim() : "";
2790
+ if (!id) return { status: "failure", message: "participantId required" };
2791
+ const { reqGet } = createRequestHelpersFromEnv(getConfig());
2792
+ const res = await reqGet("/Auth/IsAuthorized", { participant_id: id });
2793
+ if (res.status === "success" && res.data) {
2794
+ res.data = mapParticipantFromApi(res.data);
2795
+ }
2796
+ return res;
2797
+ },
2872
2798
  /**
2873
2799
  * Open Google/Microsoft OAuth in a centered popup.
2874
2800
  * @param {string} authorizationUrl — from getAuthorizationUrl().data.authorizationUrl
@@ -2889,6 +2815,8 @@ var AuthModel = {
2889
2815
  },
2890
2816
  /**
2891
2817
  * Subscribe to calendar-authorization postMessage from OAuth popup (Authorized.html / Error.html).
2818
+ * Do not use popup.closed to detect completion — Cross-Origin-Opener-Policy on the parent app
2819
+ * blocks that check after the popup navigates to Google/Microsoft.
2892
2820
  * @param {(payload: { type: string, status: 'success' | 'error', message?: string }) => void} handler
2893
2821
  * @returns {() => void} unsubscribe
2894
2822
  */
@@ -2959,25 +2887,21 @@ export {
2959
2887
  RecurringFrequency,
2960
2888
  RootStore,
2961
2889
  Setting_default as SettingModel,
2962
- TOKEN_PATH,
2963
2890
  TimeFrame_default as TimeFrameModel,
2964
2891
  TimeSlot_default as TimeSlotModel,
2965
2892
  Unit,
2966
2893
  buildAuthHeaders,
2967
2894
  clearAccessToken,
2968
- clearApiCredentials,
2969
2895
  clearAuth,
2970
2896
  configure,
2971
2897
  createRootStore,
2972
2898
  ensureValidAccessToken,
2973
- fetchAccessToken2 as fetchAccessToken,
2974
2899
  getAuth,
2975
2900
  getConfig,
2976
2901
  getConfigStore,
2977
2902
  isAccessTokenExpired,
2978
- requestAccessToken,
2979
2903
  setAccessToken,
2980
- setApiCredentials,
2981
2904
  setBaseUrl,
2982
- setConsumer
2905
+ setConsumer,
2906
+ setGetAccessToken
2983
2907
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blazeo.com/calendar-client",
3
- "version": "1.0.27",
3
+ "version": "1.0.29",
4
4
  "description": "Blazeo Calendar / Appointment API client with MobX State Tree models",
5
5
  "exports": {
6
6
  ".": {