@openmrs/esm-offline 3.1.15-pre.892 → 3.2.1-pre.1012
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/openmrs-esm-offline.js +1 -1
- package/dist/openmrs-esm-offline.js.map +1 -1
- package/package.json +6 -6
- package/src/index.ts +1 -0
- package/src/mode.ts +79 -0
- package/src/sync.ts +152 -24
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openmrs/esm-offline",
|
|
3
|
-
"version": "3.1
|
|
3
|
+
"version": "3.2.1-pre.1012",
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
5
|
"description": "Helper utilities for OpenMRS",
|
|
6
6
|
"browser": "dist/openmrs-esm-offline.js",
|
|
@@ -36,10 +36,10 @@
|
|
|
36
36
|
"access": "public"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@openmrs/esm-api": "^3.1
|
|
40
|
-
"@openmrs/esm-globals": "^3.1
|
|
41
|
-
"@openmrs/esm-state": "^3.1
|
|
42
|
-
"@openmrs/esm-styleguide": "^3.1
|
|
39
|
+
"@openmrs/esm-api": "^3.2.1-pre.1012",
|
|
40
|
+
"@openmrs/esm-globals": "^3.2.1-pre.1012",
|
|
41
|
+
"@openmrs/esm-state": "^3.2.1-pre.1012",
|
|
42
|
+
"@openmrs/esm-styleguide": "^3.2.1-pre.1012",
|
|
43
43
|
"@types/uuid": "^8.3.0",
|
|
44
44
|
"rxjs": "^6.5.3"
|
|
45
45
|
},
|
|
@@ -56,5 +56,5 @@
|
|
|
56
56
|
"uuid": "^8.3.2",
|
|
57
57
|
"workbox-window": "^6.1.5"
|
|
58
58
|
},
|
|
59
|
-
"gitHead": "
|
|
59
|
+
"gitHead": "96acf2eb0cf6d8d83d7ce12280bbf445c34c5672"
|
|
60
60
|
}
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from "./service-worker-http-headers";
|
|
|
3
3
|
export * from "./service-worker-messaging";
|
|
4
4
|
export * from "./service-worker-events";
|
|
5
5
|
export * from "./events";
|
|
6
|
+
export * from "./mode";
|
|
6
7
|
export * from "./patches";
|
|
7
8
|
export * from "./sync";
|
|
8
9
|
export * from "./uuid";
|
package/src/mode.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
async function isSafariPrivateBrowsing() {
|
|
2
|
+
const storage = window.sessionStorage;
|
|
3
|
+
|
|
4
|
+
try {
|
|
5
|
+
storage.setItem("someKeyHere", "test");
|
|
6
|
+
storage.removeItem("someKeyHere");
|
|
7
|
+
} catch (e) {
|
|
8
|
+
if (e.code === DOMException.QUOTA_EXCEEDED_ERR && storage.length === 0) {
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function isEdgePrivateBrowsing() {
|
|
17
|
+
return !window.indexedDB;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function isFirefoxPrivateBrowsing() {
|
|
21
|
+
return new Promise<boolean>((resolve) => {
|
|
22
|
+
const db = indexedDB.open("test");
|
|
23
|
+
db.onerror = () => resolve(true);
|
|
24
|
+
db.onsuccess = () => resolve(false);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function isPrivateBrowsing() {
|
|
29
|
+
return (
|
|
30
|
+
(await isFirefoxPrivateBrowsing()) ||
|
|
31
|
+
(await isEdgePrivateBrowsing()) ||
|
|
32
|
+
(await isSafariPrivateBrowsing())
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type OfflineMode = "on" | "off" | "unavailable";
|
|
37
|
+
|
|
38
|
+
export interface OfflineModeResult {
|
|
39
|
+
current: OfflineMode;
|
|
40
|
+
notAvailable: boolean;
|
|
41
|
+
active: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const offlineModeStorageKey = "openmrs3:offline-mode";
|
|
45
|
+
let offlineMode: OfflineMode = "unavailable";
|
|
46
|
+
|
|
47
|
+
export function getCurrentOfflineMode(): OfflineModeResult {
|
|
48
|
+
return {
|
|
49
|
+
current: offlineMode,
|
|
50
|
+
notAvailable: offlineMode === "unavailable",
|
|
51
|
+
active: offlineMode === "on",
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function setCurrentOfflineMode(mode: OfflineMode) {
|
|
56
|
+
if (offlineMode !== "unavailable" && mode !== "unavailable") {
|
|
57
|
+
localStorage.setItem(
|
|
58
|
+
offlineModeStorageKey,
|
|
59
|
+
mode === "on" ? "active" : "disabled"
|
|
60
|
+
);
|
|
61
|
+
offlineMode = mode;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function activateOfflineCapability() {
|
|
66
|
+
const isPrivate = await isPrivateBrowsing();
|
|
67
|
+
|
|
68
|
+
if (!isPrivate) {
|
|
69
|
+
if (localStorage.getItem(offlineModeStorageKey) === "active") {
|
|
70
|
+
offlineMode = "on";
|
|
71
|
+
} else {
|
|
72
|
+
offlineMode = "off";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (navigator.onLine && offlineMode === "on") {
|
|
77
|
+
//TODO trigger here --> update cycle
|
|
78
|
+
}
|
|
79
|
+
}
|
package/src/sync.ts
CHANGED
|
@@ -2,11 +2,16 @@ import Dexie, { Table } from "dexie";
|
|
|
2
2
|
import { getLoggedInUser } from "@openmrs/esm-api";
|
|
3
3
|
import { createGlobalStore } from "@openmrs/esm-state";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Defines an item queued up in the offline synchronization queue.
|
|
7
|
+
* A `SyncItem` contains both meta information about the item in the sync queue, as well as the
|
|
8
|
+
* actual data to be synchronized (i.e. the item's `content`).
|
|
9
|
+
*/
|
|
10
|
+
export interface SyncItem<T = any> {
|
|
6
11
|
id?: number;
|
|
7
12
|
userId: string;
|
|
8
13
|
type: string;
|
|
9
|
-
content:
|
|
14
|
+
content: T;
|
|
10
15
|
createdOn: Date;
|
|
11
16
|
descriptor: QueueItemDescriptor;
|
|
12
17
|
lastError?: {
|
|
@@ -15,6 +20,12 @@ export interface SyncItem {
|
|
|
15
20
|
};
|
|
16
21
|
}
|
|
17
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Contains information about the sync item which has been provided externally by the caller
|
|
25
|
+
* who added the item to the queue.
|
|
26
|
+
* This information is all optional, but, when provided while enqueuing the item, can be used in other
|
|
27
|
+
* locations to better represent the sync item, e.g. in the UI.
|
|
28
|
+
*/
|
|
18
29
|
export interface QueueItemDescriptor {
|
|
19
30
|
id?: string;
|
|
20
31
|
dependencies?: Array<{
|
|
@@ -25,19 +36,69 @@ export interface QueueItemDescriptor {
|
|
|
25
36
|
displayName?: string;
|
|
26
37
|
}
|
|
27
38
|
|
|
39
|
+
/**
|
|
40
|
+
* A function which, when invoked, performs the actual client-server synchronization of the given
|
|
41
|
+
* `item` (which is the actual data to be synchronized).
|
|
42
|
+
* The function receives additional `options` which provide additional data that can be used
|
|
43
|
+
* for synchronizing.
|
|
44
|
+
*/
|
|
28
45
|
export type ProcessSyncItem<T> = (
|
|
29
46
|
item: T,
|
|
30
47
|
options: SyncProcessOptions<T>
|
|
31
48
|
) => Promise<any>;
|
|
32
49
|
|
|
33
|
-
|
|
34
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Additional data which can be used for synchronizing data in a {@link ProcessSyncItem} function.
|
|
52
|
+
*/
|
|
53
|
+
export interface SyncProcessOptions<T> {
|
|
54
|
+
abort: AbortController;
|
|
55
|
+
userId: string;
|
|
56
|
+
index: number;
|
|
57
|
+
items: Array<T>;
|
|
58
|
+
dependencies: Array<any>;
|
|
35
59
|
}
|
|
36
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Defines additional options which can optionally be provided when setting up a synchronization callback
|
|
63
|
+
* for a specific synchronization item type.
|
|
64
|
+
* These are not required, but, when set, allow further
|
|
65
|
+
*/
|
|
66
|
+
interface SetupOfflineSyncOptions<T> {
|
|
67
|
+
/**
|
|
68
|
+
* Invoked when the user requests to edit a sync item.
|
|
69
|
+
* The typical behavior for such a callback is to launch a UI which allows editing the content
|
|
70
|
+
* encapsulated by the sync item.
|
|
71
|
+
* @param syncItem The sync item to be edited.
|
|
72
|
+
*/
|
|
73
|
+
onBeginEditSyncItem?(syncItem: SyncItem<T>): void;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Represents a synchronization handler which has been globally registered by the
|
|
78
|
+
* {@link setupOfflineSync} function.
|
|
79
|
+
* These handlers are used for synchronizing queued data.
|
|
80
|
+
*/
|
|
37
81
|
interface SyncHandler {
|
|
38
|
-
type: string;
|
|
39
|
-
dependsOn:
|
|
40
|
-
process: ProcessSyncItem<unknown>;
|
|
82
|
+
readonly type: string;
|
|
83
|
+
readonly dependsOn: ReadonlyArray<string>;
|
|
84
|
+
readonly process: ProcessSyncItem<unknown>;
|
|
85
|
+
readonly options: Readonly<SetupOfflineSyncOptions<any>>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Represents the data inside the global offline synchronization store.
|
|
90
|
+
* Provides information about a currently ongoing synchronization.
|
|
91
|
+
*/
|
|
92
|
+
export interface OfflineSynchronizationStore {
|
|
93
|
+
synchronization?: {
|
|
94
|
+
totalCount: number;
|
|
95
|
+
pendingCount: number;
|
|
96
|
+
abortController: AbortController;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
interface SyncResultBag {
|
|
101
|
+
[type: string]: Record<string, any>;
|
|
41
102
|
}
|
|
42
103
|
|
|
43
104
|
class OfflineDb extends Dexie {
|
|
@@ -64,14 +125,6 @@ export function getOfflineDb() {
|
|
|
64
125
|
return db;
|
|
65
126
|
}
|
|
66
127
|
|
|
67
|
-
export interface OfflineSynchronizationStore {
|
|
68
|
-
synchronization?: {
|
|
69
|
-
totalCount: number;
|
|
70
|
-
pendingCount: number;
|
|
71
|
-
abortController: AbortController;
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
128
|
const syncStore = createGlobalStore<OfflineSynchronizationStore>(
|
|
76
129
|
"offline-synchronization",
|
|
77
130
|
{}
|
|
@@ -81,6 +134,9 @@ export function getOfflineSynchronizationStore() {
|
|
|
81
134
|
return syncStore;
|
|
82
135
|
}
|
|
83
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Runs a full synchronization of **all** queued synchronization items.
|
|
139
|
+
*/
|
|
84
140
|
export async function runSynchronization() {
|
|
85
141
|
if (syncStore.getState().synchronization) {
|
|
86
142
|
return;
|
|
@@ -192,6 +248,13 @@ async function getUserId() {
|
|
|
192
248
|
return user?.uuid || "*";
|
|
193
249
|
}
|
|
194
250
|
|
|
251
|
+
/**
|
|
252
|
+
* Enqueues a new item in the sync queue for a specific user.
|
|
253
|
+
* @param userId The user with whom the sync item should be associated with.
|
|
254
|
+
* @param type The identifying type of the synchronization item.
|
|
255
|
+
* @param content The actual data to be synchronized.
|
|
256
|
+
* @param descriptor An optional descriptor providing additional metadata about the sync item.
|
|
257
|
+
*/
|
|
195
258
|
export async function queueSynchronizationItemFor<T>(
|
|
196
259
|
userId: string,
|
|
197
260
|
type: string,
|
|
@@ -220,6 +283,12 @@ export async function queueSynchronizationItemFor<T>(
|
|
|
220
283
|
return id;
|
|
221
284
|
}
|
|
222
285
|
|
|
286
|
+
/**
|
|
287
|
+
* Enqueues a new item in the sync queue and associates the item with the currently signed in user.
|
|
288
|
+
* @param type The identifying type of the synchronization item.
|
|
289
|
+
* @param content The actual data to be synchronized.
|
|
290
|
+
* @param descriptor An optional descriptor providing additional metadata about the sync item.
|
|
291
|
+
*/
|
|
223
292
|
export async function queueSynchronizationItem<T>(
|
|
224
293
|
type: string,
|
|
225
294
|
content: T,
|
|
@@ -229,6 +298,11 @@ export async function queueSynchronizationItem<T>(
|
|
|
229
298
|
return await queueSynchronizationItemFor(userId, type, content, descriptor);
|
|
230
299
|
}
|
|
231
300
|
|
|
301
|
+
/**
|
|
302
|
+
* Returns all currently queued up sync items of a given user.
|
|
303
|
+
* @param userId The ID of the user whose synchronization items should be returned.
|
|
304
|
+
* @param type The identifying type of the synchronization items to be returned..
|
|
305
|
+
*/
|
|
232
306
|
export async function getSynchronizationItemsFor<T>(
|
|
233
307
|
userId: string,
|
|
234
308
|
type: string
|
|
@@ -243,31 +317,85 @@ export async function getSynchronizationItemsFor<T>(
|
|
|
243
317
|
return items;
|
|
244
318
|
}
|
|
245
319
|
|
|
320
|
+
/**
|
|
321
|
+
* Returns all currently queued up sync items of the currently signed in user.
|
|
322
|
+
* @param type The identifying type of the synchronization items to be returned.
|
|
323
|
+
*/
|
|
246
324
|
export async function getSynchronizationItems<T>(type: string) {
|
|
247
325
|
const userId = await getUserId();
|
|
248
326
|
return await getSynchronizationItemsFor<T>(userId, type);
|
|
249
327
|
}
|
|
250
328
|
|
|
251
|
-
|
|
252
|
-
|
|
329
|
+
/**
|
|
330
|
+
* Returns a queued sync item with the given ID or `undefined` if no such item exists.
|
|
331
|
+
* @param id The ID of the requested sync item.
|
|
332
|
+
*/
|
|
333
|
+
export async function getSynchronizationItem<T = any>(
|
|
334
|
+
id: number
|
|
335
|
+
): Promise<SyncItem<T> | undefined> {
|
|
336
|
+
return await db.syncQueue.get(id);
|
|
253
337
|
}
|
|
254
338
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
339
|
+
/**
|
|
340
|
+
* Returns whether editing synchronization items of the given type is supported by the currently
|
|
341
|
+
* registered synchronization handlers.
|
|
342
|
+
* @param type The identifying type of the synchronization item which should be edited.
|
|
343
|
+
*/
|
|
344
|
+
export function canBeginEditSynchronizationItemsOfType(type: string) {
|
|
345
|
+
// Editing an item can be requested as long as callback for this flow exists on the associated handler.
|
|
346
|
+
return !!handlers[type]?.options.onBeginEditSyncItem;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Triggers an edit flow for the given synchronization item.
|
|
351
|
+
* If this is not possible, throws an error.
|
|
352
|
+
* @param id The ID of the synchronization item to be edited.
|
|
353
|
+
*/
|
|
354
|
+
export async function beginEditSynchronizationItem(id: number) {
|
|
355
|
+
const item = await getSynchronizationItem(id);
|
|
356
|
+
if (!item) {
|
|
357
|
+
throw new Error(`No sync item with the ID ${id} exists.`);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const editCallback = handlers[item.type]?.options.onBeginEditSyncItem;
|
|
361
|
+
if (!editCallback) {
|
|
362
|
+
throw new Error(
|
|
363
|
+
`A sync item with the ID ${id} exists, but the associated handler (if one exists) doesn't support editing the item. You can avoid this error by either verifying that sync items of this type can be edited via the "canEditSynchronizationItemsOfType(type: string)" function or alternatively ensure that the synchronizaton handler for sync items of type "${item.type}" supports editing items.`
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
editCallback(item);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Deletes a queued up sync item with the given ID.
|
|
372
|
+
* @param id The ID of the synchronization item to be deleted.
|
|
373
|
+
*/
|
|
374
|
+
export async function deleteSynchronizationItem(id: number) {
|
|
375
|
+
await db.syncQueue.delete(id);
|
|
261
376
|
}
|
|
262
377
|
|
|
378
|
+
/**
|
|
379
|
+
* Registers a new synchronization handler which is able to synchronize data of a specific type.
|
|
380
|
+
* @param type The identifying type of the synchronization items which can be handled by this handler.
|
|
381
|
+
* @param dependsOn An array of other sync item types which must be synchronized before this handler
|
|
382
|
+
* can synchronize its own data. Items of these types are effectively dependencies of the data
|
|
383
|
+
* synchronized by this handler.
|
|
384
|
+
* @param process A function which, when invoked, performs the actual client-server synchronization of the given
|
|
385
|
+
* `item` (which is the actual data to be synchronized).
|
|
386
|
+
* @param options Additional options which can optionally be provided when setting up a synchronization callback
|
|
387
|
+
* for a specific synchronization item type.
|
|
388
|
+
*/
|
|
263
389
|
export function setupOfflineSync<T>(
|
|
264
390
|
type: string,
|
|
265
391
|
dependsOn: Array<string>,
|
|
266
|
-
process: ProcessSyncItem<T
|
|
392
|
+
process: ProcessSyncItem<T>,
|
|
393
|
+
options: SetupOfflineSyncOptions<T> = {}
|
|
267
394
|
) {
|
|
268
395
|
handlers[type] = {
|
|
269
396
|
type,
|
|
270
397
|
dependsOn,
|
|
271
398
|
process,
|
|
399
|
+
options,
|
|
272
400
|
};
|
|
273
401
|
}
|