react-native-iinstall 0.2.14 → 0.2.16

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.
@@ -1,393 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.FeedbackModal = void 0;
37
- const react_1 = __importStar(require("react"));
38
- const react_native_1 = require("react-native");
39
- const nativeModules_1 = require("./nativeModules");
40
- const FeedbackModal = ({ visible, onClose, screenshotUri, videoUri, onStartRecording, apiKey, apiEndpoint }) => {
41
- const [description, setDescription] = (0, react_1.useState)('');
42
- const [isSending, setIsSending] = (0, react_1.useState)(false);
43
- // Audio State
44
- const [isRecordingAudio, setIsRecordingAudio] = (0, react_1.useState)(false);
45
- const [audioUri, setAudioUri] = (0, react_1.useState)(null);
46
- const [audioDuration, setAudioDuration] = (0, react_1.useState)('00:00');
47
- const audioRecordingAvailable = (0, nativeModules_1.hasAudioRecordingSupport)();
48
- const audioRecorderPlayer = (0, react_1.useRef)((0, nativeModules_1.createAudioRecorderPlayer)()).current;
49
- (0, react_1.useEffect)(() => {
50
- return () => {
51
- // Cleanup
52
- if (isRecordingAudio && audioRecorderPlayer) {
53
- audioRecorderPlayer.stopRecorder().catch(() => undefined);
54
- }
55
- };
56
- }, [audioRecorderPlayer, isRecordingAudio]);
57
- const onStartAudioRecord = async () => {
58
- if (!audioRecorderPlayer || !audioRecordingAvailable) {
59
- react_native_1.Alert.alert('Audio unavailable', 'Install react-native-audio-recorder-player in your app root, then rebuild the app.');
60
- return;
61
- }
62
- if (react_native_1.Platform.OS === 'android') {
63
- try {
64
- const grants = await react_native_1.PermissionsAndroid.requestMultiple([
65
- react_native_1.PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
66
- react_native_1.PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE,
67
- react_native_1.PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
68
- ]);
69
- // Note: Android 13+ might need different permissions for media, but keeping it simple
70
- if (grants['android.permission.RECORD_AUDIO'] !== react_native_1.PermissionsAndroid.RESULTS.GRANTED) {
71
- react_native_1.Alert.alert('Permission needed', 'Audio recording permission is required.');
72
- return;
73
- }
74
- }
75
- catch (err) {
76
- console.warn(err);
77
- return;
78
- }
79
- }
80
- try {
81
- const result = await audioRecorderPlayer.startRecorder(undefined, (0, nativeModules_1.getAudioRecordingPreset)());
82
- audioRecorderPlayer.addRecordBackListener((e) => {
83
- setAudioDuration(audioRecorderPlayer.mmssss(Math.floor(e.currentPosition)));
84
- return;
85
- });
86
- setIsRecordingAudio(true);
87
- setAudioUri(null);
88
- }
89
- catch (error) {
90
- console.error('Failed to start recording', error);
91
- react_native_1.Alert.alert('Error', 'Could not start audio recording');
92
- }
93
- };
94
- const onStopAudioRecord = async () => {
95
- if (!audioRecorderPlayer) {
96
- return;
97
- }
98
- try {
99
- const result = await audioRecorderPlayer.stopRecorder();
100
- audioRecorderPlayer.removeRecordBackListener();
101
- setIsRecordingAudio(false);
102
- setAudioUri(result);
103
- }
104
- catch (error) {
105
- console.error('Failed to stop recording', error);
106
- }
107
- };
108
- const resetDraft = () => {
109
- setDescription('');
110
- setAudioUri(null);
111
- setAudioDuration('00:00');
112
- };
113
- const handleClose = async () => {
114
- if (isRecordingAudio && audioRecorderPlayer) {
115
- try {
116
- await audioRecorderPlayer.stopRecorder();
117
- }
118
- catch (error) {
119
- console.warn('Failed to stop recorder during close', error);
120
- }
121
- finally {
122
- audioRecorderPlayer.removeRecordBackListener();
123
- setIsRecordingAudio(false);
124
- }
125
- }
126
- resetDraft();
127
- onClose();
128
- };
129
- const sendFeedback = async () => {
130
- if (!description.trim() && !audioUri && !videoUri) {
131
- react_native_1.Alert.alert('Please provide a description, audio, or video.');
132
- return;
133
- }
134
- setIsSending(true);
135
- try {
136
- const formData = new FormData();
137
- const normalizeUploadUri = (uri) => {
138
- if (react_native_1.Platform.OS === 'ios' && uri.startsWith('/')) {
139
- return `file://${uri}`;
140
- }
141
- return uri;
142
- };
143
- // 1. Screenshot
144
- if (screenshotUri) {
145
- const filename = screenshotUri.split('/').pop();
146
- const match = /\.(\w+)$/.exec(filename || '');
147
- const type = match ? `image/${match[1]}` : `image/png`;
148
- formData.append('screenshot', {
149
- uri: screenshotUri,
150
- name: filename || 'screenshot.png',
151
- type,
152
- });
153
- }
154
- // 2. Audio
155
- if (audioUri) {
156
- const normalizedAudioUri = normalizeUploadUri(audioUri);
157
- formData.append('audio', {
158
- uri: normalizedAudioUri,
159
- name: 'feedback.m4a',
160
- type: 'audio/mp4',
161
- });
162
- }
163
- // 3. Video
164
- if (videoUri) {
165
- const normalizedVideoUri = normalizeUploadUri(videoUri);
166
- const videoFilename = normalizedVideoUri.split('/').pop() || 'screen_record.mp4';
167
- const videoType = /\.mov$/i.test(videoFilename) ? 'video/quicktime' : 'video/mp4';
168
- formData.append('video', {
169
- uri: normalizedVideoUri,
170
- name: videoFilename,
171
- type: videoType,
172
- });
173
- }
174
- // 4. Metadata
175
- const metadata = await (0, nativeModules_1.getDeviceMetadata)();
176
- formData.append('metadata', JSON.stringify(metadata));
177
- // 5. Other fields
178
- formData.append('description', description);
179
- formData.append('apiKey', apiKey);
180
- const deviceUdid = await (0, nativeModules_1.getDeviceUniqueId)();
181
- formData.append('udid', deviceUdid || 'unknown');
182
- // 6. Send
183
- const response = await fetch(`${apiEndpoint}/api/feedback`, {
184
- method: 'POST',
185
- body: formData,
186
- });
187
- const result = await response.json();
188
- if (response.ok) {
189
- react_native_1.Alert.alert('Success', 'Feedback sent successfully!');
190
- resetDraft();
191
- onClose();
192
- }
193
- else {
194
- throw new Error(result.error || 'Failed to send');
195
- }
196
- }
197
- catch (error) {
198
- console.error('TesterFlow Error:', error);
199
- react_native_1.Alert.alert('Error', 'Failed to send feedback. Please try again.');
200
- }
201
- finally {
202
- setIsSending(false);
203
- }
204
- };
205
- if (!visible)
206
- return null;
207
- const dismissKeyboard = () => {
208
- react_native_1.Keyboard.dismiss();
209
- };
210
- return (<react_native_1.Modal visible={visible} transparent animationType="slide" onRequestClose={handleClose}>
211
- <react_native_1.TouchableWithoutFeedback onPress={dismissKeyboard}>
212
- <react_native_1.KeyboardAvoidingView behavior={react_native_1.Platform.OS === 'ios' ? 'padding' : 'height'} style={styles.container}>
213
- <react_native_1.ScrollView contentContainerStyle={styles.scrollContainer} keyboardShouldPersistTaps="handled" showsVerticalScrollIndicator={false}>
214
- <react_native_1.View style={styles.card}>
215
- <react_native_1.View style={styles.header}>
216
- <react_native_1.Text style={styles.title}>Report an Issue</react_native_1.Text>
217
- <react_native_1.TouchableOpacity onPress={handleClose}>
218
- <react_native_1.Text style={styles.closeBtn}>✕</react_native_1.Text>
219
- </react_native_1.TouchableOpacity>
220
- </react_native_1.View>
221
-
222
- {/* Media Previews */}
223
- <react_native_1.View style={styles.mediaRow}>
224
- {/* Screenshot Preview */}
225
- {screenshotUri && !videoUri && (<react_native_1.View style={styles.mediaItem}>
226
- <react_native_1.Image source={{ uri: screenshotUri }} style={styles.mediaPreview} resizeMode="cover"/>
227
- <react_native_1.View style={styles.mediaBadge}><react_native_1.Text style={styles.mediaBadgeText}>Screenshot</react_native_1.Text></react_native_1.View>
228
- </react_native_1.View>)}
229
-
230
- {/* Video Preview (Placeholder) */}
231
- {videoUri && (<react_native_1.View style={styles.mediaItem}>
232
- <react_native_1.View style={[styles.mediaPreview, { backgroundColor: '#000', justifyContent: 'center', alignItems: 'center' }]}>
233
- <react_native_1.Text style={{ color: '#FFF' }}>▶ Video</react_native_1.Text>
234
- </react_native_1.View>
235
- <react_native_1.View style={styles.mediaBadge}><react_native_1.Text style={styles.mediaBadgeText}>Screen Rec</react_native_1.Text></react_native_1.View>
236
- </react_native_1.View>)}
237
- </react_native_1.View>
238
-
239
- <react_native_1.TextInput style={styles.input} placeholder="What happened? Describe the bug..." placeholderTextColor="#999" multiline value={description} onChangeText={setDescription} returnKeyType="done" blurOnSubmit={true} onSubmitEditing={() => react_native_1.Keyboard.dismiss()}/>
240
-
241
- {/* Media Actions Row */}
242
- <react_native_1.View style={styles.actionsRow}>
243
- {/* Audio Recorder */}
244
- <react_native_1.TouchableOpacity style={[
245
- styles.actionBtn,
246
- isRecordingAudio ? styles.recordingBtn : undefined,
247
- audioUri ? styles.hasAudioBtn : undefined,
248
- !audioRecordingAvailable ? styles.disabledActionBtn : undefined,
249
- ]} onPress={isRecordingAudio ? onStopAudioRecord : onStartAudioRecord} disabled={!audioRecordingAvailable}>
250
- <react_native_1.Text style={[styles.actionBtnText, (isRecordingAudio || audioUri) ? { color: '#FFF' } : undefined]}>
251
- {!audioRecordingAvailable
252
- ? '🎙 Audio Unavailable'
253
- : isRecordingAudio
254
- ? `Stop (${audioDuration})`
255
- : audioUri
256
- ? 'Re-record Audio'
257
- : '🎙 Record Audio'}
258
- </react_native_1.Text>
259
- </react_native_1.TouchableOpacity>
260
-
261
- {/* Screen Recorder */}
262
- {onStartRecording && !videoUri && (<react_native_1.TouchableOpacity style={styles.actionBtn} onPress={onStartRecording}>
263
- <react_native_1.Text style={styles.actionBtnText}>🎥 Record Screen</react_native_1.Text>
264
- </react_native_1.TouchableOpacity>)}
265
- </react_native_1.View>
266
-
267
- <react_native_1.TouchableOpacity style={[styles.submitBtn, isSending && styles.disabledBtn]} onPress={sendFeedback} disabled={isSending}>
268
- {isSending ? (<react_native_1.ActivityIndicator color="#FFF"/>) : (<react_native_1.Text style={styles.submitText}>Send Feedback</react_native_1.Text>)}
269
- </react_native_1.TouchableOpacity>
270
- </react_native_1.View>
271
- </react_native_1.ScrollView>
272
- </react_native_1.KeyboardAvoidingView>
273
- </react_native_1.TouchableWithoutFeedback>
274
- </react_native_1.Modal>);
275
- };
276
- exports.FeedbackModal = FeedbackModal;
277
- const styles = react_native_1.StyleSheet.create({
278
- container: {
279
- flex: 1,
280
- backgroundColor: 'rgba(0,0,0,0.5)',
281
- justifyContent: 'flex-end',
282
- },
283
- scrollContainer: {
284
- flexGrow: 1,
285
- justifyContent: 'flex-end',
286
- },
287
- card: {
288
- backgroundColor: '#FFF',
289
- borderTopLeftRadius: 20,
290
- borderTopRightRadius: 20,
291
- padding: 20,
292
- paddingBottom: 40,
293
- maxHeight: '90%',
294
- },
295
- header: {
296
- flexDirection: 'row',
297
- justifyContent: 'space-between',
298
- alignItems: 'center',
299
- marginBottom: 20,
300
- },
301
- title: {
302
- fontSize: 20,
303
- fontWeight: 'bold',
304
- color: '#000',
305
- },
306
- closeBtn: {
307
- fontSize: 24,
308
- color: '#999',
309
- padding: 5,
310
- },
311
- mediaRow: {
312
- flexDirection: 'row',
313
- marginBottom: 15,
314
- },
315
- mediaItem: {
316
- width: 100,
317
- height: 100,
318
- borderRadius: 10,
319
- overflow: 'hidden',
320
- marginRight: 10,
321
- position: 'relative',
322
- },
323
- mediaPreview: {
324
- width: '100%',
325
- height: '100%',
326
- },
327
- mediaBadge: {
328
- position: 'absolute',
329
- bottom: 0,
330
- left: 0,
331
- right: 0,
332
- backgroundColor: 'rgba(0,0,0,0.6)',
333
- padding: 4,
334
- alignItems: 'center',
335
- },
336
- mediaBadgeText: {
337
- color: '#FFF',
338
- fontSize: 10,
339
- fontWeight: 'bold',
340
- },
341
- input: {
342
- backgroundColor: '#f9f9f9',
343
- borderRadius: 10,
344
- padding: 15,
345
- height: 100,
346
- textAlignVertical: 'top',
347
- fontSize: 16,
348
- color: '#000',
349
- marginBottom: 15,
350
- },
351
- actionsRow: {
352
- flexDirection: 'row',
353
- justifyContent: 'space-between',
354
- marginBottom: 20,
355
- },
356
- actionBtn: {
357
- flex: 1,
358
- backgroundColor: '#f0f0f0',
359
- padding: 12,
360
- borderRadius: 8,
361
- alignItems: 'center',
362
- marginHorizontal: 5,
363
- },
364
- recordingBtn: {
365
- backgroundColor: '#ff4444',
366
- },
367
- hasAudioBtn: {
368
- backgroundColor: '#4CAF50',
369
- },
370
- disabledActionBtn: {
371
- opacity: 0.45,
372
- },
373
- actionBtnText: {
374
- fontSize: 14,
375
- fontWeight: '600',
376
- color: '#333',
377
- },
378
- submitBtn: {
379
- backgroundColor: '#000',
380
- borderRadius: 12,
381
- padding: 16,
382
- alignItems: 'center',
383
- marginTop: 10,
384
- },
385
- disabledBtn: {
386
- opacity: 0.7,
387
- },
388
- submitText: {
389
- color: '#FFF',
390
- fontSize: 16,
391
- fontWeight: 'bold',
392
- },
393
- });
@@ -1,16 +0,0 @@
1
- import React from 'react';
2
- interface IInstallWrapperProps {
3
- apiKey: string;
4
- apiEndpoint?: string;
5
- children?: React.ReactNode;
6
- enabled?: boolean;
7
- showDebugButton?: boolean;
8
- showFloatingButtonOnEmulator?: boolean;
9
- floatingButtonLabel?: string;
10
- pushToken?: string;
11
- autoRegisterPushToken?: boolean;
12
- projectId?: string;
13
- onPushTokenRegisterError?: (error: string) => void;
14
- }
15
- export declare const IInstallWrapper: React.FC<IInstallWrapperProps>;
16
- export default IInstallWrapper;
@@ -1,18 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.IInstallWrapper = void 0;
7
- const react_1 = __importDefault(require("react"));
8
- const index_1 = require("./index");
9
- // Backward-compatible wrapper. The core IInstall component now handles:
10
- // - shake gesture on real devices
11
- // - floating manual trigger button on simulator/emulator
12
- const IInstallWrapper = ({ apiKey, apiEndpoint = 'https://iinstall.app', children, enabled = true, showDebugButton: _showDebugButton = false, showFloatingButtonOnEmulator = true, floatingButtonLabel = 'Report Issue', pushToken, autoRegisterPushToken = true, projectId, onPushTokenRegisterError, }) => {
13
- return (<index_1.IInstall apiKey={apiKey} apiEndpoint={apiEndpoint} enabled={enabled} showFloatingButtonOnEmulator={showFloatingButtonOnEmulator} floatingButtonLabel={floatingButtonLabel} pushToken={pushToken} autoRegisterPushToken={autoRegisterPushToken} projectId={projectId} onPushTokenRegisterError={onPushTokenRegisterError}>
14
- {children}
15
- </index_1.IInstall>);
16
- };
17
- exports.IInstallWrapper = IInstallWrapper;
18
- exports.default = exports.IInstallWrapper;
@@ -1,8 +0,0 @@
1
- export declare class ShakeDetector {
2
- private subscription;
3
- private lastShakeTime;
4
- private onShake;
5
- constructor(onShake: () => void);
6
- start(): void;
7
- stop(): void;
8
- }
@@ -1,68 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ShakeDetector = void 0;
4
- /* eslint-disable */
5
- const operators_1 = require("rxjs/operators");
6
- // Safe import for react-native-sensors to prevent crashes if native module is missing
7
- let accelerometer;
8
- let setUpdateIntervalForType;
9
- let SensorTypes;
10
- try {
11
- const sensors = require('react-native-sensors');
12
- accelerometer = sensors.accelerometer;
13
- setUpdateIntervalForType = sensors.setUpdateIntervalForType;
14
- SensorTypes = sensors.SensorTypes;
15
- }
16
- catch (e) {
17
- console.warn('[iInstall SDK] Shake detection disabled: react-native-sensors native module not found. Please ensure the native module is linked and the app is rebuilt.');
18
- }
19
- const SHAKE_THRESHOLD = 2.5; // g-force
20
- const MIN_TIME_BETWEEN_SHAKES = 1000; // ms
21
- class ShakeDetector {
22
- subscription = null;
23
- lastShakeTime = 0;
24
- onShake;
25
- constructor(onShake) {
26
- this.onShake = onShake;
27
- if (setUpdateIntervalForType && SensorTypes) {
28
- try {
29
- setUpdateIntervalForType(SensorTypes.accelerometer, 100); // 100ms interval
30
- }
31
- catch (e) {
32
- console.warn('[iInstall SDK] Failed to set accelerometer update interval:', e);
33
- }
34
- }
35
- }
36
- start() {
37
- if (this.subscription)
38
- return;
39
- if (!accelerometer) {
40
- // Don't spam logs, just return silently or maybe log once if debugging
41
- return;
42
- }
43
- try {
44
- this.subscription = accelerometer
45
- .pipe((0, operators_1.map)(({ x, y, z }) => Math.sqrt(x * x + y * y + z * z)), (0, operators_1.filter)((g) => g > SHAKE_THRESHOLD))
46
- .subscribe(() => {
47
- const now = Date.now();
48
- if (now - this.lastShakeTime > MIN_TIME_BETWEEN_SHAKES) {
49
- this.lastShakeTime = now;
50
- console.log('TesterFlow: Shake detected!');
51
- this.onShake();
52
- }
53
- }, (error) => {
54
- console.warn('[iInstall SDK] Error in shake detection subscription:', error);
55
- });
56
- }
57
- catch (e) {
58
- console.warn('[iInstall SDK] Failed to start shake detection:', e);
59
- }
60
- }
61
- stop() {
62
- if (this.subscription) {
63
- this.subscription.unsubscribe();
64
- this.subscription = null;
65
- }
66
- }
67
- }
68
- exports.ShakeDetector = ShakeDetector;
@@ -1,17 +0,0 @@
1
- import React from 'react';
2
- interface IInstallProps {
3
- apiKey: string;
4
- apiEndpoint?: string;
5
- children?: React.ReactNode;
6
- enabled?: boolean;
7
- showFloatingButtonOnEmulator?: boolean;
8
- floatingButtonLabel?: string;
9
- pushToken?: string;
10
- autoRegisterPushToken?: boolean;
11
- projectId?: string;
12
- onPushTokenRegisterError?: (error: string) => void;
13
- }
14
- export declare const IInstall: React.FC<IInstallProps>;
15
- export default IInstall;
16
- export { IInstallWrapper } from './IInstallWrapper';
17
- export { registerPushToken, unregisterPushToken, type RegisterPushTokenParams, type UnregisterPushTokenParams, type PushRegistrationResult, type PushPlatform, } from './pushRegistration';