@medplum/core 1.0.6 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/client.d.ts +38 -6
- package/dist/cjs/index.cjs +383 -746
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.min.cjs +1 -1
- package/dist/cjs/types.d.ts +6 -0
- package/dist/cjs/utils.d.ts +8 -1
- package/dist/esm/base-schema.json.mjs +219 -690
- package/dist/esm/base-schema.json.mjs.map +1 -1
- package/dist/esm/client.d.ts +38 -6
- package/dist/esm/client.mjs +136 -56
- package/dist/esm/client.mjs.map +1 -1
- package/dist/esm/index.min.mjs +1 -1
- package/dist/esm/index.mjs +2 -2
- package/dist/esm/node_modules/tslib/tslib.es6.mjs.map +1 -1
- package/dist/esm/types.d.ts +6 -0
- package/dist/esm/types.mjs +12 -1
- package/dist/esm/types.mjs.map +1 -1
- package/dist/esm/utils.d.ts +8 -1
- package/dist/esm/utils.mjs +16 -1
- package/dist/esm/utils.mjs.map +1 -1
- package/package.json +1 -1
- package/rollup.config.mjs +0 -103
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-schema.json.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"base-schema.json.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/esm/client.d.ts
CHANGED
|
@@ -71,6 +71,14 @@ export interface MedplumClientOptions {
|
|
|
71
71
|
* See: https://developer.mozilla.org/en-US/docs/Web/API/Request/cache
|
|
72
72
|
*/
|
|
73
73
|
cacheTime?: number;
|
|
74
|
+
/**
|
|
75
|
+
* The length of time in milliseconds to delay requests for auto batching.
|
|
76
|
+
*
|
|
77
|
+
* Auto batching attempts to group multiple requests together into a single batch request.
|
|
78
|
+
*
|
|
79
|
+
* Default value is 0, which disables auto batching.
|
|
80
|
+
*/
|
|
81
|
+
autoBatchTime?: number;
|
|
74
82
|
/**
|
|
75
83
|
* Fetch implementation.
|
|
76
84
|
*
|
|
@@ -146,6 +154,7 @@ export interface BaseLoginRequest {
|
|
|
146
154
|
readonly codeChallengeMethod?: string;
|
|
147
155
|
readonly googleClientId?: string;
|
|
148
156
|
readonly launch?: string;
|
|
157
|
+
readonly redirectUri?: string;
|
|
149
158
|
}
|
|
150
159
|
export interface EmailPasswordLoginRequest extends BaseLoginRequest {
|
|
151
160
|
readonly email: string;
|
|
@@ -338,6 +347,12 @@ export declare class MedplumClient extends EventTarget {
|
|
|
338
347
|
* @category Authentication
|
|
339
348
|
*/
|
|
340
349
|
clear(): void;
|
|
350
|
+
/**
|
|
351
|
+
* Clears the active login from local storage.
|
|
352
|
+
* Does not clear all local storage (such as other logins).
|
|
353
|
+
* @category Authentication
|
|
354
|
+
*/
|
|
355
|
+
clearActiveLogin(): void;
|
|
341
356
|
/**
|
|
342
357
|
* Invalidates any cached values or cached requests for the given URL.
|
|
343
358
|
* @category Caching
|
|
@@ -467,10 +482,15 @@ export declare class MedplumClient extends EventTarget {
|
|
|
467
482
|
* @returns Promise to the authentication response.
|
|
468
483
|
*/
|
|
469
484
|
startGoogleLogin(loginRequest: GoogleLoginRequest): Promise<LoginAuthenticationResponse>;
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
485
|
+
/**
|
|
486
|
+
* Returns the PKCE code challenge and method.
|
|
487
|
+
* If the login request already includes a code challenge, it is returned.
|
|
488
|
+
* Otherwise, a new PKCE code challenge is generated.
|
|
489
|
+
* @category Authentication
|
|
490
|
+
* @param loginRequest The original login request.
|
|
491
|
+
* @returns The PKCE code challenge and method.
|
|
492
|
+
*/
|
|
493
|
+
ensureCodeChallenge<T extends BaseLoginRequest>(loginRequest: T): Promise<T>;
|
|
474
494
|
/**
|
|
475
495
|
* Signs out locally.
|
|
476
496
|
* Does not invalidate tokens with the server.
|
|
@@ -482,14 +502,23 @@ export declare class MedplumClient extends EventTarget {
|
|
|
482
502
|
* Returns true if the user is signed in.
|
|
483
503
|
* This may result in navigating away to the sign in page.
|
|
484
504
|
* @category Authentication
|
|
505
|
+
* @param loginParams Optional login parameters.
|
|
485
506
|
*/
|
|
486
|
-
signInWithRedirect(): Promise<ProfileResource | void>;
|
|
507
|
+
signInWithRedirect(loginParams?: Partial<BaseLoginRequest>): Promise<ProfileResource | void>;
|
|
487
508
|
/**
|
|
488
509
|
* Tries to sign out the user.
|
|
489
510
|
* See: https://docs.aws.amazon.com/cognito/latest/developerguide/logout-endpoint.html
|
|
490
511
|
* @category Authentication
|
|
491
512
|
*/
|
|
492
513
|
signOutWithRedirect(): void;
|
|
514
|
+
/**
|
|
515
|
+
* Initiates sign in with an external identity provider.
|
|
516
|
+
* @param authorizeUrl The external authorization URL.
|
|
517
|
+
* @param clientId The external client ID.
|
|
518
|
+
* @param redirectUri The external identity provider redirect URI.
|
|
519
|
+
* @param baseLogin The Medplum login request.
|
|
520
|
+
*/
|
|
521
|
+
signInWithExternalAuth(authorizeUrl: string, clientId: string, redirectUri: string, baseLogin: BaseLoginRequest): Promise<void>;
|
|
493
522
|
/**
|
|
494
523
|
* Builds a FHIR URL from a collection of URL path components.
|
|
495
524
|
* For example, `buildUrl('/Patient', '123')` returns `fhir/R4/Patient/123`.
|
|
@@ -1154,7 +1183,10 @@ export declare class MedplumClient extends EventTarget {
|
|
|
1154
1183
|
* Starts a new PKCE flow.
|
|
1155
1184
|
* These PKCE values are stateful, and must survive redirects and page refreshes.
|
|
1156
1185
|
*/
|
|
1157
|
-
startPkce(): Promise<
|
|
1186
|
+
startPkce(): Promise<{
|
|
1187
|
+
codeChallengeMethod: string;
|
|
1188
|
+
codeChallenge: string;
|
|
1189
|
+
}>;
|
|
1158
1190
|
/**
|
|
1159
1191
|
* Processes an OAuth authorization code.
|
|
1160
1192
|
* See: https://openid.net/specs/openid-connect-core-1_0.html#TokenRequest
|
package/dist/esm/client.mjs
CHANGED
|
@@ -10,8 +10,8 @@ import { createReference, arrayBufferToBase64 } from './utils.mjs';
|
|
|
10
10
|
|
|
11
11
|
// PKCE auth based on:
|
|
12
12
|
// https://aws.amazon.com/blogs/security/how-to-add-authentication-single-page-web-application-with-amazon-cognito-oauth2-implementation/
|
|
13
|
-
var _MedplumClient_instances, _MedplumClient_fetch, _MedplumClient_createPdf, _MedplumClient_storage, _MedplumClient_requestCache, _MedplumClient_cacheTime, _MedplumClient_baseUrl, _MedplumClient_authorizeUrl, _MedplumClient_tokenUrl, _MedplumClient_logoutUrl, _MedplumClient_onUnauthenticated, _MedplumClient_clientId, _MedplumClient_clientSecret, _MedplumClient_accessToken, _MedplumClient_refreshToken, _MedplumClient_refreshPromise, _MedplumClient_profilePromise, _MedplumClient_profile, _MedplumClient_config, _MedplumClient_addLogin, _MedplumClient_refreshProfile, _MedplumClient_getCacheEntry, _MedplumClient_setCacheEntry, _MedplumClient_request, _MedplumClient_addFetchOptionsDefaults, _MedplumClient_setRequestContentType, _MedplumClient_setRequestBody, _MedplumClient_handleUnauthenticated, _MedplumClient_requestAuthorization, _MedplumClient_refresh, _MedplumClient_fetchTokens, _MedplumClient_verifyTokens, _MedplumClient_setupStorageListener;
|
|
14
|
-
const MEDPLUM_VERSION = "
|
|
13
|
+
var _MedplumClient_instances, _MedplumClient_fetch, _MedplumClient_createPdf, _MedplumClient_storage, _MedplumClient_requestCache, _MedplumClient_cacheTime, _MedplumClient_baseUrl, _MedplumClient_fhirBaseUrl, _MedplumClient_authorizeUrl, _MedplumClient_tokenUrl, _MedplumClient_logoutUrl, _MedplumClient_onUnauthenticated, _MedplumClient_autoBatchTime, _MedplumClient_autoBatchQueue, _MedplumClient_clientId, _MedplumClient_clientSecret, _MedplumClient_autoBatchTimerId, _MedplumClient_accessToken, _MedplumClient_refreshToken, _MedplumClient_refreshPromise, _MedplumClient_profilePromise, _MedplumClient_profile, _MedplumClient_config, _MedplumClient_addLogin, _MedplumClient_refreshProfile, _MedplumClient_getCacheEntry, _MedplumClient_setCacheEntry, _MedplumClient_request, _MedplumClient_executeAutoBatch, _MedplumClient_addFetchOptionsDefaults, _MedplumClient_setRequestContentType, _MedplumClient_setRequestBody, _MedplumClient_handleUnauthenticated, _MedplumClient_requestAuthorization, _MedplumClient_refresh, _MedplumClient_fetchTokens, _MedplumClient_verifyTokens, _MedplumClient_setupStorageListener;
|
|
14
|
+
const MEDPLUM_VERSION = "2.0.1-89a5b1c5";
|
|
15
15
|
const DEFAULT_BASE_URL = 'https://api.medplum.com/';
|
|
16
16
|
const DEFAULT_RESOURCE_CACHE_SIZE = 1000;
|
|
17
17
|
const DEFAULT_CACHE_TIME = 60000; // 60 seconds
|
|
@@ -80,12 +80,16 @@ class MedplumClient extends EventTarget {
|
|
|
80
80
|
_MedplumClient_requestCache.set(this, void 0);
|
|
81
81
|
_MedplumClient_cacheTime.set(this, void 0);
|
|
82
82
|
_MedplumClient_baseUrl.set(this, void 0);
|
|
83
|
+
_MedplumClient_fhirBaseUrl.set(this, void 0);
|
|
83
84
|
_MedplumClient_authorizeUrl.set(this, void 0);
|
|
84
85
|
_MedplumClient_tokenUrl.set(this, void 0);
|
|
85
86
|
_MedplumClient_logoutUrl.set(this, void 0);
|
|
86
87
|
_MedplumClient_onUnauthenticated.set(this, void 0);
|
|
88
|
+
_MedplumClient_autoBatchTime.set(this, void 0);
|
|
89
|
+
_MedplumClient_autoBatchQueue.set(this, void 0);
|
|
87
90
|
_MedplumClient_clientId.set(this, void 0);
|
|
88
91
|
_MedplumClient_clientSecret.set(this, void 0);
|
|
92
|
+
_MedplumClient_autoBatchTimerId.set(this, void 0);
|
|
89
93
|
_MedplumClient_accessToken.set(this, void 0);
|
|
90
94
|
_MedplumClient_refreshToken.set(this, void 0);
|
|
91
95
|
_MedplumClient_refreshPromise.set(this, void 0);
|
|
@@ -103,11 +107,14 @@ class MedplumClient extends EventTarget {
|
|
|
103
107
|
__classPrivateFieldSet(this, _MedplumClient_requestCache, new LRUCache(options?.resourceCacheSize ?? DEFAULT_RESOURCE_CACHE_SIZE), "f");
|
|
104
108
|
__classPrivateFieldSet(this, _MedplumClient_cacheTime, options?.cacheTime ?? DEFAULT_CACHE_TIME, "f");
|
|
105
109
|
__classPrivateFieldSet(this, _MedplumClient_baseUrl, ensureTrailingSlash(options?.baseUrl) || DEFAULT_BASE_URL, "f");
|
|
110
|
+
__classPrivateFieldSet(this, _MedplumClient_fhirBaseUrl, __classPrivateFieldGet(this, _MedplumClient_baseUrl, "f") + 'fhir/R4/', "f");
|
|
106
111
|
__classPrivateFieldSet(this, _MedplumClient_clientId, options?.clientId || '', "f");
|
|
107
112
|
__classPrivateFieldSet(this, _MedplumClient_authorizeUrl, options?.authorizeUrl || __classPrivateFieldGet(this, _MedplumClient_baseUrl, "f") + 'oauth2/authorize', "f");
|
|
108
113
|
__classPrivateFieldSet(this, _MedplumClient_tokenUrl, options?.tokenUrl || __classPrivateFieldGet(this, _MedplumClient_baseUrl, "f") + 'oauth2/token', "f");
|
|
109
114
|
__classPrivateFieldSet(this, _MedplumClient_logoutUrl, options?.logoutUrl || __classPrivateFieldGet(this, _MedplumClient_baseUrl, "f") + 'oauth2/logout', "f");
|
|
110
115
|
__classPrivateFieldSet(this, _MedplumClient_onUnauthenticated, options?.onUnauthenticated, "f");
|
|
116
|
+
__classPrivateFieldSet(this, _MedplumClient_autoBatchTime, options?.autoBatchTime ?? 0, "f");
|
|
117
|
+
__classPrivateFieldSet(this, _MedplumClient_autoBatchQueue, [], "f");
|
|
111
118
|
const activeLogin = this.getActiveLogin();
|
|
112
119
|
if (activeLogin) {
|
|
113
120
|
__classPrivateFieldSet(this, _MedplumClient_accessToken, activeLogin.accessToken, "f");
|
|
@@ -132,6 +139,15 @@ class MedplumClient extends EventTarget {
|
|
|
132
139
|
*/
|
|
133
140
|
clear() {
|
|
134
141
|
__classPrivateFieldGet(this, _MedplumClient_storage, "f").clear();
|
|
142
|
+
this.clearActiveLogin();
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Clears the active login from local storage.
|
|
146
|
+
* Does not clear all local storage (such as other logins).
|
|
147
|
+
* @category Authentication
|
|
148
|
+
*/
|
|
149
|
+
clearActiveLogin() {
|
|
150
|
+
__classPrivateFieldGet(this, _MedplumClient_storage, "f").setString('activeLogin', undefined);
|
|
135
151
|
__classPrivateFieldGet(this, _MedplumClient_requestCache, "f").clear();
|
|
136
152
|
__classPrivateFieldSet(this, _MedplumClient_accessToken, undefined, "f");
|
|
137
153
|
__classPrivateFieldSet(this, _MedplumClient_refreshToken, undefined, "f");
|
|
@@ -179,9 +195,27 @@ class MedplumClient extends EventTarget {
|
|
|
179
195
|
if (cached) {
|
|
180
196
|
return cached.value;
|
|
181
197
|
}
|
|
182
|
-
|
|
183
|
-
__classPrivateFieldGet(this,
|
|
184
|
-
|
|
198
|
+
let promise;
|
|
199
|
+
if (url.startsWith(__classPrivateFieldGet(this, _MedplumClient_fhirBaseUrl, "f")) && __classPrivateFieldGet(this, _MedplumClient_autoBatchTime, "f") > 0) {
|
|
200
|
+
promise = new Promise((resolve, reject) => {
|
|
201
|
+
__classPrivateFieldGet(this, _MedplumClient_autoBatchQueue, "f").push({
|
|
202
|
+
method: 'GET',
|
|
203
|
+
url: url.replace(__classPrivateFieldGet(this, _MedplumClient_fhirBaseUrl, "f"), ''),
|
|
204
|
+
options,
|
|
205
|
+
resolve,
|
|
206
|
+
reject,
|
|
207
|
+
});
|
|
208
|
+
if (!__classPrivateFieldGet(this, _MedplumClient_autoBatchTimerId, "f")) {
|
|
209
|
+
__classPrivateFieldSet(this, _MedplumClient_autoBatchTimerId, setTimeout(() => __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_executeAutoBatch).call(this), __classPrivateFieldGet(this, _MedplumClient_autoBatchTime, "f")), "f");
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
promise = __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_request).call(this, 'GET', url, options);
|
|
215
|
+
}
|
|
216
|
+
const readablePromise = new ReadablePromise(promise);
|
|
217
|
+
__classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_setCacheEntry).call(this, url, readablePromise);
|
|
218
|
+
return readablePromise;
|
|
185
219
|
}
|
|
186
220
|
/**
|
|
187
221
|
* Makes an HTTP POST request to the specified URL.
|
|
@@ -283,11 +317,11 @@ class MedplumClient extends EventTarget {
|
|
|
283
317
|
* @returns Promise to the authentication response.
|
|
284
318
|
*/
|
|
285
319
|
async startNewUser(newUserRequest) {
|
|
286
|
-
await this.startPkce();
|
|
320
|
+
const { codeChallengeMethod, codeChallenge } = await this.startPkce();
|
|
287
321
|
return this.post('auth/newuser', {
|
|
288
322
|
...newUserRequest,
|
|
289
|
-
codeChallengeMethod
|
|
290
|
-
codeChallenge
|
|
323
|
+
codeChallengeMethod,
|
|
324
|
+
codeChallenge,
|
|
291
325
|
});
|
|
292
326
|
}
|
|
293
327
|
/**
|
|
@@ -319,13 +353,10 @@ class MedplumClient extends EventTarget {
|
|
|
319
353
|
* @returns Promise to the authentication response.
|
|
320
354
|
*/
|
|
321
355
|
async startLogin(loginRequest) {
|
|
322
|
-
const { codeChallenge, codeChallengeMethod } = this.getCodeChallenge(loginRequest);
|
|
323
356
|
return this.post('auth/login', {
|
|
324
|
-
...loginRequest,
|
|
357
|
+
...(await this.ensureCodeChallenge(loginRequest)),
|
|
325
358
|
clientId: loginRequest.clientId ?? __classPrivateFieldGet(this, _MedplumClient_clientId, "f"),
|
|
326
359
|
scope: loginRequest.scope,
|
|
327
|
-
codeChallengeMethod,
|
|
328
|
-
codeChallenge,
|
|
329
360
|
});
|
|
330
361
|
}
|
|
331
362
|
/**
|
|
@@ -337,30 +368,25 @@ class MedplumClient extends EventTarget {
|
|
|
337
368
|
* @returns Promise to the authentication response.
|
|
338
369
|
*/
|
|
339
370
|
async startGoogleLogin(loginRequest) {
|
|
340
|
-
const { codeChallenge, codeChallengeMethod } = this.getCodeChallenge(loginRequest);
|
|
341
371
|
return this.post('auth/google', {
|
|
342
|
-
...loginRequest,
|
|
372
|
+
...(await this.ensureCodeChallenge(loginRequest)),
|
|
343
373
|
clientId: loginRequest.clientId ?? __classPrivateFieldGet(this, _MedplumClient_clientId, "f"),
|
|
344
374
|
scope: loginRequest.scope,
|
|
345
|
-
codeChallengeMethod,
|
|
346
|
-
codeChallenge,
|
|
347
375
|
});
|
|
348
376
|
}
|
|
349
|
-
|
|
377
|
+
/**
|
|
378
|
+
* Returns the PKCE code challenge and method.
|
|
379
|
+
* If the login request already includes a code challenge, it is returned.
|
|
380
|
+
* Otherwise, a new PKCE code challenge is generated.
|
|
381
|
+
* @category Authentication
|
|
382
|
+
* @param loginRequest The original login request.
|
|
383
|
+
* @returns The PKCE code challenge and method.
|
|
384
|
+
*/
|
|
385
|
+
async ensureCodeChallenge(loginRequest) {
|
|
350
386
|
if (loginRequest.codeChallenge) {
|
|
351
|
-
return
|
|
352
|
-
codeChallenge: loginRequest.codeChallenge,
|
|
353
|
-
codeChallengeMethod: loginRequest.codeChallengeMethod,
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
|
-
const codeChallenge = sessionStorage.getItem('codeChallenge');
|
|
357
|
-
if (codeChallenge) {
|
|
358
|
-
return {
|
|
359
|
-
codeChallenge,
|
|
360
|
-
codeChallengeMethod: 'S256',
|
|
361
|
-
};
|
|
387
|
+
return loginRequest;
|
|
362
388
|
}
|
|
363
|
-
return {};
|
|
389
|
+
return { ...loginRequest, ...(await this.startPkce()) };
|
|
364
390
|
}
|
|
365
391
|
/**
|
|
366
392
|
* Signs out locally.
|
|
@@ -375,12 +401,13 @@ class MedplumClient extends EventTarget {
|
|
|
375
401
|
* Returns true if the user is signed in.
|
|
376
402
|
* This may result in navigating away to the sign in page.
|
|
377
403
|
* @category Authentication
|
|
404
|
+
* @param loginParams Optional login parameters.
|
|
378
405
|
*/
|
|
379
|
-
async signInWithRedirect() {
|
|
406
|
+
async signInWithRedirect(loginParams) {
|
|
380
407
|
const urlParams = new URLSearchParams(window.location.search);
|
|
381
408
|
const code = urlParams.get('code');
|
|
382
409
|
if (!code) {
|
|
383
|
-
await __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_requestAuthorization).call(this);
|
|
410
|
+
await __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_requestAuthorization).call(this, loginParams);
|
|
384
411
|
return undefined;
|
|
385
412
|
}
|
|
386
413
|
else {
|
|
@@ -395,6 +422,23 @@ class MedplumClient extends EventTarget {
|
|
|
395
422
|
signOutWithRedirect() {
|
|
396
423
|
window.location.assign(__classPrivateFieldGet(this, _MedplumClient_logoutUrl, "f"));
|
|
397
424
|
}
|
|
425
|
+
/**
|
|
426
|
+
* Initiates sign in with an external identity provider.
|
|
427
|
+
* @param authorizeUrl The external authorization URL.
|
|
428
|
+
* @param clientId The external client ID.
|
|
429
|
+
* @param redirectUri The external identity provider redirect URI.
|
|
430
|
+
* @param baseLogin The Medplum login request.
|
|
431
|
+
*/
|
|
432
|
+
async signInWithExternalAuth(authorizeUrl, clientId, redirectUri, baseLogin) {
|
|
433
|
+
const loginRequest = await this.ensureCodeChallenge(baseLogin);
|
|
434
|
+
const url = new URL(authorizeUrl);
|
|
435
|
+
url.searchParams.set('response_type', 'code');
|
|
436
|
+
url.searchParams.set('client_id', clientId);
|
|
437
|
+
url.searchParams.set('redirect_uri', redirectUri);
|
|
438
|
+
url.searchParams.set('scope', 'openid profile email');
|
|
439
|
+
url.searchParams.set('state', JSON.stringify(loginRequest));
|
|
440
|
+
window.location.assign(url.toString());
|
|
441
|
+
}
|
|
398
442
|
/**
|
|
399
443
|
* Builds a FHIR URL from a collection of URL path components.
|
|
400
444
|
* For example, `buildUrl('/Patient', '123')` returns `fhir/R4/Patient/123`.
|
|
@@ -403,7 +447,7 @@ class MedplumClient extends EventTarget {
|
|
|
403
447
|
* @returns The well-formed FHIR URL.
|
|
404
448
|
*/
|
|
405
449
|
fhirUrl(...path) {
|
|
406
|
-
return new URL(__classPrivateFieldGet(this,
|
|
450
|
+
return new URL(__classPrivateFieldGet(this, _MedplumClient_fhirBaseUrl, "f") + path.join('/'));
|
|
407
451
|
}
|
|
408
452
|
/**
|
|
409
453
|
* Builds a FHIR search URL from a search query or structured query object.
|
|
@@ -1233,13 +1277,11 @@ class MedplumClient extends EventTarget {
|
|
|
1233
1277
|
* @category Authentication
|
|
1234
1278
|
*/
|
|
1235
1279
|
async setActiveLogin(login) {
|
|
1280
|
+
this.clearActiveLogin();
|
|
1236
1281
|
__classPrivateFieldSet(this, _MedplumClient_accessToken, login.accessToken, "f");
|
|
1237
1282
|
__classPrivateFieldSet(this, _MedplumClient_refreshToken, login.refreshToken, "f");
|
|
1238
|
-
__classPrivateFieldSet(this, _MedplumClient_profile, undefined, "f");
|
|
1239
|
-
__classPrivateFieldSet(this, _MedplumClient_config, undefined, "f");
|
|
1240
1283
|
__classPrivateFieldGet(this, _MedplumClient_storage, "f").setObject('activeLogin', login);
|
|
1241
1284
|
__classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_addLogin).call(this, login);
|
|
1242
|
-
__classPrivateFieldGet(this, _MedplumClient_requestCache, "f").clear();
|
|
1243
1285
|
__classPrivateFieldSet(this, _MedplumClient_refreshPromise, undefined, "f");
|
|
1244
1286
|
await __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_refreshProfile).call(this);
|
|
1245
1287
|
}
|
|
@@ -1318,6 +1360,7 @@ class MedplumClient extends EventTarget {
|
|
|
1318
1360
|
const arrayHash = await encryptSHA256(codeVerifier);
|
|
1319
1361
|
const codeChallenge = arrayBufferToBase64(arrayHash).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
1320
1362
|
sessionStorage.setItem('codeChallenge', codeChallenge);
|
|
1363
|
+
return { codeChallengeMethod: 'S256', codeChallenge };
|
|
1321
1364
|
}
|
|
1322
1365
|
/**
|
|
1323
1366
|
* Processes an OAuth authorization code.
|
|
@@ -1354,7 +1397,7 @@ class MedplumClient extends EventTarget {
|
|
|
1354
1397
|
return __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_fetchTokens).call(this, formBody);
|
|
1355
1398
|
}
|
|
1356
1399
|
}
|
|
1357
|
-
_MedplumClient_fetch = new WeakMap(), _MedplumClient_createPdf = new WeakMap(), _MedplumClient_storage = new WeakMap(), _MedplumClient_requestCache = new WeakMap(), _MedplumClient_cacheTime = new WeakMap(), _MedplumClient_baseUrl = new WeakMap(), _MedplumClient_authorizeUrl = new WeakMap(), _MedplumClient_tokenUrl = new WeakMap(), _MedplumClient_logoutUrl = new WeakMap(), _MedplumClient_onUnauthenticated = new WeakMap(), _MedplumClient_clientId = new WeakMap(), _MedplumClient_clientSecret = new WeakMap(), _MedplumClient_accessToken = new WeakMap(), _MedplumClient_refreshToken = new WeakMap(), _MedplumClient_refreshPromise = new WeakMap(), _MedplumClient_profilePromise = new WeakMap(), _MedplumClient_profile = new WeakMap(), _MedplumClient_config = new WeakMap(), _MedplumClient_instances = new WeakSet(), _MedplumClient_addLogin = function _MedplumClient_addLogin(newLogin) {
|
|
1400
|
+
_MedplumClient_fetch = new WeakMap(), _MedplumClient_createPdf = new WeakMap(), _MedplumClient_storage = new WeakMap(), _MedplumClient_requestCache = new WeakMap(), _MedplumClient_cacheTime = new WeakMap(), _MedplumClient_baseUrl = new WeakMap(), _MedplumClient_fhirBaseUrl = new WeakMap(), _MedplumClient_authorizeUrl = new WeakMap(), _MedplumClient_tokenUrl = new WeakMap(), _MedplumClient_logoutUrl = new WeakMap(), _MedplumClient_onUnauthenticated = new WeakMap(), _MedplumClient_autoBatchTime = new WeakMap(), _MedplumClient_autoBatchQueue = new WeakMap(), _MedplumClient_clientId = new WeakMap(), _MedplumClient_clientSecret = new WeakMap(), _MedplumClient_autoBatchTimerId = new WeakMap(), _MedplumClient_accessToken = new WeakMap(), _MedplumClient_refreshToken = new WeakMap(), _MedplumClient_refreshPromise = new WeakMap(), _MedplumClient_profilePromise = new WeakMap(), _MedplumClient_profile = new WeakMap(), _MedplumClient_config = new WeakMap(), _MedplumClient_instances = new WeakSet(), _MedplumClient_addLogin = function _MedplumClient_addLogin(newLogin) {
|
|
1358
1401
|
const logins = this.getLogins().filter((login) => login.profile?.reference !== newLogin.profile?.reference);
|
|
1359
1402
|
logins.push(newLogin);
|
|
1360
1403
|
__classPrivateFieldGet(this, _MedplumClient_storage, "f").setObject('logins', logins);
|
|
@@ -1415,6 +1458,43 @@ async function _MedplumClient_request(method, url, options = {}) {
|
|
|
1415
1458
|
throw obj;
|
|
1416
1459
|
}
|
|
1417
1460
|
return obj;
|
|
1461
|
+
}, _MedplumClient_executeAutoBatch =
|
|
1462
|
+
/**
|
|
1463
|
+
* Executes a batch of requests that were automatically batched together.
|
|
1464
|
+
*/
|
|
1465
|
+
async function _MedplumClient_executeAutoBatch() {
|
|
1466
|
+
// Get the current queue
|
|
1467
|
+
const entries = [...__classPrivateFieldGet(this, _MedplumClient_autoBatchQueue, "f")];
|
|
1468
|
+
// Clear the queue
|
|
1469
|
+
__classPrivateFieldGet(this, _MedplumClient_autoBatchQueue, "f").length = 0;
|
|
1470
|
+
// Clear the timer
|
|
1471
|
+
__classPrivateFieldSet(this, _MedplumClient_autoBatchTimerId, undefined, "f");
|
|
1472
|
+
// If there is only one request in the batch, just execute it
|
|
1473
|
+
if (entries.length === 1) {
|
|
1474
|
+
const entry = entries[0];
|
|
1475
|
+
entry.resolve(await __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_request).call(this, entry.method, __classPrivateFieldGet(this, _MedplumClient_fhirBaseUrl, "f") + entry.url, entry.options));
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1478
|
+
// Build the batch request
|
|
1479
|
+
const batch = {
|
|
1480
|
+
resourceType: 'Bundle',
|
|
1481
|
+
type: 'batch',
|
|
1482
|
+
entry: entries.map((e) => ({
|
|
1483
|
+
request: {
|
|
1484
|
+
method: e.method,
|
|
1485
|
+
url: e.url,
|
|
1486
|
+
},
|
|
1487
|
+
resource: e.options.body ? JSON.parse(e.options.body) : undefined,
|
|
1488
|
+
})),
|
|
1489
|
+
};
|
|
1490
|
+
// Execute the batch request
|
|
1491
|
+
const response = (await this.post('fhir/R4', batch));
|
|
1492
|
+
// Process the response
|
|
1493
|
+
for (let i = 0; i < entries.length; i++) {
|
|
1494
|
+
const entry = entries[i];
|
|
1495
|
+
const responseEntry = response.entry?.[i];
|
|
1496
|
+
entry.resolve(responseEntry?.resource);
|
|
1497
|
+
}
|
|
1418
1498
|
}, _MedplumClient_addFetchOptionsDefaults = function _MedplumClient_addFetchOptionsDefaults(options) {
|
|
1419
1499
|
if (!options.headers) {
|
|
1420
1500
|
options.headers = {};
|
|
@@ -1453,7 +1533,7 @@ async function _MedplumClient_request(method, url, options = {}) {
|
|
|
1453
1533
|
if (__classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_refresh).call(this)) {
|
|
1454
1534
|
return __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_request).call(this, method, url, options);
|
|
1455
1535
|
}
|
|
1456
|
-
this.
|
|
1536
|
+
this.clearActiveLogin();
|
|
1457
1537
|
if (__classPrivateFieldGet(this, _MedplumClient_onUnauthenticated, "f")) {
|
|
1458
1538
|
__classPrivateFieldGet(this, _MedplumClient_onUnauthenticated, "f").call(this);
|
|
1459
1539
|
}
|
|
@@ -1464,15 +1544,16 @@ async function _MedplumClient_request(method, url, options = {}) {
|
|
|
1464
1544
|
* Clears all auth state including local storage and session storage.
|
|
1465
1545
|
* See: https://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint
|
|
1466
1546
|
*/
|
|
1467
|
-
async function _MedplumClient_requestAuthorization() {
|
|
1468
|
-
await this.
|
|
1547
|
+
async function _MedplumClient_requestAuthorization(loginParams) {
|
|
1548
|
+
const loginRequest = await this.ensureCodeChallenge(loginParams || {});
|
|
1469
1549
|
const url = new URL(__classPrivateFieldGet(this, _MedplumClient_authorizeUrl, "f"));
|
|
1470
1550
|
url.searchParams.set('response_type', 'code');
|
|
1471
1551
|
url.searchParams.set('state', sessionStorage.getItem('pkceState'));
|
|
1472
|
-
url.searchParams.set('client_id', __classPrivateFieldGet(this, _MedplumClient_clientId, "f"));
|
|
1473
|
-
url.searchParams.set('redirect_uri', getBaseUrl());
|
|
1474
|
-
url.searchParams.set('code_challenge_method',
|
|
1475
|
-
url.searchParams.set('code_challenge',
|
|
1552
|
+
url.searchParams.set('client_id', loginRequest.clientId || __classPrivateFieldGet(this, _MedplumClient_clientId, "f"));
|
|
1553
|
+
url.searchParams.set('redirect_uri', loginRequest.redirectUri || getBaseUrl());
|
|
1554
|
+
url.searchParams.set('code_challenge_method', loginRequest.codeChallengeMethod);
|
|
1555
|
+
url.searchParams.set('code_challenge', loginRequest.codeChallenge);
|
|
1556
|
+
url.searchParams.set('scope', loginRequest.scope || 'openid profile');
|
|
1476
1557
|
window.location.assign(url.toString());
|
|
1477
1558
|
}, _MedplumClient_refresh = function _MedplumClient_refresh() {
|
|
1478
1559
|
if (__classPrivateFieldGet(this, _MedplumClient_refreshPromise, "f")) {
|
|
@@ -1498,20 +1579,19 @@ async function _MedplumClient_requestAuthorization() {
|
|
|
1498
1579
|
* @param formBody Token parameters in URL encoded format.
|
|
1499
1580
|
*/
|
|
1500
1581
|
async function _MedplumClient_fetchTokens(formBody) {
|
|
1501
|
-
|
|
1582
|
+
const response = await __classPrivateFieldGet(this, _MedplumClient_fetch, "f").call(this, __classPrivateFieldGet(this, _MedplumClient_tokenUrl, "f"), {
|
|
1502
1583
|
method: 'POST',
|
|
1503
1584
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
1504
1585
|
body: formBody,
|
|
1505
1586
|
credentials: 'include',
|
|
1506
|
-
})
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
.then(() => this.getProfile());
|
|
1587
|
+
});
|
|
1588
|
+
if (!response.ok) {
|
|
1589
|
+
this.clearActiveLogin();
|
|
1590
|
+
throw new Error('Failed to fetch tokens');
|
|
1591
|
+
}
|
|
1592
|
+
const tokens = await response.json();
|
|
1593
|
+
await __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_verifyTokens).call(this, tokens);
|
|
1594
|
+
return this.getProfile();
|
|
1515
1595
|
}, _MedplumClient_verifyTokens =
|
|
1516
1596
|
/**
|
|
1517
1597
|
* Verifies the tokens received from the auth server.
|
|
@@ -1524,15 +1604,15 @@ async function _MedplumClient_verifyTokens(tokens) {
|
|
|
1524
1604
|
// Verify token has not expired
|
|
1525
1605
|
const tokenPayload = parseJWTPayload(token);
|
|
1526
1606
|
if (Date.now() >= tokenPayload.exp * 1000) {
|
|
1527
|
-
this.
|
|
1607
|
+
this.clearActiveLogin();
|
|
1528
1608
|
throw new Error('Token expired');
|
|
1529
1609
|
}
|
|
1530
1610
|
// Verify app_client_id
|
|
1531
1611
|
if (__classPrivateFieldGet(this, _MedplumClient_clientId, "f") && tokenPayload.client_id !== __classPrivateFieldGet(this, _MedplumClient_clientId, "f")) {
|
|
1532
|
-
this.
|
|
1612
|
+
this.clearActiveLogin();
|
|
1533
1613
|
throw new Error('Token was not issued for this audience');
|
|
1534
1614
|
}
|
|
1535
|
-
|
|
1615
|
+
return this.setActiveLogin({
|
|
1536
1616
|
accessToken: token,
|
|
1537
1617
|
refreshToken: tokens.refresh_token,
|
|
1538
1618
|
project: tokens.project,
|