@ovok/core 0.4.49-dev.496 → 0.4.49-dev.497
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/conformance/capability-requirements.js +1 -1
- package/dist/next/auth/saved-accounts-store.d.ts +26 -0
- package/dist/next/auth/saved-accounts-store.js +93 -0
- package/dist/next/auth/user-session.d.ts +13 -1
- package/dist/next/auth/user-session.js +106 -5
- package/dist/next/errors/ovok-account-error.d.ts +5 -0
- package/dist/next/errors/ovok-account-error.js +13 -0
- package/dist/next/http/http-client.d.ts +1 -0
- package/dist/next/http/http-client.js +3 -0
- package/dist/next/index.d.ts +5 -0
- package/dist/next/index.js +3 -1
- package/dist/next/ovok.d.ts +2 -0
- package/dist/next/ovok.js +6 -3
- package/dist/next/resources/invites/invites-resource.d.ts +6 -1
- package/dist/next/resources/invites/invites-resource.js +16 -1
- package/dist/next/resources/session/saved-accounts-resource.d.ts +13 -0
- package/dist/next/resources/session/saved-accounts-resource.js +44 -0
- package/dist/next/resources/session/session-resource.d.ts +11 -0
- package/dist/next/resources/session/session-resource.js +15 -0
- package/dist/next/resources/slim/live-reads.d.ts +1 -0
- package/dist/next/resources/slim/live-reads.js +13 -2
- package/dist/next/resources/slim/slim-devices-resource.d.ts +2 -1
- package/dist/next/resources/slim/slim-devices-resource.js +2 -1
- package/dist/next/resources/slim/slim-resource.d.ts +2 -1
- package/dist/next/resources/slim/slim-resource.js +2 -2
- package/package.json +1 -1
|
@@ -48,7 +48,7 @@ exports.OVOK_CORE_REQUIREMENTS = {
|
|
|
48
48
|
description: "The FHIR surface a server must provide for the @ovok/core client library's own methods to function.",
|
|
49
49
|
software: {
|
|
50
50
|
name: "@ovok/core",
|
|
51
|
-
version: "0.4.49-dev.
|
|
51
|
+
version: "0.4.49-dev.497+7201066",
|
|
52
52
|
},
|
|
53
53
|
fhirVersion: "4.0.1",
|
|
54
54
|
format: ["json"],
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { OvokStorage } from "../storage/ovok-storage";
|
|
2
|
+
import { OvokReference, Session } from "./session";
|
|
3
|
+
export type SavedAccount = {
|
|
4
|
+
id: string;
|
|
5
|
+
profile: OvokReference;
|
|
6
|
+
project: OvokReference;
|
|
7
|
+
active: boolean;
|
|
8
|
+
expired: boolean;
|
|
9
|
+
};
|
|
10
|
+
export type StoredSavedAccount = {
|
|
11
|
+
session: Session;
|
|
12
|
+
expired: boolean;
|
|
13
|
+
};
|
|
14
|
+
export declare class SavedAccountsStore {
|
|
15
|
+
private readonly storage;
|
|
16
|
+
private readonly key;
|
|
17
|
+
constructor(storage: OvokStorage, key: string);
|
|
18
|
+
list(activeAccountId: string | null): Promise<SavedAccount[]>;
|
|
19
|
+
get(accountId: string): Promise<StoredSavedAccount | null>;
|
|
20
|
+
save(session: Session): Promise<void>;
|
|
21
|
+
markExpired(accountId: string): Promise<void>;
|
|
22
|
+
remove(accountId: string): Promise<void>;
|
|
23
|
+
private read;
|
|
24
|
+
private write;
|
|
25
|
+
}
|
|
26
|
+
export declare const savedAccountsStoreFor: (storage: OvokStorage, tenantCode: string) => SavedAccountsStore;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.savedAccountsStoreFor = exports.SavedAccountsStore = void 0;
|
|
4
|
+
class SavedAccountsStore {
|
|
5
|
+
constructor(storage, key) {
|
|
6
|
+
this.storage = storage;
|
|
7
|
+
this.key = key;
|
|
8
|
+
}
|
|
9
|
+
async list(activeAccountId) {
|
|
10
|
+
return (await this.read()).map(({ session, expired }) => ({
|
|
11
|
+
id: session.profile.reference,
|
|
12
|
+
profile: session.profile,
|
|
13
|
+
project: session.project,
|
|
14
|
+
active: session.profile.reference === activeAccountId,
|
|
15
|
+
expired,
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
18
|
+
async get(accountId) {
|
|
19
|
+
var _a;
|
|
20
|
+
return (_a = (await this.read()).find(({ session }) => session.profile.reference === accountId)) !== null && _a !== void 0 ? _a : null;
|
|
21
|
+
}
|
|
22
|
+
async save(session) {
|
|
23
|
+
const accounts = await this.read();
|
|
24
|
+
const saved = { session, expired: false };
|
|
25
|
+
const existingIndex = accounts.findIndex((account) => account.session.profile.reference === session.profile.reference);
|
|
26
|
+
if (existingIndex === -1) {
|
|
27
|
+
accounts.unshift(saved);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
accounts[existingIndex] = saved;
|
|
31
|
+
}
|
|
32
|
+
await this.write(accounts);
|
|
33
|
+
}
|
|
34
|
+
async markExpired(accountId) {
|
|
35
|
+
const accounts = await this.read();
|
|
36
|
+
const account = accounts.find(({ session }) => session.profile.reference === accountId);
|
|
37
|
+
if (!account) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
account.session = { ...account.session, accessToken: "", refreshToken: null };
|
|
41
|
+
account.expired = true;
|
|
42
|
+
await this.write(accounts);
|
|
43
|
+
}
|
|
44
|
+
async remove(accountId) {
|
|
45
|
+
const accounts = await this.read();
|
|
46
|
+
await this.write(accounts.filter(({ session }) => session.profile.reference !== accountId));
|
|
47
|
+
}
|
|
48
|
+
async read() {
|
|
49
|
+
const raw = await this.storage.getItem(this.key);
|
|
50
|
+
if (raw === null) {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const value = JSON.parse(raw);
|
|
55
|
+
return Array.isArray(value) ? value.filter(isStoredSavedAccount) : [];
|
|
56
|
+
}
|
|
57
|
+
catch (_a) {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async write(accounts) {
|
|
62
|
+
if (accounts.length === 0) {
|
|
63
|
+
await this.storage.removeItem(this.key);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
await this.storage.setItem(this.key, JSON.stringify(accounts));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
exports.SavedAccountsStore = SavedAccountsStore;
|
|
70
|
+
const savedAccountsStoreFor = (storage, tenantCode) => new SavedAccountsStore(storage, `ovok:${tenantCode}:saved-accounts`);
|
|
71
|
+
exports.savedAccountsStoreFor = savedAccountsStoreFor;
|
|
72
|
+
const isStoredSavedAccount = (value) => {
|
|
73
|
+
if (typeof value !== "object" || value === null || !("session" in value)) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
const record = value;
|
|
77
|
+
const session = record.session;
|
|
78
|
+
if (typeof session !== "object" || session === null || typeof record.expired !== "boolean") {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
const candidate = session;
|
|
82
|
+
return (typeof candidate.accessToken === "string" &&
|
|
83
|
+
(candidate.refreshToken === null || typeof candidate.refreshToken === "string") &&
|
|
84
|
+
typeof candidate.expiresAt === "number" &&
|
|
85
|
+
isReference(candidate.profile) &&
|
|
86
|
+
isReference(candidate.project));
|
|
87
|
+
};
|
|
88
|
+
const isReference = (value) => typeof value === "object" &&
|
|
89
|
+
value !== null &&
|
|
90
|
+
"reference" in value &&
|
|
91
|
+
typeof value.reference === "string" &&
|
|
92
|
+
"display" in value &&
|
|
93
|
+
(typeof value.display === "string" || value.display === null);
|
|
@@ -1,21 +1,32 @@
|
|
|
1
1
|
import { OvokSettings } from "../config/ovok-config";
|
|
2
2
|
import { Clock } from "./access-token";
|
|
3
3
|
import { Credentials } from "./credentials";
|
|
4
|
+
import { SavedAccount, SavedAccountsStore, StoredSavedAccount } from "./saved-accounts-store";
|
|
4
5
|
import { LoginTokens, Session } from "./session";
|
|
5
6
|
import { SessionStore } from "./session-store";
|
|
6
7
|
import { TokenEndpoint } from "./token-endpoint";
|
|
7
8
|
type SessionSettings = Pick<OvokSettings, "clientId" | "onUnauthenticated">;
|
|
9
|
+
export type SessionChangeListener = (session: Session | null) => void;
|
|
8
10
|
export declare class UserSession implements Credentials {
|
|
9
11
|
private readonly store;
|
|
10
12
|
private readonly endpoint;
|
|
11
13
|
private readonly settings;
|
|
12
14
|
private readonly now;
|
|
15
|
+
private readonly savedAccounts;
|
|
13
16
|
private loaded;
|
|
14
17
|
private pendingRefresh;
|
|
15
|
-
|
|
18
|
+
private readonly changeListeners;
|
|
19
|
+
constructor(store: SessionStore, endpoint: TokenEndpoint, settings: SessionSettings, now?: Clock, savedAccounts?: SavedAccountsStore | null);
|
|
16
20
|
current(): Promise<Session | null>;
|
|
21
|
+
onChange(listener: SessionChangeListener): () => void;
|
|
17
22
|
begin(tokens: LoginTokens): Promise<Session>;
|
|
18
23
|
end(): Promise<void>;
|
|
24
|
+
listSavedAccounts(): Promise<SavedAccount[]>;
|
|
25
|
+
getSavedAccount(accountId: string): Promise<StoredSavedAccount | null>;
|
|
26
|
+
switchSavedAccount(accountId: string): Promise<void>;
|
|
27
|
+
removeSavedAccount(accountId: string): Promise<void>;
|
|
28
|
+
markSavedAccountExpired(accountId: string): Promise<void>;
|
|
29
|
+
forSavedAccount(session: Session): UserSession;
|
|
19
30
|
accessToken(): Promise<string | null>;
|
|
20
31
|
refresh(): Promise<boolean>;
|
|
21
32
|
private exchangeRefreshToken;
|
|
@@ -23,5 +34,6 @@ export declare class UserSession implements Credentials {
|
|
|
23
34
|
private signOutIfRejected;
|
|
24
35
|
private refreshForm;
|
|
25
36
|
private save;
|
|
37
|
+
private notifyChange;
|
|
26
38
|
}
|
|
27
39
|
export {};
|
|
@@ -1,31 +1,120 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.UserSession = void 0;
|
|
4
|
+
const ovok_account_error_1 = require("../errors/ovok-account-error");
|
|
4
5
|
const ovok_errors_1 = require("../errors/ovok-errors");
|
|
6
|
+
const ovok_storage_1 = require("../storage/ovok-storage");
|
|
5
7
|
const session_1 = require("./session");
|
|
8
|
+
const session_store_1 = require("./session-store");
|
|
6
9
|
const REJECTED_REFRESH_STATUSES = new Set([400, 401, 403]);
|
|
7
10
|
class UserSession {
|
|
8
|
-
constructor(store, endpoint, settings, now = Date.now) {
|
|
11
|
+
constructor(store, endpoint, settings, now = Date.now, savedAccounts = null) {
|
|
9
12
|
this.store = store;
|
|
10
13
|
this.endpoint = endpoint;
|
|
11
14
|
this.settings = settings;
|
|
12
15
|
this.now = now;
|
|
16
|
+
this.savedAccounts = savedAccounts;
|
|
13
17
|
this.loaded = null;
|
|
14
18
|
this.pendingRefresh = null;
|
|
19
|
+
this.changeListeners = new Set();
|
|
15
20
|
}
|
|
16
21
|
current() {
|
|
17
|
-
|
|
18
|
-
|
|
22
|
+
if (this.loaded === null) {
|
|
23
|
+
const pending = this.store.load().catch((error) => {
|
|
24
|
+
if (this.loaded === pending) {
|
|
25
|
+
this.loaded = null;
|
|
26
|
+
}
|
|
27
|
+
throw error;
|
|
28
|
+
});
|
|
29
|
+
this.loaded = pending;
|
|
30
|
+
}
|
|
19
31
|
return this.loaded;
|
|
20
32
|
}
|
|
33
|
+
onChange(listener) {
|
|
34
|
+
this.changeListeners.add(listener);
|
|
35
|
+
return () => this.changeListeners.delete(listener);
|
|
36
|
+
}
|
|
21
37
|
async begin(tokens) {
|
|
22
38
|
const session = (0, session_1.sessionFromLogin)(tokens, this.now());
|
|
23
39
|
await this.save(session);
|
|
40
|
+
this.notifyChange(session);
|
|
24
41
|
return session;
|
|
25
42
|
}
|
|
26
43
|
async end() {
|
|
44
|
+
var _a;
|
|
45
|
+
const current = await this.current().catch(() => null);
|
|
27
46
|
this.loaded = Promise.resolve(null);
|
|
28
47
|
await this.store.clear();
|
|
48
|
+
if (current) {
|
|
49
|
+
await ((_a = this.savedAccounts) === null || _a === void 0 ? void 0 : _a.remove(current.profile.reference));
|
|
50
|
+
}
|
|
51
|
+
this.notifyChange(null);
|
|
52
|
+
}
|
|
53
|
+
async listSavedAccounts() {
|
|
54
|
+
var _a;
|
|
55
|
+
const current = await this.current();
|
|
56
|
+
const currentAccountId = (_a = current === null || current === void 0 ? void 0 : current.profile.reference) !== null && _a !== void 0 ? _a : null;
|
|
57
|
+
if (this.savedAccounts === null) {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
const accounts = await this.savedAccounts.list(currentAccountId);
|
|
61
|
+
if (current && !accounts.some((account) => account.active)) {
|
|
62
|
+
await this.savedAccounts.save(current);
|
|
63
|
+
return this.savedAccounts.list(currentAccountId);
|
|
64
|
+
}
|
|
65
|
+
return accounts;
|
|
66
|
+
}
|
|
67
|
+
async getSavedAccount(accountId) {
|
|
68
|
+
var _a, _b;
|
|
69
|
+
return (_b = (_a = this.savedAccounts) === null || _a === void 0 ? void 0 : _a.get(accountId)) !== null && _b !== void 0 ? _b : null;
|
|
70
|
+
}
|
|
71
|
+
async switchSavedAccount(accountId) {
|
|
72
|
+
const account = await this.getSavedAccount(accountId);
|
|
73
|
+
if (account === null) {
|
|
74
|
+
throw new ovok_account_error_1.OvokAccountError("account_not_found");
|
|
75
|
+
}
|
|
76
|
+
if (account.expired) {
|
|
77
|
+
throw new ovok_account_error_1.OvokAccountError("account_expired");
|
|
78
|
+
}
|
|
79
|
+
const current = await this.current();
|
|
80
|
+
if ((current === null || current === void 0 ? void 0 : current.profile.reference) === accountId) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
await this.store.save(account.session);
|
|
84
|
+
this.loaded = Promise.resolve(account.session);
|
|
85
|
+
this.notifyChange(account.session);
|
|
86
|
+
}
|
|
87
|
+
async removeSavedAccount(accountId) {
|
|
88
|
+
var _a, _b, _c;
|
|
89
|
+
const account = await this.getSavedAccount(accountId);
|
|
90
|
+
if (account === null) {
|
|
91
|
+
throw new ovok_account_error_1.OvokAccountError("account_not_found");
|
|
92
|
+
}
|
|
93
|
+
const current = await this.current();
|
|
94
|
+
const wasActive = (current === null || current === void 0 ? void 0 : current.profile.reference) === accountId;
|
|
95
|
+
if (wasActive) {
|
|
96
|
+
await this.store.clear();
|
|
97
|
+
this.loaded = Promise.resolve(null);
|
|
98
|
+
}
|
|
99
|
+
await ((_a = this.savedAccounts) === null || _a === void 0 ? void 0 : _a.remove(accountId));
|
|
100
|
+
if (wasActive) {
|
|
101
|
+
const nextAccount = ((_c = await ((_b = this.savedAccounts) === null || _b === void 0 ? void 0 : _b.list(null))) !== null && _c !== void 0 ? _c : []).find((saved) => !saved.expired);
|
|
102
|
+
if (nextAccount) {
|
|
103
|
+
await this.switchSavedAccount(nextAccount.id);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
this.notifyChange(null);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async markSavedAccountExpired(accountId) {
|
|
111
|
+
var _a;
|
|
112
|
+
await ((_a = this.savedAccounts) === null || _a === void 0 ? void 0 : _a.markExpired(accountId));
|
|
113
|
+
}
|
|
114
|
+
forSavedAccount(session) {
|
|
115
|
+
const isolated = new UserSession(new session_store_1.SessionStore(new ovok_storage_1.MemoryStorage(), "ovok:isolated-session"), this.endpoint, { ...this.settings, onUnauthenticated: () => undefined }, this.now);
|
|
116
|
+
isolated.loaded = Promise.resolve(session);
|
|
117
|
+
return isolated;
|
|
29
118
|
}
|
|
30
119
|
async accessToken() {
|
|
31
120
|
var _a, _b;
|
|
@@ -61,10 +150,17 @@ class UserSession {
|
|
|
61
150
|
}
|
|
62
151
|
/** Only a refused refresh token signs the user out; an outage keeps the session for a later retry. */
|
|
63
152
|
async signOutIfRejected(error) {
|
|
153
|
+
var _a;
|
|
64
154
|
const status = error instanceof ovok_errors_1.OvokError ? error.status : null;
|
|
65
155
|
const rejected = status !== null && REJECTED_REFRESH_STATUSES.has(status);
|
|
66
156
|
if (rejected) {
|
|
67
|
-
await this.
|
|
157
|
+
const current = await this.current();
|
|
158
|
+
if (current) {
|
|
159
|
+
await ((_a = this.savedAccounts) === null || _a === void 0 ? void 0 : _a.markExpired(current.profile.reference));
|
|
160
|
+
}
|
|
161
|
+
this.loaded = Promise.resolve(null);
|
|
162
|
+
await this.store.clear();
|
|
163
|
+
this.notifyChange(null);
|
|
68
164
|
this.settings.onUnauthenticated({ type: "refresh-rejected", status });
|
|
69
165
|
return false;
|
|
70
166
|
}
|
|
@@ -75,8 +171,13 @@ class UserSession {
|
|
|
75
171
|
return { grant_type: "refresh_token", refresh_token: refreshToken, ...client };
|
|
76
172
|
}
|
|
77
173
|
async save(session) {
|
|
78
|
-
|
|
174
|
+
var _a;
|
|
79
175
|
await this.store.save(session);
|
|
176
|
+
await ((_a = this.savedAccounts) === null || _a === void 0 ? void 0 : _a.save(session));
|
|
177
|
+
this.loaded = Promise.resolve(session);
|
|
178
|
+
}
|
|
179
|
+
notifyChange(session) {
|
|
180
|
+
this.changeListeners.forEach((listener) => listener(session));
|
|
80
181
|
}
|
|
81
182
|
}
|
|
82
183
|
exports.UserSession = UserSession;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OvokAccountError = void 0;
|
|
4
|
+
class OvokAccountError extends Error {
|
|
5
|
+
constructor(code) {
|
|
6
|
+
super(code === "account_not_found"
|
|
7
|
+
? "Saved account was not found."
|
|
8
|
+
: "This account's session has expired. Sign in again.");
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.name = "OvokAccountError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.OvokAccountError = OvokAccountError;
|
|
@@ -21,6 +21,7 @@ export declare class HttpClient {
|
|
|
21
21
|
constructor(context: HttpContext, basePath?: string);
|
|
22
22
|
under(path: string): HttpClient;
|
|
23
23
|
anonymous(): HttpClient;
|
|
24
|
+
withCredentials(credentials: Credentials): HttpClient;
|
|
24
25
|
get<T>(path: string, options?: RequestOptions): Promise<T>;
|
|
25
26
|
post<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T>;
|
|
26
27
|
put<T>(path: string, body: unknown, options?: RequestOptions): Promise<T>;
|
|
@@ -20,6 +20,9 @@ class HttpClient {
|
|
|
20
20
|
anonymous() {
|
|
21
21
|
return new HttpClient({ ...this.context, credentials: credentials_1.noCredentials }, this.basePath);
|
|
22
22
|
}
|
|
23
|
+
withCredentials(credentials) {
|
|
24
|
+
return new HttpClient({ ...this.context, credentials }, this.basePath);
|
|
25
|
+
}
|
|
23
26
|
get(path, options) {
|
|
24
27
|
return this.send({ method: "GET", path }, options);
|
|
25
28
|
}
|
package/dist/next/index.d.ts
CHANGED
|
@@ -3,11 +3,14 @@ export type { OvokConfig, OvokFetch, OvokUnauthenticatedReason } from "./config/
|
|
|
3
3
|
export { MemoryStorage } from "./storage/ovok-storage";
|
|
4
4
|
export type { OvokStorage } from "./storage/ovok-storage";
|
|
5
5
|
export { OvokAuthenticationError, OvokConfigError, OvokConflictError, OvokConnectionError, OvokError, OvokNotFoundError, OvokPermissionError, OvokRateLimitError, OvokServiceError, OvokValidationError, } from "./errors/ovok-errors";
|
|
6
|
+
export { OvokAccountError } from "./errors/ovok-account-error";
|
|
6
7
|
export type { OvokErrorDetails } from "./errors/ovok-errors";
|
|
7
8
|
export type { RequestOptions } from "./http/transmit";
|
|
8
9
|
export type { QueryParams, QueryValue } from "./http/url";
|
|
9
10
|
export type { Page, PagePromise } from "./paging/page";
|
|
10
11
|
export type { OvokReference, Session } from "./auth/session";
|
|
12
|
+
export type { SavedAccount } from "./auth/saved-accounts-store";
|
|
13
|
+
export type { SessionChangeListener } from "./auth/user-session";
|
|
11
14
|
export type { LoginCredentials, LoginResult, MfaChallenge, ProfileType } from "./resources/auth-resource";
|
|
12
15
|
export type { CreateOptions, PatchOperation } from "./resources/fhir-resource";
|
|
13
16
|
export type { HealthCheck } from "./resources/health-resource";
|
|
@@ -47,6 +50,8 @@ export type { PatientResource } from "./resources/patient/patient-resource";
|
|
|
47
50
|
export type { ProjectsResource } from "./resources/projects/projects-resource";
|
|
48
51
|
export type { QuestionnaireResponsesResource, QuestionnairesResource, } from "./resources/questionnaires/questionnaires-resource";
|
|
49
52
|
export type { SharingResource } from "./resources/sharing/sharing-resource";
|
|
53
|
+
export type { SavedAccountsResource } from "./resources/session/saved-accounts-resource";
|
|
54
|
+
export type { SessionResource } from "./resources/session/session-resource";
|
|
50
55
|
export type { SignalsResource } from "./resources/signals/signals-resource";
|
|
51
56
|
export type { SignalsGatewayRequest } from "./resources/signals/signals-gateway-resource";
|
|
52
57
|
export type { SlimResource } from "./resources/slim/slim-resource";
|
package/dist/next/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.OvokValidationError = exports.OvokServiceError = exports.OvokRateLimitError = exports.OvokPermissionError = exports.OvokNotFoundError = exports.OvokError = exports.OvokConnectionError = exports.OvokConflictError = exports.OvokConfigError = exports.OvokAuthenticationError = exports.MemoryStorage = exports.Ovok = void 0;
|
|
17
|
+
exports.OvokAccountError = exports.OvokValidationError = exports.OvokServiceError = exports.OvokRateLimitError = exports.OvokPermissionError = exports.OvokNotFoundError = exports.OvokError = exports.OvokConnectionError = exports.OvokConflictError = exports.OvokConfigError = exports.OvokAuthenticationError = exports.MemoryStorage = exports.Ovok = void 0;
|
|
18
18
|
var ovok_1 = require("./ovok");
|
|
19
19
|
Object.defineProperty(exports, "Ovok", { enumerable: true, get: function () { return ovok_1.Ovok; } });
|
|
20
20
|
var ovok_storage_1 = require("./storage/ovok-storage");
|
|
@@ -30,4 +30,6 @@ Object.defineProperty(exports, "OvokPermissionError", { enumerable: true, get: f
|
|
|
30
30
|
Object.defineProperty(exports, "OvokRateLimitError", { enumerable: true, get: function () { return ovok_errors_1.OvokRateLimitError; } });
|
|
31
31
|
Object.defineProperty(exports, "OvokServiceError", { enumerable: true, get: function () { return ovok_errors_1.OvokServiceError; } });
|
|
32
32
|
Object.defineProperty(exports, "OvokValidationError", { enumerable: true, get: function () { return ovok_errors_1.OvokValidationError; } });
|
|
33
|
+
var ovok_account_error_1 = require("./errors/ovok-account-error");
|
|
34
|
+
Object.defineProperty(exports, "OvokAccountError", { enumerable: true, get: function () { return ovok_account_error_1.OvokAccountError; } });
|
|
33
35
|
__exportStar(require("../types/MeasurementTypes"), exports);
|
package/dist/next/ovok.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { MeasurementsResource } from "./resources/measurements/measurements-reso
|
|
|
17
17
|
import { PatientResource } from "./resources/patient/patient-resource";
|
|
18
18
|
import { ProjectsResource } from "./resources/projects/projects-resource";
|
|
19
19
|
import { QuestionnaireResponsesResource, QuestionnairesResource } from "./resources/questionnaires/questionnaires-resource";
|
|
20
|
+
import { SessionResource } from "./resources/session/session-resource";
|
|
20
21
|
import { SharingResource } from "./resources/sharing/sharing-resource";
|
|
21
22
|
import { SignalsResource } from "./resources/signals/signals-resource";
|
|
22
23
|
import { SlimResource } from "./resources/slim/slim-resource";
|
|
@@ -25,6 +26,7 @@ import { WearablesResource } from "./resources/wearables/wearables-resource";
|
|
|
25
26
|
/** One client per tenant; each namespace mirrors an area of the Ovok API. */
|
|
26
27
|
export declare class Ovok {
|
|
27
28
|
readonly auth: AuthResource;
|
|
29
|
+
readonly session: SessionResource;
|
|
28
30
|
readonly fhir: FhirResource;
|
|
29
31
|
readonly health: HealthResource;
|
|
30
32
|
readonly patient: PatientResource;
|
package/dist/next/ovok.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Ovok = void 0;
|
|
4
4
|
const client_credentials_1 = require("./auth/client-credentials");
|
|
5
|
+
const saved_accounts_store_1 = require("./auth/saved-accounts-store");
|
|
5
6
|
const session_store_1 = require("./auth/session-store");
|
|
6
7
|
const token_endpoint_1 = require("./auth/token-endpoint");
|
|
7
8
|
const user_session_1 = require("./auth/user-session");
|
|
@@ -26,6 +27,7 @@ const measurements_resource_1 = require("./resources/measurements/measurements-r
|
|
|
26
27
|
const patient_resource_1 = require("./resources/patient/patient-resource");
|
|
27
28
|
const projects_resource_1 = require("./resources/projects/projects-resource");
|
|
28
29
|
const questionnaires_resource_1 = require("./resources/questionnaires/questionnaires-resource");
|
|
30
|
+
const session_resource_1 = require("./resources/session/session-resource");
|
|
29
31
|
const sharing_resource_1 = require("./resources/sharing/sharing-resource");
|
|
30
32
|
const signals_resource_1 = require("./resources/signals/signals-resource");
|
|
31
33
|
const slim_resource_1 = require("./resources/slim/slim-resource");
|
|
@@ -40,6 +42,7 @@ class Ovok {
|
|
|
40
42
|
const session = credentials instanceof user_session_1.UserSession ? credentials : null;
|
|
41
43
|
const fhir = new fhir_resource_1.FhirResource(http.under(settings.fhirPath));
|
|
42
44
|
this.auth = new auth_resource_1.AuthResource(http, settings.tenantCode, session, settings.clientId);
|
|
45
|
+
this.session = new session_resource_1.SessionResource(http, session);
|
|
43
46
|
this.fhir = fhir;
|
|
44
47
|
this.health = new health_resource_1.HealthResource(http.anonymous());
|
|
45
48
|
this.patient = new patient_resource_1.PatientResource(http, fhir);
|
|
@@ -47,10 +50,10 @@ class Ovok {
|
|
|
47
50
|
this.questionnaires = new questionnaires_resource_1.QuestionnairesResource(http);
|
|
48
51
|
this.questionnaireResponses = new questionnaires_resource_1.QuestionnaireResponsesResource(http, fhir);
|
|
49
52
|
this.consent = new consent_resource_1.ConsentResource(http);
|
|
50
|
-
this.invites = new invites_resource_1.InvitesResource(http);
|
|
53
|
+
this.invites = new invites_resource_1.InvitesResource(http, session);
|
|
51
54
|
this.sharing = new sharing_resource_1.SharingResource(http);
|
|
52
55
|
this.signals = new signals_resource_1.SignalsResource(http);
|
|
53
|
-
this.slim = new slim_resource_1.SlimResource(http);
|
|
56
|
+
this.slim = new slim_resource_1.SlimResource(http, session);
|
|
54
57
|
this.projects = new projects_resource_1.ProjectsResource(http);
|
|
55
58
|
this.accounts = new accounts_resource_1.AccountsResource(http);
|
|
56
59
|
this.locales = new locales_resource_1.LocalesResource(http);
|
|
@@ -70,5 +73,5 @@ const credentialsFor = (settings) => {
|
|
|
70
73
|
const tokenEndpoint = new token_endpoint_1.TokenEndpoint(settings);
|
|
71
74
|
return settings.machineClient
|
|
72
75
|
? new client_credentials_1.ClientCredentials(tokenEndpoint, settings.machineClient)
|
|
73
|
-
: new user_session_1.UserSession((0, session_store_1.sessionStoreFor)(settings.storage, settings.tenantCode), tokenEndpoint, settings);
|
|
76
|
+
: new user_session_1.UserSession((0, session_store_1.sessionStoreFor)(settings.storage, settings.tenantCode), tokenEndpoint, settings, Date.now, (0, saved_accounts_store_1.savedAccountsStoreFor)(settings.storage, settings.tenantCode));
|
|
74
77
|
};
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { AcceptInviteBody, AcceptRecordShareBody, InviteBody, RecordShareAccepted, RecordShareBody } from "../../../client/invites/types";
|
|
2
2
|
import type { PatientSharingPractitionerShare } from "../../../client/patient-sharing/types";
|
|
3
|
+
import type { UserSession } from "../../auth/user-session";
|
|
3
4
|
import { HttpClient } from "../../http/http-client";
|
|
5
|
+
import { RequestOptions } from "../../http/transmit";
|
|
4
6
|
import { InviteFlowResource } from "./invite-flow-resource";
|
|
5
7
|
export type RecordShareFlow = InviteFlowResource<RecordShareBody, AcceptRecordShareBody, RecordShareAccepted>;
|
|
6
8
|
export type PersonInviteFlow = InviteFlowResource<InviteBody, AcceptInviteBody, PatientSharingPractitionerShare>;
|
|
7
9
|
export declare class InvitesResource {
|
|
10
|
+
private readonly session;
|
|
8
11
|
/** A patient shares their record with a practitioner, or some codes with a carer. */
|
|
9
12
|
readonly recordShares: RecordShareFlow;
|
|
10
13
|
/** A practitioner invites a patient into their care. */
|
|
@@ -13,5 +16,7 @@ export declare class InvitesResource {
|
|
|
13
16
|
readonly practitioners: PersonInviteFlow;
|
|
14
17
|
/** @deprecated Use `care`; the route sunsets on 2026-12-01. */
|
|
15
18
|
readonly patients: PersonInviteFlow;
|
|
16
|
-
constructor(http: HttpClient);
|
|
19
|
+
constructor(http: HttpClient, session?: UserSession | null);
|
|
20
|
+
/** Accepts a record share as a practitioner or a care invite as a patient. */
|
|
21
|
+
accept(body: AcceptInviteBody, options?: RequestOptions): Promise<PatientSharingPractitionerShare>;
|
|
17
22
|
}
|
|
@@ -1,14 +1,29 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.InvitesResource = void 0;
|
|
4
|
+
const ovok_errors_1 = require("../../errors/ovok-errors");
|
|
4
5
|
const invite_flow_resource_1 = require("./invite-flow-resource");
|
|
5
6
|
const INVITES_PATH = "v1/invites";
|
|
6
7
|
class InvitesResource {
|
|
7
|
-
constructor(http) {
|
|
8
|
+
constructor(http, session = null) {
|
|
9
|
+
this.session = session;
|
|
8
10
|
this.recordShares = new invite_flow_resource_1.InviteFlowResource(http, `${INVITES_PATH}/record-share`);
|
|
9
11
|
this.care = new invite_flow_resource_1.InviteFlowResource(http, `${INVITES_PATH}/care`);
|
|
10
12
|
this.practitioners = new invite_flow_resource_1.InviteFlowResource(http, `${INVITES_PATH}/practitioner`);
|
|
11
13
|
this.patients = new invite_flow_resource_1.InviteFlowResource(http, `${INVITES_PATH}/patient`);
|
|
12
14
|
}
|
|
15
|
+
/** Accepts a record share as a practitioner or a care invite as a patient. */
|
|
16
|
+
async accept(body, options) {
|
|
17
|
+
var _a, _b;
|
|
18
|
+
const profileReference = (_b = (await ((_a = this.session) === null || _a === void 0 ? void 0 : _a.current()))) === null || _b === void 0 ? void 0 : _b.profile.reference;
|
|
19
|
+
const profileType = profileReference === null || profileReference === void 0 ? void 0 : profileReference.split("/", 1)[0];
|
|
20
|
+
if (profileType === "Practitioner") {
|
|
21
|
+
return this.recordShares.accept(body, options);
|
|
22
|
+
}
|
|
23
|
+
if (profileType === "Patient") {
|
|
24
|
+
return this.care.accept(body, options);
|
|
25
|
+
}
|
|
26
|
+
throw new ovok_errors_1.OvokConfigError((0, ovok_errors_1.detailsFor)("Accepting an invite requires a signed-in Patient or Practitioner profile."));
|
|
27
|
+
}
|
|
13
28
|
}
|
|
14
29
|
exports.InvitesResource = InvitesResource;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { SavedAccount } from "../../auth/saved-accounts-store";
|
|
2
|
+
import { UserSession } from "../../auth/user-session";
|
|
3
|
+
import { HttpClient } from "../../http/http-client";
|
|
4
|
+
import { RequestOptions } from "../../http/transmit";
|
|
5
|
+
export declare class SavedAccountsResource {
|
|
6
|
+
private readonly http;
|
|
7
|
+
private readonly session;
|
|
8
|
+
constructor(http: HttpClient, session: UserSession | null);
|
|
9
|
+
list(): Promise<SavedAccount[]>;
|
|
10
|
+
switch(accountId: string): Promise<void>;
|
|
11
|
+
remove(accountId: string, options?: RequestOptions): Promise<void>;
|
|
12
|
+
private userSession;
|
|
13
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SavedAccountsResource = void 0;
|
|
4
|
+
const ovok_account_error_1 = require("../../errors/ovok-account-error");
|
|
5
|
+
const ovok_errors_1 = require("../../errors/ovok-errors");
|
|
6
|
+
const CURRENT_SESSION_PATH = "auth/session/current";
|
|
7
|
+
class SavedAccountsResource {
|
|
8
|
+
constructor(http, session) {
|
|
9
|
+
this.http = http;
|
|
10
|
+
this.session = session;
|
|
11
|
+
}
|
|
12
|
+
list() {
|
|
13
|
+
return this.userSession().listSavedAccounts();
|
|
14
|
+
}
|
|
15
|
+
switch(accountId) {
|
|
16
|
+
return this.userSession().switchSavedAccount(accountId);
|
|
17
|
+
}
|
|
18
|
+
async remove(accountId, options) {
|
|
19
|
+
const session = this.userSession();
|
|
20
|
+
const account = await session.getSavedAccount(accountId);
|
|
21
|
+
if (account === null) {
|
|
22
|
+
throw new ovok_account_error_1.OvokAccountError("account_not_found");
|
|
23
|
+
}
|
|
24
|
+
if (!account.expired) {
|
|
25
|
+
const isolatedSession = session.forSavedAccount(account.session);
|
|
26
|
+
try {
|
|
27
|
+
await this.http.withCredentials(isolatedSession).delete(CURRENT_SESSION_PATH, options);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (!(error instanceof ovok_errors_1.OvokError && error.status === 401) && await isolatedSession.current() !== null) {
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
await session.removeSavedAccount(accountId);
|
|
36
|
+
}
|
|
37
|
+
userSession() {
|
|
38
|
+
if (this.session === null) {
|
|
39
|
+
throw new ovok_errors_1.OvokConfigError((0, ovok_errors_1.detailsFor)("Saved accounts need a user session."));
|
|
40
|
+
}
|
|
41
|
+
return this.session;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
exports.SavedAccountsResource = SavedAccountsResource;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Session } from "../../auth/session";
|
|
2
|
+
import type { SessionChangeListener, UserSession } from "../../auth/user-session";
|
|
3
|
+
import { HttpClient } from "../../http/http-client";
|
|
4
|
+
import { SavedAccountsResource } from "./saved-accounts-resource";
|
|
5
|
+
export declare class SessionResource {
|
|
6
|
+
private readonly session;
|
|
7
|
+
readonly savedAccounts: SavedAccountsResource;
|
|
8
|
+
constructor(http: HttpClient, session: UserSession | null);
|
|
9
|
+
onChange(listener: SessionChangeListener): () => void;
|
|
10
|
+
}
|
|
11
|
+
export type { Session };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SessionResource = void 0;
|
|
4
|
+
const saved_accounts_resource_1 = require("./saved-accounts-resource");
|
|
5
|
+
class SessionResource {
|
|
6
|
+
constructor(http, session) {
|
|
7
|
+
this.session = session;
|
|
8
|
+
this.savedAccounts = new saved_accounts_resource_1.SavedAccountsResource(http, session);
|
|
9
|
+
}
|
|
10
|
+
onChange(listener) {
|
|
11
|
+
var _a, _b;
|
|
12
|
+
return (_b = (_a = this.session) === null || _a === void 0 ? void 0 : _a.onChange(listener)) !== null && _b !== void 0 ? _b : (() => undefined);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.SessionResource = SessionResource;
|
|
@@ -20,11 +20,17 @@ class SharedRead {
|
|
|
20
20
|
this.subscribers -= 1;
|
|
21
21
|
const abandoned = this.pending && this.subscribers === 0;
|
|
22
22
|
if (abandoned) {
|
|
23
|
-
this.
|
|
24
|
-
this.controller.abort();
|
|
23
|
+
this.abort();
|
|
25
24
|
}
|
|
26
25
|
}
|
|
26
|
+
abort() {
|
|
27
|
+
this.finish();
|
|
28
|
+
this.controller.abort();
|
|
29
|
+
}
|
|
27
30
|
finish() {
|
|
31
|
+
if (!this.pending) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
28
34
|
this.pending = false;
|
|
29
35
|
this.onDone();
|
|
30
36
|
}
|
|
@@ -35,6 +41,11 @@ class LiveReads {
|
|
|
35
41
|
this.http = http;
|
|
36
42
|
this.inFlight = new Map();
|
|
37
43
|
}
|
|
44
|
+
clear() {
|
|
45
|
+
const reads = [...this.inFlight.values()];
|
|
46
|
+
this.inFlight.clear();
|
|
47
|
+
reads.forEach((read) => read.abort());
|
|
48
|
+
}
|
|
38
49
|
get(path, options = {}) {
|
|
39
50
|
const shareable = Object.keys(options).every((key) => key === SIGNAL_OPTION);
|
|
40
51
|
return shareable ? this.share(path, options.signal) : this.http.get(path, options);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SlimAssignDevice, SlimCreateDevice, SlimDevice, SlimDeviceListItem, SlimDeviceListQuery, SlimDeviceTelemetry, SlimLiveQuery, SlimLiveTelemetry, SlimObservationIds, SlimUpdateDevice } from "../../../client/slim/types";
|
|
2
|
+
import type { UserSession } from "../../auth/user-session";
|
|
2
3
|
import { HttpClient } from "../../http/http-client";
|
|
3
4
|
import { RequestOptions } from "../../http/transmit";
|
|
4
5
|
import { PagePromise } from "../../paging/page";
|
|
@@ -8,7 +9,7 @@ export type SlimObservationIdsByDevice = Record<string, SlimObservationIds>;
|
|
|
8
9
|
export declare class SlimDevicesResource {
|
|
9
10
|
private readonly http;
|
|
10
11
|
private readonly liveReads;
|
|
11
|
-
constructor(http: HttpClient);
|
|
12
|
+
constructor(http: HttpClient, session?: UserSession | null);
|
|
12
13
|
list(params?: SlimDeviceListQuery, options?: RequestOptions): PagePromise<SlimDeviceListItem>;
|
|
13
14
|
retrieve(deviceId: string, options?: RequestOptions): Promise<SlimDevice>;
|
|
14
15
|
/** 409 when another customer holds the serial; 422 when this project already uses it. */
|
|
@@ -20,9 +20,10 @@ const LIVE_COUNT_MESSAGE = `count must be an integer from ${MIN_LIVE_COUNT} to $
|
|
|
20
20
|
const devicePath = (deviceId) => `${DEVICES_PATH}/${(0, url_1.pathSegment)(deviceId)}`;
|
|
21
21
|
const telemetryPath = (deviceId) => `${TELEMETRY_PATH}/${(0, url_1.pathSegment)(deviceId)}`;
|
|
22
22
|
class SlimDevicesResource {
|
|
23
|
-
constructor(http) {
|
|
23
|
+
constructor(http, session = null) {
|
|
24
24
|
this.http = http;
|
|
25
25
|
this.liveReads = new live_reads_1.LiveReads(http);
|
|
26
|
+
session === null || session === void 0 ? void 0 : session.onChange(() => this.liveReads.clear());
|
|
26
27
|
}
|
|
27
28
|
list(params = {}, options) {
|
|
28
29
|
return (0, numbered_pages_1.numberedRoutePages)((path) => this.http.get(path, options), DEVICES_PATH, params);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SlimHealth } from "../../../client/slim/types";
|
|
2
|
+
import type { UserSession } from "../../auth/user-session";
|
|
2
3
|
import { HttpClient } from "../../http/http-client";
|
|
3
4
|
import { RequestOptions } from "../../http/transmit";
|
|
4
5
|
import { SlimChangeHistoryResource } from "./slim-change-history-resource";
|
|
@@ -33,6 +34,6 @@ export declare class SlimResource {
|
|
|
33
34
|
readonly organizations: SlimOrganizationsResource;
|
|
34
35
|
readonly practitioners: SlimPractitionersResource;
|
|
35
36
|
readonly users: SlimUsersResource;
|
|
36
|
-
constructor(http: HttpClient);
|
|
37
|
+
constructor(http: HttpClient, session?: UserSession | null);
|
|
37
38
|
health(options?: RequestOptions): Promise<SlimHealth>;
|
|
38
39
|
}
|
|
@@ -19,10 +19,10 @@ const slim_users_resource_1 = require("./slim-users-resource");
|
|
|
19
19
|
const slim_zones_resource_1 = require("./slim-zones-resource");
|
|
20
20
|
const HEALTH_PATH = `${slim_paths_1.SLIM_PATH}/health`;
|
|
21
21
|
class SlimResource {
|
|
22
|
-
constructor(http) {
|
|
22
|
+
constructor(http, session = null) {
|
|
23
23
|
this.http = http;
|
|
24
24
|
this.patients = new slim_patients_resource_1.SlimPatientsResource(http);
|
|
25
|
-
this.devices = new slim_devices_resource_1.SlimDevicesResource(http);
|
|
25
|
+
this.devices = new slim_devices_resource_1.SlimDevicesResource(http, session);
|
|
26
26
|
this.notifications = new slim_notifications_resource_1.SlimNotificationsResource(http);
|
|
27
27
|
this.thresholds = new slim_thresholds_resource_1.SlimThresholdsResource(http);
|
|
28
28
|
this.trends = new slim_trends_resource_1.SlimTrendsResource(http);
|