@auxilium/datalynk-client 1.5.2 → 1.6.1
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/api.d.ts +27 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/auth.d.ts +2 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/chat-entry.d.ts +3 -0
- package/dist/chat-entry.d.ts.map +1 -0
- package/dist/chat-gui.d.ts +64 -0
- package/dist/chat-gui.d.ts.map +1 -0
- package/dist/chat-gui.view.d.ts +3 -0
- package/dist/chat-gui.view.d.ts.map +1 -0
- package/dist/chat.cjs +2202 -0
- package/dist/chat.d.ts +75 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.mjs +2198 -0
- package/dist/index.cjs +347 -23
- package/dist/index.mjs +347 -23
- package/dist/offline-auth.d.ts +5 -0
- package/dist/offline-auth.d.ts.map +1 -0
- package/dist/service.worker.mjs +113 -14
- package/dist/socket.d.ts +14 -1
- package/dist/socket.d.ts.map +1 -1
- package/package.json +7 -2
package/dist/index.cjs
CHANGED
|
@@ -1536,13 +1536,81 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1536
1536
|
`;
|
|
1537
1537
|
});
|
|
1538
1538
|
let LoginPrompt = _LoginPrompt;
|
|
1539
|
+
const KEY = "datalynk-offline-login";
|
|
1540
|
+
const ITERATIONS = 31e4;
|
|
1541
|
+
async function hash(password, salt) {
|
|
1542
|
+
const key = await crypto.subtle.importKey("raw", new TextEncoder().encode(password), "PBKDF2", false, ["deriveBits"]);
|
|
1543
|
+
return Array.from(new Uint8Array(await crypto.subtle.deriveBits(
|
|
1544
|
+
{ name: "PBKDF2", salt: new Uint8Array(salt), iterations: ITERATIONS, hash: "SHA-256" },
|
|
1545
|
+
key,
|
|
1546
|
+
256
|
|
1547
|
+
)));
|
|
1548
|
+
}
|
|
1549
|
+
async function rememberOfflineLogin(realm, login, uid, password) {
|
|
1550
|
+
const salt = crypto.getRandomValues(new Uint8Array(16));
|
|
1551
|
+
const record = { realm, login: login.trim().toLowerCase(), uid, salt: Array.from(salt), hash: await hash(password, salt) };
|
|
1552
|
+
localStorage.setItem(KEY, JSON.stringify(record));
|
|
1553
|
+
}
|
|
1554
|
+
async function verifyOfflineLogin(realm, login, uid, password) {
|
|
1555
|
+
const saved = localStorage.getItem(KEY);
|
|
1556
|
+
if (!saved) return false;
|
|
1557
|
+
let record;
|
|
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;
|
|
1569
|
+
}
|
|
1570
|
+
function hasOfflineLogin(realm, uid) {
|
|
1571
|
+
try {
|
|
1572
|
+
const record = JSON.parse(localStorage.getItem(KEY) || "null");
|
|
1573
|
+
return (record == null ? void 0 : record.realm) === realm && (record == null ? void 0 : record.uid) === uid;
|
|
1574
|
+
} catch {
|
|
1575
|
+
return false;
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
function forgetOfflineLogin() {
|
|
1579
|
+
localStorage.removeItem(KEY);
|
|
1580
|
+
}
|
|
1539
1581
|
class Auth {
|
|
1540
1582
|
constructor(api) {
|
|
1541
1583
|
__publicField(this, "onlinePrompt");
|
|
1584
|
+
__publicField(this, "offlineUnlocked", false);
|
|
1585
|
+
__publicField(this, "wasOffline", false);
|
|
1542
1586
|
/** Current user as an observable */
|
|
1543
1587
|
__publicField(this, "user$", new BehaviorSubject(void 0));
|
|
1544
1588
|
var _a;
|
|
1545
1589
|
this.api = api;
|
|
1590
|
+
this.api.status$.subscribe(async (status) => {
|
|
1591
|
+
if (status === "offline" || status === "unavailable") this.wasOffline = true;
|
|
1592
|
+
if (status !== "online" || !this.wasOffline) return;
|
|
1593
|
+
this.wasOffline = false;
|
|
1594
|
+
const wasUnlocked = this.offlineUnlocked;
|
|
1595
|
+
const previousUser = this.user;
|
|
1596
|
+
this.offlineUnlocked = false;
|
|
1597
|
+
this.user = void 0;
|
|
1598
|
+
try {
|
|
1599
|
+
const user = await this.current();
|
|
1600
|
+
const identity2 = this.api.jwtPayload;
|
|
1601
|
+
if (!user || user.guest || !identity2 || !hasOfflineLogin(identity2.realm, identity2.uid))
|
|
1602
|
+
forgetOfflineLogin();
|
|
1603
|
+
this.user = user;
|
|
1604
|
+
} catch (error) {
|
|
1605
|
+
if (this.api.status === "unauthorized") forgetOfflineLogin();
|
|
1606
|
+
else {
|
|
1607
|
+
this.wasOffline = true;
|
|
1608
|
+
this.offlineUnlocked = wasUnlocked;
|
|
1609
|
+
this.user = previousUser;
|
|
1610
|
+
}
|
|
1611
|
+
console.error("Unable to recheck the Datalynk user", error);
|
|
1612
|
+
}
|
|
1613
|
+
});
|
|
1546
1614
|
this.api.token$.subscribe(async (token) => {
|
|
1547
1615
|
if (token === void 0) return;
|
|
1548
1616
|
try {
|
|
@@ -1554,8 +1622,9 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1554
1622
|
});
|
|
1555
1623
|
if ((_a = this.api.options.offline) == null ? void 0 : _a.length)
|
|
1556
1624
|
this.user$.pipe(filter((u) => u !== void 0)).subscribe((u) => {
|
|
1557
|
-
if (u == null)
|
|
1558
|
-
|
|
1625
|
+
if (u == null) {
|
|
1626
|
+
if (this.api.online) localStorage.removeItem("datalynk-user");
|
|
1627
|
+
} else localStorage.setItem("datalynk-user", JSON.stringify(u));
|
|
1559
1628
|
});
|
|
1560
1629
|
}
|
|
1561
1630
|
/** Current user */
|
|
@@ -1579,8 +1648,9 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1579
1648
|
var _a;
|
|
1580
1649
|
if (!token) return null;
|
|
1581
1650
|
else if (token == ((_a = this.user) == null ? void 0 : _a.token)) return this.user;
|
|
1582
|
-
else if (!this.api.online && typeof localStorage != "undefined" && localStorage.getItem("datalynk-user"))
|
|
1651
|
+
else if (!this.api.online && this.offlineUnlocked && typeof localStorage != "undefined" && localStorage.getItem("datalynk-user"))
|
|
1583
1652
|
return JSON.parse(localStorage.getItem("datalynk-user"));
|
|
1653
|
+
else if (!this.api.online) return null;
|
|
1584
1654
|
return this.api.request([{ "$/auth/current": {} }, { "$/env/me": {} }], { token }).then((resp) => resp[0] || resp[1] ? { ...resp[0], ...resp[1] } : null);
|
|
1585
1655
|
}
|
|
1586
1656
|
/**
|
|
@@ -1592,7 +1662,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1592
1662
|
* @return {Promise<void>} Login complete
|
|
1593
1663
|
*/
|
|
1594
1664
|
async handleLogin(spoke, options) {
|
|
1595
|
-
var _a, _b, _c;
|
|
1665
|
+
var _a, _b, _c, _d;
|
|
1596
1666
|
if (this.onlinePrompt) window.removeEventListener("online", this.onlinePrompt);
|
|
1597
1667
|
if ((_a = this.api.options.offline) == null ? void 0 : _a.length) window.removeEventListener("online", this.onlinePrompt = () => this.handleLogin(spoke, options));
|
|
1598
1668
|
const urlParams = new URLSearchParams(location.search);
|
|
@@ -1601,10 +1671,13 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1601
1671
|
this.api.token = urlToken;
|
|
1602
1672
|
urlParams.delete("datalynkToken");
|
|
1603
1673
|
location.href = location.pathname + (urlParams.toString() ? "?" + urlParams.toString() : "") + location.hash;
|
|
1604
|
-
} else if (
|
|
1674
|
+
} else if (!this.api.online && ((_b = this.api.options.offline) == null ? void 0 : _b.length) && !this.offlineUnlocked) {
|
|
1675
|
+
await this.loginPrompt(spoke, options).done;
|
|
1676
|
+
return;
|
|
1677
|
+
} else if (this.api.reauthenticationPending && !this.api.online && ((_c = this.api.jwtPayload) == null ? void 0 : _c.realm) == spoke) {
|
|
1605
1678
|
return;
|
|
1606
1679
|
} else if (this.api.token && !this.api.expired) {
|
|
1607
|
-
if (((
|
|
1680
|
+
if (((_d = this.api.jwtPayload) == null ? void 0 : _d.realm) != spoke) {
|
|
1608
1681
|
this.api.token = null;
|
|
1609
1682
|
location.reload();
|
|
1610
1683
|
}
|
|
@@ -1664,10 +1737,23 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1664
1737
|
* @param {twoFactor?: string, expire?: string} opts 2FA code & expire date (YYYY-MM-DD)
|
|
1665
1738
|
* @returns {Promise<User>} User account
|
|
1666
1739
|
*/
|
|
1667
|
-
login(spoke, login, password, opts) {
|
|
1740
|
+
async login(spoke, login, password, opts) {
|
|
1741
|
+
var _a, _b, _c;
|
|
1742
|
+
if (((_a = this.api.options.offline) == null ? void 0 : _a.length) && (!this.api.online || typeof navigator !== "undefined" && !navigator.onLine)) {
|
|
1743
|
+
const identity2 = this.api.jwtPayload;
|
|
1744
|
+
const saved = localStorage.getItem("datalynk-user");
|
|
1745
|
+
if (!identity2 || identity2.realm !== spoke || !saved || !await verifyOfflineLogin(spoke, login, identity2.uid, password.trim()))
|
|
1746
|
+
throw new Error("Offline login is not available for this account on this device.");
|
|
1747
|
+
const user = JSON.parse(saved);
|
|
1748
|
+
if (![user.login, user.email].some((value) => (value == null ? void 0 : value.toLowerCase()) === login.trim().toLowerCase()))
|
|
1749
|
+
throw new Error("Offline login is not available for this account on this device.");
|
|
1750
|
+
this.offlineUnlocked = true;
|
|
1751
|
+
this.user = user;
|
|
1752
|
+
return user;
|
|
1753
|
+
}
|
|
1668
1754
|
const date = /* @__PURE__ */ new Date();
|
|
1669
1755
|
date.setFullYear(date.getFullYear() + 1);
|
|
1670
|
-
|
|
1756
|
+
const resp = await fetch(`${this.api.url}login`, {
|
|
1671
1757
|
method: "POST",
|
|
1672
1758
|
headers: { "Content-Type": "application/json" },
|
|
1673
1759
|
body: JSON.stringify(clean({
|
|
@@ -1678,12 +1764,13 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1678
1764
|
expireAt: (opts == null ? void 0 : opts.expire) == null ? `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, "0")}-${date.getDate().toString().padStart(2, "0")}` : opts == null ? void 0 : opts.expire,
|
|
1679
1765
|
dateFormat: "ISO8601"
|
|
1680
1766
|
}))
|
|
1681
|
-
}).then(async (resp) => {
|
|
1682
|
-
const data = await resp.json().catch(() => ({}));
|
|
1683
|
-
if (!resp.ok || data["error"]) throw Object.assign(errorFromCode(resp.status, data.error) || {}, data);
|
|
1684
|
-
this.api.token = data["token"];
|
|
1685
|
-
return data;
|
|
1686
1767
|
});
|
|
1768
|
+
const data = await resp.json().catch(() => ({}));
|
|
1769
|
+
if (!resp.ok || data["error"]) throw Object.assign(errorFromCode(resp.status, data.error) || {}, data);
|
|
1770
|
+
this.api.token = data["token"];
|
|
1771
|
+
if (((_b = this.api.options.offline) == null ? void 0 : _b.length) && this.api.options.saveSession && ((_c = this.api.jwtPayload) == null ? void 0 : _c.uid) != null)
|
|
1772
|
+
await rememberOfflineLogin(spoke, login, this.api.jwtPayload.uid, password.trim());
|
|
1773
|
+
return data;
|
|
1687
1774
|
}
|
|
1688
1775
|
/**
|
|
1689
1776
|
* Login as guest user
|
|
@@ -1715,6 +1802,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1715
1802
|
*/
|
|
1716
1803
|
logout(reload = true) {
|
|
1717
1804
|
localStorage.removeItem("datalynk-user");
|
|
1805
|
+
forgetOfflineLogin();
|
|
1718
1806
|
return this.api.request({ "$/auth/logout": {} }).then((resp) => {
|
|
1719
1807
|
if (reload) location.reload();
|
|
1720
1808
|
this.api.token = null;
|
|
@@ -3106,6 +3194,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
3106
3194
|
// [ Callback, Re-subscribe ]
|
|
3107
3195
|
__publicField(this, "retry");
|
|
3108
3196
|
__publicField(this, "socket");
|
|
3197
|
+
__publicField(this, "connectGeneration", 0);
|
|
3109
3198
|
__publicField(this, "open", false);
|
|
3110
3199
|
this.api = api;
|
|
3111
3200
|
this.options = options;
|
|
@@ -3114,7 +3203,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
3114
3203
|
this.options.url = origin.replace("http", "ws").replace(/:\d+/g, "") + `/s/`;
|
|
3115
3204
|
}
|
|
3116
3205
|
if (this.options.url !== false)
|
|
3117
|
-
api.token$.pipe(filter((u) => u !== void 0), distinctUntilChanged()).subscribe(() => this.connect());
|
|
3206
|
+
api.token$.pipe(filter((u) => u !== void 0), distinctUntilChanged()).subscribe(() => void this.connect());
|
|
3118
3207
|
}
|
|
3119
3208
|
/**
|
|
3120
3209
|
* Add listener for all socket events
|
|
@@ -3136,6 +3225,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
3136
3225
|
*/
|
|
3137
3226
|
close() {
|
|
3138
3227
|
var _a;
|
|
3228
|
+
if (this.options.authentication === "ticket") this.connectGeneration++;
|
|
3139
3229
|
if (this.open) console.debug("Datalynk socket: disconnected");
|
|
3140
3230
|
this.open = false;
|
|
3141
3231
|
(_a = this.socket) == null ? void 0 : _a.close();
|
|
@@ -3148,6 +3238,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
3148
3238
|
* @param {number} timeout Retry to connect every x seconds
|
|
3149
3239
|
*/
|
|
3150
3240
|
connect(timeout = 30) {
|
|
3241
|
+
if (this.options.authentication === "ticket") return this.connectWithTicket(timeout);
|
|
3151
3242
|
if (this.options.url === false) return console.warn("Datalynk socket disabled");
|
|
3152
3243
|
if (this.open) this.close();
|
|
3153
3244
|
this.retry = setTimeout(() => {
|
|
@@ -3177,6 +3268,80 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
3177
3268
|
};
|
|
3178
3269
|
}
|
|
3179
3270
|
}
|
|
3271
|
+
/**
|
|
3272
|
+
* Ticket-authenticated connection path, used only once a chat-capable host switches
|
|
3273
|
+
* this instance to `authentication: 'ticket'` (see Api.openChat). Entirely separate
|
|
3274
|
+
* from the token path above so existing non-chat consumers are never affected.
|
|
3275
|
+
*/
|
|
3276
|
+
async connectWithTicket(timeout = 30) {
|
|
3277
|
+
var _a;
|
|
3278
|
+
if (this.options.url === false) return console.warn("Datalynk socket disabled");
|
|
3279
|
+
const generation = ++this.connectGeneration;
|
|
3280
|
+
if (this.open || this.socket) {
|
|
3281
|
+
this.open = false;
|
|
3282
|
+
(_a = this.socket) == null ? void 0 : _a.close();
|
|
3283
|
+
this.socket = void 0;
|
|
3284
|
+
}
|
|
3285
|
+
if (this.retry) clearTimeout(this.retry);
|
|
3286
|
+
this.retry = setTimeout(() => {
|
|
3287
|
+
if (this.open || generation !== this.connectGeneration) return;
|
|
3288
|
+
void this.connect(timeout);
|
|
3289
|
+
}, timeout * 1e3);
|
|
3290
|
+
if (!this.api.online || !this.api.token) return;
|
|
3291
|
+
let socketUrl = this.normalizeUrl(String(this.options.url || ""));
|
|
3292
|
+
try {
|
|
3293
|
+
const response = await this.api.request({ "$/chat/socketTicket": {} }, { noOptimize: true });
|
|
3294
|
+
if (generation !== this.connectGeneration) return;
|
|
3295
|
+
if (response == null ? void 0 : response.ticket) socketUrl += `${socketUrl.includes("?") ? "&" : "?"}ticket=${encodeURIComponent(response.ticket)}`;
|
|
3296
|
+
else throw new Error("Missing socket ticket");
|
|
3297
|
+
} catch (_) {
|
|
3298
|
+
if (generation !== this.connectGeneration) return;
|
|
3299
|
+
this.scheduleReconnect(generation, timeout);
|
|
3300
|
+
return;
|
|
3301
|
+
}
|
|
3302
|
+
if (generation !== this.connectGeneration) return;
|
|
3303
|
+
this.socket = new WebSocket(socketUrl);
|
|
3304
|
+
this.socket.onopen = () => void 0;
|
|
3305
|
+
this.socket.onclose = () => {
|
|
3306
|
+
this.open = false;
|
|
3307
|
+
this.socket = void 0;
|
|
3308
|
+
this.scheduleReconnect(generation, timeout);
|
|
3309
|
+
};
|
|
3310
|
+
this.socket.onerror = () => {
|
|
3311
|
+
};
|
|
3312
|
+
this.socket.onmessage = (message) => {
|
|
3313
|
+
var _a2;
|
|
3314
|
+
let payload;
|
|
3315
|
+
try {
|
|
3316
|
+
payload = JSON.parse(message.data);
|
|
3317
|
+
} catch {
|
|
3318
|
+
return;
|
|
3319
|
+
}
|
|
3320
|
+
if (payload.connected != void 0) {
|
|
3321
|
+
if (payload.connected) {
|
|
3322
|
+
this.open = true;
|
|
3323
|
+
if (this.retry) clearTimeout(this.retry);
|
|
3324
|
+
this.retry = null;
|
|
3325
|
+
console.debug("Datalynk socket: connected");
|
|
3326
|
+
this.listeners.forEach((l) => l[1]());
|
|
3327
|
+
} else {
|
|
3328
|
+
this.open = false;
|
|
3329
|
+
console.warn(`Datalynk socket failed: ${payload.error || "authentication failed"}`);
|
|
3330
|
+
(_a2 = this.socket) == null ? void 0 : _a2.close();
|
|
3331
|
+
}
|
|
3332
|
+
} else {
|
|
3333
|
+
this.listeners.forEach((l) => l[0](payload));
|
|
3334
|
+
}
|
|
3335
|
+
};
|
|
3336
|
+
}
|
|
3337
|
+
scheduleReconnect(generation, timeout) {
|
|
3338
|
+
if (generation !== this.connectGeneration || this.options.url === false) return;
|
|
3339
|
+
if (this.retry) clearTimeout(this.retry);
|
|
3340
|
+
this.retry = setTimeout(() => {
|
|
3341
|
+
if (generation !== this.connectGeneration) return;
|
|
3342
|
+
void this.connect(timeout);
|
|
3343
|
+
}, timeout * 1e3);
|
|
3344
|
+
}
|
|
3180
3345
|
/**
|
|
3181
3346
|
* Send data to socket server
|
|
3182
3347
|
*
|
|
@@ -3203,6 +3368,17 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
3203
3368
|
unsubscribe();
|
|
3204
3369
|
};
|
|
3205
3370
|
}
|
|
3371
|
+
normalizeUrl(value) {
|
|
3372
|
+
let url = value.replace(/^http:\/\//i, "ws://").replace(/^https:\/\//i, "wss://");
|
|
3373
|
+
try {
|
|
3374
|
+
const parsed = new URL(url);
|
|
3375
|
+
if (parsed.pathname === "/" || parsed.pathname === "") parsed.pathname = "/s/";
|
|
3376
|
+
else if (parsed.pathname === "/s") parsed.pathname = "/s/";
|
|
3377
|
+
url = parsed.toString();
|
|
3378
|
+
} catch {
|
|
3379
|
+
}
|
|
3380
|
+
return url;
|
|
3381
|
+
}
|
|
3206
3382
|
}
|
|
3207
3383
|
class Superuser {
|
|
3208
3384
|
constructor(api) {
|
|
@@ -3234,7 +3410,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
3234
3410
|
} });
|
|
3235
3411
|
}
|
|
3236
3412
|
}
|
|
3237
|
-
const version = "1.
|
|
3413
|
+
const version = "1.6.1";
|
|
3238
3414
|
class WebRtc {
|
|
3239
3415
|
constructor(api) {
|
|
3240
3416
|
__publicField(this, "ice");
|
|
@@ -3801,6 +3977,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
3801
3977
|
__publicField(this, "tokenStorageListener", null);
|
|
3802
3978
|
/** Pending requests cache */
|
|
3803
3979
|
__publicField(this, "pending", {});
|
|
3980
|
+
__publicField(this, "chatModulePromise", null);
|
|
3981
|
+
__publicField(this, "chatInstance", null);
|
|
3982
|
+
__publicField(this, "chatLauncherHost", null);
|
|
3983
|
+
__publicField(this, "chatLauncherButton", null);
|
|
3804
3984
|
/** Helpers */
|
|
3805
3985
|
/** Authentication */
|
|
3806
3986
|
__publicField(this, "auth");
|
|
@@ -3855,6 +4035,8 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
3855
4035
|
offline: [],
|
|
3856
4036
|
origin: typeof location !== "undefined" ? location.host : "Unknown",
|
|
3857
4037
|
saveSession: true,
|
|
4038
|
+
manageSession: true,
|
|
4039
|
+
chat: false,
|
|
3858
4040
|
replayOfflineQueue: true,
|
|
3859
4041
|
watchTokenExpiry: true,
|
|
3860
4042
|
serviceWorker: "/service.worker.mjs",
|
|
@@ -3886,9 +4068,9 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
3886
4068
|
window.addEventListener("storage", this.tokenStorageListener);
|
|
3887
4069
|
}
|
|
3888
4070
|
}
|
|
3889
|
-
if (this.options.watchTokenExpiry)
|
|
4071
|
+
if (this.options.manageSession !== false && this.options.watchTokenExpiry)
|
|
3890
4072
|
this.token$.pipe(distinctUntilChanged()).subscribe((token) => this.scheduleTokenExpiry(token));
|
|
3891
|
-
this.socket = new Socket(this, { url: options.socket });
|
|
4073
|
+
this.socket = new Socket(this, { url: options.socket, authentication: options.socketAuthentication, realm: options.socketRealm });
|
|
3892
4074
|
this.gps = new Gps(this, options.gps);
|
|
3893
4075
|
this.auth = new Auth(this);
|
|
3894
4076
|
this.files = new Files(this);
|
|
@@ -3914,12 +4096,19 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
3914
4096
|
if (this.options.serviceWorker && typeof navigator["serviceWorker"] != "undefined") {
|
|
3915
4097
|
(async () => {
|
|
3916
4098
|
const init = () => {
|
|
3917
|
-
var _a2;
|
|
4099
|
+
var _a2, _b2;
|
|
3918
4100
|
(_a2 = navigator.serviceWorker.controller) == null ? void 0 : _a2.postMessage({ options: { ...this.options, url: this.url } });
|
|
3919
4101
|
this.token$.subscribe((token) => {
|
|
3920
4102
|
var _a3;
|
|
3921
4103
|
return (_a3 = navigator.serviceWorker.controller) == null ? void 0 : _a3.postMessage({ token });
|
|
3922
4104
|
});
|
|
4105
|
+
const assets = [
|
|
4106
|
+
location.pathname,
|
|
4107
|
+
...Array.from(document.querySelectorAll('script[src], link[rel="stylesheet"][href], link[rel="icon"][href], img[src]')).map((element) => element.getAttribute("src") || element.getAttribute("href") || ""),
|
|
4108
|
+
...performance.getEntriesByType("resource").filter((entry) => ["script", "link", "css", "img", "font"].includes(entry.initiatorType)).map((entry) => entry.name),
|
|
4109
|
+
...this.options.offlineAssets || []
|
|
4110
|
+
].filter(Boolean).map((value) => new URL(value, location.href).href);
|
|
4111
|
+
(_b2 = navigator.serviceWorker.controller) == null ? void 0 : _b2.postMessage({ offlineAssets: assets });
|
|
3923
4112
|
};
|
|
3924
4113
|
await navigator.serviceWorker.getRegistration(this.options.serviceWorker).then((reg) => reg ?? navigator.serviceWorker.register(this.options.serviceWorker, { scope: "/", type: "module" }));
|
|
3925
4114
|
navigator.serviceWorker.ready.then(() => {
|
|
@@ -3929,6 +4118,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
3929
4118
|
})();
|
|
3930
4119
|
}
|
|
3931
4120
|
}
|
|
4121
|
+
this.setupChatLauncher();
|
|
3932
4122
|
}
|
|
3933
4123
|
/** Is token expired */
|
|
3934
4124
|
get expired() {
|
|
@@ -4000,7 +4190,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4000
4190
|
var _a;
|
|
4001
4191
|
if (this.recovery && token !== this.recovery.token)
|
|
4002
4192
|
this.cancelRecovery(errorFromCode(401, "Session changed during API recovery"));
|
|
4003
|
-
if (token && this.isTokenExpired(token)) {
|
|
4193
|
+
if (this.options.manageSession !== false && token && this.isTokenExpired(token)) {
|
|
4004
4194
|
if (this.adoptStoredToken(token)) return;
|
|
4005
4195
|
if (this.shouldDeferLocalExpiry()) {
|
|
4006
4196
|
this.deferOfflineExpiry(token);
|
|
@@ -4018,6 +4208,138 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4018
4208
|
}
|
|
4019
4209
|
if (token && ((_a = this.options) == null ? void 0 : _a.replayOfflineQueue) !== false) void this.replayPendingQueue();
|
|
4020
4210
|
}
|
|
4211
|
+
/** Mount the optional launcher without loading or initializing the chat bundle. */
|
|
4212
|
+
setupChatLauncher() {
|
|
4213
|
+
if (!this.options.chat || typeof document === "undefined") return;
|
|
4214
|
+
const mount = () => {
|
|
4215
|
+
if (this.chatLauncherHost || !document.body || document.querySelector("[data-datalynk-chat-launcher]")) return;
|
|
4216
|
+
const host = document.createElement("div");
|
|
4217
|
+
host.setAttribute("data-datalynk-chat-launcher", "");
|
|
4218
|
+
const root = host.attachShadow({ mode: "open" });
|
|
4219
|
+
root.innerHTML = `
|
|
4220
|
+
<style>
|
|
4221
|
+
:host {
|
|
4222
|
+
all: initial;
|
|
4223
|
+
position: fixed;
|
|
4224
|
+
right: 20px;
|
|
4225
|
+
bottom: 20px;
|
|
4226
|
+
z-index: 2147482999;
|
|
4227
|
+
pointer-events: none;
|
|
4228
|
+
}
|
|
4229
|
+
button {
|
|
4230
|
+
display: grid;
|
|
4231
|
+
place-items: center;
|
|
4232
|
+
width: 52px;
|
|
4233
|
+
height: 52px;
|
|
4234
|
+
padding: 0;
|
|
4235
|
+
border: 1px solid #2f596d;
|
|
4236
|
+
border-radius: 50%;
|
|
4237
|
+
background: #3f6f85;
|
|
4238
|
+
color: #fff;
|
|
4239
|
+
box-shadow: 0 4px 14px rgb(15 42 54 / 24%);
|
|
4240
|
+
cursor: pointer;
|
|
4241
|
+
pointer-events: auto;
|
|
4242
|
+
transition: background-color 120ms ease-out, box-shadow 120ms ease-out, transform 120ms ease-out;
|
|
4243
|
+
}
|
|
4244
|
+
button:hover { background: #315d71; box-shadow: 0 6px 18px rgb(15 42 54 / 30%); }
|
|
4245
|
+
button:active { transform: translateY(1px); }
|
|
4246
|
+
button:focus-visible { outline: 3px solid #f2c94c; outline-offset: 3px; }
|
|
4247
|
+
button:disabled { cursor: wait; opacity: .72; }
|
|
4248
|
+
.status {
|
|
4249
|
+
position: absolute;
|
|
4250
|
+
right: 62px;
|
|
4251
|
+
bottom: 7px;
|
|
4252
|
+
width: max-content;
|
|
4253
|
+
max-width: min(260px, calc(100vw - 94px));
|
|
4254
|
+
padding: 9px 11px;
|
|
4255
|
+
border: 1px solid #c6d3d9;
|
|
4256
|
+
border-radius: 6px;
|
|
4257
|
+
background: #fff;
|
|
4258
|
+
box-shadow: 0 3px 12px rgb(15 42 54 / 16%);
|
|
4259
|
+
color: #263942;
|
|
4260
|
+
font: 600 13px/1.35 "Segoe UI", Arial, sans-serif;
|
|
4261
|
+
pointer-events: none;
|
|
4262
|
+
}
|
|
4263
|
+
.status:empty { display: none; }
|
|
4264
|
+
svg { width: 25px; height: 25px; }
|
|
4265
|
+
@media (max-width: 600px) { :host { right: 12px; bottom: 12px; } }
|
|
4266
|
+
@media (prefers-reduced-motion: reduce) { button { transition: none; } }
|
|
4267
|
+
</style>
|
|
4268
|
+
<button type="button" aria-label="Open Datalynk Chat" title="Chat">
|
|
4269
|
+
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
|
4270
|
+
<path fill="currentColor" d="M4 4h16a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H9l-5 3v-3a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2Zm2.5 8.25a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5Zm5.5 0a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5Zm5.5 0a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5Z"/>
|
|
4271
|
+
</svg>
|
|
4272
|
+
</button>
|
|
4273
|
+
<span class="status" role="status" aria-live="polite"></span>`;
|
|
4274
|
+
const button = root.querySelector("button");
|
|
4275
|
+
button.addEventListener("click", () => void this.openChatFromLauncher());
|
|
4276
|
+
document.body.appendChild(host);
|
|
4277
|
+
this.chatLauncherHost = host;
|
|
4278
|
+
this.chatLauncherButton = button;
|
|
4279
|
+
};
|
|
4280
|
+
if (document.body) mount();
|
|
4281
|
+
else document.addEventListener("DOMContentLoaded", mount, { once: true });
|
|
4282
|
+
}
|
|
4283
|
+
async openChatFromLauncher() {
|
|
4284
|
+
const button = this.chatLauncherButton;
|
|
4285
|
+
if (!button) return;
|
|
4286
|
+
const status = button.getRootNode().querySelector(".status");
|
|
4287
|
+
button.disabled = true;
|
|
4288
|
+
button.setAttribute("aria-busy", "true");
|
|
4289
|
+
status.textContent = "Opening chat…";
|
|
4290
|
+
try {
|
|
4291
|
+
await this.openChat();
|
|
4292
|
+
status.textContent = "";
|
|
4293
|
+
} catch (error) {
|
|
4294
|
+
button.disabled = false;
|
|
4295
|
+
button.removeAttribute("aria-busy");
|
|
4296
|
+
status.textContent = "Chat is unavailable for this session.";
|
|
4297
|
+
console.error("Unable to open Datalynk Chat.", error);
|
|
4298
|
+
}
|
|
4299
|
+
}
|
|
4300
|
+
restoreChatLauncher() {
|
|
4301
|
+
if (!this.chatLauncherHost || !this.chatLauncherButton) return;
|
|
4302
|
+
this.chatLauncherHost.hidden = false;
|
|
4303
|
+
this.chatLauncherButton.disabled = false;
|
|
4304
|
+
this.chatLauncherButton.removeAttribute("aria-busy");
|
|
4305
|
+
this.chatLauncherButton.focus();
|
|
4306
|
+
}
|
|
4307
|
+
/** Lazily load and construct chat. No chat code or requests run before this call. */
|
|
4308
|
+
async chat() {
|
|
4309
|
+
if (this.chatInstance) return this.chatInstance;
|
|
4310
|
+
const client = await this.loadChatModule();
|
|
4311
|
+
return this.chatInstance || (this.chatInstance = new client.Chat(this));
|
|
4312
|
+
}
|
|
4313
|
+
/** Lazily load, authorize, and open the reusable chat window. */
|
|
4314
|
+
async openChat(options = {}) {
|
|
4315
|
+
const client = await this.loadChatModule();
|
|
4316
|
+
const chat = await this.chat();
|
|
4317
|
+
if (!await chat.available()) throw new Error("Chat is disabled or this session is not authorized.");
|
|
4318
|
+
if (this.socket.options.authentication !== "ticket") {
|
|
4319
|
+
this.socket.options.authentication = "ticket";
|
|
4320
|
+
void this.socket.connect();
|
|
4321
|
+
}
|
|
4322
|
+
const onClose = options.onClose;
|
|
4323
|
+
const chatWindow = client.openChat(chat, {
|
|
4324
|
+
...options,
|
|
4325
|
+
onClose: () => {
|
|
4326
|
+
this.restoreChatLauncher();
|
|
4327
|
+
onClose == null ? void 0 : onClose();
|
|
4328
|
+
}
|
|
4329
|
+
});
|
|
4330
|
+
if (this.chatLauncherHost) this.chatLauncherHost.hidden = true;
|
|
4331
|
+
return chatWindow;
|
|
4332
|
+
}
|
|
4333
|
+
loadChatModule() {
|
|
4334
|
+
if (!this.chatModulePromise) {
|
|
4335
|
+
const moduleUrl = this.options.chatModule || "./chat.mjs";
|
|
4336
|
+
this.chatModulePromise = import(
|
|
4337
|
+
/* @vite-ignore */
|
|
4338
|
+
moduleUrl
|
|
4339
|
+
);
|
|
4340
|
+
}
|
|
4341
|
+
return this.chatModulePromise;
|
|
4342
|
+
}
|
|
4021
4343
|
async _request(req, options = {}) {
|
|
4022
4344
|
if (this.recovery) throw errorFromCode(503, "Datalynk is unavailable");
|
|
4023
4345
|
const retryRequest = deepCopy(req);
|
|
@@ -4034,7 +4356,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4034
4356
|
/** Execute exactly one HTTP API attempt. Recovery uses this directly to avoid recursive retry loops. */
|
|
4035
4357
|
async _requestOnce(req, options = {}, recoveryAttempt = false) {
|
|
4036
4358
|
const token = options.token || this.token;
|
|
4037
|
-
if (token && this.isTokenExpired(token)) {
|
|
4359
|
+
if (this.options.manageSession !== false && token && this.isTokenExpired(token)) {
|
|
4038
4360
|
if (token === this.token) this.expireToken(token);
|
|
4039
4361
|
throw errorFromCode(401, "Session token expired");
|
|
4040
4362
|
}
|
|
@@ -4201,7 +4523,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4201
4523
|
}
|
|
4202
4524
|
return;
|
|
4203
4525
|
}
|
|
4204
|
-
if (this.token && this.isTokenExpired(this.token)) {
|
|
4526
|
+
if (this.options.manageSession !== false && this.token && this.isTokenExpired(this.token)) {
|
|
4205
4527
|
this.expireToken(this.token);
|
|
4206
4528
|
return;
|
|
4207
4529
|
}
|
|
@@ -4320,6 +4642,8 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4320
4642
|
}
|
|
4321
4643
|
async finishUnauthorizedLogout(preservePending = false) {
|
|
4322
4644
|
var _a;
|
|
4645
|
+
if (this.options.manageSession === false) return;
|
|
4646
|
+
forgetOfflineLogin();
|
|
4323
4647
|
if (typeof localStorage != "undefined") {
|
|
4324
4648
|
localStorage.removeItem(this.localStorageKey);
|
|
4325
4649
|
localStorage.removeItem("datalynk-user");
|
|
@@ -4335,13 +4659,13 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4335
4659
|
await this.clearOfflineSessionCache(preservePending);
|
|
4336
4660
|
if (typeof caches != "undefined") {
|
|
4337
4661
|
try {
|
|
4338
|
-
await
|
|
4662
|
+
await caches.delete("api-settings");
|
|
4339
4663
|
} catch (error) {
|
|
4340
4664
|
console.error("Unable to clear Datalynk service worker cache", error);
|
|
4341
4665
|
}
|
|
4342
4666
|
}
|
|
4343
4667
|
if (typeof navigator != "undefined" && ((_a = navigator.serviceWorker) == null ? void 0 : _a.controller)) {
|
|
4344
|
-
navigator.serviceWorker.controller.postMessage({ token: null
|
|
4668
|
+
navigator.serviceWorker.controller.postMessage({ token: null });
|
|
4345
4669
|
}
|
|
4346
4670
|
if (typeof location != "undefined" && typeof location.reload == "function")
|
|
4347
4671
|
location.reload();
|