@ovok/core 0.4.30 → 0.4.32

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.
Files changed (123) hide show
  1. package/dist/client/ai-fhir/methods/AIFhirResources.d.ts +2 -1
  2. package/dist/client/ai-fhir/methods/AIFhirResources.js +2 -2
  3. package/dist/client/auth/methods/appleLogin.d.ts +2 -1
  4. package/dist/client/auth/methods/appleLogin.js +2 -2
  5. package/dist/client/auth/methods/changePassword.d.ts +2 -1
  6. package/dist/client/auth/methods/changePassword.js +2 -2
  7. package/dist/client/auth/methods/getSessions.d.ts +2 -1
  8. package/dist/client/auth/methods/getSessions.js +2 -2
  9. package/dist/client/auth/methods/googleLogin.d.ts +2 -1
  10. package/dist/client/auth/methods/googleLogin.js +2 -2
  11. package/dist/client/auth/methods/index.d.ts +1 -0
  12. package/dist/client/auth/methods/index.js +1 -0
  13. package/dist/client/auth/methods/login.d.ts +6 -2
  14. package/dist/client/auth/methods/login.js +60 -44
  15. package/dist/client/auth/methods/logout.d.ts +2 -1
  16. package/dist/client/auth/methods/logout.js +2 -2
  17. package/dist/client/auth/methods/refetchProfile.d.ts +2 -1
  18. package/dist/client/auth/methods/refetchProfile.js +3 -4
  19. package/dist/client/auth/methods/register.d.ts +2 -1
  20. package/dist/client/auth/methods/register.js +2 -2
  21. package/dist/client/auth/methods/registerPractitioner.d.ts +2 -1
  22. package/dist/client/auth/methods/registerPractitioner.js +2 -2
  23. package/dist/client/auth/methods/reloadSession.d.ts +7 -0
  24. package/dist/client/auth/methods/reloadSession.js +23 -0
  25. package/dist/client/auth/methods/requestDeleteUser.d.ts +2 -1
  26. package/dist/client/auth/methods/requestDeleteUser.js +5 -5
  27. package/dist/client/auth/methods/resetPassword.d.ts +2 -1
  28. package/dist/client/auth/methods/resetPassword.js +2 -2
  29. package/dist/client/auth/methods/revokeSessions.d.ts +2 -1
  30. package/dist/client/auth/methods/revokeSessions.js +2 -2
  31. package/dist/client/auth/methods/setPasswordFromReset.d.ts +2 -1
  32. package/dist/client/auth/methods/setPasswordFromReset.js +2 -2
  33. package/dist/client/auth/types/LoginBody.d.ts +10 -20
  34. package/dist/client/auth/types/LoginError.d.ts +15 -0
  35. package/dist/client/auth/types/LoginError.js +15 -0
  36. package/dist/client/bot/methods/executeBot.d.ts +2 -1
  37. package/dist/client/bot/methods/executeBot.js +2 -2
  38. package/dist/client/change-history/methods.d.ts +2 -1
  39. package/dist/client/change-history/methods.js +2 -2
  40. package/dist/client/chat/ai/methods/createAiSession.d.ts +2 -1
  41. package/dist/client/chat/ai/methods/createAiSession.js +2 -2
  42. package/dist/client/cms/methods.d.ts +6 -5
  43. package/dist/client/cms/methods.js +10 -10
  44. package/dist/client/consent/methods.d.ts +5 -4
  45. package/dist/client/consent/methods.js +8 -8
  46. package/dist/client/email/methods.d.ts +2 -1
  47. package/dist/client/email/methods.js +2 -2
  48. package/dist/client/errors/ovok-error-utils.d.ts +7 -0
  49. package/dist/client/errors/ovok-error-utils.js +49 -5
  50. package/dist/client/errors/ovok-error.d.ts +4 -0
  51. package/dist/client/errors/ovok-error.js +17 -4
  52. package/dist/client/errors/rate-limit-error.js +5 -3
  53. package/dist/client/experience/methods.d.ts +32 -31
  54. package/dist/client/experience/methods.js +62 -62
  55. package/dist/client/invites/methods.d.ts +6 -5
  56. package/dist/client/invites/methods.js +11 -11
  57. package/dist/client/notifications/methods.d.ts +6 -5
  58. package/dist/client/notifications/methods.js +10 -10
  59. package/dist/client/notifications/types.d.ts +92 -11
  60. package/dist/client/observation/methods/getLatestObservationsByCodes.d.ts +2 -1
  61. package/dist/client/observation/methods/getLatestObservationsByCodes.js +4 -4
  62. package/dist/client/offline/offline-measurement-queue.d.ts +4 -3
  63. package/dist/client/offline/offline-measurement-queue.js +6 -6
  64. package/dist/client/ovok-client.d.ts +28 -11
  65. package/dist/client/ovok-client.js +353 -31
  66. package/dist/client/patient/methods.d.ts +24 -23
  67. package/dist/client/patient/methods.js +49 -49
  68. package/dist/client/patient-sharing/methods.d.ts +7 -6
  69. package/dist/client/patient-sharing/methods.js +12 -12
  70. package/dist/client/platform/methods.d.ts +24 -23
  71. package/dist/client/platform/methods.js +46 -46
  72. package/dist/client/questionnaire-response/methods/createQuestionnaireResponseWithObservations.d.ts +2 -1
  73. package/dist/client/questionnaire-response/methods/createQuestionnaireResponseWithObservations.js +2 -2
  74. package/dist/client/questionnaire-response/methods/extractQuestionnaireObservations.d.ts +2 -1
  75. package/dist/client/questionnaire-response/methods/extractQuestionnaireObservations.js +2 -2
  76. package/dist/client/questionnaire-response/methods/populateQuestionnaire.d.ts +2 -1
  77. package/dist/client/questionnaire-response/methods/populateQuestionnaire.js +2 -2
  78. package/dist/client/questionnaire-response/methods/submitQuestionnaireResponse.d.ts +2 -1
  79. package/dist/client/questionnaire-response/methods/submitQuestionnaireResponse.js +2 -2
  80. package/dist/client/signals/methods.d.ts +22 -21
  81. package/dist/client/signals/methods.js +42 -42
  82. package/dist/client/slim/methods.d.ts +93 -57
  83. package/dist/client/slim/methods.js +295 -108
  84. package/dist/client/slim/paging.d.ts +18 -0
  85. package/dist/client/slim/paging.js +57 -0
  86. package/dist/client/slim/types.d.ts +370 -3
  87. package/dist/conformance/api-requirements.js +22 -0
  88. package/dist/conformance/capability-requirements.js +1 -1
  89. package/dist/hooks/account-hooks.js +7 -7
  90. package/dist/hooks/api-hooks.d.ts +5 -2
  91. package/dist/hooks/api-hooks.js +39 -16
  92. package/dist/hooks/cms-hooks.js +3 -3
  93. package/dist/hooks/email-hooks.js +1 -1
  94. package/dist/hooks/experience-hooks.js +5 -5
  95. package/dist/hooks/index.d.ts +2 -0
  96. package/dist/hooks/index.js +5 -3
  97. package/dist/hooks/notification-ack-hooks.d.ts +2 -1
  98. package/dist/hooks/notification-hooks.d.ts +4 -2
  99. package/dist/hooks/notification-hooks.js +11 -23
  100. package/dist/hooks/notification-invalidation.d.ts +3 -2
  101. package/dist/hooks/notification-invalidation.js +10 -1
  102. package/dist/hooks/notification-note-hooks.js +3 -3
  103. package/dist/hooks/observation-hooks.d.ts +5 -1
  104. package/dist/hooks/observation-hooks.js +6 -6
  105. package/dist/hooks/patient-hooks.js +18 -18
  106. package/dist/hooks/patient-sharing-hooks.js +2 -2
  107. package/dist/hooks/platform-hooks.js +3 -3
  108. package/dist/hooks/poll-hooks.d.ts +14 -0
  109. package/dist/hooks/poll-hooks.js +127 -0
  110. package/dist/hooks/signals-alert-hooks.d.ts +5 -20
  111. package/dist/hooks/signals-alert-hooks.js +38 -34
  112. package/dist/hooks/signals-alert-item-hooks.d.ts +19 -0
  113. package/dist/hooks/signals-alert-item-hooks.js +62 -0
  114. package/dist/hooks/signals-hooks.js +2 -2
  115. package/dist/index.d.ts +4 -0
  116. package/dist/index.js +4 -1
  117. package/dist/utils/auth/exchange-code.d.ts +2 -0
  118. package/dist/utils/auth/exchange-code.js +2 -2
  119. package/dist/utils/auth/handle-tenant-code.d.ts +1 -1
  120. package/dist/utils/auth/handle-tenant-code.js +13 -4
  121. package/dist/utils/auth/start-login.d.ts +3 -1
  122. package/dist/utils/auth/start-login.js +3 -3
  123. package/package.json +1 -1
@@ -47,6 +47,7 @@ const cmsMethods = __importStar(require("./cms/methods"));
47
47
  const consentMethods = __importStar(require("./consent/methods"));
48
48
  const emailMethods = __importStar(require("./email"));
49
49
  const ovok_error_1 = require("./errors/ovok-error");
50
+ const ovok_error_utils_1 = require("./errors/ovok-error-utils");
50
51
  const rate_limit_error_1 = require("./errors/rate-limit-error");
51
52
  const experienceMethods = __importStar(require("./experience/methods"));
52
53
  const inviteMethods = __importStar(require("./invites/methods"));
@@ -64,6 +65,127 @@ const slimMethods = __importStar(require("./slim/methods"));
64
65
  exports.MAX_CONDITIONAL_OBSERVATION_ENTRIES = 8;
65
66
  var observation_bundle_utils_2 = require("./observation/observation-bundle-utils");
66
67
  Object.defineProperty(exports, "MAX_OBSERVATION_BUNDLE_BYTES", { enumerable: true, get: function () { return observation_bundle_utils_2.MAX_OBSERVATION_BUNDLE_BYTES; } });
68
+ const retryAfterMs = (response) => {
69
+ var _a, _b;
70
+ const value = (_b = (_a = response.headers) === null || _a === void 0 ? void 0 : _a.get) === null || _b === void 0 ? void 0 : _b.call(_a, "Retry-After");
71
+ if (!value) {
72
+ return undefined;
73
+ }
74
+ const seconds = Number(value);
75
+ if (Number.isFinite(seconds)) {
76
+ return Math.max(0, seconds * 1000);
77
+ }
78
+ const date = Date.parse(value);
79
+ return Number.isFinite(date) ? Math.max(0, date - Date.now()) : undefined;
80
+ };
81
+ const timeoutError = () => {
82
+ const error = new Error("The request timed out.");
83
+ error.name = "TimeoutError";
84
+ return error;
85
+ };
86
+ const withHttpErrorMetadata = async (response) => {
87
+ var _a, _b;
88
+ if (response.ok || typeof response.clone !== "function") {
89
+ return response;
90
+ }
91
+ let bodyText = "";
92
+ let body;
93
+ try {
94
+ bodyText = await response.clone().text();
95
+ body = JSON.parse(bodyText);
96
+ }
97
+ catch (_c) {
98
+ body = undefined;
99
+ }
100
+ const outcome = body && typeof body === "object" && body.resourceType === "OperationOutcome"
101
+ ? body
102
+ : undefined;
103
+ const envelope = body && typeof body === "object" ? body : undefined;
104
+ const nestedError = (envelope === null || envelope === void 0 ? void 0 : envelope.error) && typeof envelope.error === "object"
105
+ ? envelope.error
106
+ : undefined;
107
+ const safeResponse = envelope
108
+ ? Object.fromEntries(Object.entries(envelope).filter(([key]) => ["code", "error", "message", "requestId", "retryAfterSec", "status", "statusCode", "errors", "missing"].includes(key)))
109
+ : { message: typeof body === "string" ? body : response.statusText };
110
+ const status = response.status;
111
+ const retryAfter = (_a = retryAfterMs(response)) !== null && _a !== void 0 ? _a : (typeof (envelope === null || envelope === void 0 ? void 0 : envelope.retryAfterSec) === "number" ? envelope.retryAfterSec * 1000 : undefined);
112
+ const requestId = (_b = response.headers.get("x-request-id")) !== null && _b !== void 0 ? _b : (typeof (envelope === null || envelope === void 0 ? void 0 : envelope.requestId) === "string" ? envelope.requestId : undefined);
113
+ const message = typeof (envelope === null || envelope === void 0 ? void 0 : envelope.message) === "string"
114
+ ? envelope.message
115
+ : typeof (nestedError === null || nestedError === void 0 ? void 0 : nestedError.message) === "string"
116
+ ? nestedError.message
117
+ : response.statusText || `HTTP ${status}`;
118
+ const extensions = [];
119
+ const addMetadata = (url, value, kind) => {
120
+ if (value !== undefined) {
121
+ extensions.push({ url, [`value${kind}`]: value });
122
+ }
123
+ };
124
+ addMetadata("status", status, "Integer");
125
+ addMetadata("requestId", requestId, "String");
126
+ addMetadata("retryAfterMs", retryAfter, "Integer");
127
+ if (!outcome) {
128
+ extensions.push({ url: "response", valueString: JSON.stringify(safeResponse) });
129
+ }
130
+ const enrichedOutcome = outcome !== null && outcome !== void 0 ? outcome : {
131
+ resourceType: "OperationOutcome",
132
+ id: "ovok-http-error",
133
+ issue: [{
134
+ severity: "error",
135
+ code: status === 429 ? "throttled" : "exception",
136
+ details: { text: bodyText || message },
137
+ }],
138
+ };
139
+ const existingExtensions = Array.isArray(enrichedOutcome.extension)
140
+ ? enrichedOutcome.extension
141
+ : [];
142
+ enrichedOutcome.extension = [
143
+ ...existingExtensions,
144
+ { url: ovok_error_utils_1.OVOK_HTTP_ERROR_EXTENSION_URL, extension: extensions },
145
+ ];
146
+ const headers = new Headers(response.headers);
147
+ headers.delete("content-length");
148
+ headers.set("content-type", "application/fhir+json");
149
+ return new Response(JSON.stringify(enrichedOutcome), {
150
+ status,
151
+ statusText: response.statusText,
152
+ headers,
153
+ });
154
+ };
155
+ const waitForRetry = (milliseconds, signal) => new Promise((resolve, reject) => {
156
+ var _a;
157
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
158
+ const error = new Error("Token refresh aborted");
159
+ error.name = "AbortError";
160
+ reject((_a = signal.reason) !== null && _a !== void 0 ? _a : error);
161
+ return;
162
+ }
163
+ const timer = setTimeout(() => {
164
+ signal === null || signal === void 0 ? void 0 : signal.removeEventListener("abort", abort);
165
+ resolve();
166
+ }, milliseconds);
167
+ const abort = () => {
168
+ var _a;
169
+ clearTimeout(timer);
170
+ const error = new Error("Token refresh aborted");
171
+ error.name = "AbortError";
172
+ reject((_a = signal === null || signal === void 0 ? void 0 : signal.reason) !== null && _a !== void 0 ? _a : error);
173
+ };
174
+ signal === null || signal === void 0 ? void 0 : signal.addEventListener("abort", abort, { once: true });
175
+ });
176
+ const isTemporaryRefreshFailure = (status) => status === 429 || (status >= 500 && status <= 599);
177
+ const refreshUnavailableError = (response) => {
178
+ const error = new Error("Token refresh is temporarily unavailable");
179
+ error.name = "NetworkError";
180
+ Object.assign(error, { cause: response });
181
+ return error;
182
+ };
183
+ const refreshResponseError = (response) => {
184
+ const error = new Error(`Token refresh returned HTTP ${response.status}`);
185
+ error.name = "TokenRefreshError";
186
+ Object.assign(error, { status: response.status, cause: response });
187
+ return error;
188
+ };
67
189
  const isNetworkError = (error) => {
68
190
  if (!(error instanceof Error)) {
69
191
  return false;
@@ -75,6 +197,7 @@ const isNetworkError = (error) => {
75
197
  }
76
198
  return isNetworkError(error.cause);
77
199
  };
200
+ const isRetrySafePost = (pathname) => pathname === "/ai/fhir/search";
78
201
  class OvokClient extends core_1.MedplumClient {
79
202
  /**
80
203
  * @param config - The configuration for the client.
@@ -82,30 +205,217 @@ class OvokClient extends core_1.MedplumClient {
82
205
  */
83
206
  constructor(config) {
84
207
  var _a, _b, _c, _d, _e, _f;
85
- const storage = (_a = config.storage) !== null && _a !== void 0 ? _a : new core_1.ClientStorage(undefined, config.storagePrefix);
208
+ if (config.requestTimeoutMs !== undefined && (!Number.isFinite(config.requestTimeoutMs) || config.requestTimeoutMs <= 0)) {
209
+ throw new RangeError("requestTimeoutMs must be a positive finite number.");
210
+ }
211
+ const storage = (_a = config.storage) !== null && _a !== void 0 ? _a : new core_1.ClientStorage(new core_1.MemoryStorage(), config.storagePrefix);
86
212
  const fetchImplementation = (_b = config.fetch) !== null && _b !== void 0 ? _b : globalThis.fetch.bind(globalThis);
87
213
  const baseUrl = (_c = config.baseUrl) !== null && _c !== void 0 ? _c : "https://api.medplum.com/";
88
214
  const normalizedBaseUrl = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
89
215
  const tokenUrl = new URL((_d = config.tokenUrl) !== null && _d !== void 0 ? _d : "oauth2/token", normalizedBaseUrl).href;
90
- super({
91
- ...config,
92
- storage,
93
- fetch: async (url, options) => {
94
- var _a, _b;
95
- const response = await fetchImplementation(url, options);
96
- const requestUrl = new URL(url, normalizedBaseUrl);
97
- const refreshRefused = requestUrl.href === tokenUrl && [400, 401, 403].includes(response.status);
98
- const sessionEnded = requestUrl.pathname.endsWith("/auth/session/current") && response.status === 401;
99
- if (refreshRefused || sessionEnded) {
216
+ let unauthenticatedReason;
217
+ let clearActiveLogin = () => undefined;
218
+ let terminalRefreshToken;
219
+ let terminalRefreshError;
220
+ const isTokenRefresh = (url, options) => new URL(url, normalizedBaseUrl).href === tokenUrl &&
221
+ (options === null || options === void 0 ? void 0 : options.method) === "POST" &&
222
+ typeof options.body === "string" &&
223
+ new URLSearchParams(options.body).get("grant_type") === "refresh_token";
224
+ const markActiveAccountExpired = () => {
225
+ var _a, _b;
226
+ const accountId = (_b = (_a = storage.getObject("activeLogin")) === null || _a === void 0 ? void 0 : _a.profile) === null || _b === void 0 ? void 0 : _b.reference;
227
+ if (accountId) {
228
+ (0, account_storage_1.setStoredAccountExpired)(storage, accountId, true);
229
+ }
230
+ };
231
+ const fetchOnce = async (url, options) => {
232
+ var _a, _b;
233
+ const requestTimeoutMs = config.requestTimeoutMs;
234
+ let response;
235
+ if (requestTimeoutMs === undefined) {
236
+ response = await fetchImplementation(url, options);
237
+ }
238
+ else {
239
+ const controller = new AbortController();
240
+ const callerSignal = options === null || options === void 0 ? void 0 : options.signal;
241
+ let timedOut = false;
242
+ const abortFromCaller = () => controller.abort(callerSignal === null || callerSignal === void 0 ? void 0 : callerSignal.reason);
243
+ const timeout = setTimeout(() => {
244
+ timedOut = true;
245
+ controller.abort(timeoutError());
246
+ }, requestTimeoutMs);
247
+ if (callerSignal === null || callerSignal === void 0 ? void 0 : callerSignal.aborted) {
248
+ abortFromCaller();
249
+ }
250
+ else {
251
+ callerSignal === null || callerSignal === void 0 ? void 0 : callerSignal.addEventListener("abort", abortFromCaller, { once: true });
252
+ }
253
+ try {
254
+ response = await fetchImplementation(url, { ...options, signal: controller.signal });
255
+ }
256
+ catch (error) {
257
+ if (timedOut) {
258
+ throw timeoutError();
259
+ }
260
+ throw error;
261
+ }
262
+ finally {
263
+ clearTimeout(timeout);
264
+ callerSignal === null || callerSignal === void 0 ? void 0 : callerSignal.removeEventListener("abort", abortFromCaller);
265
+ }
266
+ }
267
+ const requestUrl = new URL(url, normalizedBaseUrl);
268
+ if (isTokenRefresh(url, options)) {
269
+ if (response.status === 400) {
270
+ let invalidGrant = false;
271
+ try {
272
+ invalidGrant = (await response.clone().json()).error === "invalid_grant";
273
+ }
274
+ catch (_c) {
275
+ // Do not sign out unless the response confirms that the refresh token was revoked.
276
+ }
277
+ if (!invalidGrant) {
278
+ throw refreshResponseError(response);
279
+ }
280
+ }
281
+ if ([400, 401, 403].includes(response.status)) {
282
+ unauthenticatedReason = {
283
+ type: "refresh-rejected",
284
+ status: response.status,
285
+ };
286
+ markActiveAccountExpired();
287
+ }
288
+ else if (response.ok) {
100
289
  const accountId = (_b = (_a = storage.getObject("activeLogin")) === null || _a === void 0 ? void 0 : _a.profile) === null || _b === void 0 ? void 0 : _b.reference;
101
290
  if (accountId) {
102
- (0, account_storage_1.setStoredAccountExpired)(storage, accountId, true);
291
+ (0, account_storage_1.setStoredAccountExpired)(storage, accountId, false);
103
292
  }
293
+ unauthenticatedReason = undefined;
294
+ }
295
+ }
296
+ else if (requestUrl.href === tokenUrl && !response.ok) {
297
+ unauthenticatedReason = {
298
+ type: "authentication-rejected",
299
+ status: response.status,
300
+ };
301
+ }
302
+ else if (response.status === 401) {
303
+ unauthenticatedReason = { type: "request-unauthorized", status: 401 };
304
+ }
305
+ return isTokenRefresh(url, options)
306
+ ? response
307
+ : withHttpErrorMetadata(response);
308
+ };
309
+ const fetchWithRefreshRetry = async (url, options) => {
310
+ var _a, _b, _c, _d;
311
+ const requestUrl = new URL(url, normalizedBaseUrl);
312
+ if (!isTokenRefresh(url, options)) {
313
+ const response = await fetchOnce(url, options);
314
+ const sessionEnded = requestUrl.pathname.endsWith("/auth/session/current") && response.status === 401;
315
+ if (sessionEnded) {
316
+ markActiveAccountExpired();
317
+ }
318
+ return response;
319
+ }
320
+ const login = storage.getObject("activeLogin");
321
+ if (terminalRefreshError) {
322
+ if ((login === null || login === void 0 ? void 0 : login.accessToken) && login.accessToken !== terminalRefreshToken) {
323
+ terminalRefreshToken = undefined;
324
+ terminalRefreshError = undefined;
325
+ }
326
+ else {
327
+ throw terminalRefreshError;
328
+ }
329
+ }
330
+ const expiresAt = (login === null || login === void 0 ? void 0 : login.accessToken) ? (0, core_1.tryGetJwtExpiration)(login.accessToken) : undefined;
331
+ const signal = options === null || options === void 0 ? void 0 : options.signal;
332
+ if (expiresAt !== undefined && options) {
333
+ const tokenRequestOptions = options;
334
+ tokenRequestOptions.maxRetries = 0;
335
+ tokenRequestOptions.maxRetryTime = 0;
336
+ }
337
+ if (expiresAt === undefined) {
338
+ const response = await fetchOnce(url, options);
339
+ if (isTemporaryRefreshFailure(response.status)) {
340
+ void ((_a = response.body) === null || _a === void 0 ? void 0 : _a.cancel().catch(() => undefined));
341
+ throw refreshUnavailableError(response);
104
342
  }
105
343
  return response;
344
+ }
345
+ let attempt = 0;
346
+ let firstAttempt = true;
347
+ let lastResponse;
348
+ let lastNetworkError;
349
+ while (firstAttempt || Date.now() < expiresAt) {
350
+ firstAttempt = false;
351
+ try {
352
+ lastResponse = await fetchOnce(url, options);
353
+ }
354
+ catch (error) {
355
+ if ((signal === null || signal === void 0 ? void 0 : signal.aborted) || !isNetworkError(error)) {
356
+ throw error;
357
+ }
358
+ if (Date.now() >= expiresAt) {
359
+ lastNetworkError = error;
360
+ break;
361
+ }
362
+ lastNetworkError = error;
363
+ const remainingMs = expiresAt - Date.now();
364
+ const backoffMs = Math.min(500 * (2 ** Math.min(attempt, 6)), 30000);
365
+ await waitForRetry(Math.min(backoffMs, remainingMs), signal);
366
+ attempt += 1;
367
+ continue;
368
+ }
369
+ if (!isTemporaryRefreshFailure(lastResponse.status)) {
370
+ return lastResponse;
371
+ }
372
+ const remainingMs = expiresAt - Date.now();
373
+ if (remainingMs <= 0) {
374
+ break;
375
+ }
376
+ const backoffMs = Math.min(500 * (2 ** Math.min(attempt, 6)), 30000);
377
+ const waitMs = Math.min(Math.max((_b = retryAfterMs(lastResponse)) !== null && _b !== void 0 ? _b : 0, backoffMs), remainingMs);
378
+ if (waitMs >= remainingMs) {
379
+ await waitForRetry(remainingMs, signal);
380
+ break;
381
+ }
382
+ void ((_c = lastResponse.body) === null || _c === void 0 ? void 0 : _c.cancel().catch(() => undefined));
383
+ await waitForRetry(waitMs, signal);
384
+ attempt += 1;
385
+ }
386
+ const status = lastResponse === null || lastResponse === void 0 ? void 0 : lastResponse.status;
387
+ const reason = {
388
+ type: "refresh-unavailable",
389
+ ...(status === undefined ? {} : { status }),
390
+ };
391
+ unauthenticatedReason = reason;
392
+ markActiveAccountExpired();
393
+ if (lastResponse) {
394
+ return lastResponse;
395
+ }
396
+ terminalRefreshToken = login === null || login === void 0 ? void 0 : login.accessToken;
397
+ terminalRefreshError = lastNetworkError !== null && lastNetworkError !== void 0 ? lastNetworkError : refreshUnavailableError(undefined);
398
+ clearActiveLogin();
399
+ (_d = config.onUnauthenticated) === null || _d === void 0 ? void 0 : _d.call(config, reason);
400
+ unauthenticatedReason = undefined;
401
+ throw terminalRefreshError;
402
+ };
403
+ super({
404
+ ...config,
405
+ storage,
406
+ onUnauthenticated: () => {
407
+ var _a;
408
+ const reason = unauthenticatedReason !== null && unauthenticatedReason !== void 0 ? unauthenticatedReason : {
409
+ type: "request-unauthorized",
410
+ status: 401,
411
+ };
412
+ unauthenticatedReason = undefined;
413
+ (_a = config.onUnauthenticated) === null || _a === void 0 ? void 0 : _a.call(config, reason);
106
414
  },
415
+ fetch: fetchWithRefreshRetry,
107
416
  });
108
417
  this.socialLoginClientId = "";
418
+ clearActiveLogin = this.clearActiveLogin.bind(this);
109
419
  this.hasExplicitStorageAdapter = config.storage !== undefined;
110
420
  this.socialLoginClientId = (_e = config.socialLoginClientId) !== null && _e !== void 0 ? _e : "";
111
421
  this.addEventListener("profileRefreshed", () => this.syncSavedAccountProfile());
@@ -135,6 +445,13 @@ class OvokClient extends core_1.MedplumClient {
135
445
  this.clientStorage = storage;
136
446
  this.offlineQueueOptions = (_f = config.offlineQueue) !== null && _f !== void 0 ? _f : {};
137
447
  }
448
+ post(url, body, contentType, options) {
449
+ const pathname = new URL(url.toString(), this.getBaseUrl()).pathname;
450
+ if (!isRetrySafePost(pathname)) {
451
+ options = { ...options, maxRetries: 0, maxRetryTime: 0 };
452
+ }
453
+ return super.post(url, body, contentType, options);
454
+ }
138
455
  /** Lists saved accounts without exposing their access or refresh tokens. */
139
456
  getSavedAccounts() {
140
457
  var _a, _b;
@@ -186,7 +503,7 @@ class OvokClient extends core_1.MedplumClient {
186
503
  * Accounts with an expired server session are removed locally; network and service
187
504
  * failures retain the account so it can be removed after connectivity returns.
188
505
  */
189
- async removeAccount(accountId) {
506
+ async removeAccount(accountId, requestOptions) {
190
507
  var _a;
191
508
  const login = this.getLogin(accountId);
192
509
  if (!login) {
@@ -196,7 +513,7 @@ class OvokClient extends core_1.MedplumClient {
196
513
  const previousAccountId = (_a = previousLogin === null || previousLogin === void 0 ? void 0 : previousLogin.profile) === null || _a === void 0 ? void 0 : _a.reference;
197
514
  try {
198
515
  await this.setActiveLogin(login);
199
- await this.delete("auth/session/current");
516
+ await this.delete("auth/session/current", ...(requestOptions === undefined ? [] : [requestOptions]));
200
517
  }
201
518
  catch (error) {
202
519
  if (this.getExpiredAccountIds().includes(accountId)) {
@@ -251,34 +568,34 @@ class OvokClient extends core_1.MedplumClient {
251
568
  }
252
569
  }
253
570
  /** Saves a measurement immediately, or durably queues it when the opt-in queue is enabled. */
254
- async saveMeasurement(params) {
571
+ async saveMeasurement(params, requestOptions) {
255
572
  const observations = await this.generateObservationBodiesByMeasurement(params);
256
- return this.saveObservations(observations);
573
+ return this.saveObservations(observations, ...(requestOptions === undefined ? [] : [requestOptions]));
257
574
  }
258
575
  /** Reads Ovok's composed FHIR and public-route capability statement. */
259
- async getCapabilityStatement(version = "R4") {
260
- return this.get(`/fhir/${version}/metadata`);
576
+ async getCapabilityStatement(version = "R4", requestOptions) {
577
+ return this.get(`/fhir/${version}/metadata`, ...(requestOptions === undefined ? [] : [requestOptions]));
261
578
  }
262
579
  /** Returns the non-FHIR public routes published by the backend statement. */
263
- async getPublicApiEndpoints(version = "R4") {
264
- return (0, capability_1.getOvokPublicApiEndpoints)(await this.getCapabilityStatement(version));
580
+ async getPublicApiEndpoints(version = "R4", requestOptions) {
581
+ return (0, capability_1.getOvokPublicApiEndpoints)(await this.getCapabilityStatement(version, ...(requestOptions === undefined ? [] : [requestOptions])));
265
582
  }
266
583
  /** Saves observations with conditional creates so a retry cannot create a duplicate. */
267
- async saveObservations(observations) {
584
+ async saveObservations(observations, requestOptions) {
268
585
  const linkedObservations = (0, observation_bundle_utils_1.linkObservationMembers)(observations);
269
586
  const queue = this.getOfflineMeasurementQueue();
270
587
  if (queue) {
271
- const { id, flush } = await queue.enqueueAndFlush(linkedObservations);
588
+ const { id, flush } = await queue.enqueueAndFlush(linkedObservations, ...(requestOptions === undefined ? [] : [requestOptions]));
272
589
  const response = flush.responses[id];
273
590
  return response
274
591
  ? { status: "saved", response }
275
592
  : { status: "queued", queueId: id };
276
593
  }
277
- const response = await this.executeConditionalObservationCreates(linkedObservations);
594
+ const response = await this.executeConditionalObservationCreates(linkedObservations, requestOptions);
278
595
  return { status: "saved", response };
279
596
  }
280
597
  /** Flushes measurements queued while offline. Call this on connectivity/app-resume events. */
281
- async flushOfflineMeasurementQueue(options = {}) {
598
+ async flushOfflineMeasurementQueue(options = {}, requestOptions) {
282
599
  const queue = this.getOfflineMeasurementQueue();
283
600
  if (!queue) {
284
601
  return {
@@ -289,7 +606,7 @@ class OvokClient extends core_1.MedplumClient {
289
606
  responses: {},
290
607
  };
291
608
  }
292
- return queue.flush(options);
609
+ return queue.flush(options, ...(requestOptions === undefined ? [] : [requestOptions]));
293
610
  }
294
611
  getOfflineMeasurementQueue() {
295
612
  var _a;
@@ -299,7 +616,7 @@ class OvokClient extends core_1.MedplumClient {
299
616
  if (!this.clientStorage || !this.hasExplicitStorageAdapter) {
300
617
  throw new Error("Offline measurement queue requires a durable client storage adapter.");
301
618
  }
302
- (_a = this.offlineMeasurementQueue) !== null && _a !== void 0 ? _a : (this.offlineMeasurementQueue = new offline_1.OfflineMeasurementQueue(this.clientStorage, (bundle) => this.executeObservationBundle(bundle), {
619
+ (_a = this.offlineMeasurementQueue) !== null && _a !== void 0 ? _a : (this.offlineMeasurementQueue = new offline_1.OfflineMeasurementQueue(this.clientStorage, (bundle, requestOptions) => this.executeObservationBundle(bundle, ...(requestOptions === undefined ? [] : [requestOptions])), {
303
620
  ...this.offlineQueueOptions,
304
621
  getOwnerKey: () => {
305
622
  var _a, _b, _c;
@@ -325,8 +642,13 @@ class OvokClient extends core_1.MedplumClient {
325
642
  clear() {
326
643
  var _a, _b, _c;
327
644
  const storage = this.clientStorage;
328
- const logins = this.getLogins();
329
645
  const activeAccountId = (_b = (_a = this.getActiveLogin()) === null || _a === void 0 ? void 0 : _a.profile) === null || _b === void 0 ? void 0 : _b.reference;
646
+ const logins = this.getLogins().map((login) => {
647
+ var _a;
648
+ return ((_a = login.profile) === null || _a === void 0 ? void 0 : _a.reference) === activeAccountId
649
+ ? { ...login, accessToken: "", refreshToken: "" }
650
+ : login;
651
+ });
330
652
  const expiredAccountIds = new Set(this.getExpiredAccountIds());
331
653
  if (activeAccountId) {
332
654
  expiredAccountIds.add(activeAccountId);
@@ -384,16 +706,16 @@ class OvokClient extends core_1.MedplumClient {
384
706
  // Keep the saved login available for a later retry if restoration is offline.
385
707
  }
386
708
  }
387
- async executeConditionalObservationCreates(observations) {
709
+ async executeConditionalObservationCreates(observations, requestOptions) {
388
710
  const entries = observations.map(offline_1.entryForObservation);
389
711
  return this.executeObservationBundle({
390
712
  resourceType: "Bundle",
391
713
  type: "transaction",
392
714
  entry: entries,
393
- });
715
+ }, ...(requestOptions === undefined ? [] : [requestOptions]));
394
716
  }
395
- async executeObservationBundle(bundle) {
396
- const response = await (0, observation_bundle_utils_1.executeObservationBundle)(bundle, (request) => this.executeBatch(request));
717
+ async executeObservationBundle(bundle, requestOptions) {
718
+ const response = await (0, observation_bundle_utils_1.executeObservationBundle)(bundle, (request) => this.executeBatch(request, ...(requestOptions === undefined ? [] : [requestOptions])));
397
719
  this.invalidateSearches("Observation");
398
720
  return response;
399
721
  }
@@ -1,25 +1,26 @@
1
+ import type { MedplumRequestOptions } from "@medplum/core";
1
2
  import { OvokClient } from "../ovok-client";
2
3
  import { OvokCurrentPatientProfile, OvokPatientLatestObservationsQuery, OvokPatientObservationBundle, OvokPatientAppointment, OvokPatientCarePlan, OvokPatientDashboard, OvokPatientDashboardQuery, OvokPatientDevice, OvokPatientMeasurement, OvokPatientNotification, OvokPatientQuestionnaire, OvokPatientQuestionnaireResponse, OvokPatientResourceQuery, OvokPatientSchedule, OvokPatientSchedulingQuery, OvokPatientSlot, OvokPatientTelemetryHistory, OvokPatientTelemetryHistoryQuery } from "./types";
3
- export declare function getCurrentPatientProfile(this: OvokClient): Promise<OvokCurrentPatientProfile>;
4
- export declare function getPatientLatestObservations(this: OvokClient, params?: OvokPatientLatestObservationsQuery): Promise<OvokPatientObservationBundle>;
5
- export declare function getPatientDeviceTelemetry(this: OvokClient, deviceId: string): Promise<OvokPatientObservationBundle>;
6
- export declare function getPatientDeviceTelemetryHistory(this: OvokClient, deviceId: string, params?: OvokPatientTelemetryHistoryQuery): Promise<OvokPatientTelemetryHistory>;
7
- export declare function listPatientAppointments(this: OvokClient, params?: OvokPatientSchedulingQuery): Promise<OvokPatientAppointment[]>;
8
- export declare function getPatientAppointment(this: OvokClient, appointmentId: string): Promise<OvokPatientAppointment>;
9
- export declare function createPatientAppointment(this: OvokClient, appointment: OvokPatientAppointment): Promise<OvokPatientAppointment>;
10
- export declare function updatePatientAppointment(this: OvokClient, appointment: OvokPatientAppointment): Promise<OvokPatientAppointment>;
11
- export declare function listPatientSchedules(this: OvokClient, params?: OvokPatientSchedulingQuery): Promise<OvokPatientSchedule[]>;
12
- export declare function listPatientSlots(this: OvokClient, params?: OvokPatientSchedulingQuery): Promise<OvokPatientSlot[]>;
13
- export declare function listPatientMeasurementHistory(this: OvokClient, params?: OvokPatientResourceQuery): Promise<OvokPatientMeasurement[]>;
14
- export declare function getPatientMeasurement(this: OvokClient, measurementId: string): Promise<OvokPatientMeasurement>;
15
- export declare function listPatientCarePlans(this: OvokClient, params?: OvokPatientResourceQuery): Promise<OvokPatientCarePlan[]>;
16
- export declare function getPatientCarePlan(this: OvokClient, carePlanId: string): Promise<OvokPatientCarePlan>;
17
- export declare function listPatientQuestionnaires(this: OvokClient, params?: OvokPatientResourceQuery): Promise<OvokPatientQuestionnaire[]>;
18
- export declare function getPatientQuestionnaire(this: OvokClient, questionnaireId: string): Promise<OvokPatientQuestionnaire>;
19
- export declare function listPatientQuestionnaireResponses(this: OvokClient, params?: OvokPatientResourceQuery): Promise<OvokPatientQuestionnaireResponse[]>;
20
- export declare function getPatientQuestionnaireResponse(this: OvokClient, questionnaireResponseId: string): Promise<OvokPatientQuestionnaireResponse>;
21
- export declare function listPatientDevices(this: OvokClient, params?: OvokPatientResourceQuery): Promise<OvokPatientDevice[]>;
22
- export declare function getPatientDevice(this: OvokClient, deviceId: string): Promise<OvokPatientDevice>;
23
- export declare function listPatientNotifications(this: OvokClient, params?: OvokPatientResourceQuery): Promise<OvokPatientNotification[]>;
24
- export declare function getPatientNotification(this: OvokClient, notificationId: string): Promise<OvokPatientNotification>;
25
- export declare function getPatientDashboard(this: OvokClient, params?: OvokPatientDashboardQuery): Promise<OvokPatientDashboard>;
4
+ export declare function getCurrentPatientProfile(this: OvokClient, requestOptions?: MedplumRequestOptions): Promise<OvokCurrentPatientProfile>;
5
+ export declare function getPatientLatestObservations(this: OvokClient, params?: OvokPatientLatestObservationsQuery, requestOptions?: MedplumRequestOptions): Promise<OvokPatientObservationBundle>;
6
+ export declare function getPatientDeviceTelemetry(this: OvokClient, deviceId: string, requestOptions?: MedplumRequestOptions): Promise<OvokPatientObservationBundle>;
7
+ export declare function getPatientDeviceTelemetryHistory(this: OvokClient, deviceId: string, params?: OvokPatientTelemetryHistoryQuery, requestOptions?: MedplumRequestOptions): Promise<OvokPatientTelemetryHistory>;
8
+ export declare function listPatientAppointments(this: OvokClient, params?: OvokPatientSchedulingQuery, requestOptions?: MedplumRequestOptions): Promise<OvokPatientAppointment[]>;
9
+ export declare function getPatientAppointment(this: OvokClient, appointmentId: string, requestOptions?: MedplumRequestOptions): Promise<OvokPatientAppointment>;
10
+ export declare function createPatientAppointment(this: OvokClient, appointment: OvokPatientAppointment, requestOptions?: MedplumRequestOptions): Promise<OvokPatientAppointment>;
11
+ export declare function updatePatientAppointment(this: OvokClient, appointment: OvokPatientAppointment, requestOptions?: MedplumRequestOptions): Promise<OvokPatientAppointment>;
12
+ export declare function listPatientSchedules(this: OvokClient, params?: OvokPatientSchedulingQuery, requestOptions?: MedplumRequestOptions): Promise<OvokPatientSchedule[]>;
13
+ export declare function listPatientSlots(this: OvokClient, params?: OvokPatientSchedulingQuery, requestOptions?: MedplumRequestOptions): Promise<OvokPatientSlot[]>;
14
+ export declare function listPatientMeasurementHistory(this: OvokClient, params?: OvokPatientResourceQuery, requestOptions?: MedplumRequestOptions): Promise<OvokPatientMeasurement[]>;
15
+ export declare function getPatientMeasurement(this: OvokClient, measurementId: string, requestOptions?: MedplumRequestOptions): Promise<OvokPatientMeasurement>;
16
+ export declare function listPatientCarePlans(this: OvokClient, params?: OvokPatientResourceQuery, requestOptions?: MedplumRequestOptions): Promise<OvokPatientCarePlan[]>;
17
+ export declare function getPatientCarePlan(this: OvokClient, carePlanId: string, requestOptions?: MedplumRequestOptions): Promise<OvokPatientCarePlan>;
18
+ export declare function listPatientQuestionnaires(this: OvokClient, params?: OvokPatientResourceQuery, requestOptions?: MedplumRequestOptions): Promise<OvokPatientQuestionnaire[]>;
19
+ export declare function getPatientQuestionnaire(this: OvokClient, questionnaireId: string, requestOptions?: MedplumRequestOptions): Promise<OvokPatientQuestionnaire>;
20
+ export declare function listPatientQuestionnaireResponses(this: OvokClient, params?: OvokPatientResourceQuery, requestOptions?: MedplumRequestOptions): Promise<OvokPatientQuestionnaireResponse[]>;
21
+ export declare function getPatientQuestionnaireResponse(this: OvokClient, questionnaireResponseId: string, requestOptions?: MedplumRequestOptions): Promise<OvokPatientQuestionnaireResponse>;
22
+ export declare function listPatientDevices(this: OvokClient, params?: OvokPatientResourceQuery, requestOptions?: MedplumRequestOptions): Promise<OvokPatientDevice[]>;
23
+ export declare function getPatientDevice(this: OvokClient, deviceId: string, requestOptions?: MedplumRequestOptions): Promise<OvokPatientDevice>;
24
+ export declare function listPatientNotifications(this: OvokClient, params?: OvokPatientResourceQuery, requestOptions?: MedplumRequestOptions): Promise<OvokPatientNotification[]>;
25
+ export declare function getPatientNotification(this: OvokClient, notificationId: string, requestOptions?: MedplumRequestOptions): Promise<OvokPatientNotification>;
26
+ export declare function getPatientDashboard(this: OvokClient, params?: OvokPatientDashboardQuery, requestOptions?: MedplumRequestOptions): Promise<OvokPatientDashboard>;