@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.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);
263
+ }
264
+ delete(key) {
265
+ return this.tx((store) => store.delete(key));
870
266
  }
871
- removeItem(key) {
872
- delete this[key];
267
+ clear() {
268
+ return this.tx((store) => store.clear());
873
269
  }
874
- setItem(key, value) {
875
- this[key] = value;
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.0";
3232
+ const version = "1.5.0";
3778
3233
  class WebRtc {
3779
3234
  constructor(api) {
3780
3235
  __publicField(this, "ice");
@@ -3895,8 +3350,9 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3895
3350
  return session;
3896
3351
  }
3897
3352
  }
3898
- class Gps {
3353
+ const _Gps = class _Gps {
3899
3354
  constructor(api, options) {
3355
+ __publicField(this, "ackResolvers", /* @__PURE__ */ new Map());
3900
3356
  __publicField(this, "deviceId");
3901
3357
  __publicField(this, "heartbeat");
3902
3358
  __publicField(this, "lastFixAt", 0);
@@ -3914,6 +3370,15 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3914
3370
  __publicField(this, "watchId");
3915
3371
  __publicField(this, "watchStartedAt", 0);
3916
3372
  __publicField(this, "options");
3373
+ __publicField(this, "onSocketMessage", (event) => {
3374
+ const ack = event == null ? void 0 : event.gps;
3375
+ if (!ack || ack.seq == null) return;
3376
+ const pending = this.ackResolvers.get(Number(ack.seq));
3377
+ if (!pending) return;
3378
+ clearTimeout(pending.timeout);
3379
+ this.ackResolvers.delete(Number(ack.seq));
3380
+ pending.resolve(ack);
3381
+ });
3917
3382
  __publicField(this, "onPosition", (position) => {
3918
3383
  if (!this.options) return;
3919
3384
  if (!this.isFresh(position)) return;
@@ -3969,18 +3434,23 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3969
3434
  this.unsubs.forEach((unsub) => unsub());
3970
3435
  this.unsubs = [];
3971
3436
  this.pendingPayloads = [];
3437
+ this.clearAckResolvers("GPS stopped");
3972
3438
  (_a = this.socket) == null ? void 0 : _a.close();
3973
3439
  this.socket = void 0;
3974
3440
  }
3975
- /** Send a raw GPS message. Useful when an app already has its own GPS engine */
3976
- send(position, extra = {}) {
3441
+ /**
3442
+ * Send a position from an app-owned GPS engine.
3443
+ * By default returns after the message is queued/sent; set `waitForAck` to await server confirmation.
3444
+ */
3445
+ send(position, extra = {}, options = {}) {
3977
3446
  if (!this.options) throw new Error("Datalynk GPS is not configured");
3978
3447
  if (!this.api.token) throw new Error("Datalynk GPS cannot send before login");
3979
3448
  this.connectSocket();
3980
- this.sendPayload({
3449
+ const waitForAck = options.waitForAck ?? this.options.waitForAck ?? false;
3450
+ return this.sendPayload({
3981
3451
  ...extra,
3982
3452
  position: this.normalizePosition(position)
3983
- });
3453
+ }, waitForAck);
3984
3454
  }
3985
3455
  /** Listen to GPS updates for the configured slice/field */
3986
3456
  listen(callback, options = {}) {
@@ -4020,12 +3490,18 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4020
3490
  document.removeEventListener("visibilitychange", restart);
4021
3491
  });
4022
3492
  }
3493
+ clearAckResolvers(reason) {
3494
+ this.ackResolvers.forEach(({ timeout, resolve }) => {
3495
+ clearTimeout(timeout);
3496
+ resolve({ ok: false, error: reason });
3497
+ });
3498
+ this.ackResolvers.clear();
3499
+ }
4023
3500
  connectSocket() {
4024
3501
  if (!this.options || this.socket) return;
4025
3502
  if (!this.api.token) throw new Error("Datalynk GPS socket cannot connect before login");
4026
3503
  this.socket = new Socket(this.api, { url: this.options.socketUrl });
4027
- this.unsubs.push(this.socket.addListener(() => {
4028
- }, () => this.flushPendingPayloads()));
3504
+ this.unsubs.push(this.socket.addListener(this.onSocketMessage, () => this.flushPendingPayloads()));
4029
3505
  }
4030
3506
  createId() {
4031
3507
  if (typeof crypto != "undefined" && crypto.randomUUID) return crypto.randomUUID();
@@ -4142,10 +3618,11 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4142
3618
  this.watchId = void 0;
4143
3619
  this.startWatch();
4144
3620
  }
4145
- sendPayload(payload) {
3621
+ sendPayload(payload, waitForAck = false) {
4146
3622
  var _a, _b;
4147
- if (!this.options) return;
3623
+ if (!this.options) return Promise.resolve({ ok: false, error: "Datalynk GPS is not configured" });
4148
3624
  this.connectSocket();
3625
+ const seq = ++this.seq;
4149
3626
  const message = {
4150
3627
  gps: {
4151
3628
  slice: this.options.slice,
@@ -4154,7 +3631,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4154
3631
  trackerId: this.trackerId,
4155
3632
  sessionId: this.sessionId,
4156
3633
  deviceId: this.deviceId,
4157
- seq: ++this.seq,
3634
+ seq,
4158
3635
  client: this.getClientDetails(),
4159
3636
  sentAt: (/* @__PURE__ */ new Date()).toISOString(),
4160
3637
  gpsTimestamp: ((_a = payload.position) == null ? void 0 : _a.timestamp) || null,
@@ -4163,12 +3640,24 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4163
3640
  ...payload
4164
3641
  }
4165
3642
  };
3643
+ let ackPromise;
3644
+ if (waitForAck) {
3645
+ const timeoutMs = this.options.ackTimeoutMs ?? _Gps.GPS_ACK_TIMEOUT_MS;
3646
+ ackPromise = new Promise((resolve) => {
3647
+ const timeout = setTimeout(() => {
3648
+ this.ackResolvers.delete(seq);
3649
+ resolve({ ok: false, error: "GPS ack timeout", seq });
3650
+ }, timeoutMs);
3651
+ this.ackResolvers.set(seq, { resolve, timeout });
3652
+ });
3653
+ }
4166
3654
  if (!((_b = this.socket) == null ? void 0 : _b.open)) {
4167
3655
  this.pendingPayloads.push(message);
4168
3656
  this.pendingPayloads = this.pendingPayloads.slice(-25);
4169
- return;
3657
+ return ackPromise ?? Promise.resolve({ ok: true, seq, pending: true });
4170
3658
  }
4171
3659
  this.socket.send(message);
3660
+ return ackPromise ?? Promise.resolve({ ok: true, seq, pending: false });
4172
3661
  }
4173
3662
  sendPosition(position, force) {
4174
3663
  this.sendPayload({
@@ -4239,7 +3728,11 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4239
3728
  return;
4240
3729
  }
4241
3730
  const sub = this.api.token$.subscribe((token) => {
4242
- if (!token || this.started || !this.options || this.options.autoStart === false) return;
3731
+ if (!token) {
3732
+ if (this.started) this.stop();
3733
+ return;
3734
+ }
3735
+ if (this.started || !this.options || this.options.autoStart === false) return;
4243
3736
  try {
4244
3737
  this.start();
4245
3738
  } catch (error) {
@@ -4248,6 +3741,16 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4248
3741
  });
4249
3742
  this.unsubs.push(() => sub.unsubscribe());
4250
3743
  }
3744
+ };
3745
+ __publicField(_Gps, "GPS_ACK_TIMEOUT_MS", 15e3);
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
+ }
4251
3754
  }
4252
3755
  const _Api = class _Api {
4253
3756
  /**
@@ -4272,6 +3775,13 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4272
3775
  target: "version.php",
4273
3776
  timeout: 6e4
4274
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);
4275
3785
  /** LocalStorage key for persisting logins */
4276
3786
  __publicField(this, "localStorageKey", "datalynk-token");
4277
3787
  /** Pending requests cache */
@@ -4304,7 +3814,20 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4304
3814
  /** Client library version */
4305
3815
  __publicField(this, "version", version);
4306
3816
  __publicField(this, "onlineOverride", false);
4307
- __publicField(this, "online$", new BehaviorSubject(typeof navigator == "undefined" || typeof navigator.onLine == "undefined" ? true : navigator.onLine));
3817
+ __publicField(this, "initialOnline", typeof navigator == "undefined" || typeof navigator.onLine == "undefined" ? true : navigator.onLine);
3818
+ /**
3819
+ * Detailed API connection state.
3820
+ *
3821
+ * Subscribe to this when callers need to distinguish physical/network
3822
+ * offline state from authentication failure or server/API unavailability.
3823
+ */
3824
+ __publicField(this, "status$", new BehaviorSubject(this.initialOnline ? "online" : "offline"));
3825
+ /**
3826
+ * Backwards-compatible boolean connection state.
3827
+ *
3828
+ * `false` includes `offline`, `unauthorized`, and `unavailable` states.
3829
+ */
3830
+ __publicField(this, "online$", new BehaviorSubject(this.initialOnline));
4308
3831
  /** API Session token */
4309
3832
  __publicField(this, "token$", new BehaviorSubject(void 0));
4310
3833
  var _a, _b;
@@ -4325,14 +3848,14 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4325
3848
  ...options.webrtc || {}
4326
3849
  }
4327
3850
  };
4328
- if (this.options.saveSession) {
4329
- if (typeof localStorage == "undefined") return;
3851
+ if (this.options.saveSession && typeof localStorage != "undefined") {
4330
3852
  this.token = localStorage.getItem(this.localStorageKey) || null;
4331
3853
  this.token$.pipe(distinctUntilChanged()).subscribe((token) => {
4332
3854
  if (token) localStorage.setItem(this.localStorageKey, token);
4333
3855
  else localStorage.removeItem(this.localStorageKey);
4334
3856
  });
4335
3857
  }
3858
+ this.token$.pipe(distinctUntilChanged()).subscribe((token) => this.scheduleTokenExpiry(token));
4336
3859
  this.socket = new Socket(this, { url: options.socket });
4337
3860
  this.gps = new Gps(this, options.gps);
4338
3861
  this.auth = new Auth(this);
@@ -4355,12 +3878,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4355
3878
  });
4356
3879
  }
4357
3880
  if (typeof window !== "undefined") {
4358
- const handleOffline = (state = this.offline) => {
4359
- if (this.online != state) this.online$.next(state);
4360
- };
4361
- window.addEventListener("online", () => handleOffline(true));
4362
- window.addEventListener("offline", () => handleOffline(false));
3881
+ window.addEventListener("online", () => this.checkConnection());
3882
+ window.addEventListener("offline", () => this.setConnectionStatus("offline"));
4363
3883
  this.online$.subscribe(() => this.offlineBanner());
3884
+ this.startHeartbeat();
4364
3885
  }
4365
3886
  if ((_a = this.options.offline) == null ? void 0 : _a.length) {
4366
3887
  this.pwa.setup();
@@ -4393,26 +3914,50 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4393
3914
  /** Get session info from JWT payload */
4394
3915
  get jwtPayload() {
4395
3916
  if (!this.token) return null;
4396
- return decodeJwt(this.token);
3917
+ try {
3918
+ return decodeJwt(this.token);
3919
+ } catch {
3920
+ return null;
3921
+ }
4397
3922
  }
4398
- /** Check if we are connected */
3923
+ /** Current detailed Datalynk API connection state. */
3924
+ get status() {
3925
+ return this.status$.getValue();
3926
+ }
3927
+ /**
3928
+ * Whether normal Datalynk network requests are currently available.
3929
+ *
3930
+ * This becomes `false` for network outages, rejected/expired sessions, and
3931
+ * request-owned server recovery. It therefore describes Datalynk
3932
+ * availability rather than only `navigator.onLine`.
3933
+ */
4399
3934
  get online() {
4400
3935
  return this.online$.getValue();
4401
3936
  }
3937
+ /**
3938
+ * Whether normal Datalynk API access is currently unavailable.
3939
+ *
3940
+ * This is the inverse of {@link online}. It can be `true` while the browser
3941
+ * still has Internet access, for example during MySQL/HTTP 5xx recovery.
3942
+ */
4402
3943
  get offline() {
4403
3944
  return !this.online;
4404
3945
  }
4405
- /** Override connection status */
3946
+ /**
3947
+ * Override the boolean connection state.
3948
+ *
3949
+ * Set `true` or `false` to force the corresponding state. Set `null` to
3950
+ * remove the override and resume normal connection checking. This is a
3951
+ * manual override and can supersede the current recovery state, so normal
3952
+ * applications should generally observe {@link status} instead of forcing it.
3953
+ */
4406
3954
  set online(value) {
4407
3955
  if (value == null) {
4408
3956
  this.onlineOverride = false;
4409
- this.online$.next(typeof navigator == "undefined" ? true : navigator.onLine);
3957
+ this.checkConnection();
4410
3958
  } else {
4411
3959
  this.onlineOverride = true;
4412
- if (value == this.online) return;
4413
- this.online$.next(value);
4414
- if (value) this.startHeartbeat();
4415
- else this.stopHeartbeat();
3960
+ this.setConnectionStatus(value ? "online" : "offline");
4416
3961
  }
4417
3962
  }
4418
3963
  /** Logged in spoke */
@@ -4424,56 +3969,246 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4424
3969
  return this.token$.getValue();
4425
3970
  }
4426
3971
  set token(token) {
3972
+ if (this.recovery && token !== this.recovery.token)
3973
+ this.cancelRecovery(errorFromCode(401, "Session changed during API recovery"));
3974
+ if (token && this.isTokenExpired(token)) {
3975
+ this.authenticationInvalid = true;
3976
+ this.token$.next(null);
3977
+ this.setConnectionStatus("unauthorized");
3978
+ return;
3979
+ }
3980
+ this.authenticationInvalid = false;
4427
3981
  this.token$.next(token);
3982
+ if (token && !this.online && !this.recovery) {
3983
+ this.setConnectionStatus(typeof navigator == "undefined" || navigator.onLine ? "online" : "offline");
3984
+ }
4428
3985
  }
4429
- _request(req, options = {}) {
3986
+ async _request(req, options = {}) {
3987
+ if (this.recovery) throw errorFromCode(503, "Datalynk is unavailable");
3988
+ const retryRequest = deepCopy(req);
3989
+ const retryOptions = { ...options };
4430
3990
  const token = options.token || this.token;
4431
- return fetch(this.url, {
4432
- method: "POST",
4433
- headers: clean({
4434
- Authorization: token ? `Bearer ${token}` : void 0,
4435
- "Content-Type": "application/json",
4436
- "X-Date-Return-Format": this.options.legacyDates ? void 0 : "ISO8601"
4437
- }),
4438
- body: JSON.stringify(_Api.translateTokens(req))
4439
- }).then(async (resp) => {
4440
- const warning = resp.headers["X-Warning"];
4441
- if (warning) console.warn(warning);
4442
- const banner = resp.headers["X-User-Notice"];
4443
- if (banner) {
4444
- createBanner(banner);
4445
- setTimeout(() => removeBanner(), 1e4);
3991
+ try {
3992
+ return await this._requestOnce(req, options, false);
3993
+ } catch (error) {
3994
+ if (!this.isRecoverableResponseError(error)) throw error;
3995
+ this.beginRecovery(retryRequest, retryOptions, token);
3996
+ throw error;
3997
+ }
3998
+ }
3999
+ /** Execute exactly one HTTP API attempt. Recovery uses this directly to avoid recursive retry loops. */
4000
+ async _requestOnce(req, options = {}, recoveryAttempt = false) {
4001
+ const token = options.token || this.token;
4002
+ if (token && this.isTokenExpired(token)) {
4003
+ this.markUnauthorized(token);
4004
+ throw errorFromCode(401, "Session token expired");
4005
+ }
4006
+ let resp;
4007
+ try {
4008
+ resp = await fetch(this.url, {
4009
+ method: "POST",
4010
+ headers: clean({
4011
+ Authorization: token ? `Bearer ${token}` : void 0,
4012
+ "Content-Type": "application/json",
4013
+ "X-Date-Return-Format": this.options.legacyDates ? void 0 : "ISO8601"
4014
+ }),
4015
+ body: JSON.stringify(_Api.translateTokens(req))
4016
+ });
4017
+ } catch (error) {
4018
+ this.setConnectionStatus("offline");
4019
+ throw error;
4020
+ }
4021
+ const warning = resp.headers.get("X-Warning");
4022
+ if (warning) console.warn(warning);
4023
+ const banner = resp.headers.get("X-User-Notice");
4024
+ if (banner) {
4025
+ createBanner(banner);
4026
+ setTimeout(() => removeBanner(), 1e4);
4027
+ }
4028
+ const body = await resp.text();
4029
+ let data;
4030
+ try {
4031
+ data = JSON.parse(body);
4032
+ } catch {
4033
+ throw new UnexpectedApiResponseError(resp, body);
4034
+ }
4035
+ if (resp.status === 401) this.markUnauthorized(token);
4036
+ if (!resp.ok || (data == null ? void 0 : data.error)) {
4037
+ const error = Object.assign(errorFromCode(resp.status, data == null ? void 0 : data.error), data);
4038
+ Object.defineProperty(error, "response", { value: resp, configurable: true });
4039
+ if (!recoveryAttempt && resp.status < 500 && resp.status !== 401 && !this.isMysqlError(error))
4040
+ this.setConnectionStatus("online");
4041
+ throw error;
4042
+ }
4043
+ if (!options.raw) data = _Api.translateTokens(data);
4044
+ if (!recoveryAttempt) this.setConnectionStatus("online");
4045
+ return data;
4046
+ }
4047
+ /** Only server-response failures own global recovery; auth and ordinary 4xx errors do not. */
4048
+ isRecoverableResponseError(error) {
4049
+ var _a;
4050
+ if (error instanceof UnexpectedApiResponseError) return true;
4051
+ if ((error == null ? void 0 : error.code) === 401) return false;
4052
+ const status = ((_a = error == null ? void 0 : error.response) == null ? void 0 : _a.status) ?? (error == null ? void 0 : error.code);
4053
+ return status >= 500 || this.isMysqlError(error);
4054
+ }
4055
+ isMysqlError(error) {
4056
+ const message = String((error == null ? void 0 : error.error) ?? (error == null ? void 0 : error.message) ?? "");
4057
+ return /\bSQLSTATE\[[A-Z0-9]+\]/i.test(message);
4058
+ }
4059
+ /**
4060
+ * A returned server failure immediately makes the client unavailable. The exact
4061
+ * failed request is then retried twice back-to-back. After that, retries are
4062
+ * scheduled 30 seconds after each completed failed attempt, never with overlap.
4063
+ */
4064
+ beginRecovery(req, options, token) {
4065
+ if (this.recovery) return;
4066
+ this.setConnectionStatus("unavailable");
4067
+ this.recovery = {
4068
+ request: deepCopy(req),
4069
+ options: { ...options },
4070
+ token,
4071
+ timer: null,
4072
+ running: false
4073
+ };
4074
+ void this.runImmediateRecovery();
4075
+ }
4076
+ async runImmediateRecovery() {
4077
+ for (let attempt = 0; attempt < this.recoveryImmediateRetries; attempt++) {
4078
+ const result = await this.tryRecoveryOnce();
4079
+ if (result.ok) {
4080
+ this.finishRecovery();
4081
+ return;
4446
4082
  }
4447
- this.online = true;
4448
- let data = JSONAttemptParse(await resp.text());
4449
- if (!resp.ok || (data == null ? void 0 : data.error)) throw Object.assign(errorFromCode(resp.status, data.error), data);
4450
- if (!options.raw) data = _Api.translateTokens(data);
4451
- return data;
4452
- });
4083
+ if (!this.recovery || this.status === "unauthorized") return;
4084
+ }
4085
+ if (this.recovery) this.scheduleRecovery();
4086
+ }
4087
+ async tryRecoveryOnce() {
4088
+ const recovery = this.recovery;
4089
+ if (!recovery) return { ok: false, error: errorFromCode(503, "API recovery was cancelled") };
4090
+ if (recovery.running) return { ok: false, error: errorFromCode(503, "API recovery request is already running") };
4091
+ if (recovery.token !== (recovery.options.token || this.token)) {
4092
+ const error = errorFromCode(401, "Session changed during API recovery");
4093
+ this.cancelRecovery(error);
4094
+ return { ok: false, error };
4095
+ }
4096
+ recovery.running = true;
4097
+ try {
4098
+ const value = await this._requestOnce(
4099
+ deepCopy(recovery.request),
4100
+ { ...recovery.options, token: recovery.token || void 0 },
4101
+ true
4102
+ );
4103
+ return { ok: true, value };
4104
+ } catch (error) {
4105
+ return { ok: false, error };
4106
+ } finally {
4107
+ if (this.recovery === recovery) recovery.running = false;
4108
+ }
4109
+ }
4110
+ scheduleRecovery() {
4111
+ const recovery = this.recovery;
4112
+ if (!recovery) return;
4113
+ if (recovery.timer) clearTimeout(recovery.timer);
4114
+ recovery.timer = setTimeout(async () => {
4115
+ const current = this.recovery;
4116
+ if (!current || current !== recovery) return;
4117
+ current.timer = null;
4118
+ const result = await this.tryRecoveryOnce();
4119
+ if (result.ok) {
4120
+ this.finishRecovery();
4121
+ return;
4122
+ }
4123
+ if (this.recovery && this.status !== "unauthorized") this.scheduleRecovery();
4124
+ }, this.recoveryRetryInterval);
4125
+ }
4126
+ finishRecovery() {
4127
+ const recovery = this.recovery;
4128
+ if (!recovery) return;
4129
+ if (recovery.timer) clearTimeout(recovery.timer);
4130
+ this.recovery = null;
4131
+ this.setConnectionStatus("online");
4132
+ }
4133
+ cancelRecovery(_error) {
4134
+ const recovery = this.recovery;
4135
+ if (!recovery) return;
4136
+ if (recovery.timer) clearTimeout(recovery.timer);
4137
+ this.recovery = null;
4453
4138
  }
4454
4139
  async checkConnection() {
4140
+ if (this.recovery) return;
4455
4141
  if (typeof navigator != "undefined" && !navigator.onLine) {
4456
- this.online$.next(false);
4142
+ this.setConnectionStatus("offline");
4457
4143
  return;
4458
4144
  }
4459
4145
  if (this.onlineOverride) return;
4146
+ if (this.authenticationInvalid) {
4147
+ this.setConnectionStatus("unauthorized");
4148
+ return;
4149
+ }
4150
+ if (this.token && this.isTokenExpired(this.token)) {
4151
+ this.markUnauthorized(this.token);
4152
+ return;
4153
+ }
4460
4154
  const controller = new AbortController();
4461
4155
  const timeout = setTimeout(() => controller.abort(), this.heartbeat.timeout);
4462
4156
  try {
4463
4157
  const response = await fetch(this.url + this.heartbeat.target, { signal: controller.signal });
4464
- this.online$.next(response.ok);
4158
+ if (this.recovery) return;
4159
+ this.setConnectionStatus(response.ok ? "online" : "unavailable");
4465
4160
  } catch (error) {
4466
- this.online$.next(false);
4161
+ if (!this.recovery) this.setConnectionStatus("offline");
4467
4162
  } finally {
4468
4163
  clearTimeout(timeout);
4469
4164
  }
4470
4165
  }
4166
+ isTokenExpired(token) {
4167
+ var _a;
4168
+ try {
4169
+ return (((_a = decodeJwt(token)) == null ? void 0 : _a.exp) ?? 0) * 1e3 <= Date.now();
4170
+ } catch {
4171
+ return true;
4172
+ }
4173
+ }
4174
+ scheduleTokenExpiry(token) {
4175
+ var _a, _b, _c;
4176
+ if (this.tokenExpiryTimeout) clearTimeout(this.tokenExpiryTimeout);
4177
+ this.tokenExpiryTimeout = null;
4178
+ if (!token) return;
4179
+ let expiresAt = 0;
4180
+ try {
4181
+ expiresAt = (((_a = decodeJwt(token)) == null ? void 0 : _a.exp) ?? 0) * 1e3;
4182
+ } catch {
4183
+ return this.markUnauthorized(token);
4184
+ }
4185
+ const delay = expiresAt - Date.now();
4186
+ if (delay <= 0) return this.markUnauthorized(token);
4187
+ this.tokenExpiryTimeout = setTimeout(() => {
4188
+ if (delay > 2147e6) this.scheduleTokenExpiry(token);
4189
+ else this.markUnauthorized(token);
4190
+ }, Math.min(delay, 2147e6));
4191
+ (_c = (_b = this.tokenExpiryTimeout) == null ? void 0 : _b.unref) == null ? void 0 : _c.call(_b);
4192
+ }
4193
+ markUnauthorized(token) {
4194
+ if (token && token !== this.token) return;
4195
+ this.authenticationInvalid = true;
4196
+ this.cancelRecovery(errorFromCode(401, "Session expired"));
4197
+ if (this.token != null) this.token$.next(null);
4198
+ this.setConnectionStatus("unauthorized");
4199
+ }
4200
+ setConnectionStatus(status) {
4201
+ if (this.status !== status) this.status$.next(status);
4202
+ const online = status === "online";
4203
+ if (this.online !== online) this.online$.next(online);
4204
+ }
4471
4205
  offlineBanner() {
4472
4206
  if (this.options.offlineBanner === false || typeof document == "undefined") return;
4473
- if (this.online) {
4207
+ if (this.status === "online") {
4474
4208
  removeBanner("datalynk-offline-banner");
4475
4209
  } else {
4476
- createBanner("⚠️ You are offline, please reconnect to sync changes", {
4210
+ 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";
4211
+ createBanner(message, {
4477
4212
  id: "datalynk-offline-banner",
4478
4213
  position: this.options.offlineBanner === "top" ? "top" : "bottom"
4479
4214
  });
@@ -4585,9 +4320,13 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4585
4320
  var _a, _b;
4586
4321
  data = typeof data == "string" ? { [data]: {} } : data;
4587
4322
  let key = JSON.stringify(data);
4588
- if (this.offline) {
4589
- (_b = (_a = this.database) == null ? void 0 : _a.table("pending")) == null ? void 0 : _b.add(data, key);
4590
- return Promise.resolve();
4323
+ if (this.offline && typeof navigator != "undefined") {
4324
+ if (this.status === "unauthorized") return Promise.reject(errorFromCode(401, "Session expired"));
4325
+ if (options.offline) {
4326
+ (_b = (_a = this.database) == null ? void 0 : _a.table("pending")) == null ? void 0 : _b.add(data, key);
4327
+ return Promise.resolve();
4328
+ }
4329
+ return Promise.reject(errorFromCode(503, this.status === "unavailable" ? "Datalynk is unavailable" : "You are offline"));
4591
4330
  }
4592
4331
  if (options.noOptimize) {
4593
4332
  return new Promise((res, rej) => {
@@ -4598,7 +4337,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4598
4337
  }
4599
4338
  if (!this.pending[key]) {
4600
4339
  this.pending[key] = new Promise((res, rej) => this.bundle.push({ data, res, rej }));
4601
- this.pending[key].catch().then(() => delete this.pending[key]);
4340
+ this.pending[key].then(() => delete this.pending[key], () => delete this.pending[key]);
4602
4341
  if (!this.bundleOngoing) {
4603
4342
  this.bundleOngoing = true;
4604
4343
  setTimeout(() => {
@@ -4608,7 +4347,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4608
4347
  data = originalBundle.map((row) => row.data);
4609
4348
  this._request(data, options).then((resp) => {
4610
4349
  if (!(resp instanceof Array)) resp = [resp];
4611
- resp.forEach((row, i) => (row == null ? void 0 : row.error) ? originalBundle[i].rej(row.error) : originalBundle[i].res(row));
4350
+ originalBundle.forEach((request, index) => {
4351
+ const row = resp[index];
4352
+ (row == null ? void 0 : row.error) ? request.rej(row.error) : request.res(row);
4353
+ });
4612
4354
  }).catch((err) => originalBundle.forEach((req) => req.rej(err)));
4613
4355
  }, this.options.bundleTime);
4614
4356
  }
@@ -4648,6 +4390,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4648
4390
  exports2.Slice = Slice;
4649
4391
  exports2.Socket = Socket;
4650
4392
  exports2.Superuser = Superuser;
4393
+ exports2.UnexpectedApiResponseError = UnexpectedApiResponseError;
4651
4394
  exports2.getTheme = getTheme;
4652
4395
  Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
4653
4396
  });