@auxilium/datalynk-client 1.4.1 → 1.5.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/index.cjs CHANGED
@@ -1,902 +1,276 @@
1
- (function(global2, factory) {
2
- typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define(["exports"], factory) : (global2 = typeof globalThis !== "undefined" ? globalThis : global2 || self, factory(global2.utils = {}));
1
+ (function(global, factory) {
2
+ typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define(["exports"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.utils = {}));
3
3
  })(this, function(exports2) {
4
4
  "use strict";var __defProp = Object.defineProperty;
5
5
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6
6
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
7
7
 
8
- var __defProp2 = Object.defineProperty;
9
- var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
10
- var __publicField2 = (obj, key, value) => __defNormalProp2(obj, typeof key !== "symbol" ? key + "" : key, value);
11
- function clean(obj, undefinedOnly = false) {
12
- if (obj == null) throw new Error("Cannot clean a NULL value");
13
- if (Array.isArray(obj)) {
14
- obj = obj.filter((o) => undefinedOnly ? o !== void 0 : o != null);
15
- } else {
16
- Object.entries(obj).forEach(([key, value]) => {
17
- if (undefinedOnly && value === void 0 || !undefinedOnly && value == null) delete obj[key];
18
- });
19
- }
20
- return obj;
8
+ function clean(value, undefinedOnly = false) {
9
+ if (value == null) throw new Error("Cannot clean a NULL value");
10
+ if (Array.isArray(value)) return value.filter((item) => undefinedOnly ? item !== void 0 : item != null);
11
+ Object.entries(value).forEach(([key, item]) => {
12
+ if (undefinedOnly && item === void 0 || !undefinedOnly && item == null) delete value[key];
13
+ });
14
+ return value;
21
15
  }
22
16
  function deepCopy(value) {
23
17
  try {
24
18
  return structuredClone(value);
25
19
  } catch {
26
- return JSON.parse(JSONSanitize(value));
20
+ const seen = [];
21
+ return JSON.parse(JSON.stringify(value, (_key, item) => {
22
+ if (typeof item === "object" && item !== null) {
23
+ if (seen.includes(item)) return "[Circular]";
24
+ seen.push(item);
25
+ }
26
+ return item;
27
+ }));
27
28
  }
28
29
  }
29
- function dotNotation(obj, prop, set) {
30
- if (obj == null || !prop) return void 0;
31
- return prop.split(/[.[\]]/g).filter((prop2) => prop2.length).reduce((obj2, prop2, i, arr) => {
32
- if (prop2[0] == '"' || prop2[0] == "'") prop2 = prop2.slice(1, -1);
33
- if (!(obj2 == null ? void 0 : obj2.hasOwnProperty(prop2))) {
34
- return void 0;
35
- }
36
- return obj2[prop2];
37
- }, obj);
38
- }
39
- function isEqual(a, b) {
40
- const ta = typeof a, tb = typeof b;
41
- if (ta != "object" || a == null || (tb != "object" || b == null))
42
- return ta == "function" && tb == "function" ? a.toString() == b.toString() : a === b;
43
- const keys = Object.keys(a);
44
- if (keys.length != Object.keys(b).length) return false;
45
- return Object.keys(a).every((key) => isEqual(a[key], b[key]));
46
- }
47
- function JSONAttemptParse(json) {
30
+ function JSONAttemptParse(value) {
48
31
  try {
49
- return JSON.parse(json);
32
+ return JSON.parse(value);
50
33
  } catch {
51
- return json;
52
- }
53
- }
54
- function JSONSanitize(obj, space) {
55
- const cache = [];
56
- return JSON.stringify(obj, (key, value) => {
57
- if (typeof value === "object" && value !== null) {
58
- if (cache.includes(value)) return "[Circular]";
59
- cache.push(value);
60
- }
61
34
  return value;
62
- }, space);
63
- }
64
- class ASet extends Array {
65
- /** Number of elements in set */
66
- get size() {
67
- return this.length;
68
- }
69
- /**
70
- * Array to create set from, duplicate values will be removed
71
- * @param {T[]} elements Elements which will be added to set
72
- */
73
- constructor(elements = []) {
74
- super();
75
- if (!!(elements == null ? void 0 : elements["forEach"]))
76
- elements.forEach((el) => this.add(el));
77
- }
78
- /**
79
- * Add elements to set if unique
80
- * @param items
81
- */
82
- add(...items) {
83
- items.filter((el) => !this.has(el)).forEach((el) => this.push(el));
84
- return this;
85
- }
86
- /**
87
- * Remove all elements
88
- */
89
- clear() {
90
- this.splice(0, this.length);
91
- return this;
92
- }
93
- /**
94
- * Delete elements from set
95
- * @param items Elements that will be deleted
96
- */
97
- delete(...items) {
98
- items.forEach((el) => {
99
- const index = this.indexOf(el);
100
- if (index != -1) this.splice(index, 1);
101
- });
102
- return this;
103
- }
104
- /**
105
- * Create list of elements this set has which the comparison set does not
106
- * @param {ASet<T>} set Set to compare against
107
- * @return {ASet<T>} Different elements
108
- */
109
- difference(set) {
110
- return new ASet(this.filter((el) => !set.has(el)));
111
- }
112
- /**
113
- * Check if set includes element
114
- * @param {T} el Element to look for
115
- * @return {boolean} True if element was found, false otherwise
116
- */
117
- has(el) {
118
- return this.indexOf(el) != -1;
119
- }
120
- /**
121
- * Find index number of element, or -1 if it doesn't exist. Matches by equality not reference
122
- *
123
- * @param {T} search Element to find
124
- * @param {number} fromIndex Starting index position
125
- * @return {number} Element index number or -1 if missing
126
- */
127
- indexOf(search2, fromIndex) {
128
- return super.findIndex((el) => isEqual(el, search2), fromIndex);
129
- }
130
- /**
131
- * Create list of elements this set has in common with the comparison set
132
- * @param {ASet<T>} set Set to compare against
133
- * @return {boolean} Set of common elements
134
- */
135
- intersection(set) {
136
- return new ASet(this.filter((el) => set.has(el)));
137
- }
138
- /**
139
- * Check if this set has no elements in common with the comparison set
140
- * @param {ASet<T>} set Set to compare against
141
- * @return {boolean} True if nothing in common, false otherwise
142
- */
143
- isDisjointFrom(set) {
144
- return this.intersection(set).size == 0;
145
- }
146
- /**
147
- * Check if all elements in this set are included in the comparison set
148
- * @param {ASet<T>} set Set to compare against
149
- * @return {boolean} True if all elements are included, false otherwise
150
- */
151
- isSubsetOf(set) {
152
- return this.findIndex((el) => !set.has(el)) == -1;
153
- }
154
- /**
155
- * Check if all elements from comparison set are included in this set
156
- * @param {ASet<T>} set Set to compare against
157
- * @return {boolean} True if all elements are included, false otherwise
158
- */
159
- isSuperset(set) {
160
- return set.findIndex((el) => !this.has(el)) == -1;
161
- }
162
- /**
163
- * Create list of elements that are only in one set but not both (XOR)
164
- * @param {ASet<T>} set Set to compare against
165
- * @return {ASet<T>} New set of unique elements
166
- */
167
- symmetricDifference(set) {
168
- return new ASet([...this.difference(set), ...set.difference(this)]);
169
- }
170
- /**
171
- * Create joined list of elements included in this & the comparison set
172
- * @param {ASet<T>} set Set join
173
- * @return {ASet<T>} New set of both previous sets combined
174
- */
175
- union(set) {
176
- return new ASet([...this, ...set]);
177
35
  }
178
36
  }
179
- function sortByProp(prop, reverse = false) {
180
- return function(a, b) {
181
- const aVal = dotNotation(a, prop);
182
- const bVal = dotNotation(b, prop);
183
- if (typeof aVal == "number" && typeof bVal == "number")
184
- return (reverse ? -1 : 1) * (aVal - bVal);
185
- if (aVal > bVal) return reverse ? -1 : 1;
186
- if (aVal < bVal) return reverse ? 1 : -1;
187
- return 0;
188
- };
189
- }
190
37
  function makeArray(value) {
191
38
  return Array.isArray(value) ? value : [value];
192
39
  }
193
- class Database {
194
- constructor(database, tables, version2) {
195
- __publicField2(this, "connection");
196
- __publicField2(this, "tables");
197
- this.database = database;
198
- this.version = version2;
199
- this.connection = new Promise((resolve, reject) => {
200
- const req = indexedDB.open(this.database, this.version);
201
- this.tables = tables.map((t) => {
202
- t = typeof t == "object" ? t : { name: t };
203
- return { ...t, name: t.name.toString() };
204
- });
205
- const tableNames = new ASet(this.tables.map((t) => t.name));
206
- req.onerror = () => reject(req.error);
207
- req.onsuccess = () => {
208
- const db = req.result;
209
- if (tableNames.symmetricDifference(new ASet(Array.from(db.objectStoreNames))).length) {
210
- db.close();
211
- Object.assign(this, new Database(this.database, this.tables, db.version + 1));
212
- } else {
213
- this.version = db.version;
214
- resolve(db);
215
- }
216
- };
217
- req.onupgradeneeded = () => {
218
- const db = req.result;
219
- const existingTables = new ASet(Array.from(db.objectStoreNames));
220
- existingTables.difference(tableNames).forEach((name) => db.deleteObjectStore(name));
221
- tableNames.difference(existingTables).forEach((name) => db.createObjectStore(name));
222
- };
223
- });
224
- }
225
- includes(name) {
226
- return !!this.tables.find((t) => t.name == name.toString());
227
- }
228
- table(name) {
229
- return new Table(this, name.toString());
230
- }
40
+ function property(value, path) {
41
+ return path.split(".").reduce((current, key) => current == null ? void 0 : current[key], value);
231
42
  }
232
- class Table {
233
- constructor(database, name) {
234
- this.database = database;
235
- this.name = name;
236
- }
237
- async tx(table, fn2, readonly = false) {
238
- const db = await this.database.connection;
239
- const tx = db.transaction(table, readonly ? "readonly" : "readwrite");
240
- const store = tx.objectStore(table);
241
- return new Promise((resolve, reject) => {
242
- const request = fn2(store);
243
- request.onsuccess = () => resolve(request.result);
244
- request.onerror = () => reject(request.error);
245
- });
246
- }
247
- add(value, key) {
248
- return this.tx(this.name, (store) => store.add(value, key));
249
- }
250
- count() {
251
- return this.tx(this.name, (store) => store.count(), true);
252
- }
253
- put(key, value) {
254
- return this.tx(this.name, (store) => store.put(value, key));
255
- }
256
- getAll() {
257
- return this.tx(this.name, (store) => store.getAll(), true);
258
- }
259
- getAllKeys() {
260
- return this.tx(this.name, (store) => store.getAllKeys(), true);
261
- }
262
- get(key) {
263
- return this.tx(this.name, (store) => store.get(key), true);
264
- }
265
- delete(key) {
266
- return this.tx(this.name, (store) => store.delete(key));
267
- }
268
- clear() {
269
- return this.tx(this.name, (store) => store.clear());
270
- }
43
+ function sortByProp(path, reverse = false) {
44
+ return (a, b) => {
45
+ const aValue = property(a, path);
46
+ const bValue = property(b, path);
47
+ if (typeof aValue == "number" && typeof bValue == "number")
48
+ return (reverse ? -1 : 1) * (aValue - bValue);
49
+ if (aValue > bValue) return reverse ? -1 : 1;
50
+ if (aValue < bValue) return reverse ? 1 : -1;
51
+ return 0;
52
+ };
53
+ }
54
+ async function sleepWhile(condition, interval = 100) {
55
+ while (await condition()) await new Promise((resolve) => setTimeout(resolve, interval));
271
56
  }
272
57
  function contrast(background) {
273
- const exploded = background == null ? void 0 : background.match(background.length >= 6 ? /[0-9a-fA-F]{2}/g : /[0-9a-fA-F]/g);
274
- if (!exploded || (exploded == null ? void 0 : exploded.length) < 3) return "black";
275
- const [r, g, b] = exploded.map((hex) => parseInt(hex.length == 1 ? `${hex}${hex}` : hex, 16));
276
- const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
277
- return luminance > 0.5 ? "black" : "white";
58
+ const parts = background == null ? void 0 : background.match(background.length >= 6 ? /[0-9a-fA-F]{2}/g : /[0-9a-fA-F]/g);
59
+ if (!parts || parts.length < 3) return "black";
60
+ const [red, green, blue] = parts.map((hex) => parseInt(hex.length === 1 ? hex + hex : hex, 16));
61
+ return (0.299 * red + 0.587 * green + 0.114 * blue) / 255 > 0.5 ? "black" : "white";
278
62
  }
279
- const LETTER_LIST = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
280
- const NUMBER_LIST = "0123456789";
281
- const SYMBOL_LIST = "~`!@#$%^&*()_-+={[}]|\\:;\"'<,>.?/";
282
63
  function randomStringBuilder(length, letters = false, numbers = false, symbols = false) {
64
+ const LETTER_LIST = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
65
+ const NUMBER_LIST = "0123456789";
66
+ const SYMBOL_LIST = "~`!@#$%^&*()_-+={[}]|\\:;\"'<,>.?/";
283
67
  if (!letters && !numbers && !symbols) throw new Error("Must enable at least one: letters, numbers, symbols");
284
68
  return Array(length).fill(null).map(() => {
285
- let c;
69
+ let character;
286
70
  do {
287
71
  const type = ~~(Math.random() * 3);
288
- if (letters && type == 0) {
289
- c = LETTER_LIST[~~(Math.random() * LETTER_LIST.length)];
290
- } else if (numbers && type == 1) {
291
- c = NUMBER_LIST[~~(Math.random() * NUMBER_LIST.length)];
292
- } else if (symbols && type == 2) {
293
- c = SYMBOL_LIST[~~(Math.random() * SYMBOL_LIST.length)];
294
- }
295
- } while (!c);
296
- return c;
72
+ if (letters && type == 0) character = LETTER_LIST[~~(Math.random() * LETTER_LIST.length)];
73
+ else if (numbers && type == 1) character = NUMBER_LIST[~~(Math.random() * NUMBER_LIST.length)];
74
+ else if (symbols && type == 2) character = SYMBOL_LIST[~~(Math.random() * SYMBOL_LIST.length)];
75
+ } while (!character);
76
+ return character;
297
77
  }).join("");
298
78
  }
299
- class PromiseProgress extends Promise {
300
- constructor(executor) {
301
- super((resolve, reject) => executor(
302
- (value) => resolve(value),
303
- (reason) => reject(reason),
304
- (progress) => this.progress = progress
305
- ));
306
- __publicField2(this, "listeners", []);
307
- __publicField2(this, "_progress", 0);
308
- }
309
- get progress() {
310
- return this._progress;
311
- }
312
- set progress(p) {
313
- if (p == this._progress) return;
314
- this._progress = p;
315
- this.listeners.forEach((l) => l(p));
316
- }
317
- static from(promise) {
318
- if (promise instanceof PromiseProgress) return promise;
319
- return new PromiseProgress((res, rej) => promise.then((...args) => res(...args)).catch((...args) => rej(...args)));
320
- }
321
- from(promise) {
322
- const newPromise = PromiseProgress.from(promise);
323
- this.onProgress((p) => newPromise.progress = p);
324
- return newPromise;
325
- }
326
- onProgress(callback) {
327
- this.listeners.push(callback);
328
- return this;
329
- }
330
- then(res, rej) {
331
- const resp = super.then(res, rej);
332
- return this.from(resp);
333
- }
334
- catch(rej) {
335
- return this.from(super.catch(rej));
336
- }
337
- finally(res) {
338
- return this.from(super.finally(res));
339
- }
340
- }
341
- function sleep(ms) {
342
- return new Promise((res) => setTimeout(res, ms));
343
- }
344
- async function sleepWhile(fn2, checkInterval = 100) {
345
- while (await fn2()) await sleep(checkInterval);
346
- }
347
- class TypedEmitter {
348
- constructor() {
349
- __publicField2(this, "listeners", {});
350
- }
351
- static emit(event, ...args) {
352
- (this.listeners["*"] || []).forEach((l) => l(event, ...args));
353
- (this.listeners[event.toString()] || []).forEach((l) => l(...args));
354
- }
355
- static off(event, listener) {
356
- const e = event.toString();
357
- this.listeners[e] = (this.listeners[e] || []).filter((l) => l != listener);
358
- }
359
- static on(event, listener) {
360
- var _a;
361
- const e = event.toString();
362
- if (!this.listeners[e]) this.listeners[e] = [];
363
- (_a = this.listeners[e]) == null ? void 0 : _a.push(listener);
364
- return () => this.off(event, listener);
365
- }
366
- static once(event, listener) {
367
- return new Promise((res) => {
368
- const unsubscribe = this.on(event, (...args) => {
369
- res(args.length == 1 ? args[0] : args);
370
- if (listener) listener(...args);
371
- unsubscribe();
372
- });
373
- });
374
- }
375
- emit(event, ...args) {
376
- (this.listeners["*"] || []).forEach((l) => l(event, ...args));
377
- (this.listeners[event] || []).forEach((l) => l(...args));
378
- }
379
- off(event, listener) {
380
- this.listeners[event] = (this.listeners[event] || []).filter((l) => l != listener);
381
- }
382
- on(event, listener) {
383
- var _a;
384
- if (!this.listeners[event]) this.listeners[event] = [];
385
- (_a = this.listeners[event]) == null ? void 0 : _a.push(listener);
386
- return () => this.off(event, listener);
387
- }
388
- once(event, listener) {
389
- return new Promise((res) => {
390
- const unsubscribe = this.on(event, (...args) => {
391
- res(args.length == 1 ? args[0] : args);
392
- if (listener) listener(...args);
393
- unsubscribe();
394
- });
395
- });
396
- }
397
- }
398
- __publicField2(TypedEmitter, "listeners", {});
399
79
  class CustomError extends Error {
400
80
  constructor(message, code) {
401
81
  super(message);
402
- __publicField2(this, "_code");
82
+ __publicField(this, "_code");
403
83
  if (code != null) this._code = code;
404
84
  }
405
85
  get code() {
406
86
  return this._code || this.constructor.code;
407
87
  }
408
- set code(c) {
409
- this._code = c;
410
- }
411
- static from(err) {
412
- const code = Number(err.statusCode) ?? Number(err.code);
413
- const newErr = new this(err.message || err.toString());
414
- return Object.assign(newErr, {
415
- stack: err.stack,
416
- ...err,
417
- code: code ?? void 0
418
- });
419
- }
420
- static instanceof(err) {
421
- return err.constructor.code != void 0;
88
+ set code(code) {
89
+ this._code = code;
422
90
  }
423
91
  toString() {
424
92
  return this.message || super.toString();
425
93
  }
426
94
  }
427
- __publicField2(CustomError, "code", 500);
95
+ __publicField(CustomError, "code", 500);
428
96
  class BadRequestError extends CustomError {
429
97
  constructor(message = "Bad Request") {
430
98
  super(message);
431
99
  }
432
- static instanceof(err) {
433
- return err.constructor.code == this.code;
434
- }
435
100
  }
436
- __publicField2(BadRequestError, "code", 400);
101
+ __publicField(BadRequestError, "code", 400);
437
102
  class UnauthorizedError extends CustomError {
438
103
  constructor(message = "Unauthorized") {
439
104
  super(message);
440
105
  }
441
- static instanceof(err) {
442
- return err.constructor.code == this.code;
443
- }
444
106
  }
445
- __publicField2(UnauthorizedError, "code", 401);
107
+ __publicField(UnauthorizedError, "code", 401);
446
108
  class PaymentRequiredError extends CustomError {
447
109
  constructor(message = "Payment Required") {
448
110
  super(message);
449
111
  }
450
- static instanceof(err) {
451
- return err.constructor.code == this.code;
452
- }
453
112
  }
454
- __publicField2(PaymentRequiredError, "code", 402);
113
+ __publicField(PaymentRequiredError, "code", 402);
455
114
  class ForbiddenError extends CustomError {
456
115
  constructor(message = "Forbidden") {
457
116
  super(message);
458
117
  }
459
- static instanceof(err) {
460
- return err.constructor.code == this.code;
461
- }
462
118
  }
463
- __publicField2(ForbiddenError, "code", 403);
119
+ __publicField(ForbiddenError, "code", 403);
464
120
  class NotFoundError extends CustomError {
465
121
  constructor(message = "Not Found") {
466
122
  super(message);
467
123
  }
468
- static instanceof(err) {
469
- return err.constructor.code == this.code;
470
- }
471
124
  }
472
- __publicField2(NotFoundError, "code", 404);
125
+ __publicField(NotFoundError, "code", 404);
473
126
  class MethodNotAllowedError extends CustomError {
474
127
  constructor(message = "Method Not Allowed") {
475
128
  super(message);
476
129
  }
477
- static instanceof(err) {
478
- return err.constructor.code == this.code;
479
- }
480
130
  }
481
- __publicField2(MethodNotAllowedError, "code", 405);
131
+ __publicField(MethodNotAllowedError, "code", 405);
482
132
  class NotAcceptableError extends CustomError {
483
133
  constructor(message = "Not Acceptable") {
484
134
  super(message);
485
135
  }
486
- static instanceof(err) {
487
- return err.constructor.code == this.code;
488
- }
489
136
  }
490
- __publicField2(NotAcceptableError, "code", 406);
137
+ __publicField(NotAcceptableError, "code", 406);
491
138
  class InternalServerError extends CustomError {
492
139
  constructor(message = "Internal Server Error") {
493
140
  super(message);
494
141
  }
495
- static instanceof(err) {
496
- return err.constructor.code == this.code;
497
- }
498
142
  }
499
- __publicField2(InternalServerError, "code", 500);
143
+ __publicField(InternalServerError, "code", 500);
500
144
  class NotImplementedError extends CustomError {
501
145
  constructor(message = "Not Implemented") {
502
146
  super(message);
503
147
  }
504
- static instanceof(err) {
505
- return err.constructor.code == this.code;
506
- }
507
148
  }
508
- __publicField2(NotImplementedError, "code", 501);
149
+ __publicField(NotImplementedError, "code", 501);
509
150
  class BadGatewayError extends CustomError {
510
151
  constructor(message = "Bad Gateway") {
511
152
  super(message);
512
153
  }
513
- static instanceof(err) {
514
- return err.constructor.code == this.code;
515
- }
516
154
  }
517
- __publicField2(BadGatewayError, "code", 502);
155
+ __publicField(BadGatewayError, "code", 502);
518
156
  class ServiceUnavailableError extends CustomError {
519
157
  constructor(message = "Service Unavailable") {
520
158
  super(message);
521
159
  }
522
- static instanceof(err) {
523
- return err.constructor.code == this.code;
524
- }
525
160
  }
526
- __publicField2(ServiceUnavailableError, "code", 503);
161
+ __publicField(ServiceUnavailableError, "code", 503);
527
162
  class GatewayTimeoutError extends CustomError {
528
163
  constructor(message = "Gateway Timeout") {
529
164
  super(message);
530
165
  }
531
- static instanceof(err) {
532
- return err.constructor.code == this.code;
533
- }
534
166
  }
535
- __publicField2(GatewayTimeoutError, "code", 504);
167
+ __publicField(GatewayTimeoutError, "code", 504);
536
168
  function errorFromCode(code, message) {
537
- switch (code) {
538
- case 400:
539
- return new BadRequestError(message);
540
- case 401:
541
- return new UnauthorizedError(message);
542
- case 402:
543
- return new PaymentRequiredError(message);
544
- case 403:
545
- return new ForbiddenError(message);
546
- case 404:
547
- return new NotFoundError(message);
548
- case 405:
549
- return new MethodNotAllowedError(message);
550
- case 406:
551
- return new NotAcceptableError(message);
552
- case 500:
553
- return new InternalServerError(message);
554
- case 501:
555
- return new NotImplementedError(message);
556
- case 502:
557
- return new BadGatewayError(message);
558
- case 503:
559
- return new ServiceUnavailableError(message);
560
- case 504:
561
- return new GatewayTimeoutError(message);
562
- default:
563
- return new CustomError(message, code);
564
- }
565
- }
566
- class HttpResponse extends Response {
567
- constructor(resp, stream) {
568
- const body = [204, 205, 304].includes(resp.status) ? null : stream;
569
- super(body, {
570
- headers: resp.headers,
571
- status: resp.status,
572
- statusText: resp.statusText
573
- });
574
- __publicField2(this, "data");
575
- __publicField2(this, "ok");
576
- __publicField2(this, "redirected");
577
- __publicField2(this, "type");
578
- __publicField2(this, "url");
579
- this.ok = resp.ok;
580
- this.redirected = resp.redirected;
581
- this.type = resp.type;
582
- this.url = resp.url;
583
- }
169
+ const errors = {
170
+ 400: BadRequestError,
171
+ 401: UnauthorizedError,
172
+ 402: PaymentRequiredError,
173
+ 403: ForbiddenError,
174
+ 404: NotFoundError,
175
+ 405: MethodNotAllowedError,
176
+ 406: NotAcceptableError,
177
+ 500: InternalServerError,
178
+ 501: NotImplementedError,
179
+ 502: BadGatewayError,
180
+ 503: ServiceUnavailableError,
181
+ 504: GatewayTimeoutError
182
+ };
183
+ const ErrorType = errors[code];
184
+ return ErrorType ? new ErrorType(message) : new CustomError(message, code);
584
185
  }
585
- const _Http = class _Http2 {
586
- constructor(defaults = {}) {
587
- __publicField2(this, "interceptors", {});
588
- __publicField2(this, "headers", {});
589
- __publicField2(this, "url");
590
- this.url = defaults.url ?? null;
591
- this.headers = defaults.headers || {};
592
- if (defaults.interceptors) {
593
- defaults.interceptors.forEach((i) => _Http2.addInterceptor(i));
594
- }
595
- }
596
- static addInterceptor(fn2) {
597
- const key = Object.keys(_Http2.interceptors).length.toString();
598
- _Http2.interceptors[key] = fn2;
599
- return () => {
600
- _Http2.interceptors[key] = null;
601
- };
602
- }
603
- addInterceptor(fn2) {
604
- const key = Object.keys(this.interceptors).length.toString();
605
- this.interceptors[key] = fn2;
606
- return () => {
607
- this.interceptors[key] = null;
608
- };
609
- }
610
- request(opts = {}) {
611
- var _a;
612
- if (!this.url && !opts.url) throw new Error("URL needs to be set");
613
- let url = ((_a = opts.url) == null ? void 0 : _a.startsWith("http")) ? opts.url : (this.url || "") + (opts.url || "");
614
- url = url.replaceAll(/([^:]\/)\/+/g, "$1");
615
- if (opts.fragment) url.includes("#") ? url.replace(/#.*(\?|\n)/g, (match, arg1) => `#${opts.fragment}${arg1}`) : `${url}#${opts.fragment}`;
616
- if (opts.query) {
617
- const q = Array.isArray(opts.query) ? opts.query : Object.keys(opts.query).map((k) => ({ key: k, value: opts.query[k] }));
618
- url += (url.includes("?") ? "&" : "?") + q.map((q2) => `${q2.key}=${q2.value}`).join("&");
619
- }
620
- const headers = clean({
621
- "Content-Type": !opts.body ? void 0 : opts.body instanceof FormData ? "multipart/form-data" : "application/json",
622
- ..._Http2.headers,
623
- ...this.headers,
624
- ...opts.headers
625
- });
626
- if (typeof opts.body == "object" && opts.body != null && headers["Content-Type"] == "application/json")
627
- opts.body = JSON.stringify(opts.body);
628
- return new PromiseProgress((res, rej, prog) => {
629
- try {
630
- fetch(url, {
631
- headers,
632
- method: opts.method || (opts.body ? "POST" : "GET"),
633
- body: opts.body
634
- }).then(async (resp) => {
635
- var _a2, _b;
636
- for (let fn2 of [...Object.values(_Http2.interceptors), ...Object.values(this.interceptors)]) {
637
- await new Promise((res2) => fn2(resp, () => res2()));
638
- }
639
- const contentLength = resp.headers.get("Content-Length");
640
- const total = contentLength ? parseInt(contentLength, 10) : 0;
641
- let loaded = 0;
642
- const reader = (_a2 = resp.body) == null ? void 0 : _a2.getReader();
643
- const stream = new ReadableStream({
644
- start(controller) {
645
- function push() {
646
- reader == null ? void 0 : reader.read().then((event) => {
647
- if (event.done) return controller.close();
648
- loaded += event.value.byteLength;
649
- prog(loaded / total);
650
- controller.enqueue(event.value);
651
- push();
652
- }).catch((error) => controller.error(error));
653
- }
654
- push();
655
- }
656
- });
657
- resp = new HttpResponse(resp, stream);
658
- if (opts.decode !== false) {
659
- const content = (_b = resp.headers.get("Content-Type")) == null ? void 0 : _b.toLowerCase();
660
- if (content == null ? void 0 : content.includes("form")) resp.data = await resp.formData();
661
- else if (content == null ? void 0 : content.includes("json")) resp.data = await resp.json();
662
- else if (content == null ? void 0 : content.includes("text")) resp.data = await resp.text();
663
- else if (content == null ? void 0 : content.includes("application")) resp.data = await resp.blob();
664
- }
665
- if (resp.ok) res(resp);
666
- else rej(resp);
667
- }).catch((err) => rej(err));
668
- } catch (err) {
669
- rej(err);
670
- }
671
- });
672
- }
673
- };
674
- __publicField2(_Http, "interceptors", {});
675
- __publicField2(_Http, "headers", {});
676
186
  function decodeJwt(token) {
677
187
  const base64 = token.split(".")[1].replace(/-/g, "+").replace(/_/g, "/");
678
- return JSONAttemptParse(decodeURIComponent(atob(base64).split("").map(function(c) {
679
- return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
680
- }).join("")));
188
+ return JSONAttemptParse(decodeURIComponent(atob(base64).split("").map(
189
+ (character) => "%" + ("00" + character.charCodeAt(0).toString(16)).slice(-2)
190
+ ).join("")));
681
191
  }
682
- const CliEffects = {
683
- CLEAR: "\x1B[0m"
684
- };
685
- const CliForeground = {
686
- RED: "\x1B[31m",
687
- YELLOW: "\x1B[33m",
688
- BLUE: "\x1B[34m",
689
- LIGHT_GREY: "\x1B[37m"
690
- };
691
- const _Logger = class _Logger2 extends TypedEmitter {
692
- constructor(namespace) {
693
- super();
694
- this.namespace = namespace;
695
- }
696
- format(...text) {
697
- const now = /* @__PURE__ */ new Date();
698
- const timestamp = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()} ${now.getHours().toString().padStart(2, "0")}:${now.getMinutes().toString().padStart(2, "0")}:${now.getSeconds().toString().padStart(2, "0")}.${now.getMilliseconds().toString().padEnd(3, "0")}`;
699
- return `${timestamp}${this.namespace ? ` [${this.namespace}]` : ""} ${text.map((t) => typeof t == "string" ? t : JSONSanitize(t, 2)).join(" ")}`;
700
- }
701
- debug(...args) {
702
- if (_Logger2.LOG_LEVEL < 4) return;
703
- const str = this.format(...args);
704
- _Logger2.emit(4, str);
705
- console.debug(CliForeground.LIGHT_GREY + str + CliEffects.CLEAR);
706
- }
707
- log(...args) {
708
- if (_Logger2.LOG_LEVEL < 3) return;
709
- const str = this.format(...args);
710
- _Logger2.emit(3, str);
711
- console.log(CliEffects.CLEAR + str);
712
- }
713
- info(...args) {
714
- if (_Logger2.LOG_LEVEL < 2) return;
715
- const str = this.format(...args);
716
- _Logger2.emit(2, str);
717
- console.info(CliForeground.BLUE + str + CliEffects.CLEAR);
718
- }
719
- warn(...args) {
720
- if (_Logger2.LOG_LEVEL < 1) return;
721
- const str = this.format(...args);
722
- _Logger2.emit(1, str);
723
- console.warn(CliForeground.YELLOW + str + CliEffects.CLEAR);
724
- }
725
- error(...args) {
726
- if (_Logger2.LOG_LEVEL < 0) return;
727
- const str = this.format(...args);
728
- _Logger2.emit(0, str);
729
- console.error(CliForeground.RED + str + CliEffects.CLEAR);
730
- }
731
- };
732
- __publicField2(_Logger, "LOG_LEVEL", 4);
733
- var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
734
- var dist = {};
735
- var persist$1 = {};
736
- Object.defineProperty(persist$1, "__esModule", { value: true });
737
- persist$1.persist = persist$1.Persist = void 0;
738
- class Persist {
739
- /**
740
- * @param {string} key Primary key value will be stored under
741
- * @param {PersistOptions<T>} options Configure using {@link PersistOptions}
742
- */
743
- constructor(key, options = {}) {
744
- __publicField2(this, "key");
745
- __publicField2(this, "options");
746
- __publicField2(this, "storage");
747
- __publicField2(this, "watches", {});
748
- __publicField2(this, "_value");
749
- this.key = key;
750
- this.options = options;
751
- this.storage = options.storage || localStorage;
752
- this.load();
753
- }
754
- /** Current value or default if undefined */
755
- get value() {
756
- var _a;
757
- return this._value !== void 0 ? this._value : (_a = this.options) == null ? void 0 : _a.default;
758
- }
759
- /** Set value with proxy object wrapper to sync future changes */
760
- set value(v) {
761
- if (v == null || typeof v != "object")
762
- this._value = v;
763
- else
764
- this._value = new Proxy(v, {
765
- get: (target, p) => {
766
- const f = typeof target[p] == "function";
767
- if (!f)
768
- return target[p];
769
- return (...args) => {
770
- const value = target[p](...args);
771
- this.save();
772
- return value;
773
- };
774
- },
775
- set: (target, p, newValue) => {
776
- target[p] = newValue;
777
- this.save();
778
- return true;
192
+ class Database {
193
+ constructor(database, tables, version2) {
194
+ __publicField(this, "connection");
195
+ __publicField(this, "tables");
196
+ this.database = database;
197
+ this.version = version2;
198
+ this.tables = tables.map((table) => typeof table === "object" ? { ...table, name: table.name.toString() } : { name: table.toString() });
199
+ this.connection = new Promise((resolve, reject) => {
200
+ const request = indexedDB.open(this.database, this.version);
201
+ const requested = new Set(this.tables.map((table) => table.name));
202
+ request.onerror = () => reject(request.error);
203
+ request.onsuccess = () => {
204
+ const db = request.result;
205
+ const existing = new Set(Array.from(db.objectStoreNames));
206
+ if ([...requested].some((name) => !existing.has(name)) || [...existing].some((name) => !requested.has(name))) {
207
+ db.close();
208
+ const upgraded = new Database(this.database, this.tables, db.version + 1);
209
+ this.version = upgraded.version;
210
+ this.connection = upgraded.connection;
211
+ this.connection.then(resolve, reject);
212
+ } else {
213
+ this.version = db.version;
214
+ resolve(db);
779
215
  }
780
- });
781
- this.save();
216
+ };
217
+ request.onupgradeneeded = () => {
218
+ const db = request.result;
219
+ const existing = new Set(Array.from(db.objectStoreNames));
220
+ existing.forEach((name) => {
221
+ if (!requested.has(name)) db.deleteObjectStore(name);
222
+ });
223
+ requested.forEach((name) => {
224
+ if (!existing.has(name)) db.createObjectStore(name);
225
+ });
226
+ };
227
+ });
782
228
  }
783
- /** Notify listeners of change */
784
- notify(value) {
785
- Object.values(this.watches).forEach((watch) => watch(value));
229
+ includes(name) {
230
+ return this.tables.some((table) => table.name === name.toString());
786
231
  }
787
- /** Delete value from storage */
788
- clear() {
789
- this.storage.removeItem(this.key);
790
- }
791
- /** Save current value to storage */
792
- save() {
793
- if (this._value === void 0)
794
- this.clear();
795
- else
796
- this.storage.setItem(this.key, JSON.stringify(this._value));
797
- this.notify(this.value);
798
- }
799
- /** Load value from storage */
800
- load() {
801
- if (this.storage[this.key] != void 0) {
802
- let value = JSON.parse(this.storage.getItem(this.key));
803
- if (value != null && typeof value == "object" && this.options.type)
804
- value.__proto__ = this.options.type.prototype;
805
- this.value = value;
806
- } else
807
- this.value = this.options.default || void 0;
232
+ table(name) {
233
+ return new Table(this, name.toString());
808
234
  }
809
- /**
810
- * Callback function which is run when there are changes
811
- *
812
- * @param {(value: T) => any} fn Callback will run on each change; it's passed the next value & it's return is ignored
813
- * @returns {() => void} Function which will unsubscribe the watch/callback when called
814
- */
815
- watch(fn2) {
816
- const index = Object.keys(this.watches).length;
817
- this.watches[index] = fn2;
818
- return () => {
819
- delete this.watches[index];
820
- };
235
+ }
236
+ class Table {
237
+ constructor(database, name) {
238
+ this.database = database;
239
+ this.name = name;
821
240
  }
822
- /**
823
- * Return value as JSON string
824
- *
825
- * @returns {string} Stringified object as JSON
826
- */
827
- toString() {
828
- return JSON.stringify(this.value);
241
+ async tx(operation, readonly = false) {
242
+ const db = await this.database.connection;
243
+ return new Promise((resolve, reject) => {
244
+ const request = operation(db.transaction(this.name, readonly ? "readonly" : "readwrite").objectStore(this.name));
245
+ request.onsuccess = () => resolve(request.result);
246
+ request.onerror = () => reject(request.error);
247
+ });
829
248
  }
830
- /**
831
- * Return current value
832
- *
833
- * @returns {T} Current value
834
- */
835
- valueOf() {
836
- return this.value;
249
+ add(value, key) {
250
+ return this.tx((store) => store.add(value, key));
837
251
  }
838
- }
839
- persist$1.Persist = Persist;
840
- function persist(options) {
841
- return (target, prop) => {
842
- const key = (options == null ? void 0 : options.key) || `${target.constructor.name}.${prop.toString()}`;
843
- const wrapper = new Persist(key, options);
844
- Object.defineProperty(target, prop, {
845
- get: function() {
846
- return wrapper.value;
847
- },
848
- set: function(v) {
849
- wrapper.value = v;
850
- }
851
- });
852
- };
853
- }
854
- persist$1.persist = persist;
855
- var memoryStorage = {};
856
- Object.defineProperty(memoryStorage, "__esModule", { value: true });
857
- memoryStorage.MemoryStorage = void 0;
858
- class MemoryStorage {
859
- get length() {
860
- return Object.keys(this).length;
252
+ put(key, value) {
253
+ return this.tx((store) => store.put(value, key));
861
254
  }
862
- clear() {
863
- Object.keys(this).forEach((k) => this.removeItem(k));
255
+ get(key) {
256
+ return this.tx((store) => store.get(key), true);
864
257
  }
865
- getItem(key) {
866
- return this[key];
258
+ getAll() {
259
+ return this.tx((store) => store.getAll(), true);
867
260
  }
868
- key(index) {
869
- return Object.keys(this)[index];
261
+ getAllKeys() {
262
+ return this.tx((store) => store.getAllKeys(), true);
870
263
  }
871
- removeItem(key) {
872
- delete this[key];
264
+ delete(key) {
265
+ return this.tx((store) => store.delete(key));
873
266
  }
874
- setItem(key, value) {
875
- this[key] = value;
267
+ clear() {
268
+ return this.tx((store) => store.clear());
269
+ }
270
+ count() {
271
+ return this.tx((store) => store.count(), true);
876
272
  }
877
273
  }
878
- memoryStorage.MemoryStorage = MemoryStorage;
879
- (function(exports3) {
880
- var __createBinding = commonjsGlobal && commonjsGlobal.__createBinding || (Object.create ? function(o, m, k, k2) {
881
- if (k2 === void 0) k2 = k;
882
- var desc = Object.getOwnPropertyDescriptor(m, k);
883
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
884
- desc = { enumerable: true, get: function() {
885
- return m[k];
886
- } };
887
- }
888
- Object.defineProperty(o, k2, desc);
889
- } : function(o, m, k, k2) {
890
- if (k2 === void 0) k2 = k;
891
- o[k2] = m[k];
892
- });
893
- var __exportStar = commonjsGlobal && commonjsGlobal.__exportStar || function(m, exports22) {
894
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports22, p)) __createBinding(exports22, m, p);
895
- };
896
- Object.defineProperty(exports3, "__esModule", { value: true });
897
- __exportStar(persist$1, exports3);
898
- __exportStar(memoryStorage, exports3);
899
- })(dist);
900
274
  var extendStatics = function(d, b) {
901
275
  extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function(d2, b2) {
902
276
  d2.__proto__ = b2;
@@ -1751,6 +1125,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
1751
1125
  __publicField(this, "alert");
1752
1126
  __publicField(this, "button");
1753
1127
  __publicField(this, "form");
1128
+ __publicField(this, "forgotLink");
1754
1129
  __publicField(this, "password");
1755
1130
  __publicField(this, "persist");
1756
1131
  __publicField(this, "username");
@@ -1779,8 +1154,14 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
1779
1154
  this.password = document.querySelector('#datalynk-login-form input[name="password"]');
1780
1155
  this.persist = document.querySelector('#datalynk-login-form input[name="persist"]');
1781
1156
  this.username = document.querySelector('#datalynk-login-form input[name="username"]');
1157
+ this.forgotLink = document.querySelector("#datalynk-login-forgot");
1782
1158
  if (this.options.persist === false) this.persist.parentElement.remove();
1783
1159
  this.form.onsubmit = (event) => this.login(event);
1160
+ this.forgotLink.onclick = (event) => this.forgotPassword(event);
1161
+ const toggleForgotVisibility = () => this.forgotLink.classList.toggle("hidden", !this.username.value.trim());
1162
+ this.username.addEventListener("input", toggleForgotVisibility);
1163
+ const passwordToggle = document.querySelector("#datalynk-password-toggle");
1164
+ if (passwordToggle) passwordToggle.onclick = () => this.password.type = this.password.type === "password" ? "text" : "password";
1784
1165
  const pwaLink = document.querySelector("#pwa-install-link");
1785
1166
  if (pwaLink) {
1786
1167
  pwaLink.addEventListener("click", async (e) => {
@@ -1852,6 +1233,19 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
1852
1233
  this.button.disabled = false;
1853
1234
  });
1854
1235
  }
1236
+ /** Forgot password link click event */
1237
+ forgotPassword(event) {
1238
+ event.preventDefault();
1239
+ const login = this.username.value.trim() || prompt("Enter your email or username") || "";
1240
+ if (!login) return;
1241
+ this.alert.classList.remove("hidden");
1242
+ this.alert.innerHTML = "Sending reset email...";
1243
+ return this.api.auth.resetRequest(login, "email").then(() => {
1244
+ this.alert.innerHTML = "If an account exists, a reset email has been sent.";
1245
+ }).catch((err) => {
1246
+ this.alert.innerHTML = err.message || "Unable to send reset email.";
1247
+ });
1248
+ }
1855
1249
  };
1856
1250
  /** Dynamically create CSS style */
1857
1251
  __publicField(_LoginPrompt, "css", (options) => `
@@ -2004,6 +1398,35 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2004
1398
  text-decoration: none;
2005
1399
  }
2006
1400
 
1401
+ #datalynk-login .login-forgot {
1402
+ display: block;
1403
+ text-align: left;
1404
+ margin-bottom: 0.75rem;
1405
+ color: var(--theme-text);
1406
+ font-size: 14px;
1407
+ text-decoration: none;
1408
+ }
1409
+
1410
+ #datalynk-login .login-forgot:hover {
1411
+ text-decoration: underline;
1412
+ }
1413
+
1414
+ #datalynk-login .password-wrapper {
1415
+ position: relative;
1416
+ }
1417
+
1418
+ #datalynk-login .password-toggle {
1419
+ position: absolute;
1420
+ right: 30px;
1421
+ top: 50%;
1422
+ transform: translateY(-50%);
1423
+ background: none;
1424
+ border: none;
1425
+ padding: 0;
1426
+ color: #333;
1427
+ cursor: pointer;
1428
+ }
1429
+
2007
1430
  #datalynk-login #pwa-install-link {
2008
1431
  display: inline-flex;
2009
1432
  align-items: center;
@@ -2061,7 +1484,6 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2061
1484
  </div>
2062
1485
  <div class="login-content">
2063
1486
  <div class="login-body" style="max-width: 300px">
2064
- <div id="datalynk-login-alert" class="hidden"></div>
2065
1487
  <form id="datalynk-login-form">
2066
1488
  <div>
2067
1489
  <label for="username">Email or Username</label>
@@ -2070,12 +1492,19 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2070
1492
  <br>
2071
1493
  <div>
2072
1494
  <label for="password">Password</label>
2073
- <input id="password" name="password" type="password" autocomplete="current-password">
1495
+ <div class="password-wrapper">
1496
+ <input id="password" name="password" type="password" autocomplete="current-password">
1497
+ <button type="button" id="datalynk-password-toggle" class="password-toggle" tabindex="-1">
1498
+ <svg xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 0 24 24" width="20px" fill="currentColor"><path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zm0 12.5c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5zm0-8a3 3 0 100 6 3 3 0 000-6z"/></svg>
1499
+ </button>
1500
+ </div>
2074
1501
  </div>
1502
+ <a href="#" id="datalynk-login-forgot" class="login-forgot hidden">Forgot Password?</a>
2075
1503
  <br>
2076
1504
  <label style="display: block; margin-bottom: 0.75rem;">
2077
1505
  <input type="checkbox" name="persist" style="width: 20px"> Stay Logged In
2078
1506
  </label>
1507
+ <div id="datalynk-login-alert" class="hidden"></div>
2079
1508
  <button type="submit">Login</button>
2080
1509
  </form>
2081
1510
  </div>
@@ -2116,7 +1545,12 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2116
1545
  this.api = api;
2117
1546
  this.api.token$.subscribe(async (token) => {
2118
1547
  if (token === void 0) return;
2119
- this.user = await this.current(token);
1548
+ try {
1549
+ this.user = await this.current(token);
1550
+ } catch (error) {
1551
+ if (this.api.status === "unauthorized") this.user = null;
1552
+ else console.error("Unable to refresh the current Datalynk user", error);
1553
+ }
2120
1554
  });
2121
1555
  if ((_a = this.api.options.offline) == null ? void 0 : _a.length)
2122
1556
  this.user$.pipe(filter((u) => u !== void 0)).subscribe((u) => localStorage.setItem("datalynk-user", JSON.stringify(u)));
@@ -2636,9 +2070,9 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2636
2070
  document.head.append(style);
2637
2071
  }
2638
2072
  const iconUrl = this.resolvedIconUrl || "https://datalynk-client.primary.auxilium.world/logo.png";
2639
- const prompt = document.createElement("div");
2640
- prompt.classList.add("pwa-prompt");
2641
- prompt.innerHTML = `
2073
+ const prompt2 = document.createElement("div");
2074
+ prompt2.classList.add("pwa-prompt");
2075
+ prompt2.innerHTML = `
2642
2076
  <div class="pwa-prompt-header">
2643
2077
  <img src="${iconUrl}" alt="Logo" />
2644
2078
  <h1>Install ${this.api.options.name}</h1>
@@ -2654,14 +2088,14 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2654
2088
  ${this.nativeInstallPrompt ? `<button id="installPwaBtn">Install App</button>` : stepsHtml}
2655
2089
  </div>
2656
2090
  `;
2657
- const closeBtn = prompt.querySelector(".pwa-prompt-close");
2091
+ const closeBtn = prompt2.querySelector(".pwa-prompt-close");
2658
2092
  closeBtn.onclick = () => {
2659
2093
  var _a2;
2660
2094
  !!((_a2 = this.api.options.pwaSettings) == null ? void 0 : _a2.dismissExpiry) ? localStorage.setItem(storageKey, Date.now().toString()) : localStorage.removeItem(storageKey);
2661
- prompt.remove();
2095
+ prompt2.remove();
2662
2096
  };
2663
- document.body.append(prompt);
2664
- const img = prompt.querySelector(".pwa-prompt-header img");
2097
+ document.body.append(prompt2);
2098
+ const img = prompt2.querySelector(".pwa-prompt-header img");
2665
2099
  if (img) img.onerror = () => img.src = "https://datalynk-client.primary.auxilium.world/logo.png";
2666
2100
  if (this.nativeInstallPrompt) this.bindInstallButton();
2667
2101
  }
@@ -3249,7 +2683,13 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3249
2683
  set cache(cache) {
3250
2684
  this.cache$.next(cache);
3251
2685
  }
3252
- /** Is slice offline support enabled */
2686
+ /**
2687
+ * Whether this slice has local IndexedDB/offline support enabled.
2688
+ *
2689
+ * Offline-enabled slices can fall back to their local cache when either the
2690
+ * browser loses network connectivity or the Datalynk client enters an
2691
+ * unavailable recovery state after a returned server/API failure.
2692
+ */
3253
2693
  get offlineEnabled() {
3254
2694
  var _a;
3255
2695
  return (_a = this.api.database) == null ? void 0 : _a.includes(this.slice.toString());
@@ -3262,7 +2702,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3262
2702
  const onlineExec = call.exec.bind(call);
3263
2703
  return async () => {
3264
2704
  const offlineSupported = this.offlineEnabled && typeof navigator !== "undefined";
3265
- const offlineNow = offlineSupported && !(navigator == null ? void 0 : navigator.onLine);
2705
+ const offlineNow = offlineSupported && (!this.api.online || !(navigator == null ? void 0 : navigator.onLine));
3266
2706
  const offlineSim = async () => {
3267
2707
  var _a, _b, _c;
3268
2708
  const where = (row, condition) => {
@@ -3561,24 +3001,39 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3561
3001
  return this.info;
3562
3002
  }
3563
3003
  /**
3564
- * Synchronize cache with server
3004
+ * Synchronize the local slice cache with the server and subscribe to socket changes.
3005
+ *
3006
+ * For an offline-enabled slice, synchronization failures caused by network or
3007
+ * API unavailability are contained in the background rather than becoming
3008
+ * unhandled promise rejections. Local cached data remains usable while the
3009
+ * {@link Api} recovery loop determines when the failed API path works again.
3010
+ * When the client becomes online again, pending local changes are scheduled
3011
+ * for upload without overlapping push operations.
3012
+ *
3013
+ * Calling `sync(false)` unsubscribes from slice socket events.
3014
+ *
3565
3015
  * @example
3566
3016
  * ```ts
3567
3017
  * const slice: Slice = new Slice<T>(Slices.Contact);
3568
- * slice.sync().subscribe((rows: T[]) => {});
3018
+ * const rows$ = slice.sync();
3019
+ * rows$?.subscribe((rows: T[]) => console.log(rows));
3569
3020
  * ```
3570
- * @param {boolean} on Enable/disable events
3571
- * @return {BehaviorSubject<T[]>} Cache which can be subscribed to
3021
+ * @param on Enable or disable synchronization/socket events.
3022
+ * @returns The observable local cache when synchronization is enabled.
3572
3023
  */
3573
3024
  sync(on = true) {
3574
3025
  if (on) {
3575
- this.pushChanges().then(() => this.select().rows().exec().then((rows) => {
3026
+ void this.pushChanges().then(() => this.select().rows().exec()).then((rows) => {
3576
3027
  this.cache = rows;
3577
3028
  this.loaded = true;
3578
- }));
3029
+ }).catch((error) => {
3030
+ if (this.api.online) console.warn("Unable to synchronize offline slice", error);
3031
+ });
3579
3032
  if (!this.unsubscribe) this.unsubscribe = this.api.socket.sliceEvents(this.slice, (event) => {
3580
3033
  const ids = [...event.data.new, ...event.data.changed];
3581
- this.select(ids).rows().exec().then((rows) => this.cache = [...this.cache.filter((c) => c.id != null && !ids.includes(c.id)), ...rows]);
3034
+ void this.select(ids).rows().exec().then((rows) => this.cache = [...this.cache.filter((c) => c.id != null && !ids.includes(c.id)), ...rows]).catch((error) => {
3035
+ if (this.api.online) console.warn("Unable to refresh offline slice from socket event", error);
3036
+ });
3582
3037
  this.cache = this.cache.filter((v) => v.id && !event.data.lost.includes(v.id));
3583
3038
  });
3584
3039
  return this.cache$;
@@ -3651,7 +3106,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3651
3106
  this.options = options;
3652
3107
  if (!options.url && options.url !== false) {
3653
3108
  const origin = new URL(this.api.url).origin;
3654
- this.options.url = origin.replace("http", "ws").replace(/:\d+/g, "") + `:9390`;
3109
+ this.options.url = origin.replace("http", "ws").replace(/:\d+/g, "") + `/s/`;
3655
3110
  }
3656
3111
  if (this.options.url !== false)
3657
3112
  api.token$.pipe(filter((u) => u !== void 0), distinctUntilChanged()).subscribe(() => this.connect());
@@ -3774,7 +3229,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3774
3229
  } });
3775
3230
  }
3776
3231
  }
3777
- const version = "1.4.1";
3232
+ const version = "1.5.1";
3778
3233
  class WebRtc {
3779
3234
  constructor(api) {
3780
3235
  __publicField(this, "ice");
@@ -4289,6 +3744,14 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4289
3744
  };
4290
3745
  __publicField(_Gps, "GPS_ACK_TIMEOUT_MS", 15e3);
4291
3746
  let Gps = _Gps;
3747
+ class UnexpectedApiResponseError extends Error {
3748
+ constructor(response, body) {
3749
+ super(`Unexpected API response (${response.status} ${response.statusText})`);
3750
+ this.response = response;
3751
+ this.body = body;
3752
+ this.name = "UnexpectedApiResponseError";
3753
+ }
3754
+ }
4292
3755
  const _Api = class _Api {
4293
3756
  /**
4294
3757
  * Connect to Datalynk & send requests
@@ -4312,8 +3775,16 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4312
3775
  target: "version.php",
4313
3776
  timeout: 6e4
4314
3777
  });
3778
+ __publicField(this, "authenticationInvalid", false);
3779
+ __publicField(this, "tokenExpiryTimeout", null);
3780
+ /** Request-specific recovery after a server response proves an API call is broken. */
3781
+ __publicField(this, "recovery", null);
3782
+ /** Retry the failed request twice immediately, then this long after each failed recovery response. */
3783
+ __publicField(this, "recoveryRetryInterval", 3e4);
3784
+ __publicField(this, "recoveryImmediateRetries", 2);
4315
3785
  /** LocalStorage key for persisting logins */
4316
3786
  __publicField(this, "localStorageKey", "datalynk-token");
3787
+ __publicField(this, "tokenStorageListener", null);
4317
3788
  /** Pending requests cache */
4318
3789
  __publicField(this, "pending", {});
4319
3790
  /** Helpers */
@@ -4344,18 +3815,33 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4344
3815
  /** Client library version */
4345
3816
  __publicField(this, "version", version);
4346
3817
  __publicField(this, "onlineOverride", false);
4347
- __publicField(this, "online$", new BehaviorSubject(typeof navigator == "undefined" || typeof navigator.onLine == "undefined" ? true : navigator.onLine));
3818
+ __publicField(this, "initialOnline", typeof navigator == "undefined" || typeof navigator.onLine == "undefined" ? true : navigator.onLine);
3819
+ /**
3820
+ * Detailed API connection state.
3821
+ *
3822
+ * Subscribe to this when callers need to distinguish physical/network
3823
+ * offline state from authentication failure or server/API unavailability.
3824
+ */
3825
+ __publicField(this, "status$", new BehaviorSubject(this.initialOnline ? "online" : "offline"));
3826
+ /**
3827
+ * Backwards-compatible boolean connection state.
3828
+ *
3829
+ * `false` includes `offline`, `unauthorized`, and `unavailable` states.
3830
+ */
3831
+ __publicField(this, "online$", new BehaviorSubject(this.initialOnline));
4348
3832
  /** API Session token */
4349
3833
  __publicField(this, "token$", new BehaviorSubject(void 0));
4350
3834
  var _a, _b;
4351
3835
  this.origin = origin;
4352
3836
  this.url = `${new URL(origin).origin}/api/`;
3837
+ const development = this.isDevelopmentEnvironment(origin);
4353
3838
  this.options = {
4354
3839
  manifest: {},
4355
3840
  name: typeof document != "undefined" ? document.title : "Datalynk",
4356
3841
  offline: [],
4357
3842
  origin: typeof location !== "undefined" ? location.host : "Unknown",
4358
3843
  saveSession: true,
3844
+ watchTokenExpiry: true,
4359
3845
  serviceWorker: "/service.worker.mjs",
4360
3846
  ...options,
4361
3847
  webrtc: {
@@ -4365,14 +3851,28 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4365
3851
  ...options.webrtc || {}
4366
3852
  }
4367
3853
  };
4368
- if (this.options.saveSession) {
4369
- if (typeof localStorage == "undefined") return;
3854
+ if (development) this.options.watchTokenExpiry = false;
3855
+ if (this.options.saveSession && typeof localStorage != "undefined") {
4370
3856
  this.token = localStorage.getItem(this.localStorageKey) || null;
4371
3857
  this.token$.pipe(distinctUntilChanged()).subscribe((token) => {
4372
3858
  if (token) localStorage.setItem(this.localStorageKey, token);
4373
3859
  else localStorage.removeItem(this.localStorageKey);
4374
3860
  });
3861
+ if (typeof window != "undefined") {
3862
+ this.tokenStorageListener = (event) => {
3863
+ if (event.storageArea === localStorage && event.key == null) {
3864
+ this.token = null;
3865
+ return;
3866
+ }
3867
+ if (event.key !== this.localStorageKey || event.newValue === this.token) return;
3868
+ if (event.newValue && this.canAdoptToken(event.newValue, this.token)) this.token = event.newValue;
3869
+ else if (event.newValue == null) this.token = null;
3870
+ };
3871
+ window.addEventListener("storage", this.tokenStorageListener);
3872
+ }
4375
3873
  }
3874
+ if (this.options.watchTokenExpiry)
3875
+ this.token$.pipe(distinctUntilChanged()).subscribe((token) => this.scheduleTokenExpiry(token));
4376
3876
  this.socket = new Socket(this, { url: options.socket });
4377
3877
  this.gps = new Gps(this, options.gps);
4378
3878
  this.auth = new Auth(this);
@@ -4395,12 +3895,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4395
3895
  });
4396
3896
  }
4397
3897
  if (typeof window !== "undefined") {
4398
- const handleOffline = (state = this.offline) => {
4399
- if (this.online != state) this.online$.next(state);
4400
- };
4401
- window.addEventListener("online", () => handleOffline(true));
4402
- window.addEventListener("offline", () => handleOffline(false));
3898
+ window.addEventListener("online", () => this.checkConnection());
3899
+ window.addEventListener("offline", () => this.setConnectionStatus("offline"));
4403
3900
  this.online$.subscribe(() => this.offlineBanner());
3901
+ this.startHeartbeat();
4404
3902
  }
4405
3903
  if ((_a = this.options.offline) == null ? void 0 : _a.length) {
4406
3904
  this.pwa.setup();
@@ -4433,26 +3931,50 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4433
3931
  /** Get session info from JWT payload */
4434
3932
  get jwtPayload() {
4435
3933
  if (!this.token) return null;
4436
- return decodeJwt(this.token);
3934
+ try {
3935
+ return decodeJwt(this.token);
3936
+ } catch {
3937
+ return null;
3938
+ }
3939
+ }
3940
+ /** Current detailed Datalynk API connection state. */
3941
+ get status() {
3942
+ return this.status$.getValue();
4437
3943
  }
4438
- /** Check if we are connected */
3944
+ /**
3945
+ * Whether normal Datalynk network requests are currently available.
3946
+ *
3947
+ * This becomes `false` for network outages, rejected/expired sessions, and
3948
+ * request-owned server recovery. It therefore describes Datalynk
3949
+ * availability rather than only `navigator.onLine`.
3950
+ */
4439
3951
  get online() {
4440
3952
  return this.online$.getValue();
4441
3953
  }
3954
+ /**
3955
+ * Whether normal Datalynk API access is currently unavailable.
3956
+ *
3957
+ * This is the inverse of {@link online}. It can be `true` while the browser
3958
+ * still has Internet access, for example during MySQL/HTTP 5xx recovery.
3959
+ */
4442
3960
  get offline() {
4443
3961
  return !this.online;
4444
3962
  }
4445
- /** Override connection status */
3963
+ /**
3964
+ * Override the boolean connection state.
3965
+ *
3966
+ * Set `true` or `false` to force the corresponding state. Set `null` to
3967
+ * remove the override and resume normal connection checking. This is a
3968
+ * manual override and can supersede the current recovery state, so normal
3969
+ * applications should generally observe {@link status} instead of forcing it.
3970
+ */
4446
3971
  set online(value) {
4447
3972
  if (value == null) {
4448
3973
  this.onlineOverride = false;
4449
- this.online$.next(typeof navigator == "undefined" ? true : navigator.onLine);
3974
+ this.checkConnection();
4450
3975
  } else {
4451
3976
  this.onlineOverride = true;
4452
- if (value == this.online) return;
4453
- this.online$.next(value);
4454
- if (value) this.startHeartbeat();
4455
- else this.stopHeartbeat();
3977
+ this.setConnectionStatus(value ? "online" : "offline");
4456
3978
  }
4457
3979
  }
4458
3980
  /** Logged in spoke */
@@ -4464,56 +3986,285 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4464
3986
  return this.token$.getValue();
4465
3987
  }
4466
3988
  set token(token) {
3989
+ if (this.recovery && token !== this.recovery.token)
3990
+ this.cancelRecovery(errorFromCode(401, "Session changed during API recovery"));
3991
+ if (token && this.isTokenExpired(token)) {
3992
+ if (this.adoptStoredToken(token)) return;
3993
+ this.authenticationInvalid = true;
3994
+ this.token$.next(null);
3995
+ this.setConnectionStatus("unauthorized");
3996
+ return;
3997
+ }
3998
+ this.authenticationInvalid = false;
4467
3999
  this.token$.next(token);
4000
+ if (token && !this.online && !this.recovery) {
4001
+ const browserOnline = typeof navigator == "undefined" || typeof navigator.onLine == "undefined" || navigator.onLine;
4002
+ this.setConnectionStatus(browserOnline ? "online" : "offline");
4003
+ }
4468
4004
  }
4469
- _request(req, options = {}) {
4005
+ async _request(req, options = {}) {
4006
+ if (this.recovery) throw errorFromCode(503, "Datalynk is unavailable");
4007
+ const retryRequest = deepCopy(req);
4008
+ const retryOptions = { ...options };
4470
4009
  const token = options.token || this.token;
4471
- return fetch(this.url, {
4472
- method: "POST",
4473
- headers: clean({
4474
- Authorization: token ? `Bearer ${token}` : void 0,
4475
- "Content-Type": "application/json",
4476
- "X-Date-Return-Format": this.options.legacyDates ? void 0 : "ISO8601"
4477
- }),
4478
- body: JSON.stringify(_Api.translateTokens(req))
4479
- }).then(async (resp) => {
4480
- const warning = resp.headers["X-Warning"];
4481
- if (warning) console.warn(warning);
4482
- const banner = resp.headers["X-User-Notice"];
4483
- if (banner) {
4484
- createBanner(banner);
4485
- setTimeout(() => removeBanner(), 1e4);
4010
+ try {
4011
+ return await this._requestOnce(req, options, false);
4012
+ } catch (error) {
4013
+ if (!this.isRecoverableResponseError(error)) throw error;
4014
+ this.beginRecovery(retryRequest, retryOptions, token);
4015
+ throw error;
4016
+ }
4017
+ }
4018
+ /** Execute exactly one HTTP API attempt. Recovery uses this directly to avoid recursive retry loops. */
4019
+ async _requestOnce(req, options = {}, recoveryAttempt = false) {
4020
+ const token = options.token || this.token;
4021
+ if (token && this.isTokenExpired(token)) {
4022
+ this.markUnauthorized(token);
4023
+ throw errorFromCode(401, "Session token expired");
4024
+ }
4025
+ let resp;
4026
+ try {
4027
+ resp = await fetch(this.url, {
4028
+ method: "POST",
4029
+ headers: clean({
4030
+ Authorization: token ? `Bearer ${token}` : void 0,
4031
+ "Content-Type": "application/json",
4032
+ "X-Date-Return-Format": this.options.legacyDates ? void 0 : "ISO8601"
4033
+ }),
4034
+ body: JSON.stringify(_Api.translateTokens(req))
4035
+ });
4036
+ } catch (error) {
4037
+ this.setConnectionStatus("offline");
4038
+ throw error;
4039
+ }
4040
+ const warning = resp.headers.get("X-Warning");
4041
+ if (warning) console.warn(warning);
4042
+ const banner = resp.headers.get("X-User-Notice");
4043
+ if (banner) {
4044
+ createBanner(banner);
4045
+ setTimeout(() => removeBanner(), 1e4);
4046
+ }
4047
+ const body = await resp.text();
4048
+ let data;
4049
+ try {
4050
+ data = JSON.parse(body);
4051
+ } catch {
4052
+ throw new UnexpectedApiResponseError(resp, body);
4053
+ }
4054
+ if (resp.status === 401) this.markUnauthorized(token);
4055
+ if (!resp.ok || (data == null ? void 0 : data.error)) {
4056
+ const error = Object.assign(errorFromCode(resp.status, data == null ? void 0 : data.error), data);
4057
+ Object.defineProperty(error, "response", { value: resp, configurable: true });
4058
+ if (!recoveryAttempt && resp.status < 500 && resp.status !== 401 && !this.isMysqlError(error))
4059
+ this.setConnectionStatus("online");
4060
+ throw error;
4061
+ }
4062
+ if (!options.raw) data = _Api.translateTokens(data);
4063
+ if (!recoveryAttempt) this.setConnectionStatus("online");
4064
+ return data;
4065
+ }
4066
+ /** Only server-response failures own global recovery; auth and ordinary 4xx errors do not. */
4067
+ isRecoverableResponseError(error) {
4068
+ var _a;
4069
+ if (error instanceof UnexpectedApiResponseError) return true;
4070
+ if ((error == null ? void 0 : error.code) === 401) return false;
4071
+ const status = ((_a = error == null ? void 0 : error.response) == null ? void 0 : _a.status) ?? (error == null ? void 0 : error.code);
4072
+ return status >= 500 || this.isMysqlError(error);
4073
+ }
4074
+ isMysqlError(error) {
4075
+ const message = String((error == null ? void 0 : error.error) ?? (error == null ? void 0 : error.message) ?? "");
4076
+ return /\bSQLSTATE\[[A-Z0-9]+\]/i.test(message);
4077
+ }
4078
+ /**
4079
+ * A returned server failure immediately makes the client unavailable. The exact
4080
+ * failed request is then retried twice back-to-back. After that, retries are
4081
+ * scheduled 30 seconds after each completed failed attempt, never with overlap.
4082
+ */
4083
+ beginRecovery(req, options, token) {
4084
+ if (this.recovery) return;
4085
+ this.setConnectionStatus("unavailable");
4086
+ this.recovery = {
4087
+ request: deepCopy(req),
4088
+ options: { ...options },
4089
+ token,
4090
+ timer: null,
4091
+ running: false
4092
+ };
4093
+ void this.runImmediateRecovery();
4094
+ }
4095
+ async runImmediateRecovery() {
4096
+ for (let attempt = 0; attempt < this.recoveryImmediateRetries; attempt++) {
4097
+ const result = await this.tryRecoveryOnce();
4098
+ if (result.ok) {
4099
+ this.finishRecovery();
4100
+ return;
4486
4101
  }
4487
- this.online = true;
4488
- let data = JSONAttemptParse(await resp.text());
4489
- if (!resp.ok || (data == null ? void 0 : data.error)) throw Object.assign(errorFromCode(resp.status, data.error), data);
4490
- if (!options.raw) data = _Api.translateTokens(data);
4491
- return data;
4492
- });
4102
+ if (!this.recovery || this.status === "unauthorized") return;
4103
+ }
4104
+ if (this.recovery) this.scheduleRecovery();
4105
+ }
4106
+ async tryRecoveryOnce() {
4107
+ const recovery = this.recovery;
4108
+ if (!recovery) return { ok: false, error: errorFromCode(503, "API recovery was cancelled") };
4109
+ if (recovery.running) return { ok: false, error: errorFromCode(503, "API recovery request is already running") };
4110
+ if (recovery.token !== (recovery.options.token || this.token)) {
4111
+ const error = errorFromCode(401, "Session changed during API recovery");
4112
+ this.cancelRecovery(error);
4113
+ return { ok: false, error };
4114
+ }
4115
+ recovery.running = true;
4116
+ try {
4117
+ const value = await this._requestOnce(
4118
+ deepCopy(recovery.request),
4119
+ { ...recovery.options, token: recovery.token || void 0 },
4120
+ true
4121
+ );
4122
+ return { ok: true, value };
4123
+ } catch (error) {
4124
+ return { ok: false, error };
4125
+ } finally {
4126
+ if (this.recovery === recovery) recovery.running = false;
4127
+ }
4128
+ }
4129
+ scheduleRecovery() {
4130
+ const recovery = this.recovery;
4131
+ if (!recovery) return;
4132
+ if (recovery.timer) clearTimeout(recovery.timer);
4133
+ recovery.timer = setTimeout(async () => {
4134
+ const current = this.recovery;
4135
+ if (!current || current !== recovery) return;
4136
+ current.timer = null;
4137
+ const result = await this.tryRecoveryOnce();
4138
+ if (result.ok) {
4139
+ this.finishRecovery();
4140
+ return;
4141
+ }
4142
+ if (this.recovery && this.status !== "unauthorized") this.scheduleRecovery();
4143
+ }, this.recoveryRetryInterval);
4144
+ }
4145
+ finishRecovery() {
4146
+ const recovery = this.recovery;
4147
+ if (!recovery) return;
4148
+ if (recovery.timer) clearTimeout(recovery.timer);
4149
+ this.recovery = null;
4150
+ this.setConnectionStatus("online");
4151
+ }
4152
+ cancelRecovery(_error) {
4153
+ const recovery = this.recovery;
4154
+ if (!recovery) return;
4155
+ if (recovery.timer) clearTimeout(recovery.timer);
4156
+ this.recovery = null;
4493
4157
  }
4494
4158
  async checkConnection() {
4159
+ if (this.recovery) return;
4495
4160
  if (typeof navigator != "undefined" && !navigator.onLine) {
4496
- this.online$.next(false);
4161
+ this.setConnectionStatus("offline");
4497
4162
  return;
4498
4163
  }
4499
4164
  if (this.onlineOverride) return;
4165
+ if (this.authenticationInvalid) {
4166
+ this.setConnectionStatus("unauthorized");
4167
+ return;
4168
+ }
4169
+ if (this.token && this.isTokenExpired(this.token)) {
4170
+ this.markUnauthorized(this.token);
4171
+ return;
4172
+ }
4500
4173
  const controller = new AbortController();
4501
4174
  const timeout = setTimeout(() => controller.abort(), this.heartbeat.timeout);
4502
4175
  try {
4503
4176
  const response = await fetch(this.url + this.heartbeat.target, { signal: controller.signal });
4504
- this.online$.next(response.ok);
4177
+ if (this.recovery) return;
4178
+ this.setConnectionStatus(response.ok ? "online" : "unavailable");
4505
4179
  } catch (error) {
4506
- this.online$.next(false);
4180
+ if (!this.recovery) this.setConnectionStatus("offline");
4507
4181
  } finally {
4508
4182
  clearTimeout(timeout);
4509
4183
  }
4510
4184
  }
4185
+ isTokenExpired(token) {
4186
+ var _a;
4187
+ try {
4188
+ return (((_a = decodeJwt(token)) == null ? void 0 : _a.exp) ?? 0) * 1e3 <= Date.now();
4189
+ } catch {
4190
+ return true;
4191
+ }
4192
+ }
4193
+ isDevelopmentEnvironment(origin) {
4194
+ var _a;
4195
+ const isDevHost = (hostname) => {
4196
+ hostname = hostname.toLowerCase();
4197
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]" || hostname.endsWith(".datalynk") || hostname.endsWith(".test");
4198
+ };
4199
+ const configuredDev = typeof window != "undefined" && ((_a = window.dojoConfig) == null ? void 0 : _a.isDev) === true;
4200
+ const pageHost = typeof location == "undefined" ? "" : location.hostname;
4201
+ return configuredDev || isDevHost(new URL(origin).hostname) || !!pageHost && isDevHost(pageHost);
4202
+ }
4203
+ canAdoptToken(candidate, rejected) {
4204
+ if (!candidate || candidate === rejected || this.isTokenExpired(candidate)) return false;
4205
+ try {
4206
+ const next = decodeJwt(candidate);
4207
+ const previous = rejected ? decodeJwt(rejected) : null;
4208
+ if ((previous == null ? void 0 : previous.realm) && (next == null ? void 0 : next.realm) && previous.realm !== next.realm) return false;
4209
+ const previousUser = (previous == null ? void 0 : previous.uid) ?? (previous == null ? void 0 : previous.sub);
4210
+ const nextUser = (next == null ? void 0 : next.uid) ?? (next == null ? void 0 : next.sub);
4211
+ return previousUser == null || String(previousUser) === String(nextUser);
4212
+ } catch {
4213
+ return false;
4214
+ }
4215
+ }
4216
+ /** Adopt a newer canonical token written by another auth path before expiring this session. */
4217
+ adoptStoredToken(rejected) {
4218
+ var _a;
4219
+ if (!((_a = this.options) == null ? void 0 : _a.saveSession) || typeof localStorage == "undefined") return false;
4220
+ const stored = localStorage.getItem(this.localStorageKey);
4221
+ if (!stored || !this.canAdoptToken(stored, rejected)) return false;
4222
+ this.token = stored;
4223
+ return true;
4224
+ }
4225
+ scheduleTokenExpiry(token) {
4226
+ var _a, _b, _c;
4227
+ if (this.tokenExpiryTimeout) clearTimeout(this.tokenExpiryTimeout);
4228
+ this.tokenExpiryTimeout = null;
4229
+ if (!token) return;
4230
+ let expiresAt = 0;
4231
+ try {
4232
+ expiresAt = (((_a = decodeJwt(token)) == null ? void 0 : _a.exp) ?? 0) * 1e3;
4233
+ } catch {
4234
+ if (!this.adoptStoredToken(token)) this.markUnauthorized(token);
4235
+ return;
4236
+ }
4237
+ const delay = expiresAt - Date.now();
4238
+ if (delay <= 0) {
4239
+ if (!this.adoptStoredToken(token)) this.markUnauthorized(token);
4240
+ return;
4241
+ }
4242
+ this.tokenExpiryTimeout = setTimeout(() => {
4243
+ if (delay > 2147e6) this.scheduleTokenExpiry(token);
4244
+ else if (!this.adoptStoredToken(token)) this.markUnauthorized(token);
4245
+ }, Math.min(delay, 2147e6));
4246
+ (_c = (_b = this.tokenExpiryTimeout) == null ? void 0 : _b.unref) == null ? void 0 : _c.call(_b);
4247
+ }
4248
+ markUnauthorized(token) {
4249
+ if (token && token !== this.token) return;
4250
+ if (this.adoptStoredToken(token)) return;
4251
+ this.authenticationInvalid = true;
4252
+ this.cancelRecovery(errorFromCode(401, "Session expired"));
4253
+ if (this.token != null) this.token$.next(null);
4254
+ this.setConnectionStatus("unauthorized");
4255
+ }
4256
+ setConnectionStatus(status) {
4257
+ if (this.status !== status) this.status$.next(status);
4258
+ const online = status === "online";
4259
+ if (this.online !== online) this.online$.next(online);
4260
+ }
4511
4261
  offlineBanner() {
4512
4262
  if (this.options.offlineBanner === false || typeof document == "undefined") return;
4513
- if (this.online) {
4263
+ if (this.status === "online") {
4514
4264
  removeBanner("datalynk-offline-banner");
4515
4265
  } else {
4516
- createBanner("⚠️ You are offline, please reconnect to sync changes", {
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, {
4517
4268
  id: "datalynk-offline-banner",
4518
4269
  position: this.options.offlineBanner === "top" ? "top" : "bottom"
4519
4270
  });
@@ -4625,9 +4376,13 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4625
4376
  var _a, _b;
4626
4377
  data = typeof data == "string" ? { [data]: {} } : data;
4627
4378
  let key = JSON.stringify(data);
4628
- if (this.offline) {
4629
- (_b = (_a = this.database) == null ? void 0 : _a.table("pending")) == null ? void 0 : _b.add(data, key);
4630
- return Promise.resolve();
4379
+ if (this.offline && typeof navigator != "undefined") {
4380
+ if (this.status === "unauthorized") return Promise.reject(errorFromCode(401, "Session expired"));
4381
+ if (options.offline) {
4382
+ (_b = (_a = this.database) == null ? void 0 : _a.table("pending")) == null ? void 0 : _b.add(data, key);
4383
+ return Promise.resolve();
4384
+ }
4385
+ return Promise.reject(errorFromCode(503, this.status === "unavailable" ? "Datalynk is unavailable" : "You are offline"));
4631
4386
  }
4632
4387
  if (options.noOptimize) {
4633
4388
  return new Promise((res, rej) => {
@@ -4638,7 +4393,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4638
4393
  }
4639
4394
  if (!this.pending[key]) {
4640
4395
  this.pending[key] = new Promise((res, rej) => this.bundle.push({ data, res, rej }));
4641
- this.pending[key].catch().then(() => delete this.pending[key]);
4396
+ this.pending[key].then(() => delete this.pending[key], () => delete this.pending[key]);
4642
4397
  if (!this.bundleOngoing) {
4643
4398
  this.bundleOngoing = true;
4644
4399
  setTimeout(() => {
@@ -4648,7 +4403,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4648
4403
  data = originalBundle.map((row) => row.data);
4649
4404
  this._request(data, options).then((resp) => {
4650
4405
  if (!(resp instanceof Array)) resp = [resp];
4651
- resp.forEach((row, i) => (row == null ? void 0 : row.error) ? originalBundle[i].rej(row.error) : originalBundle[i].res(row));
4406
+ originalBundle.forEach((request, index) => {
4407
+ const row = resp[index];
4408
+ (row == null ? void 0 : row.error) ? request.rej(row.error) : request.res(row);
4409
+ });
4652
4410
  }).catch((err) => originalBundle.forEach((req) => req.rej(err)));
4653
4411
  }, this.options.bundleTime);
4654
4412
  }
@@ -4688,6 +4446,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4688
4446
  exports2.Slice = Slice;
4689
4447
  exports2.Socket = Socket;
4690
4448
  exports2.Superuser = Superuser;
4449
+ exports2.UnexpectedApiResponseError = UnexpectedApiResponseError;
4691
4450
  exports2.getTheme = getTheme;
4692
4451
  Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
4693
4452
  });