@health-samurai/aidbox-client 0.0.0-alpha.2 → 0.0.0-alpha.4
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 +295 -66
- package/dist/src/auth-providers.d.ts +22 -0
- package/dist/src/auth-providers.d.ts.map +1 -1
- package/dist/src/auth-providers.js +149 -13
- package/dist/src/client.d.ts +504 -31
- package/dist/src/client.d.ts.map +1 -1
- package/dist/src/client.js +1455 -934
- package/dist/src/fhir-types/hl7-fhir-r4-core/Address.d.ts +26 -0
- package/dist/src/fhir-types/hl7-fhir-r4-core/Address.d.ts.map +1 -0
- package/dist/src/fhir-types/hl7-fhir-r4-core/Address.js +5 -0
- package/dist/src/fhir-types/hl7-fhir-r4-core/ContactPoint.d.ts +16 -0
- package/dist/src/fhir-types/hl7-fhir-r4-core/ContactPoint.d.ts.map +1 -0
- package/dist/src/fhir-types/hl7-fhir-r4-core/ContactPoint.js +5 -0
- package/dist/src/fhir-types/hl7-fhir-r4-core/DomainResource.d.ts +1 -1
- package/dist/src/fhir-types/hl7-fhir-r4-core/DomainResource.d.ts.map +1 -1
- package/dist/src/fhir-types/hl7-fhir-r4-core/HumanName.d.ts +20 -0
- package/dist/src/fhir-types/hl7-fhir-r4-core/HumanName.d.ts.map +1 -0
- package/dist/src/fhir-types/hl7-fhir-r4-core/HumanName.js +5 -0
- package/dist/src/fhir-types/hl7-fhir-r4-core/Patient.d.ts +58 -0
- package/dist/src/fhir-types/hl7-fhir-r4-core/Patient.d.ts.map +1 -0
- package/dist/src/fhir-types/hl7-fhir-r4-core/Patient.js +10 -0
- package/dist/src/fhir-types/hl7-fhir-r4-core/Resource.d.ts +1 -1
- package/dist/src/fhir-types/hl7-fhir-r4-core/Resource.d.ts.map +1 -1
- package/dist/src/fhir-types/hl7-fhir-r4-core/index.d.ts +5 -0
- package/dist/src/fhir-types/hl7-fhir-r4-core/index.d.ts.map +1 -1
- package/dist/src/fhir-types/hl7-fhir-r4-core/index.js +1 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/result.d.ts +114 -3
- package/dist/src/result.d.ts.map +1 -1
- package/dist/src/result.js +6 -2
- package/dist/src/smart-backend-services.d.ts +44 -0
- package/dist/src/smart-backend-services.d.ts.map +1 -0
- package/dist/src/smart-backend-services.js +685 -0
- package/dist/src/types.d.ts +104 -70
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js +10 -2
- package/dist/src/utils.d.ts +10 -0
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +22 -0
- package/dist/test/auth-providers.test.d.ts +2 -0
- package/dist/test/auth-providers.test.d.ts.map +1 -0
- package/dist/test/basic-auth.test.d.ts +2 -0
- package/dist/test/basic-auth.test.d.ts.map +1 -0
- package/dist/test/smart-backend-services.test.d.ts +2 -0
- package/dist/test/smart-backend-services.test.d.ts.map +1 -0
- package/dist/test/utils.test.d.ts +2 -0
- package/dist/test/utils.test.d.ts.map +1 -0
- package/package.json +3 -1
- package/dist/src/fhir-http.d.ts +0 -96
- package/dist/src/fhir-http.d.ts.map +0 -1
- package/dist/src/fhir-http.js +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Typescript FHIR client
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A TypeScript client for interacting with a FHIR server.
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
@@ -8,21 +8,207 @@ The client is created with the `makeClient` function:
|
|
|
8
8
|
|
|
9
9
|
```typescript
|
|
10
10
|
const baseUrl = "https://fhir-server.address";
|
|
11
|
-
const client =
|
|
11
|
+
const client = new AidboxClient(
|
|
12
12
|
baseUrl,
|
|
13
|
-
|
|
13
|
+
new BrowserAuthProvider(baseUrl),
|
|
14
|
+
);
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Documentation
|
|
18
|
+
|
|
19
|
+
Documentation is generated automatically, and can be found [here](https://healthsamurai.github.io/aidbox-ts-sdk/aidbox-client/).
|
|
20
|
+
|
|
21
|
+
## Type Generator
|
|
22
|
+
|
|
23
|
+
This project is designed around the type generator that provides FHIR types based on the specified package.
|
|
24
|
+
However, not all types are provided in the client itself, only the necessary ones, like `Bundle`, and `OperationOutcome`.
|
|
25
|
+
If your application requires more types, use [atomic-ehr/codegen](https://github.com/atomic-ehr/codegen) to generate more types.
|
|
26
|
+
|
|
27
|
+
For example, using `atomic-ehr/codegen`, we can generate and import an `Observation` type, and ensure that all fields are provided when creating a resource:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import type { Observation } from "hl7-fhir-r4-core";
|
|
31
|
+
|
|
32
|
+
client.create<Observation>({
|
|
33
|
+
resourceType: "Observation",
|
|
34
|
+
status: "final",
|
|
35
|
+
code: {
|
|
36
|
+
coding: [{
|
|
37
|
+
system: "http://loinc.org",
|
|
38
|
+
code: "59408-5",
|
|
39
|
+
display: "Blood pressure systolic & diastolic"
|
|
40
|
+
}],
|
|
41
|
+
text: "Blood pressure"
|
|
42
|
+
},
|
|
43
|
+
subject: {
|
|
44
|
+
reference: "Patient/pt-1"
|
|
45
|
+
},
|
|
46
|
+
effectiveDateTime: "2025-12-05T00:00:00Z",
|
|
47
|
+
valueString: "minimal"
|
|
48
|
+
})
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The default set of types in the client is based on FHIR R4 Core.
|
|
52
|
+
If your application requires a different set of types, it is possible to override that through type parameters when creating a client:
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import type * as R5 from "hl7-fhir-r5-core";
|
|
56
|
+
import type { User } from "@health-samurai/aidbox-client";
|
|
57
|
+
|
|
58
|
+
const baseUrl = "https://fhir-server.address";
|
|
59
|
+
|
|
60
|
+
const client = new AidboxClient<R5.Bundle, R5.OperationOutcome, User> (
|
|
61
|
+
baseUrl,
|
|
62
|
+
new BrowserAuthProvider(baseUrl),
|
|
63
|
+
);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## [FHIR Interactions](https://hl7.org/fhir/http.html)
|
|
67
|
+
|
|
68
|
+
This client provides a set of methods to work with a FHIR server in a more convenient way:
|
|
69
|
+
|
|
70
|
+
- Instance Level Interaction
|
|
71
|
+
- `read` - Read the current state of the resource
|
|
72
|
+
- `vread` - Read the state of a specific version of the resource
|
|
73
|
+
- `update` - Update an existing resource by its id (or create it if it is new)
|
|
74
|
+
- `conditionalUpdate` - Update an existing resource based on some identification criteria (or create it if it is new).
|
|
75
|
+
- `patch` - Update an existing resource by posting a set of changes to it.
|
|
76
|
+
- `conditionalPatch` - Update an existing resource, based on some identification criteria, by posting a set of changes to it.
|
|
77
|
+
- `delete` - Delete a resource.
|
|
78
|
+
- `deleteHistory` - Delete all historical versions of a resource.
|
|
79
|
+
- `deleteHistoryVersion` - Delete a specific version of a resource.
|
|
80
|
+
- `history` - Retrieve the change history for a particular resource.
|
|
81
|
+
- Type Level Interaction
|
|
82
|
+
- `create` - Create a new resource with a server assigned id
|
|
83
|
+
- `conditionalCreate` - Create a new resource with a server assigned id if an equivalent resource does not already exist.
|
|
84
|
+
- `search` - Search the resource type based on some filter criteria.
|
|
85
|
+
- `conditionalDelete` - Conditional delete a single or multiple resources based on some identification criteria.
|
|
86
|
+
- `history` - Retrieve the change history for a particular resource type.
|
|
87
|
+
- Whole System Interaction
|
|
88
|
+
- `capabilities` - Get a capability statement for the system.
|
|
89
|
+
- `batch`/`transaction` - Perform multiple interactions (e.g., create, read, update, delete, patch, and/or [extended operations]) in a single interaction.
|
|
90
|
+
- `delete` - Conditional Delete across all resource types based on some filter criteria.
|
|
91
|
+
- `history` - Retrieve the change history for all resources.
|
|
92
|
+
- `search` - Search across all resource types based on some filter criteria.
|
|
93
|
+
- Compartment Interaction
|
|
94
|
+
- `search` - Search resources associated with a specific compartment instance (see [Search Contexts](https://build.fhir.org/search.html#searchcontexts) and [Compartments](https://build.fhir.org/compartmentdefinition.html))
|
|
95
|
+
- Operations Framework
|
|
96
|
+
- `operation` - Perform an operation as defined by an `OperationDefinition`.
|
|
97
|
+
- `validate` - Perform the Validate Operation.
|
|
98
|
+
|
|
99
|
+
### Patient CRUD Example
|
|
100
|
+
|
|
101
|
+
Here's an example of
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { AidboxClient, BrowserAuthProvider } from "@health-samurai/aidbox-client";
|
|
105
|
+
import type { Patient } from "hl7-fhir-r4-core";
|
|
106
|
+
import { formatOperationOutcome } from "utils";
|
|
107
|
+
|
|
108
|
+
const client = new AidboxClient(
|
|
109
|
+
"http://localhost:8080",
|
|
110
|
+
new BrowserAuthProvider("http://localhost:8080"),
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
// Create a new Patient resource
|
|
114
|
+
const result = await client.create<Patient>({
|
|
115
|
+
type: "Patient",
|
|
116
|
+
resource: {
|
|
117
|
+
gender: "female",
|
|
118
|
+
resourceType: "Patient",
|
|
119
|
+
},
|
|
14
120
|
});
|
|
121
|
+
|
|
122
|
+
// Check if interaction was successful
|
|
123
|
+
if (result.isErr())
|
|
124
|
+
throw Error(formatOperationOutcome(result.value.resource), {
|
|
125
|
+
cause: result.value.resource,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const patient = result.value.resource;
|
|
129
|
+
|
|
130
|
+
if (!patient.id)
|
|
131
|
+
throw Error(
|
|
132
|
+
"id is optional in FHIR, so we check it to satisfy the type checker",
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// Updating the patient
|
|
136
|
+
|
|
137
|
+
patient.name = [
|
|
138
|
+
{
|
|
139
|
+
given: ["Jane"],
|
|
140
|
+
family: "Doe",
|
|
141
|
+
},
|
|
142
|
+
];
|
|
143
|
+
|
|
144
|
+
const updateResult = await client.update<Patient>({
|
|
145
|
+
id: patient.id,
|
|
146
|
+
type: "Patient",
|
|
147
|
+
resource: patient,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
if (updateResult.isErr())
|
|
151
|
+
throw Error(formatOperationOutcome(updateResult.value.resource), {
|
|
152
|
+
cause: updateResult.value.resource,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Deleting the patient
|
|
156
|
+
|
|
157
|
+
const deleteResult = await client.delete<Patient>({
|
|
158
|
+
id: patient.id,
|
|
159
|
+
type: "Patient",
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
if (deleteResult.isErr())
|
|
163
|
+
throw Error(formatOperationOutcome(deleteResult.value.resource), {
|
|
164
|
+
cause: deleteResult.value.resource,
|
|
165
|
+
});
|
|
15
166
|
```
|
|
16
167
|
|
|
17
|
-
|
|
168
|
+
### Return data format
|
|
18
169
|
|
|
19
|
-
|
|
170
|
+
As seen in the example above, most methods return a `Result<T, E>` object.
|
|
171
|
+
This object represents a successful or erroneous state of the response.
|
|
20
172
|
|
|
21
|
-
|
|
22
|
-
- `request<T>` - send request to the FHIR server and recieve response with its body parsed to the specified type `T`
|
|
173
|
+
A general usage pattern is as follows:
|
|
23
174
|
|
|
24
|
-
|
|
25
|
-
|
|
175
|
+
```typescript
|
|
176
|
+
const result = await client.read<Patient>({ type: 'Patient', id: 'patient-id' });
|
|
177
|
+
|
|
178
|
+
if (result.isErr())
|
|
179
|
+
throw new Error("error reading Patient", { cause: result.value.resource })
|
|
180
|
+
|
|
181
|
+
const patient = result.value.resource;
|
|
182
|
+
|
|
183
|
+
// work with patient.
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
It is also possible to work with resources without unwrapping the `Result` object:
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
const result = await client.read<Patient>({ type: 'Patient', id: 'patient-id' });
|
|
190
|
+
|
|
191
|
+
return result
|
|
192
|
+
.map(({resource}: {resource: Patient}): Patient => {
|
|
193
|
+
/* work with Patient resource */
|
|
194
|
+
})
|
|
195
|
+
.mapErr(({resource}: {resource: OperationOutcome}): OperationOutcome => {
|
|
196
|
+
/* work with OperationOutcome resource */
|
|
197
|
+
});
|
|
198
|
+
// result is still Result<Patient, OperationOutcome>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
See the [documentation](https://healthsamurai.github.io/aidbox-ts-sdk/aidbox-client/) for more info.
|
|
202
|
+
|
|
203
|
+
## Low-level methods
|
|
204
|
+
|
|
205
|
+
The client provides two basic methods for writing custom interactions:
|
|
206
|
+
|
|
207
|
+
- `rawRequest` - send request to the FHIR server and receive response in a raw format
|
|
208
|
+
- `request<T>` - send request to the FHIR server and receive response with its body parsed to the specified type `T`
|
|
209
|
+
|
|
210
|
+
In a successful case, the `rawRequest` returns an object with JavaScript Response and additional meta information.
|
|
211
|
+
When the server responds with an error code, this function throws an error:
|
|
26
212
|
|
|
27
213
|
```typescript
|
|
28
214
|
const result = await client.rawRequest({
|
|
@@ -33,7 +219,7 @@ const result = await client.rawRequest({
|
|
|
33
219
|
}).then((result) => {
|
|
34
220
|
const patient: Patient = await result.response.json();
|
|
35
221
|
// ...
|
|
36
|
-
}).catch
|
|
222
|
+
}).catch((error) => {
|
|
37
223
|
if (error instanceof ErrorResponse) {
|
|
38
224
|
const outcome = await error.responseWithMeta.response.json
|
|
39
225
|
// ...
|
|
@@ -41,7 +227,7 @@ const result = await client.rawRequest({
|
|
|
41
227
|
});
|
|
42
228
|
```
|
|
43
229
|
|
|
44
|
-
Alternatively,
|
|
230
|
+
Alternatively, the `request` method can be used.
|
|
45
231
|
It returns a `Result<T, OperationOutcome>`, which contains an already parsed result, coerced to the specified type `T`.
|
|
46
232
|
|
|
47
233
|
```typescript
|
|
@@ -63,68 +249,111 @@ if (result.isErr()) {
|
|
|
63
249
|
}
|
|
64
250
|
```
|
|
65
251
|
|
|
66
|
-
Both methods can throw `RequestError` class
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
- [x] `deleteHistoryVersion` - Delete a specific version of a resource
|
|
82
|
-
- [x] `history` - Retrieve the change history for a particular resource
|
|
83
|
-
- [x] Type Level Interaction
|
|
84
|
-
- [x] `create` - Create a new resource with a server assigned id
|
|
85
|
-
- [x] `conditionalCreate` - Create a new resource with a server assigned id if an equivalent resource does not already exist
|
|
86
|
-
- [x] `search` - Search the resource type based on some filter criteria
|
|
87
|
-
- [x] `conditionalDelete` - Conditional delete a single or multiple resources based on some identification criteria
|
|
88
|
-
- [x] `history` - Retrieve the change history for a particular resource type
|
|
89
|
-
- [x] Whole System Interaction
|
|
90
|
-
- [x] `capabilities` - Get a capability statement for the system
|
|
91
|
-
- [x] `batch`/`transaction` - Perform multiple interactions (e.g., create, read, update, delete, patch, and/or [extended operations]) in a single interaction
|
|
92
|
-
- [x] `delete` - Conditional Delete across all resource types based on some filter criteria
|
|
93
|
-
- [x] `history` - Retrieve the change history for all resources
|
|
94
|
-
- [x] `search` - Search across all resource types based on some filter criteria
|
|
95
|
-
- [x] Compartment Interaction
|
|
96
|
-
- [x] `search` - Search resources associated with a specific compartment instance (see [Search Contexts](https://build.fhir.org/search.html#searchcontexts) and [Compartments](https://build.fhir.org/compartmentdefinition.html))
|
|
97
|
-
|
|
98
|
-
<!--
|
|
99
|
-
TODO: Operations
|
|
100
|
-
https://build.fhir.org/operations.html
|
|
101
|
-
-->
|
|
102
|
-
|
|
103
|
-
## Return data format
|
|
104
|
-
|
|
105
|
-
Most client methods return a `Result<T, E>` object, with methods to check if the request was successful:
|
|
252
|
+
Both methods can throw the `RequestError` class if the error happened before the request was actually made.
|
|
253
|
+
|
|
254
|
+
## Authentication Providers
|
|
255
|
+
|
|
256
|
+
Authentication is managed via the `AuthProvider` interface. The client ships with three built-in providers:
|
|
257
|
+
|
|
258
|
+
| Provider | Environment | Auth Method |
|
|
259
|
+
|----------|-------------|-------------|
|
|
260
|
+
| `BrowserAuthProvider` | Browser | Cookie-based sessions |
|
|
261
|
+
| `BasicAuthProvider` | Any | HTTP Basic Auth |
|
|
262
|
+
| `SmartBackendServicesAuthProvider` | Server-side | OAuth 2.0 client_credentials with JWT bearer |
|
|
263
|
+
|
|
264
|
+
### BrowserAuthProvider
|
|
265
|
+
|
|
266
|
+
For browser applications. Uses cookie-based sessions and redirects to the login page on 401.
|
|
106
267
|
|
|
107
268
|
```typescript
|
|
108
|
-
|
|
109
|
-
if (result.isErr())
|
|
110
|
-
throw new Error("error reading Patient", { cause: result.value })
|
|
269
|
+
import { AidboxClient, BrowserAuthProvider } from "@health-samurai/aidbox-client";
|
|
111
270
|
|
|
112
|
-
const
|
|
271
|
+
const baseUrl = "https://fhir-server.address";
|
|
272
|
+
const client = new AidboxClient(baseUrl, new BrowserAuthProvider(baseUrl));
|
|
273
|
+
```
|
|
113
274
|
|
|
114
|
-
|
|
275
|
+
### BasicAuthProvider
|
|
276
|
+
|
|
277
|
+
For server-side applications using HTTP Basic Auth.
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
import { AidboxClient, BasicAuthProvider } from "@health-samurai/aidbox-client";
|
|
281
|
+
|
|
282
|
+
const baseUrl = "https://fhir-server.address";
|
|
283
|
+
const client = new AidboxClient(
|
|
284
|
+
baseUrl,
|
|
285
|
+
new BasicAuthProvider(baseUrl, "username", "password"),
|
|
286
|
+
);
|
|
115
287
|
```
|
|
116
288
|
|
|
117
|
-
|
|
289
|
+
### SmartBackendServicesAuthProvider
|
|
290
|
+
|
|
291
|
+
For server-to-server authentication using [SMART Backend Services](https://www.hl7.org/fhir/smart-app-launch/backend-services.html) (OAuth 2.0 client_credentials grant with JWT bearer assertion).
|
|
292
|
+
|
|
293
|
+
Features:
|
|
294
|
+
- Token caching with proactive refresh before expiry
|
|
295
|
+
- Thundering herd prevention — concurrent requests share a single token fetch
|
|
296
|
+
- Automatic retry on 401 with fresh token
|
|
297
|
+
- OAuth2 discovery from `.well-known/smart-configuration`
|
|
118
298
|
|
|
119
299
|
```typescript
|
|
120
|
-
|
|
300
|
+
import { AidboxClient, SmartBackendServicesAuthProvider } from "@health-samurai/aidbox-client";
|
|
121
301
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
302
|
+
// Generate or import your private key using Web Crypto API
|
|
303
|
+
const privateKey = await crypto.subtle.generateKey(
|
|
304
|
+
{ name: "RSASSA-PKCS1-v1_5", modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-384" },
|
|
305
|
+
true,
|
|
306
|
+
["sign", "verify"]
|
|
307
|
+
).then(kp => kp.privateKey);
|
|
308
|
+
|
|
309
|
+
const auth = new SmartBackendServicesAuthProvider({
|
|
310
|
+
baseUrl: "https://fhir-server.address",
|
|
311
|
+
clientId: "my-service",
|
|
312
|
+
privateKey: privateKey, // CryptoKey from Web Crypto API
|
|
313
|
+
keyId: "key-001", // Must match kid in JWKS
|
|
314
|
+
scope: "system/*.read",
|
|
315
|
+
// tokenExpirationBuffer: 30, // Optional: seconds before expiry to refresh (default: 30)
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
const client = new AidboxClient("https://fhir-server.address", auth);
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Custom Auth Provider
|
|
322
|
+
|
|
323
|
+
For other authentication methods, implement the `AuthProvider` interface:
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
import type { AuthProvider } from "@health-samurai/aidbox-client";
|
|
327
|
+
|
|
328
|
+
export class CustomAuthProvider implements AuthProvider {
|
|
329
|
+
public baseUrl: string;
|
|
330
|
+
|
|
331
|
+
constructor(baseUrl: string) {
|
|
332
|
+
this.baseUrl = baseUrl;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
public async establishSession() {
|
|
336
|
+
/* code to establish a session */
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
public async revokeSession() {
|
|
340
|
+
/* code to revoke the session */
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* A wrapper around the `fetch` function, that does all the
|
|
345
|
+
* necessary preparations and argument patching required for the
|
|
346
|
+
* request to go through.
|
|
347
|
+
*
|
|
348
|
+
* Optionally, security checks can be implemented, like verifying
|
|
349
|
+
* that the request indeed goes to the `baseUrl`, and not
|
|
350
|
+
* somewhere else.
|
|
351
|
+
*/
|
|
352
|
+
public async fetch(
|
|
353
|
+
input: RequestInfo | URL,
|
|
354
|
+
init?: RequestInit,
|
|
355
|
+
): Promise<Response> {
|
|
356
|
+
/* ... */
|
|
357
|
+
}
|
|
358
|
+
}
|
|
130
359
|
```
|
|
@@ -1,8 +1,30 @@
|
|
|
1
1
|
import type { AuthProvider } from "./types";
|
|
2
2
|
export declare class BrowserAuthProvider implements AuthProvider {
|
|
3
3
|
#private;
|
|
4
|
+
/** @ignore */
|
|
4
5
|
baseUrl: string;
|
|
5
6
|
constructor(baseUrl: string);
|
|
7
|
+
/**
|
|
8
|
+
* Checks if the session is already authenticated, and if not, redirects to the login page.
|
|
9
|
+
*/
|
|
10
|
+
establishSession(): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Sends a POST request to `baseurl/auth/logout`.
|
|
13
|
+
*/
|
|
14
|
+
revokeSession(): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* A thin wrapper around `fetch` function.
|
|
17
|
+
* Checks if the client is authorized to perform a request, and redirects to a login page if not.
|
|
18
|
+
*
|
|
19
|
+
* Accepts the same arguments as `fetch`.
|
|
20
|
+
*/
|
|
21
|
+
fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
|
|
22
|
+
}
|
|
23
|
+
export declare class BasicAuthProvider implements AuthProvider {
|
|
24
|
+
#private;
|
|
25
|
+
/** @ignore */
|
|
26
|
+
baseUrl: string;
|
|
27
|
+
constructor(baseUrl: string, username: string, password: string);
|
|
6
28
|
establishSession(): Promise<void>;
|
|
7
29
|
revokeSession(): Promise<void>;
|
|
8
30
|
fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-providers.d.ts","sourceRoot":"","sources":["../../src/auth-providers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"auth-providers.d.ts","sourceRoot":"","sources":["../../src/auth-providers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAG5C,qBAAa,mBAAoB,YAAW,YAAY;;IACvD,cAAc;IACP,OAAO,EAAE,MAAM,CAAC;gBAEX,OAAO,EAAE,MAAM;IAgB3B;;OAEG;IACU,gBAAgB;IAQ7B;;OAEG;IACU,aAAa;IAW1B;;;;;OAKG;IACU,KAAK,CACjB,KAAK,EAAE,WAAW,GAAG,GAAG,EACxB,IAAI,CAAC,EAAE,WAAW,GAChB,OAAO,CAAC,QAAQ,CAAC;CAepB;AAED,qBAAa,iBAAkB,YAAW,YAAY;;IACrD,cAAc;IACP,OAAO,EAAE,MAAM,CAAC;gBAGX,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IASlD,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAIjC,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAI9B,KAAK,CACjB,KAAK,EAAE,WAAW,GAAG,GAAG,EACxB,IAAI,CAAC,EAAE,WAAW,GAChB,OAAO,CAAC,QAAQ,CAAC;CAcpB"}
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
function _array_like_to_array(arr, len) {
|
|
2
|
+
if (len == null || len > arr.length) len = arr.length;
|
|
3
|
+
for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
|
|
4
|
+
return arr2;
|
|
5
|
+
}
|
|
6
|
+
function _array_without_holes(arr) {
|
|
7
|
+
if (Array.isArray(arr)) return _array_like_to_array(arr);
|
|
8
|
+
}
|
|
1
9
|
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
|
|
2
10
|
try {
|
|
3
11
|
var info = gen[key](arg);
|
|
@@ -32,11 +40,46 @@ function _check_private_redeclaration(obj, privateCollection) {
|
|
|
32
40
|
throw new TypeError("Cannot initialize the same private elements twice on an object");
|
|
33
41
|
}
|
|
34
42
|
}
|
|
43
|
+
function _class_apply_descriptor_get(receiver, descriptor) {
|
|
44
|
+
if (descriptor.get) {
|
|
45
|
+
return descriptor.get.call(receiver);
|
|
46
|
+
}
|
|
47
|
+
return descriptor.value;
|
|
48
|
+
}
|
|
49
|
+
function _class_apply_descriptor_set(receiver, descriptor, value) {
|
|
50
|
+
if (descriptor.set) {
|
|
51
|
+
descriptor.set.call(receiver, value);
|
|
52
|
+
} else {
|
|
53
|
+
if (!descriptor.writable) {
|
|
54
|
+
throw new TypeError("attempted to set read only private field");
|
|
55
|
+
}
|
|
56
|
+
descriptor.value = value;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
35
59
|
function _class_call_check(instance, Constructor) {
|
|
36
60
|
if (!(instance instanceof Constructor)) {
|
|
37
61
|
throw new TypeError("Cannot call a class as a function");
|
|
38
62
|
}
|
|
39
63
|
}
|
|
64
|
+
function _class_extract_field_descriptor(receiver, privateMap, action) {
|
|
65
|
+
if (!privateMap.has(receiver)) {
|
|
66
|
+
throw new TypeError("attempted to " + action + " private field on non-instance");
|
|
67
|
+
}
|
|
68
|
+
return privateMap.get(receiver);
|
|
69
|
+
}
|
|
70
|
+
function _class_private_field_get(receiver, privateMap) {
|
|
71
|
+
var descriptor = _class_extract_field_descriptor(receiver, privateMap, "get");
|
|
72
|
+
return _class_apply_descriptor_get(receiver, descriptor);
|
|
73
|
+
}
|
|
74
|
+
function _class_private_field_init(obj, privateMap, value) {
|
|
75
|
+
_check_private_redeclaration(obj, privateMap);
|
|
76
|
+
privateMap.set(obj, value);
|
|
77
|
+
}
|
|
78
|
+
function _class_private_field_set(receiver, privateMap, value) {
|
|
79
|
+
var descriptor = _class_extract_field_descriptor(receiver, privateMap, "set");
|
|
80
|
+
_class_apply_descriptor_set(receiver, descriptor, value);
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
40
83
|
function _class_private_method_get(receiver, privateSet, fn) {
|
|
41
84
|
if (!privateSet.has(receiver)) {
|
|
42
85
|
throw new TypeError("attempted to get private field on non-instance");
|
|
@@ -81,6 +124,23 @@ function _instanceof(left, right) {
|
|
|
81
124
|
return left instanceof right;
|
|
82
125
|
}
|
|
83
126
|
}
|
|
127
|
+
function _iterable_to_array(iter) {
|
|
128
|
+
if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
|
|
129
|
+
}
|
|
130
|
+
function _non_iterable_spread() {
|
|
131
|
+
throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
132
|
+
}
|
|
133
|
+
function _to_consumable_array(arr) {
|
|
134
|
+
return _array_without_holes(arr) || _iterable_to_array(arr) || _unsupported_iterable_to_array(arr) || _non_iterable_spread();
|
|
135
|
+
}
|
|
136
|
+
function _unsupported_iterable_to_array(o, minLen) {
|
|
137
|
+
if (!o) return;
|
|
138
|
+
if (typeof o === "string") return _array_like_to_array(o, minLen);
|
|
139
|
+
var n = Object.prototype.toString.call(o).slice(8, -1);
|
|
140
|
+
if (n === "Object" && o.constructor) n = o.constructor.name;
|
|
141
|
+
if (n === "Map" || n === "Set") return Array.from(n);
|
|
142
|
+
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
|
|
143
|
+
}
|
|
84
144
|
function _ts_generator(thisArg, body) {
|
|
85
145
|
var f, y, t, _ = {
|
|
86
146
|
label: 0,
|
|
@@ -172,19 +232,22 @@ function _ts_generator(thisArg, body) {
|
|
|
172
232
|
};
|
|
173
233
|
}
|
|
174
234
|
}
|
|
235
|
+
import { mergeHeaders, validateBaseUrl } from "./utils";
|
|
175
236
|
var _checkSession = /*#__PURE__*/ new WeakSet();
|
|
176
237
|
export var BrowserAuthProvider = /*#__PURE__*/ function() {
|
|
177
238
|
"use strict";
|
|
178
239
|
function BrowserAuthProvider(baseUrl) {
|
|
179
240
|
_class_call_check(this, BrowserAuthProvider);
|
|
180
241
|
_class_private_method_init(this, _checkSession);
|
|
181
|
-
_define_property(this, "baseUrl", void 0);
|
|
242
|
+
/** @ignore */ _define_property(this, "baseUrl", void 0);
|
|
182
243
|
this.baseUrl = baseUrl;
|
|
183
244
|
}
|
|
184
245
|
_create_class(BrowserAuthProvider, [
|
|
185
246
|
{
|
|
186
247
|
key: "establishSession",
|
|
187
|
-
value:
|
|
248
|
+
value: /**
|
|
249
|
+
* Checks if the session is already authenticated, and if not, redirects to the login page.
|
|
250
|
+
*/ function establishSession() {
|
|
188
251
|
return _async_to_generator(function() {
|
|
189
252
|
var encodedLocation, redirectTo;
|
|
190
253
|
return _ts_generator(this, function(_state) {
|
|
@@ -210,7 +273,9 @@ export var BrowserAuthProvider = /*#__PURE__*/ function() {
|
|
|
210
273
|
},
|
|
211
274
|
{
|
|
212
275
|
key: "revokeSession",
|
|
213
|
-
value:
|
|
276
|
+
value: /**
|
|
277
|
+
* Sends a POST request to `baseurl/auth/logout`.
|
|
278
|
+
*/ function revokeSession() {
|
|
214
279
|
return _async_to_generator(function() {
|
|
215
280
|
return _ts_generator(this, function(_state) {
|
|
216
281
|
switch(_state.label){
|
|
@@ -238,20 +303,23 @@ export var BrowserAuthProvider = /*#__PURE__*/ function() {
|
|
|
238
303
|
},
|
|
239
304
|
{
|
|
240
305
|
key: "fetch",
|
|
241
|
-
value:
|
|
306
|
+
value: /**
|
|
307
|
+
* A thin wrapper around `fetch` function.
|
|
308
|
+
* Checks if the client is authorized to perform a request, and redirects to a login page if not.
|
|
309
|
+
*
|
|
310
|
+
* Accepts the same arguments as `fetch`.
|
|
311
|
+
*/ function fetch1(input, init) {
|
|
242
312
|
return _async_to_generator(function() {
|
|
243
|
-
var
|
|
313
|
+
var requestInit, response;
|
|
244
314
|
return _ts_generator(this, function(_state) {
|
|
245
315
|
switch(_state.label){
|
|
246
316
|
case 0:
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
i = init !== null && init !== void 0 ? init : {};
|
|
251
|
-
i.credentials = "include";
|
|
317
|
+
validateBaseUrl(input, this.baseUrl);
|
|
318
|
+
requestInit = init !== null && init !== void 0 ? init : {};
|
|
319
|
+
requestInit.credentials = "include";
|
|
252
320
|
return [
|
|
253
321
|
4,
|
|
254
|
-
fetch(input,
|
|
322
|
+
fetch(input, requestInit)
|
|
255
323
|
];
|
|
256
324
|
case 1:
|
|
257
325
|
response = _state.sent();
|
|
@@ -282,8 +350,7 @@ export var BrowserAuthProvider = /*#__PURE__*/ function() {
|
|
|
282
350
|
}
|
|
283
351
|
]);
|
|
284
352
|
return BrowserAuthProvider;
|
|
285
|
-
}
|
|
286
|
-
();
|
|
353
|
+
}();
|
|
287
354
|
function checkSession() {
|
|
288
355
|
return _async_to_generator(function() {
|
|
289
356
|
var response;
|
|
@@ -311,3 +378,72 @@ function checkSession() {
|
|
|
311
378
|
});
|
|
312
379
|
}).call(this);
|
|
313
380
|
}
|
|
381
|
+
var _authHeader = /*#__PURE__*/ new WeakMap();
|
|
382
|
+
export var BasicAuthProvider = /*#__PURE__*/ function() {
|
|
383
|
+
"use strict";
|
|
384
|
+
function BasicAuthProvider(baseUrl, username, password) {
|
|
385
|
+
var _String;
|
|
386
|
+
_class_call_check(this, BasicAuthProvider);
|
|
387
|
+
/** @ignore */ _define_property(this, "baseUrl", void 0);
|
|
388
|
+
_class_private_field_init(this, _authHeader, {
|
|
389
|
+
writable: true,
|
|
390
|
+
value: void 0
|
|
391
|
+
});
|
|
392
|
+
this.baseUrl = baseUrl;
|
|
393
|
+
// Create Base64-encoded credentials for Basic Auth header (RFC 7617: UTF-8 encoded)
|
|
394
|
+
var credentials = "".concat(username, ":").concat(password);
|
|
395
|
+
var utf8Bytes = new TextEncoder().encode(credentials);
|
|
396
|
+
var base64 = btoa((_String = String).fromCharCode.apply(_String, _to_consumable_array(utf8Bytes)));
|
|
397
|
+
_class_private_field_set(this, _authHeader, "Basic ".concat(base64));
|
|
398
|
+
}
|
|
399
|
+
_create_class(BasicAuthProvider, [
|
|
400
|
+
{
|
|
401
|
+
key: "establishSession",
|
|
402
|
+
value: function establishSession() {
|
|
403
|
+
return _async_to_generator(function() {
|
|
404
|
+
return _ts_generator(this, function(_state) {
|
|
405
|
+
return [
|
|
406
|
+
2
|
|
407
|
+
];
|
|
408
|
+
});
|
|
409
|
+
// No-op for basic auth - credentials are sent with each request
|
|
410
|
+
})();
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
key: "revokeSession",
|
|
415
|
+
value: function revokeSession() {
|
|
416
|
+
return _async_to_generator(function() {
|
|
417
|
+
return _ts_generator(this, function(_state) {
|
|
418
|
+
return [
|
|
419
|
+
2
|
|
420
|
+
];
|
|
421
|
+
});
|
|
422
|
+
// No-op for basic auth - stateless authentication
|
|
423
|
+
})();
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
key: "fetch",
|
|
428
|
+
value: function fetch1(input, init) {
|
|
429
|
+
return _async_to_generator(function() {
|
|
430
|
+
var requestInit, baseHeaders, initHeaders, headers;
|
|
431
|
+
return _ts_generator(this, function(_state) {
|
|
432
|
+
validateBaseUrl(input, this.baseUrl);
|
|
433
|
+
requestInit = init !== null && init !== void 0 ? init : {};
|
|
434
|
+
baseHeaders = _instanceof(input, Request) ? input.headers : undefined;
|
|
435
|
+
initHeaders = requestInit.headers ? new Headers(requestInit.headers) : undefined;
|
|
436
|
+
headers = mergeHeaders(baseHeaders, initHeaders);
|
|
437
|
+
headers.set("Authorization", _class_private_field_get(this, _authHeader));
|
|
438
|
+
requestInit.headers = headers;
|
|
439
|
+
return [
|
|
440
|
+
2,
|
|
441
|
+
fetch(input, requestInit)
|
|
442
|
+
];
|
|
443
|
+
});
|
|
444
|
+
}).call(this);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
]);
|
|
448
|
+
return BasicAuthProvider;
|
|
449
|
+
}();
|