@mushi-mushi/react-native 0.15.0 → 0.15.2

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
@@ -170,10 +170,12 @@ var AsyncStorageQueue = class {
170
170
  maxSize;
171
171
  apiEndpoint;
172
172
  apiKey;
173
+ onSynced;
173
174
  constructor(config) {
174
175
  this.maxSize = config.maxSize ?? 50;
175
176
  this.apiEndpoint = config.apiEndpoint;
176
177
  this.apiKey = config.apiKey;
178
+ this.onSynced = config.onSynced;
177
179
  }
178
180
  async enqueue(report) {
179
181
  const AsyncStorage = await this.getAsyncStorage();
@@ -195,6 +197,13 @@ var AsyncStorageQueue = class {
195
197
  const ok = await sendWithRetry(item, this.apiEndpoint, this.apiKey);
196
198
  if (ok) {
197
199
  flushed++;
200
+ const reportId = item.report.id;
201
+ if (typeof reportId === "string") {
202
+ try {
203
+ this.onSynced?.(reportId);
204
+ } catch {
205
+ }
206
+ }
198
207
  } else {
199
208
  remaining.push(item);
200
209
  break;
@@ -319,6 +328,7 @@ var CATEGORIES = [
319
328
  { key: "confusing", emoji: "\u{1F615}", label: "Confusing" },
320
329
  { key: "other", emoji: "\u{1F4AC}", label: "Other" }
321
330
  ];
331
+ var VERIFIABLE_STATUSES = /* @__PURE__ */ new Set(["fixed", "resolved", "verified"]);
322
332
  var MushiBottomSheet = ({
323
333
  visible,
324
334
  onClose,
@@ -340,12 +350,6 @@ var MushiBottomSheet = ({
340
350
  const [threadComments, setThreadComments] = (0, import_react.useState)([]);
341
351
  const [replyText, setReplyText] = (0, import_react.useState)("");
342
352
  const [screenshotAttached, setScreenshotAttached] = (0, import_react.useState)(true);
343
- (0, import_react.useEffect)(() => {
344
- if (visible) {
345
- setScreenshotAttached(true);
346
- if (sheetTab === "inbox") void loadInbox();
347
- }
348
- }, [visible, sheetTab]);
349
353
  const loadInbox = (0, import_react.useCallback)(async () => {
350
354
  if (!mushi?.listMyReports) return;
351
355
  setInboxLoading(true);
@@ -356,6 +360,12 @@ var MushiBottomSheet = ({
356
360
  setInboxLoading(false);
357
361
  }
358
362
  }, [mushi]);
363
+ (0, import_react.useEffect)(() => {
364
+ if (visible) {
365
+ setScreenshotAttached(true);
366
+ if (sheetTab === "inbox") void loadInbox();
367
+ }
368
+ }, [visible, sheetTab, loadInbox]);
359
369
  const openThread = (0, import_react.useCallback)(async (reportId) => {
360
370
  setSelectedReportId(reportId);
361
371
  if (!mushi?.listMyComments) return;
@@ -500,7 +510,9 @@ var MushiBottomSheet = ({
500
510
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.sub, fontSize: 11 }, children: c.author_kind }),
501
511
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.text }, children: c.body })
502
512
  ] }, c.id)),
503
- inboxReports.find((r) => r.id === selectedReportId)?.status === "fixed" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.verifyRow, children: [
513
+ VERIFIABLE_STATUSES.has(
514
+ inboxReports.find((r) => r.id === selectedReportId)?.status ?? ""
515
+ ) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.verifyRow, children: [
504
516
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { style: [s.verifyBtn, { backgroundColor: colors.accent }], onPress: () => submitFeedback("confirms"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: s.submitText, children: "Yes, fixed" }) }),
505
517
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { style: [s.verifyBtn, { backgroundColor: colors.border }], onPress: () => submitFeedback("not_fixed"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.submitText, { color: colors.text }], children: "Not fixed" }) })
506
518
  ] }),
@@ -876,12 +888,16 @@ function MushiProvider({ children, ...config }) {
876
888
  resolveReporterTokenReady = resolve;
877
889
  })
878
890
  );
879
- if (config.endpoint !== void 0 && config.endpoint.trim() === "") {
891
+ const rawEndpoint = config.apiEndpoint ?? config.endpoint;
892
+ if (rawEndpoint !== void 0 && rawEndpoint.trim() === "") {
880
893
  throw new Error(
881
- "[MushiProvider] endpoint is set to an empty string. Set endpoint to your Supabase edge function URL, e.g. https://xyz.supabase.co/functions/v1/api, or omit it to use the cloud default."
894
+ "[MushiProvider] apiEndpoint is set to an empty string. Set apiEndpoint to your Supabase edge function URL, e.g. https://xyz.supabase.co/functions/v1/api, or omit it to use the cloud default."
882
895
  );
883
896
  }
884
- const apiEndpoint = config.endpoint ?? import_core.DEFAULT_API_ENDPOINT;
897
+ if (config.endpoint && !config.apiEndpoint && __DEV__) {
898
+ console.warn("[MushiProvider] `endpoint` is deprecated \u2014 use `apiEndpoint` instead.");
899
+ }
900
+ const apiEndpoint = rawEndpoint ?? import_core.DEFAULT_API_ENDPOINT;
885
901
  (0, import_react3.useEffect)(() => {
886
902
  if (config.capture?.console !== false) {
887
903
  consoleRef.current = setupConsoleCapture(config.capture?.maxConsoleEntries);
@@ -897,7 +913,11 @@ function MushiProvider({ children, ...config }) {
897
913
  queueRef.current = new AsyncStorageQueue({
898
914
  maxSize: config.storage?.maxQueueSize,
899
915
  apiEndpoint,
900
- apiKey: config.apiKey
916
+ apiKey: config.apiKey,
917
+ // `onReportSynced` fires here — when a previously-queued report actually
918
+ // drains to the server — not on the direct-submit path (where it would be
919
+ // indistinguishable from `onReportSubmitted` and thus meaningless).
920
+ onSynced: (reportId) => config.callbacks?.onReportSynced?.(reportId)
901
921
  });
902
922
  queueRef.current.flush().catch(() => {
903
923
  });
@@ -1031,9 +1051,12 @@ function MushiProvider({ children, ...config }) {
1031
1051
  const result = await client.submitReport(report);
1032
1052
  if (!result.ok) {
1033
1053
  await queueRef.current?.enqueue(report);
1054
+ config.callbacks?.onReportQueued?.(report.id);
1055
+ } else {
1056
+ config.callbacks?.onReportSubmitted?.(report.id);
1034
1057
  }
1035
1058
  },
1036
- [config.projectId, apiEndpoint]
1059
+ [config.projectId, config.callbacks, apiEndpoint]
1037
1060
  );
1038
1061
  const userRef = (0, import_react3.useRef)(null);
1039
1062
  const metadataRef = (0, import_react3.useRef)({});
package/dist/index.d.cts CHANGED
@@ -46,7 +46,18 @@ declare function getDeviceInfo(): DeviceInfo;
46
46
  interface MushiRNConfig {
47
47
  projectId: string;
48
48
  apiKey: string;
49
+ /** @deprecated Prefer `apiEndpoint` — kept for backward compatibility */
49
50
  endpoint?: string;
51
+ /** Canonical Mushi API base URL (e.g. https://xyz.supabase.co/functions/v1/api) */
52
+ apiEndpoint?: string;
53
+ /** Lifecycle callbacks for host-app integration (replaces polling in HHTP etc.) */
54
+ callbacks?: {
55
+ onReportSubmitted?: (reportId: string) => void;
56
+ onReportQueued?: (reportId: string) => void;
57
+ onReportSynced?: (reportId: string) => void;
58
+ onReporterReply?: (reportId: string, commentId: string) => void;
59
+ onFixStatusChanged?: (reportId: string, status: string) => void;
60
+ };
50
61
  widget?: {
51
62
  trigger?: 'shake' | 'button' | 'both' | 'manual' | 'auto' | 'edge-tab' | 'hidden' | 'attach';
52
63
  shakeThreshold?: number;
@@ -240,10 +251,13 @@ declare class AsyncStorageQueue {
240
251
  private maxSize;
241
252
  private apiEndpoint;
242
253
  private apiKey;
254
+ private onSynced?;
243
255
  constructor(config: {
244
256
  maxSize?: number;
245
257
  apiEndpoint: string;
246
258
  apiKey: string;
259
+ /** Fired once per queued report that drains successfully to the server. */
260
+ onSynced?: (reportId: string) => void;
247
261
  });
248
262
  enqueue(report: object): Promise<void>;
249
263
  flush(): Promise<number>;
package/dist/index.d.ts CHANGED
@@ -46,7 +46,18 @@ declare function getDeviceInfo(): DeviceInfo;
46
46
  interface MushiRNConfig {
47
47
  projectId: string;
48
48
  apiKey: string;
49
+ /** @deprecated Prefer `apiEndpoint` — kept for backward compatibility */
49
50
  endpoint?: string;
51
+ /** Canonical Mushi API base URL (e.g. https://xyz.supabase.co/functions/v1/api) */
52
+ apiEndpoint?: string;
53
+ /** Lifecycle callbacks for host-app integration (replaces polling in HHTP etc.) */
54
+ callbacks?: {
55
+ onReportSubmitted?: (reportId: string) => void;
56
+ onReportQueued?: (reportId: string) => void;
57
+ onReportSynced?: (reportId: string) => void;
58
+ onReporterReply?: (reportId: string, commentId: string) => void;
59
+ onFixStatusChanged?: (reportId: string, status: string) => void;
60
+ };
50
61
  widget?: {
51
62
  trigger?: 'shake' | 'button' | 'both' | 'manual' | 'auto' | 'edge-tab' | 'hidden' | 'attach';
52
63
  shakeThreshold?: number;
@@ -240,10 +251,13 @@ declare class AsyncStorageQueue {
240
251
  private maxSize;
241
252
  private apiEndpoint;
242
253
  private apiKey;
254
+ private onSynced?;
243
255
  constructor(config: {
244
256
  maxSize?: number;
245
257
  apiEndpoint: string;
246
258
  apiKey: string;
259
+ /** Fired once per queued report that drains successfully to the server. */
260
+ onSynced?: (reportId: string) => void;
247
261
  });
248
262
  enqueue(report: object): Promise<void>;
249
263
  flush(): Promise<number>;
package/dist/index.js CHANGED
@@ -142,10 +142,12 @@ var AsyncStorageQueue = class {
142
142
  maxSize;
143
143
  apiEndpoint;
144
144
  apiKey;
145
+ onSynced;
145
146
  constructor(config) {
146
147
  this.maxSize = config.maxSize ?? 50;
147
148
  this.apiEndpoint = config.apiEndpoint;
148
149
  this.apiKey = config.apiKey;
150
+ this.onSynced = config.onSynced;
149
151
  }
150
152
  async enqueue(report) {
151
153
  const AsyncStorage = await this.getAsyncStorage();
@@ -167,6 +169,13 @@ var AsyncStorageQueue = class {
167
169
  const ok = await sendWithRetry(item, this.apiEndpoint, this.apiKey);
168
170
  if (ok) {
169
171
  flushed++;
172
+ const reportId = item.report.id;
173
+ if (typeof reportId === "string") {
174
+ try {
175
+ this.onSynced?.(reportId);
176
+ } catch {
177
+ }
178
+ }
170
179
  } else {
171
180
  remaining.push(item);
172
181
  break;
@@ -310,6 +319,7 @@ var CATEGORIES = [
310
319
  { key: "confusing", emoji: "\u{1F615}", label: "Confusing" },
311
320
  { key: "other", emoji: "\u{1F4AC}", label: "Other" }
312
321
  ];
322
+ var VERIFIABLE_STATUSES = /* @__PURE__ */ new Set(["fixed", "resolved", "verified"]);
313
323
  var MushiBottomSheet = ({
314
324
  visible,
315
325
  onClose,
@@ -331,12 +341,6 @@ var MushiBottomSheet = ({
331
341
  const [threadComments, setThreadComments] = useState([]);
332
342
  const [replyText, setReplyText] = useState("");
333
343
  const [screenshotAttached, setScreenshotAttached] = useState(true);
334
- useEffect(() => {
335
- if (visible) {
336
- setScreenshotAttached(true);
337
- if (sheetTab === "inbox") void loadInbox();
338
- }
339
- }, [visible, sheetTab]);
340
344
  const loadInbox = useCallback(async () => {
341
345
  if (!mushi?.listMyReports) return;
342
346
  setInboxLoading(true);
@@ -347,6 +351,12 @@ var MushiBottomSheet = ({
347
351
  setInboxLoading(false);
348
352
  }
349
353
  }, [mushi]);
354
+ useEffect(() => {
355
+ if (visible) {
356
+ setScreenshotAttached(true);
357
+ if (sheetTab === "inbox") void loadInbox();
358
+ }
359
+ }, [visible, sheetTab, loadInbox]);
350
360
  const openThread = useCallback(async (reportId) => {
351
361
  setSelectedReportId(reportId);
352
362
  if (!mushi?.listMyComments) return;
@@ -491,7 +501,9 @@ var MushiBottomSheet = ({
491
501
  /* @__PURE__ */ jsx(Text, { style: { color: colors.sub, fontSize: 11 }, children: c.author_kind }),
492
502
  /* @__PURE__ */ jsx(Text, { style: { color: colors.text }, children: c.body })
493
503
  ] }, c.id)),
494
- inboxReports.find((r) => r.id === selectedReportId)?.status === "fixed" && /* @__PURE__ */ jsxs(View, { style: s.verifyRow, children: [
504
+ VERIFIABLE_STATUSES.has(
505
+ inboxReports.find((r) => r.id === selectedReportId)?.status ?? ""
506
+ ) && /* @__PURE__ */ jsxs(View, { style: s.verifyRow, children: [
495
507
  /* @__PURE__ */ jsx(TouchableOpacity, { style: [s.verifyBtn, { backgroundColor: colors.accent }], onPress: () => submitFeedback("confirms"), children: /* @__PURE__ */ jsx(Text, { style: s.submitText, children: "Yes, fixed" }) }),
496
508
  /* @__PURE__ */ jsx(TouchableOpacity, { style: [s.verifyBtn, { backgroundColor: colors.border }], onPress: () => submitFeedback("not_fixed"), children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: colors.text }], children: "Not fixed" }) })
497
509
  ] }),
@@ -873,12 +885,16 @@ function MushiProvider({ children, ...config }) {
873
885
  resolveReporterTokenReady = resolve;
874
886
  })
875
887
  );
876
- if (config.endpoint !== void 0 && config.endpoint.trim() === "") {
888
+ const rawEndpoint = config.apiEndpoint ?? config.endpoint;
889
+ if (rawEndpoint !== void 0 && rawEndpoint.trim() === "") {
877
890
  throw new Error(
878
- "[MushiProvider] endpoint is set to an empty string. Set endpoint to your Supabase edge function URL, e.g. https://xyz.supabase.co/functions/v1/api, or omit it to use the cloud default."
891
+ "[MushiProvider] apiEndpoint is set to an empty string. Set apiEndpoint to your Supabase edge function URL, e.g. https://xyz.supabase.co/functions/v1/api, or omit it to use the cloud default."
879
892
  );
880
893
  }
881
- const apiEndpoint = config.endpoint ?? DEFAULT_API_ENDPOINT;
894
+ if (config.endpoint && !config.apiEndpoint && __DEV__) {
895
+ console.warn("[MushiProvider] `endpoint` is deprecated \u2014 use `apiEndpoint` instead.");
896
+ }
897
+ const apiEndpoint = rawEndpoint ?? DEFAULT_API_ENDPOINT;
882
898
  useEffect2(() => {
883
899
  if (config.capture?.console !== false) {
884
900
  consoleRef.current = setupConsoleCapture(config.capture?.maxConsoleEntries);
@@ -894,7 +910,11 @@ function MushiProvider({ children, ...config }) {
894
910
  queueRef.current = new AsyncStorageQueue({
895
911
  maxSize: config.storage?.maxQueueSize,
896
912
  apiEndpoint,
897
- apiKey: config.apiKey
913
+ apiKey: config.apiKey,
914
+ // `onReportSynced` fires here — when a previously-queued report actually
915
+ // drains to the server — not on the direct-submit path (where it would be
916
+ // indistinguishable from `onReportSubmitted` and thus meaningless).
917
+ onSynced: (reportId) => config.callbacks?.onReportSynced?.(reportId)
898
918
  });
899
919
  queueRef.current.flush().catch(() => {
900
920
  });
@@ -1028,9 +1048,12 @@ function MushiProvider({ children, ...config }) {
1028
1048
  const result = await client.submitReport(report);
1029
1049
  if (!result.ok) {
1030
1050
  await queueRef.current?.enqueue(report);
1051
+ config.callbacks?.onReportQueued?.(report.id);
1052
+ } else {
1053
+ config.callbacks?.onReportSubmitted?.(report.id);
1031
1054
  }
1032
1055
  },
1033
- [config.projectId, apiEndpoint]
1056
+ [config.projectId, config.callbacks, apiEndpoint]
1034
1057
  );
1035
1058
  const userRef = useRef3(null);
1036
1059
  const metadataRef = useRef3({});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mushi-mushi/react-native",
3
- "version": "0.15.0",
3
+ "version": "0.15.2",
4
4
  "license": "MIT",
5
5
  "description": "React Native SDK for Mushi Mushi — shake-to-report, bottom sheet widget, full context capture",
6
6
  "main": "./dist/index.js",
@@ -33,7 +33,7 @@
33
33
  }
34
34
  },
35
35
  "dependencies": {
36
- "@mushi-mushi/core": "^1.11.0"
36
+ "@mushi-mushi/core": "^1.12.0"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@types/react": "^19.2.14",