@ovok/core 0.4.36 → 0.4.38

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 (127) hide show
  1. package/dist/client/auth/account-storage.d.ts +1 -0
  2. package/dist/client/auth/account-storage.js +2 -1
  3. package/dist/{utils → client}/auth/exchange-code.d.ts +1 -1
  4. package/dist/{utils → client}/auth/generate-code-challenge-and-verifier.js +5 -17
  5. package/dist/{utils → client}/auth/handle-tenant-code.d.ts +1 -1
  6. package/dist/{utils → client}/auth/handle-tenant-code.js +1 -1
  7. package/dist/client/auth/methods/appleLogin.js +2 -6
  8. package/dist/client/auth/methods/changePassword.js +2 -1
  9. package/dist/client/auth/methods/getSessions.js +2 -1
  10. package/dist/client/auth/methods/googleLogin.js +2 -6
  11. package/dist/client/auth/methods/login.js +26 -44
  12. package/dist/client/auth/methods/logout.js +2 -1
  13. package/dist/client/auth/methods/requestDeleteUser.js +5 -4
  14. package/dist/client/auth/methods/revokeSessions.js +2 -1
  15. package/dist/client/auth/methods/socialLogin.d.ts +7 -0
  16. package/dist/client/auth/methods/socialLogin.js +11 -0
  17. package/dist/client/auth/session-fetch.d.ts +35 -0
  18. package/dist/client/auth/session-fetch.js +206 -0
  19. package/dist/client/auth/session-http.d.ts +11 -0
  20. package/dist/client/auth/session-http.js +169 -0
  21. package/dist/client/auth/sha256.d.ts +2 -0
  22. package/dist/client/auth/sha256.js +65 -0
  23. package/dist/{utils → client}/auth/start-login.d.ts +2 -2
  24. package/dist/client/change-history/methods.js +2 -1
  25. package/dist/client/cms/methods.js +16 -28
  26. package/dist/client/consent/methods.js +3 -2
  27. package/dist/client/email/methods.js +2 -1
  28. package/dist/client/errors/ovok-error-utils.d.ts +6 -0
  29. package/dist/client/errors/ovok-error-utils.js +10 -1
  30. package/dist/client/errors/ovok-error.js +6 -13
  31. package/dist/client/errors/rate-limit-error.js +2 -6
  32. package/dist/client/experience/methods.js +28 -37
  33. package/dist/client/invites/methods.js +8 -12
  34. package/dist/client/notifications/methods.js +5 -5
  35. package/dist/client/observation/fhir-systems.d.ts +4 -0
  36. package/dist/client/observation/fhir-systems.js +7 -0
  37. package/dist/client/observation/methods/generateObservationBodiesByMeasurement.js +23 -29
  38. package/dist/client/observation/methods/getLatestObservationsByCodes.js +6 -9
  39. package/dist/client/observation/methods/observationsToMeasurements.js +44 -91
  40. package/dist/client/observation/services/base-observation-service.js +4 -3
  41. package/dist/client/observation/services/blood-pressure.js +1 -4
  42. package/dist/client/observation/services/{baby-sleep.d.ts → component-observation-services.d.ts} +18 -13
  43. package/dist/client/observation/services/component-observation-services.js +69 -0
  44. package/dist/client/observation/services/creator.d.ts +1 -2
  45. package/dist/client/observation/services/creator.js +15 -33
  46. package/dist/client/observation/services/ecg-observation-fragments.js +5 -6
  47. package/dist/client/observation/services/ecg.js +3 -2
  48. package/dist/client/observation/services/lipid-panel.js +8 -12
  49. package/dist/client/observation/services/measurement-time.d.ts +4 -0
  50. package/dist/client/observation/services/measurement-time.js +6 -0
  51. package/dist/client/observation/services/single-value-services.d.ts +36 -0
  52. package/dist/client/observation/services/single-value-services.js +100 -0
  53. package/dist/client/observation/services/uric-acid-service.js +6 -7
  54. package/dist/client/observation/types/generate-measurement/HealthConnectTypes.d.ts +2 -458
  55. package/dist/client/observation/types/generate-measurement/HealthConnectTypes.js +0 -6
  56. package/dist/client/offline/offline-measurement-queue.js +31 -28
  57. package/dist/client/ovok-client.d.ts +7 -15
  58. package/dist/client/ovok-client.js +54 -398
  59. package/dist/client/patient/methods.js +27 -36
  60. package/dist/client/patient-sharing/methods.js +4 -4
  61. package/dist/client/platform/methods.js +21 -30
  62. package/dist/client/platform/types.d.ts +3 -6
  63. package/dist/client/query-string.d.ts +3 -0
  64. package/dist/client/query-string.js +15 -0
  65. package/dist/client/questionnaire-response/methods/createQuestionnaireResponseWithObservations.js +2 -1
  66. package/dist/client/questionnaire-response/methods/extractQuestionnaireObservations.js +1 -2
  67. package/dist/client/questionnaire-response/methods/populateQuestionnaire.js +1 -2
  68. package/dist/client/questionnaire-response/methods/submitQuestionnaireResponse.js +2 -1
  69. package/dist/client/request-options.d.ts +2 -0
  70. package/dist/client/request-options.js +6 -0
  71. package/dist/client/signals/errors.js +2 -6
  72. package/dist/client/signals/methods.js +22 -22
  73. package/dist/client/slim/live-read.d.ts +4 -0
  74. package/dist/client/slim/live-read.js +87 -0
  75. package/dist/client/slim/methods.d.ts +3 -0
  76. package/dist/client/slim/methods.js +73 -166
  77. package/dist/client/storage.js +3 -2
  78. package/dist/conformance/capability-requirements.js +1 -1
  79. package/dist/conformance/capability.js +8 -7
  80. package/dist/hooks/account-hooks.js +6 -68
  81. package/dist/hooks/api-hooks.d.ts +10 -6
  82. package/dist/hooks/api-hooks.js +29 -88
  83. package/dist/hooks/cms-hooks.js +8 -13
  84. package/dist/hooks/email-hooks.js +1 -40
  85. package/dist/hooks/experience-hooks.js +4 -60
  86. package/dist/hooks/index.d.ts +2 -2
  87. package/dist/hooks/index.js +4 -4
  88. package/dist/hooks/invites-hooks.js +0 -1
  89. package/dist/hooks/notification-hooks.d.ts +4 -1
  90. package/dist/hooks/notification-hooks.js +29 -86
  91. package/dist/hooks/notification-invalidation.d.ts +6 -0
  92. package/dist/hooks/notification-invalidation.js +52 -0
  93. package/dist/hooks/notification-note-hooks.js +13 -28
  94. package/dist/hooks/observation-hooks.js +2 -4
  95. package/dist/hooks/patient-hooks.js +13 -113
  96. package/dist/hooks/patient-sharing-hooks.js +2 -7
  97. package/dist/hooks/platform-hooks.js +3 -51
  98. package/dist/hooks/poll-hooks.js +10 -8
  99. package/dist/hooks/signals-alert-hooks.js +4 -6
  100. package/dist/hooks/signals-hooks.js +2 -45
  101. package/package.json +1 -1
  102. package/dist/client/observation/services/baby-sleep.js +0 -69
  103. package/dist/client/observation/services/blood-glucose.d.ts +0 -12
  104. package/dist/client/observation/services/blood-glucose.js +0 -36
  105. package/dist/client/observation/services/body-temperature.d.ts +0 -13
  106. package/dist/client/observation/services/body-temperature.js +0 -36
  107. package/dist/client/observation/services/body-weight.d.ts +0 -13
  108. package/dist/client/observation/services/body-weight.js +0 -36
  109. package/dist/client/observation/services/heart-rate-variability.d.ts +0 -13
  110. package/dist/client/observation/services/heart-rate-variability.js +0 -38
  111. package/dist/client/observation/services/heart-rate.d.ts +0 -13
  112. package/dist/client/observation/services/heart-rate.js +0 -39
  113. package/dist/client/observation/services/respiratory-rate.d.ts +0 -13
  114. package/dist/client/observation/services/respiratory-rate.js +0 -38
  115. package/dist/client/observation/services/respiratory-therapy.d.ts +0 -28
  116. package/dist/client/observation/services/respiratory-therapy.js +0 -69
  117. package/dist/client/observation/services/resting-heart-rate.d.ts +0 -13
  118. package/dist/client/observation/services/resting-heart-rate.js +0 -38
  119. package/dist/client/observation/services/vo2-max.d.ts +0 -13
  120. package/dist/client/observation/services/vo2-max.js +0 -38
  121. package/dist/client/observation/services/walking-heart-rate-average.d.ts +0 -12
  122. package/dist/client/observation/services/walking-heart-rate-average.js +0 -38
  123. package/dist/utils/auth/sha256.d.ts +0 -8
  124. package/dist/utils/auth/sha256.js +0 -300
  125. /package/dist/{utils → client}/auth/exchange-code.js +0 -0
  126. /package/dist/{utils → client}/auth/generate-code-challenge-and-verifier.d.ts +0 -0
  127. /package/dist/{utils → client}/auth/start-login.js +0 -0
@@ -41,6 +41,8 @@ const aiFhirMethods = __importStar(require("./ai-fhir/methods"));
41
41
  const account_storage_1 = require("./auth/account-storage");
42
42
  const errors_1 = require("./auth/errors");
43
43
  const authMethods = __importStar(require("./auth/methods"));
44
+ const session_fetch_1 = require("./auth/session-fetch");
45
+ const session_http_1 = require("./auth/session-http");
44
46
  const botMethods = __importStar(require("./bot/methods"));
45
47
  const changeHistoryMethods = __importStar(require("./change-history/methods"));
46
48
  const aiMethods = __importStar(require("./chat/ai/methods"));
@@ -48,7 +50,6 @@ const cmsMethods = __importStar(require("./cms/methods"));
48
50
  const consentMethods = __importStar(require("./consent/methods"));
49
51
  const emailMethods = __importStar(require("./email"));
50
52
  const ovok_error_1 = require("./errors/ovok-error");
51
- const ovok_error_utils_1 = require("./errors/ovok-error-utils");
52
53
  const rate_limit_error_1 = require("./errors/rate-limit-error");
53
54
  const experienceMethods = __importStar(require("./experience/methods"));
54
55
  const inviteMethods = __importStar(require("./invites/methods"));
@@ -60,144 +61,13 @@ const patientMethods = __importStar(require("./patient/methods"));
60
61
  const patientSharingMethods = __importStar(require("./patient-sharing/methods"));
61
62
  const platformMethods = __importStar(require("./platform/methods"));
62
63
  const questionnaireResponseMethods = __importStar(require("./questionnaire-response/methods"));
64
+ const request_options_1 = require("./request-options");
63
65
  const errors_2 = require("./signals/errors");
64
66
  const signalsMethods = __importStar(require("./signals/methods"));
65
67
  const slimMethods = __importStar(require("./slim/methods"));
66
- exports.MAX_CONDITIONAL_OBSERVATION_ENTRIES = 8;
67
68
  var observation_bundle_utils_2 = require("./observation/observation-bundle-utils");
69
+ Object.defineProperty(exports, "MAX_CONDITIONAL_OBSERVATION_ENTRIES", { enumerable: true, get: function () { return observation_bundle_utils_2.MAX_CONDITIONAL_OBSERVATION_ENTRIES; } });
68
70
  Object.defineProperty(exports, "MAX_OBSERVATION_BUNDLE_BYTES", { enumerable: true, get: function () { return observation_bundle_utils_2.MAX_OBSERVATION_BUNDLE_BYTES; } });
69
- const retryAfterMs = (response) => {
70
- var _a, _b;
71
- 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");
72
- if (!value) {
73
- return undefined;
74
- }
75
- const seconds = Number(value);
76
- if (Number.isFinite(seconds)) {
77
- return Math.max(0, seconds * 1000);
78
- }
79
- const date = Date.parse(value);
80
- return Number.isFinite(date) ? Math.max(0, date - Date.now()) : undefined;
81
- };
82
- const timeoutError = () => {
83
- const error = new Error("The request timed out.");
84
- error.name = "TimeoutError";
85
- return error;
86
- };
87
- const withHttpErrorMetadata = async (response) => {
88
- var _a, _b;
89
- if (response.ok || typeof response.clone !== "function") {
90
- return response;
91
- }
92
- let bodyText = "";
93
- let body;
94
- try {
95
- bodyText = await response.clone().text();
96
- body = JSON.parse(bodyText);
97
- }
98
- catch (_c) {
99
- body = undefined;
100
- }
101
- const outcome = body && typeof body === "object" && body.resourceType === "OperationOutcome"
102
- ? body
103
- : undefined;
104
- const envelope = body && typeof body === "object" ? body : undefined;
105
- const nestedError = (envelope === null || envelope === void 0 ? void 0 : envelope.error) && typeof envelope.error === "object"
106
- ? envelope.error
107
- : undefined;
108
- const safeResponse = envelope
109
- ? Object.fromEntries(Object.entries(envelope).filter(([key]) => ["code", "error", "message", "requestId", "retryAfterSec", "status", "statusCode", "errors", "missing"].includes(key)))
110
- : { message: typeof body === "string" ? body : response.statusText };
111
- const status = response.status;
112
- 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);
113
- 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);
114
- const message = typeof (envelope === null || envelope === void 0 ? void 0 : envelope.message) === "string"
115
- ? envelope.message
116
- : typeof (nestedError === null || nestedError === void 0 ? void 0 : nestedError.message) === "string"
117
- ? nestedError.message
118
- : response.statusText || `HTTP ${status}`;
119
- const extensions = [];
120
- const addMetadata = (url, value, kind) => {
121
- if (value !== undefined) {
122
- extensions.push({ url, [`value${kind}`]: value });
123
- }
124
- };
125
- addMetadata("status", status, "Integer");
126
- addMetadata("requestId", requestId, "String");
127
- addMetadata("retryAfterMs", retryAfter, "Integer");
128
- if (!outcome) {
129
- extensions.push({ url: "response", valueString: JSON.stringify(safeResponse) });
130
- }
131
- const enrichedOutcome = outcome !== null && outcome !== void 0 ? outcome : {
132
- resourceType: "OperationOutcome",
133
- id: "ovok-http-error",
134
- issue: [{
135
- severity: "error",
136
- code: status === 429 ? "throttled" : "exception",
137
- details: { text: bodyText || message },
138
- }],
139
- };
140
- const existingExtensions = Array.isArray(enrichedOutcome.extension)
141
- ? enrichedOutcome.extension
142
- : [];
143
- enrichedOutcome.extension = [
144
- ...existingExtensions,
145
- { url: ovok_error_utils_1.OVOK_HTTP_ERROR_EXTENSION_URL, extension: extensions },
146
- ];
147
- const headers = new Headers(response.headers);
148
- headers.delete("content-length");
149
- headers.set("content-type", "application/fhir+json");
150
- return new Response(JSON.stringify(enrichedOutcome), {
151
- status,
152
- statusText: response.statusText,
153
- headers,
154
- });
155
- };
156
- const waitForRetry = (milliseconds, signal) => new Promise((resolve, reject) => {
157
- var _a;
158
- if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
159
- const error = new Error("Token refresh aborted");
160
- error.name = "AbortError";
161
- reject((_a = signal.reason) !== null && _a !== void 0 ? _a : error);
162
- return;
163
- }
164
- const timer = setTimeout(() => {
165
- signal === null || signal === void 0 ? void 0 : signal.removeEventListener("abort", abort);
166
- resolve();
167
- }, milliseconds);
168
- const abort = () => {
169
- var _a;
170
- clearTimeout(timer);
171
- const error = new Error("Token refresh aborted");
172
- error.name = "AbortError";
173
- reject((_a = signal === null || signal === void 0 ? void 0 : signal.reason) !== null && _a !== void 0 ? _a : error);
174
- };
175
- signal === null || signal === void 0 ? void 0 : signal.addEventListener("abort", abort, { once: true });
176
- });
177
- const isTemporaryRefreshFailure = (status) => status === 429 || (status >= 500 && status <= 599);
178
- const refreshUnavailableError = (response) => {
179
- const error = new Error("Token refresh is temporarily unavailable");
180
- error.name = "NetworkError";
181
- Object.assign(error, { cause: response });
182
- return error;
183
- };
184
- const refreshResponseError = (response) => {
185
- const error = new Error(`Token refresh returned HTTP ${response.status}`);
186
- error.name = "TokenRefreshError";
187
- Object.assign(error, { status: response.status, cause: response });
188
- return error;
189
- };
190
- const isNetworkError = (error) => {
191
- if (!(error instanceof Error)) {
192
- return false;
193
- }
194
- if (error.name === "NetworkError" ||
195
- error.name === "UnknownHostException" ||
196
- /network request failed|failed to fetch|load failed|unknownhostexception|econn|enotfound/i.test(error.message)) {
197
- return true;
198
- }
199
- return isNetworkError(error.cause);
200
- };
201
71
  const isRetrySafePost = (pathname) => pathname === "/ai/fhir/search";
202
72
  class OvokClient extends core_1.MedplumClient {
203
73
  /**
@@ -214,236 +84,19 @@ class OvokClient extends core_1.MedplumClient {
214
84
  const baseUrl = (_c = config.baseUrl) !== null && _c !== void 0 ? _c : "https://api.medplum.com/";
215
85
  const normalizedBaseUrl = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
216
86
  const tokenUrl = new URL((_d = config.tokenUrl) !== null && _d !== void 0 ? _d : "oauth2/token", normalizedBaseUrl).href;
217
- let unauthenticatedReason;
218
- let clearActiveLogin = () => undefined;
219
- let terminalRefreshToken;
220
- let terminalRefreshError;
221
- const refreshState = { unavailable: false };
222
- const isTokenRefresh = (url, options) => new URL(url, normalizedBaseUrl).href === tokenUrl &&
223
- (options === null || options === void 0 ? void 0 : options.method) === "POST" &&
224
- typeof options.body === "string" &&
225
- new URLSearchParams(options.body).get("grant_type") === "refresh_token";
226
- const markActiveAccountExpired = () => {
227
- var _a, _b;
228
- const accountId = (_b = (_a = storage.getObject("activeLogin")) === null || _a === void 0 ? void 0 : _a.profile) === null || _b === void 0 ? void 0 : _b.reference;
229
- if (accountId) {
230
- (0, account_storage_1.setStoredAccountExpired)(storage, accountId, true);
231
- }
232
- };
233
- const fetchOnce = async (url, options) => {
234
- var _a, _b;
235
- const requestTimeoutMs = config.requestTimeoutMs;
236
- let response;
237
- if (requestTimeoutMs === undefined) {
238
- response = await fetchImplementation(url, options);
239
- }
240
- else {
241
- const controller = new AbortController();
242
- const callerSignal = options === null || options === void 0 ? void 0 : options.signal;
243
- let timedOut = false;
244
- const abortFromCaller = () => controller.abort(callerSignal === null || callerSignal === void 0 ? void 0 : callerSignal.reason);
245
- const timeout = setTimeout(() => {
246
- timedOut = true;
247
- controller.abort(timeoutError());
248
- }, requestTimeoutMs);
249
- if (callerSignal === null || callerSignal === void 0 ? void 0 : callerSignal.aborted) {
250
- abortFromCaller();
251
- }
252
- else {
253
- callerSignal === null || callerSignal === void 0 ? void 0 : callerSignal.addEventListener("abort", abortFromCaller, { once: true });
254
- }
255
- try {
256
- response = await fetchImplementation(url, { ...options, signal: controller.signal });
257
- }
258
- catch (error) {
259
- if (timedOut) {
260
- throw timeoutError();
261
- }
262
- throw error;
263
- }
264
- finally {
265
- clearTimeout(timeout);
266
- callerSignal === null || callerSignal === void 0 ? void 0 : callerSignal.removeEventListener("abort", abortFromCaller);
267
- }
268
- }
269
- const requestUrl = new URL(url, normalizedBaseUrl);
270
- if (isTokenRefresh(url, options)) {
271
- if (response.status === 400) {
272
- let invalidGrant = false;
273
- try {
274
- invalidGrant = (await response.clone().json()).error === "invalid_grant";
275
- }
276
- catch (_c) {
277
- // Do not sign out unless the response confirms that the refresh token was revoked.
278
- }
279
- if (!invalidGrant) {
280
- throw refreshResponseError(response);
281
- }
282
- }
283
- if ([400, 401, 403].includes(response.status)) {
284
- unauthenticatedReason = {
285
- type: "refresh-rejected",
286
- status: response.status,
287
- };
288
- markActiveAccountExpired();
289
- }
290
- else if (response.ok) {
291
- const accountId = (_b = (_a = storage.getObject("activeLogin")) === null || _a === void 0 ? void 0 : _a.profile) === null || _b === void 0 ? void 0 : _b.reference;
292
- if (accountId) {
293
- (0, account_storage_1.setStoredAccountExpired)(storage, accountId, false);
294
- }
295
- unauthenticatedReason = undefined;
296
- }
297
- }
298
- else if (requestUrl.href === tokenUrl && !response.ok) {
299
- unauthenticatedReason = {
300
- type: "authentication-rejected",
301
- status: response.status,
302
- };
303
- }
304
- else if (response.status === 401) {
305
- unauthenticatedReason = { type: "request-unauthorized", status: 401 };
306
- }
307
- return isTokenRefresh(url, options)
308
- ? response
309
- : withHttpErrorMetadata(response);
310
- };
311
- const fetchWithRefreshRetry = async (url, options) => {
312
- var _a, _b, _c, _d;
313
- const requestUrl = new URL(url, normalizedBaseUrl);
314
- if (!isTokenRefresh(url, options)) {
315
- const response = await fetchOnce(url, options);
316
- const sessionEnded = requestUrl.pathname.endsWith("/auth/session/current") && response.status === 401;
317
- if (sessionEnded) {
318
- markActiveAccountExpired();
319
- }
320
- return response;
321
- }
322
- const login = storage.getObject("activeLogin");
323
- if (terminalRefreshError) {
324
- if ((login === null || login === void 0 ? void 0 : login.accessToken) && login.accessToken !== terminalRefreshToken) {
325
- terminalRefreshToken = undefined;
326
- terminalRefreshError = undefined;
327
- }
328
- else {
329
- throw terminalRefreshError;
330
- }
331
- }
332
- const expiresAt = (login === null || login === void 0 ? void 0 : login.accessToken) ? (0, core_1.tryGetJwtExpiration)(login.accessToken) : undefined;
333
- const signal = options === null || options === void 0 ? void 0 : options.signal;
334
- if (expiresAt !== undefined && options) {
335
- const tokenRequestOptions = options;
336
- tokenRequestOptions.maxRetries = 0;
337
- tokenRequestOptions.maxRetryTime = 0;
338
- }
339
- if (expiresAt === undefined) {
340
- const response = await fetchOnce(url, options);
341
- if (isTemporaryRefreshFailure(response.status)) {
342
- void ((_a = response.body) === null || _a === void 0 ? void 0 : _a.cancel().catch(() => undefined));
343
- if (config.keepSessionOnRefreshUnavailable) {
344
- refreshState.unavailable = true;
345
- }
346
- throw refreshUnavailableError(response);
347
- }
348
- return response;
349
- }
350
- let attempt = 0;
351
- let firstAttempt = true;
352
- let lastResponse;
353
- let lastNetworkError;
354
- while (firstAttempt || Date.now() < expiresAt) {
355
- firstAttempt = false;
356
- try {
357
- lastResponse = await fetchOnce(url, options);
358
- }
359
- catch (error) {
360
- if ((signal === null || signal === void 0 ? void 0 : signal.aborted) || !isNetworkError(error)) {
361
- throw error;
362
- }
363
- if (config.keepSessionOnRefreshUnavailable) {
364
- refreshState.unavailable = true;
365
- lastNetworkError = error;
366
- break;
367
- }
368
- if (Date.now() >= expiresAt) {
369
- lastNetworkError = error;
370
- break;
371
- }
372
- lastNetworkError = error;
373
- const remainingMs = expiresAt - Date.now();
374
- const backoffMs = Math.min(500 * (2 ** Math.min(attempt, 6)), 30000);
375
- await waitForRetry(Math.min(backoffMs, remainingMs), signal);
376
- attempt += 1;
377
- continue;
378
- }
379
- if (!isTemporaryRefreshFailure(lastResponse.status)) {
380
- return lastResponse;
381
- }
382
- if (config.keepSessionOnRefreshUnavailable) {
383
- refreshState.unavailable = true;
384
- break;
385
- }
386
- const remainingMs = expiresAt - Date.now();
387
- if (remainingMs <= 0) {
388
- break;
389
- }
390
- const backoffMs = Math.min(500 * (2 ** Math.min(attempt, 6)), 30000);
391
- const waitMs = Math.min(Math.max((_b = retryAfterMs(lastResponse)) !== null && _b !== void 0 ? _b : 0, backoffMs), remainingMs);
392
- if (waitMs >= remainingMs) {
393
- await waitForRetry(remainingMs, signal);
394
- break;
395
- }
396
- void ((_c = lastResponse.body) === null || _c === void 0 ? void 0 : _c.cancel().catch(() => undefined));
397
- await waitForRetry(waitMs, signal);
398
- attempt += 1;
399
- }
400
- const status = lastResponse === null || lastResponse === void 0 ? void 0 : lastResponse.status;
401
- const reason = {
402
- type: "refresh-unavailable",
403
- ...(status === undefined ? {} : { status }),
404
- };
405
- if (config.keepSessionOnRefreshUnavailable) {
406
- refreshState.unavailable = true;
407
- if (lastResponse) {
408
- return lastResponse;
409
- }
410
- throw lastNetworkError !== null && lastNetworkError !== void 0 ? lastNetworkError : refreshUnavailableError(undefined);
411
- }
412
- unauthenticatedReason = reason;
413
- markActiveAccountExpired();
414
- if (lastResponse) {
415
- return lastResponse;
416
- }
417
- terminalRefreshToken = login === null || login === void 0 ? void 0 : login.accessToken;
418
- terminalRefreshError = lastNetworkError !== null && lastNetworkError !== void 0 ? lastNetworkError : refreshUnavailableError(undefined);
419
- clearActiveLogin();
420
- (_d = config.onUnauthenticated) === null || _d === void 0 ? void 0 : _d.call(config, reason);
421
- unauthenticatedReason = undefined;
422
- throw terminalRefreshError;
423
- };
87
+ const session = (0, session_fetch_1.createSessionFetch)({ config, storage, fetchImplementation, normalizedBaseUrl, tokenUrl });
424
88
  super({
425
89
  ...config,
426
90
  storage,
427
- onUnauthenticated: () => {
428
- var _a;
429
- if (refreshState.unavailable) {
430
- return;
431
- }
432
- const reason = unauthenticatedReason !== null && unauthenticatedReason !== void 0 ? unauthenticatedReason : {
433
- type: "request-unauthorized",
434
- status: 401,
435
- };
436
- unauthenticatedReason = undefined;
437
- (_a = config.onUnauthenticated) === null || _a === void 0 ? void 0 : _a.call(config, reason);
438
- },
439
- fetch: fetchWithRefreshRetry,
91
+ onUnauthenticated: session.onUnauthenticated,
92
+ fetch: session.fetch,
440
93
  });
441
94
  this.socialLoginClientId = "";
442
- clearActiveLogin = this.clearActiveLogin.bind(this);
95
+ session.setClearActiveLogin(this.clearActiveLogin.bind(this));
443
96
  this.hasExplicitStorageAdapter = config.storage !== undefined;
444
97
  this.clientOptions = config;
445
98
  this.keepSessionOnRefreshUnavailable = config.keepSessionOnRefreshUnavailable === true;
446
- this.refreshState = refreshState;
99
+ this.refreshState = session.refreshState;
447
100
  this.socialLoginClientId = (_e = config.socialLoginClientId) !== null && _e !== void 0 ? _e : "";
448
101
  this.addEventListener("profileRefreshed", () => this.syncSavedAccountProfile());
449
102
  this.bindMethods(authMethods);
@@ -494,7 +147,7 @@ class OvokClient extends core_1.MedplumClient {
494
147
  !(activeAccount === null || activeAccount === void 0 ? void 0 : activeAccount.expired) &&
495
148
  expiresAt !== undefined &&
496
149
  expiresAt > Date.now()) {
497
- (_a = this.clientStorage) === null || _a === void 0 ? void 0 : _a.setObject("activeLogin", activeLogin);
150
+ (_a = this.clientStorage) === null || _a === void 0 ? void 0 : _a.setObject(account_storage_1.ACTIVE_LOGIN_STORAGE_KEY, activeLogin);
498
151
  this.setAccessToken(accessToken, activeLogin.refreshToken);
499
152
  return;
500
153
  }
@@ -546,7 +199,7 @@ class OvokClient extends core_1.MedplumClient {
546
199
  }
547
200
  catch (error) {
548
201
  const activeLogin = this.getActiveLogin();
549
- if (((_a = activeLogin === null || activeLogin === void 0 ? void 0 : activeLogin.profile) === null || _a === void 0 ? void 0 : _a.reference) === accountId && isNetworkError(error)) {
202
+ if (((_a = activeLogin === null || activeLogin === void 0 ? void 0 : activeLogin.profile) === null || _a === void 0 ? void 0 : _a.reference) === accountId && (0, session_http_1.isNetworkError)(error)) {
550
203
  return;
551
204
  }
552
205
  if (!activeLogin &&
@@ -573,7 +226,7 @@ class OvokClient extends core_1.MedplumClient {
573
226
  const wasActive = previousAccountId === accountId;
574
227
  const sessionClient = this.createAccountSessionClient(login);
575
228
  try {
576
- await sessionClient.delete("auth/session/current", ...(requestOptions === undefined ? [] : [requestOptions]));
229
+ await sessionClient.delete("auth/session/current", ...(0, request_options_1.optionalArg)(requestOptions));
577
230
  }
578
231
  catch (error) {
579
232
  if (!sessionClient.getSavedAccounts().some((account) => account.id === accountId && account.expired)) {
@@ -601,14 +254,7 @@ class OvokClient extends core_1.MedplumClient {
601
254
  }
602
255
  /** Removes every saved account and expiry marker from client storage. */
603
256
  clearAllAccounts() {
604
- var _a;
605
- const storage = this.clientStorage;
606
- const queueKey = (_a = this.offlineQueueOptions.storageKey) !== null && _a !== void 0 ? _a : offline_1.OFFLINE_MEASUREMENT_QUEUE_STORAGE_KEY;
607
- const queuedMeasurements = storage === null || storage === void 0 ? void 0 : storage.getObject(queueKey);
608
- super.clear();
609
- if (Array.isArray(queuedMeasurements)) {
610
- storage === null || storage === void 0 ? void 0 : storage.setObject(queueKey, queuedMeasurements);
611
- }
257
+ this.clearKeepingQueue();
612
258
  }
613
259
  /** Reauthentication clears the expired marker for the account that signed in. */
614
260
  async setActiveLogin(login) {
@@ -622,22 +268,22 @@ class OvokClient extends core_1.MedplumClient {
622
268
  /** Saves a measurement immediately, or durably queues it when the opt-in queue is enabled. */
623
269
  async saveMeasurement(params, requestOptions) {
624
270
  const observations = await this.generateObservationBodiesByMeasurement(params);
625
- return this.saveObservations(observations, ...(requestOptions === undefined ? [] : [requestOptions]));
271
+ return this.saveObservations(observations, ...(0, request_options_1.optionalArg)(requestOptions));
626
272
  }
627
273
  /** Reads Ovok's composed FHIR and public-route capability statement. */
628
274
  async getCapabilityStatement(version = "R4", requestOptions) {
629
- return this.get(`/fhir/${version}/metadata`, ...(requestOptions === undefined ? [] : [requestOptions]));
275
+ return this.get(`/fhir/${version}/metadata`, ...(0, request_options_1.optionalArg)(requestOptions));
630
276
  }
631
277
  /** Returns the non-FHIR public routes published by the backend statement. */
632
278
  async getPublicApiEndpoints(version = "R4", requestOptions) {
633
- return (0, capability_1.getOvokPublicApiEndpoints)(await this.getCapabilityStatement(version, ...(requestOptions === undefined ? [] : [requestOptions])));
279
+ return (0, capability_1.getOvokPublicApiEndpoints)(await this.getCapabilityStatement(version, ...(0, request_options_1.optionalArg)(requestOptions)));
634
280
  }
635
281
  /** Saves observations with conditional creates so a retry cannot create a duplicate. */
636
282
  async saveObservations(observations, requestOptions) {
637
283
  const linkedObservations = (0, observation_bundle_utils_1.linkObservationMembers)(observations);
638
284
  const queue = this.getOfflineMeasurementQueue();
639
285
  if (queue) {
640
- const { id, flush } = await queue.enqueueAndFlush(linkedObservations, ...(requestOptions === undefined ? [] : [requestOptions]));
286
+ const { id, flush } = await queue.enqueueAndFlush(linkedObservations, ...(0, request_options_1.optionalArg)(requestOptions));
641
287
  const response = flush.responses[id];
642
288
  return response
643
289
  ? { status: "saved", response }
@@ -659,7 +305,7 @@ class OvokClient extends core_1.MedplumClient {
659
305
  failures: {},
660
306
  };
661
307
  }
662
- return queue.flush(options, ...(requestOptions === undefined ? [] : [requestOptions]));
308
+ return queue.flush(options, ...(0, request_options_1.optionalArg)(requestOptions));
663
309
  }
664
310
  getOfflineMeasurementQueue() {
665
311
  var _a;
@@ -669,31 +315,33 @@ class OvokClient extends core_1.MedplumClient {
669
315
  if (!this.clientStorage || !this.hasExplicitStorageAdapter) {
670
316
  throw new Error("Offline measurement queue requires a durable client storage adapter.");
671
317
  }
672
- (_a = this.offlineMeasurementQueue) !== null && _a !== void 0 ? _a : (this.offlineMeasurementQueue = new offline_1.OfflineMeasurementQueue(this.clientStorage, (bundle, requestOptions) => this.executeObservationBundle(bundle, ...(requestOptions === undefined ? [] : [requestOptions])), {
318
+ (_a = this.offlineMeasurementQueue) !== null && _a !== void 0 ? _a : (this.offlineMeasurementQueue = new offline_1.OfflineMeasurementQueue(this.clientStorage, (bundle, requestOptions) => this.executeObservationBundle(bundle, ...(0, request_options_1.optionalArg)(requestOptions)), {
673
319
  ...this.offlineQueueOptions,
674
- getOwnerKey: () => {
675
- var _a, _b, _c;
676
- const login = this.getActiveLogin();
677
- const profileReference = (_a = login === null || login === void 0 ? void 0 : login.profile) === null || _a === void 0 ? void 0 : _a.reference;
678
- if (!profileReference) {
679
- return undefined;
680
- }
681
- return [
682
- this.getBaseUrl(),
683
- (_c = (_b = login === null || login === void 0 ? void 0 : login.project) === null || _b === void 0 ? void 0 : _b.reference) !== null && _c !== void 0 ? _c : "",
684
- profileReference,
685
- ].join("|");
686
- },
320
+ getOwnerKey: () => this.offlineOwnerKey(),
687
321
  }));
688
322
  return this.offlineMeasurementQueue;
689
323
  }
324
+ /** Queued measurements belong to one account on one server and project. */
325
+ offlineOwnerKey() {
326
+ var _a, _b, _c;
327
+ const login = this.getActiveLogin();
328
+ const profileReference = (_a = login === null || login === void 0 ? void 0 : login.profile) === null || _a === void 0 ? void 0 : _a.reference;
329
+ if (!profileReference) {
330
+ return undefined;
331
+ }
332
+ return [
333
+ this.getBaseUrl(),
334
+ (_c = (_b = login === null || login === void 0 ? void 0 : login.project) === null || _b === void 0 ? void 0 : _b.reference) !== null && _c !== void 0 ? _c : "",
335
+ profileReference,
336
+ ].join("|");
337
+ }
690
338
  /**
691
339
  * Medplum clears its entire client storage after an unrecoverable 401. Keep
692
340
  * saved accounts (marking only the active one expired) and queued measurements.
693
341
  * Use clearAllAccounts() when the caller intends to remove all saved accounts.
694
342
  */
695
343
  clear() {
696
- var _a, _b, _c;
344
+ var _a, _b;
697
345
  const storage = this.clientStorage;
698
346
  const activeAccountId = (_b = (_a = this.getActiveLogin()) === null || _a === void 0 ? void 0 : _a.profile) === null || _b === void 0 ? void 0 : _b.reference;
699
347
  const logins = this.getLogins().map((login) => {
@@ -706,15 +354,23 @@ class OvokClient extends core_1.MedplumClient {
706
354
  if (activeAccountId) {
707
355
  expiredAccountIds.add(activeAccountId);
708
356
  }
709
- const queueKey = (_c = this.offlineQueueOptions.storageKey) !== null && _c !== void 0 ? _c : offline_1.OFFLINE_MEASUREMENT_QUEUE_STORAGE_KEY;
357
+ this.clearKeepingQueue(() => {
358
+ if (logins.length > 0) {
359
+ storage === null || storage === void 0 ? void 0 : storage.setObject(account_storage_1.LOGINS_STORAGE_KEY, logins);
360
+ }
361
+ if (expiredAccountIds.size > 0) {
362
+ storage === null || storage === void 0 ? void 0 : storage.setObject(account_storage_1.EXPIRED_ACCOUNTS_STORAGE_KEY, Array.from(expiredAccountIds));
363
+ }
364
+ });
365
+ }
366
+ /** Runs Medplum's storage clear but keeps queued offline measurements; `restore` runs in between. */
367
+ clearKeepingQueue(restore) {
368
+ var _a;
369
+ const storage = this.clientStorage;
370
+ const queueKey = (_a = this.offlineQueueOptions.storageKey) !== null && _a !== void 0 ? _a : offline_1.OFFLINE_MEASUREMENT_QUEUE_STORAGE_KEY;
710
371
  const queuedMeasurements = storage === null || storage === void 0 ? void 0 : storage.getObject(queueKey);
711
372
  super.clear();
712
- if (logins.length > 0) {
713
- storage === null || storage === void 0 ? void 0 : storage.setObject(account_storage_1.LOGINS_STORAGE_KEY, logins);
714
- }
715
- if (expiredAccountIds.size > 0) {
716
- storage === null || storage === void 0 ? void 0 : storage.setObject(account_storage_1.EXPIRED_ACCOUNTS_STORAGE_KEY, Array.from(expiredAccountIds));
717
- }
373
+ restore === null || restore === void 0 ? void 0 : restore();
718
374
  if (Array.isArray(queuedMeasurements)) {
719
375
  storage === null || storage === void 0 ? void 0 : storage.setObject(queueKey, queuedMeasurements);
720
376
  }
@@ -728,7 +384,7 @@ class OvokClient extends core_1.MedplumClient {
728
384
  }
729
385
  createAccountSessionClient(login) {
730
386
  const storage = new core_1.ClientStorage(new core_1.MemoryStorage(), this.clientOptions.storagePrefix);
731
- storage.setObject("activeLogin", login);
387
+ storage.setObject(account_storage_1.ACTIVE_LOGIN_STORAGE_KEY, login);
732
388
  storage.setObject(account_storage_1.LOGINS_STORAGE_KEY, [login]);
733
389
  const client = new OvokClient({
734
390
  ...this.clientOptions,
@@ -761,7 +417,7 @@ class OvokClient extends core_1.MedplumClient {
761
417
  ...login,
762
418
  profile: { ...login.profile, display: (0, core_1.getDisplayString)(profile) },
763
419
  };
764
- (_b = this.clientStorage) === null || _b === void 0 ? void 0 : _b.setObject("activeLogin", updatedLogin);
420
+ (_b = this.clientStorage) === null || _b === void 0 ? void 0 : _b.setObject(account_storage_1.ACTIVE_LOGIN_STORAGE_KEY, updatedLogin);
765
421
  (_c = this.clientStorage) === null || _c === void 0 ? void 0 : _c.setObject(account_storage_1.LOGINS_STORAGE_KEY, this.getLogins().map((savedLogin) => { var _a; return ((_a = savedLogin.profile) === null || _a === void 0 ? void 0 : _a.reference) === accountId ? updatedLogin : savedLogin; }));
766
422
  }
767
423
  async restoreActiveLogin(login) {
@@ -778,10 +434,10 @@ class OvokClient extends core_1.MedplumClient {
778
434
  resourceType: "Bundle",
779
435
  type: "transaction",
780
436
  entry: entries,
781
- }, ...(requestOptions === undefined ? [] : [requestOptions]));
437
+ }, ...(0, request_options_1.optionalArg)(requestOptions));
782
438
  }
783
439
  async executeObservationBundle(bundle, requestOptions) {
784
- const response = await (0, observation_bundle_utils_1.executeObservationBundle)(bundle, (request) => this.executeBatch(request, ...(requestOptions === undefined ? [] : [requestOptions])));
440
+ const response = await (0, observation_bundle_utils_1.executeObservationBundle)(bundle, (request) => this.executeBatch(request, ...(0, request_options_1.optionalArg)(requestOptions)));
785
441
  this.invalidateSearches("Observation");
786
442
  return response;
787
443
  }