@koderlabs/tasks-sdk-rn-reporter 0.1.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.js ADDED
@@ -0,0 +1,793 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/index.ts
9
+ import * as React3 from "react";
10
+
11
+ // src/modal.tsx
12
+ import * as React2 from "react";
13
+
14
+ // src/annotator.tsx
15
+ import * as React from "react";
16
+ import { jsx, jsxs } from "react/jsx-runtime";
17
+ var RN = null;
18
+ try {
19
+ RN = __require("react-native");
20
+ } catch {
21
+ }
22
+ var Svg = null;
23
+ var SvgRect = null;
24
+ var SvgG = null;
25
+ var SvgImage = null;
26
+ try {
27
+ const mod = __require("react-native-svg");
28
+ Svg = mod.default ?? mod.Svg;
29
+ SvgRect = mod.Rect;
30
+ SvgG = mod.G;
31
+ SvgImage = mod.Image;
32
+ } catch {
33
+ }
34
+ function Annotator(props) {
35
+ const [tool, setTool] = React.useState("highlight");
36
+ const [commands, setCommands] = React.useState([]);
37
+ const [draftRect, setDraftRect] = React.useState(null);
38
+ const layoutRef = React.useRef({ w: 0, h: 0 });
39
+ const startRef = React.useRef(null);
40
+ const panResponder = React.useMemo(() => {
41
+ if (!RN) return null;
42
+ return RN.PanResponder.create({
43
+ onStartShouldSetPanResponder: () => true,
44
+ onMoveShouldSetPanResponder: () => true,
45
+ onPanResponderGrant: (_e, gestureState) => {
46
+ startRef.current = { x: gestureState.x0, y: gestureState.y0 };
47
+ setDraftRect({ x: gestureState.x0, y: gestureState.y0, w: 0, h: 0 });
48
+ },
49
+ onPanResponderMove: (_e, gestureState) => {
50
+ const start = startRef.current;
51
+ if (!start) return;
52
+ setDraftRect(buildPixelRect(start, { x: gestureState.moveX, y: gestureState.moveY }));
53
+ },
54
+ onPanResponderRelease: (_e, gestureState) => {
55
+ const start = startRef.current;
56
+ startRef.current = null;
57
+ if (!start) {
58
+ setDraftRect(null);
59
+ return;
60
+ }
61
+ const endX = gestureState.moveX || start.x;
62
+ const endY = gestureState.moveY || start.y;
63
+ const rect = buildPixelRect(start, { x: endX, y: endY });
64
+ setDraftRect(null);
65
+ if (rect.w < 8 || rect.h < 8) return;
66
+ const { w: W2, h: H2 } = layoutRef.current;
67
+ if (W2 === 0 || H2 === 0) return;
68
+ setCommands((prev) => [
69
+ ...prev,
70
+ {
71
+ type: tool,
72
+ x: clamp01(rect.x / W2),
73
+ y: clamp01(rect.y / H2),
74
+ w: clamp01(rect.w / W2),
75
+ h: clamp01(rect.h / H2)
76
+ }
77
+ ]);
78
+ },
79
+ onPanResponderTerminate: () => {
80
+ startRef.current = null;
81
+ setDraftRect(null);
82
+ }
83
+ });
84
+ }, [tool]);
85
+ if (!RN) return null;
86
+ const { View, Text, TouchableOpacity, Image } = RN;
87
+ function onLayout(e) {
88
+ layoutRef.current = { w: e.nativeEvent.layout.width, h: e.nativeEvent.layout.height };
89
+ }
90
+ function undo() {
91
+ setCommands((prev) => prev.slice(0, -1));
92
+ }
93
+ function clear() {
94
+ setCommands([]);
95
+ }
96
+ const { w: W, h: H } = layoutRef.current;
97
+ const pixelCommands = W > 0 && H > 0 ? commands.map((c) => ({
98
+ type: c.type,
99
+ x: c.x * W,
100
+ y: c.y * H,
101
+ w: c.w * W,
102
+ h: c.h * H
103
+ })) : [];
104
+ return /* @__PURE__ */ jsxs(View, { style: styles.root, children: [
105
+ /* @__PURE__ */ jsxs(
106
+ View,
107
+ {
108
+ style: styles.canvas,
109
+ onLayout,
110
+ ...panResponder?.panHandlers ?? {},
111
+ children: [
112
+ /* @__PURE__ */ jsx(
113
+ Image,
114
+ {
115
+ source: { uri: props.imageUri },
116
+ style: styles.image,
117
+ resizeMode: "contain"
118
+ }
119
+ ),
120
+ Svg && W > 0 && H > 0 && /* @__PURE__ */ jsx(Svg, { width: W, height: H, style: styles.svg, pointerEvents: "none", children: /* @__PURE__ */ jsxs(SvgG, { children: [
121
+ pixelCommands.map((cmd, i) => renderRect(cmd, i)),
122
+ draftRect && renderRect({ type: tool, ...draftRect }, -1)
123
+ ] }) })
124
+ ]
125
+ }
126
+ ),
127
+ /* @__PURE__ */ jsxs(View, { style: styles.toolbar, children: [
128
+ /* @__PURE__ */ jsxs(View, { style: styles.toolGroup, children: [
129
+ /* @__PURE__ */ jsx(
130
+ ToolButton,
131
+ {
132
+ label: "Highlight",
133
+ active: tool === "highlight",
134
+ onPress: () => setTool("highlight")
135
+ }
136
+ ),
137
+ /* @__PURE__ */ jsx(
138
+ ToolButton,
139
+ {
140
+ label: "Redact",
141
+ active: tool === "hide",
142
+ onPress: () => setTool("hide")
143
+ }
144
+ )
145
+ ] }),
146
+ /* @__PURE__ */ jsxs(View, { style: styles.toolGroup, children: [
147
+ /* @__PURE__ */ jsx(
148
+ TouchableOpacity,
149
+ {
150
+ onPress: undo,
151
+ disabled: commands.length === 0,
152
+ style: [styles.iconBtn, commands.length === 0 && styles.iconBtnDisabled],
153
+ accessibilityRole: "button",
154
+ accessibilityLabel: "Undo",
155
+ children: /* @__PURE__ */ jsx(Text, { style: styles.iconBtnText, children: "Undo" })
156
+ }
157
+ ),
158
+ /* @__PURE__ */ jsx(
159
+ TouchableOpacity,
160
+ {
161
+ onPress: clear,
162
+ disabled: commands.length === 0,
163
+ style: [styles.iconBtn, commands.length === 0 && styles.iconBtnDisabled],
164
+ accessibilityRole: "button",
165
+ accessibilityLabel: "Clear all",
166
+ children: /* @__PURE__ */ jsx(Text, { style: styles.iconBtnText, children: "Clear" })
167
+ }
168
+ )
169
+ ] })
170
+ ] }),
171
+ /* @__PURE__ */ jsxs(View, { style: styles.actions, children: [
172
+ /* @__PURE__ */ jsx(
173
+ TouchableOpacity,
174
+ {
175
+ onPress: props.onCancel,
176
+ style: styles.cancelBtn,
177
+ accessibilityRole: "button",
178
+ children: /* @__PURE__ */ jsx(Text, { style: styles.cancelBtnText, children: "Cancel" })
179
+ }
180
+ ),
181
+ /* @__PURE__ */ jsx(
182
+ TouchableOpacity,
183
+ {
184
+ onPress: () => props.onCommit(commands),
185
+ style: styles.doneBtn,
186
+ accessibilityRole: "button",
187
+ children: /* @__PURE__ */ jsx(Text, { style: styles.doneBtnText, children: "Done" })
188
+ }
189
+ )
190
+ ] })
191
+ ] });
192
+ }
193
+ function ToolButton(props) {
194
+ const { TouchableOpacity, Text } = RN;
195
+ return /* @__PURE__ */ jsx(
196
+ TouchableOpacity,
197
+ {
198
+ onPress: props.onPress,
199
+ style: [styles.toolBtn, props.active && styles.toolBtnActive],
200
+ accessibilityRole: "button",
201
+ accessibilityState: { selected: props.active },
202
+ children: /* @__PURE__ */ jsx(Text, { style: [styles.toolBtnText, props.active && styles.toolBtnTextActive], children: props.label })
203
+ }
204
+ );
205
+ }
206
+ function renderRect(cmd, key) {
207
+ if (!SvgRect) return null;
208
+ const isRedact = cmd.type === "hide";
209
+ return /* @__PURE__ */ jsx(
210
+ SvgRect,
211
+ {
212
+ x: cmd.x,
213
+ y: cmd.y,
214
+ width: cmd.w,
215
+ height: cmd.h,
216
+ fill: isRedact ? "#000000" : "rgba(220, 38, 38, 0.15)",
217
+ stroke: isRedact ? "#000000" : "#dc2626",
218
+ strokeWidth: isRedact ? 0 : 3
219
+ },
220
+ key
221
+ );
222
+ }
223
+ function buildPixelRect(a, b) {
224
+ return {
225
+ x: Math.min(a.x, b.x),
226
+ y: Math.min(a.y, b.y),
227
+ w: Math.abs(b.x - a.x),
228
+ h: Math.abs(b.y - a.y)
229
+ };
230
+ }
231
+ function clamp01(v) {
232
+ if (v < 0) return 0;
233
+ if (v > 1) return 1;
234
+ return v;
235
+ }
236
+ var styles = RN ? RN.StyleSheet.create({
237
+ root: { flex: 1, backgroundColor: "#111827" },
238
+ canvas: { flex: 1, position: "relative" },
239
+ image: { ...RN.StyleSheet.absoluteFillObject },
240
+ svg: { ...RN.StyleSheet.absoluteFillObject },
241
+ toolbar: {
242
+ flexDirection: "row",
243
+ justifyContent: "space-between",
244
+ paddingHorizontal: 16,
245
+ paddingVertical: 12,
246
+ backgroundColor: "#1f2937",
247
+ borderTopWidth: 1,
248
+ borderTopColor: "#374151"
249
+ },
250
+ toolGroup: { flexDirection: "row", gap: 8 },
251
+ toolBtn: {
252
+ paddingHorizontal: 14,
253
+ paddingVertical: 8,
254
+ borderRadius: 8,
255
+ backgroundColor: "#374151"
256
+ },
257
+ toolBtnActive: { backgroundColor: "#4f46e5" },
258
+ toolBtnText: { color: "#d1d5db", fontSize: 14, fontWeight: "500" },
259
+ toolBtnTextActive: { color: "#fff" },
260
+ iconBtn: {
261
+ paddingHorizontal: 12,
262
+ paddingVertical: 8,
263
+ borderRadius: 8,
264
+ backgroundColor: "#374151"
265
+ },
266
+ iconBtnDisabled: { opacity: 0.4 },
267
+ iconBtnText: { color: "#d1d5db", fontSize: 14 },
268
+ actions: {
269
+ flexDirection: "row",
270
+ gap: 12,
271
+ padding: 16,
272
+ backgroundColor: "#1f2937",
273
+ borderTopWidth: 1,
274
+ borderTopColor: "#374151"
275
+ },
276
+ cancelBtn: {
277
+ flex: 1,
278
+ paddingVertical: 14,
279
+ borderRadius: 8,
280
+ backgroundColor: "#374151",
281
+ alignItems: "center"
282
+ },
283
+ cancelBtnText: { color: "#fff", fontSize: 16, fontWeight: "500" },
284
+ doneBtn: {
285
+ flex: 1,
286
+ paddingVertical: 14,
287
+ borderRadius: 8,
288
+ backgroundColor: "#4f46e5",
289
+ alignItems: "center"
290
+ },
291
+ doneBtnText: { color: "#fff", fontSize: 16, fontWeight: "600" }
292
+ }) : {};
293
+
294
+ // src/modal.tsx
295
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
296
+ var RN2;
297
+ try {
298
+ RN2 = __require("react-native");
299
+ } catch {
300
+ RN2 = null;
301
+ }
302
+ function BugReportModal(props) {
303
+ const [description, setDescription] = React2.useState("");
304
+ const [email, setEmail] = React2.useState(props.defaultEmail ?? "");
305
+ const [annotations, setAnnotations] = React2.useState([]);
306
+ const [annotating, setAnnotating] = React2.useState(false);
307
+ React2.useEffect(() => {
308
+ if (props.visible) {
309
+ setDescription("");
310
+ setEmail(props.defaultEmail ?? "");
311
+ setAnnotations([]);
312
+ setAnnotating(false);
313
+ }
314
+ }, [props.visible, props.defaultEmail]);
315
+ if (!RN2) return null;
316
+ const { Modal, View, Text, TextInput, TouchableOpacity, Image, ScrollView, ActivityIndicator } = RN2;
317
+ const canSubmit = description.trim().length > 0 && !props.submitting;
318
+ return /* @__PURE__ */ jsx2(
319
+ Modal,
320
+ {
321
+ visible: props.visible,
322
+ animationType: "slide",
323
+ transparent: false,
324
+ onRequestClose: props.onCancel,
325
+ children: /* @__PURE__ */ jsxs2(View, { style: styles2.container, children: [
326
+ /* @__PURE__ */ jsxs2(View, { style: styles2.header, children: [
327
+ /* @__PURE__ */ jsx2(Text, { style: styles2.title, children: "Report a bug" }),
328
+ /* @__PURE__ */ jsx2(TouchableOpacity, { onPress: props.onCancel, accessibilityRole: "button", children: /* @__PURE__ */ jsx2(Text, { style: styles2.cancelBtn, children: "Cancel" }) })
329
+ ] }),
330
+ /* @__PURE__ */ jsxs2(ScrollView, { contentContainerStyle: styles2.body, keyboardShouldPersistTaps: "handled", children: [
331
+ props.screenshotUri ? /* @__PURE__ */ jsxs2(View, { children: [
332
+ /* @__PURE__ */ jsx2(
333
+ Image,
334
+ {
335
+ source: { uri: props.screenshotUri },
336
+ style: styles2.screenshot,
337
+ resizeMode: "contain"
338
+ }
339
+ ),
340
+ /* @__PURE__ */ jsx2(View, { style: styles2.screenshotActions, children: /* @__PURE__ */ jsx2(
341
+ TouchableOpacity,
342
+ {
343
+ onPress: () => setAnnotating(true),
344
+ style: styles2.annotateBtn,
345
+ accessibilityRole: "button",
346
+ disabled: props.submitting,
347
+ children: /* @__PURE__ */ jsx2(Text, { style: styles2.annotateBtnText, children: annotations.length > 0 ? `Annotate (${annotations.length})` : "Annotate" })
348
+ }
349
+ ) })
350
+ ] }) : /* @__PURE__ */ jsx2(View, { style: styles2.placeholder, children: /* @__PURE__ */ jsx2(Text, { style: styles2.placeholderText, children: "No screenshot" }) }),
351
+ /* @__PURE__ */ jsx2(Text, { style: styles2.label, children: "What went wrong?" }),
352
+ /* @__PURE__ */ jsx2(
353
+ TextInput,
354
+ {
355
+ style: styles2.descriptionInput,
356
+ value: description,
357
+ onChangeText: setDescription,
358
+ placeholder: "Describe what you expected vs. what happened\u2026",
359
+ multiline: true,
360
+ numberOfLines: 5,
361
+ autoFocus: true,
362
+ editable: !props.submitting
363
+ }
364
+ ),
365
+ props.collectEmail && /* @__PURE__ */ jsxs2(Fragment, { children: [
366
+ /* @__PURE__ */ jsx2(Text, { style: styles2.label, children: "Your email (optional)" }),
367
+ /* @__PURE__ */ jsx2(
368
+ TextInput,
369
+ {
370
+ style: styles2.emailInput,
371
+ value: email,
372
+ onChangeText: setEmail,
373
+ placeholder: "you@example.com",
374
+ autoCapitalize: "none",
375
+ keyboardType: "email-address",
376
+ editable: !props.submitting
377
+ }
378
+ )
379
+ ] })
380
+ ] }),
381
+ /* @__PURE__ */ jsx2(View, { style: styles2.footer, children: /* @__PURE__ */ jsx2(
382
+ TouchableOpacity,
383
+ {
384
+ disabled: !canSubmit,
385
+ onPress: () => props.onSubmit({
386
+ description: description.trim(),
387
+ email: email.trim() || void 0,
388
+ annotations: annotations.length > 0 ? annotations : void 0
389
+ }),
390
+ style: [styles2.submitBtn, !canSubmit && styles2.submitBtnDisabled],
391
+ accessibilityRole: "button",
392
+ children: props.submitting ? /* @__PURE__ */ jsx2(ActivityIndicator, { color: "#fff" }) : /* @__PURE__ */ jsx2(Text, { style: styles2.submitBtnText, children: "Send" })
393
+ }
394
+ ) }),
395
+ annotating && props.screenshotUri && /* @__PURE__ */ jsx2(
396
+ Modal,
397
+ {
398
+ visible: true,
399
+ animationType: "fade",
400
+ transparent: false,
401
+ onRequestClose: () => setAnnotating(false),
402
+ children: /* @__PURE__ */ jsx2(
403
+ Annotator,
404
+ {
405
+ imageUri: props.screenshotUri,
406
+ onCommit: (cmds) => {
407
+ setAnnotations(cmds);
408
+ setAnnotating(false);
409
+ },
410
+ onCancel: () => setAnnotating(false)
411
+ }
412
+ )
413
+ }
414
+ )
415
+ ] })
416
+ }
417
+ );
418
+ }
419
+ var styles2 = RN2 ? RN2.StyleSheet.create({
420
+ container: { flex: 1, backgroundColor: "#fff" },
421
+ header: {
422
+ flexDirection: "row",
423
+ justifyContent: "space-between",
424
+ alignItems: "center",
425
+ padding: 16,
426
+ borderBottomWidth: 1,
427
+ borderBottomColor: "#e5e7eb"
428
+ },
429
+ title: { fontSize: 18, fontWeight: "600", color: "#111827" },
430
+ cancelBtn: { fontSize: 16, color: "#6b7280" },
431
+ body: { padding: 16, paddingBottom: 32 },
432
+ screenshot: {
433
+ width: "100%",
434
+ height: 240,
435
+ backgroundColor: "#f3f4f6",
436
+ borderRadius: 8,
437
+ marginBottom: 16
438
+ },
439
+ placeholder: {
440
+ width: "100%",
441
+ height: 120,
442
+ backgroundColor: "#f3f4f6",
443
+ borderRadius: 8,
444
+ marginBottom: 16,
445
+ alignItems: "center",
446
+ justifyContent: "center"
447
+ },
448
+ placeholderText: { color: "#9ca3af" },
449
+ screenshotActions: { flexDirection: "row", justifyContent: "flex-end", marginTop: -8, marginBottom: 16 },
450
+ annotateBtn: {
451
+ paddingHorizontal: 12,
452
+ paddingVertical: 6,
453
+ borderRadius: 6,
454
+ backgroundColor: "#eef2ff"
455
+ },
456
+ annotateBtnText: { color: "#4f46e5", fontSize: 13, fontWeight: "600" },
457
+ label: { fontSize: 14, fontWeight: "500", color: "#374151", marginBottom: 6, marginTop: 8 },
458
+ descriptionInput: {
459
+ borderWidth: 1,
460
+ borderColor: "#d1d5db",
461
+ borderRadius: 8,
462
+ padding: 12,
463
+ fontSize: 15,
464
+ minHeight: 120,
465
+ textAlignVertical: "top",
466
+ color: "#111827",
467
+ backgroundColor: "#fff"
468
+ },
469
+ emailInput: {
470
+ borderWidth: 1,
471
+ borderColor: "#d1d5db",
472
+ borderRadius: 8,
473
+ padding: 12,
474
+ fontSize: 15,
475
+ color: "#111827",
476
+ backgroundColor: "#fff"
477
+ },
478
+ footer: { padding: 16, borderTopWidth: 1, borderTopColor: "#e5e7eb" },
479
+ submitBtn: {
480
+ backgroundColor: "#4f46e5",
481
+ borderRadius: 8,
482
+ padding: 14,
483
+ alignItems: "center"
484
+ },
485
+ submitBtnDisabled: { backgroundColor: "#a5b4fc" },
486
+ submitBtnText: { color: "#fff", fontSize: 16, fontWeight: "600" }
487
+ }) : {};
488
+
489
+ // src/shake.ts
490
+ var SHAKE_THRESHOLD = 2.7;
491
+ var SHAKE_COOLDOWN_MS = 1500;
492
+ function installShakeListener(onShake) {
493
+ const teardowns = [];
494
+ let lastFiredAt = 0;
495
+ const fire = () => {
496
+ const now = Date.now();
497
+ if (now - lastFiredAt < SHAKE_COOLDOWN_MS) return;
498
+ lastFiredAt = now;
499
+ try {
500
+ onShake();
501
+ } catch {
502
+ }
503
+ };
504
+ try {
505
+ const RN3 = __require("react-native");
506
+ const emitter = RN3.DeviceEventEmitter ?? RN3.NativeAppEventEmitter;
507
+ if (emitter?.addListener) {
508
+ const subs = [
509
+ emitter.addListener("AccelerometerSecondaryShake", fire),
510
+ emitter.addListener("shake", fire)
511
+ ];
512
+ teardowns.push(() => subs.forEach((s) => s?.remove?.()));
513
+ }
514
+ } catch {
515
+ }
516
+ try {
517
+ const sensors = __require("expo-sensors");
518
+ if (sensors?.Accelerometer) {
519
+ sensors.Accelerometer.setUpdateInterval(50);
520
+ const sub = sensors.Accelerometer.addListener(
521
+ ({ x, y, z }) => {
522
+ const magnitude = Math.sqrt(x * x + y * y + z * z);
523
+ if (magnitude > SHAKE_THRESHOLD) fire();
524
+ }
525
+ );
526
+ teardowns.push(() => sub?.remove?.());
527
+ }
528
+ } catch {
529
+ }
530
+ return () => {
531
+ for (const t of teardowns) {
532
+ try {
533
+ t();
534
+ } catch {
535
+ }
536
+ }
537
+ };
538
+ }
539
+
540
+ // src/screenshot-detection.ts
541
+ function installScreenshotDetection(onUserAccepted) {
542
+ let RN3 = null;
543
+ try {
544
+ RN3 = __require("react-native");
545
+ } catch {
546
+ }
547
+ if (!RN3 || RN3.Platform.OS !== "ios") return () => {
548
+ };
549
+ let capture = null;
550
+ try {
551
+ capture = __require("expo-screen-capture");
552
+ } catch {
553
+ if (__DEV__) {
554
+ console.warn(
555
+ "[InstantTasks reporter] enableScreenshotDetection requires expo-screen-capture; install it or disable the option."
556
+ );
557
+ }
558
+ return () => {
559
+ };
560
+ }
561
+ const Alert = RN3.Alert;
562
+ const sub = capture.addScreenshotListener(() => {
563
+ Alert.alert(
564
+ "Report a problem?",
565
+ "You just took a screenshot. Send it as a bug report?",
566
+ [
567
+ { text: "No", style: "cancel" },
568
+ { text: "Yes", onPress: () => {
569
+ try {
570
+ onUserAccepted();
571
+ } catch {
572
+ }
573
+ } }
574
+ ],
575
+ { cancelable: true }
576
+ );
577
+ });
578
+ return () => {
579
+ try {
580
+ sub?.remove?.();
581
+ } catch {
582
+ }
583
+ };
584
+ }
585
+
586
+ // src/transport.ts
587
+ function unwrap(c) {
588
+ return c.client ?? c;
589
+ }
590
+ async function fileUriToBlob(uri) {
591
+ try {
592
+ const res = await fetch(uri);
593
+ return await res.blob();
594
+ } catch {
595
+ return null;
596
+ }
597
+ }
598
+ async function submitBugReport(c, submission) {
599
+ const client = unwrap(c);
600
+ const Platform = (
601
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
602
+ (() => {
603
+ try {
604
+ return __require("react-native").Platform ?? {};
605
+ } catch {
606
+ return {};
607
+ }
608
+ })()
609
+ );
610
+ const event = {
611
+ kind: "bug_report",
612
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
613
+ platform: {
614
+ os: Platform.OS,
615
+ version: Platform.Version
616
+ },
617
+ payload: {
618
+ description: submission.description,
619
+ email: submission.email,
620
+ defaultIssueTypeId: submission.defaultIssueTypeId,
621
+ defaultPriorityId: submission.defaultPriorityId,
622
+ annotations: submission.annotations,
623
+ metadata: {
624
+ customData: submission.customData
625
+ }
626
+ }
627
+ };
628
+ const attachments = /* @__PURE__ */ new Map();
629
+ if (submission.screenshotUri) {
630
+ const blob = await fileUriToBlob(submission.screenshotUri);
631
+ if (blob) attachments.set("screenshot", blob);
632
+ }
633
+ const result = await client.send(
634
+ event,
635
+ attachments.size > 0 ? attachments : void 0
636
+ );
637
+ const ticketKey = result.ticketKey ?? "unknown";
638
+ const baseUrl = client.options.endpoint?.replace(/\/+$/, "") ?? "";
639
+ const ticketUrl = `${baseUrl}/tickets/${ticketKey}`;
640
+ return { ticketKey, ticketUrl };
641
+ }
642
+
643
+ // src/index.ts
644
+ function initReporter(client, opts = {}) {
645
+ let isOpen = false;
646
+ let submitting = false;
647
+ let screenshotUri;
648
+ let rerender = null;
649
+ let teardowns = [];
650
+ let RN3 = null;
651
+ try {
652
+ RN3 = __require("react-native");
653
+ } catch {
654
+ }
655
+ async function captureScreenshot() {
656
+ if (!opts.captureRef?.current) return void 0;
657
+ try {
658
+ const viewShot = __require("react-native-view-shot");
659
+ const uri = await viewShot.captureRef(opts.captureRef, {
660
+ format: "png",
661
+ quality: 0.9,
662
+ result: "tmpfile"
663
+ });
664
+ return uri;
665
+ } catch {
666
+ return void 0;
667
+ }
668
+ }
669
+ async function openModal() {
670
+ if (isOpen || submitting) return;
671
+ screenshotUri = await captureScreenshot();
672
+ isOpen = true;
673
+ rerender?.();
674
+ }
675
+ function closeModal() {
676
+ if (!isOpen) return;
677
+ isOpen = false;
678
+ screenshotUri = void 0;
679
+ rerender?.();
680
+ }
681
+ async function handleSubmit(data) {
682
+ submitting = true;
683
+ rerender?.();
684
+ try {
685
+ const result = await submitBugReport(client, {
686
+ description: data.description,
687
+ email: data.email,
688
+ screenshotUri,
689
+ annotations: data.annotations,
690
+ defaultIssueTypeId: opts.defaultIssueTypeId,
691
+ defaultPriorityId: opts.defaultPriorityId,
692
+ customData: opts.customData
693
+ });
694
+ submitting = false;
695
+ isOpen = false;
696
+ screenshotUri = void 0;
697
+ rerender?.();
698
+ if (!opts.silent && RN3?.Alert) {
699
+ RN3.Alert.alert("Thanks!", `Bug report submitted (${result.ticketKey}).`);
700
+ }
701
+ } catch (err) {
702
+ submitting = false;
703
+ rerender?.();
704
+ if (!opts.silent && RN3?.Alert) {
705
+ const msg = err instanceof Error ? err.message : "Failed to submit";
706
+ RN3.Alert.alert(
707
+ "Could not send report",
708
+ msg,
709
+ [
710
+ { text: "Cancel", style: "cancel", onPress: closeModal },
711
+ { text: "Retry", onPress: () => handleSubmit(data) }
712
+ ]
713
+ );
714
+ }
715
+ }
716
+ }
717
+ reporterSingleton = {
718
+ isOpen: () => isOpen,
719
+ submitting: () => submitting,
720
+ screenshotUri: () => screenshotUri,
721
+ collectEmail: opts.collectEmail,
722
+ onCancel: closeModal,
723
+ onSubmit: (data) => {
724
+ void handleSubmit(data);
725
+ },
726
+ register: (cb) => {
727
+ rerender = cb;
728
+ },
729
+ unregister: () => {
730
+ rerender = null;
731
+ }
732
+ };
733
+ if (opts.enableShakeGesture) {
734
+ teardowns.push(installShakeListener(() => {
735
+ void openModal();
736
+ }));
737
+ }
738
+ if (opts.enableScreenshotDetection) {
739
+ teardowns.push(installScreenshotDetection(() => {
740
+ void openModal();
741
+ }));
742
+ }
743
+ const handle = {
744
+ show() {
745
+ void openModal();
746
+ },
747
+ hide() {
748
+ closeModal();
749
+ },
750
+ close() {
751
+ closeModal();
752
+ for (const fn of teardowns) {
753
+ try {
754
+ fn();
755
+ } catch {
756
+ }
757
+ }
758
+ teardowns = [];
759
+ reporterSingleton = null;
760
+ }
761
+ };
762
+ return handle;
763
+ }
764
+ var reporterSingleton = null;
765
+ function ReporterRoot() {
766
+ const [, setTick] = React3.useState(0);
767
+ const force = React3.useCallback(() => setTick((t) => t + 1), []);
768
+ React3.useEffect(() => {
769
+ if (!reporterSingleton) return;
770
+ reporterSingleton.register(force);
771
+ return () => {
772
+ reporterSingleton?.unregister();
773
+ };
774
+ }, [force]);
775
+ if (!reporterSingleton) return null;
776
+ return React3.createElement(BugReportModal, {
777
+ visible: reporterSingleton.isOpen(),
778
+ submitting: reporterSingleton.submitting(),
779
+ screenshotUri: reporterSingleton.screenshotUri(),
780
+ collectEmail: reporterSingleton.collectEmail,
781
+ onCancel: reporterSingleton.onCancel,
782
+ onSubmit: reporterSingleton.onSubmit
783
+ });
784
+ }
785
+ var index_default = initReporter;
786
+ export {
787
+ Annotator,
788
+ ReporterRoot,
789
+ index_default as default,
790
+ initReporter,
791
+ submitBugReport
792
+ };
793
+ //# sourceMappingURL=index.js.map