@ovok/core 0.4.31 → 0.4.33

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 (126) 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/errors.d.ts +6 -0
  4. package/dist/client/auth/errors.js +12 -1
  5. package/dist/client/auth/methods/appleLogin.d.ts +2 -1
  6. package/dist/client/auth/methods/appleLogin.js +2 -2
  7. package/dist/client/auth/methods/changePassword.d.ts +2 -1
  8. package/dist/client/auth/methods/changePassword.js +2 -2
  9. package/dist/client/auth/methods/getSessions.d.ts +2 -1
  10. package/dist/client/auth/methods/getSessions.js +2 -2
  11. package/dist/client/auth/methods/googleLogin.d.ts +2 -1
  12. package/dist/client/auth/methods/googleLogin.js +2 -2
  13. package/dist/client/auth/methods/index.d.ts +1 -0
  14. package/dist/client/auth/methods/index.js +1 -0
  15. package/dist/client/auth/methods/login.d.ts +6 -2
  16. package/dist/client/auth/methods/login.js +60 -44
  17. package/dist/client/auth/methods/logout.d.ts +2 -1
  18. package/dist/client/auth/methods/logout.js +2 -2
  19. package/dist/client/auth/methods/refetchProfile.d.ts +2 -1
  20. package/dist/client/auth/methods/refetchProfile.js +3 -4
  21. package/dist/client/auth/methods/register.d.ts +2 -1
  22. package/dist/client/auth/methods/register.js +2 -2
  23. package/dist/client/auth/methods/registerPractitioner.d.ts +2 -1
  24. package/dist/client/auth/methods/registerPractitioner.js +2 -2
  25. package/dist/client/auth/methods/reloadSession.d.ts +7 -0
  26. package/dist/client/auth/methods/reloadSession.js +23 -0
  27. package/dist/client/auth/methods/requestDeleteUser.d.ts +2 -1
  28. package/dist/client/auth/methods/requestDeleteUser.js +5 -5
  29. package/dist/client/auth/methods/resetPassword.d.ts +2 -1
  30. package/dist/client/auth/methods/resetPassword.js +2 -2
  31. package/dist/client/auth/methods/revokeSessions.d.ts +2 -1
  32. package/dist/client/auth/methods/revokeSessions.js +2 -2
  33. package/dist/client/auth/methods/setPasswordFromReset.d.ts +2 -1
  34. package/dist/client/auth/methods/setPasswordFromReset.js +2 -2
  35. package/dist/client/auth/types/LoginBody.d.ts +10 -20
  36. package/dist/client/auth/types/LoginError.d.ts +15 -0
  37. package/dist/client/auth/types/LoginError.js +15 -0
  38. package/dist/client/bot/methods/executeBot.d.ts +2 -1
  39. package/dist/client/bot/methods/executeBot.js +2 -2
  40. package/dist/client/change-history/methods.d.ts +2 -1
  41. package/dist/client/change-history/methods.js +2 -2
  42. package/dist/client/chat/ai/methods/createAiSession.d.ts +2 -1
  43. package/dist/client/chat/ai/methods/createAiSession.js +2 -2
  44. package/dist/client/cms/methods.d.ts +6 -5
  45. package/dist/client/cms/methods.js +10 -10
  46. package/dist/client/consent/methods.d.ts +5 -4
  47. package/dist/client/consent/methods.js +8 -8
  48. package/dist/client/email/methods.d.ts +2 -1
  49. package/dist/client/email/methods.js +2 -2
  50. package/dist/client/errors/ovok-error-utils.d.ts +7 -0
  51. package/dist/client/errors/ovok-error-utils.js +49 -5
  52. package/dist/client/errors/ovok-error.d.ts +4 -0
  53. package/dist/client/errors/ovok-error.js +17 -4
  54. package/dist/client/errors/rate-limit-error.js +5 -3
  55. package/dist/client/experience/methods.d.ts +32 -31
  56. package/dist/client/experience/methods.js +62 -62
  57. package/dist/client/invites/methods.d.ts +6 -5
  58. package/dist/client/invites/methods.js +17 -11
  59. package/dist/client/notifications/methods.d.ts +6 -5
  60. package/dist/client/notifications/methods.js +10 -10
  61. package/dist/client/notifications/types.d.ts +92 -11
  62. package/dist/client/observation/methods/getLatestObservationsByCodes.d.ts +2 -1
  63. package/dist/client/observation/methods/getLatestObservationsByCodes.js +4 -4
  64. package/dist/client/offline/offline-measurement-queue.d.ts +10 -3
  65. package/dist/client/offline/offline-measurement-queue.js +44 -29
  66. package/dist/client/ovok-client.d.ts +17 -7
  67. package/dist/client/ovok-client.js +235 -43
  68. package/dist/client/patient/methods.d.ts +24 -23
  69. package/dist/client/patient/methods.js +49 -49
  70. package/dist/client/patient-sharing/methods.d.ts +7 -6
  71. package/dist/client/patient-sharing/methods.js +16 -12
  72. package/dist/client/platform/methods.d.ts +24 -23
  73. package/dist/client/platform/methods.js +46 -46
  74. package/dist/client/questionnaire-response/methods/createQuestionnaireResponseWithObservations.d.ts +2 -1
  75. package/dist/client/questionnaire-response/methods/createQuestionnaireResponseWithObservations.js +2 -2
  76. package/dist/client/questionnaire-response/methods/extractQuestionnaireObservations.d.ts +2 -1
  77. package/dist/client/questionnaire-response/methods/extractQuestionnaireObservations.js +2 -2
  78. package/dist/client/questionnaire-response/methods/populateQuestionnaire.d.ts +2 -1
  79. package/dist/client/questionnaire-response/methods/populateQuestionnaire.js +2 -2
  80. package/dist/client/questionnaire-response/methods/submitQuestionnaireResponse.d.ts +2 -1
  81. package/dist/client/questionnaire-response/methods/submitQuestionnaireResponse.js +2 -2
  82. package/dist/client/signals/methods.d.ts +22 -21
  83. package/dist/client/signals/methods.js +42 -42
  84. package/dist/client/slim/methods.d.ts +93 -57
  85. package/dist/client/slim/methods.js +295 -108
  86. package/dist/client/slim/paging.d.ts +18 -0
  87. package/dist/client/slim/paging.js +57 -0
  88. package/dist/client/slim/types.d.ts +370 -3
  89. package/dist/conformance/api-requirements.js +22 -0
  90. package/dist/conformance/capability-requirements.js +1 -1
  91. package/dist/hooks/account-hooks.js +7 -7
  92. package/dist/hooks/api-hooks.d.ts +5 -2
  93. package/dist/hooks/api-hooks.js +39 -16
  94. package/dist/hooks/cms-hooks.js +3 -3
  95. package/dist/hooks/email-hooks.js +1 -1
  96. package/dist/hooks/experience-hooks.js +5 -5
  97. package/dist/hooks/index.d.ts +2 -0
  98. package/dist/hooks/index.js +5 -3
  99. package/dist/hooks/invites-hooks.js +7 -1
  100. package/dist/hooks/notification-ack-hooks.d.ts +2 -1
  101. package/dist/hooks/notification-hooks.d.ts +4 -2
  102. package/dist/hooks/notification-hooks.js +11 -23
  103. package/dist/hooks/notification-invalidation.d.ts +5 -2
  104. package/dist/hooks/notification-invalidation.js +10 -1
  105. package/dist/hooks/notification-note-hooks.js +3 -3
  106. package/dist/hooks/observation-hooks.d.ts +5 -1
  107. package/dist/hooks/observation-hooks.js +6 -6
  108. package/dist/hooks/patient-hooks.js +18 -18
  109. package/dist/hooks/patient-sharing-hooks.js +19 -5
  110. package/dist/hooks/platform-hooks.js +3 -3
  111. package/dist/hooks/poll-hooks.d.ts +14 -0
  112. package/dist/hooks/poll-hooks.js +127 -0
  113. package/dist/hooks/signals-alert-hooks.d.ts +5 -20
  114. package/dist/hooks/signals-alert-hooks.js +38 -34
  115. package/dist/hooks/signals-alert-item-hooks.d.ts +19 -0
  116. package/dist/hooks/signals-alert-item-hooks.js +62 -0
  117. package/dist/hooks/signals-hooks.js +2 -2
  118. package/dist/index.d.ts +3 -0
  119. package/dist/index.js +4 -1
  120. package/dist/utils/auth/exchange-code.d.ts +2 -0
  121. package/dist/utils/auth/exchange-code.js +2 -2
  122. package/dist/utils/auth/handle-tenant-code.d.ts +1 -1
  123. package/dist/utils/auth/handle-tenant-code.js +13 -4
  124. package/dist/utils/auth/start-login.d.ts +3 -1
  125. package/dist/utils/auth/start-login.js +3 -3
  126. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { IClientStorage, LoginState, MedplumClient, MedplumClientOptions, SubscriptionManager } from "@medplum/core";
1
+ import { IClientStorage, LoginState, MedplumClient, MedplumClientOptions, MedplumRequestOptions, ReadablePromise, SubscriptionManager } from "@medplum/core";
2
2
  import { Bundle, CapabilityStatement, Observation } from "@medplum/fhirtypes";
3
3
  import { OvokPublicApiEndpoint } from "../conformance/capability";
4
4
  import * as aiFhirMethods from "./ai-fhir/methods";
@@ -48,19 +48,28 @@ export type OvokUnauthenticatedReason = {
48
48
  export type OvokClientOptions = Omit<MedplumClientOptions, "onUnauthenticated"> & {
49
49
  onUnauthenticated?: (reason: OvokUnauthenticatedReason) => void;
50
50
  socialLoginClientId?: string;
51
+ requestTimeoutMs?: number;
51
52
  offlineQueue?: OfflineMeasurementQueueOptions;
53
+ /** Keep the saved login when a refresh cannot reach the server. */
54
+ keepSessionOnRefreshUnavailable?: boolean;
52
55
  };
53
56
  export declare class OvokClient extends MedplumClient {
54
57
  protected socialLoginClientId: string;
55
58
  clientStorage: IClientStorage | undefined;
56
59
  private readonly hasExplicitStorageAdapter;
57
60
  private readonly offlineQueueOptions;
61
+ private readonly clientOptions;
62
+ private readonly keepSessionOnRefreshUnavailable;
63
+ private readonly refreshState;
58
64
  private offlineMeasurementQueue;
59
65
  /**
60
66
  * @param config - The configuration for the client.
61
67
  * @param config.socialLoginClientId - The social login client id to use for the client.
62
68
  */
63
69
  constructor(config: OvokClientOptions);
70
+ get<T = unknown>(url: URL | string, options?: MedplumRequestOptions): ReadablePromise<T>;
71
+ refreshIfExpired(gracePeriod?: number): Promise<void>;
72
+ post<T = unknown>(url: URL | string, body?: unknown, contentType?: string, options?: MedplumRequestOptions): Promise<T>;
64
73
  /** Lists saved accounts without exposing their access or refresh tokens. */
65
74
  getSavedAccounts(): OvokSavedAccount[];
66
75
  /** Activates a previously saved account without asking for its password again. */
@@ -70,21 +79,21 @@ export declare class OvokClient extends MedplumClient {
70
79
  * Accounts with an expired server session are removed locally; network and service
71
80
  * failures retain the account so it can be removed after connectivity returns.
72
81
  */
73
- removeAccount(accountId: string): Promise<void>;
82
+ removeAccount(accountId: string, requestOptions?: MedplumRequestOptions): Promise<void>;
74
83
  /** Removes every saved account and expiry marker from client storage. */
75
84
  clearAllAccounts(): void;
76
85
  /** Reauthentication clears the expired marker for the account that signed in. */
77
86
  setActiveLogin(login: LoginState): Promise<void>;
78
87
  /** Saves a measurement immediately, or durably queues it when the opt-in queue is enabled. */
79
- saveMeasurement(params: GenerateObservationBodyParams): Promise<SaveObservationsResult>;
88
+ saveMeasurement(params: GenerateObservationBodyParams, requestOptions?: MedplumRequestOptions): Promise<SaveObservationsResult>;
80
89
  /** Reads Ovok's composed FHIR and public-route capability statement. */
81
- getCapabilityStatement(version?: OvokFhirVersion): Promise<CapabilityStatement>;
90
+ getCapabilityStatement(version?: OvokFhirVersion, requestOptions?: MedplumRequestOptions): Promise<CapabilityStatement>;
82
91
  /** Returns the non-FHIR public routes published by the backend statement. */
83
- getPublicApiEndpoints(version?: OvokFhirVersion): Promise<OvokPublicApiEndpoint[]>;
92
+ getPublicApiEndpoints(version?: OvokFhirVersion, requestOptions?: MedplumRequestOptions): Promise<OvokPublicApiEndpoint[]>;
84
93
  /** Saves observations with conditional creates so a retry cannot create a duplicate. */
85
- saveObservations(observations: Observation[]): Promise<SaveObservationsResult>;
94
+ saveObservations(observations: Observation[], requestOptions?: MedplumRequestOptions): Promise<SaveObservationsResult>;
86
95
  /** Flushes measurements queued while offline. Call this on connectivity/app-resume events. */
87
- flushOfflineMeasurementQueue(options?: OfflineMeasurementFlushOptions): Promise<OfflineMeasurementFlushResult>;
96
+ flushOfflineMeasurementQueue(options?: OfflineMeasurementFlushOptions, requestOptions?: MedplumRequestOptions): Promise<OfflineMeasurementFlushResult>;
88
97
  private getOfflineMeasurementQueue;
89
98
  /**
90
99
  * Medplum clears its entire client storage after an unrecoverable 401. Keep
@@ -94,6 +103,7 @@ export declare class OvokClient extends MedplumClient {
94
103
  clear(): void;
95
104
  private getLogin;
96
105
  private getExpiredAccountIds;
106
+ private createAccountSessionClient;
97
107
  private setAccountExpired;
98
108
  private removeSavedAccount;
99
109
  private syncSavedAccountProfile;
@@ -39,6 +39,7 @@ const core_1 = require("@medplum/core");
39
39
  const capability_1 = require("../conformance/capability");
40
40
  const aiFhirMethods = __importStar(require("./ai-fhir/methods"));
41
41
  const account_storage_1 = require("./auth/account-storage");
42
+ const errors_1 = require("./auth/errors");
42
43
  const authMethods = __importStar(require("./auth/methods"));
43
44
  const botMethods = __importStar(require("./bot/methods"));
44
45
  const changeHistoryMethods = __importStar(require("./change-history/methods"));
@@ -47,6 +48,7 @@ const cmsMethods = __importStar(require("./cms/methods"));
47
48
  const consentMethods = __importStar(require("./consent/methods"));
48
49
  const emailMethods = __importStar(require("./email"));
49
50
  const ovok_error_1 = require("./errors/ovok-error");
51
+ const ovok_error_utils_1 = require("./errors/ovok-error-utils");
50
52
  const rate_limit_error_1 = require("./errors/rate-limit-error");
51
53
  const experienceMethods = __importStar(require("./experience/methods"));
52
54
  const inviteMethods = __importStar(require("./invites/methods"));
@@ -58,7 +60,7 @@ const patientMethods = __importStar(require("./patient/methods"));
58
60
  const patientSharingMethods = __importStar(require("./patient-sharing/methods"));
59
61
  const platformMethods = __importStar(require("./platform/methods"));
60
62
  const questionnaireResponseMethods = __importStar(require("./questionnaire-response/methods"));
61
- const errors_1 = require("./signals/errors");
63
+ const errors_2 = require("./signals/errors");
62
64
  const signalsMethods = __importStar(require("./signals/methods"));
63
65
  const slimMethods = __importStar(require("./slim/methods"));
64
66
  exports.MAX_CONDITIONAL_OBSERVATION_ENTRIES = 8;
@@ -77,6 +79,80 @@ const retryAfterMs = (response) => {
77
79
  const date = Date.parse(value);
78
80
  return Number.isFinite(date) ? Math.max(0, date - Date.now()) : undefined;
79
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
+ };
80
156
  const waitForRetry = (milliseconds, signal) => new Promise((resolve, reject) => {
81
157
  var _a;
82
158
  if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
@@ -122,6 +198,7 @@ const isNetworkError = (error) => {
122
198
  }
123
199
  return isNetworkError(error.cause);
124
200
  };
201
+ const isRetrySafePost = (pathname) => pathname === "/ai/fhir/search";
125
202
  class OvokClient extends core_1.MedplumClient {
126
203
  /**
127
204
  * @param config - The configuration for the client.
@@ -129,7 +206,10 @@ class OvokClient extends core_1.MedplumClient {
129
206
  */
130
207
  constructor(config) {
131
208
  var _a, _b, _c, _d, _e, _f;
132
- const storage = (_a = config.storage) !== null && _a !== void 0 ? _a : new core_1.ClientStorage(undefined, config.storagePrefix);
209
+ if (config.requestTimeoutMs !== undefined && (!Number.isFinite(config.requestTimeoutMs) || config.requestTimeoutMs <= 0)) {
210
+ throw new RangeError("requestTimeoutMs must be a positive finite number.");
211
+ }
212
+ const storage = (_a = config.storage) !== null && _a !== void 0 ? _a : new core_1.ClientStorage(new core_1.MemoryStorage(), config.storagePrefix);
133
213
  const fetchImplementation = (_b = config.fetch) !== null && _b !== void 0 ? _b : globalThis.fetch.bind(globalThis);
134
214
  const baseUrl = (_c = config.baseUrl) !== null && _c !== void 0 ? _c : "https://api.medplum.com/";
135
215
  const normalizedBaseUrl = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
@@ -138,6 +218,7 @@ class OvokClient extends core_1.MedplumClient {
138
218
  let clearActiveLogin = () => undefined;
139
219
  let terminalRefreshToken;
140
220
  let terminalRefreshError;
221
+ const refreshState = { unavailable: false };
141
222
  const isTokenRefresh = (url, options) => new URL(url, normalizedBaseUrl).href === tokenUrl &&
142
223
  (options === null || options === void 0 ? void 0 : options.method) === "POST" &&
143
224
  typeof options.body === "string" &&
@@ -151,7 +232,40 @@ class OvokClient extends core_1.MedplumClient {
151
232
  };
152
233
  const fetchOnce = async (url, options) => {
153
234
  var _a, _b;
154
- const response = await fetchImplementation(url, options);
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
+ }
155
269
  const requestUrl = new URL(url, normalizedBaseUrl);
156
270
  if (isTokenRefresh(url, options)) {
157
271
  if (response.status === 400) {
@@ -190,7 +304,9 @@ class OvokClient extends core_1.MedplumClient {
190
304
  else if (response.status === 401) {
191
305
  unauthenticatedReason = { type: "request-unauthorized", status: 401 };
192
306
  }
193
- return response;
307
+ return isTokenRefresh(url, options)
308
+ ? response
309
+ : withHttpErrorMetadata(response);
194
310
  };
195
311
  const fetchWithRefreshRetry = async (url, options) => {
196
312
  var _a, _b, _c, _d;
@@ -224,6 +340,9 @@ class OvokClient extends core_1.MedplumClient {
224
340
  const response = await fetchOnce(url, options);
225
341
  if (isTemporaryRefreshFailure(response.status)) {
226
342
  void ((_a = response.body) === null || _a === void 0 ? void 0 : _a.cancel().catch(() => undefined));
343
+ if (config.keepSessionOnRefreshUnavailable) {
344
+ refreshState.unavailable = true;
345
+ }
227
346
  throw refreshUnavailableError(response);
228
347
  }
229
348
  return response;
@@ -241,6 +360,11 @@ class OvokClient extends core_1.MedplumClient {
241
360
  if ((signal === null || signal === void 0 ? void 0 : signal.aborted) || !isNetworkError(error)) {
242
361
  throw error;
243
362
  }
363
+ if (config.keepSessionOnRefreshUnavailable) {
364
+ refreshState.unavailable = true;
365
+ lastNetworkError = error;
366
+ break;
367
+ }
244
368
  if (Date.now() >= expiresAt) {
245
369
  lastNetworkError = error;
246
370
  break;
@@ -255,6 +379,10 @@ class OvokClient extends core_1.MedplumClient {
255
379
  if (!isTemporaryRefreshFailure(lastResponse.status)) {
256
380
  return lastResponse;
257
381
  }
382
+ if (config.keepSessionOnRefreshUnavailable) {
383
+ refreshState.unavailable = true;
384
+ break;
385
+ }
258
386
  const remainingMs = expiresAt - Date.now();
259
387
  if (remainingMs <= 0) {
260
388
  break;
@@ -274,6 +402,13 @@ class OvokClient extends core_1.MedplumClient {
274
402
  type: "refresh-unavailable",
275
403
  ...(status === undefined ? {} : { status }),
276
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
+ }
277
412
  unauthenticatedReason = reason;
278
413
  markActiveAccountExpired();
279
414
  if (lastResponse) {
@@ -291,6 +426,9 @@ class OvokClient extends core_1.MedplumClient {
291
426
  storage,
292
427
  onUnauthenticated: () => {
293
428
  var _a;
429
+ if (refreshState.unavailable) {
430
+ return;
431
+ }
294
432
  const reason = unauthenticatedReason !== null && unauthenticatedReason !== void 0 ? unauthenticatedReason : {
295
433
  type: "request-unauthorized",
296
434
  status: 401,
@@ -303,6 +441,9 @@ class OvokClient extends core_1.MedplumClient {
303
441
  this.socialLoginClientId = "";
304
442
  clearActiveLogin = this.clearActiveLogin.bind(this);
305
443
  this.hasExplicitStorageAdapter = config.storage !== undefined;
444
+ this.clientOptions = config;
445
+ this.keepSessionOnRefreshUnavailable = config.keepSessionOnRefreshUnavailable === true;
446
+ this.refreshState = refreshState;
306
447
  this.socialLoginClientId = (_e = config.socialLoginClientId) !== null && _e !== void 0 ? _e : "";
307
448
  this.addEventListener("profileRefreshed", () => this.syncSavedAccountProfile());
308
449
  this.bindMethods(authMethods);
@@ -323,14 +464,53 @@ class OvokClient extends core_1.MedplumClient {
323
464
  this.bindMethods(patientMethods);
324
465
  this.bindMethods(patientSharingMethods);
325
466
  this.bindMethods(signalsMethods, (error) => {
326
- const mapped = (0, errors_1.mapSignalsError)((0, rate_limit_error_1.mapRateLimitError)(error), {
467
+ const mapped = (0, errors_2.mapSignalsError)((0, rate_limit_error_1.mapRateLimitError)(error), {
327
468
  signalsRequest: true,
328
469
  });
329
- return (0, errors_1.isSignalsError)(mapped) ? mapped : (0, ovok_error_1.mapOvokApiError)(mapped);
470
+ return (0, errors_2.isSignalsError)(mapped) ? mapped : (0, ovok_error_1.mapOvokApiError)(mapped);
330
471
  });
331
472
  this.clientStorage = storage;
332
473
  this.offlineQueueOptions = (_f = config.offlineQueue) !== null && _f !== void 0 ? _f : {};
333
474
  }
475
+ get(url, options) {
476
+ const request = super.get(url, options);
477
+ void request.catch(() => this.invalidateUrl(url));
478
+ return request;
479
+ }
480
+ async refreshIfExpired(gracePeriod) {
481
+ var _a;
482
+ const activeLogin = this.getActiveLogin();
483
+ const accessToken = this.getAccessToken();
484
+ const expiresAt = accessToken ? (0, core_1.tryGetJwtExpiration)(accessToken) : undefined;
485
+ try {
486
+ await super.refreshIfExpired(gracePeriod);
487
+ }
488
+ catch (error) {
489
+ const activeAccount = this.getSavedAccounts().find((account) => { var _a; return account.id === ((_a = activeLogin === null || activeLogin === void 0 ? void 0 : activeLogin.profile) === null || _a === void 0 ? void 0 : _a.reference); });
490
+ if (this.keepSessionOnRefreshUnavailable &&
491
+ this.refreshState.unavailable &&
492
+ activeLogin &&
493
+ accessToken &&
494
+ !(activeAccount === null || activeAccount === void 0 ? void 0 : activeAccount.expired) &&
495
+ expiresAt !== undefined &&
496
+ expiresAt > Date.now()) {
497
+ (_a = this.clientStorage) === null || _a === void 0 ? void 0 : _a.setObject("activeLogin", activeLogin);
498
+ this.setAccessToken(accessToken, activeLogin.refreshToken);
499
+ return;
500
+ }
501
+ throw error;
502
+ }
503
+ finally {
504
+ this.refreshState.unavailable = false;
505
+ }
506
+ }
507
+ post(url, body, contentType, options) {
508
+ const pathname = new URL(url.toString(), this.getBaseUrl()).pathname;
509
+ if (!isRetrySafePost(pathname)) {
510
+ options = { ...options, maxRetries: 0, maxRetryTime: 0 };
511
+ }
512
+ return super.post(url, body, contentType, options);
513
+ }
334
514
  /** Lists saved accounts without exposing their access or refresh tokens. */
335
515
  getSavedAccounts() {
336
516
  var _a, _b;
@@ -355,10 +535,10 @@ class OvokClient extends core_1.MedplumClient {
355
535
  var _a, _b;
356
536
  const login = this.getLogin(accountId);
357
537
  if (!login) {
358
- throw new Error(`No saved account found for ${accountId}.`);
538
+ throw new errors_1.OvokAccountError("account_not_found");
359
539
  }
360
540
  if (this.getExpiredAccountIds().includes(accountId)) {
361
- throw new Error("This account's session has expired. Sign in again.");
541
+ throw new errors_1.OvokAccountError("account_expired");
362
542
  }
363
543
  const previousLogin = this.getActiveLogin();
364
544
  try {
@@ -382,36 +562,29 @@ class OvokClient extends core_1.MedplumClient {
382
562
  * Accounts with an expired server session are removed locally; network and service
383
563
  * failures retain the account so it can be removed after connectivity returns.
384
564
  */
385
- async removeAccount(accountId) {
565
+ async removeAccount(accountId, requestOptions) {
386
566
  var _a;
387
567
  const login = this.getLogin(accountId);
388
568
  if (!login) {
389
- throw new Error(`No saved account found for ${accountId}.`);
569
+ throw new errors_1.OvokAccountError("account_not_found");
390
570
  }
391
571
  const previousLogin = this.getActiveLogin();
392
572
  const previousAccountId = (_a = previousLogin === null || previousLogin === void 0 ? void 0 : previousLogin.profile) === null || _a === void 0 ? void 0 : _a.reference;
573
+ const wasActive = previousAccountId === accountId;
574
+ const sessionClient = this.createAccountSessionClient(login);
393
575
  try {
394
- await this.setActiveLogin(login);
395
- await this.delete("auth/session/current");
576
+ await sessionClient.delete("auth/session/current", ...(requestOptions === undefined ? [] : [requestOptions]));
396
577
  }
397
578
  catch (error) {
398
- if (this.getExpiredAccountIds().includes(accountId)) {
399
- this.removeSavedAccount(accountId);
400
- if (previousLogin && previousAccountId && previousAccountId !== accountId) {
401
- await this.restoreActiveLogin(previousLogin);
402
- }
403
- else {
404
- this.clearActiveLogin();
405
- }
406
- return;
407
- }
408
- if (previousLogin && previousAccountId !== accountId) {
409
- await this.restoreActiveLogin(previousLogin);
579
+ if (!sessionClient.getSavedAccounts().some((account) => account.id === accountId && account.expired)) {
580
+ throw error;
410
581
  }
411
- throw error;
412
582
  }
413
- this.clearActiveLogin();
414
583
  this.removeSavedAccount(accountId);
584
+ if (!wasActive) {
585
+ return;
586
+ }
587
+ this.clearActiveLogin();
415
588
  const availableAccountIds = new Set(this.getSavedAccounts().filter((account) => !account.expired).map((account) => account.id));
416
589
  let nextLogin;
417
590
  if (previousAccountId && previousAccountId !== accountId) {
@@ -447,34 +620,34 @@ class OvokClient extends core_1.MedplumClient {
447
620
  }
448
621
  }
449
622
  /** Saves a measurement immediately, or durably queues it when the opt-in queue is enabled. */
450
- async saveMeasurement(params) {
623
+ async saveMeasurement(params, requestOptions) {
451
624
  const observations = await this.generateObservationBodiesByMeasurement(params);
452
- return this.saveObservations(observations);
625
+ return this.saveObservations(observations, ...(requestOptions === undefined ? [] : [requestOptions]));
453
626
  }
454
627
  /** Reads Ovok's composed FHIR and public-route capability statement. */
455
- async getCapabilityStatement(version = "R4") {
456
- return this.get(`/fhir/${version}/metadata`);
628
+ async getCapabilityStatement(version = "R4", requestOptions) {
629
+ return this.get(`/fhir/${version}/metadata`, ...(requestOptions === undefined ? [] : [requestOptions]));
457
630
  }
458
631
  /** Returns the non-FHIR public routes published by the backend statement. */
459
- async getPublicApiEndpoints(version = "R4") {
460
- return (0, capability_1.getOvokPublicApiEndpoints)(await this.getCapabilityStatement(version));
632
+ async getPublicApiEndpoints(version = "R4", requestOptions) {
633
+ return (0, capability_1.getOvokPublicApiEndpoints)(await this.getCapabilityStatement(version, ...(requestOptions === undefined ? [] : [requestOptions])));
461
634
  }
462
635
  /** Saves observations with conditional creates so a retry cannot create a duplicate. */
463
- async saveObservations(observations) {
636
+ async saveObservations(observations, requestOptions) {
464
637
  const linkedObservations = (0, observation_bundle_utils_1.linkObservationMembers)(observations);
465
638
  const queue = this.getOfflineMeasurementQueue();
466
639
  if (queue) {
467
- const { id, flush } = await queue.enqueueAndFlush(linkedObservations);
640
+ const { id, flush } = await queue.enqueueAndFlush(linkedObservations, ...(requestOptions === undefined ? [] : [requestOptions]));
468
641
  const response = flush.responses[id];
469
642
  return response
470
643
  ? { status: "saved", response }
471
644
  : { status: "queued", queueId: id };
472
645
  }
473
- const response = await this.executeConditionalObservationCreates(linkedObservations);
646
+ const response = await this.executeConditionalObservationCreates(linkedObservations, requestOptions);
474
647
  return { status: "saved", response };
475
648
  }
476
649
  /** Flushes measurements queued while offline. Call this on connectivity/app-resume events. */
477
- async flushOfflineMeasurementQueue(options = {}) {
650
+ async flushOfflineMeasurementQueue(options = {}, requestOptions) {
478
651
  const queue = this.getOfflineMeasurementQueue();
479
652
  if (!queue) {
480
653
  return {
@@ -483,9 +656,10 @@ class OvokClient extends core_1.MedplumClient {
483
656
  queued: 0,
484
657
  exhausted: 0,
485
658
  responses: {},
659
+ failures: {},
486
660
  };
487
661
  }
488
- return queue.flush(options);
662
+ return queue.flush(options, ...(requestOptions === undefined ? [] : [requestOptions]));
489
663
  }
490
664
  getOfflineMeasurementQueue() {
491
665
  var _a;
@@ -495,7 +669,7 @@ class OvokClient extends core_1.MedplumClient {
495
669
  if (!this.clientStorage || !this.hasExplicitStorageAdapter) {
496
670
  throw new Error("Offline measurement queue requires a durable client storage adapter.");
497
671
  }
498
- (_a = this.offlineMeasurementQueue) !== null && _a !== void 0 ? _a : (this.offlineMeasurementQueue = new offline_1.OfflineMeasurementQueue(this.clientStorage, (bundle) => this.executeObservationBundle(bundle), {
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])), {
499
673
  ...this.offlineQueueOptions,
500
674
  getOwnerKey: () => {
501
675
  var _a, _b, _c;
@@ -521,8 +695,13 @@ class OvokClient extends core_1.MedplumClient {
521
695
  clear() {
522
696
  var _a, _b, _c;
523
697
  const storage = this.clientStorage;
524
- const logins = this.getLogins();
525
698
  const activeAccountId = (_b = (_a = this.getActiveLogin()) === null || _a === void 0 ? void 0 : _a.profile) === null || _b === void 0 ? void 0 : _b.reference;
699
+ const logins = this.getLogins().map((login) => {
700
+ var _a;
701
+ return ((_a = login.profile) === null || _a === void 0 ? void 0 : _a.reference) === activeAccountId
702
+ ? { ...login, accessToken: "", refreshToken: "" }
703
+ : login;
704
+ });
526
705
  const expiredAccountIds = new Set(this.getExpiredAccountIds());
527
706
  if (activeAccountId) {
528
707
  expiredAccountIds.add(activeAccountId);
@@ -547,6 +726,19 @@ class OvokClient extends core_1.MedplumClient {
547
726
  var _a, _b;
548
727
  return ((_b = (_a = this.clientStorage) === null || _a === void 0 ? void 0 : _a.getObject(account_storage_1.EXPIRED_ACCOUNTS_STORAGE_KEY)) !== null && _b !== void 0 ? _b : []);
549
728
  }
729
+ createAccountSessionClient(login) {
730
+ const storage = new core_1.ClientStorage(new core_1.MemoryStorage(), this.clientOptions.storagePrefix);
731
+ storage.setObject("activeLogin", login);
732
+ storage.setObject(account_storage_1.LOGINS_STORAGE_KEY, [login]);
733
+ const client = new OvokClient({
734
+ ...this.clientOptions,
735
+ storage,
736
+ offlineQueue: undefined,
737
+ onUnauthenticated: undefined,
738
+ });
739
+ client.setAccessToken(login.accessToken, login.refreshToken);
740
+ return client;
741
+ }
550
742
  setAccountExpired(accountId, expired) {
551
743
  if (this.clientStorage) {
552
744
  (0, account_storage_1.setStoredAccountExpired)(this.clientStorage, accountId, expired);
@@ -580,16 +772,16 @@ class OvokClient extends core_1.MedplumClient {
580
772
  // Keep the saved login available for a later retry if restoration is offline.
581
773
  }
582
774
  }
583
- async executeConditionalObservationCreates(observations) {
775
+ async executeConditionalObservationCreates(observations, requestOptions) {
584
776
  const entries = observations.map(offline_1.entryForObservation);
585
777
  return this.executeObservationBundle({
586
778
  resourceType: "Bundle",
587
779
  type: "transaction",
588
780
  entry: entries,
589
- });
781
+ }, ...(requestOptions === undefined ? [] : [requestOptions]));
590
782
  }
591
- async executeObservationBundle(bundle) {
592
- const response = await (0, observation_bundle_utils_1.executeObservationBundle)(bundle, (request) => this.executeBatch(request));
783
+ async executeObservationBundle(bundle, requestOptions) {
784
+ const response = await (0, observation_bundle_utils_1.executeObservationBundle)(bundle, (request) => this.executeBatch(request, ...(requestOptions === undefined ? [] : [requestOptions])));
593
785
  this.invalidateSearches("Observation");
594
786
  return response;
595
787
  }
@@ -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>;