@mindedsolutions/bug-reporter-sdk 0.1.2 → 0.3.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.
|
@@ -12,18 +12,24 @@ const ScreenshotPreview_1 = require("./ScreenshotPreview");
|
|
|
12
12
|
const constants_1 = require("../constants");
|
|
13
13
|
function ReportModal() {
|
|
14
14
|
const { config, translations, isModalVisible, closeModal } = (0, useBugReporter_1.useBugReporter)();
|
|
15
|
-
const {
|
|
15
|
+
const { captureAndUpload } = (0, useScreenCapture_1.useScreenCapture)();
|
|
16
16
|
const { getDeviceInfo, getAppInfo, getNetworkInfo } = (0, useDeviceInfo_1.useDeviceInfo)();
|
|
17
17
|
const [description, setDescription] = (0, react_1.useState)('');
|
|
18
18
|
const [category, setCategory] = (0, react_1.useState)(config.defaultCategory ?? 'Bug');
|
|
19
19
|
const [severity, setSeverity] = (0, react_1.useState)();
|
|
20
20
|
const [screenshotUri, setScreenshotUri] = (0, react_1.useState)(null);
|
|
21
|
+
const [screenshotUrl, setScreenshotUrl] = (0, react_1.useState)(null);
|
|
21
22
|
const [isSubmitting, setIsSubmitting] = (0, react_1.useState)(false);
|
|
22
23
|
const categories = config.categories ?? constants_1.DEFAULT_CATEGORIES;
|
|
23
|
-
// Capture screenshot when modal opens
|
|
24
|
+
// Capture screenshot and upload when modal opens
|
|
24
25
|
(0, react_1.useEffect)(() => {
|
|
25
26
|
if (isModalVisible) {
|
|
26
|
-
|
|
27
|
+
captureAndUpload().then((result) => {
|
|
28
|
+
if (result) {
|
|
29
|
+
setScreenshotUri(result.uri);
|
|
30
|
+
setScreenshotUrl(result.url || null);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
27
33
|
}
|
|
28
34
|
else {
|
|
29
35
|
// Reset form when modal closes
|
|
@@ -31,6 +37,7 @@ function ReportModal() {
|
|
|
31
37
|
setCategory(config.defaultCategory ?? 'Bug');
|
|
32
38
|
setSeverity(undefined);
|
|
33
39
|
setScreenshotUri(null);
|
|
40
|
+
setScreenshotUrl(null);
|
|
34
41
|
setIsSubmitting(false);
|
|
35
42
|
}
|
|
36
43
|
}, [isModalVisible]);
|
|
@@ -42,17 +49,11 @@ function ReportModal() {
|
|
|
42
49
|
const device = getDeviceInfo();
|
|
43
50
|
const app = getAppInfo();
|
|
44
51
|
const network = await getNetworkInfo();
|
|
45
|
-
let screenshotUrl;
|
|
46
|
-
if (screenshotUri) {
|
|
47
|
-
const url = await uploadScreenshot(screenshotUri);
|
|
48
|
-
if (url)
|
|
49
|
-
screenshotUrl = url;
|
|
50
|
-
}
|
|
51
52
|
const payload = {
|
|
52
53
|
description: description.trim(),
|
|
53
54
|
category,
|
|
54
55
|
severity,
|
|
55
|
-
screenshot_url: screenshotUrl,
|
|
56
|
+
screenshot_url: screenshotUrl || undefined,
|
|
56
57
|
device_brand: device.brand,
|
|
57
58
|
device_model: device.model,
|
|
58
59
|
device_os: device.os,
|
|
@@ -82,7 +83,7 @@ function ReportModal() {
|
|
|
82
83
|
finally {
|
|
83
84
|
setIsSubmitting(false);
|
|
84
85
|
}
|
|
85
|
-
}, [description, category, severity,
|
|
86
|
+
}, [description, category, severity, screenshotUrl, config, translations, closeModal]);
|
|
86
87
|
return ((0, jsx_runtime_1.jsx)(react_native_1.Modal, { visible: isModalVisible, animationType: "slide", presentationStyle: "fullScreen", onRequestClose: closeModal, children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.container, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.safeTop }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.header, children: [(0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { onPress: closeModal, disabled: isSubmitting, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.cancelText, children: translations.cancel }) }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.title, children: translations.reportBug }), (0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { onPress: handleSubmit, disabled: isSubmitting || !description.trim(), children: isSubmitting ? ((0, jsx_runtime_1.jsx)(react_native_1.ActivityIndicator, { size: "small", color: "#6366f1" })) : ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.submitText, !description.trim() && styles.disabledText], children: translations.submit })) })] }), (0, jsx_runtime_1.jsxs)(react_native_1.ScrollView, { style: styles.body, contentContainerStyle: styles.bodyContent, keyboardShouldPersistTaps: "handled", children: [screenshotUri && ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.section, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.label, children: translations.screenshot }), (0, jsx_runtime_1.jsx)(ScreenshotPreview_1.ScreenshotPreview, { uri: screenshotUri, onRemove: () => setScreenshotUri(null), removeLabel: translations.removeScreenshot })] })), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.section, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.label, children: translations.description }), (0, jsx_runtime_1.jsx)(react_native_1.TextInput, { style: styles.textInput, value: description, onChangeText: setDescription, placeholder: translations.descriptionPlaceholder, placeholderTextColor: "#9ca3af", multiline: true, numberOfLines: 4, textAlignVertical: "top" })] }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.section, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.label, children: translations.category }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.chipRow, children: categories.map((cat) => ((0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { style: [styles.chip, category === cat && styles.chipActive], onPress: () => setCategory(cat), children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.chipText, category === cat && styles.chipTextActive], children: cat }) }, cat))) })] }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.section, children: [(0, jsx_runtime_1.jsxs)(react_native_1.Text, { style: styles.label, children: [translations.severity, " ", (0, jsx_runtime_1.jsxs)(react_native_1.Text, { style: styles.optional, children: ["(", translations.optional, ")"] })] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.chipRow, children: constants_1.SEVERITIES.map((sev) => {
|
|
87
88
|
const labels = {
|
|
88
89
|
low: translations.severityLow,
|
|
@@ -6,45 +6,62 @@ const react_native_view_shot_1 = require("react-native-view-shot");
|
|
|
6
6
|
const supabase_js_1 = require("@supabase/supabase-js");
|
|
7
7
|
const useBugReporter_1 = require("./useBugReporter");
|
|
8
8
|
const constants_1 = require("../constants");
|
|
9
|
+
function base64ToArrayBuffer(base64) {
|
|
10
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
11
|
+
const clean = base64.replace(/[^A-Za-z0-9+/]/g, '');
|
|
12
|
+
const len = clean.length;
|
|
13
|
+
const byteLen = (len * 3) / 4 - (base64.endsWith('==') ? 2 : base64.endsWith('=') ? 1 : 0);
|
|
14
|
+
const buffer = new ArrayBuffer(byteLen);
|
|
15
|
+
const bytes = new Uint8Array(buffer);
|
|
16
|
+
let p = 0;
|
|
17
|
+
for (let i = 0; i < len; i += 4) {
|
|
18
|
+
const a = chars.indexOf(clean[i]);
|
|
19
|
+
const b = chars.indexOf(clean[i + 1]);
|
|
20
|
+
const c = chars.indexOf(clean[i + 2]);
|
|
21
|
+
const d = chars.indexOf(clean[i + 3]);
|
|
22
|
+
const bits = (a << 18) | (b << 12) | (c << 6) | d;
|
|
23
|
+
bytes[p++] = (bits >> 16) & 0xff;
|
|
24
|
+
if (p < byteLen)
|
|
25
|
+
bytes[p++] = (bits >> 8) & 0xff;
|
|
26
|
+
if (p < byteLen)
|
|
27
|
+
bytes[p++] = bits & 0xff;
|
|
28
|
+
}
|
|
29
|
+
return buffer;
|
|
30
|
+
}
|
|
9
31
|
function useScreenCapture() {
|
|
10
32
|
const { viewRef, config } = (0, useBugReporter_1.useBugReporter)();
|
|
11
|
-
const
|
|
33
|
+
const captureAndUpload = (0, react_1.useCallback)(async () => {
|
|
12
34
|
try {
|
|
13
35
|
if (!viewRef.current)
|
|
14
36
|
return null;
|
|
37
|
+
// Capture as both URI (for preview) and base64 (for upload)
|
|
15
38
|
const uri = await (0, react_native_view_shot_1.captureRef)(viewRef.current, {
|
|
16
39
|
format: constants_1.SCREENSHOT_FORMAT,
|
|
17
40
|
quality: constants_1.SCREENSHOT_QUALITY,
|
|
18
41
|
});
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const uploadScreenshot = (0, react_1.useCallback)(async (uri) => {
|
|
26
|
-
try {
|
|
42
|
+
const base64 = await (0, react_native_view_shot_1.captureRef)(viewRef.current, {
|
|
43
|
+
format: constants_1.SCREENSHOT_FORMAT,
|
|
44
|
+
quality: constants_1.SCREENSHOT_QUALITY,
|
|
45
|
+
result: 'base64',
|
|
46
|
+
});
|
|
47
|
+
// Upload base64 decoded to Supabase Storage
|
|
27
48
|
const supabase = (0, supabase_js_1.createClient)(config.supabaseUrl, config.supabaseAnonKey);
|
|
28
49
|
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
29
50
|
const fileName = `${config.projectId}/${uniqueId}.jpg`;
|
|
30
|
-
|
|
31
|
-
const formData = new FormData();
|
|
32
|
-
formData.append('', {
|
|
33
|
-
uri,
|
|
34
|
-
name: `${uniqueId}.jpg`,
|
|
35
|
-
type: 'image/jpeg',
|
|
36
|
-
});
|
|
51
|
+
const arrayBuffer = base64ToArrayBuffer(base64);
|
|
37
52
|
const { error } = await supabase.storage
|
|
38
53
|
.from('screenshots')
|
|
39
|
-
.upload(fileName,
|
|
40
|
-
if (error)
|
|
41
|
-
return
|
|
54
|
+
.upload(fileName, arrayBuffer, { contentType: 'image/jpeg' });
|
|
55
|
+
if (error) {
|
|
56
|
+
// Upload failed, still return URI for preview but no remote URL
|
|
57
|
+
return { uri, url: '' };
|
|
58
|
+
}
|
|
42
59
|
const { data } = supabase.storage.from('screenshots').getPublicUrl(fileName);
|
|
43
|
-
return data.publicUrl;
|
|
60
|
+
return { uri, url: data.publicUrl };
|
|
44
61
|
}
|
|
45
62
|
catch {
|
|
46
63
|
return null;
|
|
47
64
|
}
|
|
48
|
-
}, [config]);
|
|
49
|
-
return {
|
|
65
|
+
}, [viewRef, config]);
|
|
66
|
+
return { captureAndUpload };
|
|
50
67
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mindedsolutions/bug-reporter-sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "In-app bug reporting SDK for React Native/Expo with shake detection, screenshot capture, and Supabase integration",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "In-app bug reporting and feature request SDK for React Native/Expo with shake detection, screenshot capture, feature board, and Supabase integration",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"license": "MIT",
|