@auxilium/datalynk-client 1.5.1 → 1.5.2
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/bin/datalynk-models.mjs +0 -0
- package/dist/api.d.ts +40 -6
- package/dist/api.d.ts.map +1 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/index.cjs +280 -44
- package/dist/index.mjs +280 -44
- package/dist/service.worker.mjs +11 -6
- package/package.json +1 -1
package/bin/datalynk-models.mjs
CHANGED
|
File without changes
|
package/dist/api.d.ts
CHANGED
|
@@ -41,12 +41,14 @@ export type ApiOptions = {
|
|
|
41
41
|
* connectivity or when Datalynk enters an `unavailable` recovery state.
|
|
42
42
|
*/
|
|
43
43
|
offline?: number[];
|
|
44
|
-
/**
|
|
44
|
+
/** @deprecated Connectivity banners are no longer rendered by the SDK. Retained as a no-op for compatibility. */
|
|
45
45
|
offlineBanner?: boolean | 'top' | 'bottom';
|
|
46
46
|
/** Website hostname */
|
|
47
47
|
origin?: string;
|
|
48
48
|
/** Save session token to localStorage to persist logins */
|
|
49
49
|
saveSession?: boolean;
|
|
50
|
+
/** Replay the IndexedDB pending queue when authenticated connectivity returns. Disable in service-worker helper instances. */
|
|
51
|
+
replayOfflineQueue?: boolean;
|
|
50
52
|
/** Watch JWT expiry. This is always disabled on development hosts. */
|
|
51
53
|
watchTokenExpiry?: boolean;
|
|
52
54
|
/** Service worker URL */
|
|
@@ -89,8 +91,9 @@ export interface ApiRequestOptions {
|
|
|
89
91
|
/**
|
|
90
92
|
* Allow this request to be queued while normal API access is unavailable.
|
|
91
93
|
*
|
|
92
|
-
* Queued requests do not receive an immediate response payload.
|
|
93
|
-
*
|
|
94
|
+
* Queued requests do not receive an immediate response payload. If a token
|
|
95
|
+
* expires while Datalynk is offline, queued work is preserved and replay is
|
|
96
|
+
* deferred until the same user successfully reauthenticates.
|
|
94
97
|
*/
|
|
95
98
|
offline?: boolean;
|
|
96
99
|
/** Skip the token translating step */
|
|
@@ -143,7 +146,14 @@ export declare class Api {
|
|
|
143
146
|
/** Track online state */
|
|
144
147
|
private heartbeat;
|
|
145
148
|
private authenticationInvalid;
|
|
149
|
+
/** Token reached exp while the client could not reach Datalynk; keep the offline session alive until reconnect. */
|
|
150
|
+
private offlineAuthenticationExpired;
|
|
146
151
|
private tokenExpiryTimeout;
|
|
152
|
+
private unauthorizedLogout;
|
|
153
|
+
private replayingPending;
|
|
154
|
+
/** If replay is requested while a replay pass is already active, run another pass when it finishes. */
|
|
155
|
+
private pendingReplayRequested;
|
|
156
|
+
private offlineSessionOwner;
|
|
147
157
|
/** Request-specific recovery after a server response proves an API call is broken. */
|
|
148
158
|
private recovery;
|
|
149
159
|
/** Retry the failed request twice immediately, then this long after each failed recovery response. */
|
|
@@ -151,6 +161,8 @@ export declare class Api {
|
|
|
151
161
|
private readonly recoveryImmediateRetries;
|
|
152
162
|
/** LocalStorage key for persisting logins */
|
|
153
163
|
private localStorageKey;
|
|
164
|
+
/** Persisted owner of an offline queue so another login cannot inherit queued writes. */
|
|
165
|
+
private offlineSessionOwnerKey;
|
|
154
166
|
private tokenStorageListener;
|
|
155
167
|
/** Pending requests cache */
|
|
156
168
|
private pending;
|
|
@@ -183,6 +195,8 @@ export declare class Api {
|
|
|
183
195
|
version: string;
|
|
184
196
|
/** Is token expired */
|
|
185
197
|
get expired(): boolean;
|
|
198
|
+
/** Whether authentication expired while offline and must be renewed before replaying queued work. */
|
|
199
|
+
get reauthenticationPending(): boolean;
|
|
186
200
|
/** Get session info from JWT payload */
|
|
187
201
|
get jwtPayload(): JwtPayload | null;
|
|
188
202
|
private onlineOverride;
|
|
@@ -247,9 +261,14 @@ export declare class Api {
|
|
|
247
261
|
private _request;
|
|
248
262
|
/** Execute exactly one HTTP API attempt. Recovery uses this directly to avoid recursive retry loops. */
|
|
249
263
|
private _requestOnce;
|
|
250
|
-
/**
|
|
264
|
+
/**
|
|
265
|
+
* Only failures which indicate Datalynk as a service is unavailable own global
|
|
266
|
+
* recovery. A request-specific application failure must not take the whole
|
|
267
|
+
* client offline: another unrelated API request may still be perfectly healthy.
|
|
268
|
+
*/
|
|
251
269
|
private isRecoverableResponseError;
|
|
252
|
-
|
|
270
|
+
/** Any API error returned in PDO SQLSTATE form uses the offline/recovery path. */
|
|
271
|
+
private isSqlStateError;
|
|
253
272
|
/**
|
|
254
273
|
* A returned server failure immediately makes the client unavailable. The exact
|
|
255
274
|
* failed request is then retried twice back-to-back. After that, retries are
|
|
@@ -262,15 +281,30 @@ export declare class Api {
|
|
|
262
281
|
private finishRecovery;
|
|
263
282
|
private cancelRecovery;
|
|
264
283
|
private checkConnection;
|
|
284
|
+
private isGatewayUnavailableStatus;
|
|
265
285
|
private isTokenExpired;
|
|
266
286
|
private isDevelopmentEnvironment;
|
|
267
287
|
private canAdoptToken;
|
|
268
288
|
/** Adopt a newer canonical token written by another auth path before expiring this session. */
|
|
269
289
|
private adoptStoredToken;
|
|
270
290
|
private scheduleTokenExpiry;
|
|
291
|
+
/** Handle local JWT expiry without treating an offline device like a rejected online session. */
|
|
292
|
+
private expireToken;
|
|
293
|
+
private shouldDeferLocalExpiry;
|
|
294
|
+
private deferOfflineExpiry;
|
|
271
295
|
private markUnauthorized;
|
|
296
|
+
private finishUnauthorizedLogout;
|
|
297
|
+
private clearOfflineSessionCache;
|
|
298
|
+
private tokenIdentity;
|
|
299
|
+
private readOfflineSessionOwner;
|
|
300
|
+
private ownersMatch;
|
|
301
|
+
private rememberOfflineSessionOwner;
|
|
302
|
+
private ensureOfflineSessionOwner;
|
|
303
|
+
private hasOwnedOfflineSession;
|
|
304
|
+
private clearOfflineSessionOwner;
|
|
305
|
+
/** Replay queued writes only after a matching authenticated session is online. */
|
|
306
|
+
private replayPendingQueue;
|
|
272
307
|
private setConnectionStatus;
|
|
273
|
-
private offlineBanner;
|
|
274
308
|
private startHeartbeat;
|
|
275
309
|
private stopHeartbeat;
|
|
276
310
|
/**
|
package/dist/api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4C,QAAQ,EAAQ,MAAM,SAAS,CAAC;AACnF,OAAO,EAAC,eAAe,EAAuB,MAAM,MAAM,CAAC;AAC3D,OAAO,EAAC,IAAI,EAAC,MAAM,QAAQ,CAAC;AAE5B,OAAO,EAAC,GAAG,EAAC,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAC,KAAK,EAAC,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAC,IAAI,EAAC,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAC,GAAG,EAAC,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAC,OAAO,EAAE,KAAK,EAAC,MAAM,SAAS,CAAC;AACvC,OAAO,EAAC,MAAM,EAAC,MAAM,UAAU,CAAC;AAChC,OAAO,EAAC,SAAS,EAAC,MAAM,aAAa,CAAC;AAEtC,OAAO,EAAC,MAAM,EAAC,MAAM,UAAU,CAAC;AAChC,OAAO,EAAC,GAAG,EAAE,UAAU,EAAC,MAAM,OAAO,CAAC;AAEtC,MAAM,MAAM,UAAU,GAAG;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,GAAG,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACZ,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG;IACxB,+DAA+D;IAC/D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kCAAkC;IAClC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,6BAA6B;IAC7B,QAAQ,CAAC,EAAE,GAAG,CAAC;IACf,0BAA0B;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|
package/dist/auth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,GAAG,EAAC,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAC,eAAe,EAAS,MAAM,MAAM,CAAC;AAE7C,OAAO,EAAC,WAAW,EAAE,kBAAkB,EAAC,MAAM,gBAAgB,CAAC;AAE/D,mBAAmB;AACnB,MAAM,MAAM,IAAI,GAAG;IAClB,6BAA6B;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,cAAc;IACd,EAAE,EAAE,MAAM,CAAC;IACX,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8CAA8C;IAC9C,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,mDAAmD;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB,gDAAgD;IAChD,OAAO,EAAE,MAAM,CAAC;IAChB,kCAAkC;IAClC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,kCAAkC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,iCAAiC;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,6CAA6C;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8BAA8B;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,kDAAkD;IAClD,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,6CAA6C;IAC7C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sDAAsD;IACtD,QAAQ,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+BAA+B;IAC/B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,KAAK,EAAE,OAAO,CAAC;IACf,kCAAkC;IAClC,QAAQ,EAAE,OAAO,CAAC;IAClB,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAC;CACd,CAAA;AAED;;GAEG;AACH,qBAAa,IAAI;IAcJ,OAAO,CAAC,QAAQ,CAAC,GAAG;IAbhC,OAAO,CAAC,YAAY,CAAC,CAAM;IAE3B,oCAAoC;IACpC,KAAK,2CAA2D;IAEhE,mBAAmB;IACnB,IAAI,IAAI,IAGO,IAAI,GAAG,IAAI,GAAG,SAAS,CAHM;IAE5C,4BAA4B;IAC5B,IAAI,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,SAAS,EAA4B;IAElE,IAAI,KAAK,kBAAiD;gBAE7B,GAAG,EAAE,GAAG;
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/index.cjs
CHANGED
|
@@ -1553,7 +1553,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1553
1553
|
}
|
|
1554
1554
|
});
|
|
1555
1555
|
if ((_a = this.api.options.offline) == null ? void 0 : _a.length)
|
|
1556
|
-
this.user$.pipe(filter((u) => u !== void 0)).subscribe((u) =>
|
|
1556
|
+
this.user$.pipe(filter((u) => u !== void 0)).subscribe((u) => {
|
|
1557
|
+
if (u == null) localStorage.removeItem("datalynk-user");
|
|
1558
|
+
else localStorage.setItem("datalynk-user", JSON.stringify(u));
|
|
1559
|
+
});
|
|
1557
1560
|
}
|
|
1558
1561
|
/** Current user */
|
|
1559
1562
|
get user() {
|
|
@@ -1589,7 +1592,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1589
1592
|
* @return {Promise<void>} Login complete
|
|
1590
1593
|
*/
|
|
1591
1594
|
async handleLogin(spoke, options) {
|
|
1592
|
-
var _a, _b;
|
|
1595
|
+
var _a, _b, _c;
|
|
1593
1596
|
if (this.onlinePrompt) window.removeEventListener("online", this.onlinePrompt);
|
|
1594
1597
|
if ((_a = this.api.options.offline) == null ? void 0 : _a.length) window.removeEventListener("online", this.onlinePrompt = () => this.handleLogin(spoke, options));
|
|
1595
1598
|
const urlParams = new URLSearchParams(location.search);
|
|
@@ -1598,8 +1601,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
1598
1601
|
this.api.token = urlToken;
|
|
1599
1602
|
urlParams.delete("datalynkToken");
|
|
1600
1603
|
location.href = location.pathname + (urlParams.toString() ? "?" + urlParams.toString() : "") + location.hash;
|
|
1604
|
+
} else if (this.api.reauthenticationPending && !this.api.online && ((_b = this.api.jwtPayload) == null ? void 0 : _b.realm) == spoke) {
|
|
1605
|
+
return;
|
|
1601
1606
|
} else if (this.api.token && !this.api.expired) {
|
|
1602
|
-
if (((
|
|
1607
|
+
if (((_c = this.api.jwtPayload) == null ? void 0 : _c.realm) != spoke) {
|
|
1603
1608
|
this.api.token = null;
|
|
1604
1609
|
location.reload();
|
|
1605
1610
|
}
|
|
@@ -3229,7 +3234,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
3229
3234
|
} });
|
|
3230
3235
|
}
|
|
3231
3236
|
}
|
|
3232
|
-
const version = "1.5.
|
|
3237
|
+
const version = "1.5.2";
|
|
3233
3238
|
class WebRtc {
|
|
3234
3239
|
constructor(api) {
|
|
3235
3240
|
__publicField(this, "ice");
|
|
@@ -3776,7 +3781,14 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
3776
3781
|
timeout: 6e4
|
|
3777
3782
|
});
|
|
3778
3783
|
__publicField(this, "authenticationInvalid", false);
|
|
3784
|
+
/** Token reached exp while the client could not reach Datalynk; keep the offline session alive until reconnect. */
|
|
3785
|
+
__publicField(this, "offlineAuthenticationExpired", false);
|
|
3779
3786
|
__publicField(this, "tokenExpiryTimeout", null);
|
|
3787
|
+
__publicField(this, "unauthorizedLogout", null);
|
|
3788
|
+
__publicField(this, "replayingPending", false);
|
|
3789
|
+
/** If replay is requested while a replay pass is already active, run another pass when it finishes. */
|
|
3790
|
+
__publicField(this, "pendingReplayRequested", false);
|
|
3791
|
+
__publicField(this, "offlineSessionOwner", null);
|
|
3780
3792
|
/** Request-specific recovery after a server response proves an API call is broken. */
|
|
3781
3793
|
__publicField(this, "recovery", null);
|
|
3782
3794
|
/** Retry the failed request twice immediately, then this long after each failed recovery response. */
|
|
@@ -3784,6 +3796,8 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
3784
3796
|
__publicField(this, "recoveryImmediateRetries", 2);
|
|
3785
3797
|
/** LocalStorage key for persisting logins */
|
|
3786
3798
|
__publicField(this, "localStorageKey", "datalynk-token");
|
|
3799
|
+
/** Persisted owner of an offline queue so another login cannot inherit queued writes. */
|
|
3800
|
+
__publicField(this, "offlineSessionOwnerKey", "datalynk-offline-owner");
|
|
3787
3801
|
__publicField(this, "tokenStorageListener", null);
|
|
3788
3802
|
/** Pending requests cache */
|
|
3789
3803
|
__publicField(this, "pending", {});
|
|
@@ -3841,6 +3855,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
3841
3855
|
offline: [],
|
|
3842
3856
|
origin: typeof location !== "undefined" ? location.host : "Unknown",
|
|
3843
3857
|
saveSession: true,
|
|
3858
|
+
replayOfflineQueue: true,
|
|
3844
3859
|
watchTokenExpiry: true,
|
|
3845
3860
|
serviceWorker: "/service.worker.mjs",
|
|
3846
3861
|
...options,
|
|
@@ -3883,21 +3898,13 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
3883
3898
|
this.webrtc = new WebRtc(this);
|
|
3884
3899
|
if (typeof indexedDB != "undefined") {
|
|
3885
3900
|
this.database = new Database("datalynk", ["pending", ...this.options.offline || []]);
|
|
3886
|
-
this.online$.subscribe(
|
|
3887
|
-
|
|
3888
|
-
if (!online) return;
|
|
3889
|
-
const table = (_a2 = this.database) == null ? void 0 : _a2.table("pending");
|
|
3890
|
-
const keys = await (table == null ? void 0 : table.getAllKeys());
|
|
3891
|
-
await Promise.allSettled(keys.map(async (k) => {
|
|
3892
|
-
const r = await (table == null ? void 0 : table.get(k));
|
|
3893
|
-
await this.request(r).then(() => table == null ? void 0 : table.delete(k));
|
|
3894
|
-
}));
|
|
3901
|
+
if (this.options.replayOfflineQueue !== false) this.online$.subscribe((online) => {
|
|
3902
|
+
if (online) void this.replayPendingQueue();
|
|
3895
3903
|
});
|
|
3896
3904
|
}
|
|
3897
3905
|
if (typeof window !== "undefined") {
|
|
3898
3906
|
window.addEventListener("online", () => this.checkConnection());
|
|
3899
3907
|
window.addEventListener("offline", () => this.setConnectionStatus("offline"));
|
|
3900
|
-
this.online$.subscribe(() => this.offlineBanner());
|
|
3901
3908
|
this.startHeartbeat();
|
|
3902
3909
|
}
|
|
3903
3910
|
if ((_a = this.options.offline) == null ? void 0 : _a.length) {
|
|
@@ -3928,6 +3935,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
3928
3935
|
var _a;
|
|
3929
3936
|
return (((_a = this.jwtPayload) == null ? void 0 : _a.exp) ?? Infinity) * 1e3 <= Date.now();
|
|
3930
3937
|
}
|
|
3938
|
+
/** Whether authentication expired while offline and must be renewed before replaying queued work. */
|
|
3939
|
+
get reauthenticationPending() {
|
|
3940
|
+
return this.offlineAuthenticationExpired;
|
|
3941
|
+
}
|
|
3931
3942
|
/** Get session info from JWT payload */
|
|
3932
3943
|
get jwtPayload() {
|
|
3933
3944
|
if (!this.token) return null;
|
|
@@ -3986,21 +3997,26 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
3986
3997
|
return this.token$.getValue();
|
|
3987
3998
|
}
|
|
3988
3999
|
set token(token) {
|
|
4000
|
+
var _a;
|
|
3989
4001
|
if (this.recovery && token !== this.recovery.token)
|
|
3990
4002
|
this.cancelRecovery(errorFromCode(401, "Session changed during API recovery"));
|
|
3991
4003
|
if (token && this.isTokenExpired(token)) {
|
|
3992
4004
|
if (this.adoptStoredToken(token)) return;
|
|
3993
|
-
this.
|
|
3994
|
-
|
|
3995
|
-
|
|
4005
|
+
if (this.shouldDeferLocalExpiry()) {
|
|
4006
|
+
this.deferOfflineExpiry(token);
|
|
4007
|
+
return;
|
|
4008
|
+
}
|
|
4009
|
+
this.markUnauthorized(void 0, this.hasOwnedOfflineSession(token));
|
|
3996
4010
|
return;
|
|
3997
4011
|
}
|
|
3998
4012
|
this.authenticationInvalid = false;
|
|
4013
|
+
this.offlineAuthenticationExpired = false;
|
|
3999
4014
|
this.token$.next(token);
|
|
4000
4015
|
if (token && !this.online && !this.recovery) {
|
|
4001
4016
|
const browserOnline = typeof navigator == "undefined" || typeof navigator.onLine == "undefined" || navigator.onLine;
|
|
4002
4017
|
this.setConnectionStatus(browserOnline ? "online" : "offline");
|
|
4003
4018
|
}
|
|
4019
|
+
if (token && ((_a = this.options) == null ? void 0 : _a.replayOfflineQueue) !== false) void this.replayPendingQueue();
|
|
4004
4020
|
}
|
|
4005
4021
|
async _request(req, options = {}) {
|
|
4006
4022
|
if (this.recovery) throw errorFromCode(503, "Datalynk is unavailable");
|
|
@@ -4019,7 +4035,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4019
4035
|
async _requestOnce(req, options = {}, recoveryAttempt = false) {
|
|
4020
4036
|
const token = options.token || this.token;
|
|
4021
4037
|
if (token && this.isTokenExpired(token)) {
|
|
4022
|
-
this.
|
|
4038
|
+
if (token === this.token) this.expireToken(token);
|
|
4023
4039
|
throw errorFromCode(401, "Session token expired");
|
|
4024
4040
|
}
|
|
4025
4041
|
let resp;
|
|
@@ -4055,7 +4071,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4055
4071
|
if (!resp.ok || (data == null ? void 0 : data.error)) {
|
|
4056
4072
|
const error = Object.assign(errorFromCode(resp.status, data == null ? void 0 : data.error), data);
|
|
4057
4073
|
Object.defineProperty(error, "response", { value: resp, configurable: true });
|
|
4058
|
-
if (!recoveryAttempt && resp.status
|
|
4074
|
+
if (!recoveryAttempt && resp.status !== 401 && !this.isSqlStateError(error))
|
|
4059
4075
|
this.setConnectionStatus("online");
|
|
4060
4076
|
throw error;
|
|
4061
4077
|
}
|
|
@@ -4063,17 +4079,22 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4063
4079
|
if (!recoveryAttempt) this.setConnectionStatus("online");
|
|
4064
4080
|
return data;
|
|
4065
4081
|
}
|
|
4066
|
-
/**
|
|
4082
|
+
/**
|
|
4083
|
+
* Only failures which indicate Datalynk as a service is unavailable own global
|
|
4084
|
+
* recovery. A request-specific application failure must not take the whole
|
|
4085
|
+
* client offline: another unrelated API request may still be perfectly healthy.
|
|
4086
|
+
*/
|
|
4067
4087
|
isRecoverableResponseError(error) {
|
|
4068
4088
|
var _a;
|
|
4069
|
-
if (error instanceof UnexpectedApiResponseError) return true;
|
|
4070
4089
|
if ((error == null ? void 0 : error.code) === 401) return false;
|
|
4071
4090
|
const status = ((_a = error == null ? void 0 : error.response) == null ? void 0 : _a.status) ?? (error == null ? void 0 : error.code);
|
|
4072
|
-
|
|
4091
|
+
if (this.isGatewayUnavailableStatus(status)) return true;
|
|
4092
|
+
return this.isSqlStateError(error);
|
|
4073
4093
|
}
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4094
|
+
/** Any API error returned in PDO SQLSTATE form uses the offline/recovery path. */
|
|
4095
|
+
isSqlStateError(error) {
|
|
4096
|
+
const message = String((error == null ? void 0 : error.error) ?? (error == null ? void 0 : error.message) ?? "").trim();
|
|
4097
|
+
return /^SQLSTATE\[/i.test(message);
|
|
4077
4098
|
}
|
|
4078
4099
|
/**
|
|
4079
4100
|
* A returned server failure immediately makes the client unavailable. The exact
|
|
@@ -4166,8 +4187,22 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4166
4187
|
this.setConnectionStatus("unauthorized");
|
|
4167
4188
|
return;
|
|
4168
4189
|
}
|
|
4190
|
+
if (this.offlineAuthenticationExpired) {
|
|
4191
|
+
const controller2 = new AbortController();
|
|
4192
|
+
const timeout2 = setTimeout(() => controller2.abort(), this.heartbeat.timeout);
|
|
4193
|
+
try {
|
|
4194
|
+
const response = await fetch(this.url + this.heartbeat.target, { signal: controller2.signal });
|
|
4195
|
+
if (this.isGatewayUnavailableStatus(response.status)) this.setConnectionStatus("unavailable");
|
|
4196
|
+
else this.markUnauthorized(this.token, true);
|
|
4197
|
+
} catch {
|
|
4198
|
+
this.setConnectionStatus("offline");
|
|
4199
|
+
} finally {
|
|
4200
|
+
clearTimeout(timeout2);
|
|
4201
|
+
}
|
|
4202
|
+
return;
|
|
4203
|
+
}
|
|
4169
4204
|
if (this.token && this.isTokenExpired(this.token)) {
|
|
4170
|
-
this.
|
|
4205
|
+
this.expireToken(this.token);
|
|
4171
4206
|
return;
|
|
4172
4207
|
}
|
|
4173
4208
|
const controller = new AbortController();
|
|
@@ -4175,13 +4210,16 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4175
4210
|
try {
|
|
4176
4211
|
const response = await fetch(this.url + this.heartbeat.target, { signal: controller.signal });
|
|
4177
4212
|
if (this.recovery) return;
|
|
4178
|
-
this.setConnectionStatus(response.
|
|
4213
|
+
this.setConnectionStatus(this.isGatewayUnavailableStatus(response.status) ? "unavailable" : "online");
|
|
4179
4214
|
} catch (error) {
|
|
4180
4215
|
if (!this.recovery) this.setConnectionStatus("offline");
|
|
4181
4216
|
} finally {
|
|
4182
4217
|
clearTimeout(timeout);
|
|
4183
4218
|
}
|
|
4184
4219
|
}
|
|
4220
|
+
isGatewayUnavailableStatus(status) {
|
|
4221
|
+
return status === 502 || status === 503 || status === 504;
|
|
4222
|
+
}
|
|
4185
4223
|
isTokenExpired(token) {
|
|
4186
4224
|
var _a;
|
|
4187
4225
|
try {
|
|
@@ -4231,45 +4269,241 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4231
4269
|
try {
|
|
4232
4270
|
expiresAt = (((_a = decodeJwt(token)) == null ? void 0 : _a.exp) ?? 0) * 1e3;
|
|
4233
4271
|
} catch {
|
|
4234
|
-
if (!this.adoptStoredToken(token)) this.
|
|
4272
|
+
if (!this.adoptStoredToken(token)) this.expireToken(token);
|
|
4235
4273
|
return;
|
|
4236
4274
|
}
|
|
4237
4275
|
const delay = expiresAt - Date.now();
|
|
4238
4276
|
if (delay <= 0) {
|
|
4239
|
-
if (!this.adoptStoredToken(token)) this.
|
|
4277
|
+
if (!this.adoptStoredToken(token)) this.expireToken(token);
|
|
4240
4278
|
return;
|
|
4241
4279
|
}
|
|
4242
4280
|
this.tokenExpiryTimeout = setTimeout(() => {
|
|
4243
4281
|
if (delay > 2147e6) this.scheduleTokenExpiry(token);
|
|
4244
|
-
else if (!this.adoptStoredToken(token)) this.
|
|
4282
|
+
else if (!this.adoptStoredToken(token)) this.expireToken(token);
|
|
4245
4283
|
}, Math.min(delay, 2147e6));
|
|
4246
4284
|
(_c = (_b = this.tokenExpiryTimeout) == null ? void 0 : _b.unref) == null ? void 0 : _c.call(_b);
|
|
4247
4285
|
}
|
|
4248
|
-
|
|
4286
|
+
/** Handle local JWT expiry without treating an offline device like a rejected online session. */
|
|
4287
|
+
expireToken(token) {
|
|
4288
|
+
if (token !== this.token) return;
|
|
4289
|
+
if (this.shouldDeferLocalExpiry()) {
|
|
4290
|
+
this.deferOfflineExpiry(token);
|
|
4291
|
+
return;
|
|
4292
|
+
}
|
|
4293
|
+
this.markUnauthorized(token, this.hasOwnedOfflineSession(token));
|
|
4294
|
+
}
|
|
4295
|
+
shouldDeferLocalExpiry() {
|
|
4296
|
+
if (this.authenticationInvalid) return false;
|
|
4297
|
+
if (typeof navigator != "undefined" && typeof navigator.onLine != "undefined" && !navigator.onLine) return true;
|
|
4298
|
+
return this.status === "offline" || this.status === "unavailable";
|
|
4299
|
+
}
|
|
4300
|
+
deferOfflineExpiry(token) {
|
|
4301
|
+
this.authenticationInvalid = false;
|
|
4302
|
+
this.offlineAuthenticationExpired = true;
|
|
4303
|
+
this.cancelRecovery(errorFromCode(401, "Session expired while offline"));
|
|
4304
|
+
this.rememberOfflineSessionOwner(token);
|
|
4305
|
+
if (this.token !== token) this.token$.next(token);
|
|
4306
|
+
if (this.status === "online") this.setConnectionStatus("offline");
|
|
4307
|
+
}
|
|
4308
|
+
markUnauthorized(token, preservePending = this.hasOwnedOfflineSession(token || this.token)) {
|
|
4249
4309
|
if (token && token !== this.token) return;
|
|
4250
4310
|
if (this.adoptStoredToken(token)) return;
|
|
4311
|
+
if (this.authenticationInvalid && this.unauthorizedLogout) return;
|
|
4251
4312
|
this.authenticationInvalid = true;
|
|
4313
|
+
this.offlineAuthenticationExpired = false;
|
|
4252
4314
|
this.cancelRecovery(errorFromCode(401, "Session expired"));
|
|
4253
|
-
if (this.token
|
|
4315
|
+
if (this.token !== null) this.token$.next(null);
|
|
4254
4316
|
this.setConnectionStatus("unauthorized");
|
|
4317
|
+
this.unauthorizedLogout ?? (this.unauthorizedLogout = Promise.resolve().then(() => this.finishUnauthorizedLogout(preservePending)).finally(() => {
|
|
4318
|
+
this.unauthorizedLogout = null;
|
|
4319
|
+
}));
|
|
4320
|
+
}
|
|
4321
|
+
async finishUnauthorizedLogout(preservePending = false) {
|
|
4322
|
+
var _a;
|
|
4323
|
+
if (typeof localStorage != "undefined") {
|
|
4324
|
+
localStorage.removeItem(this.localStorageKey);
|
|
4325
|
+
localStorage.removeItem("datalynk-user");
|
|
4326
|
+
if (!preservePending) localStorage.removeItem(this.offlineSessionOwnerKey);
|
|
4327
|
+
}
|
|
4328
|
+
if (!preservePending) this.offlineSessionOwner = null;
|
|
4329
|
+
for (const slice of this.sliceCache.values()) {
|
|
4330
|
+
try {
|
|
4331
|
+
slice.sync(false);
|
|
4332
|
+
} catch {
|
|
4333
|
+
}
|
|
4334
|
+
}
|
|
4335
|
+
await this.clearOfflineSessionCache(preservePending);
|
|
4336
|
+
if (typeof caches != "undefined") {
|
|
4337
|
+
try {
|
|
4338
|
+
await Promise.all([caches.delete("datalynk"), caches.delete("api-settings")]);
|
|
4339
|
+
} catch (error) {
|
|
4340
|
+
console.error("Unable to clear Datalynk service worker cache", error);
|
|
4341
|
+
}
|
|
4342
|
+
}
|
|
4343
|
+
if (typeof navigator != "undefined" && ((_a = navigator.serviceWorker) == null ? void 0 : _a.controller)) {
|
|
4344
|
+
navigator.serviceWorker.controller.postMessage({ token: null, clearCache: true });
|
|
4345
|
+
}
|
|
4346
|
+
if (typeof location != "undefined" && typeof location.reload == "function")
|
|
4347
|
+
location.reload();
|
|
4348
|
+
}
|
|
4349
|
+
async clearOfflineSessionCache(preservePending = false) {
|
|
4350
|
+
if (typeof indexedDB == "undefined") return;
|
|
4351
|
+
try {
|
|
4352
|
+
if (this.database) {
|
|
4353
|
+
const db = await this.database.connection;
|
|
4354
|
+
const stores = Array.from(db.objectStoreNames).filter((name) => !(preservePending && name === "pending"));
|
|
4355
|
+
if (!stores.length) return;
|
|
4356
|
+
await new Promise((resolve, reject) => {
|
|
4357
|
+
const tx = db.transaction(stores, "readwrite");
|
|
4358
|
+
stores.forEach((name) => tx.objectStore(name).clear());
|
|
4359
|
+
tx.oncomplete = () => resolve();
|
|
4360
|
+
tx.onerror = () => reject(tx.error);
|
|
4361
|
+
tx.onabort = () => reject(tx.error);
|
|
4362
|
+
});
|
|
4363
|
+
return;
|
|
4364
|
+
}
|
|
4365
|
+
await new Promise((resolve, reject) => {
|
|
4366
|
+
const request = indexedDB.open("datalynk");
|
|
4367
|
+
request.onerror = () => reject(request.error);
|
|
4368
|
+
request.onsuccess = () => {
|
|
4369
|
+
const db = request.result;
|
|
4370
|
+
const stores = Array.from(db.objectStoreNames).filter((name) => !(preservePending && name === "pending"));
|
|
4371
|
+
if (!stores.length) {
|
|
4372
|
+
db.close();
|
|
4373
|
+
resolve();
|
|
4374
|
+
return;
|
|
4375
|
+
}
|
|
4376
|
+
const tx = db.transaction(stores, "readwrite");
|
|
4377
|
+
stores.forEach((name) => tx.objectStore(name).clear());
|
|
4378
|
+
tx.oncomplete = () => {
|
|
4379
|
+
db.close();
|
|
4380
|
+
resolve();
|
|
4381
|
+
};
|
|
4382
|
+
tx.onerror = () => {
|
|
4383
|
+
const error = tx.error;
|
|
4384
|
+
db.close();
|
|
4385
|
+
reject(error);
|
|
4386
|
+
};
|
|
4387
|
+
tx.onabort = () => {
|
|
4388
|
+
const error = tx.error;
|
|
4389
|
+
db.close();
|
|
4390
|
+
reject(error);
|
|
4391
|
+
};
|
|
4392
|
+
};
|
|
4393
|
+
});
|
|
4394
|
+
} catch (error) {
|
|
4395
|
+
console.error("Unable to clear Datalynk offline session cache", error);
|
|
4396
|
+
}
|
|
4397
|
+
}
|
|
4398
|
+
tokenIdentity(token) {
|
|
4399
|
+
if (!token) return null;
|
|
4400
|
+
try {
|
|
4401
|
+
const payload = decodeJwt(token);
|
|
4402
|
+
const user = (payload == null ? void 0 : payload.uid) ?? (payload == null ? void 0 : payload.sub);
|
|
4403
|
+
return {
|
|
4404
|
+
realm: (payload == null ? void 0 : payload.realm) == null ? void 0 : String(payload.realm),
|
|
4405
|
+
user: user == null ? void 0 : String(user)
|
|
4406
|
+
};
|
|
4407
|
+
} catch {
|
|
4408
|
+
return null;
|
|
4409
|
+
}
|
|
4410
|
+
}
|
|
4411
|
+
readOfflineSessionOwner() {
|
|
4412
|
+
if (this.offlineSessionOwner) return this.offlineSessionOwner;
|
|
4413
|
+
if (typeof localStorage == "undefined") return null;
|
|
4414
|
+
try {
|
|
4415
|
+
const value = localStorage.getItem(this.offlineSessionOwnerKey);
|
|
4416
|
+
if (!value) return null;
|
|
4417
|
+
const owner = JSON.parse(value);
|
|
4418
|
+
if (!owner || typeof owner !== "object") return null;
|
|
4419
|
+
return this.offlineSessionOwner = owner;
|
|
4420
|
+
} catch {
|
|
4421
|
+
return null;
|
|
4422
|
+
}
|
|
4423
|
+
}
|
|
4424
|
+
ownersMatch(owner, identity2) {
|
|
4425
|
+
if (!identity2) return false;
|
|
4426
|
+
if (owner.realm != null && owner.realm !== identity2.realm) return false;
|
|
4427
|
+
if (owner.user != null && owner.user !== identity2.user) return false;
|
|
4428
|
+
return true;
|
|
4429
|
+
}
|
|
4430
|
+
rememberOfflineSessionOwner(token) {
|
|
4431
|
+
const identity2 = this.tokenIdentity(token);
|
|
4432
|
+
if (!identity2) return;
|
|
4433
|
+
const existing = this.readOfflineSessionOwner();
|
|
4434
|
+
if (existing && !this.ownersMatch(existing, identity2)) return;
|
|
4435
|
+
this.offlineSessionOwner = identity2;
|
|
4436
|
+
if (typeof localStorage != "undefined") {
|
|
4437
|
+
try {
|
|
4438
|
+
localStorage.setItem(this.offlineSessionOwnerKey, JSON.stringify(identity2));
|
|
4439
|
+
} catch {
|
|
4440
|
+
}
|
|
4441
|
+
}
|
|
4442
|
+
}
|
|
4443
|
+
ensureOfflineSessionOwner(token) {
|
|
4444
|
+
const existing = this.readOfflineSessionOwner();
|
|
4445
|
+
const identity2 = this.tokenIdentity(token);
|
|
4446
|
+
if (existing) return this.ownersMatch(existing, identity2);
|
|
4447
|
+
if (!identity2) return true;
|
|
4448
|
+
this.rememberOfflineSessionOwner(token);
|
|
4449
|
+
return true;
|
|
4450
|
+
}
|
|
4451
|
+
hasOwnedOfflineSession(token) {
|
|
4452
|
+
const owner = this.readOfflineSessionOwner();
|
|
4453
|
+
return !!owner && this.ownersMatch(owner, this.tokenIdentity(token));
|
|
4454
|
+
}
|
|
4455
|
+
clearOfflineSessionOwner() {
|
|
4456
|
+
this.offlineSessionOwner = null;
|
|
4457
|
+
if (typeof localStorage != "undefined") {
|
|
4458
|
+
try {
|
|
4459
|
+
localStorage.removeItem(this.offlineSessionOwnerKey);
|
|
4460
|
+
} catch {
|
|
4461
|
+
}
|
|
4462
|
+
}
|
|
4463
|
+
}
|
|
4464
|
+
/** Replay queued writes only after a matching authenticated session is online. */
|
|
4465
|
+
async replayPendingQueue() {
|
|
4466
|
+
if (!this.database || !this.online || this.authenticationInvalid || this.offlineAuthenticationExpired) return;
|
|
4467
|
+
if (this.replayingPending) {
|
|
4468
|
+
this.pendingReplayRequested = true;
|
|
4469
|
+
return;
|
|
4470
|
+
}
|
|
4471
|
+
const table = this.database.table("pending");
|
|
4472
|
+
const owner = this.readOfflineSessionOwner();
|
|
4473
|
+
if (owner && !this.ownersMatch(owner, this.tokenIdentity(this.token))) {
|
|
4474
|
+
if (this.token) console.warn("Datalynk offline queue is quarantined because it belongs to another authenticated user");
|
|
4475
|
+
return;
|
|
4476
|
+
}
|
|
4477
|
+
this.replayingPending = true;
|
|
4478
|
+
try {
|
|
4479
|
+
const keys = await table.getAllKeys();
|
|
4480
|
+
for (const key of keys) {
|
|
4481
|
+
if (!this.online || this.authenticationInvalid || this.offlineAuthenticationExpired) break;
|
|
4482
|
+
if (owner && !this.ownersMatch(owner, this.tokenIdentity(this.token))) break;
|
|
4483
|
+
const request = await table.get(key);
|
|
4484
|
+
if (request == null) continue;
|
|
4485
|
+
try {
|
|
4486
|
+
await this.request(request);
|
|
4487
|
+
await table.delete(key);
|
|
4488
|
+
} catch (error) {
|
|
4489
|
+
break;
|
|
4490
|
+
}
|
|
4491
|
+
}
|
|
4492
|
+
if (await table.count() === 0) this.clearOfflineSessionOwner();
|
|
4493
|
+
} finally {
|
|
4494
|
+
this.replayingPending = false;
|
|
4495
|
+
if (this.pendingReplayRequested) {
|
|
4496
|
+
this.pendingReplayRequested = false;
|
|
4497
|
+
if (this.online && !this.authenticationInvalid && !this.offlineAuthenticationExpired)
|
|
4498
|
+
void this.replayPendingQueue();
|
|
4499
|
+
}
|
|
4500
|
+
}
|
|
4255
4501
|
}
|
|
4256
4502
|
setConnectionStatus(status) {
|
|
4257
4503
|
if (this.status !== status) this.status$.next(status);
|
|
4258
4504
|
const online = status === "online";
|
|
4259
4505
|
if (this.online !== online) this.online$.next(online);
|
|
4260
4506
|
}
|
|
4261
|
-
offlineBanner() {
|
|
4262
|
-
if (this.options.offlineBanner === false || typeof document == "undefined") return;
|
|
4263
|
-
if (this.status === "online") {
|
|
4264
|
-
removeBanner("datalynk-offline-banner");
|
|
4265
|
-
} else {
|
|
4266
|
-
const message = this.status === "unauthorized" ? "⚠️ Your session expired, please sign in again" : this.status === "unavailable" ? "⚠️ Datalynk is currently unavailable" : "⚠️ You are offline, please reconnect to sync changes";
|
|
4267
|
-
createBanner(message, {
|
|
4268
|
-
id: "datalynk-offline-banner",
|
|
4269
|
-
position: this.options.offlineBanner === "top" ? "top" : "bottom"
|
|
4270
|
-
});
|
|
4271
|
-
}
|
|
4272
|
-
}
|
|
4273
4507
|
startHeartbeat() {
|
|
4274
4508
|
this.stopHeartbeat();
|
|
4275
4509
|
this.heartbeat.interval = setInterval(() => this.checkConnection(), this.heartbeat.timeout / 2);
|
|
@@ -4379,6 +4613,8 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4379
4613
|
if (this.offline && typeof navigator != "undefined") {
|
|
4380
4614
|
if (this.status === "unauthorized") return Promise.reject(errorFromCode(401, "Session expired"));
|
|
4381
4615
|
if (options.offline) {
|
|
4616
|
+
if (!this.ensureOfflineSessionOwner(this.token))
|
|
4617
|
+
return Promise.reject(errorFromCode(409, "Offline queue belongs to another authenticated user"));
|
|
4382
4618
|
(_b = (_a = this.database) == null ? void 0 : _a.table("pending")) == null ? void 0 : _b.add(data, key);
|
|
4383
4619
|
return Promise.resolve();
|
|
4384
4620
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -1549,7 +1549,10 @@ class Auth {
|
|
|
1549
1549
|
}
|
|
1550
1550
|
});
|
|
1551
1551
|
if ((_a = this.api.options.offline) == null ? void 0 : _a.length)
|
|
1552
|
-
this.user$.pipe(filter((u) => u !== void 0)).subscribe((u) =>
|
|
1552
|
+
this.user$.pipe(filter((u) => u !== void 0)).subscribe((u) => {
|
|
1553
|
+
if (u == null) localStorage.removeItem("datalynk-user");
|
|
1554
|
+
else localStorage.setItem("datalynk-user", JSON.stringify(u));
|
|
1555
|
+
});
|
|
1553
1556
|
}
|
|
1554
1557
|
/** Current user */
|
|
1555
1558
|
get user() {
|
|
@@ -1585,7 +1588,7 @@ class Auth {
|
|
|
1585
1588
|
* @return {Promise<void>} Login complete
|
|
1586
1589
|
*/
|
|
1587
1590
|
async handleLogin(spoke, options) {
|
|
1588
|
-
var _a, _b;
|
|
1591
|
+
var _a, _b, _c;
|
|
1589
1592
|
if (this.onlinePrompt) window.removeEventListener("online", this.onlinePrompt);
|
|
1590
1593
|
if ((_a = this.api.options.offline) == null ? void 0 : _a.length) window.removeEventListener("online", this.onlinePrompt = () => this.handleLogin(spoke, options));
|
|
1591
1594
|
const urlParams = new URLSearchParams(location.search);
|
|
@@ -1594,8 +1597,10 @@ class Auth {
|
|
|
1594
1597
|
this.api.token = urlToken;
|
|
1595
1598
|
urlParams.delete("datalynkToken");
|
|
1596
1599
|
location.href = location.pathname + (urlParams.toString() ? "?" + urlParams.toString() : "") + location.hash;
|
|
1600
|
+
} else if (this.api.reauthenticationPending && !this.api.online && ((_b = this.api.jwtPayload) == null ? void 0 : _b.realm) == spoke) {
|
|
1601
|
+
return;
|
|
1597
1602
|
} else if (this.api.token && !this.api.expired) {
|
|
1598
|
-
if (((
|
|
1603
|
+
if (((_c = this.api.jwtPayload) == null ? void 0 : _c.realm) != spoke) {
|
|
1599
1604
|
this.api.token = null;
|
|
1600
1605
|
location.reload();
|
|
1601
1606
|
}
|
|
@@ -3225,7 +3230,7 @@ class Superuser {
|
|
|
3225
3230
|
} });
|
|
3226
3231
|
}
|
|
3227
3232
|
}
|
|
3228
|
-
const version = "1.5.
|
|
3233
|
+
const version = "1.5.2";
|
|
3229
3234
|
class WebRtc {
|
|
3230
3235
|
constructor(api) {
|
|
3231
3236
|
__publicField(this, "ice");
|
|
@@ -3772,7 +3777,14 @@ const _Api = class _Api {
|
|
|
3772
3777
|
timeout: 6e4
|
|
3773
3778
|
});
|
|
3774
3779
|
__publicField(this, "authenticationInvalid", false);
|
|
3780
|
+
/** Token reached exp while the client could not reach Datalynk; keep the offline session alive until reconnect. */
|
|
3781
|
+
__publicField(this, "offlineAuthenticationExpired", false);
|
|
3775
3782
|
__publicField(this, "tokenExpiryTimeout", null);
|
|
3783
|
+
__publicField(this, "unauthorizedLogout", null);
|
|
3784
|
+
__publicField(this, "replayingPending", false);
|
|
3785
|
+
/** If replay is requested while a replay pass is already active, run another pass when it finishes. */
|
|
3786
|
+
__publicField(this, "pendingReplayRequested", false);
|
|
3787
|
+
__publicField(this, "offlineSessionOwner", null);
|
|
3776
3788
|
/** Request-specific recovery after a server response proves an API call is broken. */
|
|
3777
3789
|
__publicField(this, "recovery", null);
|
|
3778
3790
|
/** Retry the failed request twice immediately, then this long after each failed recovery response. */
|
|
@@ -3780,6 +3792,8 @@ const _Api = class _Api {
|
|
|
3780
3792
|
__publicField(this, "recoveryImmediateRetries", 2);
|
|
3781
3793
|
/** LocalStorage key for persisting logins */
|
|
3782
3794
|
__publicField(this, "localStorageKey", "datalynk-token");
|
|
3795
|
+
/** Persisted owner of an offline queue so another login cannot inherit queued writes. */
|
|
3796
|
+
__publicField(this, "offlineSessionOwnerKey", "datalynk-offline-owner");
|
|
3783
3797
|
__publicField(this, "tokenStorageListener", null);
|
|
3784
3798
|
/** Pending requests cache */
|
|
3785
3799
|
__publicField(this, "pending", {});
|
|
@@ -3837,6 +3851,7 @@ const _Api = class _Api {
|
|
|
3837
3851
|
offline: [],
|
|
3838
3852
|
origin: typeof location !== "undefined" ? location.host : "Unknown",
|
|
3839
3853
|
saveSession: true,
|
|
3854
|
+
replayOfflineQueue: true,
|
|
3840
3855
|
watchTokenExpiry: true,
|
|
3841
3856
|
serviceWorker: "/service.worker.mjs",
|
|
3842
3857
|
...options,
|
|
@@ -3879,21 +3894,13 @@ const _Api = class _Api {
|
|
|
3879
3894
|
this.webrtc = new WebRtc(this);
|
|
3880
3895
|
if (typeof indexedDB != "undefined") {
|
|
3881
3896
|
this.database = new Database("datalynk", ["pending", ...this.options.offline || []]);
|
|
3882
|
-
this.online$.subscribe(
|
|
3883
|
-
|
|
3884
|
-
if (!online) return;
|
|
3885
|
-
const table = (_a2 = this.database) == null ? void 0 : _a2.table("pending");
|
|
3886
|
-
const keys = await (table == null ? void 0 : table.getAllKeys());
|
|
3887
|
-
await Promise.allSettled(keys.map(async (k) => {
|
|
3888
|
-
const r = await (table == null ? void 0 : table.get(k));
|
|
3889
|
-
await this.request(r).then(() => table == null ? void 0 : table.delete(k));
|
|
3890
|
-
}));
|
|
3897
|
+
if (this.options.replayOfflineQueue !== false) this.online$.subscribe((online) => {
|
|
3898
|
+
if (online) void this.replayPendingQueue();
|
|
3891
3899
|
});
|
|
3892
3900
|
}
|
|
3893
3901
|
if (typeof window !== "undefined") {
|
|
3894
3902
|
window.addEventListener("online", () => this.checkConnection());
|
|
3895
3903
|
window.addEventListener("offline", () => this.setConnectionStatus("offline"));
|
|
3896
|
-
this.online$.subscribe(() => this.offlineBanner());
|
|
3897
3904
|
this.startHeartbeat();
|
|
3898
3905
|
}
|
|
3899
3906
|
if ((_a = this.options.offline) == null ? void 0 : _a.length) {
|
|
@@ -3924,6 +3931,10 @@ const _Api = class _Api {
|
|
|
3924
3931
|
var _a;
|
|
3925
3932
|
return (((_a = this.jwtPayload) == null ? void 0 : _a.exp) ?? Infinity) * 1e3 <= Date.now();
|
|
3926
3933
|
}
|
|
3934
|
+
/** Whether authentication expired while offline and must be renewed before replaying queued work. */
|
|
3935
|
+
get reauthenticationPending() {
|
|
3936
|
+
return this.offlineAuthenticationExpired;
|
|
3937
|
+
}
|
|
3927
3938
|
/** Get session info from JWT payload */
|
|
3928
3939
|
get jwtPayload() {
|
|
3929
3940
|
if (!this.token) return null;
|
|
@@ -3982,21 +3993,26 @@ const _Api = class _Api {
|
|
|
3982
3993
|
return this.token$.getValue();
|
|
3983
3994
|
}
|
|
3984
3995
|
set token(token) {
|
|
3996
|
+
var _a;
|
|
3985
3997
|
if (this.recovery && token !== this.recovery.token)
|
|
3986
3998
|
this.cancelRecovery(errorFromCode(401, "Session changed during API recovery"));
|
|
3987
3999
|
if (token && this.isTokenExpired(token)) {
|
|
3988
4000
|
if (this.adoptStoredToken(token)) return;
|
|
3989
|
-
this.
|
|
3990
|
-
|
|
3991
|
-
|
|
4001
|
+
if (this.shouldDeferLocalExpiry()) {
|
|
4002
|
+
this.deferOfflineExpiry(token);
|
|
4003
|
+
return;
|
|
4004
|
+
}
|
|
4005
|
+
this.markUnauthorized(void 0, this.hasOwnedOfflineSession(token));
|
|
3992
4006
|
return;
|
|
3993
4007
|
}
|
|
3994
4008
|
this.authenticationInvalid = false;
|
|
4009
|
+
this.offlineAuthenticationExpired = false;
|
|
3995
4010
|
this.token$.next(token);
|
|
3996
4011
|
if (token && !this.online && !this.recovery) {
|
|
3997
4012
|
const browserOnline = typeof navigator == "undefined" || typeof navigator.onLine == "undefined" || navigator.onLine;
|
|
3998
4013
|
this.setConnectionStatus(browserOnline ? "online" : "offline");
|
|
3999
4014
|
}
|
|
4015
|
+
if (token && ((_a = this.options) == null ? void 0 : _a.replayOfflineQueue) !== false) void this.replayPendingQueue();
|
|
4000
4016
|
}
|
|
4001
4017
|
async _request(req, options = {}) {
|
|
4002
4018
|
if (this.recovery) throw errorFromCode(503, "Datalynk is unavailable");
|
|
@@ -4015,7 +4031,7 @@ const _Api = class _Api {
|
|
|
4015
4031
|
async _requestOnce(req, options = {}, recoveryAttempt = false) {
|
|
4016
4032
|
const token = options.token || this.token;
|
|
4017
4033
|
if (token && this.isTokenExpired(token)) {
|
|
4018
|
-
this.
|
|
4034
|
+
if (token === this.token) this.expireToken(token);
|
|
4019
4035
|
throw errorFromCode(401, "Session token expired");
|
|
4020
4036
|
}
|
|
4021
4037
|
let resp;
|
|
@@ -4051,7 +4067,7 @@ const _Api = class _Api {
|
|
|
4051
4067
|
if (!resp.ok || (data == null ? void 0 : data.error)) {
|
|
4052
4068
|
const error = Object.assign(errorFromCode(resp.status, data == null ? void 0 : data.error), data);
|
|
4053
4069
|
Object.defineProperty(error, "response", { value: resp, configurable: true });
|
|
4054
|
-
if (!recoveryAttempt && resp.status
|
|
4070
|
+
if (!recoveryAttempt && resp.status !== 401 && !this.isSqlStateError(error))
|
|
4055
4071
|
this.setConnectionStatus("online");
|
|
4056
4072
|
throw error;
|
|
4057
4073
|
}
|
|
@@ -4059,17 +4075,22 @@ const _Api = class _Api {
|
|
|
4059
4075
|
if (!recoveryAttempt) this.setConnectionStatus("online");
|
|
4060
4076
|
return data;
|
|
4061
4077
|
}
|
|
4062
|
-
/**
|
|
4078
|
+
/**
|
|
4079
|
+
* Only failures which indicate Datalynk as a service is unavailable own global
|
|
4080
|
+
* recovery. A request-specific application failure must not take the whole
|
|
4081
|
+
* client offline: another unrelated API request may still be perfectly healthy.
|
|
4082
|
+
*/
|
|
4063
4083
|
isRecoverableResponseError(error) {
|
|
4064
4084
|
var _a;
|
|
4065
|
-
if (error instanceof UnexpectedApiResponseError) return true;
|
|
4066
4085
|
if ((error == null ? void 0 : error.code) === 401) return false;
|
|
4067
4086
|
const status = ((_a = error == null ? void 0 : error.response) == null ? void 0 : _a.status) ?? (error == null ? void 0 : error.code);
|
|
4068
|
-
|
|
4087
|
+
if (this.isGatewayUnavailableStatus(status)) return true;
|
|
4088
|
+
return this.isSqlStateError(error);
|
|
4069
4089
|
}
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4090
|
+
/** Any API error returned in PDO SQLSTATE form uses the offline/recovery path. */
|
|
4091
|
+
isSqlStateError(error) {
|
|
4092
|
+
const message = String((error == null ? void 0 : error.error) ?? (error == null ? void 0 : error.message) ?? "").trim();
|
|
4093
|
+
return /^SQLSTATE\[/i.test(message);
|
|
4073
4094
|
}
|
|
4074
4095
|
/**
|
|
4075
4096
|
* A returned server failure immediately makes the client unavailable. The exact
|
|
@@ -4162,8 +4183,22 @@ const _Api = class _Api {
|
|
|
4162
4183
|
this.setConnectionStatus("unauthorized");
|
|
4163
4184
|
return;
|
|
4164
4185
|
}
|
|
4186
|
+
if (this.offlineAuthenticationExpired) {
|
|
4187
|
+
const controller2 = new AbortController();
|
|
4188
|
+
const timeout2 = setTimeout(() => controller2.abort(), this.heartbeat.timeout);
|
|
4189
|
+
try {
|
|
4190
|
+
const response = await fetch(this.url + this.heartbeat.target, { signal: controller2.signal });
|
|
4191
|
+
if (this.isGatewayUnavailableStatus(response.status)) this.setConnectionStatus("unavailable");
|
|
4192
|
+
else this.markUnauthorized(this.token, true);
|
|
4193
|
+
} catch {
|
|
4194
|
+
this.setConnectionStatus("offline");
|
|
4195
|
+
} finally {
|
|
4196
|
+
clearTimeout(timeout2);
|
|
4197
|
+
}
|
|
4198
|
+
return;
|
|
4199
|
+
}
|
|
4165
4200
|
if (this.token && this.isTokenExpired(this.token)) {
|
|
4166
|
-
this.
|
|
4201
|
+
this.expireToken(this.token);
|
|
4167
4202
|
return;
|
|
4168
4203
|
}
|
|
4169
4204
|
const controller = new AbortController();
|
|
@@ -4171,13 +4206,16 @@ const _Api = class _Api {
|
|
|
4171
4206
|
try {
|
|
4172
4207
|
const response = await fetch(this.url + this.heartbeat.target, { signal: controller.signal });
|
|
4173
4208
|
if (this.recovery) return;
|
|
4174
|
-
this.setConnectionStatus(response.
|
|
4209
|
+
this.setConnectionStatus(this.isGatewayUnavailableStatus(response.status) ? "unavailable" : "online");
|
|
4175
4210
|
} catch (error) {
|
|
4176
4211
|
if (!this.recovery) this.setConnectionStatus("offline");
|
|
4177
4212
|
} finally {
|
|
4178
4213
|
clearTimeout(timeout);
|
|
4179
4214
|
}
|
|
4180
4215
|
}
|
|
4216
|
+
isGatewayUnavailableStatus(status) {
|
|
4217
|
+
return status === 502 || status === 503 || status === 504;
|
|
4218
|
+
}
|
|
4181
4219
|
isTokenExpired(token) {
|
|
4182
4220
|
var _a;
|
|
4183
4221
|
try {
|
|
@@ -4227,45 +4265,241 @@ const _Api = class _Api {
|
|
|
4227
4265
|
try {
|
|
4228
4266
|
expiresAt = (((_a = decodeJwt(token)) == null ? void 0 : _a.exp) ?? 0) * 1e3;
|
|
4229
4267
|
} catch {
|
|
4230
|
-
if (!this.adoptStoredToken(token)) this.
|
|
4268
|
+
if (!this.adoptStoredToken(token)) this.expireToken(token);
|
|
4231
4269
|
return;
|
|
4232
4270
|
}
|
|
4233
4271
|
const delay = expiresAt - Date.now();
|
|
4234
4272
|
if (delay <= 0) {
|
|
4235
|
-
if (!this.adoptStoredToken(token)) this.
|
|
4273
|
+
if (!this.adoptStoredToken(token)) this.expireToken(token);
|
|
4236
4274
|
return;
|
|
4237
4275
|
}
|
|
4238
4276
|
this.tokenExpiryTimeout = setTimeout(() => {
|
|
4239
4277
|
if (delay > 2147e6) this.scheduleTokenExpiry(token);
|
|
4240
|
-
else if (!this.adoptStoredToken(token)) this.
|
|
4278
|
+
else if (!this.adoptStoredToken(token)) this.expireToken(token);
|
|
4241
4279
|
}, Math.min(delay, 2147e6));
|
|
4242
4280
|
(_c = (_b = this.tokenExpiryTimeout) == null ? void 0 : _b.unref) == null ? void 0 : _c.call(_b);
|
|
4243
4281
|
}
|
|
4244
|
-
|
|
4282
|
+
/** Handle local JWT expiry without treating an offline device like a rejected online session. */
|
|
4283
|
+
expireToken(token) {
|
|
4284
|
+
if (token !== this.token) return;
|
|
4285
|
+
if (this.shouldDeferLocalExpiry()) {
|
|
4286
|
+
this.deferOfflineExpiry(token);
|
|
4287
|
+
return;
|
|
4288
|
+
}
|
|
4289
|
+
this.markUnauthorized(token, this.hasOwnedOfflineSession(token));
|
|
4290
|
+
}
|
|
4291
|
+
shouldDeferLocalExpiry() {
|
|
4292
|
+
if (this.authenticationInvalid) return false;
|
|
4293
|
+
if (typeof navigator != "undefined" && typeof navigator.onLine != "undefined" && !navigator.onLine) return true;
|
|
4294
|
+
return this.status === "offline" || this.status === "unavailable";
|
|
4295
|
+
}
|
|
4296
|
+
deferOfflineExpiry(token) {
|
|
4297
|
+
this.authenticationInvalid = false;
|
|
4298
|
+
this.offlineAuthenticationExpired = true;
|
|
4299
|
+
this.cancelRecovery(errorFromCode(401, "Session expired while offline"));
|
|
4300
|
+
this.rememberOfflineSessionOwner(token);
|
|
4301
|
+
if (this.token !== token) this.token$.next(token);
|
|
4302
|
+
if (this.status === "online") this.setConnectionStatus("offline");
|
|
4303
|
+
}
|
|
4304
|
+
markUnauthorized(token, preservePending = this.hasOwnedOfflineSession(token || this.token)) {
|
|
4245
4305
|
if (token && token !== this.token) return;
|
|
4246
4306
|
if (this.adoptStoredToken(token)) return;
|
|
4307
|
+
if (this.authenticationInvalid && this.unauthorizedLogout) return;
|
|
4247
4308
|
this.authenticationInvalid = true;
|
|
4309
|
+
this.offlineAuthenticationExpired = false;
|
|
4248
4310
|
this.cancelRecovery(errorFromCode(401, "Session expired"));
|
|
4249
|
-
if (this.token
|
|
4311
|
+
if (this.token !== null) this.token$.next(null);
|
|
4250
4312
|
this.setConnectionStatus("unauthorized");
|
|
4313
|
+
this.unauthorizedLogout ?? (this.unauthorizedLogout = Promise.resolve().then(() => this.finishUnauthorizedLogout(preservePending)).finally(() => {
|
|
4314
|
+
this.unauthorizedLogout = null;
|
|
4315
|
+
}));
|
|
4316
|
+
}
|
|
4317
|
+
async finishUnauthorizedLogout(preservePending = false) {
|
|
4318
|
+
var _a;
|
|
4319
|
+
if (typeof localStorage != "undefined") {
|
|
4320
|
+
localStorage.removeItem(this.localStorageKey);
|
|
4321
|
+
localStorage.removeItem("datalynk-user");
|
|
4322
|
+
if (!preservePending) localStorage.removeItem(this.offlineSessionOwnerKey);
|
|
4323
|
+
}
|
|
4324
|
+
if (!preservePending) this.offlineSessionOwner = null;
|
|
4325
|
+
for (const slice of this.sliceCache.values()) {
|
|
4326
|
+
try {
|
|
4327
|
+
slice.sync(false);
|
|
4328
|
+
} catch {
|
|
4329
|
+
}
|
|
4330
|
+
}
|
|
4331
|
+
await this.clearOfflineSessionCache(preservePending);
|
|
4332
|
+
if (typeof caches != "undefined") {
|
|
4333
|
+
try {
|
|
4334
|
+
await Promise.all([caches.delete("datalynk"), caches.delete("api-settings")]);
|
|
4335
|
+
} catch (error) {
|
|
4336
|
+
console.error("Unable to clear Datalynk service worker cache", error);
|
|
4337
|
+
}
|
|
4338
|
+
}
|
|
4339
|
+
if (typeof navigator != "undefined" && ((_a = navigator.serviceWorker) == null ? void 0 : _a.controller)) {
|
|
4340
|
+
navigator.serviceWorker.controller.postMessage({ token: null, clearCache: true });
|
|
4341
|
+
}
|
|
4342
|
+
if (typeof location != "undefined" && typeof location.reload == "function")
|
|
4343
|
+
location.reload();
|
|
4344
|
+
}
|
|
4345
|
+
async clearOfflineSessionCache(preservePending = false) {
|
|
4346
|
+
if (typeof indexedDB == "undefined") return;
|
|
4347
|
+
try {
|
|
4348
|
+
if (this.database) {
|
|
4349
|
+
const db = await this.database.connection;
|
|
4350
|
+
const stores = Array.from(db.objectStoreNames).filter((name) => !(preservePending && name === "pending"));
|
|
4351
|
+
if (!stores.length) return;
|
|
4352
|
+
await new Promise((resolve, reject) => {
|
|
4353
|
+
const tx = db.transaction(stores, "readwrite");
|
|
4354
|
+
stores.forEach((name) => tx.objectStore(name).clear());
|
|
4355
|
+
tx.oncomplete = () => resolve();
|
|
4356
|
+
tx.onerror = () => reject(tx.error);
|
|
4357
|
+
tx.onabort = () => reject(tx.error);
|
|
4358
|
+
});
|
|
4359
|
+
return;
|
|
4360
|
+
}
|
|
4361
|
+
await new Promise((resolve, reject) => {
|
|
4362
|
+
const request = indexedDB.open("datalynk");
|
|
4363
|
+
request.onerror = () => reject(request.error);
|
|
4364
|
+
request.onsuccess = () => {
|
|
4365
|
+
const db = request.result;
|
|
4366
|
+
const stores = Array.from(db.objectStoreNames).filter((name) => !(preservePending && name === "pending"));
|
|
4367
|
+
if (!stores.length) {
|
|
4368
|
+
db.close();
|
|
4369
|
+
resolve();
|
|
4370
|
+
return;
|
|
4371
|
+
}
|
|
4372
|
+
const tx = db.transaction(stores, "readwrite");
|
|
4373
|
+
stores.forEach((name) => tx.objectStore(name).clear());
|
|
4374
|
+
tx.oncomplete = () => {
|
|
4375
|
+
db.close();
|
|
4376
|
+
resolve();
|
|
4377
|
+
};
|
|
4378
|
+
tx.onerror = () => {
|
|
4379
|
+
const error = tx.error;
|
|
4380
|
+
db.close();
|
|
4381
|
+
reject(error);
|
|
4382
|
+
};
|
|
4383
|
+
tx.onabort = () => {
|
|
4384
|
+
const error = tx.error;
|
|
4385
|
+
db.close();
|
|
4386
|
+
reject(error);
|
|
4387
|
+
};
|
|
4388
|
+
};
|
|
4389
|
+
});
|
|
4390
|
+
} catch (error) {
|
|
4391
|
+
console.error("Unable to clear Datalynk offline session cache", error);
|
|
4392
|
+
}
|
|
4393
|
+
}
|
|
4394
|
+
tokenIdentity(token) {
|
|
4395
|
+
if (!token) return null;
|
|
4396
|
+
try {
|
|
4397
|
+
const payload = decodeJwt(token);
|
|
4398
|
+
const user = (payload == null ? void 0 : payload.uid) ?? (payload == null ? void 0 : payload.sub);
|
|
4399
|
+
return {
|
|
4400
|
+
realm: (payload == null ? void 0 : payload.realm) == null ? void 0 : String(payload.realm),
|
|
4401
|
+
user: user == null ? void 0 : String(user)
|
|
4402
|
+
};
|
|
4403
|
+
} catch {
|
|
4404
|
+
return null;
|
|
4405
|
+
}
|
|
4406
|
+
}
|
|
4407
|
+
readOfflineSessionOwner() {
|
|
4408
|
+
if (this.offlineSessionOwner) return this.offlineSessionOwner;
|
|
4409
|
+
if (typeof localStorage == "undefined") return null;
|
|
4410
|
+
try {
|
|
4411
|
+
const value = localStorage.getItem(this.offlineSessionOwnerKey);
|
|
4412
|
+
if (!value) return null;
|
|
4413
|
+
const owner = JSON.parse(value);
|
|
4414
|
+
if (!owner || typeof owner !== "object") return null;
|
|
4415
|
+
return this.offlineSessionOwner = owner;
|
|
4416
|
+
} catch {
|
|
4417
|
+
return null;
|
|
4418
|
+
}
|
|
4419
|
+
}
|
|
4420
|
+
ownersMatch(owner, identity2) {
|
|
4421
|
+
if (!identity2) return false;
|
|
4422
|
+
if (owner.realm != null && owner.realm !== identity2.realm) return false;
|
|
4423
|
+
if (owner.user != null && owner.user !== identity2.user) return false;
|
|
4424
|
+
return true;
|
|
4425
|
+
}
|
|
4426
|
+
rememberOfflineSessionOwner(token) {
|
|
4427
|
+
const identity2 = this.tokenIdentity(token);
|
|
4428
|
+
if (!identity2) return;
|
|
4429
|
+
const existing = this.readOfflineSessionOwner();
|
|
4430
|
+
if (existing && !this.ownersMatch(existing, identity2)) return;
|
|
4431
|
+
this.offlineSessionOwner = identity2;
|
|
4432
|
+
if (typeof localStorage != "undefined") {
|
|
4433
|
+
try {
|
|
4434
|
+
localStorage.setItem(this.offlineSessionOwnerKey, JSON.stringify(identity2));
|
|
4435
|
+
} catch {
|
|
4436
|
+
}
|
|
4437
|
+
}
|
|
4438
|
+
}
|
|
4439
|
+
ensureOfflineSessionOwner(token) {
|
|
4440
|
+
const existing = this.readOfflineSessionOwner();
|
|
4441
|
+
const identity2 = this.tokenIdentity(token);
|
|
4442
|
+
if (existing) return this.ownersMatch(existing, identity2);
|
|
4443
|
+
if (!identity2) return true;
|
|
4444
|
+
this.rememberOfflineSessionOwner(token);
|
|
4445
|
+
return true;
|
|
4446
|
+
}
|
|
4447
|
+
hasOwnedOfflineSession(token) {
|
|
4448
|
+
const owner = this.readOfflineSessionOwner();
|
|
4449
|
+
return !!owner && this.ownersMatch(owner, this.tokenIdentity(token));
|
|
4450
|
+
}
|
|
4451
|
+
clearOfflineSessionOwner() {
|
|
4452
|
+
this.offlineSessionOwner = null;
|
|
4453
|
+
if (typeof localStorage != "undefined") {
|
|
4454
|
+
try {
|
|
4455
|
+
localStorage.removeItem(this.offlineSessionOwnerKey);
|
|
4456
|
+
} catch {
|
|
4457
|
+
}
|
|
4458
|
+
}
|
|
4459
|
+
}
|
|
4460
|
+
/** Replay queued writes only after a matching authenticated session is online. */
|
|
4461
|
+
async replayPendingQueue() {
|
|
4462
|
+
if (!this.database || !this.online || this.authenticationInvalid || this.offlineAuthenticationExpired) return;
|
|
4463
|
+
if (this.replayingPending) {
|
|
4464
|
+
this.pendingReplayRequested = true;
|
|
4465
|
+
return;
|
|
4466
|
+
}
|
|
4467
|
+
const table = this.database.table("pending");
|
|
4468
|
+
const owner = this.readOfflineSessionOwner();
|
|
4469
|
+
if (owner && !this.ownersMatch(owner, this.tokenIdentity(this.token))) {
|
|
4470
|
+
if (this.token) console.warn("Datalynk offline queue is quarantined because it belongs to another authenticated user");
|
|
4471
|
+
return;
|
|
4472
|
+
}
|
|
4473
|
+
this.replayingPending = true;
|
|
4474
|
+
try {
|
|
4475
|
+
const keys = await table.getAllKeys();
|
|
4476
|
+
for (const key of keys) {
|
|
4477
|
+
if (!this.online || this.authenticationInvalid || this.offlineAuthenticationExpired) break;
|
|
4478
|
+
if (owner && !this.ownersMatch(owner, this.tokenIdentity(this.token))) break;
|
|
4479
|
+
const request = await table.get(key);
|
|
4480
|
+
if (request == null) continue;
|
|
4481
|
+
try {
|
|
4482
|
+
await this.request(request);
|
|
4483
|
+
await table.delete(key);
|
|
4484
|
+
} catch (error) {
|
|
4485
|
+
break;
|
|
4486
|
+
}
|
|
4487
|
+
}
|
|
4488
|
+
if (await table.count() === 0) this.clearOfflineSessionOwner();
|
|
4489
|
+
} finally {
|
|
4490
|
+
this.replayingPending = false;
|
|
4491
|
+
if (this.pendingReplayRequested) {
|
|
4492
|
+
this.pendingReplayRequested = false;
|
|
4493
|
+
if (this.online && !this.authenticationInvalid && !this.offlineAuthenticationExpired)
|
|
4494
|
+
void this.replayPendingQueue();
|
|
4495
|
+
}
|
|
4496
|
+
}
|
|
4251
4497
|
}
|
|
4252
4498
|
setConnectionStatus(status) {
|
|
4253
4499
|
if (this.status !== status) this.status$.next(status);
|
|
4254
4500
|
const online = status === "online";
|
|
4255
4501
|
if (this.online !== online) this.online$.next(online);
|
|
4256
4502
|
}
|
|
4257
|
-
offlineBanner() {
|
|
4258
|
-
if (this.options.offlineBanner === false || typeof document == "undefined") return;
|
|
4259
|
-
if (this.status === "online") {
|
|
4260
|
-
removeBanner("datalynk-offline-banner");
|
|
4261
|
-
} else {
|
|
4262
|
-
const message = this.status === "unauthorized" ? "⚠️ Your session expired, please sign in again" : this.status === "unavailable" ? "⚠️ Datalynk is currently unavailable" : "⚠️ You are offline, please reconnect to sync changes";
|
|
4263
|
-
createBanner(message, {
|
|
4264
|
-
id: "datalynk-offline-banner",
|
|
4265
|
-
position: this.options.offlineBanner === "top" ? "top" : "bottom"
|
|
4266
|
-
});
|
|
4267
|
-
}
|
|
4268
|
-
}
|
|
4269
4503
|
startHeartbeat() {
|
|
4270
4504
|
this.stopHeartbeat();
|
|
4271
4505
|
this.heartbeat.interval = setInterval(() => this.checkConnection(), this.heartbeat.timeout / 2);
|
|
@@ -4375,6 +4609,8 @@ const _Api = class _Api {
|
|
|
4375
4609
|
if (this.offline && typeof navigator != "undefined") {
|
|
4376
4610
|
if (this.status === "unauthorized") return Promise.reject(errorFromCode(401, "Session expired"));
|
|
4377
4611
|
if (options.offline) {
|
|
4612
|
+
if (!this.ensureOfflineSessionOwner(this.token))
|
|
4613
|
+
return Promise.reject(errorFromCode(409, "Offline queue belongs to another authenticated user"));
|
|
4378
4614
|
(_b = (_a = this.database) == null ? void 0 : _a.table("pending")) == null ? void 0 : _b.add(data, key);
|
|
4379
4615
|
return Promise.resolve();
|
|
4380
4616
|
}
|
package/dist/service.worker.mjs
CHANGED
|
@@ -48,7 +48,7 @@ self.addEventListener('activate', (event) => {
|
|
|
48
48
|
|
|
49
49
|
const settings = await getSettings();
|
|
50
50
|
if(settings?.url) {
|
|
51
|
-
api = new Api(settings.url, { ...(settings.options || {}), serviceWorker: false });
|
|
51
|
+
api = new Api(settings.url, { ...(settings.options || {}), serviceWorker: false, replayOfflineQueue: false });
|
|
52
52
|
if(settings.token) api.token = settings.token;
|
|
53
53
|
}
|
|
54
54
|
})());
|
|
@@ -117,12 +117,17 @@ self.addEventListener('fetch', (event) => {
|
|
|
117
117
|
|
|
118
118
|
self.addEventListener('message', async (event) => {
|
|
119
119
|
if(event.data?.options) { // 🔧 Update settings
|
|
120
|
-
api = new Api(event.data.options.url, { ...event.data.options, serviceWorker: false });
|
|
120
|
+
api = new Api(event.data.options.url, { ...event.data.options, serviceWorker: false, replayOfflineQueue: false });
|
|
121
121
|
await setSettings(event.data.options);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
}
|
|
123
|
+
// `null` is an intentional token update during logout/expiry, so check for the
|
|
124
|
+
// property rather than truthiness.
|
|
125
|
+
if(event.data && Object.prototype.hasOwnProperty.call(event.data, 'token')) { // 🔑 Update token
|
|
126
|
+
const token = event.data.token ?? null;
|
|
127
|
+
if(api) api.token = token;
|
|
128
|
+
await setSettings({token});
|
|
129
|
+
}
|
|
130
|
+
if(event.data?.clearCache) { // 🧹 Clear cache
|
|
126
131
|
await caches.delete(CACHE_NAME);
|
|
127
132
|
}
|
|
128
133
|
});
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@auxilium/datalynk-client",
|
|
3
3
|
"description": "Datalynk client library",
|
|
4
4
|
"repository": "https://gitlab.auxiliumgroup.com/auxilium/datalynk/datalynk-client",
|
|
5
|
-
"version": "1.5.
|
|
5
|
+
"version": "1.5.2",
|
|
6
6
|
"author": "Auxilium Group <devops@auxiliumgroup.com>",
|
|
7
7
|
"private": false,
|
|
8
8
|
"main": "./dist/index.cjs",
|