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