@mushi-mushi/core 1.10.0 → 1.12.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,5 +1,47 @@
1
1
  'use strict';
2
2
 
3
+ // src/payload-guard.ts
4
+ var MAX_REPORT_PAYLOAD_BYTES = 4 * 1024 * 1024;
5
+ var MAX_SCREENSHOT_DATA_URL_BYTES = 1.5 * 1024 * 1024;
6
+ function safeStringify(value) {
7
+ try {
8
+ return JSON.stringify(value);
9
+ } catch {
10
+ return null;
11
+ }
12
+ }
13
+ function estimateJsonBytes(value) {
14
+ const json = safeStringify(value);
15
+ return json === null ? Number.MAX_SAFE_INTEGER : new TextEncoder().encode(json).length;
16
+ }
17
+ function checkReportPayloadSize(payload, maxBytes = MAX_REPORT_PAYLOAD_BYTES) {
18
+ const json = safeStringify(payload);
19
+ if (json === null) {
20
+ return {
21
+ ok: false,
22
+ bytes: 0,
23
+ maxBytes,
24
+ serializeFailed: true,
25
+ reason: "Report could not be serialized (circular reference in metadata?)"
26
+ };
27
+ }
28
+ const bytes = new TextEncoder().encode(json).length;
29
+ if (bytes <= maxBytes) {
30
+ return { ok: true, bytes, maxBytes };
31
+ }
32
+ return {
33
+ ok: false,
34
+ bytes,
35
+ maxBytes,
36
+ reason: `Report payload ${formatBytes(bytes)} exceeds limit ${formatBytes(maxBytes)}`
37
+ };
38
+ }
39
+ function formatBytes(n) {
40
+ if (n < 1024) return `${n} B`;
41
+ if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
42
+ return `${(n / (1024 * 1024)).toFixed(2)} MB`;
43
+ }
44
+
3
45
  // src/api-client.ts
4
46
  var DEFAULT_API_ENDPOINT = "https://dxptnwrhwsqckaftyymj.supabase.co/functions/v1/api";
5
47
  var MUSHI_INTERNAL_HEADER = "X-Mushi-Internal";
@@ -109,6 +151,16 @@ function createApiClient(options) {
109
151
  }
110
152
  return {
111
153
  async submitReport(report) {
154
+ const guard = checkReportPayloadSize(report);
155
+ if (!guard.ok) {
156
+ return {
157
+ ok: false,
158
+ error: {
159
+ code: guard.serializeFailed ? "SERIALIZE_FAILED" : "PAYLOAD_TOO_LARGE",
160
+ message: guard.reason ?? "Report payload exceeds size limit"
161
+ }
162
+ };
163
+ }
112
164
  return request("POST", "/v1/reports", report, maxRetries, "report-submit");
113
165
  },
114
166
  async getReportStatus(reportId) {
@@ -140,12 +192,54 @@ function createApiClient(options) {
140
192
  reporterToken
141
193
  );
142
194
  },
143
- async replyToReporterReport(reportId, reporterToken, body) {
195
+ async replyToReporterReport(reportId, reporterToken, body, feedbackSignal) {
144
196
  return requestForReporter(
145
197
  "POST",
146
198
  `/v1/reporter/reports/${reportId}/reply`,
147
199
  reporterToken,
148
- { body }
200
+ { body, ...feedbackSignal ? { feedback_signal: feedbackSignal } : {} }
201
+ );
202
+ },
203
+ async reopenReporterReport(reportId, reporterToken, note) {
204
+ return requestForReporter(
205
+ "POST",
206
+ `/v1/reporter/reports/${reportId}/reopen`,
207
+ reporterToken,
208
+ { note: note ?? "" }
209
+ );
210
+ },
211
+ async listNotifications(reporterToken, opts) {
212
+ const qs = new URLSearchParams();
213
+ if (opts?.since) qs.set("since", opts.since);
214
+ if (opts?.limit) qs.set("limit", String(opts.limit));
215
+ const suffix = qs.size ? `?${qs.toString()}` : "";
216
+ return requestForReporter(
217
+ "GET",
218
+ `/v1/notifications${suffix}`,
219
+ reporterToken
220
+ );
221
+ },
222
+ async markNotificationRead(notificationId, reporterToken) {
223
+ return requestForReporter(
224
+ "POST",
225
+ `/v1/notifications/${notificationId}/read`,
226
+ reporterToken,
227
+ {}
228
+ );
229
+ },
230
+ async listReporterFeatureBoard(reporterToken) {
231
+ return requestForReporter(
232
+ "GET",
233
+ "/v1/reporter/feature-board",
234
+ reporterToken
235
+ );
236
+ },
237
+ async voteReporterFeatureBoard(requestId, reporterToken) {
238
+ return requestForReporter(
239
+ "POST",
240
+ `/v1/reporter/feature-board/${requestId}/vote`,
241
+ reporterToken,
242
+ {}
149
243
  );
150
244
  },
151
245
  // ─── Rewards program (P1) ──────────────────────────────────
@@ -790,7 +884,11 @@ function createOfflineQueue(config = {}) {
790
884
  }
791
885
  sent++;
792
886
  } else {
793
- const permanent = result.error?.code === "HTTP_400" || result.error?.code === "HTTP_422" || result.error?.code === "INGEST_ERROR" || result.error?.code === "VALIDATION_ERROR" || typeof result.error?.message === "string" && /invalid payload|description must be at least|validation/i.test(
887
+ 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;
888
+ // retrying re-serialises the multi-MB body every sync tick and wedges
889
+ // the queue (it matches neither permanent nor transient otherwise).
890
+ // SERIALIZE_FAILED (circular ref) is likewise unrecoverable on retry.
891
+ result.error?.code === "PAYLOAD_TOO_LARGE" || result.error?.code === "SERIALIZE_FAILED" || typeof result.error?.message === "string" && /invalid payload|description must be at least|validation/i.test(
794
892
  result.error.message
795
893
  );
796
894
  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"));
@@ -1331,10 +1429,13 @@ function normaliseThrown(thrown) {
1331
1429
  }
1332
1430
 
1333
1431
  exports.DEFAULT_API_ENDPOINT = DEFAULT_API_ENDPOINT;
1432
+ exports.MAX_REPORT_PAYLOAD_BYTES = MAX_REPORT_PAYLOAD_BYTES;
1433
+ exports.MAX_SCREENSHOT_DATA_URL_BYTES = MAX_SCREENSHOT_DATA_URL_BYTES;
1334
1434
  exports.MUSHI_INTERNAL_HEADER = MUSHI_INTERNAL_HEADER;
1335
1435
  exports.MUSHI_INTERNAL_INIT_MARKER = MUSHI_INTERNAL_INIT_MARKER;
1336
1436
  exports.REGION_ENDPOINTS = REGION_ENDPOINTS;
1337
1437
  exports.captureEnvironment = captureEnvironment;
1438
+ exports.checkReportPayloadSize = checkReportPayloadSize;
1338
1439
  exports.createApiClient = createApiClient;
1339
1440
  exports.createBreadcrumbBuffer = createBreadcrumbBuffer;
1340
1441
  exports.createLogger = createLogger;
@@ -1342,6 +1443,8 @@ exports.createOfflineQueue = createOfflineQueue;
1342
1443
  exports.createPiiScrubber = createPiiScrubber;
1343
1444
  exports.createPreFilter = createPreFilter;
1344
1445
  exports.createRateLimiter = createRateLimiter;
1446
+ exports.estimateJsonBytes = estimateJsonBytes;
1447
+ exports.formatBytes = formatBytes;
1345
1448
  exports.getDeviceFingerprintHash = getDeviceFingerprintHash;
1346
1449
  exports.getReporterToken = getReporterToken;
1347
1450
  exports.getSessionId = getSessionId;