@mushi-mushi/core 1.16.0 → 1.18.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
@@ -44,6 +44,8 @@ function formatBytes(n) {
44
44
 
45
45
  // src/api-client.ts
46
46
  var MUSHI_USER_TOKEN_HEADER = "X-Mushi-User-Token";
47
+ var MUSHI_SDK_PACKAGE_HEADER = "X-Mushi-SDK-Package";
48
+ var MUSHI_SDK_VERSION_HEADER = "X-Mushi-SDK-Version";
47
49
  var DEFAULT_API_ENDPOINT = "https://dxptnwrhwsqckaftyymj.supabase.co/functions/v1/api";
48
50
  var MUSHI_INTERNAL_HEADER = "X-Mushi-Internal";
49
51
  var MUSHI_INTERNAL_INIT_MARKER = "__mushiInternal";
@@ -56,7 +58,9 @@ function createApiClient(options) {
56
58
  apiEndpoint = DEFAULT_API_ENDPOINT,
57
59
  timeout = DEFAULT_TIMEOUT,
58
60
  maxRetries = DEFAULT_MAX_RETRIES,
59
- getUserToken
61
+ getUserToken,
62
+ sdkPackage,
63
+ sdkVersion
60
64
  } = options;
61
65
  let baseUrl = apiEndpoint.replace(/\/$/, "");
62
66
  async function request(method, path, body, retries = maxRetries, internalKind, extraHeaders) {
@@ -71,6 +75,8 @@ function createApiClient(options) {
71
75
  "Content-Type": "application/json",
72
76
  "X-Mushi-Api-Key": apiKey,
73
77
  "X-Mushi-Project": projectId,
78
+ ...sdkPackage ? { [MUSHI_SDK_PACKAGE_HEADER]: sdkPackage } : {},
79
+ ...sdkVersion ? { [MUSHI_SDK_VERSION_HEADER]: sdkVersion } : {},
74
80
  ...userToken ? { [MUSHI_USER_TOKEN_HEADER]: userToken } : {},
75
81
  ...internalKind ? { [MUSHI_INTERNAL_HEADER]: internalKind } : {},
76
82
  ...extraHeaders
@@ -133,6 +139,8 @@ function createApiClient(options) {
133
139
  "Content-Type": "application/json",
134
140
  "X-Mushi-Api-Key": apiKey,
135
141
  "X-Mushi-Project": projectId,
142
+ ...sdkPackage ? { [MUSHI_SDK_PACKAGE_HEADER]: sdkPackage } : {},
143
+ ...sdkVersion ? { [MUSHI_SDK_VERSION_HEADER]: sdkVersion } : {},
136
144
  [MUSHI_INTERNAL_HEADER]: "reporter-poll",
137
145
  "X-Reporter-Token-Hash": tokenHash,
138
146
  "X-Reporter-Ts": ts,
@@ -834,6 +842,8 @@ var LS_KEY = "mushi_offline_queue";
834
842
  var BATCH_SIZE = 10;
835
843
  var MAX_BACKOFF_MS = 6e4;
836
844
  var AUTO_FLUSH_INTERVAL_MS = 3e4;
845
+ var MAX_DELIVERY_ATTEMPTS = 8;
846
+ var MAX_QUEUE_AGE_MS = 24 * 60 * 60 * 1e3;
837
847
  function createOfflineQueue(config = {}) {
838
848
  const { enabled = true, maxQueueSize = 50, syncOnReconnect = true, encryptAtRest = true } = config;
839
849
  let syncCleanup = null;
@@ -866,6 +876,17 @@ function createOfflineQueue(config = {}) {
866
876
  function isEncryptedRecord(row) {
867
877
  return !!row.payload && isEncryptedPayload(row.payload);
868
878
  }
879
+ function rowAttempts(row) {
880
+ const n = row.attempts;
881
+ return typeof n === "number" && Number.isFinite(n) ? n : 0;
882
+ }
883
+ function isExpired(row, now) {
884
+ const queuedAt = row.queuedAt;
885
+ if (!queuedAt) return false;
886
+ const ts = Date.parse(queuedAt);
887
+ if (Number.isNaN(ts)) return false;
888
+ return now - ts > MAX_QUEUE_AGE_MS;
889
+ }
869
890
  function detectBackend() {
870
891
  if (backendType) return backendType;
871
892
  if (typeof indexedDB !== "undefined") {
@@ -921,6 +942,15 @@ function createOfflineQueue(config = {}) {
921
942
  tx.onerror = () => reject(tx.error);
922
943
  });
923
944
  }
945
+ async function idbPutRow(row) {
946
+ const db = await openDb();
947
+ return new Promise((resolve, reject) => {
948
+ const tx = db.transaction(STORE_NAME, "readwrite");
949
+ tx.objectStore(STORE_NAME).put(row);
950
+ tx.oncomplete = () => resolve();
951
+ tx.onerror = () => reject(tx.error);
952
+ });
953
+ }
924
954
  async function idbSize() {
925
955
  const db = await openDb();
926
956
  return new Promise((resolve, reject) => {
@@ -962,6 +992,14 @@ function createOfflineQueue(config = {}) {
962
992
  const rows = lsRead().filter((r) => r.id !== id);
963
993
  lsWrite(rows);
964
994
  }
995
+ function lsUpdateRow(row) {
996
+ const rows = lsRead();
997
+ const idx = rows.findIndex((r) => r.id === row.id);
998
+ if (idx >= 0) {
999
+ rows[idx] = row;
1000
+ lsWrite(rows);
1001
+ }
1002
+ }
965
1003
  async function enqueue(report) {
966
1004
  if (!enabled) return;
967
1005
  const currentSize = await size();
@@ -1002,6 +1040,23 @@ function createOfflineQueue(config = {}) {
1002
1040
  } else {
1003
1041
  rows = lsRead();
1004
1042
  }
1043
+ const now = Date.now();
1044
+ const fresh = [];
1045
+ for (const row of rows) {
1046
+ if (isExpired(row, now)) {
1047
+ const rowId = row.id;
1048
+ try {
1049
+ if (backend === "indexeddb") await idbDelete(rowId);
1050
+ else lsDelete(rowId);
1051
+ } catch {
1052
+ lsDelete(rowId);
1053
+ }
1054
+ queueLog.debug("Offline queue: evicting stale report", { id: rowId });
1055
+ } else {
1056
+ fresh.push(row);
1057
+ }
1058
+ }
1059
+ rows = fresh;
1005
1060
  const batch = rows.slice(0, BATCH_SIZE);
1006
1061
  let sent = 0;
1007
1062
  let failed = 0;
@@ -1045,10 +1100,33 @@ function createOfflineQueue(config = {}) {
1045
1100
  lsDelete(rowId);
1046
1101
  }
1047
1102
  } else if (transient) {
1048
- queueLog.debug("Offline queue: transient failure, will retry", {
1049
- id: rowId,
1050
- code: result.error?.code
1051
- });
1103
+ const nextAttempts = rowAttempts(row) + 1;
1104
+ if (nextAttempts >= MAX_DELIVERY_ATTEMPTS) {
1105
+ try {
1106
+ if (backend === "indexeddb") await idbDelete(rowId);
1107
+ else lsDelete(rowId);
1108
+ } catch {
1109
+ lsDelete(rowId);
1110
+ }
1111
+ queueLog.warn("Offline queue: giving up on report after repeated failures", {
1112
+ id: rowId,
1113
+ attempts: nextAttempts,
1114
+ code: result.error?.code
1115
+ });
1116
+ } else {
1117
+ row.attempts = nextAttempts;
1118
+ try {
1119
+ if (backend === "indexeddb") await idbPutRow(row);
1120
+ else lsUpdateRow(row);
1121
+ } catch {
1122
+ lsUpdateRow(row);
1123
+ }
1124
+ queueLog.debug("Offline queue: transient failure, will retry", {
1125
+ id: rowId,
1126
+ attempts: nextAttempts,
1127
+ code: result.error?.code
1128
+ });
1129
+ }
1052
1130
  }
1053
1131
  failed++;
1054
1132
  if (i < batch.length - 1) {