react-native-bug-reporter 1.0.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.
Files changed (152) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +217 -0
  3. package/android/build.gradle +49 -0
  4. package/android/src/main/AndroidManifest.xml +11 -0
  5. package/android/src/main/java/com/bugreporter/ScreenshotDetectorModule.kt +227 -0
  6. package/android/src/main/java/com/bugreporter/ScreenshotDetectorPackage.kt +20 -0
  7. package/ios/RNBugReporterScreenshot.m +11 -0
  8. package/ios/RNBugReporterScreenshot.swift +113 -0
  9. package/lib/commonjs/BugReporterProvider.js +139 -0
  10. package/lib/commonjs/BugReporterProvider.js.map +1 -0
  11. package/lib/commonjs/collectors/appInfo.js +21 -0
  12. package/lib/commonjs/collectors/appInfo.js.map +1 -0
  13. package/lib/commonjs/collectors/collectContext.js +45 -0
  14. package/lib/commonjs/collectors/collectContext.js.map +1 -0
  15. package/lib/commonjs/collectors/deviceInfo.js +41 -0
  16. package/lib/commonjs/collectors/deviceInfo.js.map +1 -0
  17. package/lib/commonjs/collectors/networkInfo.js +33 -0
  18. package/lib/commonjs/collectors/networkInfo.js.map +1 -0
  19. package/lib/commonjs/components/BugReportAdminScreen.js +225 -0
  20. package/lib/commonjs/components/BugReportAdminScreen.js.map +1 -0
  21. package/lib/commonjs/components/BugReportModal.js +341 -0
  22. package/lib/commonjs/components/BugReportModal.js.map +1 -0
  23. package/lib/commonjs/components/ScreenshotEditor.js +466 -0
  24. package/lib/commonjs/components/ScreenshotEditor.js.map +1 -0
  25. package/lib/commonjs/components/ScreenshotPreview.js +134 -0
  26. package/lib/commonjs/components/ScreenshotPreview.js.map +1 -0
  27. package/lib/commonjs/components/SeveritySelector.js +65 -0
  28. package/lib/commonjs/components/SeveritySelector.js.map +1 -0
  29. package/lib/commonjs/context/BugReporterContext.js +24 -0
  30. package/lib/commonjs/context/BugReporterContext.js.map +1 -0
  31. package/lib/commonjs/hooks/useScreenshotDetector.js +22 -0
  32. package/lib/commonjs/hooks/useScreenshotDetector.js.map +1 -0
  33. package/lib/commonjs/index.js +87 -0
  34. package/lib/commonjs/index.js.map +1 -0
  35. package/lib/commonjs/native/ScreenshotDetector.js +72 -0
  36. package/lib/commonjs/native/ScreenshotDetector.js.map +1 -0
  37. package/lib/commonjs/navigation/screenTracker.js +47 -0
  38. package/lib/commonjs/navigation/screenTracker.js.map +1 -0
  39. package/lib/commonjs/package.json +1 -0
  40. package/lib/commonjs/services/bugReportService.js +61 -0
  41. package/lib/commonjs/services/bugReportService.js.map +1 -0
  42. package/lib/commonjs/services/supabaseService.js +166 -0
  43. package/lib/commonjs/services/supabaseService.js.map +1 -0
  44. package/lib/commonjs/theme.js +28 -0
  45. package/lib/commonjs/theme.js.map +1 -0
  46. package/lib/commonjs/types.js +35 -0
  47. package/lib/commonjs/types.js.map +1 -0
  48. package/lib/commonjs/utils/logger.js +29 -0
  49. package/lib/commonjs/utils/logger.js.map +1 -0
  50. package/lib/module/BugReporterProvider.js +134 -0
  51. package/lib/module/BugReporterProvider.js.map +1 -0
  52. package/lib/module/collectors/appInfo.js +16 -0
  53. package/lib/module/collectors/appInfo.js.map +1 -0
  54. package/lib/module/collectors/collectContext.js +41 -0
  55. package/lib/module/collectors/collectContext.js.map +1 -0
  56. package/lib/module/collectors/deviceInfo.js +37 -0
  57. package/lib/module/collectors/deviceInfo.js.map +1 -0
  58. package/lib/module/collectors/networkInfo.js +29 -0
  59. package/lib/module/collectors/networkInfo.js.map +1 -0
  60. package/lib/module/components/BugReportAdminScreen.js +221 -0
  61. package/lib/module/components/BugReportAdminScreen.js.map +1 -0
  62. package/lib/module/components/BugReportModal.js +337 -0
  63. package/lib/module/components/BugReportModal.js.map +1 -0
  64. package/lib/module/components/ScreenshotEditor.js +461 -0
  65. package/lib/module/components/ScreenshotEditor.js.map +1 -0
  66. package/lib/module/components/ScreenshotPreview.js +130 -0
  67. package/lib/module/components/ScreenshotPreview.js.map +1 -0
  68. package/lib/module/components/SeveritySelector.js +61 -0
  69. package/lib/module/components/SeveritySelector.js.map +1 -0
  70. package/lib/module/context/BugReporterContext.js +19 -0
  71. package/lib/module/context/BugReporterContext.js.map +1 -0
  72. package/lib/module/hooks/useScreenshotDetector.js +18 -0
  73. package/lib/module/hooks/useScreenshotDetector.js.map +1 -0
  74. package/lib/module/index.js +32 -0
  75. package/lib/module/index.js.map +1 -0
  76. package/lib/module/native/ScreenshotDetector.js +68 -0
  77. package/lib/module/native/ScreenshotDetector.js.map +1 -0
  78. package/lib/module/navigation/screenTracker.js +41 -0
  79. package/lib/module/navigation/screenTracker.js.map +1 -0
  80. package/lib/module/services/bugReportService.js +57 -0
  81. package/lib/module/services/bugReportService.js.map +1 -0
  82. package/lib/module/services/supabaseService.js +159 -0
  83. package/lib/module/services/supabaseService.js.map +1 -0
  84. package/lib/module/theme.js +23 -0
  85. package/lib/module/theme.js.map +1 -0
  86. package/lib/module/types.js +31 -0
  87. package/lib/module/types.js.map +1 -0
  88. package/lib/module/utils/logger.js +25 -0
  89. package/lib/module/utils/logger.js.map +1 -0
  90. package/lib/typescript/src/BugReporterProvider.d.ts +18 -0
  91. package/lib/typescript/src/BugReporterProvider.d.ts.map +1 -0
  92. package/lib/typescript/src/collectors/appInfo.d.ts +6 -0
  93. package/lib/typescript/src/collectors/appInfo.d.ts.map +1 -0
  94. package/lib/typescript/src/collectors/collectContext.d.ts +7 -0
  95. package/lib/typescript/src/collectors/collectContext.d.ts.map +1 -0
  96. package/lib/typescript/src/collectors/deviceInfo.d.ts +7 -0
  97. package/lib/typescript/src/collectors/deviceInfo.d.ts.map +1 -0
  98. package/lib/typescript/src/collectors/networkInfo.d.ts +6 -0
  99. package/lib/typescript/src/collectors/networkInfo.d.ts.map +1 -0
  100. package/lib/typescript/src/components/BugReportAdminScreen.d.ts +11 -0
  101. package/lib/typescript/src/components/BugReportAdminScreen.d.ts.map +1 -0
  102. package/lib/typescript/src/components/BugReportModal.d.ts +20 -0
  103. package/lib/typescript/src/components/BugReportModal.d.ts.map +1 -0
  104. package/lib/typescript/src/components/ScreenshotEditor.d.ts +16 -0
  105. package/lib/typescript/src/components/ScreenshotEditor.d.ts.map +1 -0
  106. package/lib/typescript/src/components/ScreenshotPreview.d.ts +11 -0
  107. package/lib/typescript/src/components/ScreenshotPreview.d.ts.map +1 -0
  108. package/lib/typescript/src/components/SeveritySelector.d.ts +10 -0
  109. package/lib/typescript/src/components/SeveritySelector.d.ts.map +1 -0
  110. package/lib/typescript/src/context/BugReporterContext.d.ts +20 -0
  111. package/lib/typescript/src/context/BugReporterContext.d.ts.map +1 -0
  112. package/lib/typescript/src/hooks/useScreenshotDetector.d.ts +7 -0
  113. package/lib/typescript/src/hooks/useScreenshotDetector.d.ts.map +1 -0
  114. package/lib/typescript/src/index.d.ts +26 -0
  115. package/lib/typescript/src/index.d.ts.map +1 -0
  116. package/lib/typescript/src/native/ScreenshotDetector.d.ts +15 -0
  117. package/lib/typescript/src/native/ScreenshotDetector.d.ts.map +1 -0
  118. package/lib/typescript/src/navigation/screenTracker.d.ts +7 -0
  119. package/lib/typescript/src/navigation/screenTracker.d.ts.map +1 -0
  120. package/lib/typescript/src/services/bugReportService.d.ts +17 -0
  121. package/lib/typescript/src/services/bugReportService.d.ts.map +1 -0
  122. package/lib/typescript/src/services/supabaseService.d.ts +38 -0
  123. package/lib/typescript/src/services/supabaseService.d.ts.map +1 -0
  124. package/lib/typescript/src/theme.d.ts +7 -0
  125. package/lib/typescript/src/theme.d.ts.map +1 -0
  126. package/lib/typescript/src/types.d.ts +144 -0
  127. package/lib/typescript/src/types.d.ts.map +1 -0
  128. package/lib/typescript/src/utils/logger.d.ts +7 -0
  129. package/lib/typescript/src/utils/logger.d.ts.map +1 -0
  130. package/package.json +100 -0
  131. package/react-native-bug-reporter.podspec +22 -0
  132. package/react-native.config.js +18 -0
  133. package/src/BugReporterProvider.tsx +178 -0
  134. package/src/collectors/appInfo.ts +15 -0
  135. package/src/collectors/collectContext.ts +47 -0
  136. package/src/collectors/deviceInfo.ts +51 -0
  137. package/src/collectors/networkInfo.ts +31 -0
  138. package/src/components/BugReportAdminScreen.tsx +160 -0
  139. package/src/components/BugReportModal.tsx +315 -0
  140. package/src/components/ScreenshotEditor.tsx +410 -0
  141. package/src/components/ScreenshotPreview.tsx +98 -0
  142. package/src/components/SeveritySelector.tsx +59 -0
  143. package/src/context/BugReporterContext.ts +29 -0
  144. package/src/hooks/useScreenshotDetector.ts +20 -0
  145. package/src/index.ts +51 -0
  146. package/src/native/ScreenshotDetector.ts +87 -0
  147. package/src/navigation/screenTracker.ts +40 -0
  148. package/src/services/bugReportService.ts +81 -0
  149. package/src/services/supabaseService.ts +195 -0
  150. package/src/theme.ts +23 -0
  151. package/src/types.ts +156 -0
  152. package/src/utils/logger.ts +24 -0
@@ -0,0 +1,87 @@
1
+ import { NativeEventEmitter, NativeModules, Platform } from 'react-native';
2
+ import type { ScreenshotData } from '../types';
3
+ import { logger } from '../utils/logger';
4
+
5
+ const LINKING_ERROR =
6
+ `The native module 'RNBugReporterScreenshot' from 'react-native-bug-reporter' is not linked.\n` +
7
+ Platform.select({
8
+ ios: '- Run `cd ios && pod install`, then rebuild the app.\n',
9
+ android: '- Rebuild the Android app (autolinking registers the module automatically).\n',
10
+ default: '',
11
+ }) +
12
+ '- A native rebuild is required after install — Fast Refresh is not enough.\n';
13
+
14
+ const NativeScreenshot = NativeModules.RNBugReporterScreenshot as
15
+ | {
16
+ start: () => void;
17
+ stop: () => void;
18
+ addListener?: (event: string) => void;
19
+ removeListeners?: (count: number) => void;
20
+ }
21
+ | undefined;
22
+
23
+ export type ScreenshotListener = (screenshot: ScreenshotData) => void;
24
+
25
+ /**
26
+ * Thin wrapper around the native screenshot detector. Exposes a single
27
+ * `subscribe` method that starts detection and returns an unsubscribe fn.
28
+ */
29
+ class ScreenshotDetector {
30
+ private emitter: NativeEventEmitter | null = null;
31
+ private listenerCount = 0;
32
+
33
+ isAvailable(): boolean {
34
+ return !!NativeScreenshot;
35
+ }
36
+
37
+ subscribe(listener: ScreenshotListener): () => void {
38
+ if (!NativeScreenshot) {
39
+ logger.warn(LINKING_ERROR);
40
+ return () => {};
41
+ }
42
+
43
+ if (!this.emitter) {
44
+ this.emitter = new NativeEventEmitter(NativeScreenshot as any);
45
+ }
46
+
47
+ const subscription = this.emitter.addListener(
48
+ 'BugReporterScreenshotTaken',
49
+ (payload: { uri?: string | null; width?: number; height?: number }) => {
50
+ if (!payload || !payload.uri) {
51
+ // Detection fired but capture failed — still notify with no image so
52
+ // the user can file a report manually.
53
+ listener({ uri: '', width: 0, height: 0 });
54
+ return;
55
+ }
56
+ listener({
57
+ uri: payload.uri,
58
+ width: payload.width,
59
+ height: payload.height,
60
+ });
61
+ },
62
+ );
63
+
64
+ if (this.listenerCount === 0) {
65
+ try {
66
+ NativeScreenshot.start();
67
+ } catch (e) {
68
+ logger.warn('failed to start native detector', e);
69
+ }
70
+ }
71
+ this.listenerCount += 1;
72
+
73
+ return () => {
74
+ subscription.remove();
75
+ this.listenerCount = Math.max(0, this.listenerCount - 1);
76
+ if (this.listenerCount === 0) {
77
+ try {
78
+ NativeScreenshot.stop();
79
+ } catch (e) {
80
+ logger.warn('failed to stop native detector', e);
81
+ }
82
+ }
83
+ };
84
+ }
85
+ }
86
+
87
+ export const screenshotDetector = new ScreenshotDetector();
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Lightweight current-screen tracker.
3
+ *
4
+ * The SDK is navigation-library agnostic. The host app reports its active
5
+ * route name here, and the SDK reads it when collecting context.
6
+ *
7
+ * Usage with React Navigation:
8
+ *
9
+ * <NavigationContainer
10
+ * onStateChange={(state) => {
11
+ * const route = getActiveRouteName(state);
12
+ * if (route) setCurrentScreen(route);
13
+ * }}
14
+ * />
15
+ *
16
+ * Or call `setCurrentScreen('Home')` manually from any screen's effect.
17
+ */
18
+ let currentScreen: string | null = null;
19
+
20
+ export function setCurrentScreen(name: string | null): void {
21
+ currentScreen = name;
22
+ }
23
+
24
+ export function getCurrentScreen(): string | null {
25
+ return currentScreen;
26
+ }
27
+
28
+ /**
29
+ * Walks a React Navigation state object to find the deepest active route name.
30
+ */
31
+ export function getActiveRouteName(state: any): string | null {
32
+ if (!state || typeof state.index !== 'number' || !Array.isArray(state.routes)) {
33
+ return null;
34
+ }
35
+ const route = state.routes[state.index];
36
+ if (route?.state) {
37
+ return getActiveRouteName(route.state);
38
+ }
39
+ return route?.name ?? null;
40
+ }
@@ -0,0 +1,81 @@
1
+ import type {
2
+ BugReport,
3
+ BugReportInput,
4
+ BugReporterConfig,
5
+ CollectedContext,
6
+ ScreenshotData,
7
+ SubmitState,
8
+ } from '../types';
9
+ import { insertReport, targetFromConfig, uploadScreenshot } from './supabaseService';
10
+ import { logger } from '../utils/logger';
11
+
12
+ export interface SubmitArgs {
13
+ input: BugReportInput;
14
+ context: CollectedContext;
15
+ screenshot: ScreenshotData | null;
16
+ config: BugReporterConfig;
17
+ onState?: (state: SubmitState) => void;
18
+ }
19
+
20
+ /**
21
+ * End-to-end submission pipeline (Supabase):
22
+ * 1. upload the screenshot to Supabase Storage (if present)
23
+ * 2. insert the report row into Postgres
24
+ *
25
+ * A Database Webhook → Edge Function then sends the notification email.
26
+ */
27
+ export async function submitBugReport({
28
+ input,
29
+ context,
30
+ screenshot,
31
+ config,
32
+ onState,
33
+ }: SubmitArgs): Promise<BugReport> {
34
+ const target = targetFromConfig(config);
35
+
36
+ // Normalize configured recipient(s).
37
+ const notifyEmails = (
38
+ Array.isArray(config.notifyEmails)
39
+ ? config.notifyEmails
40
+ : config.notifyEmails
41
+ ? [config.notifyEmails]
42
+ : []
43
+ )
44
+ .map(e => e.trim())
45
+ .filter(Boolean);
46
+
47
+ let screenshotUrl: string | null = null;
48
+ let screenshotPath: string | null = null;
49
+
50
+ try {
51
+ if (screenshot?.uri) {
52
+ onState?.('uploading');
53
+ const result = await uploadScreenshot(target, screenshot.uri);
54
+ screenshotUrl = result.publicUrl;
55
+ screenshotPath = result.storagePath;
56
+ }
57
+
58
+ onState?.('saving');
59
+ const report: Omit<BugReport, 'id'> = {
60
+ title: input.title.trim(),
61
+ description: input.description.trim(),
62
+ severity: input.severity,
63
+ screenshotUrl,
64
+ screenshotPath,
65
+ context,
66
+ status: 'open',
67
+ createdAt: context.timestamp,
68
+ ...(notifyEmails.length ? { notifyEmails } : {}),
69
+ };
70
+
71
+ const saved = await insertReport(target, report);
72
+ onState?.('done');
73
+ config.onSubmitted?.(saved);
74
+ return saved;
75
+ } catch (error) {
76
+ logger.error('submit failed', error);
77
+ onState?.('error');
78
+ config.onError?.(error);
79
+ throw error;
80
+ }
81
+ }
@@ -0,0 +1,195 @@
1
+ import 'react-native-url-polyfill/auto';
2
+ import { createClient, type SupabaseClient } from '@supabase/supabase-js';
3
+ import type { BugReport } from '../types';
4
+ import { logger } from '../utils/logger';
5
+
6
+ /**
7
+ * Supabase backend for the SDK.
8
+ *
9
+ * - Screenshots upload to a Supabase Storage bucket.
10
+ * - Reports are inserted into a Postgres table.
11
+ * - A Database Webhook → Edge Function (see supabase/) emails each new report.
12
+ *
13
+ * The client is pure JS — no native config files. You only need the project URL
14
+ * and anon key (passed via BugReporterConfig).
15
+ */
16
+
17
+ let client: SupabaseClient | null = null;
18
+ let clientKey = '';
19
+
20
+ function getClient(url: string, anonKey: string): SupabaseClient {
21
+ const key = `${url}::${anonKey}`;
22
+ if (!client || clientKey !== key) {
23
+ client = createClient(url, anonKey, {
24
+ auth: { persistSession: false, autoRefreshToken: false },
25
+ });
26
+ clientKey = key;
27
+ }
28
+ return client;
29
+ }
30
+
31
+ export interface SupabaseTarget {
32
+ supabaseUrl: string;
33
+ supabaseAnonKey: string;
34
+ tableName: string;
35
+ storageBucket: string;
36
+ }
37
+
38
+ /** DB row (snake_case) → SDK BugReport (camelCase). */
39
+ function mapRow(row: Record<string, unknown>): BugReport {
40
+ return {
41
+ id: row.id as string,
42
+ title: (row.title as string) ?? '',
43
+ description: (row.description as string) ?? '',
44
+ severity: (row.severity as BugReport['severity']) ?? 'medium',
45
+ screenshotUrl: (row.screenshot_url as string) ?? null,
46
+ screenshotPath: (row.screenshot_path as string) ?? null,
47
+ context: (row.context as BugReport['context']) ?? ({} as BugReport['context']),
48
+ status: (row.status as BugReport['status']) ?? 'open',
49
+ createdAt: (row.created_at as string) ?? '',
50
+ notifyEmails: (row.notify_emails as string[]) ?? undefined,
51
+ };
52
+ }
53
+
54
+ export interface UploadResult {
55
+ publicUrl: string;
56
+ storagePath: string;
57
+ }
58
+
59
+ /**
60
+ * Uploads a local screenshot file to Supabase Storage; returns its public URL.
61
+ *
62
+ * Uses a multipart FormData POST to the Storage REST endpoint. React Native's
63
+ * networking streams `file://` / `content://` uris natively this way, which is
64
+ * reliable on real devices — unlike `fetch(fileUri).arrayBuffer()`, which throws
65
+ * "Network request failed" on Android.
66
+ */
67
+ export async function uploadScreenshot(
68
+ target: SupabaseTarget,
69
+ localUri: string,
70
+ ): Promise<UploadResult> {
71
+ const storagePath = `${Date.now()}_${Math.floor(Math.random() * 1e6)}.png`;
72
+ const endpoint = `${target.supabaseUrl.replace(/\/+$/, '')}/storage/v1/object/${target.storageBucket}/${storagePath}`;
73
+
74
+ const form = new FormData();
75
+ form.append('file', {
76
+ uri: localUri,
77
+ name: storagePath,
78
+ type: 'image/png',
79
+ } as unknown as Blob);
80
+
81
+ const res = await fetch(endpoint, {
82
+ method: 'POST',
83
+ headers: {
84
+ apikey: target.supabaseAnonKey,
85
+ Authorization: `Bearer ${target.supabaseAnonKey}`,
86
+ 'x-upsert': 'true',
87
+ },
88
+ body: form,
89
+ });
90
+
91
+ if (!res.ok) {
92
+ const text = await res.text().catch(() => '');
93
+ throw new Error(`Storage upload failed (${res.status}): ${text}`);
94
+ }
95
+
96
+ const publicUrl = `${target.supabaseUrl.replace(/\/+$/, '')}/storage/v1/object/public/${target.storageBucket}/${storagePath}`;
97
+ logger.log('screenshot uploaded', storagePath);
98
+ return { publicUrl, storagePath };
99
+ }
100
+
101
+ /** Inserts a bug report row and returns the saved report. */
102
+ export async function insertReport(
103
+ target: SupabaseTarget,
104
+ report: Omit<BugReport, 'id'>,
105
+ ): Promise<BugReport> {
106
+ const supabase = getClient(target.supabaseUrl, target.supabaseAnonKey);
107
+ const { data, error } = await supabase
108
+ .from(target.tableName)
109
+ .insert({
110
+ title: report.title,
111
+ description: report.description,
112
+ severity: report.severity,
113
+ screenshot_url: report.screenshotUrl,
114
+ screenshot_path: report.screenshotPath,
115
+ context: report.context,
116
+ status: report.status,
117
+ created_at: report.createdAt,
118
+ notify_emails: report.notifyEmails ?? null,
119
+ })
120
+ .select()
121
+ .single();
122
+ if (error) throw error;
123
+ logger.log('report inserted', data.id);
124
+ return mapRow(data);
125
+ }
126
+
127
+ /**
128
+ * Subscribes to the reports table (newest first). Does an initial fetch, then
129
+ * refetches on any realtime change. Returns an unsubscribe function.
130
+ */
131
+ export function subscribeToReports(
132
+ target: SupabaseTarget,
133
+ onData: (reports: BugReport[]) => void,
134
+ onError?: (error: Error) => void,
135
+ ): () => void {
136
+ const supabase = getClient(target.supabaseUrl, target.supabaseAnonKey);
137
+
138
+ const fetchAll = async () => {
139
+ const { data, error } = await supabase
140
+ .from(target.tableName)
141
+ .select('*')
142
+ .order('created_at', { ascending: false })
143
+ .limit(200);
144
+ if (error) {
145
+ logger.error('reports fetch error', error);
146
+ onError?.(error as unknown as Error);
147
+ return;
148
+ }
149
+ onData((data ?? []).map(mapRow));
150
+ };
151
+
152
+ fetchAll();
153
+
154
+ const channel = supabase
155
+ .channel(`bugreporter:${target.tableName}`)
156
+ .on(
157
+ 'postgres_changes',
158
+ { event: '*', schema: 'public', table: target.tableName },
159
+ () => fetchAll(),
160
+ )
161
+ .subscribe();
162
+
163
+ return () => {
164
+ supabase.removeChannel(channel);
165
+ };
166
+ }
167
+
168
+ /** Updates a report's status (admin). */
169
+ export async function updateReportStatus(
170
+ target: SupabaseTarget,
171
+ reportId: string,
172
+ status: BugReport['status'],
173
+ ): Promise<void> {
174
+ const supabase = getClient(target.supabaseUrl, target.supabaseAnonKey);
175
+ const { error } = await supabase
176
+ .from(target.tableName)
177
+ .update({ status })
178
+ .eq('id', reportId);
179
+ if (error) throw error;
180
+ }
181
+
182
+ /** Builds a SupabaseTarget from the SDK config (with defaults). */
183
+ export function targetFromConfig(config: {
184
+ supabaseUrl: string;
185
+ supabaseAnonKey: string;
186
+ tableName?: string;
187
+ storageBucket?: string;
188
+ }): SupabaseTarget {
189
+ return {
190
+ supabaseUrl: config.supabaseUrl,
191
+ supabaseAnonKey: config.supabaseAnonKey,
192
+ tableName: config.tableName ?? 'bug_reports',
193
+ storageBucket: config.storageBucket ?? 'bug-reports',
194
+ };
195
+ }
package/src/theme.ts ADDED
@@ -0,0 +1,23 @@
1
+ import type { BugReporterTheme } from './types';
2
+
3
+ export interface ResolvedTheme extends Required<BugReporterTheme> {}
4
+
5
+ export const defaultTheme: ResolvedTheme = {
6
+ primaryColor: '#2563eb',
7
+ backgroundColor: '#ffffff',
8
+ textColor: '#111827',
9
+ mutedColor: '#6b7280',
10
+ borderColor: '#e5e7eb',
11
+ errorColor: '#ef4444',
12
+ };
13
+
14
+ export function resolveTheme(theme?: BugReporterTheme): ResolvedTheme {
15
+ return { ...defaultTheme, ...(theme || {}) };
16
+ }
17
+
18
+ export const SEVERITY_COLORS: Record<string, string> = {
19
+ low: '#3b82f6',
20
+ medium: '#f59e0b',
21
+ high: '#ef4444',
22
+ critical: '#991b1b',
23
+ };
package/src/types.ts ADDED
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Core type definitions for the Universal Bug Reporter SDK.
3
+ */
4
+
5
+ export type BugSeverity = 'low' | 'medium' | 'high' | 'critical';
6
+
7
+ export const SEVERITIES: BugSeverity[] = ['low', 'medium', 'high', 'critical'];
8
+
9
+ /**
10
+ * Information about the signed-in user, supplied by the host app.
11
+ * Everything is optional so the SDK works in apps without auth.
12
+ */
13
+ export interface UserInfo {
14
+ id?: string | null;
15
+ name?: string | null;
16
+ email?: string | null;
17
+ /** Any extra attributes the host app wants to attach. */
18
+ [key: string]: unknown;
19
+ }
20
+
21
+ /** Device metadata collected automatically at report time. */
22
+ export interface DeviceInfo {
23
+ brand: string;
24
+ manufacturer: string;
25
+ model: string;
26
+ deviceId: string;
27
+ systemName: string;
28
+ systemVersion: string;
29
+ isTablet: boolean;
30
+ isEmulator: boolean;
31
+ /** Unique, anonymous, install-scoped id. */
32
+ uniqueId: string;
33
+ totalMemory?: number;
34
+ usedMemory?: number;
35
+ batteryLevel?: number;
36
+ /** Free disk space in bytes. */
37
+ freeDisk?: number;
38
+ }
39
+
40
+ /** Host application metadata collected automatically. */
41
+ export interface AppInfo {
42
+ appName: string;
43
+ bundleId: string;
44
+ version: string;
45
+ buildNumber: string;
46
+ /** Build channel/environment, e.g. "production". */
47
+ environment?: string;
48
+ }
49
+
50
+ /** Network connectivity snapshot. */
51
+ export interface NetworkInfo {
52
+ isConnected: boolean | null;
53
+ isInternetReachable: boolean | null;
54
+ /** wifi | cellular | none | unknown | ... */
55
+ type: string;
56
+ /** Cellular generation when applicable, e.g. "4g". */
57
+ cellularGeneration?: string | null;
58
+ }
59
+
60
+ /** Everything the SDK auto-collects when a report is opened. */
61
+ export interface CollectedContext {
62
+ user: UserInfo;
63
+ device: DeviceInfo;
64
+ app: AppInfo;
65
+ network: NetworkInfo;
66
+ currentScreen: string | null;
67
+ /** ISO-8601 timestamp. */
68
+ timestamp: string;
69
+ }
70
+
71
+ /** User-entered fields from the report modal. */
72
+ export interface BugReportInput {
73
+ title: string;
74
+ description: string;
75
+ severity: BugSeverity;
76
+ }
77
+
78
+ /** The full bug report persisted on the backend. */
79
+ export interface BugReport extends BugReportInput {
80
+ id?: string;
81
+ screenshotUrl: string | null;
82
+ screenshotPath: string | null;
83
+ context: CollectedContext;
84
+ status: 'open' | 'in_progress' | 'resolved' | 'closed';
85
+ createdAt: string;
86
+ /** Recipient address(es) for the notification email (from config). */
87
+ notifyEmails?: string[];
88
+ }
89
+
90
+ /** A screenshot captured / detected on device. */
91
+ export interface ScreenshotData {
92
+ /** Local file URI (file:// or content:// or ph://). */
93
+ uri: string;
94
+ /** Optional base64 payload when a uri is unavailable. */
95
+ base64?: string | null;
96
+ width?: number;
97
+ height?: number;
98
+ }
99
+
100
+ export type SubmitState = 'idle' | 'collecting' | 'uploading' | 'saving' | 'done' | 'error';
101
+
102
+ /** Theme overrides for the modal UI. */
103
+ export interface BugReporterTheme {
104
+ primaryColor?: string;
105
+ backgroundColor?: string;
106
+ textColor?: string;
107
+ mutedColor?: string;
108
+ borderColor?: string;
109
+ errorColor?: string;
110
+ }
111
+
112
+ /** Configuration passed to BugReporterProvider. */
113
+ export interface BugReporterConfig {
114
+ /** Supabase project URL, e.g. "https://xxxx.supabase.co". */
115
+ supabaseUrl: string;
116
+ /** Supabase anon (public) API key. */
117
+ supabaseAnonKey: string;
118
+ /** Postgres table reports are inserted into. Defaults to "bug_reports". */
119
+ tableName?: string;
120
+ /** Supabase Storage bucket for screenshots. Defaults to "bug-reports". */
121
+ storageBucket?: string;
122
+ /**
123
+ * Returns the current signed-in user. Called lazily each time a report
124
+ * is opened, so it always reflects the latest auth state.
125
+ */
126
+ getUser?: () => UserInfo | Promise<UserInfo>;
127
+ /**
128
+ * Returns the current screen/route name. Wire this to your navigation
129
+ * library (see `screenTracker`).
130
+ */
131
+ getCurrentScreen?: () => string | null;
132
+ /**
133
+ * Email address(es) the notification should be sent to. Stored on the report
134
+ * row; the Supabase Edge Function reads it and emails these recipients.
135
+ * Accepts a single address or an array.
136
+ */
137
+ notifyEmails?: string | string[];
138
+ /** Build channel/environment label attached to every report. */
139
+ environment?: string;
140
+ /** Disable automatic screenshot detection (manual trigger only). */
141
+ disableAutoDetect?: boolean;
142
+ /**
143
+ * Show the "Automatically attached" context summary (screen, user, app,
144
+ * device, network) inside the report modal. Defaults to true. Set false to
145
+ * hide it from the user — the data is still collected and sent to the backend.
146
+ */
147
+ showContextSummary?: boolean;
148
+ /** Theme overrides. */
149
+ theme?: BugReporterTheme;
150
+ /** Called after a report is successfully submitted. */
151
+ onSubmitted?: (report: BugReport) => void;
152
+ /** Called when submission fails. */
153
+ onError?: (error: unknown) => void;
154
+ /** Enable verbose console logging. */
155
+ debug?: boolean;
156
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Tiny namespaced logger that can be toggled via config.debug.
3
+ */
4
+ let enabled = false;
5
+
6
+ const PREFIX = '[BugReporter]';
7
+
8
+ export const logger = {
9
+ setEnabled(value: boolean) {
10
+ enabled = value;
11
+ },
12
+ log(...args: unknown[]) {
13
+ if (enabled) {
14
+ console.log(PREFIX, ...args);
15
+ }
16
+ },
17
+ warn(...args: unknown[]) {
18
+ // Warnings always surface — they usually indicate a setup problem.
19
+ console.warn(PREFIX, ...args);
20
+ },
21
+ error(...args: unknown[]) {
22
+ console.error(PREFIX, ...args);
23
+ },
24
+ };