@medplum/core 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/dist/cjs/index.js +1842 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/index.min.js +16 -0
- package/dist/cjs/index.min.js.map +1 -0
- package/dist/esm/index.js +1792 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/index.min.js +16 -0
- package/dist/esm/index.min.js.map +1 -0
- package/dist/{cache.d.ts → types/cache.d.ts} +1 -3
- package/dist/{client.d.ts → types/client.d.ts} +54 -69
- package/dist/{crypto.d.ts → types/crypto.d.ts} +0 -0
- package/dist/{eventtarget.d.ts → types/eventtarget.d.ts} +3 -3
- package/dist/{format.d.ts → types/format.d.ts} +0 -0
- package/dist/{index.d.ts → types/index.d.ts} +0 -0
- package/dist/types/jwt.d.ts +5 -0
- package/dist/{outcomes.d.ts → types/outcomes.d.ts} +6 -1
- package/dist/{search.d.ts → types/search.d.ts} +4 -0
- package/dist/{searchparams.d.ts → types/searchparams.d.ts} +2 -1
- package/dist/{storage.d.ts → types/storage.d.ts} +2 -2
- package/dist/{types.d.ts → types/types.d.ts} +18 -3
- package/dist/{utils.d.ts → types/utils.d.ts} +6 -0
- package/package.json +11 -5
- package/dist/cache.js +0 -39
- package/dist/cache.js.map +0 -1
- package/dist/client.js +0 -572
- package/dist/client.js.map +0 -1
- package/dist/crypto.js +0 -33
- package/dist/crypto.js.map +0 -1
- package/dist/eventtarget.js +0 -38
- package/dist/eventtarget.js.map +0 -1
- package/dist/format.js +0 -56
- package/dist/format.js.map +0 -1
- package/dist/index.js +0 -20
- package/dist/index.js.map +0 -1
- package/dist/jwt.d.ts +0 -5
- package/dist/jwt.js +0 -28
- package/dist/jwt.js.map +0 -1
- package/dist/outcomes.js +0 -154
- package/dist/outcomes.js.map +0 -1
- package/dist/search.js +0 -120
- package/dist/search.js.map +0 -1
- package/dist/searchparams.js +0 -128
- package/dist/searchparams.js.map +0 -1
- package/dist/storage.js +0 -90
- package/dist/storage.js.map +0 -1
- package/dist/types.js +0 -171
- package/dist/types.js.map +0 -1
- package/dist/utils.js +0 -239
- package/dist/utils.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@medplum/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Medplum TS/JS Library",
|
|
5
5
|
"author": "Medplum <hello@medplum.com>",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -12,14 +12,20 @@
|
|
|
12
12
|
},
|
|
13
13
|
"scripts": {
|
|
14
14
|
"clean": "rimraf dist",
|
|
15
|
-
"build": "npm run clean && tsc",
|
|
15
|
+
"build": "npm run clean && tsc && npm run rollup",
|
|
16
|
+
"rollup": "rollup --config rollup.config.js",
|
|
16
17
|
"test": "jest"
|
|
17
18
|
},
|
|
18
19
|
"devDependencies": {
|
|
19
|
-
"@medplum/fhirtypes": "0.
|
|
20
|
+
"@medplum/fhirtypes": "0.5.0"
|
|
20
21
|
},
|
|
21
|
-
"main": "dist/index.js",
|
|
22
|
-
"
|
|
22
|
+
"main": "dist/cjs/index.js",
|
|
23
|
+
"module": "dist/esm/index.js",
|
|
24
|
+
"exports": {
|
|
25
|
+
"require": "./dist/cjs/index.js",
|
|
26
|
+
"import": "./dist/esm/index.js"
|
|
27
|
+
},
|
|
28
|
+
"types": "dist/types/index.d.ts",
|
|
23
29
|
"sideEffects": false,
|
|
24
30
|
"keywords": [
|
|
25
31
|
"medplum",
|
package/dist/cache.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.LRUCache = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* LRU cache (least recently used)
|
|
6
|
-
* Source: https://stackoverflow.com/a/46432113
|
|
7
|
-
*/
|
|
8
|
-
class LRUCache {
|
|
9
|
-
constructor(max = 10) {
|
|
10
|
-
this.max = max;
|
|
11
|
-
this.cache = new Map();
|
|
12
|
-
}
|
|
13
|
-
clear() {
|
|
14
|
-
this.cache.clear();
|
|
15
|
-
}
|
|
16
|
-
get(key) {
|
|
17
|
-
const item = this.cache.get(key);
|
|
18
|
-
if (item) {
|
|
19
|
-
this.cache.delete(key);
|
|
20
|
-
this.cache.set(key, item);
|
|
21
|
-
}
|
|
22
|
-
return item;
|
|
23
|
-
}
|
|
24
|
-
set(key, val) {
|
|
25
|
-
if (this.cache.has(key)) {
|
|
26
|
-
this.cache.delete(key);
|
|
27
|
-
}
|
|
28
|
-
else if (this.cache.size >= this.max) {
|
|
29
|
-
this.cache.delete(this.first());
|
|
30
|
-
}
|
|
31
|
-
this.cache.set(key, val);
|
|
32
|
-
}
|
|
33
|
-
first() {
|
|
34
|
-
// This works because the Map class maintains ordered keys.
|
|
35
|
-
return this.cache.keys().next().value;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
exports.LRUCache = LRUCache;
|
|
39
|
-
//# sourceMappingURL=cache.js.map
|
package/dist/cache.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACH,MAAa,QAAQ;IAInB,YAAY,GAAG,GAAG,EAAE;QAClB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,KAAK,GAAG,IAAI,GAAG,EAAE,CAAC;IACzB,CAAC;IAED,KAAK;QACH,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;IAED,GAAG,CAAC,GAAW;QACb,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,IAAI,EAAE;YACR,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACvB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;SAC3B;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,GAAG,CAAC,GAAW,EAAE,GAAM;QACrB,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;YACvB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;SACxB;aAAM,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,GAAG,EAAE;YACtC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;SACjC;QACD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAC3B,CAAC;IAEO,KAAK;QACX,2DAA2D;QAC3D,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;IACxC,CAAC;CACF;AAnCD,4BAmCC","sourcesContent":["/**\n * LRU cache (least recently used)\n * Source: https://stackoverflow.com/a/46432113\n */\nexport class LRUCache<T> {\n private readonly max: number;\n private readonly cache: Map<string, T>;\n\n constructor(max = 10) {\n this.max = max;\n this.cache = new Map();\n }\n\n clear() {\n this.cache.clear();\n }\n\n get(key: string): T | undefined {\n const item = this.cache.get(key);\n if (item) {\n this.cache.delete(key);\n this.cache.set(key, item);\n }\n return item;\n }\n\n set(key: string, val: T) {\n if (this.cache.has(key)) {\n this.cache.delete(key);\n } else if (this.cache.size >= this.max) {\n this.cache.delete(this.first());\n }\n this.cache.set(key, val);\n }\n\n private first() {\n // This works because the Map class maintains ordered keys.\n return this.cache.keys().next().value;\n }\n}\n"]}
|
package/dist/client.js
DELETED
|
@@ -1,572 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// PKCE auth ased on:
|
|
3
|
-
// https://aws.amazon.com/blogs/security/how-to-add-authentication-single-page-web-application-with-amazon-cognito-oauth2-implementation/
|
|
4
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
5
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
6
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
7
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
8
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
9
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
10
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
11
|
-
});
|
|
12
|
-
};
|
|
13
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
-
exports.MedplumClient = void 0;
|
|
15
|
-
const cache_1 = require("./cache");
|
|
16
|
-
const crypto_1 = require("./crypto");
|
|
17
|
-
const eventtarget_1 = require("./eventtarget");
|
|
18
|
-
const jwt_1 = require("./jwt");
|
|
19
|
-
const outcomes_1 = require("./outcomes");
|
|
20
|
-
const search_1 = require("./search");
|
|
21
|
-
const storage_1 = require("./storage");
|
|
22
|
-
const types_1 = require("./types");
|
|
23
|
-
const utils_1 = require("./utils");
|
|
24
|
-
const DEFAULT_BASE_URL = 'https://api.medplum.com/';
|
|
25
|
-
const DEFAULT_SCOPE = 'launch/patient openid fhirUser offline_access user/*.*';
|
|
26
|
-
const DEFAULT_RESOURCE_CACHE_SIZE = 1000;
|
|
27
|
-
const JSON_CONTENT_TYPE = 'application/json';
|
|
28
|
-
const FHIR_CONTENT_TYPE = 'application/fhir+json';
|
|
29
|
-
const PATCH_CONTENT_TYPE = 'application/json-patch+json';
|
|
30
|
-
class MedplumClient extends eventtarget_1.EventTarget {
|
|
31
|
-
constructor(options) {
|
|
32
|
-
var _a, _b, _c;
|
|
33
|
-
super();
|
|
34
|
-
if (options.baseUrl) {
|
|
35
|
-
if (!options.baseUrl.startsWith('http')) {
|
|
36
|
-
throw new Error('Base URL must start with http or https');
|
|
37
|
-
}
|
|
38
|
-
if (!options.baseUrl.endsWith('/')) {
|
|
39
|
-
throw new Error('Base URL must end with a trailing slash');
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
this.fetch = options.fetch || window.fetch.bind(window);
|
|
43
|
-
this.storage = new storage_1.ClientStorage();
|
|
44
|
-
this.schema = new Map();
|
|
45
|
-
this.resourceCache = new cache_1.LRUCache((_a = options.resourceCacheSize) !== null && _a !== void 0 ? _a : DEFAULT_RESOURCE_CACHE_SIZE);
|
|
46
|
-
this.baseUrl = options.baseUrl || DEFAULT_BASE_URL;
|
|
47
|
-
this.clientId = options.clientId || '';
|
|
48
|
-
this.authorizeUrl = options.authorizeUrl || this.baseUrl + 'oauth2/authorize';
|
|
49
|
-
this.tokenUrl = options.tokenUrl || this.baseUrl + 'oauth2/token';
|
|
50
|
-
this.logoutUrl = options.logoutUrl || this.baseUrl + 'oauth2/logout';
|
|
51
|
-
this.onUnauthenticated = options.onUnauthenticated;
|
|
52
|
-
this.activeLogin = this.storage.getObject('activeLogin');
|
|
53
|
-
if (!((_c = (_b = this.activeLogin) === null || _b === void 0 ? void 0 : _b.profile) === null || _c === void 0 ? void 0 : _c.reference)) {
|
|
54
|
-
this.clear();
|
|
55
|
-
}
|
|
56
|
-
this.loading = false;
|
|
57
|
-
this.refreshProfile().catch(console.log);
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Clears all auth state including local storage and session storage.
|
|
61
|
-
*/
|
|
62
|
-
clear() {
|
|
63
|
-
this.storage.clear();
|
|
64
|
-
this.schema.clear();
|
|
65
|
-
this.resourceCache.clear();
|
|
66
|
-
this.activeLogin = undefined;
|
|
67
|
-
this.profile = undefined;
|
|
68
|
-
this.dispatchEvent({ type: 'change' });
|
|
69
|
-
}
|
|
70
|
-
get(url) {
|
|
71
|
-
return this.request('GET', url);
|
|
72
|
-
}
|
|
73
|
-
post(url, body, contentType) {
|
|
74
|
-
return this.request('POST', url, contentType, body);
|
|
75
|
-
}
|
|
76
|
-
put(url, body, contentType) {
|
|
77
|
-
return this.request('PUT', url, contentType, body);
|
|
78
|
-
}
|
|
79
|
-
delete(url) {
|
|
80
|
-
return this.request('DELETE', url);
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Tries to register a new user.
|
|
84
|
-
* @param request The registration request.
|
|
85
|
-
* @returns Promise to the authentication response.
|
|
86
|
-
*/
|
|
87
|
-
register(request) {
|
|
88
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
89
|
-
const response = yield this.post('auth/register', request);
|
|
90
|
-
yield this.setActiveLogin(response);
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Initiates a user login flow.
|
|
95
|
-
* @param email The email address of the user.
|
|
96
|
-
* @param password The password of the user.
|
|
97
|
-
* @param remember Optional flag to remember the user.
|
|
98
|
-
* @returns Promise to the authentication response.
|
|
99
|
-
*/
|
|
100
|
-
startLogin(email, password, remember) {
|
|
101
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
102
|
-
yield this.startPkce();
|
|
103
|
-
return this.post('auth/login', {
|
|
104
|
-
clientId: this.clientId,
|
|
105
|
-
scope: DEFAULT_SCOPE,
|
|
106
|
-
codeChallengeMethod: 'S256',
|
|
107
|
-
codeChallenge: this.storage.getString('codeChallenge'),
|
|
108
|
-
email,
|
|
109
|
-
password,
|
|
110
|
-
remember: !!remember,
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Tries to sign in with Google authentication.
|
|
116
|
-
* The response parameter is the result of a Google authentication.
|
|
117
|
-
* See: https://developers.google.com/identity/gsi/web/guides/handle-credential-responses-js-functions
|
|
118
|
-
* @param googleResponse The Google credential response.
|
|
119
|
-
* @returns Promise to the authentication response.
|
|
120
|
-
*/
|
|
121
|
-
startGoogleLogin(googleResponse) {
|
|
122
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
123
|
-
yield this.startPkce();
|
|
124
|
-
return this.post('auth/google', googleResponse);
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Signs out locally.
|
|
129
|
-
* Does not invalidate tokens with the server.
|
|
130
|
-
*/
|
|
131
|
-
signOut() {
|
|
132
|
-
this.clear();
|
|
133
|
-
return Promise.resolve();
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Tries to sign in the user.
|
|
137
|
-
* Returns true if the user is signed in.
|
|
138
|
-
* This may result in navigating away to the sign in page.
|
|
139
|
-
*/
|
|
140
|
-
signInWithRedirect() {
|
|
141
|
-
const urlParams = new URLSearchParams(window.location.search);
|
|
142
|
-
const code = urlParams.get('code');
|
|
143
|
-
if (!code) {
|
|
144
|
-
this.requestAuthorization();
|
|
145
|
-
return undefined;
|
|
146
|
-
}
|
|
147
|
-
else {
|
|
148
|
-
return this.processCode(code);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Tries to sign out the user.
|
|
153
|
-
* See: https://docs.aws.amazon.com/cognito/latest/developerguide/logout-endpoint.html
|
|
154
|
-
*/
|
|
155
|
-
signOutWithRedirect() {
|
|
156
|
-
window.location.assign(this.logoutUrl);
|
|
157
|
-
}
|
|
158
|
-
fhirUrl(...path) {
|
|
159
|
-
const builder = [this.baseUrl, 'fhir/R4'];
|
|
160
|
-
path.forEach((p) => builder.push('/', encodeURIComponent(p)));
|
|
161
|
-
return builder.join('');
|
|
162
|
-
}
|
|
163
|
-
search(search) {
|
|
164
|
-
if (typeof search === 'string') {
|
|
165
|
-
return this.get('fhir/R4/' + search);
|
|
166
|
-
}
|
|
167
|
-
else {
|
|
168
|
-
return this.get(this.fhirUrl(search.resourceType) + (0, search_1.formatSearchQuery)(search));
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
* Searches a ValueSet resource using the "expand" operation.
|
|
173
|
-
* See: https://www.hl7.org/fhir/operation-valueset-expand.html
|
|
174
|
-
* @param system The ValueSet system url.
|
|
175
|
-
* @param filter The search string.
|
|
176
|
-
* @returns Promise to expanded ValueSet.
|
|
177
|
-
*/
|
|
178
|
-
searchValueSet(system, filter) {
|
|
179
|
-
return this.get(this.fhirUrl('ValueSet', '$expand') +
|
|
180
|
-
`?url=${encodeURIComponent(system)}` +
|
|
181
|
-
`&filter=${encodeURIComponent(filter)}`);
|
|
182
|
-
}
|
|
183
|
-
read(resourceType, id) {
|
|
184
|
-
const cacheKey = resourceType + '/' + id;
|
|
185
|
-
const promise = this.get(this.fhirUrl(resourceType, id)).then((resource) => {
|
|
186
|
-
this.resourceCache.set(cacheKey, resource);
|
|
187
|
-
return resource;
|
|
188
|
-
});
|
|
189
|
-
this.resourceCache.set(cacheKey, promise);
|
|
190
|
-
return promise;
|
|
191
|
-
}
|
|
192
|
-
readCached(resourceType, id) {
|
|
193
|
-
const cached = this.resourceCache.get(resourceType + '/' + id);
|
|
194
|
-
return cached ? Promise.resolve(cached) : this.read(resourceType, id);
|
|
195
|
-
}
|
|
196
|
-
readReference(reference) {
|
|
197
|
-
const refString = reference === null || reference === void 0 ? void 0 : reference.reference;
|
|
198
|
-
if (!refString) {
|
|
199
|
-
return Promise.reject('Missing reference');
|
|
200
|
-
}
|
|
201
|
-
const [resourceType, id] = refString.split('/');
|
|
202
|
-
return this.read(resourceType, id);
|
|
203
|
-
}
|
|
204
|
-
readCachedReference(reference) {
|
|
205
|
-
const refString = reference === null || reference === void 0 ? void 0 : reference.reference;
|
|
206
|
-
if (!refString) {
|
|
207
|
-
return Promise.reject('Missing reference');
|
|
208
|
-
}
|
|
209
|
-
const [resourceType, id] = refString.split('/');
|
|
210
|
-
return this.readCached(resourceType, id);
|
|
211
|
-
}
|
|
212
|
-
getTypeDefinition(resourceType) {
|
|
213
|
-
if (!resourceType) {
|
|
214
|
-
return Promise.reject('Missing resourceType');
|
|
215
|
-
}
|
|
216
|
-
const cached = this.schema.get(resourceType);
|
|
217
|
-
if (cached) {
|
|
218
|
-
return Promise.resolve(cached);
|
|
219
|
-
}
|
|
220
|
-
let typeDef;
|
|
221
|
-
return this.search('StructureDefinition?name:exact=' + encodeURIComponent(resourceType))
|
|
222
|
-
.then((result) => {
|
|
223
|
-
var _a;
|
|
224
|
-
if (!((_a = result.entry) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
225
|
-
throw new Error('StructureDefinition not found');
|
|
226
|
-
}
|
|
227
|
-
const resource = result.entry[0].resource;
|
|
228
|
-
if (!resource) {
|
|
229
|
-
throw new Error('StructureDefinition not found');
|
|
230
|
-
}
|
|
231
|
-
typeDef = (0, types_1.indexStructureDefinition)(resource);
|
|
232
|
-
})
|
|
233
|
-
.then(() => this.search({
|
|
234
|
-
resourceType: 'SearchParameter',
|
|
235
|
-
count: 100,
|
|
236
|
-
filters: [
|
|
237
|
-
{
|
|
238
|
-
code: 'base',
|
|
239
|
-
operator: search_1.Operator.EQUALS,
|
|
240
|
-
value: resourceType,
|
|
241
|
-
},
|
|
242
|
-
],
|
|
243
|
-
}))
|
|
244
|
-
.then((result) => {
|
|
245
|
-
const entries = result.entry;
|
|
246
|
-
if (entries) {
|
|
247
|
-
typeDef.types[resourceType].searchParams = entries
|
|
248
|
-
.map((e) => e.resource)
|
|
249
|
-
.sort((a, b) => { var _a, _b; return (_b = (_a = a.name) === null || _a === void 0 ? void 0 : _a.localeCompare(b.name)) !== null && _b !== void 0 ? _b : 0; });
|
|
250
|
-
}
|
|
251
|
-
this.schema.set(resourceType, typeDef);
|
|
252
|
-
return typeDef;
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
readHistory(resourceType, id) {
|
|
256
|
-
return this.get(this.fhirUrl(resourceType, id, '_history'));
|
|
257
|
-
}
|
|
258
|
-
readPatientEverything(id) {
|
|
259
|
-
return this.get(this.fhirUrl('Patient', id, '$everything'));
|
|
260
|
-
}
|
|
261
|
-
create(resource) {
|
|
262
|
-
if (!resource.resourceType) {
|
|
263
|
-
throw new Error('Missing resourceType');
|
|
264
|
-
}
|
|
265
|
-
return this.post(this.fhirUrl(resource.resourceType), resource);
|
|
266
|
-
}
|
|
267
|
-
createBinary(data, contentType) {
|
|
268
|
-
return this.post(this.fhirUrl('Binary'), data, contentType);
|
|
269
|
-
}
|
|
270
|
-
update(resource) {
|
|
271
|
-
if (!resource.resourceType) {
|
|
272
|
-
throw new Error('Missing resourceType');
|
|
273
|
-
}
|
|
274
|
-
if (!resource.id) {
|
|
275
|
-
throw new Error('Missing id');
|
|
276
|
-
}
|
|
277
|
-
return this.put(this.fhirUrl(resource.resourceType, resource.id), resource);
|
|
278
|
-
}
|
|
279
|
-
patch(resourceType, id, operations) {
|
|
280
|
-
return this.request('PATCH', this.fhirUrl(resourceType, id), PATCH_CONTENT_TYPE, operations);
|
|
281
|
-
}
|
|
282
|
-
deleteResource(resourceType, id) {
|
|
283
|
-
return this.delete(this.fhirUrl(resourceType, id));
|
|
284
|
-
}
|
|
285
|
-
graphql(gql) {
|
|
286
|
-
return this.post(this.fhirUrl('$graphql'), gql, JSON_CONTENT_TYPE);
|
|
287
|
-
}
|
|
288
|
-
subscribe(criteria, handler) {
|
|
289
|
-
return this.create({
|
|
290
|
-
resourceType: 'Subscription',
|
|
291
|
-
status: 'active',
|
|
292
|
-
criteria: criteria,
|
|
293
|
-
channel: {
|
|
294
|
-
type: 'sse',
|
|
295
|
-
},
|
|
296
|
-
}).then((sub) => {
|
|
297
|
-
const eventSource = new EventSource(this.baseUrl + 'sse?subscription=' + encodeURIComponent(sub.id), {
|
|
298
|
-
withCredentials: true,
|
|
299
|
-
});
|
|
300
|
-
eventSource.onmessage = (e) => {
|
|
301
|
-
handler(JSON.parse(e.data));
|
|
302
|
-
};
|
|
303
|
-
return eventSource;
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
getActiveLogin() {
|
|
307
|
-
return this.activeLogin;
|
|
308
|
-
}
|
|
309
|
-
setActiveLogin(login) {
|
|
310
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
311
|
-
this.activeLogin = login;
|
|
312
|
-
this.storage.setObject('activeLogin', login);
|
|
313
|
-
this.addLogin(login);
|
|
314
|
-
this.resourceCache.clear();
|
|
315
|
-
this.refreshPromise = undefined;
|
|
316
|
-
yield this.refreshProfile();
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
getLogins() {
|
|
320
|
-
var _a;
|
|
321
|
-
return (_a = this.storage.getObject('logins')) !== null && _a !== void 0 ? _a : [];
|
|
322
|
-
}
|
|
323
|
-
addLogin(newLogin) {
|
|
324
|
-
const logins = this.getLogins().filter((login) => { var _a, _b; return ((_a = login.profile) === null || _a === void 0 ? void 0 : _a.reference) !== ((_b = newLogin.profile) === null || _b === void 0 ? void 0 : _b.reference); });
|
|
325
|
-
logins.push(newLogin);
|
|
326
|
-
this.storage.setObject('logins', logins);
|
|
327
|
-
}
|
|
328
|
-
refreshProfile() {
|
|
329
|
-
var _a;
|
|
330
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
331
|
-
const reference = (_a = this.getActiveLogin()) === null || _a === void 0 ? void 0 : _a.profile;
|
|
332
|
-
if (reference === null || reference === void 0 ? void 0 : reference.reference) {
|
|
333
|
-
this.loading = true;
|
|
334
|
-
this.profile = yield this.readCachedReference(reference);
|
|
335
|
-
this.loading = false;
|
|
336
|
-
this.dispatchEvent({ type: 'change' });
|
|
337
|
-
}
|
|
338
|
-
return this.profile;
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
getProfile() {
|
|
342
|
-
return this.profile;
|
|
343
|
-
}
|
|
344
|
-
isLoading() {
|
|
345
|
-
return this.loading;
|
|
346
|
-
}
|
|
347
|
-
/**
|
|
348
|
-
* Makes an HTTP request.
|
|
349
|
-
* @param {string} method
|
|
350
|
-
* @param {string} url
|
|
351
|
-
* @param {string=} contentType
|
|
352
|
-
* @param {Object=} body
|
|
353
|
-
*/
|
|
354
|
-
request(method, url, contentType, body) {
|
|
355
|
-
var _a;
|
|
356
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
357
|
-
if (this.refreshPromise) {
|
|
358
|
-
yield this.refreshPromise;
|
|
359
|
-
}
|
|
360
|
-
if (!url.startsWith('http')) {
|
|
361
|
-
url = this.baseUrl + url;
|
|
362
|
-
}
|
|
363
|
-
const headers = {
|
|
364
|
-
'Content-Type': contentType || FHIR_CONTENT_TYPE,
|
|
365
|
-
};
|
|
366
|
-
const accessToken = (_a = this.getActiveLogin()) === null || _a === void 0 ? void 0 : _a.accessToken;
|
|
367
|
-
if (accessToken) {
|
|
368
|
-
headers['Authorization'] = 'Bearer ' + accessToken;
|
|
369
|
-
}
|
|
370
|
-
const options = {
|
|
371
|
-
method: method,
|
|
372
|
-
cache: 'no-cache',
|
|
373
|
-
credentials: 'include',
|
|
374
|
-
headers,
|
|
375
|
-
};
|
|
376
|
-
if (body) {
|
|
377
|
-
if (typeof body === 'string' || (typeof File !== 'undefined' && body instanceof File)) {
|
|
378
|
-
options.body = body;
|
|
379
|
-
}
|
|
380
|
-
else {
|
|
381
|
-
options.body = (0, utils_1.stringify)(body);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
const response = yield this.fetch(url, options);
|
|
385
|
-
if (response.status === 401) {
|
|
386
|
-
// Refresh and try again
|
|
387
|
-
return this.handleUnauthenticated(method, url, contentType, body);
|
|
388
|
-
}
|
|
389
|
-
if (response.status === 204 || response.status === 304) {
|
|
390
|
-
// No content or change
|
|
391
|
-
return undefined;
|
|
392
|
-
}
|
|
393
|
-
const obj = yield response.json();
|
|
394
|
-
if (obj.resourceType === 'OperationOutcome' && !(0, outcomes_1.isOk)(obj)) {
|
|
395
|
-
return Promise.reject(new outcomes_1.OperationOutcomeError(obj));
|
|
396
|
-
}
|
|
397
|
-
return obj;
|
|
398
|
-
});
|
|
399
|
-
}
|
|
400
|
-
/**
|
|
401
|
-
* Handles an unauthenticated response from the server.
|
|
402
|
-
* First, tries to refresh the access token and retry the request.
|
|
403
|
-
* Otherwise, calls unauthenticated callbacks and rejects.
|
|
404
|
-
* @param method The HTTP method of the original request.
|
|
405
|
-
* @param url The URL of the original request.
|
|
406
|
-
* @param contentType The content type of the original request.
|
|
407
|
-
* @param body The body of the original request.
|
|
408
|
-
*/
|
|
409
|
-
handleUnauthenticated(method, url, contentType, body) {
|
|
410
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
411
|
-
return this.refresh()
|
|
412
|
-
.then(() => this.request(method, url, contentType, body))
|
|
413
|
-
.catch((error) => {
|
|
414
|
-
this.clear();
|
|
415
|
-
if (this.onUnauthenticated) {
|
|
416
|
-
this.onUnauthenticated();
|
|
417
|
-
}
|
|
418
|
-
return Promise.reject(error);
|
|
419
|
-
});
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
/**
|
|
423
|
-
* Starts a new PKCE flow.
|
|
424
|
-
* These PKCE values are stateful, and must survive redirects and page refreshes.
|
|
425
|
-
*/
|
|
426
|
-
startPkce() {
|
|
427
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
428
|
-
const pkceState = (0, crypto_1.getRandomString)();
|
|
429
|
-
this.storage.setString('pkceState', pkceState);
|
|
430
|
-
const codeVerifier = (0, crypto_1.getRandomString)();
|
|
431
|
-
this.storage.setString('codeVerifier', codeVerifier);
|
|
432
|
-
const arrayHash = yield (0, crypto_1.encryptSHA256)(codeVerifier);
|
|
433
|
-
const codeChallenge = (0, utils_1.arrayBufferToBase64)(arrayHash).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
434
|
-
this.storage.setString('codeChallenge', codeChallenge);
|
|
435
|
-
});
|
|
436
|
-
}
|
|
437
|
-
/**
|
|
438
|
-
* Redirects the user to the login screen for authorization.
|
|
439
|
-
* Clears all auth state including local storage and session storage.
|
|
440
|
-
* See: https://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint
|
|
441
|
-
*/
|
|
442
|
-
requestAuthorization() {
|
|
443
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
444
|
-
if (!this.authorizeUrl) {
|
|
445
|
-
throw new Error('Missing authorize URL');
|
|
446
|
-
}
|
|
447
|
-
this.startPkce();
|
|
448
|
-
window.location.assign(this.authorizeUrl +
|
|
449
|
-
'?response_type=code' +
|
|
450
|
-
'&state=' +
|
|
451
|
-
encodeURIComponent(this.storage.getString('pkceState')) +
|
|
452
|
-
'&client_id=' +
|
|
453
|
-
encodeURIComponent(this.clientId) +
|
|
454
|
-
'&redirect_uri=' +
|
|
455
|
-
encodeURIComponent(getBaseUrl()) +
|
|
456
|
-
'&scope=' +
|
|
457
|
-
encodeURIComponent(DEFAULT_SCOPE) +
|
|
458
|
-
'&code_challenge_method=S256' +
|
|
459
|
-
'&code_challenge=' +
|
|
460
|
-
encodeURIComponent(this.storage.getString('codeChallenge')));
|
|
461
|
-
});
|
|
462
|
-
}
|
|
463
|
-
/**
|
|
464
|
-
* Processes an OAuth authorization code.
|
|
465
|
-
* See: https://openid.net/specs/openid-connect-core-1_0.html#TokenRequest
|
|
466
|
-
* @param code The authorization code received by URL parameter.
|
|
467
|
-
*/
|
|
468
|
-
processCode(code) {
|
|
469
|
-
const pkceState = this.storage.getString('pkceState');
|
|
470
|
-
if (!pkceState) {
|
|
471
|
-
this.clear();
|
|
472
|
-
throw new Error('Invalid PCKE state');
|
|
473
|
-
}
|
|
474
|
-
const codeVerifier = this.storage.getString('codeVerifier');
|
|
475
|
-
if (!codeVerifier) {
|
|
476
|
-
this.clear();
|
|
477
|
-
throw new Error('Invalid PCKE code verifier');
|
|
478
|
-
}
|
|
479
|
-
return this.fetchTokens('grant_type=authorization_code' +
|
|
480
|
-
(this.clientId ? '&client_id=' + encodeURIComponent(this.clientId) : '') +
|
|
481
|
-
'&code_verifier=' +
|
|
482
|
-
encodeURIComponent(codeVerifier) +
|
|
483
|
-
'&redirect_uri=' +
|
|
484
|
-
encodeURIComponent(getBaseUrl()) +
|
|
485
|
-
'&code=' +
|
|
486
|
-
encodeURIComponent(code));
|
|
487
|
-
}
|
|
488
|
-
/**
|
|
489
|
-
* Tries to refresh the auth tokens.
|
|
490
|
-
* See: https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokens
|
|
491
|
-
*/
|
|
492
|
-
refresh() {
|
|
493
|
-
var _a;
|
|
494
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
495
|
-
if (this.refreshPromise) {
|
|
496
|
-
return this.refreshPromise;
|
|
497
|
-
}
|
|
498
|
-
const refreshToken = (_a = this.getActiveLogin()) === null || _a === void 0 ? void 0 : _a.refreshToken;
|
|
499
|
-
if (!refreshToken) {
|
|
500
|
-
this.clear();
|
|
501
|
-
return Promise.reject('Invalid refresh token');
|
|
502
|
-
}
|
|
503
|
-
this.refreshPromise = this.fetchTokens('grant_type=refresh_token' +
|
|
504
|
-
'&client_id=' +
|
|
505
|
-
encodeURIComponent(this.clientId) +
|
|
506
|
-
'&refresh_token=' +
|
|
507
|
-
encodeURIComponent(refreshToken));
|
|
508
|
-
yield this.refreshPromise;
|
|
509
|
-
});
|
|
510
|
-
}
|
|
511
|
-
/**
|
|
512
|
-
* Makes a POST request to the tokens endpoint.
|
|
513
|
-
* See: https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint
|
|
514
|
-
* @param formBody Token parameters in URL encoded format.
|
|
515
|
-
*/
|
|
516
|
-
fetchTokens(formBody) {
|
|
517
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
518
|
-
if (!this.tokenUrl) {
|
|
519
|
-
return Promise.reject('Missing token URL');
|
|
520
|
-
}
|
|
521
|
-
return this.fetch(this.tokenUrl, {
|
|
522
|
-
method: 'POST',
|
|
523
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
524
|
-
body: formBody,
|
|
525
|
-
})
|
|
526
|
-
.then((response) => {
|
|
527
|
-
if (!response.ok) {
|
|
528
|
-
return Promise.reject('Failed to fetch tokens');
|
|
529
|
-
}
|
|
530
|
-
return response.json();
|
|
531
|
-
})
|
|
532
|
-
.then((tokens) => this.verifyTokens(tokens))
|
|
533
|
-
.then(() => this.profile);
|
|
534
|
-
});
|
|
535
|
-
}
|
|
536
|
-
/**
|
|
537
|
-
* Verifies the tokens received from the auth server.
|
|
538
|
-
* Validates the JWT against the JWKS.
|
|
539
|
-
* See: https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint
|
|
540
|
-
* @param tokens
|
|
541
|
-
*/
|
|
542
|
-
verifyTokens(tokens) {
|
|
543
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
544
|
-
const token = tokens.access_token;
|
|
545
|
-
// Verify token has not expired
|
|
546
|
-
const tokenPayload = (0, jwt_1.parseJWTPayload)(token);
|
|
547
|
-
if (Date.now() >= tokenPayload.exp * 1000) {
|
|
548
|
-
this.clear();
|
|
549
|
-
return Promise.reject('Token expired');
|
|
550
|
-
}
|
|
551
|
-
// Verify app_client_id
|
|
552
|
-
if (this.clientId && tokenPayload.client_id !== this.clientId) {
|
|
553
|
-
this.clear();
|
|
554
|
-
return Promise.reject('Token was not issued for this audience');
|
|
555
|
-
}
|
|
556
|
-
yield this.setActiveLogin({
|
|
557
|
-
accessToken: token,
|
|
558
|
-
refreshToken: tokens.refresh_token,
|
|
559
|
-
project: tokens.project,
|
|
560
|
-
profile: tokens.profile,
|
|
561
|
-
});
|
|
562
|
-
});
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
exports.MedplumClient = MedplumClient;
|
|
566
|
-
/**
|
|
567
|
-
* Returns the base URL for the current page.
|
|
568
|
-
*/
|
|
569
|
-
function getBaseUrl() {
|
|
570
|
-
return window.location.protocol + '//' + window.location.host + '/';
|
|
571
|
-
}
|
|
572
|
-
//# sourceMappingURL=client.js.map
|