@auxilium/datalynk-client 1.4.0 → 1.5.0

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.mjs CHANGED
@@ -1,898 +1,272 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
- var __defProp2 = Object.defineProperty;
5
- var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6
- var __publicField2 = (obj, key, value) => __defNormalProp2(obj, typeof key !== "symbol" ? key + "" : key, value);
7
- function clean(obj, undefinedOnly = false) {
8
- if (obj == null) throw new Error("Cannot clean a NULL value");
9
- if (Array.isArray(obj)) {
10
- obj = obj.filter((o) => undefinedOnly ? o !== void 0 : o != null);
11
- } else {
12
- Object.entries(obj).forEach(([key, value]) => {
13
- if (undefinedOnly && value === void 0 || !undefinedOnly && value == null) delete obj[key];
14
- });
15
- }
16
- return obj;
4
+ function clean(value, undefinedOnly = false) {
5
+ if (value == null) throw new Error("Cannot clean a NULL value");
6
+ if (Array.isArray(value)) return value.filter((item) => undefinedOnly ? item !== void 0 : item != null);
7
+ Object.entries(value).forEach(([key, item]) => {
8
+ if (undefinedOnly && item === void 0 || !undefinedOnly && item == null) delete value[key];
9
+ });
10
+ return value;
17
11
  }
18
12
  function deepCopy(value) {
19
13
  try {
20
14
  return structuredClone(value);
21
15
  } catch {
22
- return JSON.parse(JSONSanitize(value));
16
+ const seen = [];
17
+ return JSON.parse(JSON.stringify(value, (_key, item) => {
18
+ if (typeof item === "object" && item !== null) {
19
+ if (seen.includes(item)) return "[Circular]";
20
+ seen.push(item);
21
+ }
22
+ return item;
23
+ }));
23
24
  }
24
25
  }
25
- function dotNotation(obj, prop, set) {
26
- if (obj == null || !prop) return void 0;
27
- return prop.split(/[.[\]]/g).filter((prop2) => prop2.length).reduce((obj2, prop2, i, arr) => {
28
- if (prop2[0] == '"' || prop2[0] == "'") prop2 = prop2.slice(1, -1);
29
- if (!(obj2 == null ? void 0 : obj2.hasOwnProperty(prop2))) {
30
- return void 0;
31
- }
32
- return obj2[prop2];
33
- }, obj);
34
- }
35
- function isEqual(a, b) {
36
- const ta = typeof a, tb = typeof b;
37
- if (ta != "object" || a == null || (tb != "object" || b == null))
38
- return ta == "function" && tb == "function" ? a.toString() == b.toString() : a === b;
39
- const keys = Object.keys(a);
40
- if (keys.length != Object.keys(b).length) return false;
41
- return Object.keys(a).every((key) => isEqual(a[key], b[key]));
42
- }
43
- function JSONAttemptParse(json) {
26
+ function JSONAttemptParse(value) {
44
27
  try {
45
- return JSON.parse(json);
28
+ return JSON.parse(value);
46
29
  } catch {
47
- return json;
48
- }
49
- }
50
- function JSONSanitize(obj, space) {
51
- const cache = [];
52
- return JSON.stringify(obj, (key, value) => {
53
- if (typeof value === "object" && value !== null) {
54
- if (cache.includes(value)) return "[Circular]";
55
- cache.push(value);
56
- }
57
30
  return value;
58
- }, space);
59
- }
60
- class ASet extends Array {
61
- /** Number of elements in set */
62
- get size() {
63
- return this.length;
64
- }
65
- /**
66
- * Array to create set from, duplicate values will be removed
67
- * @param {T[]} elements Elements which will be added to set
68
- */
69
- constructor(elements = []) {
70
- super();
71
- if (!!(elements == null ? void 0 : elements["forEach"]))
72
- elements.forEach((el) => this.add(el));
73
- }
74
- /**
75
- * Add elements to set if unique
76
- * @param items
77
- */
78
- add(...items) {
79
- items.filter((el) => !this.has(el)).forEach((el) => this.push(el));
80
- return this;
81
- }
82
- /**
83
- * Remove all elements
84
- */
85
- clear() {
86
- this.splice(0, this.length);
87
- return this;
88
- }
89
- /**
90
- * Delete elements from set
91
- * @param items Elements that will be deleted
92
- */
93
- delete(...items) {
94
- items.forEach((el) => {
95
- const index = this.indexOf(el);
96
- if (index != -1) this.splice(index, 1);
97
- });
98
- return this;
99
- }
100
- /**
101
- * Create list of elements this set has which the comparison set does not
102
- * @param {ASet<T>} set Set to compare against
103
- * @return {ASet<T>} Different elements
104
- */
105
- difference(set) {
106
- return new ASet(this.filter((el) => !set.has(el)));
107
- }
108
- /**
109
- * Check if set includes element
110
- * @param {T} el Element to look for
111
- * @return {boolean} True if element was found, false otherwise
112
- */
113
- has(el) {
114
- return this.indexOf(el) != -1;
115
- }
116
- /**
117
- * Find index number of element, or -1 if it doesn't exist. Matches by equality not reference
118
- *
119
- * @param {T} search Element to find
120
- * @param {number} fromIndex Starting index position
121
- * @return {number} Element index number or -1 if missing
122
- */
123
- indexOf(search2, fromIndex) {
124
- return super.findIndex((el) => isEqual(el, search2), fromIndex);
125
- }
126
- /**
127
- * Create list of elements this set has in common with the comparison set
128
- * @param {ASet<T>} set Set to compare against
129
- * @return {boolean} Set of common elements
130
- */
131
- intersection(set) {
132
- return new ASet(this.filter((el) => set.has(el)));
133
- }
134
- /**
135
- * Check if this set has no elements in common with the comparison set
136
- * @param {ASet<T>} set Set to compare against
137
- * @return {boolean} True if nothing in common, false otherwise
138
- */
139
- isDisjointFrom(set) {
140
- return this.intersection(set).size == 0;
141
- }
142
- /**
143
- * Check if all elements in this set are included in the comparison set
144
- * @param {ASet<T>} set Set to compare against
145
- * @return {boolean} True if all elements are included, false otherwise
146
- */
147
- isSubsetOf(set) {
148
- return this.findIndex((el) => !set.has(el)) == -1;
149
- }
150
- /**
151
- * Check if all elements from comparison set are included in this set
152
- * @param {ASet<T>} set Set to compare against
153
- * @return {boolean} True if all elements are included, false otherwise
154
- */
155
- isSuperset(set) {
156
- return set.findIndex((el) => !this.has(el)) == -1;
157
- }
158
- /**
159
- * Create list of elements that are only in one set but not both (XOR)
160
- * @param {ASet<T>} set Set to compare against
161
- * @return {ASet<T>} New set of unique elements
162
- */
163
- symmetricDifference(set) {
164
- return new ASet([...this.difference(set), ...set.difference(this)]);
165
- }
166
- /**
167
- * Create joined list of elements included in this & the comparison set
168
- * @param {ASet<T>} set Set join
169
- * @return {ASet<T>} New set of both previous sets combined
170
- */
171
- union(set) {
172
- return new ASet([...this, ...set]);
173
31
  }
174
32
  }
175
- function sortByProp(prop, reverse = false) {
176
- return function(a, b) {
177
- const aVal = dotNotation(a, prop);
178
- const bVal = dotNotation(b, prop);
179
- if (typeof aVal == "number" && typeof bVal == "number")
180
- return (reverse ? -1 : 1) * (aVal - bVal);
181
- if (aVal > bVal) return reverse ? -1 : 1;
182
- if (aVal < bVal) return reverse ? 1 : -1;
183
- return 0;
184
- };
185
- }
186
33
  function makeArray(value) {
187
34
  return Array.isArray(value) ? value : [value];
188
35
  }
189
- class Database {
190
- constructor(database, tables, version2) {
191
- __publicField2(this, "connection");
192
- __publicField2(this, "tables");
193
- this.database = database;
194
- this.version = version2;
195
- this.connection = new Promise((resolve, reject) => {
196
- const req = indexedDB.open(this.database, this.version);
197
- this.tables = tables.map((t) => {
198
- t = typeof t == "object" ? t : { name: t };
199
- return { ...t, name: t.name.toString() };
200
- });
201
- const tableNames = new ASet(this.tables.map((t) => t.name));
202
- req.onerror = () => reject(req.error);
203
- req.onsuccess = () => {
204
- const db = req.result;
205
- if (tableNames.symmetricDifference(new ASet(Array.from(db.objectStoreNames))).length) {
206
- db.close();
207
- Object.assign(this, new Database(this.database, this.tables, db.version + 1));
208
- } else {
209
- this.version = db.version;
210
- resolve(db);
211
- }
212
- };
213
- req.onupgradeneeded = () => {
214
- const db = req.result;
215
- const existingTables = new ASet(Array.from(db.objectStoreNames));
216
- existingTables.difference(tableNames).forEach((name) => db.deleteObjectStore(name));
217
- tableNames.difference(existingTables).forEach((name) => db.createObjectStore(name));
218
- };
219
- });
220
- }
221
- includes(name) {
222
- return !!this.tables.find((t) => t.name == name.toString());
223
- }
224
- table(name) {
225
- return new Table(this, name.toString());
226
- }
36
+ function property(value, path) {
37
+ return path.split(".").reduce((current, key) => current == null ? void 0 : current[key], value);
227
38
  }
228
- class Table {
229
- constructor(database, name) {
230
- this.database = database;
231
- this.name = name;
232
- }
233
- async tx(table, fn2, readonly = false) {
234
- const db = await this.database.connection;
235
- const tx = db.transaction(table, readonly ? "readonly" : "readwrite");
236
- const store = tx.objectStore(table);
237
- return new Promise((resolve, reject) => {
238
- const request = fn2(store);
239
- request.onsuccess = () => resolve(request.result);
240
- request.onerror = () => reject(request.error);
241
- });
242
- }
243
- add(value, key) {
244
- return this.tx(this.name, (store) => store.add(value, key));
245
- }
246
- count() {
247
- return this.tx(this.name, (store) => store.count(), true);
248
- }
249
- put(key, value) {
250
- return this.tx(this.name, (store) => store.put(value, key));
251
- }
252
- getAll() {
253
- return this.tx(this.name, (store) => store.getAll(), true);
254
- }
255
- getAllKeys() {
256
- return this.tx(this.name, (store) => store.getAllKeys(), true);
257
- }
258
- get(key) {
259
- return this.tx(this.name, (store) => store.get(key), true);
260
- }
261
- delete(key) {
262
- return this.tx(this.name, (store) => store.delete(key));
263
- }
264
- clear() {
265
- return this.tx(this.name, (store) => store.clear());
266
- }
39
+ function sortByProp(path, reverse = false) {
40
+ return (a, b) => {
41
+ const aValue = property(a, path);
42
+ const bValue = property(b, path);
43
+ if (typeof aValue == "number" && typeof bValue == "number")
44
+ return (reverse ? -1 : 1) * (aValue - bValue);
45
+ if (aValue > bValue) return reverse ? -1 : 1;
46
+ if (aValue < bValue) return reverse ? 1 : -1;
47
+ return 0;
48
+ };
49
+ }
50
+ async function sleepWhile(condition, interval = 100) {
51
+ while (await condition()) await new Promise((resolve) => setTimeout(resolve, interval));
267
52
  }
268
53
  function contrast(background) {
269
- const exploded = background == null ? void 0 : background.match(background.length >= 6 ? /[0-9a-fA-F]{2}/g : /[0-9a-fA-F]/g);
270
- if (!exploded || (exploded == null ? void 0 : exploded.length) < 3) return "black";
271
- const [r, g, b] = exploded.map((hex) => parseInt(hex.length == 1 ? `${hex}${hex}` : hex, 16));
272
- const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
273
- return luminance > 0.5 ? "black" : "white";
54
+ const parts = background == null ? void 0 : background.match(background.length >= 6 ? /[0-9a-fA-F]{2}/g : /[0-9a-fA-F]/g);
55
+ if (!parts || parts.length < 3) return "black";
56
+ const [red, green, blue] = parts.map((hex) => parseInt(hex.length === 1 ? hex + hex : hex, 16));
57
+ return (0.299 * red + 0.587 * green + 0.114 * blue) / 255 > 0.5 ? "black" : "white";
274
58
  }
275
- const LETTER_LIST = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
276
- const NUMBER_LIST = "0123456789";
277
- const SYMBOL_LIST = "~`!@#$%^&*()_-+={[}]|\\:;\"'<,>.?/";
278
59
  function randomStringBuilder(length, letters = false, numbers = false, symbols = false) {
60
+ const LETTER_LIST = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
61
+ const NUMBER_LIST = "0123456789";
62
+ const SYMBOL_LIST = "~`!@#$%^&*()_-+={[}]|\\:;\"'<,>.?/";
279
63
  if (!letters && !numbers && !symbols) throw new Error("Must enable at least one: letters, numbers, symbols");
280
64
  return Array(length).fill(null).map(() => {
281
- let c;
65
+ let character;
282
66
  do {
283
67
  const type = ~~(Math.random() * 3);
284
- if (letters && type == 0) {
285
- c = LETTER_LIST[~~(Math.random() * LETTER_LIST.length)];
286
- } else if (numbers && type == 1) {
287
- c = NUMBER_LIST[~~(Math.random() * NUMBER_LIST.length)];
288
- } else if (symbols && type == 2) {
289
- c = SYMBOL_LIST[~~(Math.random() * SYMBOL_LIST.length)];
290
- }
291
- } while (!c);
292
- return c;
68
+ if (letters && type == 0) character = LETTER_LIST[~~(Math.random() * LETTER_LIST.length)];
69
+ else if (numbers && type == 1) character = NUMBER_LIST[~~(Math.random() * NUMBER_LIST.length)];
70
+ else if (symbols && type == 2) character = SYMBOL_LIST[~~(Math.random() * SYMBOL_LIST.length)];
71
+ } while (!character);
72
+ return character;
293
73
  }).join("");
294
74
  }
295
- class PromiseProgress extends Promise {
296
- constructor(executor) {
297
- super((resolve, reject) => executor(
298
- (value) => resolve(value),
299
- (reason) => reject(reason),
300
- (progress) => this.progress = progress
301
- ));
302
- __publicField2(this, "listeners", []);
303
- __publicField2(this, "_progress", 0);
304
- }
305
- get progress() {
306
- return this._progress;
307
- }
308
- set progress(p) {
309
- if (p == this._progress) return;
310
- this._progress = p;
311
- this.listeners.forEach((l) => l(p));
312
- }
313
- static from(promise) {
314
- if (promise instanceof PromiseProgress) return promise;
315
- return new PromiseProgress((res, rej) => promise.then((...args) => res(...args)).catch((...args) => rej(...args)));
316
- }
317
- from(promise) {
318
- const newPromise = PromiseProgress.from(promise);
319
- this.onProgress((p) => newPromise.progress = p);
320
- return newPromise;
321
- }
322
- onProgress(callback) {
323
- this.listeners.push(callback);
324
- return this;
325
- }
326
- then(res, rej) {
327
- const resp = super.then(res, rej);
328
- return this.from(resp);
329
- }
330
- catch(rej) {
331
- return this.from(super.catch(rej));
332
- }
333
- finally(res) {
334
- return this.from(super.finally(res));
335
- }
336
- }
337
- function sleep(ms) {
338
- return new Promise((res) => setTimeout(res, ms));
339
- }
340
- async function sleepWhile(fn2, checkInterval = 100) {
341
- while (await fn2()) await sleep(checkInterval);
342
- }
343
- class TypedEmitter {
344
- constructor() {
345
- __publicField2(this, "listeners", {});
346
- }
347
- static emit(event, ...args) {
348
- (this.listeners["*"] || []).forEach((l) => l(event, ...args));
349
- (this.listeners[event.toString()] || []).forEach((l) => l(...args));
350
- }
351
- static off(event, listener) {
352
- const e = event.toString();
353
- this.listeners[e] = (this.listeners[e] || []).filter((l) => l != listener);
354
- }
355
- static on(event, listener) {
356
- var _a;
357
- const e = event.toString();
358
- if (!this.listeners[e]) this.listeners[e] = [];
359
- (_a = this.listeners[e]) == null ? void 0 : _a.push(listener);
360
- return () => this.off(event, listener);
361
- }
362
- static once(event, listener) {
363
- return new Promise((res) => {
364
- const unsubscribe = this.on(event, (...args) => {
365
- res(args.length == 1 ? args[0] : args);
366
- if (listener) listener(...args);
367
- unsubscribe();
368
- });
369
- });
370
- }
371
- emit(event, ...args) {
372
- (this.listeners["*"] || []).forEach((l) => l(event, ...args));
373
- (this.listeners[event] || []).forEach((l) => l(...args));
374
- }
375
- off(event, listener) {
376
- this.listeners[event] = (this.listeners[event] || []).filter((l) => l != listener);
377
- }
378
- on(event, listener) {
379
- var _a;
380
- if (!this.listeners[event]) this.listeners[event] = [];
381
- (_a = this.listeners[event]) == null ? void 0 : _a.push(listener);
382
- return () => this.off(event, listener);
383
- }
384
- once(event, listener) {
385
- return new Promise((res) => {
386
- const unsubscribe = this.on(event, (...args) => {
387
- res(args.length == 1 ? args[0] : args);
388
- if (listener) listener(...args);
389
- unsubscribe();
390
- });
391
- });
392
- }
393
- }
394
- __publicField2(TypedEmitter, "listeners", {});
395
75
  class CustomError extends Error {
396
76
  constructor(message, code) {
397
77
  super(message);
398
- __publicField2(this, "_code");
78
+ __publicField(this, "_code");
399
79
  if (code != null) this._code = code;
400
80
  }
401
81
  get code() {
402
82
  return this._code || this.constructor.code;
403
83
  }
404
- set code(c) {
405
- this._code = c;
406
- }
407
- static from(err) {
408
- const code = Number(err.statusCode) ?? Number(err.code);
409
- const newErr = new this(err.message || err.toString());
410
- return Object.assign(newErr, {
411
- stack: err.stack,
412
- ...err,
413
- code: code ?? void 0
414
- });
415
- }
416
- static instanceof(err) {
417
- return err.constructor.code != void 0;
84
+ set code(code) {
85
+ this._code = code;
418
86
  }
419
87
  toString() {
420
88
  return this.message || super.toString();
421
89
  }
422
90
  }
423
- __publicField2(CustomError, "code", 500);
91
+ __publicField(CustomError, "code", 500);
424
92
  class BadRequestError extends CustomError {
425
93
  constructor(message = "Bad Request") {
426
94
  super(message);
427
95
  }
428
- static instanceof(err) {
429
- return err.constructor.code == this.code;
430
- }
431
96
  }
432
- __publicField2(BadRequestError, "code", 400);
97
+ __publicField(BadRequestError, "code", 400);
433
98
  class UnauthorizedError extends CustomError {
434
99
  constructor(message = "Unauthorized") {
435
100
  super(message);
436
101
  }
437
- static instanceof(err) {
438
- return err.constructor.code == this.code;
439
- }
440
102
  }
441
- __publicField2(UnauthorizedError, "code", 401);
103
+ __publicField(UnauthorizedError, "code", 401);
442
104
  class PaymentRequiredError extends CustomError {
443
105
  constructor(message = "Payment Required") {
444
106
  super(message);
445
107
  }
446
- static instanceof(err) {
447
- return err.constructor.code == this.code;
448
- }
449
108
  }
450
- __publicField2(PaymentRequiredError, "code", 402);
109
+ __publicField(PaymentRequiredError, "code", 402);
451
110
  class ForbiddenError extends CustomError {
452
111
  constructor(message = "Forbidden") {
453
112
  super(message);
454
113
  }
455
- static instanceof(err) {
456
- return err.constructor.code == this.code;
457
- }
458
114
  }
459
- __publicField2(ForbiddenError, "code", 403);
115
+ __publicField(ForbiddenError, "code", 403);
460
116
  class NotFoundError extends CustomError {
461
117
  constructor(message = "Not Found") {
462
118
  super(message);
463
119
  }
464
- static instanceof(err) {
465
- return err.constructor.code == this.code;
466
- }
467
120
  }
468
- __publicField2(NotFoundError, "code", 404);
121
+ __publicField(NotFoundError, "code", 404);
469
122
  class MethodNotAllowedError extends CustomError {
470
123
  constructor(message = "Method Not Allowed") {
471
124
  super(message);
472
125
  }
473
- static instanceof(err) {
474
- return err.constructor.code == this.code;
475
- }
476
126
  }
477
- __publicField2(MethodNotAllowedError, "code", 405);
127
+ __publicField(MethodNotAllowedError, "code", 405);
478
128
  class NotAcceptableError extends CustomError {
479
129
  constructor(message = "Not Acceptable") {
480
130
  super(message);
481
131
  }
482
- static instanceof(err) {
483
- return err.constructor.code == this.code;
484
- }
485
132
  }
486
- __publicField2(NotAcceptableError, "code", 406);
133
+ __publicField(NotAcceptableError, "code", 406);
487
134
  class InternalServerError extends CustomError {
488
135
  constructor(message = "Internal Server Error") {
489
136
  super(message);
490
137
  }
491
- static instanceof(err) {
492
- return err.constructor.code == this.code;
493
- }
494
138
  }
495
- __publicField2(InternalServerError, "code", 500);
139
+ __publicField(InternalServerError, "code", 500);
496
140
  class NotImplementedError extends CustomError {
497
141
  constructor(message = "Not Implemented") {
498
142
  super(message);
499
143
  }
500
- static instanceof(err) {
501
- return err.constructor.code == this.code;
502
- }
503
144
  }
504
- __publicField2(NotImplementedError, "code", 501);
145
+ __publicField(NotImplementedError, "code", 501);
505
146
  class BadGatewayError extends CustomError {
506
147
  constructor(message = "Bad Gateway") {
507
148
  super(message);
508
149
  }
509
- static instanceof(err) {
510
- return err.constructor.code == this.code;
511
- }
512
150
  }
513
- __publicField2(BadGatewayError, "code", 502);
151
+ __publicField(BadGatewayError, "code", 502);
514
152
  class ServiceUnavailableError extends CustomError {
515
153
  constructor(message = "Service Unavailable") {
516
154
  super(message);
517
155
  }
518
- static instanceof(err) {
519
- return err.constructor.code == this.code;
520
- }
521
156
  }
522
- __publicField2(ServiceUnavailableError, "code", 503);
157
+ __publicField(ServiceUnavailableError, "code", 503);
523
158
  class GatewayTimeoutError extends CustomError {
524
159
  constructor(message = "Gateway Timeout") {
525
160
  super(message);
526
161
  }
527
- static instanceof(err) {
528
- return err.constructor.code == this.code;
529
- }
530
162
  }
531
- __publicField2(GatewayTimeoutError, "code", 504);
163
+ __publicField(GatewayTimeoutError, "code", 504);
532
164
  function errorFromCode(code, message) {
533
- switch (code) {
534
- case 400:
535
- return new BadRequestError(message);
536
- case 401:
537
- return new UnauthorizedError(message);
538
- case 402:
539
- return new PaymentRequiredError(message);
540
- case 403:
541
- return new ForbiddenError(message);
542
- case 404:
543
- return new NotFoundError(message);
544
- case 405:
545
- return new MethodNotAllowedError(message);
546
- case 406:
547
- return new NotAcceptableError(message);
548
- case 500:
549
- return new InternalServerError(message);
550
- case 501:
551
- return new NotImplementedError(message);
552
- case 502:
553
- return new BadGatewayError(message);
554
- case 503:
555
- return new ServiceUnavailableError(message);
556
- case 504:
557
- return new GatewayTimeoutError(message);
558
- default:
559
- return new CustomError(message, code);
560
- }
561
- }
562
- class HttpResponse extends Response {
563
- constructor(resp, stream) {
564
- const body = [204, 205, 304].includes(resp.status) ? null : stream;
565
- super(body, {
566
- headers: resp.headers,
567
- status: resp.status,
568
- statusText: resp.statusText
569
- });
570
- __publicField2(this, "data");
571
- __publicField2(this, "ok");
572
- __publicField2(this, "redirected");
573
- __publicField2(this, "type");
574
- __publicField2(this, "url");
575
- this.ok = resp.ok;
576
- this.redirected = resp.redirected;
577
- this.type = resp.type;
578
- this.url = resp.url;
579
- }
165
+ const errors = {
166
+ 400: BadRequestError,
167
+ 401: UnauthorizedError,
168
+ 402: PaymentRequiredError,
169
+ 403: ForbiddenError,
170
+ 404: NotFoundError,
171
+ 405: MethodNotAllowedError,
172
+ 406: NotAcceptableError,
173
+ 500: InternalServerError,
174
+ 501: NotImplementedError,
175
+ 502: BadGatewayError,
176
+ 503: ServiceUnavailableError,
177
+ 504: GatewayTimeoutError
178
+ };
179
+ const ErrorType = errors[code];
180
+ return ErrorType ? new ErrorType(message) : new CustomError(message, code);
580
181
  }
581
- const _Http = class _Http2 {
582
- constructor(defaults = {}) {
583
- __publicField2(this, "interceptors", {});
584
- __publicField2(this, "headers", {});
585
- __publicField2(this, "url");
586
- this.url = defaults.url ?? null;
587
- this.headers = defaults.headers || {};
588
- if (defaults.interceptors) {
589
- defaults.interceptors.forEach((i) => _Http2.addInterceptor(i));
590
- }
591
- }
592
- static addInterceptor(fn2) {
593
- const key = Object.keys(_Http2.interceptors).length.toString();
594
- _Http2.interceptors[key] = fn2;
595
- return () => {
596
- _Http2.interceptors[key] = null;
597
- };
598
- }
599
- addInterceptor(fn2) {
600
- const key = Object.keys(this.interceptors).length.toString();
601
- this.interceptors[key] = fn2;
602
- return () => {
603
- this.interceptors[key] = null;
604
- };
605
- }
606
- request(opts = {}) {
607
- var _a;
608
- if (!this.url && !opts.url) throw new Error("URL needs to be set");
609
- let url = ((_a = opts.url) == null ? void 0 : _a.startsWith("http")) ? opts.url : (this.url || "") + (opts.url || "");
610
- url = url.replaceAll(/([^:]\/)\/+/g, "$1");
611
- if (opts.fragment) url.includes("#") ? url.replace(/#.*(\?|\n)/g, (match, arg1) => `#${opts.fragment}${arg1}`) : `${url}#${opts.fragment}`;
612
- if (opts.query) {
613
- const q = Array.isArray(opts.query) ? opts.query : Object.keys(opts.query).map((k) => ({ key: k, value: opts.query[k] }));
614
- url += (url.includes("?") ? "&" : "?") + q.map((q2) => `${q2.key}=${q2.value}`).join("&");
615
- }
616
- const headers = clean({
617
- "Content-Type": !opts.body ? void 0 : opts.body instanceof FormData ? "multipart/form-data" : "application/json",
618
- ..._Http2.headers,
619
- ...this.headers,
620
- ...opts.headers
621
- });
622
- if (typeof opts.body == "object" && opts.body != null && headers["Content-Type"] == "application/json")
623
- opts.body = JSON.stringify(opts.body);
624
- return new PromiseProgress((res, rej, prog) => {
625
- try {
626
- fetch(url, {
627
- headers,
628
- method: opts.method || (opts.body ? "POST" : "GET"),
629
- body: opts.body
630
- }).then(async (resp) => {
631
- var _a2, _b;
632
- for (let fn2 of [...Object.values(_Http2.interceptors), ...Object.values(this.interceptors)]) {
633
- await new Promise((res2) => fn2(resp, () => res2()));
634
- }
635
- const contentLength = resp.headers.get("Content-Length");
636
- const total = contentLength ? parseInt(contentLength, 10) : 0;
637
- let loaded = 0;
638
- const reader = (_a2 = resp.body) == null ? void 0 : _a2.getReader();
639
- const stream = new ReadableStream({
640
- start(controller) {
641
- function push() {
642
- reader == null ? void 0 : reader.read().then((event) => {
643
- if (event.done) return controller.close();
644
- loaded += event.value.byteLength;
645
- prog(loaded / total);
646
- controller.enqueue(event.value);
647
- push();
648
- }).catch((error) => controller.error(error));
649
- }
650
- push();
651
- }
652
- });
653
- resp = new HttpResponse(resp, stream);
654
- if (opts.decode !== false) {
655
- const content = (_b = resp.headers.get("Content-Type")) == null ? void 0 : _b.toLowerCase();
656
- if (content == null ? void 0 : content.includes("form")) resp.data = await resp.formData();
657
- else if (content == null ? void 0 : content.includes("json")) resp.data = await resp.json();
658
- else if (content == null ? void 0 : content.includes("text")) resp.data = await resp.text();
659
- else if (content == null ? void 0 : content.includes("application")) resp.data = await resp.blob();
660
- }
661
- if (resp.ok) res(resp);
662
- else rej(resp);
663
- }).catch((err) => rej(err));
664
- } catch (err) {
665
- rej(err);
666
- }
667
- });
668
- }
669
- };
670
- __publicField2(_Http, "interceptors", {});
671
- __publicField2(_Http, "headers", {});
672
182
  function decodeJwt(token) {
673
183
  const base64 = token.split(".")[1].replace(/-/g, "+").replace(/_/g, "/");
674
- return JSONAttemptParse(decodeURIComponent(atob(base64).split("").map(function(c) {
675
- return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
676
- }).join("")));
184
+ return JSONAttemptParse(decodeURIComponent(atob(base64).split("").map(
185
+ (character) => "%" + ("00" + character.charCodeAt(0).toString(16)).slice(-2)
186
+ ).join("")));
677
187
  }
678
- const CliEffects = {
679
- CLEAR: "\x1B[0m"
680
- };
681
- const CliForeground = {
682
- RED: "\x1B[31m",
683
- YELLOW: "\x1B[33m",
684
- BLUE: "\x1B[34m",
685
- LIGHT_GREY: "\x1B[37m"
686
- };
687
- const _Logger = class _Logger2 extends TypedEmitter {
688
- constructor(namespace) {
689
- super();
690
- this.namespace = namespace;
691
- }
692
- format(...text) {
693
- const now = /* @__PURE__ */ new Date();
694
- 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")}`;
695
- return `${timestamp}${this.namespace ? ` [${this.namespace}]` : ""} ${text.map((t) => typeof t == "string" ? t : JSONSanitize(t, 2)).join(" ")}`;
696
- }
697
- debug(...args) {
698
- if (_Logger2.LOG_LEVEL < 4) return;
699
- const str = this.format(...args);
700
- _Logger2.emit(4, str);
701
- console.debug(CliForeground.LIGHT_GREY + str + CliEffects.CLEAR);
702
- }
703
- log(...args) {
704
- if (_Logger2.LOG_LEVEL < 3) return;
705
- const str = this.format(...args);
706
- _Logger2.emit(3, str);
707
- console.log(CliEffects.CLEAR + str);
708
- }
709
- info(...args) {
710
- if (_Logger2.LOG_LEVEL < 2) return;
711
- const str = this.format(...args);
712
- _Logger2.emit(2, str);
713
- console.info(CliForeground.BLUE + str + CliEffects.CLEAR);
714
- }
715
- warn(...args) {
716
- if (_Logger2.LOG_LEVEL < 1) return;
717
- const str = this.format(...args);
718
- _Logger2.emit(1, str);
719
- console.warn(CliForeground.YELLOW + str + CliEffects.CLEAR);
720
- }
721
- error(...args) {
722
- if (_Logger2.LOG_LEVEL < 0) return;
723
- const str = this.format(...args);
724
- _Logger2.emit(0, str);
725
- console.error(CliForeground.RED + str + CliEffects.CLEAR);
726
- }
727
- };
728
- __publicField2(_Logger, "LOG_LEVEL", 4);
729
- var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
730
- var dist = {};
731
- var persist$1 = {};
732
- Object.defineProperty(persist$1, "__esModule", { value: true });
733
- persist$1.persist = persist$1.Persist = void 0;
734
- class Persist {
735
- /**
736
- * @param {string} key Primary key value will be stored under
737
- * @param {PersistOptions<T>} options Configure using {@link PersistOptions}
738
- */
739
- constructor(key, options = {}) {
740
- __publicField2(this, "key");
741
- __publicField2(this, "options");
742
- __publicField2(this, "storage");
743
- __publicField2(this, "watches", {});
744
- __publicField2(this, "_value");
745
- this.key = key;
746
- this.options = options;
747
- this.storage = options.storage || localStorage;
748
- this.load();
749
- }
750
- /** Current value or default if undefined */
751
- get value() {
752
- var _a;
753
- return this._value !== void 0 ? this._value : (_a = this.options) == null ? void 0 : _a.default;
754
- }
755
- /** Set value with proxy object wrapper to sync future changes */
756
- set value(v) {
757
- if (v == null || typeof v != "object")
758
- this._value = v;
759
- else
760
- this._value = new Proxy(v, {
761
- get: (target, p) => {
762
- const f = typeof target[p] == "function";
763
- if (!f)
764
- return target[p];
765
- return (...args) => {
766
- const value = target[p](...args);
767
- this.save();
768
- return value;
769
- };
770
- },
771
- set: (target, p, newValue) => {
772
- target[p] = newValue;
773
- this.save();
774
- return true;
188
+ class Database {
189
+ constructor(database, tables, version2) {
190
+ __publicField(this, "connection");
191
+ __publicField(this, "tables");
192
+ this.database = database;
193
+ this.version = version2;
194
+ this.tables = tables.map((table) => typeof table === "object" ? { ...table, name: table.name.toString() } : { name: table.toString() });
195
+ this.connection = new Promise((resolve, reject) => {
196
+ const request = indexedDB.open(this.database, this.version);
197
+ const requested = new Set(this.tables.map((table) => table.name));
198
+ request.onerror = () => reject(request.error);
199
+ request.onsuccess = () => {
200
+ const db = request.result;
201
+ const existing = new Set(Array.from(db.objectStoreNames));
202
+ if ([...requested].some((name) => !existing.has(name)) || [...existing].some((name) => !requested.has(name))) {
203
+ db.close();
204
+ const upgraded = new Database(this.database, this.tables, db.version + 1);
205
+ this.version = upgraded.version;
206
+ this.connection = upgraded.connection;
207
+ this.connection.then(resolve, reject);
208
+ } else {
209
+ this.version = db.version;
210
+ resolve(db);
775
211
  }
776
- });
777
- this.save();
212
+ };
213
+ request.onupgradeneeded = () => {
214
+ const db = request.result;
215
+ const existing = new Set(Array.from(db.objectStoreNames));
216
+ existing.forEach((name) => {
217
+ if (!requested.has(name)) db.deleteObjectStore(name);
218
+ });
219
+ requested.forEach((name) => {
220
+ if (!existing.has(name)) db.createObjectStore(name);
221
+ });
222
+ };
223
+ });
778
224
  }
779
- /** Notify listeners of change */
780
- notify(value) {
781
- Object.values(this.watches).forEach((watch) => watch(value));
225
+ includes(name) {
226
+ return this.tables.some((table) => table.name === name.toString());
782
227
  }
783
- /** Delete value from storage */
784
- clear() {
785
- this.storage.removeItem(this.key);
786
- }
787
- /** Save current value to storage */
788
- save() {
789
- if (this._value === void 0)
790
- this.clear();
791
- else
792
- this.storage.setItem(this.key, JSON.stringify(this._value));
793
- this.notify(this.value);
794
- }
795
- /** Load value from storage */
796
- load() {
797
- if (this.storage[this.key] != void 0) {
798
- let value = JSON.parse(this.storage.getItem(this.key));
799
- if (value != null && typeof value == "object" && this.options.type)
800
- value.__proto__ = this.options.type.prototype;
801
- this.value = value;
802
- } else
803
- this.value = this.options.default || void 0;
228
+ table(name) {
229
+ return new Table(this, name.toString());
804
230
  }
805
- /**
806
- * Callback function which is run when there are changes
807
- *
808
- * @param {(value: T) => any} fn Callback will run on each change; it's passed the next value & it's return is ignored
809
- * @returns {() => void} Function which will unsubscribe the watch/callback when called
810
- */
811
- watch(fn2) {
812
- const index = Object.keys(this.watches).length;
813
- this.watches[index] = fn2;
814
- return () => {
815
- delete this.watches[index];
816
- };
231
+ }
232
+ class Table {
233
+ constructor(database, name) {
234
+ this.database = database;
235
+ this.name = name;
817
236
  }
818
- /**
819
- * Return value as JSON string
820
- *
821
- * @returns {string} Stringified object as JSON
822
- */
823
- toString() {
824
- return JSON.stringify(this.value);
237
+ async tx(operation, readonly = false) {
238
+ const db = await this.database.connection;
239
+ return new Promise((resolve, reject) => {
240
+ const request = operation(db.transaction(this.name, readonly ? "readonly" : "readwrite").objectStore(this.name));
241
+ request.onsuccess = () => resolve(request.result);
242
+ request.onerror = () => reject(request.error);
243
+ });
825
244
  }
826
- /**
827
- * Return current value
828
- *
829
- * @returns {T} Current value
830
- */
831
- valueOf() {
832
- return this.value;
245
+ add(value, key) {
246
+ return this.tx((store) => store.add(value, key));
833
247
  }
834
- }
835
- persist$1.Persist = Persist;
836
- function persist(options) {
837
- return (target, prop) => {
838
- const key = (options == null ? void 0 : options.key) || `${target.constructor.name}.${prop.toString()}`;
839
- const wrapper = new Persist(key, options);
840
- Object.defineProperty(target, prop, {
841
- get: function() {
842
- return wrapper.value;
843
- },
844
- set: function(v) {
845
- wrapper.value = v;
846
- }
847
- });
848
- };
849
- }
850
- persist$1.persist = persist;
851
- var memoryStorage = {};
852
- Object.defineProperty(memoryStorage, "__esModule", { value: true });
853
- memoryStorage.MemoryStorage = void 0;
854
- class MemoryStorage {
855
- get length() {
856
- return Object.keys(this).length;
248
+ put(key, value) {
249
+ return this.tx((store) => store.put(value, key));
857
250
  }
858
- clear() {
859
- Object.keys(this).forEach((k) => this.removeItem(k));
251
+ get(key) {
252
+ return this.tx((store) => store.get(key), true);
860
253
  }
861
- getItem(key) {
862
- return this[key];
254
+ getAll() {
255
+ return this.tx((store) => store.getAll(), true);
863
256
  }
864
- key(index) {
865
- return Object.keys(this)[index];
257
+ getAllKeys() {
258
+ return this.tx((store) => store.getAllKeys(), true);
259
+ }
260
+ delete(key) {
261
+ return this.tx((store) => store.delete(key));
866
262
  }
867
- removeItem(key) {
868
- delete this[key];
263
+ clear() {
264
+ return this.tx((store) => store.clear());
869
265
  }
870
- setItem(key, value) {
871
- this[key] = value;
266
+ count() {
267
+ return this.tx((store) => store.count(), true);
872
268
  }
873
269
  }
874
- memoryStorage.MemoryStorage = MemoryStorage;
875
- (function(exports) {
876
- var __createBinding = commonjsGlobal && commonjsGlobal.__createBinding || (Object.create ? function(o, m, k, k2) {
877
- if (k2 === void 0) k2 = k;
878
- var desc = Object.getOwnPropertyDescriptor(m, k);
879
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
880
- desc = { enumerable: true, get: function() {
881
- return m[k];
882
- } };
883
- }
884
- Object.defineProperty(o, k2, desc);
885
- } : function(o, m, k, k2) {
886
- if (k2 === void 0) k2 = k;
887
- o[k2] = m[k];
888
- });
889
- var __exportStar = commonjsGlobal && commonjsGlobal.__exportStar || function(m, exports2) {
890
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports2, p)) __createBinding(exports2, m, p);
891
- };
892
- Object.defineProperty(exports, "__esModule", { value: true });
893
- __exportStar(persist$1, exports);
894
- __exportStar(memoryStorage, exports);
895
- })(dist);
896
270
  var extendStatics = function(d, b) {
897
271
  extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function(d2, b2) {
898
272
  d2.__proto__ = b2;
@@ -1747,6 +1121,7 @@ const _LoginPrompt = class _LoginPrompt {
1747
1121
  __publicField(this, "alert");
1748
1122
  __publicField(this, "button");
1749
1123
  __publicField(this, "form");
1124
+ __publicField(this, "forgotLink");
1750
1125
  __publicField(this, "password");
1751
1126
  __publicField(this, "persist");
1752
1127
  __publicField(this, "username");
@@ -1775,8 +1150,14 @@ const _LoginPrompt = class _LoginPrompt {
1775
1150
  this.password = document.querySelector('#datalynk-login-form input[name="password"]');
1776
1151
  this.persist = document.querySelector('#datalynk-login-form input[name="persist"]');
1777
1152
  this.username = document.querySelector('#datalynk-login-form input[name="username"]');
1153
+ this.forgotLink = document.querySelector("#datalynk-login-forgot");
1778
1154
  if (this.options.persist === false) this.persist.parentElement.remove();
1779
1155
  this.form.onsubmit = (event) => this.login(event);
1156
+ this.forgotLink.onclick = (event) => this.forgotPassword(event);
1157
+ const toggleForgotVisibility = () => this.forgotLink.classList.toggle("hidden", !this.username.value.trim());
1158
+ this.username.addEventListener("input", toggleForgotVisibility);
1159
+ const passwordToggle = document.querySelector("#datalynk-password-toggle");
1160
+ if (passwordToggle) passwordToggle.onclick = () => this.password.type = this.password.type === "password" ? "text" : "password";
1780
1161
  const pwaLink = document.querySelector("#pwa-install-link");
1781
1162
  if (pwaLink) {
1782
1163
  pwaLink.addEventListener("click", async (e) => {
@@ -1848,6 +1229,19 @@ const _LoginPrompt = class _LoginPrompt {
1848
1229
  this.button.disabled = false;
1849
1230
  });
1850
1231
  }
1232
+ /** Forgot password link click event */
1233
+ forgotPassword(event) {
1234
+ event.preventDefault();
1235
+ const login = this.username.value.trim() || prompt("Enter your email or username") || "";
1236
+ if (!login) return;
1237
+ this.alert.classList.remove("hidden");
1238
+ this.alert.innerHTML = "Sending reset email...";
1239
+ return this.api.auth.resetRequest(login, "email").then(() => {
1240
+ this.alert.innerHTML = "If an account exists, a reset email has been sent.";
1241
+ }).catch((err) => {
1242
+ this.alert.innerHTML = err.message || "Unable to send reset email.";
1243
+ });
1244
+ }
1851
1245
  };
1852
1246
  /** Dynamically create CSS style */
1853
1247
  __publicField(_LoginPrompt, "css", (options) => `
@@ -2000,6 +1394,35 @@ __publicField(_LoginPrompt, "css", (options) => `
2000
1394
  text-decoration: none;
2001
1395
  }
2002
1396
 
1397
+ #datalynk-login .login-forgot {
1398
+ display: block;
1399
+ text-align: left;
1400
+ margin-bottom: 0.75rem;
1401
+ color: var(--theme-text);
1402
+ font-size: 14px;
1403
+ text-decoration: none;
1404
+ }
1405
+
1406
+ #datalynk-login .login-forgot:hover {
1407
+ text-decoration: underline;
1408
+ }
1409
+
1410
+ #datalynk-login .password-wrapper {
1411
+ position: relative;
1412
+ }
1413
+
1414
+ #datalynk-login .password-toggle {
1415
+ position: absolute;
1416
+ right: 30px;
1417
+ top: 50%;
1418
+ transform: translateY(-50%);
1419
+ background: none;
1420
+ border: none;
1421
+ padding: 0;
1422
+ color: #333;
1423
+ cursor: pointer;
1424
+ }
1425
+
2003
1426
  #datalynk-login #pwa-install-link {
2004
1427
  display: inline-flex;
2005
1428
  align-items: center;
@@ -2057,7 +1480,6 @@ __publicField(_LoginPrompt, "template", (options) => {
2057
1480
  </div>
2058
1481
  <div class="login-content">
2059
1482
  <div class="login-body" style="max-width: 300px">
2060
- <div id="datalynk-login-alert" class="hidden"></div>
2061
1483
  <form id="datalynk-login-form">
2062
1484
  <div>
2063
1485
  <label for="username">Email or Username</label>
@@ -2066,12 +1488,19 @@ __publicField(_LoginPrompt, "template", (options) => {
2066
1488
  <br>
2067
1489
  <div>
2068
1490
  <label for="password">Password</label>
2069
- <input id="password" name="password" type="password" autocomplete="current-password">
1491
+ <div class="password-wrapper">
1492
+ <input id="password" name="password" type="password" autocomplete="current-password">
1493
+ <button type="button" id="datalynk-password-toggle" class="password-toggle" tabindex="-1">
1494
+ <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>
1495
+ </button>
1496
+ </div>
2070
1497
  </div>
1498
+ <a href="#" id="datalynk-login-forgot" class="login-forgot hidden">Forgot Password?</a>
2071
1499
  <br>
2072
1500
  <label style="display: block; margin-bottom: 0.75rem;">
2073
1501
  <input type="checkbox" name="persist" style="width: 20px"> Stay Logged In
2074
1502
  </label>
1503
+ <div id="datalynk-login-alert" class="hidden"></div>
2075
1504
  <button type="submit">Login</button>
2076
1505
  </form>
2077
1506
  </div>
@@ -2112,7 +1541,12 @@ class Auth {
2112
1541
  this.api = api;
2113
1542
  this.api.token$.subscribe(async (token) => {
2114
1543
  if (token === void 0) return;
2115
- this.user = await this.current(token);
1544
+ try {
1545
+ this.user = await this.current(token);
1546
+ } catch (error) {
1547
+ if (this.api.status === "unauthorized") this.user = null;
1548
+ else console.error("Unable to refresh the current Datalynk user", error);
1549
+ }
2116
1550
  });
2117
1551
  if ((_a = this.api.options.offline) == null ? void 0 : _a.length)
2118
1552
  this.user$.pipe(filter((u) => u !== void 0)).subscribe((u) => localStorage.setItem("datalynk-user", JSON.stringify(u)));
@@ -2632,9 +2066,9 @@ class PWA {
2632
2066
  document.head.append(style);
2633
2067
  }
2634
2068
  const iconUrl = this.resolvedIconUrl || "https://datalynk-client.primary.auxilium.world/logo.png";
2635
- const prompt = document.createElement("div");
2636
- prompt.classList.add("pwa-prompt");
2637
- prompt.innerHTML = `
2069
+ const prompt2 = document.createElement("div");
2070
+ prompt2.classList.add("pwa-prompt");
2071
+ prompt2.innerHTML = `
2638
2072
  <div class="pwa-prompt-header">
2639
2073
  <img src="${iconUrl}" alt="Logo" />
2640
2074
  <h1>Install ${this.api.options.name}</h1>
@@ -2650,14 +2084,14 @@ class PWA {
2650
2084
  ${this.nativeInstallPrompt ? `<button id="installPwaBtn">Install App</button>` : stepsHtml}
2651
2085
  </div>
2652
2086
  `;
2653
- const closeBtn = prompt.querySelector(".pwa-prompt-close");
2087
+ const closeBtn = prompt2.querySelector(".pwa-prompt-close");
2654
2088
  closeBtn.onclick = () => {
2655
2089
  var _a2;
2656
2090
  !!((_a2 = this.api.options.pwaSettings) == null ? void 0 : _a2.dismissExpiry) ? localStorage.setItem(storageKey, Date.now().toString()) : localStorage.removeItem(storageKey);
2657
- prompt.remove();
2091
+ prompt2.remove();
2658
2092
  };
2659
- document.body.append(prompt);
2660
- const img = prompt.querySelector(".pwa-prompt-header img");
2093
+ document.body.append(prompt2);
2094
+ const img = prompt2.querySelector(".pwa-prompt-header img");
2661
2095
  if (img) img.onerror = () => img.src = "https://datalynk-client.primary.auxilium.world/logo.png";
2662
2096
  if (this.nativeInstallPrompt) this.bindInstallButton();
2663
2097
  }
@@ -3245,7 +2679,13 @@ const _Slice = class _Slice {
3245
2679
  set cache(cache) {
3246
2680
  this.cache$.next(cache);
3247
2681
  }
3248
- /** Is slice offline support enabled */
2682
+ /**
2683
+ * Whether this slice has local IndexedDB/offline support enabled.
2684
+ *
2685
+ * Offline-enabled slices can fall back to their local cache when either the
2686
+ * browser loses network connectivity or the Datalynk client enters an
2687
+ * unavailable recovery state after a returned server/API failure.
2688
+ */
3249
2689
  get offlineEnabled() {
3250
2690
  var _a;
3251
2691
  return (_a = this.api.database) == null ? void 0 : _a.includes(this.slice.toString());
@@ -3258,7 +2698,7 @@ const _Slice = class _Slice {
3258
2698
  const onlineExec = call.exec.bind(call);
3259
2699
  return async () => {
3260
2700
  const offlineSupported = this.offlineEnabled && typeof navigator !== "undefined";
3261
- const offlineNow = offlineSupported && !(navigator == null ? void 0 : navigator.onLine);
2701
+ const offlineNow = offlineSupported && (!this.api.online || !(navigator == null ? void 0 : navigator.onLine));
3262
2702
  const offlineSim = async () => {
3263
2703
  var _a, _b, _c;
3264
2704
  const where = (row, condition) => {
@@ -3557,24 +2997,39 @@ const _Slice = class _Slice {
3557
2997
  return this.info;
3558
2998
  }
3559
2999
  /**
3560
- * Synchronize cache with server
3000
+ * Synchronize the local slice cache with the server and subscribe to socket changes.
3001
+ *
3002
+ * For an offline-enabled slice, synchronization failures caused by network or
3003
+ * API unavailability are contained in the background rather than becoming
3004
+ * unhandled promise rejections. Local cached data remains usable while the
3005
+ * {@link Api} recovery loop determines when the failed API path works again.
3006
+ * When the client becomes online again, pending local changes are scheduled
3007
+ * for upload without overlapping push operations.
3008
+ *
3009
+ * Calling `sync(false)` unsubscribes from slice socket events.
3010
+ *
3561
3011
  * @example
3562
3012
  * ```ts
3563
3013
  * const slice: Slice = new Slice<T>(Slices.Contact);
3564
- * slice.sync().subscribe((rows: T[]) => {});
3014
+ * const rows$ = slice.sync();
3015
+ * rows$?.subscribe((rows: T[]) => console.log(rows));
3565
3016
  * ```
3566
- * @param {boolean} on Enable/disable events
3567
- * @return {BehaviorSubject<T[]>} Cache which can be subscribed to
3017
+ * @param on Enable or disable synchronization/socket events.
3018
+ * @returns The observable local cache when synchronization is enabled.
3568
3019
  */
3569
3020
  sync(on = true) {
3570
3021
  if (on) {
3571
- this.pushChanges().then(() => this.select().rows().exec().then((rows) => {
3022
+ void this.pushChanges().then(() => this.select().rows().exec()).then((rows) => {
3572
3023
  this.cache = rows;
3573
3024
  this.loaded = true;
3574
- }));
3025
+ }).catch((error) => {
3026
+ if (this.api.online) console.warn("Unable to synchronize offline slice", error);
3027
+ });
3575
3028
  if (!this.unsubscribe) this.unsubscribe = this.api.socket.sliceEvents(this.slice, (event) => {
3576
3029
  const ids = [...event.data.new, ...event.data.changed];
3577
- this.select(ids).rows().exec().then((rows) => this.cache = [...this.cache.filter((c) => c.id != null && !ids.includes(c.id)), ...rows]);
3030
+ void this.select(ids).rows().exec().then((rows) => this.cache = [...this.cache.filter((c) => c.id != null && !ids.includes(c.id)), ...rows]).catch((error) => {
3031
+ if (this.api.online) console.warn("Unable to refresh offline slice from socket event", error);
3032
+ });
3578
3033
  this.cache = this.cache.filter((v) => v.id && !event.data.lost.includes(v.id));
3579
3034
  });
3580
3035
  return this.cache$;
@@ -3647,7 +3102,7 @@ class Socket {
3647
3102
  this.options = options;
3648
3103
  if (!options.url && options.url !== false) {
3649
3104
  const origin = new URL(this.api.url).origin;
3650
- this.options.url = origin.replace("http", "ws").replace(/:\d+/g, "") + `:9390`;
3105
+ this.options.url = origin.replace("http", "ws").replace(/:\d+/g, "") + `/s/`;
3651
3106
  }
3652
3107
  if (this.options.url !== false)
3653
3108
  api.token$.pipe(filter((u) => u !== void 0), distinctUntilChanged()).subscribe(() => this.connect());
@@ -3770,7 +3225,7 @@ class Superuser {
3770
3225
  } });
3771
3226
  }
3772
3227
  }
3773
- const version = "1.4.0";
3228
+ const version = "1.5.0";
3774
3229
  class WebRtc {
3775
3230
  constructor(api) {
3776
3231
  __publicField(this, "ice");
@@ -3891,8 +3346,9 @@ class WebRtc {
3891
3346
  return session;
3892
3347
  }
3893
3348
  }
3894
- class Gps {
3349
+ const _Gps = class _Gps {
3895
3350
  constructor(api, options) {
3351
+ __publicField(this, "ackResolvers", /* @__PURE__ */ new Map());
3896
3352
  __publicField(this, "deviceId");
3897
3353
  __publicField(this, "heartbeat");
3898
3354
  __publicField(this, "lastFixAt", 0);
@@ -3910,6 +3366,15 @@ class Gps {
3910
3366
  __publicField(this, "watchId");
3911
3367
  __publicField(this, "watchStartedAt", 0);
3912
3368
  __publicField(this, "options");
3369
+ __publicField(this, "onSocketMessage", (event) => {
3370
+ const ack = event == null ? void 0 : event.gps;
3371
+ if (!ack || ack.seq == null) return;
3372
+ const pending = this.ackResolvers.get(Number(ack.seq));
3373
+ if (!pending) return;
3374
+ clearTimeout(pending.timeout);
3375
+ this.ackResolvers.delete(Number(ack.seq));
3376
+ pending.resolve(ack);
3377
+ });
3913
3378
  __publicField(this, "onPosition", (position) => {
3914
3379
  if (!this.options) return;
3915
3380
  if (!this.isFresh(position)) return;
@@ -3965,18 +3430,23 @@ class Gps {
3965
3430
  this.unsubs.forEach((unsub) => unsub());
3966
3431
  this.unsubs = [];
3967
3432
  this.pendingPayloads = [];
3433
+ this.clearAckResolvers("GPS stopped");
3968
3434
  (_a = this.socket) == null ? void 0 : _a.close();
3969
3435
  this.socket = void 0;
3970
3436
  }
3971
- /** Send a raw GPS message. Useful when an app already has its own GPS engine */
3972
- send(position, extra = {}) {
3437
+ /**
3438
+ * Send a position from an app-owned GPS engine.
3439
+ * By default returns after the message is queued/sent; set `waitForAck` to await server confirmation.
3440
+ */
3441
+ send(position, extra = {}, options = {}) {
3973
3442
  if (!this.options) throw new Error("Datalynk GPS is not configured");
3974
3443
  if (!this.api.token) throw new Error("Datalynk GPS cannot send before login");
3975
3444
  this.connectSocket();
3976
- this.sendPayload({
3445
+ const waitForAck = options.waitForAck ?? this.options.waitForAck ?? false;
3446
+ return this.sendPayload({
3977
3447
  ...extra,
3978
3448
  position: this.normalizePosition(position)
3979
- });
3449
+ }, waitForAck);
3980
3450
  }
3981
3451
  /** Listen to GPS updates for the configured slice/field */
3982
3452
  listen(callback, options = {}) {
@@ -4016,12 +3486,18 @@ class Gps {
4016
3486
  document.removeEventListener("visibilitychange", restart);
4017
3487
  });
4018
3488
  }
3489
+ clearAckResolvers(reason) {
3490
+ this.ackResolvers.forEach(({ timeout, resolve }) => {
3491
+ clearTimeout(timeout);
3492
+ resolve({ ok: false, error: reason });
3493
+ });
3494
+ this.ackResolvers.clear();
3495
+ }
4019
3496
  connectSocket() {
4020
3497
  if (!this.options || this.socket) return;
4021
3498
  if (!this.api.token) throw new Error("Datalynk GPS socket cannot connect before login");
4022
3499
  this.socket = new Socket(this.api, { url: this.options.socketUrl });
4023
- this.unsubs.push(this.socket.addListener(() => {
4024
- }, () => this.flushPendingPayloads()));
3500
+ this.unsubs.push(this.socket.addListener(this.onSocketMessage, () => this.flushPendingPayloads()));
4025
3501
  }
4026
3502
  createId() {
4027
3503
  if (typeof crypto != "undefined" && crypto.randomUUID) return crypto.randomUUID();
@@ -4138,10 +3614,11 @@ class Gps {
4138
3614
  this.watchId = void 0;
4139
3615
  this.startWatch();
4140
3616
  }
4141
- sendPayload(payload) {
3617
+ sendPayload(payload, waitForAck = false) {
4142
3618
  var _a, _b;
4143
- if (!this.options) return;
3619
+ if (!this.options) return Promise.resolve({ ok: false, error: "Datalynk GPS is not configured" });
4144
3620
  this.connectSocket();
3621
+ const seq = ++this.seq;
4145
3622
  const message = {
4146
3623
  gps: {
4147
3624
  slice: this.options.slice,
@@ -4150,7 +3627,7 @@ class Gps {
4150
3627
  trackerId: this.trackerId,
4151
3628
  sessionId: this.sessionId,
4152
3629
  deviceId: this.deviceId,
4153
- seq: ++this.seq,
3630
+ seq,
4154
3631
  client: this.getClientDetails(),
4155
3632
  sentAt: (/* @__PURE__ */ new Date()).toISOString(),
4156
3633
  gpsTimestamp: ((_a = payload.position) == null ? void 0 : _a.timestamp) || null,
@@ -4159,12 +3636,24 @@ class Gps {
4159
3636
  ...payload
4160
3637
  }
4161
3638
  };
3639
+ let ackPromise;
3640
+ if (waitForAck) {
3641
+ const timeoutMs = this.options.ackTimeoutMs ?? _Gps.GPS_ACK_TIMEOUT_MS;
3642
+ ackPromise = new Promise((resolve) => {
3643
+ const timeout = setTimeout(() => {
3644
+ this.ackResolvers.delete(seq);
3645
+ resolve({ ok: false, error: "GPS ack timeout", seq });
3646
+ }, timeoutMs);
3647
+ this.ackResolvers.set(seq, { resolve, timeout });
3648
+ });
3649
+ }
4162
3650
  if (!((_b = this.socket) == null ? void 0 : _b.open)) {
4163
3651
  this.pendingPayloads.push(message);
4164
3652
  this.pendingPayloads = this.pendingPayloads.slice(-25);
4165
- return;
3653
+ return ackPromise ?? Promise.resolve({ ok: true, seq, pending: true });
4166
3654
  }
4167
3655
  this.socket.send(message);
3656
+ return ackPromise ?? Promise.resolve({ ok: true, seq, pending: false });
4168
3657
  }
4169
3658
  sendPosition(position, force) {
4170
3659
  this.sendPayload({
@@ -4235,7 +3724,11 @@ class Gps {
4235
3724
  return;
4236
3725
  }
4237
3726
  const sub = this.api.token$.subscribe((token) => {
4238
- if (!token || this.started || !this.options || this.options.autoStart === false) return;
3727
+ if (!token) {
3728
+ if (this.started) this.stop();
3729
+ return;
3730
+ }
3731
+ if (this.started || !this.options || this.options.autoStart === false) return;
4239
3732
  try {
4240
3733
  this.start();
4241
3734
  } catch (error) {
@@ -4244,6 +3737,16 @@ class Gps {
4244
3737
  });
4245
3738
  this.unsubs.push(() => sub.unsubscribe());
4246
3739
  }
3740
+ };
3741
+ __publicField(_Gps, "GPS_ACK_TIMEOUT_MS", 15e3);
3742
+ let Gps = _Gps;
3743
+ class UnexpectedApiResponseError extends Error {
3744
+ constructor(response, body) {
3745
+ super(`Unexpected API response (${response.status} ${response.statusText})`);
3746
+ this.response = response;
3747
+ this.body = body;
3748
+ this.name = "UnexpectedApiResponseError";
3749
+ }
4247
3750
  }
4248
3751
  const _Api = class _Api {
4249
3752
  /**
@@ -4268,6 +3771,13 @@ const _Api = class _Api {
4268
3771
  target: "version.php",
4269
3772
  timeout: 6e4
4270
3773
  });
3774
+ __publicField(this, "authenticationInvalid", false);
3775
+ __publicField(this, "tokenExpiryTimeout", null);
3776
+ /** Request-specific recovery after a server response proves an API call is broken. */
3777
+ __publicField(this, "recovery", null);
3778
+ /** Retry the failed request twice immediately, then this long after each failed recovery response. */
3779
+ __publicField(this, "recoveryRetryInterval", 3e4);
3780
+ __publicField(this, "recoveryImmediateRetries", 2);
4271
3781
  /** LocalStorage key for persisting logins */
4272
3782
  __publicField(this, "localStorageKey", "datalynk-token");
4273
3783
  /** Pending requests cache */
@@ -4300,7 +3810,20 @@ const _Api = class _Api {
4300
3810
  /** Client library version */
4301
3811
  __publicField(this, "version", version);
4302
3812
  __publicField(this, "onlineOverride", false);
4303
- __publicField(this, "online$", new BehaviorSubject(typeof navigator == "undefined" || typeof navigator.onLine == "undefined" ? true : navigator.onLine));
3813
+ __publicField(this, "initialOnline", typeof navigator == "undefined" || typeof navigator.onLine == "undefined" ? true : navigator.onLine);
3814
+ /**
3815
+ * Detailed API connection state.
3816
+ *
3817
+ * Subscribe to this when callers need to distinguish physical/network
3818
+ * offline state from authentication failure or server/API unavailability.
3819
+ */
3820
+ __publicField(this, "status$", new BehaviorSubject(this.initialOnline ? "online" : "offline"));
3821
+ /**
3822
+ * Backwards-compatible boolean connection state.
3823
+ *
3824
+ * `false` includes `offline`, `unauthorized`, and `unavailable` states.
3825
+ */
3826
+ __publicField(this, "online$", new BehaviorSubject(this.initialOnline));
4304
3827
  /** API Session token */
4305
3828
  __publicField(this, "token$", new BehaviorSubject(void 0));
4306
3829
  var _a, _b;
@@ -4321,14 +3844,14 @@ const _Api = class _Api {
4321
3844
  ...options.webrtc || {}
4322
3845
  }
4323
3846
  };
4324
- if (this.options.saveSession) {
4325
- if (typeof localStorage == "undefined") return;
3847
+ if (this.options.saveSession && typeof localStorage != "undefined") {
4326
3848
  this.token = localStorage.getItem(this.localStorageKey) || null;
4327
3849
  this.token$.pipe(distinctUntilChanged()).subscribe((token) => {
4328
3850
  if (token) localStorage.setItem(this.localStorageKey, token);
4329
3851
  else localStorage.removeItem(this.localStorageKey);
4330
3852
  });
4331
3853
  }
3854
+ this.token$.pipe(distinctUntilChanged()).subscribe((token) => this.scheduleTokenExpiry(token));
4332
3855
  this.socket = new Socket(this, { url: options.socket });
4333
3856
  this.gps = new Gps(this, options.gps);
4334
3857
  this.auth = new Auth(this);
@@ -4351,12 +3874,10 @@ const _Api = class _Api {
4351
3874
  });
4352
3875
  }
4353
3876
  if (typeof window !== "undefined") {
4354
- const handleOffline = (state = this.offline) => {
4355
- if (this.online != state) this.online$.next(state);
4356
- };
4357
- window.addEventListener("online", () => handleOffline(true));
4358
- window.addEventListener("offline", () => handleOffline(false));
3877
+ window.addEventListener("online", () => this.checkConnection());
3878
+ window.addEventListener("offline", () => this.setConnectionStatus("offline"));
4359
3879
  this.online$.subscribe(() => this.offlineBanner());
3880
+ this.startHeartbeat();
4360
3881
  }
4361
3882
  if ((_a = this.options.offline) == null ? void 0 : _a.length) {
4362
3883
  this.pwa.setup();
@@ -4389,26 +3910,50 @@ const _Api = class _Api {
4389
3910
  /** Get session info from JWT payload */
4390
3911
  get jwtPayload() {
4391
3912
  if (!this.token) return null;
4392
- return decodeJwt(this.token);
3913
+ try {
3914
+ return decodeJwt(this.token);
3915
+ } catch {
3916
+ return null;
3917
+ }
4393
3918
  }
4394
- /** Check if we are connected */
3919
+ /** Current detailed Datalynk API connection state. */
3920
+ get status() {
3921
+ return this.status$.getValue();
3922
+ }
3923
+ /**
3924
+ * Whether normal Datalynk network requests are currently available.
3925
+ *
3926
+ * This becomes `false` for network outages, rejected/expired sessions, and
3927
+ * request-owned server recovery. It therefore describes Datalynk
3928
+ * availability rather than only `navigator.onLine`.
3929
+ */
4395
3930
  get online() {
4396
3931
  return this.online$.getValue();
4397
3932
  }
3933
+ /**
3934
+ * Whether normal Datalynk API access is currently unavailable.
3935
+ *
3936
+ * This is the inverse of {@link online}. It can be `true` while the browser
3937
+ * still has Internet access, for example during MySQL/HTTP 5xx recovery.
3938
+ */
4398
3939
  get offline() {
4399
3940
  return !this.online;
4400
3941
  }
4401
- /** Override connection status */
3942
+ /**
3943
+ * Override the boolean connection state.
3944
+ *
3945
+ * Set `true` or `false` to force the corresponding state. Set `null` to
3946
+ * remove the override and resume normal connection checking. This is a
3947
+ * manual override and can supersede the current recovery state, so normal
3948
+ * applications should generally observe {@link status} instead of forcing it.
3949
+ */
4402
3950
  set online(value) {
4403
3951
  if (value == null) {
4404
3952
  this.onlineOverride = false;
4405
- this.online$.next(typeof navigator == "undefined" ? true : navigator.onLine);
3953
+ this.checkConnection();
4406
3954
  } else {
4407
3955
  this.onlineOverride = true;
4408
- if (value == this.online) return;
4409
- this.online$.next(value);
4410
- if (value) this.startHeartbeat();
4411
- else this.stopHeartbeat();
3956
+ this.setConnectionStatus(value ? "online" : "offline");
4412
3957
  }
4413
3958
  }
4414
3959
  /** Logged in spoke */
@@ -4420,56 +3965,246 @@ const _Api = class _Api {
4420
3965
  return this.token$.getValue();
4421
3966
  }
4422
3967
  set token(token) {
3968
+ if (this.recovery && token !== this.recovery.token)
3969
+ this.cancelRecovery(errorFromCode(401, "Session changed during API recovery"));
3970
+ if (token && this.isTokenExpired(token)) {
3971
+ this.authenticationInvalid = true;
3972
+ this.token$.next(null);
3973
+ this.setConnectionStatus("unauthorized");
3974
+ return;
3975
+ }
3976
+ this.authenticationInvalid = false;
4423
3977
  this.token$.next(token);
3978
+ if (token && !this.online && !this.recovery) {
3979
+ this.setConnectionStatus(typeof navigator == "undefined" || navigator.onLine ? "online" : "offline");
3980
+ }
4424
3981
  }
4425
- _request(req, options = {}) {
3982
+ async _request(req, options = {}) {
3983
+ if (this.recovery) throw errorFromCode(503, "Datalynk is unavailable");
3984
+ const retryRequest = deepCopy(req);
3985
+ const retryOptions = { ...options };
4426
3986
  const token = options.token || this.token;
4427
- return fetch(this.url, {
4428
- method: "POST",
4429
- headers: clean({
4430
- Authorization: token ? `Bearer ${token}` : void 0,
4431
- "Content-Type": "application/json",
4432
- "X-Date-Return-Format": this.options.legacyDates ? void 0 : "ISO8601"
4433
- }),
4434
- body: JSON.stringify(_Api.translateTokens(req))
4435
- }).then(async (resp) => {
4436
- const warning = resp.headers["X-Warning"];
4437
- if (warning) console.warn(warning);
4438
- const banner = resp.headers["X-User-Notice"];
4439
- if (banner) {
4440
- createBanner(banner);
4441
- setTimeout(() => removeBanner(), 1e4);
3987
+ try {
3988
+ return await this._requestOnce(req, options, false);
3989
+ } catch (error) {
3990
+ if (!this.isRecoverableResponseError(error)) throw error;
3991
+ this.beginRecovery(retryRequest, retryOptions, token);
3992
+ throw error;
3993
+ }
3994
+ }
3995
+ /** Execute exactly one HTTP API attempt. Recovery uses this directly to avoid recursive retry loops. */
3996
+ async _requestOnce(req, options = {}, recoveryAttempt = false) {
3997
+ const token = options.token || this.token;
3998
+ if (token && this.isTokenExpired(token)) {
3999
+ this.markUnauthorized(token);
4000
+ throw errorFromCode(401, "Session token expired");
4001
+ }
4002
+ let resp;
4003
+ try {
4004
+ resp = await fetch(this.url, {
4005
+ method: "POST",
4006
+ headers: clean({
4007
+ Authorization: token ? `Bearer ${token}` : void 0,
4008
+ "Content-Type": "application/json",
4009
+ "X-Date-Return-Format": this.options.legacyDates ? void 0 : "ISO8601"
4010
+ }),
4011
+ body: JSON.stringify(_Api.translateTokens(req))
4012
+ });
4013
+ } catch (error) {
4014
+ this.setConnectionStatus("offline");
4015
+ throw error;
4016
+ }
4017
+ const warning = resp.headers.get("X-Warning");
4018
+ if (warning) console.warn(warning);
4019
+ const banner = resp.headers.get("X-User-Notice");
4020
+ if (banner) {
4021
+ createBanner(banner);
4022
+ setTimeout(() => removeBanner(), 1e4);
4023
+ }
4024
+ const body = await resp.text();
4025
+ let data;
4026
+ try {
4027
+ data = JSON.parse(body);
4028
+ } catch {
4029
+ throw new UnexpectedApiResponseError(resp, body);
4030
+ }
4031
+ if (resp.status === 401) this.markUnauthorized(token);
4032
+ if (!resp.ok || (data == null ? void 0 : data.error)) {
4033
+ const error = Object.assign(errorFromCode(resp.status, data == null ? void 0 : data.error), data);
4034
+ Object.defineProperty(error, "response", { value: resp, configurable: true });
4035
+ if (!recoveryAttempt && resp.status < 500 && resp.status !== 401 && !this.isMysqlError(error))
4036
+ this.setConnectionStatus("online");
4037
+ throw error;
4038
+ }
4039
+ if (!options.raw) data = _Api.translateTokens(data);
4040
+ if (!recoveryAttempt) this.setConnectionStatus("online");
4041
+ return data;
4042
+ }
4043
+ /** Only server-response failures own global recovery; auth and ordinary 4xx errors do not. */
4044
+ isRecoverableResponseError(error) {
4045
+ var _a;
4046
+ if (error instanceof UnexpectedApiResponseError) return true;
4047
+ if ((error == null ? void 0 : error.code) === 401) return false;
4048
+ const status = ((_a = error == null ? void 0 : error.response) == null ? void 0 : _a.status) ?? (error == null ? void 0 : error.code);
4049
+ return status >= 500 || this.isMysqlError(error);
4050
+ }
4051
+ isMysqlError(error) {
4052
+ const message = String((error == null ? void 0 : error.error) ?? (error == null ? void 0 : error.message) ?? "");
4053
+ return /\bSQLSTATE\[[A-Z0-9]+\]/i.test(message);
4054
+ }
4055
+ /**
4056
+ * A returned server failure immediately makes the client unavailable. The exact
4057
+ * failed request is then retried twice back-to-back. After that, retries are
4058
+ * scheduled 30 seconds after each completed failed attempt, never with overlap.
4059
+ */
4060
+ beginRecovery(req, options, token) {
4061
+ if (this.recovery) return;
4062
+ this.setConnectionStatus("unavailable");
4063
+ this.recovery = {
4064
+ request: deepCopy(req),
4065
+ options: { ...options },
4066
+ token,
4067
+ timer: null,
4068
+ running: false
4069
+ };
4070
+ void this.runImmediateRecovery();
4071
+ }
4072
+ async runImmediateRecovery() {
4073
+ for (let attempt = 0; attempt < this.recoveryImmediateRetries; attempt++) {
4074
+ const result = await this.tryRecoveryOnce();
4075
+ if (result.ok) {
4076
+ this.finishRecovery();
4077
+ return;
4442
4078
  }
4443
- this.online = true;
4444
- let data = JSONAttemptParse(await resp.text());
4445
- if (!resp.ok || (data == null ? void 0 : data.error)) throw Object.assign(errorFromCode(resp.status, data.error), data);
4446
- if (!options.raw) data = _Api.translateTokens(data);
4447
- return data;
4448
- });
4079
+ if (!this.recovery || this.status === "unauthorized") return;
4080
+ }
4081
+ if (this.recovery) this.scheduleRecovery();
4082
+ }
4083
+ async tryRecoveryOnce() {
4084
+ const recovery = this.recovery;
4085
+ if (!recovery) return { ok: false, error: errorFromCode(503, "API recovery was cancelled") };
4086
+ if (recovery.running) return { ok: false, error: errorFromCode(503, "API recovery request is already running") };
4087
+ if (recovery.token !== (recovery.options.token || this.token)) {
4088
+ const error = errorFromCode(401, "Session changed during API recovery");
4089
+ this.cancelRecovery(error);
4090
+ return { ok: false, error };
4091
+ }
4092
+ recovery.running = true;
4093
+ try {
4094
+ const value = await this._requestOnce(
4095
+ deepCopy(recovery.request),
4096
+ { ...recovery.options, token: recovery.token || void 0 },
4097
+ true
4098
+ );
4099
+ return { ok: true, value };
4100
+ } catch (error) {
4101
+ return { ok: false, error };
4102
+ } finally {
4103
+ if (this.recovery === recovery) recovery.running = false;
4104
+ }
4105
+ }
4106
+ scheduleRecovery() {
4107
+ const recovery = this.recovery;
4108
+ if (!recovery) return;
4109
+ if (recovery.timer) clearTimeout(recovery.timer);
4110
+ recovery.timer = setTimeout(async () => {
4111
+ const current = this.recovery;
4112
+ if (!current || current !== recovery) return;
4113
+ current.timer = null;
4114
+ const result = await this.tryRecoveryOnce();
4115
+ if (result.ok) {
4116
+ this.finishRecovery();
4117
+ return;
4118
+ }
4119
+ if (this.recovery && this.status !== "unauthorized") this.scheduleRecovery();
4120
+ }, this.recoveryRetryInterval);
4121
+ }
4122
+ finishRecovery() {
4123
+ const recovery = this.recovery;
4124
+ if (!recovery) return;
4125
+ if (recovery.timer) clearTimeout(recovery.timer);
4126
+ this.recovery = null;
4127
+ this.setConnectionStatus("online");
4128
+ }
4129
+ cancelRecovery(_error) {
4130
+ const recovery = this.recovery;
4131
+ if (!recovery) return;
4132
+ if (recovery.timer) clearTimeout(recovery.timer);
4133
+ this.recovery = null;
4449
4134
  }
4450
4135
  async checkConnection() {
4136
+ if (this.recovery) return;
4451
4137
  if (typeof navigator != "undefined" && !navigator.onLine) {
4452
- this.online$.next(false);
4138
+ this.setConnectionStatus("offline");
4453
4139
  return;
4454
4140
  }
4455
4141
  if (this.onlineOverride) return;
4142
+ if (this.authenticationInvalid) {
4143
+ this.setConnectionStatus("unauthorized");
4144
+ return;
4145
+ }
4146
+ if (this.token && this.isTokenExpired(this.token)) {
4147
+ this.markUnauthorized(this.token);
4148
+ return;
4149
+ }
4456
4150
  const controller = new AbortController();
4457
4151
  const timeout = setTimeout(() => controller.abort(), this.heartbeat.timeout);
4458
4152
  try {
4459
4153
  const response = await fetch(this.url + this.heartbeat.target, { signal: controller.signal });
4460
- this.online$.next(response.ok);
4154
+ if (this.recovery) return;
4155
+ this.setConnectionStatus(response.ok ? "online" : "unavailable");
4461
4156
  } catch (error) {
4462
- this.online$.next(false);
4157
+ if (!this.recovery) this.setConnectionStatus("offline");
4463
4158
  } finally {
4464
4159
  clearTimeout(timeout);
4465
4160
  }
4466
4161
  }
4162
+ isTokenExpired(token) {
4163
+ var _a;
4164
+ try {
4165
+ return (((_a = decodeJwt(token)) == null ? void 0 : _a.exp) ?? 0) * 1e3 <= Date.now();
4166
+ } catch {
4167
+ return true;
4168
+ }
4169
+ }
4170
+ scheduleTokenExpiry(token) {
4171
+ var _a, _b, _c;
4172
+ if (this.tokenExpiryTimeout) clearTimeout(this.tokenExpiryTimeout);
4173
+ this.tokenExpiryTimeout = null;
4174
+ if (!token) return;
4175
+ let expiresAt = 0;
4176
+ try {
4177
+ expiresAt = (((_a = decodeJwt(token)) == null ? void 0 : _a.exp) ?? 0) * 1e3;
4178
+ } catch {
4179
+ return this.markUnauthorized(token);
4180
+ }
4181
+ const delay = expiresAt - Date.now();
4182
+ if (delay <= 0) return this.markUnauthorized(token);
4183
+ this.tokenExpiryTimeout = setTimeout(() => {
4184
+ if (delay > 2147e6) this.scheduleTokenExpiry(token);
4185
+ else this.markUnauthorized(token);
4186
+ }, Math.min(delay, 2147e6));
4187
+ (_c = (_b = this.tokenExpiryTimeout) == null ? void 0 : _b.unref) == null ? void 0 : _c.call(_b);
4188
+ }
4189
+ markUnauthorized(token) {
4190
+ if (token && token !== this.token) return;
4191
+ this.authenticationInvalid = true;
4192
+ this.cancelRecovery(errorFromCode(401, "Session expired"));
4193
+ if (this.token != null) this.token$.next(null);
4194
+ this.setConnectionStatus("unauthorized");
4195
+ }
4196
+ setConnectionStatus(status) {
4197
+ if (this.status !== status) this.status$.next(status);
4198
+ const online = status === "online";
4199
+ if (this.online !== online) this.online$.next(online);
4200
+ }
4467
4201
  offlineBanner() {
4468
4202
  if (this.options.offlineBanner === false || typeof document == "undefined") return;
4469
- if (this.online) {
4203
+ if (this.status === "online") {
4470
4204
  removeBanner("datalynk-offline-banner");
4471
4205
  } else {
4472
- createBanner("⚠️ You are offline, please reconnect to sync changes", {
4206
+ 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";
4207
+ createBanner(message, {
4473
4208
  id: "datalynk-offline-banner",
4474
4209
  position: this.options.offlineBanner === "top" ? "top" : "bottom"
4475
4210
  });
@@ -4581,9 +4316,13 @@ const _Api = class _Api {
4581
4316
  var _a, _b;
4582
4317
  data = typeof data == "string" ? { [data]: {} } : data;
4583
4318
  let key = JSON.stringify(data);
4584
- if (this.offline) {
4585
- (_b = (_a = this.database) == null ? void 0 : _a.table("pending")) == null ? void 0 : _b.add(data, key);
4586
- return Promise.resolve();
4319
+ if (this.offline && typeof navigator != "undefined") {
4320
+ if (this.status === "unauthorized") return Promise.reject(errorFromCode(401, "Session expired"));
4321
+ if (options.offline) {
4322
+ (_b = (_a = this.database) == null ? void 0 : _a.table("pending")) == null ? void 0 : _b.add(data, key);
4323
+ return Promise.resolve();
4324
+ }
4325
+ return Promise.reject(errorFromCode(503, this.status === "unavailable" ? "Datalynk is unavailable" : "You are offline"));
4587
4326
  }
4588
4327
  if (options.noOptimize) {
4589
4328
  return new Promise((res, rej) => {
@@ -4594,7 +4333,7 @@ const _Api = class _Api {
4594
4333
  }
4595
4334
  if (!this.pending[key]) {
4596
4335
  this.pending[key] = new Promise((res, rej) => this.bundle.push({ data, res, rej }));
4597
- this.pending[key].catch().then(() => delete this.pending[key]);
4336
+ this.pending[key].then(() => delete this.pending[key], () => delete this.pending[key]);
4598
4337
  if (!this.bundleOngoing) {
4599
4338
  this.bundleOngoing = true;
4600
4339
  setTimeout(() => {
@@ -4604,7 +4343,10 @@ const _Api = class _Api {
4604
4343
  data = originalBundle.map((row) => row.data);
4605
4344
  this._request(data, options).then((resp) => {
4606
4345
  if (!(resp instanceof Array)) resp = [resp];
4607
- resp.forEach((row, i) => (row == null ? void 0 : row.error) ? originalBundle[i].rej(row.error) : originalBundle[i].res(row));
4346
+ originalBundle.forEach((request, index) => {
4347
+ const row = resp[index];
4348
+ (row == null ? void 0 : row.error) ? request.rej(row.error) : request.res(row);
4349
+ });
4608
4350
  }).catch((err) => originalBundle.forEach((req) => req.rej(err)));
4609
4351
  }, this.options.bundleTime);
4610
4352
  }
@@ -4645,5 +4387,6 @@ export {
4645
4387
  Slice,
4646
4388
  Socket,
4647
4389
  Superuser,
4390
+ UnexpectedApiResponseError,
4648
4391
  getTheme
4649
4392
  };