@goliapkg/sentori-react-native 0.5.7 → 0.6.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/lib/capture.d.ts +6 -0
- package/lib/capture.d.ts.map +1 -1
- package/lib/capture.js +65 -9
- package/lib/capture.js.map +1 -1
- package/lib/config.d.ts +2 -0
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js.map +1 -1
- package/lib/handlers/screenshot.d.ts +12 -0
- package/lib/handlers/screenshot.d.ts.map +1 -0
- package/lib/handlers/screenshot.js +85 -0
- package/lib/handlers/screenshot.js.map +1 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +5 -0
- package/lib/index.js.map +1 -1
- package/lib/init.d.ts +5 -0
- package/lib/init.d.ts.map +1 -1
- package/lib/init.js +1 -0
- package/lib/init.js.map +1 -1
- package/lib/mask.d.ts +30 -0
- package/lib/mask.d.ts.map +1 -0
- package/lib/mask.js +77 -0
- package/lib/mask.js.map +1 -0
- package/lib/transport.d.ts +22 -0
- package/lib/transport.d.ts.map +1 -1
- package/lib/transport.js +62 -0
- package/lib/transport.js.map +1 -1
- package/lib/types.d.ts +1 -1
- package/lib/types.d.ts.map +1 -1
- package/package.json +9 -4
- package/src/__tests__/screenshot.test.ts +88 -0
- package/src/capture.ts +79 -9
- package/src/config.ts +2 -0
- package/src/handlers/screenshot.ts +115 -0
- package/src/index.ts +5 -0
- package/src/init.ts +6 -0
- package/src/mask.tsx +95 -0
- package/src/transport.ts +77 -0
- package/src/types.ts +3 -0
package/lib/transport.js.map
CHANGED
|
@@ -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
|
package/lib/types.d.ts.map
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "0.6.0",
|
|
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.
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
.catch(() => {})
|
|
70
|
-
|
|
71
|
-
|
|
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
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// Phase 42 sub-D.03/04 — capture a screenshot of the current view tree
|
|
2
|
+
// on `captureException`. Off-main-thread, best-effort, opt-in.
|
|
3
|
+
//
|
|
4
|
+
// Performance contract (sub-D.04):
|
|
5
|
+
// - Wait for the in-flight RN interaction batch to drain before
|
|
6
|
+
// touching the view shot (`InteractionManager.runAfterInteractions`)
|
|
7
|
+
// so we never extend the active gesture / animation by a frame.
|
|
8
|
+
// - Yield one paint by chaining a `requestAnimationFrame` so the
|
|
9
|
+
// screenshot reflects post-error UI state, not the frame that
|
|
10
|
+
// was already half-laid-out.
|
|
11
|
+
// - Capped output: 480 px on the longest edge, WebP q=70. Typical
|
|
12
|
+
// payload 30-80 KB; multipart hard cap is 500 KB.
|
|
13
|
+
// - On any failure we silently return null. The error event still
|
|
14
|
+
// goes to the server; the user just doesn't see a thumbnail.
|
|
15
|
+
//
|
|
16
|
+
// `react-native-view-shot` is an OPTIONAL peer. We `require()` it
|
|
17
|
+
// lazily so apps that don't install it never pay the bundle cost
|
|
18
|
+
// or fail at import time. Without it, `captureScreenshot()` returns
|
|
19
|
+
// `null` immediately.
|
|
20
|
+
|
|
21
|
+
import { InteractionManager } from 'react-native';
|
|
22
|
+
|
|
23
|
+
type CaptureRef = (
|
|
24
|
+
// Phase 42: the lib accepts a React ref or — when we pass `undefined` —
|
|
25
|
+
// shoots the root window. We always go for the root (no per-component
|
|
26
|
+
// ref) so the screenshot lines up with what the user just saw.
|
|
27
|
+
refOrUndefined: undefined,
|
|
28
|
+
opts: {
|
|
29
|
+
format?: 'jpg' | 'png' | 'webm';
|
|
30
|
+
quality?: number;
|
|
31
|
+
result?: 'base64' | 'data-uri' | 'tmpfile';
|
|
32
|
+
width?: number;
|
|
33
|
+
height?: number;
|
|
34
|
+
},
|
|
35
|
+
) => Promise<string>;
|
|
36
|
+
|
|
37
|
+
type ViewShotModule = { captureRef?: CaptureRef; default?: { captureRef?: CaptureRef } };
|
|
38
|
+
|
|
39
|
+
function loadCaptureRef(): CaptureRef | null {
|
|
40
|
+
try {
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
42
|
+
const mod = require('react-native-view-shot') as ViewShotModule;
|
|
43
|
+
return mod.captureRef ?? mod.default?.captureRef ?? null;
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const MAX_LONG_EDGE_PX = 480;
|
|
50
|
+
const WEBP_QUALITY = 0.7;
|
|
51
|
+
const CAPTURE_TIMEOUT_MS = 1500;
|
|
52
|
+
|
|
53
|
+
/** What `captureScreenshot()` hands back when it succeeds. */
|
|
54
|
+
export type ScreenshotBlob = {
|
|
55
|
+
base64: string;
|
|
56
|
+
mediaType: string;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Take one screenshot, yielding the JS thread first. Returns null on
|
|
61
|
+
* any error (missing peer dep, native side refused, timeout, etc.).
|
|
62
|
+
* Caller is responsible for opt-in checks (`config.screenshotsEnabled`).
|
|
63
|
+
*/
|
|
64
|
+
export async function captureScreenshot(): Promise<ScreenshotBlob | null> {
|
|
65
|
+
const captureRef = loadCaptureRef();
|
|
66
|
+
if (!captureRef) return null;
|
|
67
|
+
|
|
68
|
+
// Wait for the in-flight RN interaction batch to drain. This is
|
|
69
|
+
// why screenshot capture doesn't visibly stall the user's last
|
|
70
|
+
// action — we let React commit before we ask the OS to render.
|
|
71
|
+
await new Promise<void>((resolve) => {
|
|
72
|
+
InteractionManager.runAfterInteractions(() => resolve());
|
|
73
|
+
});
|
|
74
|
+
await new Promise<void>((resolve) => {
|
|
75
|
+
requestAnimationFrame(() => resolve());
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const base64 = await withTimeout(
|
|
80
|
+
captureRef(undefined, {
|
|
81
|
+
format: 'jpg',
|
|
82
|
+
quality: WEBP_QUALITY,
|
|
83
|
+
result: 'base64',
|
|
84
|
+
// Long-edge cap. RN view-shot scales preserving aspect ratio
|
|
85
|
+
// when only one dimension is set.
|
|
86
|
+
width: MAX_LONG_EDGE_PX,
|
|
87
|
+
}),
|
|
88
|
+
CAPTURE_TIMEOUT_MS,
|
|
89
|
+
);
|
|
90
|
+
if (!base64) return null;
|
|
91
|
+
// view-shot doesn't ship a WebP encoder on every RN version.
|
|
92
|
+
// JPEG q=70 fits the budget too (typical 40-100 KB) and every
|
|
93
|
+
// version handles it identically. We can swap to WebP once the
|
|
94
|
+
// RN minimum we support has it everywhere.
|
|
95
|
+
return { base64, mediaType: 'image/jpeg' };
|
|
96
|
+
} catch {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function withTimeout<T>(p: Promise<T>, ms: number): Promise<T | null> {
|
|
102
|
+
return new Promise((resolve) => {
|
|
103
|
+
const t = setTimeout(() => resolve(null as unknown as T), ms);
|
|
104
|
+
p.then(
|
|
105
|
+
(v) => {
|
|
106
|
+
clearTimeout(t);
|
|
107
|
+
resolve(v);
|
|
108
|
+
},
|
|
109
|
+
() => {
|
|
110
|
+
clearTimeout(t);
|
|
111
|
+
resolve(null as unknown as T);
|
|
112
|
+
},
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { init } from './init';
|
|
|
2
2
|
import { addBreadcrumb } from './breadcrumbs';
|
|
3
3
|
import { setUser, getUser, captureError, captureException } from './capture';
|
|
4
4
|
import { ErrorBoundary } from './error-boundary';
|
|
5
|
+
import { MaskRegion, setMaskedNode, unsetMaskedNode } from './mask';
|
|
5
6
|
import {
|
|
6
7
|
endSession,
|
|
7
8
|
markSessionCrashed,
|
|
@@ -16,6 +17,9 @@ export const sentori = {
|
|
|
16
17
|
captureError,
|
|
17
18
|
captureException,
|
|
18
19
|
ErrorBoundary,
|
|
20
|
+
MaskRegion,
|
|
21
|
+
setMaskedNode,
|
|
22
|
+
unsetMaskedNode,
|
|
19
23
|
startSession,
|
|
20
24
|
endSession,
|
|
21
25
|
markSessionCrashed,
|
|
@@ -27,6 +31,7 @@ export { init, init as initSentori } from './init';
|
|
|
27
31
|
export { addBreadcrumb } from './breadcrumbs';
|
|
28
32
|
export { setUser, getUser, captureError, captureException } from './capture';
|
|
29
33
|
export { ErrorBoundary } from './error-boundary';
|
|
34
|
+
export { MaskRegion, setMaskedNode, unsetMaskedNode } from './mask';
|
|
30
35
|
export {
|
|
31
36
|
startAnrWatchdog,
|
|
32
37
|
stopAnrWatchdog,
|
package/src/init.ts
CHANGED
|
@@ -28,6 +28,11 @@ export type InitOptions = {
|
|
|
28
28
|
* foreground (`AppState` → `active`), ends it on background.
|
|
29
29
|
* Drives crash-free rate. Set `false` to opt out. */
|
|
30
30
|
sessions?: boolean;
|
|
31
|
+
/** Phase 42 sub-D.07: capture a screenshot of the current screen
|
|
32
|
+
* on `captureException`. Opt-in — requires `react-native-view-shot`
|
|
33
|
+
* installed and `<MaskRegion>` placed over any sensitive UI. The
|
|
34
|
+
* image is webp q=70 480 px max, < 100 KB typical. */
|
|
35
|
+
screenshot?: boolean;
|
|
31
36
|
};
|
|
32
37
|
};
|
|
33
38
|
|
|
@@ -51,6 +56,7 @@ export const init = (options: InitOptions): void => {
|
|
|
51
56
|
environment: env,
|
|
52
57
|
ingestUrl: options.ingestUrl ?? DEFAULT_INGEST_URL,
|
|
53
58
|
enabled: true,
|
|
59
|
+
screenshotsEnabled: options.capture?.screenshot === true,
|
|
54
60
|
});
|
|
55
61
|
|
|
56
62
|
// Tell the native crash handler about the config so the JSON it writes
|
package/src/mask.tsx
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// Phase 42 sub-D.09/10 — mark UI regions as "do not screenshot".
|
|
2
|
+
//
|
|
3
|
+
// `<MaskRegion>` wraps any subtree the SDK should redact before
|
|
4
|
+
// shipping a crash screenshot. It's purely declarative — the
|
|
5
|
+
// component renders its children as-is in normal flight, but its
|
|
6
|
+
// `View` is tagged with `collapsable={false}` + a sentinel
|
|
7
|
+
// `nativeID` so the platform-level screenshotters
|
|
8
|
+
// (`react-native-view-shot`, the iOS / Android native crash
|
|
9
|
+
// capturers we add in sub-E / sub-F) can find it and paint over.
|
|
10
|
+
//
|
|
11
|
+
// `setMaskedNode(ref)` is the imperative escape hatch: useful
|
|
12
|
+
// when the sensitive view isn't yours to wrap (a third-party
|
|
13
|
+
// modal, a video player, etc.). Pass a React ref obtained via
|
|
14
|
+
// `createRef()` / `useRef()` and the SDK will redact that
|
|
15
|
+
// subtree the next time it captures.
|
|
16
|
+
//
|
|
17
|
+
// `getMaskedRegions()` returns the current set of native tags;
|
|
18
|
+
// `captureScreenshot()` would consult this list, but
|
|
19
|
+
// `react-native-view-shot` doesn't expose a "redact these rects"
|
|
20
|
+
// hook — so this iteration ships the registration API only and
|
|
21
|
+
// the rendered overlay lives behind a default-on
|
|
22
|
+
// `<View style={{ backgroundColor: '#000' }}>` you can wrap
|
|
23
|
+
// yourself. The iOS / Android crash-time screenshotters in
|
|
24
|
+
// sub-E / sub-F will read this list before drawing.
|
|
25
|
+
|
|
26
|
+
import React, { type ReactNode, useEffect, useRef } from 'react';
|
|
27
|
+
import { View, type ViewProps } from 'react-native';
|
|
28
|
+
|
|
29
|
+
/** Component-level node identifiers we've been asked to redact. */
|
|
30
|
+
const _maskedRefs = new Set<React.Component | View | unknown>();
|
|
31
|
+
const _maskedNativeIds = new Set<string>();
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Imperative registration: when you can't wrap the sensitive view
|
|
35
|
+
* in `<MaskRegion>`, drop a ref on it and call `setMaskedNode(ref)`.
|
|
36
|
+
* Future captures will mask the subtree.
|
|
37
|
+
*/
|
|
38
|
+
export function setMaskedNode(node: React.Component | View | null | unknown): void {
|
|
39
|
+
if (!node) return;
|
|
40
|
+
_maskedRefs.add(node);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Removes a previously registered ref. Pair this with mount/unmount
|
|
44
|
+
* lifecycle hooks if the node is short-lived. */
|
|
45
|
+
export function unsetMaskedNode(node: React.Component | View | null | unknown): void {
|
|
46
|
+
if (!node) return;
|
|
47
|
+
_maskedRefs.delete(node);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Returns the current set of registered masked nodes + nativeIDs.
|
|
51
|
+
* Read by the native screenshotter layer in sub-E / sub-F. */
|
|
52
|
+
export function getMaskedRegions(): {
|
|
53
|
+
refs: Set<unknown>;
|
|
54
|
+
nativeIds: Set<string>;
|
|
55
|
+
} {
|
|
56
|
+
return { nativeIds: _maskedNativeIds, refs: _maskedRefs };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Declarative redaction. `<MaskRegion>{children}</MaskRegion>` keeps
|
|
61
|
+
* the children visible in normal flight; under capture, the wrapping
|
|
62
|
+
* view is repainted black so the rendered screenshot doesn't leak
|
|
63
|
+
* the underlying pixels.
|
|
64
|
+
*/
|
|
65
|
+
export function MaskRegion({
|
|
66
|
+
children,
|
|
67
|
+
nativeID,
|
|
68
|
+
...rest
|
|
69
|
+
}: { children: ReactNode; nativeID?: string } & ViewProps): React.JSX.Element {
|
|
70
|
+
// Auto-generate a stable nativeID per mount so the native
|
|
71
|
+
// screenshotter can find this view by ID at capture time.
|
|
72
|
+
const idRef = useRef<string>(
|
|
73
|
+
nativeID ?? `sentori-mask-${Math.random().toString(36).slice(2, 10)}`,
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
const id = idRef.current;
|
|
78
|
+
_maskedNativeIds.add(id);
|
|
79
|
+
return () => {
|
|
80
|
+
_maskedNativeIds.delete(id);
|
|
81
|
+
};
|
|
82
|
+
}, []);
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<View collapsable={false} nativeID={idRef.current} {...rest}>
|
|
86
|
+
{children}
|
|
87
|
+
</View>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Test-only — flush registration tables. */
|
|
92
|
+
export function __resetMaskedRegionsForTests(): void {
|
|
93
|
+
_maskedRefs.clear();
|
|
94
|
+
_maskedNativeIds.clear();
|
|
95
|
+
}
|
package/src/transport.ts
CHANGED
|
@@ -241,3 +241,80 @@ export const sendSessionPing = async (
|
|
|
241
241
|
// best-effort
|
|
242
242
|
}
|
|
243
243
|
};
|
|
244
|
+
|
|
245
|
+
// ──────────────────────────────────────────────────────────────────
|
|
246
|
+
// Phase 42 sub-D.05 — attachment upload pipeline
|
|
247
|
+
// ──────────────────────────────────────────────────────────────────
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Upload a base64-encoded binary blob as an attachment for a known
|
|
251
|
+
* event. The event must NOT have been POSTed yet — the server-side
|
|
252
|
+
* ingest validation in events.rs only honours `event.attachments[].ref`
|
|
253
|
+
* when the matching `event_attachments` row already exists for the
|
|
254
|
+
* same (event_id, project_id). Caller's contract:
|
|
255
|
+
*
|
|
256
|
+
* 1. Generate `event.id` (uuidV7).
|
|
257
|
+
* 2. Build the blob (e.g. via `captureScreenshot`).
|
|
258
|
+
* 3. `await uploadAttachment(...)` → get `{ ref, sizeBytes, mediaType }`.
|
|
259
|
+
* 4. Push `{ ref, kind, ... }` into `event.attachments` then enqueue.
|
|
260
|
+
*
|
|
261
|
+
* Returns `null` on any non-fatal failure (network down, store
|
|
262
|
+
* disabled, 4xx, timeout). The error event still ships without the
|
|
263
|
+
* attachment so we never lose the actual crash.
|
|
264
|
+
*/
|
|
265
|
+
export const uploadAttachment = async (
|
|
266
|
+
eventId: string,
|
|
267
|
+
kind: import('./types').AttachmentMeta['kind'],
|
|
268
|
+
blob: { base64: string; mediaType: string },
|
|
269
|
+
opts: { source?: 'android' | 'ios' | 'js' } = {},
|
|
270
|
+
): Promise<import('./types').AttachmentMeta | null> => {
|
|
271
|
+
const config = getConfig();
|
|
272
|
+
if (!config) return null;
|
|
273
|
+
const url = `${config.ingestUrl}/v1/events/${encodeURIComponent(eventId)}/attachments/${encodeURIComponent(kind)}`;
|
|
274
|
+
|
|
275
|
+
// RN-style multipart: `{ uri, type, name }` is what the native
|
|
276
|
+
// FormData implementation expects for a file part — the bridge
|
|
277
|
+
// serializes a data: URI without us having to allocate a Blob.
|
|
278
|
+
const form = new FormData();
|
|
279
|
+
form.append(
|
|
280
|
+
'file',
|
|
281
|
+
{
|
|
282
|
+
name: filenameFor(kind, blob.mediaType),
|
|
283
|
+
type: blob.mediaType,
|
|
284
|
+
uri: `data:${blob.mediaType};base64,${blob.base64}`,
|
|
285
|
+
} as unknown as Blob,
|
|
286
|
+
);
|
|
287
|
+
form.append('source', opts.source ?? 'js');
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
const resp = await fetch(url, {
|
|
291
|
+
body: form,
|
|
292
|
+
headers: {
|
|
293
|
+
Authorization: `Bearer ${config.token}`,
|
|
294
|
+
'Sentori-Sdk': `react-native/${SDK_VERSION}`,
|
|
295
|
+
},
|
|
296
|
+
method: 'POST',
|
|
297
|
+
});
|
|
298
|
+
if (resp.status !== 201) return null;
|
|
299
|
+
const j = (await resp.json()) as {
|
|
300
|
+
refId: string;
|
|
301
|
+
sizeBytes: number;
|
|
302
|
+
mediaType: string;
|
|
303
|
+
kind: string;
|
|
304
|
+
};
|
|
305
|
+
return {
|
|
306
|
+
kind,
|
|
307
|
+
mediaType: j.mediaType,
|
|
308
|
+
ref: j.refId,
|
|
309
|
+
sizeBytes: j.sizeBytes,
|
|
310
|
+
source: opts.source ?? 'js',
|
|
311
|
+
};
|
|
312
|
+
} catch {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
function filenameFor(kind: string, mediaType: string): string {
|
|
318
|
+
const ext = mediaType.split('/')[1] ?? 'bin';
|
|
319
|
+
return `${kind}.${ext}`;
|
|
320
|
+
}
|