@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.d.cts CHANGED
@@ -1115,6 +1115,8 @@ interface MushiSDKInstance {
1115
1115
  hide(): void;
1116
1116
  attachTo(selectorOrElement: string | Element, options?: MushiWidgetConfig): () => void;
1117
1117
  setTrigger(trigger: NonNullable<MushiWidgetConfig['trigger']>): void;
1118
+ /** Open the widget directly to the reporter's "My reports" history view. */
1119
+ openReporter(): void;
1118
1120
  close(): void;
1119
1121
  destroy(): void;
1120
1122
  updateConfig(config: MushiRuntimeSdkConfig): void;
@@ -1551,6 +1553,9 @@ interface ApiClientOptions {
1551
1553
  apiEndpoint?: string;
1552
1554
  timeout?: number;
1553
1555
  maxRetries?: number;
1556
+ /** When set, sent on every request so heartbeats record the running SDK version. */
1557
+ sdkPackage?: string;
1558
+ sdkVersion?: string;
1554
1559
  /**
1555
1560
  * Returns the current signed end-user identity JWT, or null when the user is
1556
1561
  * anonymous. When present it is sent on the X-Mushi-User-Token header so the
package/dist/index.d.ts CHANGED
@@ -1115,6 +1115,8 @@ interface MushiSDKInstance {
1115
1115
  hide(): void;
1116
1116
  attachTo(selectorOrElement: string | Element, options?: MushiWidgetConfig): () => void;
1117
1117
  setTrigger(trigger: NonNullable<MushiWidgetConfig['trigger']>): void;
1118
+ /** Open the widget directly to the reporter's "My reports" history view. */
1119
+ openReporter(): void;
1118
1120
  close(): void;
1119
1121
  destroy(): void;
1120
1122
  updateConfig(config: MushiRuntimeSdkConfig): void;
@@ -1551,6 +1553,9 @@ interface ApiClientOptions {
1551
1553
  apiEndpoint?: string;
1552
1554
  timeout?: number;
1553
1555
  maxRetries?: number;
1556
+ /** When set, sent on every request so heartbeats record the running SDK version. */
1557
+ sdkPackage?: string;
1558
+ sdkVersion?: string;
1554
1559
  /**
1555
1560
  * Returns the current signed end-user identity JWT, or null when the user is
1556
1561
  * anonymous. When present it is sent on the X-Mushi-User-Token header so the
package/dist/index.js CHANGED
@@ -42,6 +42,8 @@ function formatBytes(n) {
42
42
 
43
43
  // src/api-client.ts
44
44
  var MUSHI_USER_TOKEN_HEADER = "X-Mushi-User-Token";
45
+ var MUSHI_SDK_PACKAGE_HEADER = "X-Mushi-SDK-Package";
46
+ var MUSHI_SDK_VERSION_HEADER = "X-Mushi-SDK-Version";
45
47
  var DEFAULT_API_ENDPOINT = "https://dxptnwrhwsqckaftyymj.supabase.co/functions/v1/api";
46
48
  var MUSHI_INTERNAL_HEADER = "X-Mushi-Internal";
47
49
  var MUSHI_INTERNAL_INIT_MARKER = "__mushiInternal";
@@ -54,7 +56,9 @@ function createApiClient(options) {
54
56
  apiEndpoint = DEFAULT_API_ENDPOINT,
55
57
  timeout = DEFAULT_TIMEOUT,
56
58
  maxRetries = DEFAULT_MAX_RETRIES,
57
- getUserToken
59
+ getUserToken,
60
+ sdkPackage,
61
+ sdkVersion
58
62
  } = options;
59
63
  let baseUrl = apiEndpoint.replace(/\/$/, "");
60
64
  async function request(method, path, body, retries = maxRetries, internalKind, extraHeaders) {
@@ -69,6 +73,8 @@ function createApiClient(options) {
69
73
  "Content-Type": "application/json",
70
74
  "X-Mushi-Api-Key": apiKey,
71
75
  "X-Mushi-Project": projectId,
76
+ ...sdkPackage ? { [MUSHI_SDK_PACKAGE_HEADER]: sdkPackage } : {},
77
+ ...sdkVersion ? { [MUSHI_SDK_VERSION_HEADER]: sdkVersion } : {},
72
78
  ...userToken ? { [MUSHI_USER_TOKEN_HEADER]: userToken } : {},
73
79
  ...internalKind ? { [MUSHI_INTERNAL_HEADER]: internalKind } : {},
74
80
  ...extraHeaders
@@ -131,6 +137,8 @@ function createApiClient(options) {
131
137
  "Content-Type": "application/json",
132
138
  "X-Mushi-Api-Key": apiKey,
133
139
  "X-Mushi-Project": projectId,
140
+ ...sdkPackage ? { [MUSHI_SDK_PACKAGE_HEADER]: sdkPackage } : {},
141
+ ...sdkVersion ? { [MUSHI_SDK_VERSION_HEADER]: sdkVersion } : {},
134
142
  [MUSHI_INTERNAL_HEADER]: "reporter-poll",
135
143
  "X-Reporter-Token-Hash": tokenHash,
136
144
  "X-Reporter-Ts": ts,
@@ -832,6 +840,8 @@ var LS_KEY = "mushi_offline_queue";
832
840
  var BATCH_SIZE = 10;
833
841
  var MAX_BACKOFF_MS = 6e4;
834
842
  var AUTO_FLUSH_INTERVAL_MS = 3e4;
843
+ var MAX_DELIVERY_ATTEMPTS = 8;
844
+ var MAX_QUEUE_AGE_MS = 24 * 60 * 60 * 1e3;
835
845
  function createOfflineQueue(config = {}) {
836
846
  const { enabled = true, maxQueueSize = 50, syncOnReconnect = true, encryptAtRest = true } = config;
837
847
  let syncCleanup = null;
@@ -864,6 +874,17 @@ function createOfflineQueue(config = {}) {
864
874
  function isEncryptedRecord(row) {
865
875
  return !!row.payload && isEncryptedPayload(row.payload);
866
876
  }
877
+ function rowAttempts(row) {
878
+ const n = row.attempts;
879
+ return typeof n === "number" && Number.isFinite(n) ? n : 0;
880
+ }
881
+ function isExpired(row, now) {
882
+ const queuedAt = row.queuedAt;
883
+ if (!queuedAt) return false;
884
+ const ts = Date.parse(queuedAt);
885
+ if (Number.isNaN(ts)) return false;
886
+ return now - ts > MAX_QUEUE_AGE_MS;
887
+ }
867
888
  function detectBackend() {
868
889
  if (backendType) return backendType;
869
890
  if (typeof indexedDB !== "undefined") {
@@ -919,6 +940,15 @@ function createOfflineQueue(config = {}) {
919
940
  tx.onerror = () => reject(tx.error);
920
941
  });
921
942
  }
943
+ async function idbPutRow(row) {
944
+ const db = await openDb();
945
+ return new Promise((resolve, reject) => {
946
+ const tx = db.transaction(STORE_NAME, "readwrite");
947
+ tx.objectStore(STORE_NAME).put(row);
948
+ tx.oncomplete = () => resolve();
949
+ tx.onerror = () => reject(tx.error);
950
+ });
951
+ }
922
952
  async function idbSize() {
923
953
  const db = await openDb();
924
954
  return new Promise((resolve, reject) => {
@@ -960,6 +990,14 @@ function createOfflineQueue(config = {}) {
960
990
  const rows = lsRead().filter((r) => r.id !== id);
961
991
  lsWrite(rows);
962
992
  }
993
+ function lsUpdateRow(row) {
994
+ const rows = lsRead();
995
+ const idx = rows.findIndex((r) => r.id === row.id);
996
+ if (idx >= 0) {
997
+ rows[idx] = row;
998
+ lsWrite(rows);
999
+ }
1000
+ }
963
1001
  async function enqueue(report) {
964
1002
  if (!enabled) return;
965
1003
  const currentSize = await size();
@@ -1000,6 +1038,23 @@ function createOfflineQueue(config = {}) {
1000
1038
  } else {
1001
1039
  rows = lsRead();
1002
1040
  }
1041
+ const now = Date.now();
1042
+ const fresh = [];
1043
+ for (const row of rows) {
1044
+ if (isExpired(row, now)) {
1045
+ const rowId = row.id;
1046
+ try {
1047
+ if (backend === "indexeddb") await idbDelete(rowId);
1048
+ else lsDelete(rowId);
1049
+ } catch {
1050
+ lsDelete(rowId);
1051
+ }
1052
+ queueLog.debug("Offline queue: evicting stale report", { id: rowId });
1053
+ } else {
1054
+ fresh.push(row);
1055
+ }
1056
+ }
1057
+ rows = fresh;
1003
1058
  const batch = rows.slice(0, BATCH_SIZE);
1004
1059
  let sent = 0;
1005
1060
  let failed = 0;
@@ -1043,10 +1098,33 @@ function createOfflineQueue(config = {}) {
1043
1098
  lsDelete(rowId);
1044
1099
  }
1045
1100
  } else if (transient) {
1046
- queueLog.debug("Offline queue: transient failure, will retry", {
1047
- id: rowId,
1048
- code: result.error?.code
1049
- });
1101
+ const nextAttempts = rowAttempts(row) + 1;
1102
+ if (nextAttempts >= MAX_DELIVERY_ATTEMPTS) {
1103
+ try {
1104
+ if (backend === "indexeddb") await idbDelete(rowId);
1105
+ else lsDelete(rowId);
1106
+ } catch {
1107
+ lsDelete(rowId);
1108
+ }
1109
+ queueLog.warn("Offline queue: giving up on report after repeated failures", {
1110
+ id: rowId,
1111
+ attempts: nextAttempts,
1112
+ code: result.error?.code
1113
+ });
1114
+ } else {
1115
+ row.attempts = nextAttempts;
1116
+ try {
1117
+ if (backend === "indexeddb") await idbPutRow(row);
1118
+ else lsUpdateRow(row);
1119
+ } catch {
1120
+ lsUpdateRow(row);
1121
+ }
1122
+ queueLog.debug("Offline queue: transient failure, will retry", {
1123
+ id: rowId,
1124
+ attempts: nextAttempts,
1125
+ code: result.error?.code
1126
+ });
1127
+ }
1050
1128
  }
1051
1129
  failed++;
1052
1130
  if (i < batch.length - 1) {