@mushi-mushi/core 1.17.0 → 1.19.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 +94 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +94 -22
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -102,6 +102,20 @@ interface MushiWidgetConfig {
|
|
|
102
102
|
brandFooter?: boolean;
|
|
103
103
|
/** How the widget should surface SDK freshness warnings. Defaults to auto. */
|
|
104
104
|
outdatedBanner?: 'auto' | 'banner' | 'console-only' | 'off';
|
|
105
|
+
/**
|
|
106
|
+
* Privacy nudge shown beside an attached screenshot preview, reminding the
|
|
107
|
+
* reporter to remove anything sensitive (balances, PII, tokens) before they
|
|
108
|
+
* submit. The widget always renders the captured image as a visible preview
|
|
109
|
+
* so the user can see exactly what will be sent and remove it if needed; this
|
|
110
|
+
* flag only controls the accompanying caption.
|
|
111
|
+
* - `true` (default) — show the localized default caption.
|
|
112
|
+
* - string — show this custom caption verbatim.
|
|
113
|
+
* - `false` — hide the caption (the preview + remove control still show).
|
|
114
|
+
*
|
|
115
|
+
* Settable per-host via the SDK config and remotely via the Mushi console
|
|
116
|
+
* runtime config (it travels in the `widget` block of `GET /v1/sdk/config`).
|
|
117
|
+
*/
|
|
118
|
+
screenshotSensitiveHint?: boolean | string;
|
|
105
119
|
/**
|
|
106
120
|
* Beta mode: injects discreet "early access" messaging into the widget panel.
|
|
107
121
|
* Shows a beta strip on the category step and a contact footer on the success
|
package/dist/index.d.ts
CHANGED
|
@@ -102,6 +102,20 @@ interface MushiWidgetConfig {
|
|
|
102
102
|
brandFooter?: boolean;
|
|
103
103
|
/** How the widget should surface SDK freshness warnings. Defaults to auto. */
|
|
104
104
|
outdatedBanner?: 'auto' | 'banner' | 'console-only' | 'off';
|
|
105
|
+
/**
|
|
106
|
+
* Privacy nudge shown beside an attached screenshot preview, reminding the
|
|
107
|
+
* reporter to remove anything sensitive (balances, PII, tokens) before they
|
|
108
|
+
* submit. The widget always renders the captured image as a visible preview
|
|
109
|
+
* so the user can see exactly what will be sent and remove it if needed; this
|
|
110
|
+
* flag only controls the accompanying caption.
|
|
111
|
+
* - `true` (default) — show the localized default caption.
|
|
112
|
+
* - string — show this custom caption verbatim.
|
|
113
|
+
* - `false` — hide the caption (the preview + remove control still show).
|
|
114
|
+
*
|
|
115
|
+
* Settable per-host via the SDK config and remotely via the Mushi console
|
|
116
|
+
* runtime config (it travels in the `widget` block of `GET /v1/sdk/config`).
|
|
117
|
+
*/
|
|
118
|
+
screenshotSensitiveHint?: boolean | string;
|
|
105
119
|
/**
|
|
106
120
|
* Beta mode: injects discreet "early access" messaging into the widget panel.
|
|
107
121
|
* Shows a beta strip on the category step and a contact footer on the success
|
package/dist/index.js
CHANGED
|
@@ -840,6 +840,8 @@ var LS_KEY = "mushi_offline_queue";
|
|
|
840
840
|
var BATCH_SIZE = 10;
|
|
841
841
|
var MAX_BACKOFF_MS = 6e4;
|
|
842
842
|
var AUTO_FLUSH_INTERVAL_MS = 3e4;
|
|
843
|
+
var MAX_DELIVERY_ATTEMPTS = 8;
|
|
844
|
+
var MAX_QUEUE_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
843
845
|
function createOfflineQueue(config = {}) {
|
|
844
846
|
const { enabled = true, maxQueueSize = 50, syncOnReconnect = true, encryptAtRest = true } = config;
|
|
845
847
|
let syncCleanup = null;
|
|
@@ -872,6 +874,17 @@ function createOfflineQueue(config = {}) {
|
|
|
872
874
|
function isEncryptedRecord(row) {
|
|
873
875
|
return !!row.payload && isEncryptedPayload(row.payload);
|
|
874
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
|
+
}
|
|
875
888
|
function detectBackend() {
|
|
876
889
|
if (backendType) return backendType;
|
|
877
890
|
if (typeof indexedDB !== "undefined") {
|
|
@@ -927,6 +940,15 @@ function createOfflineQueue(config = {}) {
|
|
|
927
940
|
tx.onerror = () => reject(tx.error);
|
|
928
941
|
});
|
|
929
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
|
+
}
|
|
930
952
|
async function idbSize() {
|
|
931
953
|
const db = await openDb();
|
|
932
954
|
return new Promise((resolve, reject) => {
|
|
@@ -968,6 +990,38 @@ function createOfflineQueue(config = {}) {
|
|
|
968
990
|
const rows = lsRead().filter((r) => r.id !== id);
|
|
969
991
|
lsWrite(rows);
|
|
970
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
|
+
}
|
|
1001
|
+
async function removeRow(backend, rowId) {
|
|
1002
|
+
try {
|
|
1003
|
+
if (backend === "indexeddb") await idbDelete(rowId);
|
|
1004
|
+
else lsDelete(rowId);
|
|
1005
|
+
} catch (err) {
|
|
1006
|
+
queueLog.debug("Offline queue: row removal failed (will age out)", {
|
|
1007
|
+
id: rowId,
|
|
1008
|
+
err: String(err)
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
async function persistRow(backend, row) {
|
|
1013
|
+
try {
|
|
1014
|
+
if (backend === "indexeddb") await idbPutRow(row);
|
|
1015
|
+
else lsUpdateRow(row);
|
|
1016
|
+
return true;
|
|
1017
|
+
} catch (err) {
|
|
1018
|
+
queueLog.debug("Offline queue: row persist failed", {
|
|
1019
|
+
id: row.id,
|
|
1020
|
+
err: String(err)
|
|
1021
|
+
});
|
|
1022
|
+
return false;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
971
1025
|
async function enqueue(report) {
|
|
972
1026
|
if (!enabled) return;
|
|
973
1027
|
const currentSize = await size();
|
|
@@ -1008,6 +1062,18 @@ function createOfflineQueue(config = {}) {
|
|
|
1008
1062
|
} else {
|
|
1009
1063
|
rows = lsRead();
|
|
1010
1064
|
}
|
|
1065
|
+
const now = Date.now();
|
|
1066
|
+
const fresh = [];
|
|
1067
|
+
for (const row of rows) {
|
|
1068
|
+
if (isExpired(row, now)) {
|
|
1069
|
+
const rowId = row.id;
|
|
1070
|
+
await removeRow(backend, rowId);
|
|
1071
|
+
queueLog.debug("Offline queue: evicting stale report", { id: rowId });
|
|
1072
|
+
} else {
|
|
1073
|
+
fresh.push(row);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
rows = fresh;
|
|
1011
1077
|
const batch = rows.slice(0, BATCH_SIZE);
|
|
1012
1078
|
let sent = 0;
|
|
1013
1079
|
let failed = 0;
|
|
@@ -1016,23 +1082,13 @@ function createOfflineQueue(config = {}) {
|
|
|
1016
1082
|
const rowId = row.id;
|
|
1017
1083
|
const report = await unwrapForSend(row);
|
|
1018
1084
|
if (!report) {
|
|
1019
|
-
|
|
1020
|
-
if (backend === "indexeddb") await idbDelete(rowId);
|
|
1021
|
-
else lsDelete(rowId);
|
|
1022
|
-
} catch {
|
|
1023
|
-
lsDelete(rowId);
|
|
1024
|
-
}
|
|
1085
|
+
await removeRow(backend, rowId);
|
|
1025
1086
|
failed++;
|
|
1026
1087
|
continue;
|
|
1027
1088
|
}
|
|
1028
1089
|
const result = await client.submitReport(report);
|
|
1029
1090
|
if (result.ok) {
|
|
1030
|
-
|
|
1031
|
-
if (backend === "indexeddb") await idbDelete(rowId);
|
|
1032
|
-
else lsDelete(rowId);
|
|
1033
|
-
} catch {
|
|
1034
|
-
lsDelete(rowId);
|
|
1035
|
-
}
|
|
1091
|
+
await removeRow(backend, rowId);
|
|
1036
1092
|
sent++;
|
|
1037
1093
|
} else {
|
|
1038
1094
|
const permanent = result.error?.code === "HTTP_400" || result.error?.code === "HTTP_413" || result.error?.code === "HTTP_422" || result.error?.code === "INGEST_ERROR" || result.error?.code === "VALIDATION_ERROR" || // A payload that exceeds the size guard will never shrink on its own;
|
|
@@ -1044,17 +1100,33 @@ function createOfflineQueue(config = {}) {
|
|
|
1044
1100
|
);
|
|
1045
1101
|
const transient = !permanent && (result.error?.code === "NETWORK_ERROR" || result.error?.code === "HTTP_403" || result.error?.code === "HTTP_429" || result.error?.code === "HTTP_502" || result.error?.code === "HTTP_503" || result.error?.code === "HTTP_504" || typeof result.error?.code === "string" && result.error.code.startsWith("HTTP_5"));
|
|
1046
1102
|
if (permanent) {
|
|
1047
|
-
|
|
1048
|
-
if (backend === "indexeddb") await idbDelete(rowId);
|
|
1049
|
-
else lsDelete(rowId);
|
|
1050
|
-
} catch {
|
|
1051
|
-
lsDelete(rowId);
|
|
1052
|
-
}
|
|
1103
|
+
await removeRow(backend, rowId);
|
|
1053
1104
|
} else if (transient) {
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1105
|
+
const nextAttempts = rowAttempts(row) + 1;
|
|
1106
|
+
if (nextAttempts >= MAX_DELIVERY_ATTEMPTS) {
|
|
1107
|
+
await removeRow(backend, rowId);
|
|
1108
|
+
queueLog.warn("Offline queue: giving up on report after repeated failures", {
|
|
1109
|
+
id: rowId,
|
|
1110
|
+
attempts: nextAttempts,
|
|
1111
|
+
code: result.error?.code
|
|
1112
|
+
});
|
|
1113
|
+
} else {
|
|
1114
|
+
row.attempts = nextAttempts;
|
|
1115
|
+
const persisted = await persistRow(backend, row);
|
|
1116
|
+
if (persisted) {
|
|
1117
|
+
queueLog.debug("Offline queue: transient failure, will retry", {
|
|
1118
|
+
id: rowId,
|
|
1119
|
+
attempts: nextAttempts,
|
|
1120
|
+
code: result.error?.code
|
|
1121
|
+
});
|
|
1122
|
+
} else {
|
|
1123
|
+
await removeRow(backend, rowId);
|
|
1124
|
+
queueLog.warn("Offline queue: dropping report (attempt counter unpersistable)", {
|
|
1125
|
+
id: rowId,
|
|
1126
|
+
code: result.error?.code
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1058
1130
|
}
|
|
1059
1131
|
failed++;
|
|
1060
1132
|
if (i < batch.length - 1) {
|