@auxilium/datalynk-client 1.6.2 → 1.6.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 +13 -0
- package/dist/api.d.ts +9 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/auth.d.ts +6 -2
- package/dist/auth.d.ts.map +1 -1
- package/dist/chat-gui.d.ts +11 -0
- package/dist/chat-gui.d.ts.map +1 -1
- package/dist/chat-gui.view.d.ts +3 -1
- package/dist/chat-gui.view.d.ts.map +1 -1
- package/dist/chat.cjs +212 -43
- package/dist/chat.d.ts +13 -0
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.mjs +212 -43
- package/dist/index.cjs +455 -104
- package/dist/index.mjs +455 -104
- package/dist/offline-auth.d.ts +68 -4
- package/dist/offline-auth.d.ts.map +1 -1
- package/dist/service.worker.mjs +156 -40
- package/dist/slice.d.ts.map +1 -1
- package/dist/utils.d.ts +13 -0
- package/dist/utils.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -193,6 +193,8 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
193
193
|
constructor(database, tables, version2) {
|
|
194
194
|
__publicField(this, "connection");
|
|
195
195
|
__publicField(this, "tables");
|
|
196
|
+
/** Encrypts values at rest when set */
|
|
197
|
+
__publicField(this, "cipher");
|
|
196
198
|
this.database = database;
|
|
197
199
|
this.version = version2;
|
|
198
200
|
this.tables = tables.map((table) => typeof table === "object" ? { ...table, name: table.name.toString() } : { name: table.toString() });
|
|
@@ -241,22 +243,46 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
241
243
|
async tx(operation, readonly = false) {
|
|
242
244
|
const db = await this.database.connection;
|
|
243
245
|
return new Promise((resolve, reject) => {
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
246
|
+
const transaction = db.transaction(this.name, readonly ? "readonly" : "readwrite");
|
|
247
|
+
let result;
|
|
248
|
+
transaction.oncomplete = () => resolve(result);
|
|
249
|
+
transaction.onabort = () => reject(transaction.error || new Error("IndexedDB transaction aborted"));
|
|
250
|
+
transaction.onerror = () => reject(transaction.error || new Error("IndexedDB transaction failed"));
|
|
251
|
+
try {
|
|
252
|
+
const request = operation(transaction.objectStore(this.name));
|
|
253
|
+
request.onsuccess = () => {
|
|
254
|
+
result = request.result;
|
|
255
|
+
};
|
|
256
|
+
request.onerror = () => reject(request.error || new Error("IndexedDB request failed"));
|
|
257
|
+
} catch (error) {
|
|
258
|
+
transaction.abort();
|
|
259
|
+
reject(error);
|
|
260
|
+
}
|
|
247
261
|
});
|
|
248
262
|
}
|
|
249
|
-
|
|
250
|
-
|
|
263
|
+
seal(value) {
|
|
264
|
+
const cipher = this.database.cipher;
|
|
265
|
+
return (cipher == null ? void 0 : cipher.enabled) ? cipher.seal(value) : Promise.resolve(value);
|
|
266
|
+
}
|
|
267
|
+
open(value) {
|
|
268
|
+
const cipher = this.database.cipher;
|
|
269
|
+
return cipher ? cipher.open(value) : Promise.resolve(value);
|
|
251
270
|
}
|
|
252
|
-
|
|
253
|
-
|
|
271
|
+
async add(value, key) {
|
|
272
|
+
const sealed = await this.seal(value);
|
|
273
|
+
return this.tx((store) => store.add(sealed, key));
|
|
254
274
|
}
|
|
255
|
-
|
|
256
|
-
|
|
275
|
+
async put(key, value) {
|
|
276
|
+
const sealed = await this.seal(value);
|
|
277
|
+
return this.tx((store) => store.put(sealed, key));
|
|
257
278
|
}
|
|
258
|
-
|
|
259
|
-
return this.tx((store) => store.
|
|
279
|
+
async get(key) {
|
|
280
|
+
return await this.open(await this.tx((store) => store.get(key), true));
|
|
281
|
+
}
|
|
282
|
+
/** All readable values. Values sealed with another key are skipped. */
|
|
283
|
+
async getAll() {
|
|
284
|
+
const values = await Promise.all((await this.tx((store) => store.getAll(), true)).map((v) => this.open(v)));
|
|
285
|
+
return values.filter((v) => v !== void 0);
|
|
260
286
|
}
|
|
261
287
|
getAllKeys() {
|
|
262
288
|
return this.tx((store) => store.getAllKeys(), true);
|
|
@@ -267,6 +293,20 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
267
293
|
clear() {
|
|
268
294
|
return this.tx((store) => store.clear());
|
|
269
295
|
}
|
|
296
|
+
/** Replace the whole table in one transaction (values are sealed first, so nothing is lost while locked) */
|
|
297
|
+
async replace(entries) {
|
|
298
|
+
const sealed = await Promise.all(entries.map(async ([key, value]) => [key, await this.seal(value)]));
|
|
299
|
+
const db = await this.database.connection;
|
|
300
|
+
await new Promise((resolve, reject) => {
|
|
301
|
+
const tx = db.transaction(this.name, "readwrite");
|
|
302
|
+
const store = tx.objectStore(this.name);
|
|
303
|
+
store.clear();
|
|
304
|
+
sealed.forEach(([key, value]) => store.put(value, key));
|
|
305
|
+
tx.oncomplete = () => resolve();
|
|
306
|
+
tx.onerror = () => reject(tx.error);
|
|
307
|
+
tx.onabort = () => reject(tx.error);
|
|
308
|
+
});
|
|
309
|
+
}
|
|
270
310
|
count() {
|
|
271
311
|
return this.tx((store) => store.count(), true);
|
|
272
312
|
}
|
|
@@ -1020,6 +1060,9 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1020
1060
|
};
|
|
1021
1061
|
return BehaviorSubject2;
|
|
1022
1062
|
}(Subject);
|
|
1063
|
+
var EMPTY = new Observable(function(subscriber) {
|
|
1064
|
+
return subscriber.complete();
|
|
1065
|
+
});
|
|
1023
1066
|
var EmptyError = createErrorClass(function(_super) {
|
|
1024
1067
|
return function EmptyErrorImpl() {
|
|
1025
1068
|
_super(this);
|
|
@@ -1047,6 +1090,23 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1047
1090
|
});
|
|
1048
1091
|
});
|
|
1049
1092
|
}
|
|
1093
|
+
function firstValueFrom(source, config2) {
|
|
1094
|
+
return new Promise(function(resolve, reject) {
|
|
1095
|
+
var subscriber = new SafeSubscriber({
|
|
1096
|
+
next: function(value) {
|
|
1097
|
+
resolve(value);
|
|
1098
|
+
subscriber.unsubscribe();
|
|
1099
|
+
},
|
|
1100
|
+
error: reject,
|
|
1101
|
+
complete: function() {
|
|
1102
|
+
{
|
|
1103
|
+
reject(new EmptyError());
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
});
|
|
1107
|
+
source.subscribe(subscriber);
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1050
1110
|
function filter(predicate, thisArg) {
|
|
1051
1111
|
return operate(function(source, subscriber) {
|
|
1052
1112
|
var index = 0;
|
|
@@ -1055,6 +1115,21 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1055
1115
|
}));
|
|
1056
1116
|
});
|
|
1057
1117
|
}
|
|
1118
|
+
function take(count) {
|
|
1119
|
+
return count <= 0 ? function() {
|
|
1120
|
+
return EMPTY;
|
|
1121
|
+
} : operate(function(source, subscriber) {
|
|
1122
|
+
var seen = 0;
|
|
1123
|
+
source.subscribe(createOperatorSubscriber(subscriber, function(value) {
|
|
1124
|
+
if (++seen <= count) {
|
|
1125
|
+
subscriber.next(value);
|
|
1126
|
+
if (count <= seen) {
|
|
1127
|
+
subscriber.complete();
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
}));
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1058
1133
|
function distinctUntilChanged(comparator, keySelector) {
|
|
1059
1134
|
if (keySelector === void 0) {
|
|
1060
1135
|
keySelector = identity;
|
|
@@ -1536,52 +1611,304 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1536
1611
|
`;
|
|
1537
1612
|
});
|
|
1538
1613
|
let LoginPrompt = _LoginPrompt;
|
|
1539
|
-
const
|
|
1614
|
+
const RECORD_KEY = "datalynk-offline-login";
|
|
1615
|
+
const PROFILE_KEY = "datalynk-user";
|
|
1616
|
+
const VAULT_DB = "datalynk-vault";
|
|
1617
|
+
const VAULT_STORE = "keys";
|
|
1618
|
+
const DEVICE_KEY = "device";
|
|
1540
1619
|
const ITERATIONS = 31e4;
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
}
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
}
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
try {
|
|
1559
|
-
record = JSON.parse(saved);
|
|
1560
|
-
} catch {
|
|
1561
|
-
return false;
|
|
1562
|
-
}
|
|
1563
|
-
if (record.realm !== realm || record.login !== login.trim().toLowerCase() || record.uid !== uid || !Array.isArray(record.salt) || !Array.isArray(record.hash)) return false;
|
|
1564
|
-
const candidate = await hash(password, new Uint8Array(record.salt));
|
|
1565
|
-
if (candidate.length !== record.hash.length) return false;
|
|
1566
|
-
let difference = 0;
|
|
1567
|
-
for (let i = 0; i < candidate.length; i++) difference |= candidate[i] ^ record.hash[i];
|
|
1568
|
-
return difference === 0;
|
|
1620
|
+
const DAY = 864e5;
|
|
1621
|
+
const offlineLoginMessages = {
|
|
1622
|
+
"none": "Offline login is not available on this device. Connect to the internet to sign in.",
|
|
1623
|
+
"wrong-user": "Offline login is only available for the last user who signed in on this device.",
|
|
1624
|
+
"expired": "Offline login has expired on this device. Connect to the internet to sign in.",
|
|
1625
|
+
"locked": "Offline login is locked after too many attempts. Connect to the internet to sign in.",
|
|
1626
|
+
"bad-password": "Incorrect password."
|
|
1627
|
+
};
|
|
1628
|
+
const toB64 = (bytes) => {
|
|
1629
|
+
let out = "";
|
|
1630
|
+
new Uint8Array(bytes).forEach((b) => out += String.fromCharCode(b));
|
|
1631
|
+
return btoa(out);
|
|
1632
|
+
};
|
|
1633
|
+
const fromB64 = (text) => Uint8Array.from(atob(text), (c) => c.charCodeAt(0));
|
|
1634
|
+
const normalizeLogin = (login) => (login || "").trim().toLowerCase();
|
|
1635
|
+
function isSealed(value) {
|
|
1636
|
+
return !!value && typeof value == "object" && value.__dlv === 1 && "iv" in value && "ct" in value;
|
|
1569
1637
|
}
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1638
|
+
class OfflineVault {
|
|
1639
|
+
constructor(enabled, options = {}, startOffline) {
|
|
1640
|
+
/** False when offline login is disabled or the environment lacks WebCrypto/IndexedDB/localStorage */
|
|
1641
|
+
__publicField(this, "enabled");
|
|
1642
|
+
/** True once the data key is available in memory */
|
|
1643
|
+
__publicField(this, "unlocked$", new BehaviorSubject(false));
|
|
1644
|
+
/** Resolves once startup key loading has finished */
|
|
1645
|
+
__publicField(this, "ready");
|
|
1646
|
+
__publicField(this, "dek", null);
|
|
1647
|
+
__publicField(this, "days");
|
|
1648
|
+
__publicField(this, "attempts");
|
|
1649
|
+
this.enabled = enabled && typeof crypto != "undefined" && !!crypto.subtle && typeof indexedDB != "undefined" && typeof localStorage != "undefined";
|
|
1650
|
+
this.days = options.days ?? 7;
|
|
1651
|
+
this.attempts = options.attempts ?? 5;
|
|
1652
|
+
if (startOffline === void 0) startOffline = typeof navigator != "undefined" && navigator.onLine === false;
|
|
1653
|
+
this.ready = this.enabled ? this.init(startOffline).catch((error) => {
|
|
1654
|
+
console.error("Unable to open the Datalynk offline vault", error);
|
|
1655
|
+
}) : Promise.resolve();
|
|
1656
|
+
}
|
|
1657
|
+
get unlocked() {
|
|
1658
|
+
return this.unlocked$.getValue();
|
|
1659
|
+
}
|
|
1660
|
+
/** A saved offline login exists, but this session has not unlocked it yet */
|
|
1661
|
+
get needsPassword() {
|
|
1662
|
+
return this.enabled && !this.unlocked && !!this.readRecord();
|
|
1663
|
+
}
|
|
1664
|
+
/** Wait until the data key is available (resolves immediately when the vault is disabled) */
|
|
1665
|
+
whenUnlocked() {
|
|
1666
|
+
if (!this.enabled || this.unlocked) return Promise.resolve(true);
|
|
1667
|
+
return firstValueFrom(this.unlocked$.pipe(filter(Boolean)));
|
|
1668
|
+
}
|
|
1669
|
+
/** Saved offline login matches this user */
|
|
1670
|
+
has(realm, uid) {
|
|
1671
|
+
const record = this.readRecord();
|
|
1672
|
+
return !!record && record.realm === realm && record.uid === uid;
|
|
1673
|
+
}
|
|
1674
|
+
/**
|
|
1675
|
+
* Called after a successful online password login. Wraps the data key with the
|
|
1676
|
+
* password so this user can sign in offline later.
|
|
1677
|
+
*/
|
|
1678
|
+
async rememberLogin(realm, login, uid, password) {
|
|
1679
|
+
if (!this.enabled) return;
|
|
1680
|
+
await this.ready;
|
|
1681
|
+
const record = this.readRecord();
|
|
1682
|
+
let dek = null;
|
|
1683
|
+
if (record && record.realm === realm && record.uid === uid) {
|
|
1684
|
+
dek = await this.unwrapWithPassword(record, password).catch(() => null);
|
|
1685
|
+
if (!dek) dek = this.dek;
|
|
1686
|
+
} else if (!record) {
|
|
1687
|
+
dek = this.dek;
|
|
1688
|
+
}
|
|
1689
|
+
if (!dek) dek = await this.newDataKey();
|
|
1690
|
+
const salt = crypto.getRandomValues(new Uint8Array(16));
|
|
1691
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
1692
|
+
const kek = await this.passwordKey(password, salt);
|
|
1693
|
+
const wrapped = await crypto.subtle.wrapKey("raw", dek, kek, { name: "AES-GCM", iv });
|
|
1694
|
+
this.writeRecord({
|
|
1695
|
+
v: 2,
|
|
1696
|
+
realm,
|
|
1697
|
+
uid,
|
|
1698
|
+
login: normalizeLogin(login),
|
|
1699
|
+
salt: toB64(salt),
|
|
1700
|
+
iv: toB64(iv),
|
|
1701
|
+
wrapped: toB64(wrapped),
|
|
1702
|
+
verifiedAt: Date.now(),
|
|
1703
|
+
failures: 0
|
|
1704
|
+
});
|
|
1705
|
+
await this.setKey(dek);
|
|
1706
|
+
}
|
|
1707
|
+
/** Try to unlock offline with a password */
|
|
1708
|
+
async unlockOffline(realm, login, uid, password) {
|
|
1709
|
+
if (!this.enabled) return "none";
|
|
1710
|
+
await this.ready;
|
|
1711
|
+
const record = this.readRecord();
|
|
1712
|
+
if (!record) return "none";
|
|
1713
|
+
if (record.realm !== realm || record.login !== normalizeLogin(login) || uid != null && record.uid !== uid) return "wrong-user";
|
|
1714
|
+
if (record.locked) return "locked";
|
|
1715
|
+
if (Date.now() - record.verifiedAt > this.days * DAY) return "expired";
|
|
1716
|
+
try {
|
|
1717
|
+
const dek = await this.unwrapWithPassword(record, password);
|
|
1718
|
+
this.writeRecord({ ...record, failures: 0 });
|
|
1719
|
+
await this.setKey(dek);
|
|
1720
|
+
return "ok";
|
|
1721
|
+
} catch {
|
|
1722
|
+
const failures = (record.failures || 0) + 1;
|
|
1723
|
+
const locked = failures >= this.attempts;
|
|
1724
|
+
this.writeRecord({ ...record, failures, locked });
|
|
1725
|
+
return locked ? "locked" : "bad-password";
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
/** The server confirmed this user's session: restart the offline window */
|
|
1729
|
+
touch(realm, uid) {
|
|
1730
|
+
if (!this.enabled || realm == null || uid == null) return;
|
|
1731
|
+
const record = this.readRecord();
|
|
1732
|
+
if (record && record.realm === realm && record.uid === uid) this.writeRecord({ ...record, verifiedAt: Date.now() });
|
|
1733
|
+
}
|
|
1734
|
+
/** Drop the in-memory and device keys but keep the password copy (queued work stays recoverable) */
|
|
1735
|
+
async lock() {
|
|
1736
|
+
this.dek = null;
|
|
1737
|
+
if (this.unlocked) this.unlocked$.next(false);
|
|
1738
|
+
if (this.enabled) await this.deleteDevice();
|
|
1739
|
+
}
|
|
1740
|
+
/** Remove offline login completely; anything sealed becomes unreadable */
|
|
1741
|
+
async forget() {
|
|
1742
|
+
if (typeof localStorage != "undefined") {
|
|
1743
|
+
localStorage.removeItem(RECORD_KEY);
|
|
1744
|
+
localStorage.removeItem(PROFILE_KEY);
|
|
1745
|
+
}
|
|
1746
|
+
await this.lock();
|
|
1747
|
+
}
|
|
1748
|
+
/** Encrypt a value for IndexedDB (passes through when disabled) */
|
|
1749
|
+
async seal(value) {
|
|
1750
|
+
if (!this.enabled || value === void 0) return value;
|
|
1751
|
+
await this.whenUnlocked();
|
|
1752
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
1753
|
+
const ct = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, this.dek, new TextEncoder().encode(JSON.stringify(value)));
|
|
1754
|
+
return { __dlv: 1, iv, ct };
|
|
1755
|
+
}
|
|
1756
|
+
/** Decrypt a value from IndexedDB. Unreadable values (old key / other user) return undefined. */
|
|
1757
|
+
async open(value) {
|
|
1758
|
+
if (!isSealed(value)) return value;
|
|
1759
|
+
if (!this.enabled) return void 0;
|
|
1760
|
+
await this.whenUnlocked();
|
|
1761
|
+
try {
|
|
1762
|
+
const iv = typeof value.iv == "string" ? fromB64(value.iv) : value.iv;
|
|
1763
|
+
const ct = typeof value.ct == "string" ? fromB64(value.ct) : value.ct;
|
|
1764
|
+
const plain = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, this.dek, ct);
|
|
1765
|
+
return JSON.parse(new TextDecoder().decode(plain));
|
|
1766
|
+
} catch {
|
|
1767
|
+
return void 0;
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
/** Save the cached user profile (sealed when enabled) */
|
|
1771
|
+
async setProfile(user) {
|
|
1772
|
+
if (typeof localStorage == "undefined") return;
|
|
1773
|
+
if (user == null) return localStorage.removeItem(PROFILE_KEY);
|
|
1774
|
+
if (!this.enabled) return localStorage.setItem(PROFILE_KEY, JSON.stringify(user));
|
|
1775
|
+
const sealed = await this.seal(user);
|
|
1776
|
+
localStorage.setItem(PROFILE_KEY, JSON.stringify({ __dlv: 1, iv: toB64(sealed.iv), ct: toB64(sealed.ct) }));
|
|
1777
|
+
}
|
|
1778
|
+
/** Read the cached user profile; null while locked */
|
|
1779
|
+
async getProfile() {
|
|
1780
|
+
if (typeof localStorage == "undefined") return null;
|
|
1781
|
+
let saved;
|
|
1782
|
+
try {
|
|
1783
|
+
saved = JSON.parse(localStorage.getItem(PROFILE_KEY) || "null");
|
|
1784
|
+
} catch {
|
|
1785
|
+
return null;
|
|
1786
|
+
}
|
|
1787
|
+
if (!isSealed(saved)) return this.enabled ? null : saved;
|
|
1788
|
+
if (!this.unlocked) return null;
|
|
1789
|
+
return await this.open(saved) ?? null;
|
|
1790
|
+
}
|
|
1791
|
+
// ----- internals -----
|
|
1792
|
+
async init(startOffline) {
|
|
1793
|
+
if (startOffline) {
|
|
1794
|
+
await this.deleteDevice();
|
|
1795
|
+
return;
|
|
1796
|
+
}
|
|
1797
|
+
const device = await this.readDevice();
|
|
1798
|
+
if (device) {
|
|
1799
|
+
try {
|
|
1800
|
+
const dek = await crypto.subtle.unwrapKey(
|
|
1801
|
+
"raw",
|
|
1802
|
+
device.wrapped,
|
|
1803
|
+
device.key,
|
|
1804
|
+
{ name: "AES-GCM", iv: device.iv },
|
|
1805
|
+
{ name: "AES-GCM" },
|
|
1806
|
+
true,
|
|
1807
|
+
["encrypt", "decrypt"]
|
|
1808
|
+
);
|
|
1809
|
+
this.dek = dek;
|
|
1810
|
+
this.unlocked$.next(true);
|
|
1811
|
+
return;
|
|
1812
|
+
} catch {
|
|
1813
|
+
await this.deleteDevice();
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
if (!this.readRecord()) await this.setKey(await this.newDataKey());
|
|
1817
|
+
}
|
|
1818
|
+
async setKey(dek) {
|
|
1819
|
+
this.dek = dek;
|
|
1820
|
+
try {
|
|
1821
|
+
await this.writeDevice(dek);
|
|
1822
|
+
} catch (error) {
|
|
1823
|
+
console.warn("Unable to store the Datalynk device key; offline data will need the password", error);
|
|
1824
|
+
}
|
|
1825
|
+
if (!this.unlocked) this.unlocked$.next(true);
|
|
1826
|
+
}
|
|
1827
|
+
newDataKey() {
|
|
1828
|
+
return crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, true, ["encrypt", "decrypt"]);
|
|
1829
|
+
}
|
|
1830
|
+
async passwordKey(password, salt) {
|
|
1831
|
+
const base = await crypto.subtle.importKey("raw", new TextEncoder().encode(password), "PBKDF2", false, ["deriveKey"]);
|
|
1832
|
+
return crypto.subtle.deriveKey(
|
|
1833
|
+
{ name: "PBKDF2", salt, iterations: ITERATIONS, hash: "SHA-256" },
|
|
1834
|
+
base,
|
|
1835
|
+
{ name: "AES-GCM", length: 256 },
|
|
1836
|
+
false,
|
|
1837
|
+
["wrapKey", "unwrapKey"]
|
|
1838
|
+
);
|
|
1839
|
+
}
|
|
1840
|
+
async unwrapWithPassword(record, password) {
|
|
1841
|
+
const kek = await this.passwordKey(password, fromB64(record.salt));
|
|
1842
|
+
return crypto.subtle.unwrapKey(
|
|
1843
|
+
"raw",
|
|
1844
|
+
fromB64(record.wrapped),
|
|
1845
|
+
kek,
|
|
1846
|
+
{ name: "AES-GCM", iv: fromB64(record.iv) },
|
|
1847
|
+
{ name: "AES-GCM" },
|
|
1848
|
+
true,
|
|
1849
|
+
["encrypt", "decrypt"]
|
|
1850
|
+
);
|
|
1851
|
+
}
|
|
1852
|
+
readRecord() {
|
|
1853
|
+
if (typeof localStorage == "undefined") return null;
|
|
1854
|
+
try {
|
|
1855
|
+
const record = JSON.parse(localStorage.getItem(RECORD_KEY) || "null");
|
|
1856
|
+
if ((record == null ? void 0 : record.v) === 2) return record;
|
|
1857
|
+
if (record) localStorage.removeItem(RECORD_KEY);
|
|
1858
|
+
} catch {
|
|
1859
|
+
localStorage.removeItem(RECORD_KEY);
|
|
1860
|
+
}
|
|
1861
|
+
return null;
|
|
1862
|
+
}
|
|
1863
|
+
writeRecord(record) {
|
|
1864
|
+
localStorage.setItem(RECORD_KEY, JSON.stringify(record));
|
|
1865
|
+
}
|
|
1866
|
+
vaultDb() {
|
|
1867
|
+
return new Promise((resolve, reject) => {
|
|
1868
|
+
const request = indexedDB.open(VAULT_DB, 1);
|
|
1869
|
+
request.onupgradeneeded = () => request.result.createObjectStore(VAULT_STORE);
|
|
1870
|
+
request.onsuccess = () => resolve(request.result);
|
|
1871
|
+
request.onerror = () => reject(request.error);
|
|
1872
|
+
});
|
|
1873
|
+
}
|
|
1874
|
+
async vaultTx(mode, op) {
|
|
1875
|
+
const db = await this.vaultDb();
|
|
1876
|
+
try {
|
|
1877
|
+
return await new Promise((resolve, reject) => {
|
|
1878
|
+
const tx = db.transaction(VAULT_STORE, mode);
|
|
1879
|
+
const request = op(tx.objectStore(VAULT_STORE));
|
|
1880
|
+
tx.oncomplete = () => resolve(request.result);
|
|
1881
|
+
tx.onerror = () => reject(tx.error);
|
|
1882
|
+
tx.onabort = () => reject(tx.error);
|
|
1883
|
+
});
|
|
1884
|
+
} finally {
|
|
1885
|
+
db.close();
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
async readDevice() {
|
|
1889
|
+
try {
|
|
1890
|
+
return await this.vaultTx("readonly", (store) => store.get(DEVICE_KEY)) || null;
|
|
1891
|
+
} catch {
|
|
1892
|
+
return null;
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
async writeDevice(dek) {
|
|
1896
|
+
const key = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, false, ["wrapKey", "unwrapKey"]);
|
|
1897
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
1898
|
+
const wrapped = await crypto.subtle.wrapKey("raw", dek, key, { name: "AES-GCM", iv });
|
|
1899
|
+
await this.vaultTx("readwrite", (store) => store.put({ key, iv, wrapped }, DEVICE_KEY));
|
|
1900
|
+
}
|
|
1901
|
+
async deleteDevice() {
|
|
1902
|
+
try {
|
|
1903
|
+
await this.vaultTx("readwrite", (store) => store.delete(DEVICE_KEY));
|
|
1904
|
+
} catch (error) {
|
|
1905
|
+
console.warn("Unable to remove the Datalynk device key", error);
|
|
1906
|
+
}
|
|
1576
1907
|
}
|
|
1577
|
-
}
|
|
1578
|
-
function forgetOfflineLogin() {
|
|
1579
|
-
localStorage.removeItem(KEY);
|
|
1580
1908
|
}
|
|
1581
1909
|
class Auth {
|
|
1582
1910
|
constructor(api) {
|
|
1583
1911
|
__publicField(this, "onlinePrompt");
|
|
1584
|
-
__publicField(this, "offlineUnlocked", false);
|
|
1585
1912
|
__publicField(this, "wasOffline", false);
|
|
1586
1913
|
/** Current user as an observable */
|
|
1587
1914
|
__publicField(this, "user$", new BehaviorSubject(void 0));
|
|
@@ -1591,21 +1918,12 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1591
1918
|
if (status === "offline" || status === "unavailable") this.wasOffline = true;
|
|
1592
1919
|
if (status !== "online" || !this.wasOffline) return;
|
|
1593
1920
|
this.wasOffline = false;
|
|
1594
|
-
const wasUnlocked = this.offlineUnlocked;
|
|
1595
1921
|
const previousUser = this.user;
|
|
1596
|
-
this.offlineUnlocked = false;
|
|
1597
|
-
this.user = void 0;
|
|
1598
1922
|
try {
|
|
1599
|
-
|
|
1600
|
-
const identity2 = this.api.jwtPayload;
|
|
1601
|
-
if (!user || user.guest || !identity2 || !hasOfflineLogin(identity2.realm, identity2.uid))
|
|
1602
|
-
forgetOfflineLogin();
|
|
1603
|
-
this.user = user;
|
|
1923
|
+
this.user = await this.current(this.api.token, true);
|
|
1604
1924
|
} catch (error) {
|
|
1605
|
-
if (this.api.status
|
|
1606
|
-
else {
|
|
1925
|
+
if (this.api.status !== "unauthorized") {
|
|
1607
1926
|
this.wasOffline = true;
|
|
1608
|
-
this.offlineUnlocked = wasUnlocked;
|
|
1609
1927
|
this.user = previousUser;
|
|
1610
1928
|
}
|
|
1611
1929
|
console.error("Unable to recheck the Datalynk user", error);
|
|
@@ -1622,11 +1940,22 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1622
1940
|
});
|
|
1623
1941
|
if ((_a = this.api.options.offline) == null ? void 0 : _a.length)
|
|
1624
1942
|
this.user$.pipe(filter((u) => u !== void 0)).subscribe((u) => {
|
|
1625
|
-
if (u == null)
|
|
1626
|
-
|
|
1627
|
-
|
|
1943
|
+
if (u == null && !this.api.online) return;
|
|
1944
|
+
const vault = this.api.vault;
|
|
1945
|
+
if (vault) vault.setProfile(u).catch((error) => console.warn("Unable to cache the Datalynk user", error));
|
|
1946
|
+
else if (u == null) localStorage.removeItem("datalynk-user");
|
|
1947
|
+
else localStorage.setItem("datalynk-user", JSON.stringify(u));
|
|
1628
1948
|
});
|
|
1629
1949
|
}
|
|
1950
|
+
/** Offline login/encryption is active for this app */
|
|
1951
|
+
get vault() {
|
|
1952
|
+
var _a;
|
|
1953
|
+
return ((_a = this.api.vault) == null ? void 0 : _a.enabled) ? this.api.vault : void 0;
|
|
1954
|
+
}
|
|
1955
|
+
/** This session may use offline data (always true when offline login is opted out) */
|
|
1956
|
+
get offlineUnlocked() {
|
|
1957
|
+
return !this.vault || this.vault.unlocked;
|
|
1958
|
+
}
|
|
1630
1959
|
/** Current user */
|
|
1631
1960
|
get user() {
|
|
1632
1961
|
return this.user$.getValue();
|
|
@@ -1644,14 +1973,18 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1644
1973
|
*
|
|
1645
1974
|
* @return {Promise<User | null>}
|
|
1646
1975
|
*/
|
|
1647
|
-
async current(token = this.api.token) {
|
|
1648
|
-
var _a;
|
|
1976
|
+
async current(token = this.api.token, refresh = false) {
|
|
1977
|
+
var _a, _b, _c;
|
|
1649
1978
|
if (!token) return null;
|
|
1650
|
-
else if (token == ((_a = this.user) == null ? void 0 : _a.token)) return this.user;
|
|
1651
|
-
else if (!this.api.online
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1979
|
+
else if (!refresh && token == ((_a = this.user) == null ? void 0 : _a.token)) return this.user;
|
|
1980
|
+
else if (!this.api.online) {
|
|
1981
|
+
if (!this.offlineUnlocked || !((_b = this.api.options.offline) == null ? void 0 : _b.length)) return null;
|
|
1982
|
+
if (this.api.vault) return this.api.vault.getProfile();
|
|
1983
|
+
return null;
|
|
1984
|
+
}
|
|
1985
|
+
const user = await this.api.request([{ "$/auth/current": {} }, { "$/env/me": {} }], { token }).then((resp) => resp[0] || resp[1] ? { ...resp[0], ...resp[1] } : null);
|
|
1986
|
+
if (user && !user.guest && this.api.jwtPayload) (_c = this.vault) == null ? void 0 : _c.touch(this.api.jwtPayload.realm, this.api.jwtPayload.uid);
|
|
1987
|
+
return user;
|
|
1655
1988
|
}
|
|
1656
1989
|
/**
|
|
1657
1990
|
* Automatically handle sessions by checking localStorage & URL parameters for a token & prompting
|
|
@@ -1662,16 +1995,17 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1662
1995
|
* @return {Promise<void>} Login complete
|
|
1663
1996
|
*/
|
|
1664
1997
|
async handleLogin(spoke, options) {
|
|
1665
|
-
var _a, _b, _c, _d;
|
|
1998
|
+
var _a, _b, _c, _d, _e;
|
|
1666
1999
|
if (this.onlinePrompt) window.removeEventListener("online", this.onlinePrompt);
|
|
1667
2000
|
if ((_a = this.api.options.offline) == null ? void 0 : _a.length) window.removeEventListener("online", this.onlinePrompt = () => this.handleLogin(spoke, options));
|
|
2001
|
+
await ((_b = this.api.vault) == null ? void 0 : _b.ready);
|
|
1668
2002
|
const urlParams = new URLSearchParams(location.search);
|
|
1669
2003
|
const urlToken = urlParams.getAll("datalynkToken").filter(Boolean).pop();
|
|
1670
2004
|
if (urlToken) {
|
|
1671
2005
|
this.api.token = urlToken;
|
|
1672
2006
|
urlParams.delete("datalynkToken");
|
|
1673
2007
|
location.href = location.pathname + (urlParams.toString() ? "?" + urlParams.toString() : "") + location.hash;
|
|
1674
|
-
} else if (
|
|
2008
|
+
} else if (this.isOffline() && this.vault && !this.vault.unlocked) {
|
|
1675
2009
|
await this.loginPrompt(spoke, options).done;
|
|
1676
2010
|
return;
|
|
1677
2011
|
} else if (this.api.reauthenticationPending && !this.api.online && ((_c = this.api.jwtPayload) == null ? void 0 : _c.realm) == spoke) {
|
|
@@ -1680,6 +2014,8 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1680
2014
|
if (((_d = this.api.jwtPayload) == null ? void 0 : _d.realm) != spoke) {
|
|
1681
2015
|
this.api.token = null;
|
|
1682
2016
|
location.reload();
|
|
2017
|
+
} else if ((_e = this.vault) == null ? void 0 : _e.needsPassword) {
|
|
2018
|
+
await this.loginPrompt(spoke, options).done;
|
|
1683
2019
|
}
|
|
1684
2020
|
} else {
|
|
1685
2021
|
this.api.token = null;
|
|
@@ -1687,6 +2023,9 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1687
2023
|
location.reload();
|
|
1688
2024
|
}
|
|
1689
2025
|
}
|
|
2026
|
+
isOffline() {
|
|
2027
|
+
return !this.api.online || typeof navigator !== "undefined" && navigator.onLine === false;
|
|
2028
|
+
}
|
|
1690
2029
|
/**
|
|
1691
2030
|
* Check whether user has a token
|
|
1692
2031
|
*
|
|
@@ -1738,16 +2077,14 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1738
2077
|
* @returns {Promise<User>} User account
|
|
1739
2078
|
*/
|
|
1740
2079
|
async login(spoke, login, password, opts) {
|
|
1741
|
-
var _a
|
|
1742
|
-
if (
|
|
2080
|
+
var _a;
|
|
2081
|
+
if (this.vault && this.isOffline()) {
|
|
1743
2082
|
const identity2 = this.api.jwtPayload;
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
const user =
|
|
1748
|
-
if (!
|
|
1749
|
-
throw new Error("Offline login is not available for this account on this device.");
|
|
1750
|
-
this.offlineUnlocked = true;
|
|
2083
|
+
if (!identity2 || identity2.realm !== spoke) throw new Error(offlineLoginMessages.none);
|
|
2084
|
+
const result = await this.vault.unlockOffline(spoke, login, identity2.uid, password.trim());
|
|
2085
|
+
if (result !== "ok") throw new Error(offlineLoginMessages[result]);
|
|
2086
|
+
const user = await this.vault.getProfile();
|
|
2087
|
+
if (!user) throw new Error(offlineLoginMessages.none);
|
|
1751
2088
|
this.user = user;
|
|
1752
2089
|
return user;
|
|
1753
2090
|
}
|
|
@@ -1768,8 +2105,8 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1768
2105
|
const data = await resp.json().catch(() => ({}));
|
|
1769
2106
|
if (!resp.ok || data["error"]) throw Object.assign(errorFromCode(resp.status, data.error) || {}, data);
|
|
1770
2107
|
this.api.token = data["token"];
|
|
1771
|
-
if (
|
|
1772
|
-
await
|
|
2108
|
+
if (this.vault && ((_a = this.api.jwtPayload) == null ? void 0 : _a.uid) != null)
|
|
2109
|
+
await this.vault.rememberLogin(spoke, login, this.api.jwtPayload.uid, password.trim()).catch((error) => console.error("Unable to enable offline login", error));
|
|
1773
2110
|
return data;
|
|
1774
2111
|
}
|
|
1775
2112
|
/**
|
|
@@ -1801,8 +2138,9 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1801
2138
|
* @return {Promise<{closed: string, new: string}>}
|
|
1802
2139
|
*/
|
|
1803
2140
|
logout(reload = true) {
|
|
2141
|
+
var _a;
|
|
1804
2142
|
localStorage.removeItem("datalynk-user");
|
|
1805
|
-
|
|
2143
|
+
void ((_a = this.api.vault) == null ? void 0 : _a.forget());
|
|
1806
2144
|
return this.api.request({ "$/auth/logout": {} }).then((resp) => {
|
|
1807
2145
|
if (reload) location.reload();
|
|
1808
2146
|
this.api.token = null;
|
|
@@ -2750,15 +3088,17 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
2750
3088
|
const lowest = resp.reduce((acc, r) => Math.min(acc, r.id), 0);
|
|
2751
3089
|
if (lowest < _Slice.nextId) _Slice.nextId = lowest;
|
|
2752
3090
|
}).finally(() => this.loaded = true);
|
|
2753
|
-
this.cache$.pipe(skip(1)).subscribe(
|
|
3091
|
+
this.cache$.pipe(skip(1)).subscribe((cache) => {
|
|
3092
|
+
const table = this.table;
|
|
3093
|
+
if (!table) return;
|
|
3094
|
+
const save = typeof table.replace == "function" ? table.replace(cache.map((c) => [c.id, c])) : table.clear().then(() => Promise.all(cache.map((c) => table.put(c.id, c))));
|
|
3095
|
+
save.catch((error) => console.warn("Unable to save offline slice cache", error));
|
|
3096
|
+
});
|
|
3097
|
+
this.api.token$.pipe(filter((token) => !!token), take(1)).subscribe(async () => {
|
|
2754
3098
|
var _a2;
|
|
2755
|
-
await ((_a2 = this.
|
|
2756
|
-
|
|
2757
|
-
var _a3;
|
|
2758
|
-
return (_a3 = this.table) == null ? void 0 : _a3.put(c.id, c);
|
|
2759
|
-
}));
|
|
3099
|
+
await ((_a2 = this.api.vault) == null ? void 0 : _a2.whenUnlocked());
|
|
3100
|
+
this.sync();
|
|
2760
3101
|
});
|
|
2761
|
-
this.sync();
|
|
2762
3102
|
this.api.online$.pipe(distinctUntilChanged()).subscribe(async (online) => {
|
|
2763
3103
|
if (!online) return;
|
|
2764
3104
|
if (this.api.expired) await lastValueFrom(this.api.auth.user$.pipe(skip(1), takeWhile((u) => !u || this.api.expired, true)));
|
|
@@ -3410,7 +3750,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
3410
3750
|
} });
|
|
3411
3751
|
}
|
|
3412
3752
|
}
|
|
3413
|
-
const version = "1.6.
|
|
3753
|
+
const version = "1.6.4";
|
|
3414
3754
|
class WebRtc {
|
|
3415
3755
|
constructor(api) {
|
|
3416
3756
|
__publicField(this, "ice");
|
|
@@ -4000,6 +4340,8 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4000
4340
|
__publicField(this, "webrtc");
|
|
4001
4341
|
/** Offline database */
|
|
4002
4342
|
__publicField(this, "database");
|
|
4343
|
+
/** Offline login + offline cache encryption */
|
|
4344
|
+
__publicField(this, "vault");
|
|
4003
4345
|
/** Options */
|
|
4004
4346
|
__publicField(this, "options");
|
|
4005
4347
|
/** Created slices */
|
|
@@ -4025,7 +4367,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4025
4367
|
__publicField(this, "online$", new BehaviorSubject(this.initialOnline));
|
|
4026
4368
|
/** API Session token */
|
|
4027
4369
|
__publicField(this, "token$", new BehaviorSubject(void 0));
|
|
4028
|
-
var _a, _b;
|
|
4370
|
+
var _a, _b, _c;
|
|
4029
4371
|
this.origin = origin;
|
|
4030
4372
|
this.url = `${new URL(origin).origin}/api/`;
|
|
4031
4373
|
const development = this.isDevelopmentEnvironment(origin);
|
|
@@ -4049,6 +4391,11 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4049
4391
|
}
|
|
4050
4392
|
};
|
|
4051
4393
|
if (development) this.options.watchTokenExpiry = false;
|
|
4394
|
+
const offlineLogin = this.options.offlineLogin ?? true;
|
|
4395
|
+
this.vault = new OfflineVault(
|
|
4396
|
+
!!((_a = this.options.offline) == null ? void 0 : _a.length) && !!this.options.saveSession && offlineLogin !== false,
|
|
4397
|
+
typeof offlineLogin == "object" ? offlineLogin : {}
|
|
4398
|
+
);
|
|
4052
4399
|
if (this.options.saveSession && typeof localStorage != "undefined") {
|
|
4053
4400
|
this.token = localStorage.getItem(this.localStorageKey) || null;
|
|
4054
4401
|
this.token$.pipe(distinctUntilChanged()).subscribe((token) => {
|
|
@@ -4080,6 +4427,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4080
4427
|
this.webrtc = new WebRtc(this);
|
|
4081
4428
|
if (typeof indexedDB != "undefined") {
|
|
4082
4429
|
this.database = new Database("datalynk", ["pending", ...this.options.offline || []]);
|
|
4430
|
+
this.database.cipher = this.vault;
|
|
4083
4431
|
if (this.options.replayOfflineQueue !== false) this.online$.subscribe((online) => {
|
|
4084
4432
|
if (online) void this.replayPendingQueue();
|
|
4085
4433
|
});
|
|
@@ -4089,10 +4437,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4089
4437
|
window.addEventListener("offline", () => this.setConnectionStatus("offline"));
|
|
4090
4438
|
this.startHeartbeat();
|
|
4091
4439
|
}
|
|
4092
|
-
if ((
|
|
4440
|
+
if ((_b = this.options.offline) == null ? void 0 : _b.length) {
|
|
4093
4441
|
this.pwa.setup();
|
|
4094
4442
|
if (typeof indexedDB == "undefined") throw new Error("Cannot enable offline support, indexedDB is not available in this environment");
|
|
4095
|
-
(
|
|
4443
|
+
(_c = this.options.offline) == null ? void 0 : _c.forEach((id) => this.slice(id));
|
|
4096
4444
|
if (this.options.serviceWorker && typeof navigator["serviceWorker"] != "undefined") {
|
|
4097
4445
|
(async () => {
|
|
4098
4446
|
let initialized = false;
|
|
@@ -4404,7 +4752,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4404
4752
|
} catch {
|
|
4405
4753
|
throw new UnexpectedApiResponseError(resp, body);
|
|
4406
4754
|
}
|
|
4407
|
-
if (resp.status === 401) this.markUnauthorized(token);
|
|
4755
|
+
if (resp.status === 401 && token) this.markUnauthorized(token);
|
|
4408
4756
|
if (!resp.ok || (data == null ? void 0 : data.error)) {
|
|
4409
4757
|
const error = Object.assign(errorFromCode(resp.status, data == null ? void 0 : data.error), data);
|
|
4410
4758
|
Object.defineProperty(error, "response", { value: resp, configurable: true });
|
|
@@ -4656,9 +5004,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4656
5004
|
}));
|
|
4657
5005
|
}
|
|
4658
5006
|
async finishUnauthorizedLogout(preservePending = false) {
|
|
4659
|
-
var _a;
|
|
5007
|
+
var _a, _b, _c;
|
|
4660
5008
|
if (this.options.manageSession === false) return;
|
|
4661
|
-
|
|
5009
|
+
if (preservePending) await ((_a = this.vault) == null ? void 0 : _a.lock());
|
|
5010
|
+
else await ((_b = this.vault) == null ? void 0 : _b.forget());
|
|
4662
5011
|
if (typeof localStorage != "undefined") {
|
|
4663
5012
|
localStorage.removeItem(this.localStorageKey);
|
|
4664
5013
|
localStorage.removeItem("datalynk-user");
|
|
@@ -4679,7 +5028,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4679
5028
|
console.error("Unable to clear Datalynk service worker cache", error);
|
|
4680
5029
|
}
|
|
4681
5030
|
}
|
|
4682
|
-
if (typeof navigator != "undefined" && ((
|
|
5031
|
+
if (typeof navigator != "undefined" && ((_c = navigator.serviceWorker) == null ? void 0 : _c.controller)) {
|
|
4683
5032
|
navigator.serviceWorker.controller.postMessage({ token: null });
|
|
4684
5033
|
}
|
|
4685
5034
|
if (typeof location != "undefined" && typeof location.reload == "function")
|
|
@@ -4820,7 +5169,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4820
5169
|
if (!this.online || this.authenticationInvalid || this.offlineAuthenticationExpired) break;
|
|
4821
5170
|
if (owner && !this.ownersMatch(owner, this.tokenIdentity(this.token))) break;
|
|
4822
5171
|
const request = await table.get(key);
|
|
4823
|
-
if (request == null)
|
|
5172
|
+
if (request == null) {
|
|
5173
|
+
await table.delete(key);
|
|
5174
|
+
continue;
|
|
5175
|
+
}
|
|
4824
5176
|
try {
|
|
4825
5177
|
await this.request(request);
|
|
4826
5178
|
await table.delete(key);
|
|
@@ -4946,7 +5298,6 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4946
5298
|
});
|
|
4947
5299
|
}
|
|
4948
5300
|
request(data, options = {}) {
|
|
4949
|
-
var _a, _b;
|
|
4950
5301
|
data = typeof data == "string" ? { [data]: {} } : data;
|
|
4951
5302
|
let key = JSON.stringify(data);
|
|
4952
5303
|
if (this.offline && typeof navigator != "undefined") {
|
|
@@ -4954,8 +5305,8 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4954
5305
|
if (options.offline) {
|
|
4955
5306
|
if (!this.ensureOfflineSessionOwner(this.token))
|
|
4956
5307
|
return Promise.reject(errorFromCode(409, "Offline queue belongs to another authenticated user"));
|
|
4957
|
-
|
|
4958
|
-
return
|
|
5308
|
+
if (!this.database) return Promise.reject(new Error("Offline queue storage is unavailable"));
|
|
5309
|
+
return this.database.table("pending").add(data, key).then(() => void 0);
|
|
4959
5310
|
}
|
|
4960
5311
|
return Promise.reject(errorFromCode(503, this.status === "unavailable" ? "Datalynk is unavailable" : "You are offline"));
|
|
4961
5312
|
}
|