@mushi-mushi/core 1.11.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) {
@@ -156,6 +208,40 @@ function createApiClient(options) {
156
208
  { note: note ?? "" }
157
209
  );
158
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
+ {}
243
+ );
244
+ },
159
245
  // ─── Rewards program (P1) ──────────────────────────────────
160
246
  async submitActivity(userId, events, opts) {
161
247
  return request(
@@ -798,7 +884,11 @@ function createOfflineQueue(config = {}) {
798
884
  }
799
885
  sent++;
800
886
  } else {
801
- 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(
802
892
  result.error.message
803
893
  );
804
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"));
@@ -1339,10 +1429,13 @@ function normaliseThrown(thrown) {
1339
1429
  }
1340
1430
 
1341
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;
1342
1434
  exports.MUSHI_INTERNAL_HEADER = MUSHI_INTERNAL_HEADER;
1343
1435
  exports.MUSHI_INTERNAL_INIT_MARKER = MUSHI_INTERNAL_INIT_MARKER;
1344
1436
  exports.REGION_ENDPOINTS = REGION_ENDPOINTS;
1345
1437
  exports.captureEnvironment = captureEnvironment;
1438
+ exports.checkReportPayloadSize = checkReportPayloadSize;
1346
1439
  exports.createApiClient = createApiClient;
1347
1440
  exports.createBreadcrumbBuffer = createBreadcrumbBuffer;
1348
1441
  exports.createLogger = createLogger;
@@ -1350,6 +1443,8 @@ exports.createOfflineQueue = createOfflineQueue;
1350
1443
  exports.createPiiScrubber = createPiiScrubber;
1351
1444
  exports.createPreFilter = createPreFilter;
1352
1445
  exports.createRateLimiter = createRateLimiter;
1446
+ exports.estimateJsonBytes = estimateJsonBytes;
1447
+ exports.formatBytes = formatBytes;
1353
1448
  exports.getDeviceFingerprintHash = getDeviceFingerprintHash;
1354
1449
  exports.getReporterToken = getReporterToken;
1355
1450
  exports.getSessionId = getSessionId;