@goliapkg/sentori-react-native 0.5.7 → 0.6.1

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.
Files changed (46) hide show
  1. package/android/src/main/java/com/sentori/SentoriAnrWatchdog.kt +46 -0
  2. package/android/src/main/java/com/sentori/SentoriCrashHandler.kt +53 -0
  3. package/android/src/main/java/com/sentori/SentoriScreenshotCapture.kt +271 -0
  4. package/android/src/test/java/com/sentori/SentoriScreenshotCaptureTest.kt +93 -0
  5. package/ios/SentoriCrashHandler.swift +33 -1
  6. package/ios/SentoriScreenshotCapture.swift +169 -0
  7. package/ios/Tests/SentoriScreenshotCaptureTests.swift +59 -0
  8. package/lib/capture.d.ts +6 -0
  9. package/lib/capture.d.ts.map +1 -1
  10. package/lib/capture.js +65 -9
  11. package/lib/capture.js.map +1 -1
  12. package/lib/config.d.ts +2 -0
  13. package/lib/config.d.ts.map +1 -1
  14. package/lib/config.js.map +1 -1
  15. package/lib/handlers/screenshot.d.ts +12 -0
  16. package/lib/handlers/screenshot.d.ts.map +1 -0
  17. package/lib/handlers/screenshot.js +85 -0
  18. package/lib/handlers/screenshot.js.map +1 -0
  19. package/lib/index.d.ts +5 -0
  20. package/lib/index.d.ts.map +1 -1
  21. package/lib/index.js +5 -0
  22. package/lib/index.js.map +1 -1
  23. package/lib/init.d.ts +7 -0
  24. package/lib/init.d.ts.map +1 -1
  25. package/lib/init.js +21 -3
  26. package/lib/init.js.map +1 -1
  27. package/lib/mask.d.ts +30 -0
  28. package/lib/mask.d.ts.map +1 -0
  29. package/lib/mask.js +77 -0
  30. package/lib/mask.js.map +1 -0
  31. package/lib/transport.d.ts +22 -0
  32. package/lib/transport.d.ts.map +1 -1
  33. package/lib/transport.js +62 -0
  34. package/lib/transport.js.map +1 -1
  35. package/lib/types.d.ts +1 -1
  36. package/lib/types.d.ts.map +1 -1
  37. package/package.json +9 -4
  38. package/src/__tests__/screenshot.test.ts +88 -0
  39. package/src/capture.ts +79 -9
  40. package/src/config.ts +2 -0
  41. package/src/handlers/screenshot.ts +115 -0
  42. package/src/index.ts +5 -0
  43. package/src/init.ts +55 -4
  44. package/src/mask.tsx +95 -0
  45. package/src/transport.ts +77 -0
  46. package/src/types.ts +3 -0
package/lib/init.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AA0BzE,MAAM,kBAAkB,GAAG,iCAAiC,CAAC;AAE7D,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,OAAoB,EAAQ,EAAE;IACjD,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1D,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7E,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;IAClD,CAAC;IAED,MAAM,GAAG,GACP,OAAO,CAAC,WAAW;QACnB,CAAC,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAE/D,SAAS,CAAC;QACR,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,WAAW,EAAE,GAAG;QAChB,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,kBAAkB;QAClD,OAAO,EAAE,IAAI;KACd,CAAC,CAAC;IAEH,uEAAuE;IACvE,iEAAiE;IACjE,eAAe,CAAC;QACd,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,WAAW,EAAE,GAAG;KACjB,CAAC,CAAC;IAEH,cAAc,EAAE,CAAC;IAEjB,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC;IACtC,IAAI,OAAO,CAAC,YAAY,KAAK,KAAK;QAAE,oBAAoB,EAAE,CAAC;IAC3D,IAAI,OAAO,CAAC,iBAAiB,KAAK,KAAK;QAAE,qBAAqB,EAAE,CAAC;IACjE,IAAI,OAAO,CAAC,OAAO,KAAK,KAAK;QAAE,qBAAqB,EAAE,CAAC;IACvD,IAAI,OAAO,CAAC,QAAQ,KAAK,KAAK,EAAE,CAAC;QAC/B,+DAA+D;QAC/D,kEAAkE;QAClE,gEAAgE;QAChE,YAAY,EAAE,CAAC;QACf,uBAAuB,EAAE,CAAC;IAC5B,CAAC;IAED,8DAA8D;IAC9D,2DAA2D;IAC3D,iDAAiD;IACjD,kBAAkB,EAAE;SACjB,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;QACd,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,CAAC;gBACH,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAU,CAAC,CAAC;YACrC,CAAC;YAAC,MAAM,CAAC;gBACP,iBAAiB;YACnB,CAAC;QACH,CAAC;IACH,CAAC,CAAC;SACD,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACnB,iBAAiB,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;AACtC,CAAC,CAAC"}
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"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"}
package/lib/mask.d.ts ADDED
@@ -0,0 +1,30 @@
1
+ import React, { type ReactNode } from 'react';
2
+ import { View, type ViewProps } from 'react-native';
3
+ /**
4
+ * Imperative registration: when you can't wrap the sensitive view
5
+ * in `<MaskRegion>`, drop a ref on it and call `setMaskedNode(ref)`.
6
+ * Future captures will mask the subtree.
7
+ */
8
+ export declare function setMaskedNode(node: React.Component | View | null | unknown): void;
9
+ /** Removes a previously registered ref. Pair this with mount/unmount
10
+ * lifecycle hooks if the node is short-lived. */
11
+ export declare function unsetMaskedNode(node: React.Component | View | null | unknown): void;
12
+ /** Returns the current set of registered masked nodes + nativeIDs.
13
+ * Read by the native screenshotter layer in sub-E / sub-F. */
14
+ export declare function getMaskedRegions(): {
15
+ refs: Set<unknown>;
16
+ nativeIds: Set<string>;
17
+ };
18
+ /**
19
+ * Declarative redaction. `<MaskRegion>{children}</MaskRegion>` keeps
20
+ * the children visible in normal flight; under capture, the wrapping
21
+ * view is repainted black so the rendered screenshot doesn't leak
22
+ * the underlying pixels.
23
+ */
24
+ export declare function MaskRegion({ children, nativeID, ...rest }: {
25
+ children: ReactNode;
26
+ nativeID?: string;
27
+ } & ViewProps): React.JSX.Element;
28
+ /** Test-only — flush registration tables. */
29
+ export declare function __resetMaskedRegionsForTests(): void;
30
+ //# sourceMappingURL=mask.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mask.d.ts","sourceRoot":"","sources":["../src/mask.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,EAAE,KAAK,SAAS,EAAqB,MAAM,OAAO,CAAC;AACjE,OAAO,EAAE,IAAI,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAMpD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,KAAK,CAAC,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,OAAO,GAAG,IAAI,CAGjF;AAED;kDACkD;AAClD,wBAAgB,eAAe,CAAC,IAAI,EAAE,KAAK,CAAC,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,OAAO,GAAG,IAAI,CAGnF;AAED;+DAC+D;AAC/D,wBAAgB,gBAAgB,IAAI;IAClC,IAAI,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IACnB,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACxB,CAEA;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,QAAQ,EACR,GAAG,IAAI,EACR,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAoB5E;AAED,6CAA6C;AAC7C,wBAAgB,4BAA4B,IAAI,IAAI,CAGnD"}
package/lib/mask.js ADDED
@@ -0,0 +1,77 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // Phase 42 sub-D.09/10 — mark UI regions as "do not screenshot".
3
+ //
4
+ // `<MaskRegion>` wraps any subtree the SDK should redact before
5
+ // shipping a crash screenshot. It's purely declarative — the
6
+ // component renders its children as-is in normal flight, but its
7
+ // `View` is tagged with `collapsable={false}` + a sentinel
8
+ // `nativeID` so the platform-level screenshotters
9
+ // (`react-native-view-shot`, the iOS / Android native crash
10
+ // capturers we add in sub-E / sub-F) can find it and paint over.
11
+ //
12
+ // `setMaskedNode(ref)` is the imperative escape hatch: useful
13
+ // when the sensitive view isn't yours to wrap (a third-party
14
+ // modal, a video player, etc.). Pass a React ref obtained via
15
+ // `createRef()` / `useRef()` and the SDK will redact that
16
+ // subtree the next time it captures.
17
+ //
18
+ // `getMaskedRegions()` returns the current set of native tags;
19
+ // `captureScreenshot()` would consult this list, but
20
+ // `react-native-view-shot` doesn't expose a "redact these rects"
21
+ // hook — so this iteration ships the registration API only and
22
+ // the rendered overlay lives behind a default-on
23
+ // `<View style={{ backgroundColor: '#000' }}>` you can wrap
24
+ // yourself. The iOS / Android crash-time screenshotters in
25
+ // sub-E / sub-F will read this list before drawing.
26
+ import { useEffect, useRef } from 'react';
27
+ import { View } from 'react-native';
28
+ /** Component-level node identifiers we've been asked to redact. */
29
+ const _maskedRefs = new Set();
30
+ const _maskedNativeIds = new Set();
31
+ /**
32
+ * Imperative registration: when you can't wrap the sensitive view
33
+ * in `<MaskRegion>`, drop a ref on it and call `setMaskedNode(ref)`.
34
+ * Future captures will mask the subtree.
35
+ */
36
+ export function setMaskedNode(node) {
37
+ if (!node)
38
+ return;
39
+ _maskedRefs.add(node);
40
+ }
41
+ /** Removes a previously registered ref. Pair this with mount/unmount
42
+ * lifecycle hooks if the node is short-lived. */
43
+ export function unsetMaskedNode(node) {
44
+ if (!node)
45
+ return;
46
+ _maskedRefs.delete(node);
47
+ }
48
+ /** Returns the current set of registered masked nodes + nativeIDs.
49
+ * Read by the native screenshotter layer in sub-E / sub-F. */
50
+ export function getMaskedRegions() {
51
+ return { nativeIds: _maskedNativeIds, refs: _maskedRefs };
52
+ }
53
+ /**
54
+ * Declarative redaction. `<MaskRegion>{children}</MaskRegion>` keeps
55
+ * the children visible in normal flight; under capture, the wrapping
56
+ * view is repainted black so the rendered screenshot doesn't leak
57
+ * the underlying pixels.
58
+ */
59
+ export function MaskRegion({ children, nativeID, ...rest }) {
60
+ // Auto-generate a stable nativeID per mount so the native
61
+ // screenshotter can find this view by ID at capture time.
62
+ const idRef = useRef(nativeID ?? `sentori-mask-${Math.random().toString(36).slice(2, 10)}`);
63
+ useEffect(() => {
64
+ const id = idRef.current;
65
+ _maskedNativeIds.add(id);
66
+ return () => {
67
+ _maskedNativeIds.delete(id);
68
+ };
69
+ }, []);
70
+ return (_jsx(View, { collapsable: false, nativeID: idRef.current, ...rest, children: children }));
71
+ }
72
+ /** Test-only — flush registration tables. */
73
+ export function __resetMaskedRegionsForTests() {
74
+ _maskedRefs.clear();
75
+ _maskedNativeIds.clear();
76
+ }
77
+ //# sourceMappingURL=mask.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mask.js","sourceRoot":"","sources":["../src/mask.tsx"],"names":[],"mappings":";AAAA,iEAAiE;AACjE,EAAE;AACF,gEAAgE;AAChE,6DAA6D;AAC7D,iEAAiE;AACjE,2DAA2D;AAC3D,kDAAkD;AAClD,4DAA4D;AAC5D,iEAAiE;AACjE,EAAE;AACF,8DAA8D;AAC9D,6DAA6D;AAC7D,8DAA8D;AAC9D,0DAA0D;AAC1D,qCAAqC;AACrC,EAAE;AACF,+DAA+D;AAC/D,qDAAqD;AACrD,iEAAiE;AACjE,+DAA+D;AAC/D,iDAAiD;AACjD,4DAA4D;AAC5D,2DAA2D;AAC3D,oDAAoD;AAEpD,OAAc,EAAkB,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AACjE,OAAO,EAAE,IAAI,EAAkB,MAAM,cAAc,CAAC;AAEpD,mEAAmE;AACnE,MAAM,WAAW,GAAG,IAAI,GAAG,EAAoC,CAAC;AAChE,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAU,CAAC;AAE3C;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,IAA6C;IACzE,IAAI,CAAC,IAAI;QAAE,OAAO;IAClB,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACxB,CAAC;AAED;kDACkD;AAClD,MAAM,UAAU,eAAe,CAAC,IAA6C;IAC3E,IAAI,CAAC,IAAI;QAAE,OAAO;IAClB,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAC3B,CAAC;AAED;+DAC+D;AAC/D,MAAM,UAAU,gBAAgB;IAI9B,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;AAC5D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,EACzB,QAAQ,EACR,QAAQ,EACR,GAAG,IAAI,EACgD;IACvD,0DAA0D;IAC1D,0DAA0D;IAC1D,MAAM,KAAK,GAAG,MAAM,CAClB,QAAQ,IAAI,gBAAgB,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CACtE,CAAC;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC;QACzB,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACzB,OAAO,GAAG,EAAE;YACV,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC9B,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,CACL,KAAC,IAAI,IAAC,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,OAAO,KAAM,IAAI,YACxD,QAAQ,GACJ,CACR,CAAC;AACJ,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,4BAA4B;IAC1C,WAAW,CAAC,KAAK,EAAE,CAAC;IACpB,gBAAgB,CAAC,KAAK,EAAE,CAAC;AAC3B,CAAC"}
@@ -14,4 +14,26 @@ export declare const __peekQueue: () => readonly Event[];
14
14
  * this.
15
15
  */
16
16
  export declare const sendSessionPing: (ingestUrl: string, token: string, ping: unknown) => Promise<void>;
17
+ /**
18
+ * Upload a base64-encoded binary blob as an attachment for a known
19
+ * event. The event must NOT have been POSTed yet — the server-side
20
+ * ingest validation in events.rs only honours `event.attachments[].ref`
21
+ * when the matching `event_attachments` row already exists for the
22
+ * same (event_id, project_id). Caller's contract:
23
+ *
24
+ * 1. Generate `event.id` (uuidV7).
25
+ * 2. Build the blob (e.g. via `captureScreenshot`).
26
+ * 3. `await uploadAttachment(...)` → get `{ ref, sizeBytes, mediaType }`.
27
+ * 4. Push `{ ref, kind, ... }` into `event.attachments` then enqueue.
28
+ *
29
+ * Returns `null` on any non-fatal failure (network down, store
30
+ * disabled, 4xx, timeout). The error event still ships without the
31
+ * attachment so we never lose the actual crash.
32
+ */
33
+ export declare const uploadAttachment: (eventId: string, kind: import("./types").AttachmentMeta["kind"], blob: {
34
+ base64: string;
35
+ mediaType: string;
36
+ }, opts?: {
37
+ source?: "android" | "ios" | "js";
38
+ }) => Promise<import("./types").AttachmentMeta | null>;
17
39
  //# sourceMappingURL=transport.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAqBrC,eAAO,MAAM,OAAO,GAAI,OAAO,KAAK,KAAG,IAUtC,CAAC;AAEF,eAAO,MAAM,cAAc,QAAO,IAOjC,CAAC;AAEF,eAAO,MAAM,UAAU,QAAa,OAAO,CAAC,IAAI,CAe/C,CAAC;AAqBF,eAAO,MAAM,KAAK,QAAa,OAAO,CAAC,IAAI,CAkB1C,CAAC;AA4FF,eAAO,MAAM,iBAAiB,QAAa,OAAO,CAAC,IAAI,CAatD,CAAC;AAEF,eAAO,MAAM,eAAe,QAAO,IAOlC,CAAC;AAEF,eAAO,MAAM,WAAW,QAAO,SAAS,KAAK,EAAY,CAAC;AAE1D;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAC1B,WAAW,MAAM,EACjB,OAAO,MAAM,EACb,MAAM,OAAO,KACZ,OAAO,CAAC,IAAI,CAcd,CAAC"}
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAqBrC,eAAO,MAAM,OAAO,GAAI,OAAO,KAAK,KAAG,IAUtC,CAAC;AAEF,eAAO,MAAM,cAAc,QAAO,IAOjC,CAAC;AAEF,eAAO,MAAM,UAAU,QAAa,OAAO,CAAC,IAAI,CAe/C,CAAC;AAqBF,eAAO,MAAM,KAAK,QAAa,OAAO,CAAC,IAAI,CAkB1C,CAAC;AA4FF,eAAO,MAAM,iBAAiB,QAAa,OAAO,CAAC,IAAI,CAatD,CAAC;AAEF,eAAO,MAAM,eAAe,QAAO,IAOlC,CAAC;AAEF,eAAO,MAAM,WAAW,QAAO,SAAS,KAAK,EAAY,CAAC;AAE1D;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAC1B,WAAW,MAAM,EACjB,OAAO,MAAM,EACb,MAAM,OAAO,KACZ,OAAO,CAAC,IAAI,CAcd,CAAC;AAMF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,gBAAgB,GAC3B,SAAS,MAAM,EACf,MAAM,OAAO,SAAS,EAAE,cAAc,CAAC,MAAM,CAAC,EAC9C,MAAM;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,EAC3C,OAAM;IAAE,MAAM,CAAC,EAAE,SAAS,GAAG,KAAK,GAAG,IAAI,CAAA;CAAO,KAC/C,OAAO,CAAC,OAAO,SAAS,EAAE,cAAc,GAAG,IAAI,CA6CjD,CAAC"}
package/lib/transport.js CHANGED
@@ -213,4 +213,66 @@ export const sendSessionPing = async (ingestUrl, token, ping) => {
213
213
  // best-effort
214
214
  }
215
215
  };
216
+ // ──────────────────────────────────────────────────────────────────
217
+ // Phase 42 sub-D.05 — attachment upload pipeline
218
+ // ──────────────────────────────────────────────────────────────────
219
+ /**
220
+ * Upload a base64-encoded binary blob as an attachment for a known
221
+ * event. The event must NOT have been POSTed yet — the server-side
222
+ * ingest validation in events.rs only honours `event.attachments[].ref`
223
+ * when the matching `event_attachments` row already exists for the
224
+ * same (event_id, project_id). Caller's contract:
225
+ *
226
+ * 1. Generate `event.id` (uuidV7).
227
+ * 2. Build the blob (e.g. via `captureScreenshot`).
228
+ * 3. `await uploadAttachment(...)` → get `{ ref, sizeBytes, mediaType }`.
229
+ * 4. Push `{ ref, kind, ... }` into `event.attachments` then enqueue.
230
+ *
231
+ * Returns `null` on any non-fatal failure (network down, store
232
+ * disabled, 4xx, timeout). The error event still ships without the
233
+ * attachment so we never lose the actual crash.
234
+ */
235
+ export const uploadAttachment = async (eventId, kind, blob, opts = {}) => {
236
+ const config = getConfig();
237
+ if (!config)
238
+ return null;
239
+ const url = `${config.ingestUrl}/v1/events/${encodeURIComponent(eventId)}/attachments/${encodeURIComponent(kind)}`;
240
+ // RN-style multipart: `{ uri, type, name }` is what the native
241
+ // FormData implementation expects for a file part — the bridge
242
+ // serializes a data: URI without us having to allocate a Blob.
243
+ const form = new FormData();
244
+ form.append('file', {
245
+ name: filenameFor(kind, blob.mediaType),
246
+ type: blob.mediaType,
247
+ uri: `data:${blob.mediaType};base64,${blob.base64}`,
248
+ });
249
+ form.append('source', opts.source ?? 'js');
250
+ try {
251
+ const resp = await fetch(url, {
252
+ body: form,
253
+ headers: {
254
+ Authorization: `Bearer ${config.token}`,
255
+ 'Sentori-Sdk': `react-native/${SDK_VERSION}`,
256
+ },
257
+ method: 'POST',
258
+ });
259
+ if (resp.status !== 201)
260
+ return null;
261
+ const j = (await resp.json());
262
+ return {
263
+ kind,
264
+ mediaType: j.mediaType,
265
+ ref: j.refId,
266
+ sizeBytes: j.sizeBytes,
267
+ source: opts.source ?? 'js',
268
+ };
269
+ }
270
+ catch {
271
+ return null;
272
+ }
273
+ };
274
+ function filenameFor(kind, mediaType) {
275
+ const ext = mediaType.split('/')[1] ?? 'bin';
276
+ return `${kind}.${ext}`;
277
+ }
216
278
  //# sourceMappingURL=transport.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"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"}
package/lib/types.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export type { App, Breadcrumb, BreadcrumbType, CaptureExtras, Device, DeviceOS, Event, EventKind, Frame, Platform, SentoriError, Tags, User, } from '@goliapkg/sentori-core';
1
+ export type { App, AttachmentKind, AttachmentMeta, AttachmentSource, Breadcrumb, BreadcrumbType, CaptureExtras, Device, DeviceOS, Event, EventKind, Frame, Platform, SentoriError, Tags, User, } from '@goliapkg/sentori-core';
2
2
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAGA,YAAY,EACV,GAAG,EACH,UAAU,EACV,cAAc,EACd,aAAa,EACb,MAAM,EACN,QAAQ,EACR,KAAK,EACL,SAAS,EACT,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,IAAI,EACJ,IAAI,GACL,MAAM,wBAAwB,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAGA,YAAY,EACV,GAAG,EACH,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,cAAc,EACd,aAAa,EACb,MAAM,EACN,QAAQ,EACR,KAAK,EACL,SAAS,EACT,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,IAAI,EACJ,IAAI,GACL,MAAM,wBAAwB,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goliapkg/sentori-react-native",
3
- "version": "0.5.7",
3
+ "version": "0.6.1",
4
4
  "description": "Sentori SDK for React Native — JS-layer error capture, native crash handlers (iOS / Android), batched transport, fetch + react-navigation tracing.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://sentori.golia.jp",
@@ -41,7 +41,8 @@
41
41
  "peerDependencies": {
42
42
  "expo-modules-core": ">=2.0",
43
43
  "react": ">=18",
44
- "react-native": ">=0.74"
44
+ "react-native": ">=0.74",
45
+ "react-native-view-shot": ">=3.8"
45
46
  },
46
47
  "peerDependenciesMeta": {
47
48
  "@react-native-async-storage/async-storage": {
@@ -49,11 +50,15 @@
49
50
  },
50
51
  "expo-modules-core": {
51
52
  "optional": true
53
+ },
54
+ "react-native-view-shot": {
55
+ "optional": true
52
56
  }
53
57
  },
54
58
  "optionalDependencies": {
55
59
  "@react-native-async-storage/async-storage": ">=1.23",
56
- "expo-modules-core": ">=2.0"
60
+ "expo-modules-core": ">=2.0",
61
+ "react-native-view-shot": ">=3.8"
57
62
  },
58
63
  "devDependencies": {
59
64
  "@types/bun": "latest",
@@ -64,6 +69,6 @@
64
69
  "access": "public"
65
70
  },
66
71
  "dependencies": {
67
- "@goliapkg/sentori-core": "0.4.1"
72
+ "@goliapkg/sentori-core": "0.5.0"
68
73
  }
69
74
  }
@@ -0,0 +1,88 @@
1
+ import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test';
2
+
3
+ import { setConfig, __resetForTests as resetConfig } from '../config';
4
+ import { uploadAttachment } from '../transport';
5
+
6
+ // Phase 42 sub-D.13 — unit coverage for the upload pipeline.
7
+ //
8
+ // `captureScreenshot()` itself goes through react-native-view-shot
9
+ // + RN's InteractionManager, neither of which exist in the bun:test
10
+ // runtime, so we test it indirectly: `uploadAttachment` is the
11
+ // non-RN-API surface and that's what we hit hardest. The
12
+ // `requestAnimationFrame` / InteractionManager perf gating is a
13
+ // runtime concern asserted via manual smoke + the iOS / Android
14
+ // XCTest / instrumentation tests in sub-E / sub-F.
15
+
16
+ const origFetch = globalThis.fetch;
17
+ afterEach(() => {
18
+ globalThis.fetch = origFetch;
19
+ resetConfig();
20
+ });
21
+ beforeEach(() => {
22
+ globalThis.fetch = origFetch;
23
+ setConfig({
24
+ enabled: true,
25
+ environment: 'test',
26
+ ingestUrl: 'http://localhost:18080',
27
+ release: 'app@1.0.0+1',
28
+ screenshotsEnabled: true,
29
+ token: 'st_pk_test',
30
+ });
31
+ });
32
+
33
+ describe('uploadAttachment', () => {
34
+ test('hits POST /v1/events/<id>/attachments/<kind> with the bearer token', async () => {
35
+ const seen: { method?: string; url?: string; auth?: null | string } = {};
36
+ globalThis.fetch = mock(async (url: Request | string | URL, init?: RequestInit) => {
37
+ seen.url = String(url);
38
+ seen.method = init?.method;
39
+ // Reach the Bearer header off the Headers/Init shape used here.
40
+ const headers = (init?.headers ?? {}) as Record<string, string>;
41
+ seen.auth = headers.Authorization;
42
+ return new Response(
43
+ JSON.stringify({
44
+ kind: 'screenshot',
45
+ mediaType: 'image/jpeg',
46
+ refId: '019e3000-7000-7000-8000-000000000001',
47
+ sizeBytes: 4,
48
+ }),
49
+ { headers: { 'content-type': 'application/json' }, status: 201 },
50
+ );
51
+ }) as typeof fetch;
52
+
53
+ const out = await uploadAttachment('019eaa00-0000-7000-8000-000000000001', 'screenshot', {
54
+ base64: 'AAAA',
55
+ mediaType: 'image/jpeg',
56
+ });
57
+ expect(seen.method).toBe('POST');
58
+ expect(seen.url).toBe(
59
+ 'http://localhost:18080/v1/events/019eaa00-0000-7000-8000-000000000001/attachments/screenshot',
60
+ );
61
+ expect(seen.auth).toBe('Bearer st_pk_test');
62
+ expect(out).not.toBeNull();
63
+ expect(out!.ref).toBe('019e3000-7000-7000-8000-000000000001');
64
+ expect(out!.kind).toBe('screenshot');
65
+ expect(out!.source).toBe('js');
66
+ });
67
+
68
+ test('returns null on a non-201 response', async () => {
69
+ globalThis.fetch = (async () =>
70
+ new Response('{"error":"tooLarge"}', { status: 413 })) as typeof fetch;
71
+ const out = await uploadAttachment('e', 'screenshot', { base64: '', mediaType: 'image/jpeg' });
72
+ expect(out).toBeNull();
73
+ });
74
+
75
+ test('returns null when fetch throws (offline)', async () => {
76
+ globalThis.fetch = (async () => {
77
+ throw new TypeError('Network request failed');
78
+ }) as typeof fetch;
79
+ const out = await uploadAttachment('e', 'screenshot', { base64: '', mediaType: 'image/jpeg' });
80
+ expect(out).toBeNull();
81
+ });
82
+
83
+ test('returns null without an active config (init never ran)', async () => {
84
+ resetConfig();
85
+ const out = await uploadAttachment('e', 'screenshot', { base64: '', mediaType: 'image/jpeg' });
86
+ expect(out).toBeNull();
87
+ });
88
+ });
package/src/capture.ts CHANGED
@@ -1,16 +1,31 @@
1
+ import { addBreadcrumb, getBreadcrumbs } from './breadcrumbs';
1
2
  import { getConfig, isInitialized } from './config';
2
- import { getBreadcrumbs } from './breadcrumbs';
3
3
  import { symbolicateErrorViaMetro } from './handlers/dev-symbolicate';
4
+ import { captureScreenshot } from './handlers/screenshot';
4
5
  import { markSessionErrored } from './session-tracker';
5
6
  import { parseStack } from './stack';
6
- import { enqueue } from './transport';
7
+ import { enqueue, uploadAttachment } from './transport';
7
8
  import { uuidV7 } from './uuid';
8
- import type { App, Device, Event, SentoriError, Tags, User } from './types';
9
+ import type { App, AttachmentMeta, Device, Event, SentoriError, Tags, User } from './types';
9
10
 
10
11
  declare const __DEV__: boolean | undefined;
11
12
 
12
13
  let _user: User | null = null;
13
14
 
15
+ // Phase 42 sub-D.08 — per-session screenshot quota. Defaults: 10 in
16
+ // prod, unlimited (-1 sentinel) in dev so test loops + react-error-
17
+ // overlay reruns don't run out partway through the session.
18
+ const SCREENSHOT_PROD_LIMIT = 10;
19
+ let _screenshotsTaken = 0;
20
+
21
+ function screenshotBudget(): number {
22
+ return typeof __DEV__ !== 'undefined' && __DEV__ ? -1 : SCREENSHOT_PROD_LIMIT;
23
+ }
24
+
25
+ export const __resetScreenshotBudgetForTests = (): void => {
26
+ _screenshotsTaken = 0;
27
+ };
28
+
14
29
  /**
15
30
  * Attach a stable user identifier to events captured after this call.
16
31
  *
@@ -33,6 +48,11 @@ export type CaptureExtras = {
33
48
  tags?: Tags;
34
49
  user?: User;
35
50
  fingerprint?: string[];
51
+ /** Phase 42 sub-D.07: per-call screenshot override. `false` skips
52
+ * screenshot capture even when `init({ capture: { screenshot:
53
+ * true } })` is on — handy for sensitive screens. Defaults to
54
+ * whatever `config.screenshotsEnabled` says. */
55
+ screenshot?: boolean;
36
56
  };
37
57
 
38
58
  export const captureError = (error: Error, extras?: CaptureExtras): void => {
@@ -60,21 +80,71 @@ export const captureError = (error: Error, extras?: CaptureExtras): void => {
60
80
  // `errored` so the next AppState=background ping reports unhealthy.
61
81
  markSessionErrored();
62
82
 
83
+ // Phase 42 sub-D.07: opt-in screenshot. Default off; per-call
84
+ // `extras.screenshot: false` always wins so callers can mute it
85
+ // on a sensitive flow even when init has it on globally.
86
+ const wantScreenshot =
87
+ config.screenshotsEnabled && extras?.screenshot !== false && allowScreenshot();
88
+
63
89
  // Phase 40 sub-E: in dev there's no uploaded source map, so ask
64
90
  // Metro to symbolicate the stack before we send it (best-effort,
65
91
  // short timeout). Release builds skip straight to enqueue and let
66
92
  // the server symbolicate at ingest against the uploaded map.
67
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
68
- void symbolicateErrorViaMetro(event.error)
69
- .catch(() => {})
70
- .then(() => enqueue(event));
71
- } else {
93
+ const pipeline = async (): Promise<void> => {
94
+ if (typeof __DEV__ !== 'undefined' && __DEV__) {
95
+ await symbolicateErrorViaMetro(event.error).catch(() => {});
96
+ }
97
+ if (wantScreenshot) {
98
+ await captureAndAttachScreenshot(event);
99
+ }
72
100
  enqueue(event);
73
- }
101
+ };
102
+ void pipeline();
74
103
  };
75
104
 
76
105
  export const captureException = captureError;
77
106
 
107
+ /** Phase 42 sub-D.08: per-session screenshot quota gate. */
108
+ function allowScreenshot(): boolean {
109
+ const budget = screenshotBudget();
110
+ if (budget < 0) return true; // dev: unlimited
111
+ if (_screenshotsTaken >= budget) return false;
112
+ _screenshotsTaken += 1;
113
+ return true;
114
+ }
115
+
116
+ /**
117
+ * Phase 42 sub-D.06/07: take a screenshot, upload it, push the
118
+ * server-issued ref into `event.attachments`. Every step is
119
+ * best-effort — on any failure we leave a breadcrumb and let the
120
+ * event ship without a thumbnail.
121
+ */
122
+ async function captureAndAttachScreenshot(event: Event): Promise<void> {
123
+ let blob: Awaited<ReturnType<typeof captureScreenshot>> = null;
124
+ try {
125
+ blob = await captureScreenshot();
126
+ } catch {
127
+ // capture itself shouldn't throw — `captureScreenshot` already
128
+ // catches — but be defensive.
129
+ }
130
+ if (!blob) {
131
+ addBreadcrumb({ type: 'custom', data: { reason: 'screenshot-capture-failed' } });
132
+ return;
133
+ }
134
+ const attachment: AttachmentMeta | null = await uploadAttachment(
135
+ event.id,
136
+ 'screenshot',
137
+ blob,
138
+ { source: 'js' },
139
+ );
140
+ if (!attachment) {
141
+ addBreadcrumb({ type: 'custom', data: { reason: 'screenshot-upload-failed' } });
142
+ return;
143
+ }
144
+ if (!event.attachments) event.attachments = [];
145
+ event.attachments.push(attachment);
146
+ }
147
+
78
148
  const errorToObject = (error: Error): SentoriError => {
79
149
  const causeRaw = (error as { cause?: unknown }).cause;
80
150
  let cause: SentoriError | null = null;
package/src/config.ts CHANGED
@@ -4,6 +4,8 @@ export type Config = {
4
4
  environment: string;
5
5
  ingestUrl: string;
6
6
  enabled: boolean;
7
+ /** Phase 42 sub-D.07: opt-in screenshot capture on captureException. */
8
+ screenshotsEnabled: boolean;
7
9
  };
8
10
 
9
11
  let _config: Config | null = null;