yaver-feedback-react-native 0.8.0 → 0.8.1
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/README.md +40 -6
- package/dist/FeedbackModal.js +211 -203
- package/dist/P2PClient.js +7 -0
- package/dist/QuickActionIcon.d.ts +10 -2
- package/dist/QuickActionIcon.js +37 -12
- package/dist/YaverFeedback.js +2 -0
- package/dist/capture.d.ts +11 -0
- package/dist/capture.js +59 -0
- package/dist/index.d.ts +9 -7
- package/dist/index.js +10 -7
- package/dist/types.d.ts +29 -4
- package/dist/upload.d.ts +1 -0
- package/dist/upload.js +8 -0
- package/package.json +6 -2
- package/src/FeedbackModal.tsx +257 -243
- package/src/P2PClient.ts +8 -0
- package/src/QuickActionIcon.tsx +55 -12
- package/src/YaverFeedback.ts +2 -0
- package/src/capture.ts +74 -0
- package/src/index.ts +9 -6
- package/src/types.ts +29 -4
- package/src/upload.ts +9 -0
package/src/QuickActionIcon.tsx
CHANGED
|
@@ -26,12 +26,23 @@ function isRunningInsideYaverHost(): boolean {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
const DEFAULT_SIZE = 44;
|
|
29
|
-
const
|
|
29
|
+
const DEFAULT_BACKGROUND_COLOR = '#ff6b2c';
|
|
30
|
+
const DEFAULT_LABEL_COLOR = '#111111';
|
|
31
|
+
const DEFAULT_BORDER_COLOR = 'rgba(255,255,255,0.92)';
|
|
32
|
+
const DEFAULT_SHADOW_COLOR = '#000000';
|
|
30
33
|
const LONG_PRESS_MS = 550;
|
|
31
34
|
|
|
32
35
|
export interface QuickActionIconProps {
|
|
33
|
-
/**
|
|
36
|
+
/** Deprecated alias for `backgroundColor`. */
|
|
34
37
|
color?: string;
|
|
38
|
+
/** Override the background from FeedbackConfig.quickIconBackgroundColor. */
|
|
39
|
+
backgroundColor?: string;
|
|
40
|
+
/** Override the label color from FeedbackConfig.quickIconForegroundColor. */
|
|
41
|
+
foregroundColor?: string;
|
|
42
|
+
/** Override the border color from FeedbackConfig.quickIconBorderColor. */
|
|
43
|
+
borderColor?: string;
|
|
44
|
+
/** Override the shadow color from FeedbackConfig.quickIconShadowColor. */
|
|
45
|
+
shadowColor?: string;
|
|
35
46
|
/** Override the initial position from FeedbackConfig.quickIconInitialPosition. */
|
|
36
47
|
initialPosition?: { x: number; y: number };
|
|
37
48
|
/** Override the icon diameter. Default 44. */
|
|
@@ -52,7 +63,7 @@ export interface QuickActionIconProps {
|
|
|
52
63
|
* hidden the user can still shake to open feedback.
|
|
53
64
|
*
|
|
54
65
|
* Visibility is controlled by `FeedbackConfig.quickIcon`:
|
|
55
|
-
* - `'auto'` (default) → `'
|
|
66
|
+
* - `'auto'` (default) → `'after-shake'` on iOS/Android, `'off'` on web.
|
|
56
67
|
* - `'always'` → visible from first render.
|
|
57
68
|
* - `'after-shake'` → hidden until `yaverFeedback:firstShake` fires.
|
|
58
69
|
* - `'off'` → never rendered.
|
|
@@ -62,6 +73,10 @@ export interface QuickActionIconProps {
|
|
|
62
73
|
*/
|
|
63
74
|
export const QuickActionIcon: React.FC<QuickActionIconProps> = ({
|
|
64
75
|
color: colorProp,
|
|
76
|
+
backgroundColor: backgroundColorProp,
|
|
77
|
+
foregroundColor: foregroundColorProp,
|
|
78
|
+
borderColor: borderColorProp,
|
|
79
|
+
shadowColor: shadowColorProp,
|
|
65
80
|
initialPosition: initialPositionProp,
|
|
66
81
|
size = DEFAULT_SIZE,
|
|
67
82
|
}) => {
|
|
@@ -70,12 +85,29 @@ export const QuickActionIcon: React.FC<QuickActionIconProps> = ({
|
|
|
70
85
|
const mode: 'always' | 'after-shake' | 'off' = (() => {
|
|
71
86
|
const raw = config?.quickIcon ?? 'auto';
|
|
72
87
|
if (raw === 'auto') {
|
|
73
|
-
return Platform.OS === 'web' ? 'off' : '
|
|
88
|
+
return Platform.OS === 'web' ? 'off' : 'after-shake';
|
|
74
89
|
}
|
|
75
90
|
return raw;
|
|
76
91
|
})();
|
|
77
92
|
|
|
78
|
-
const
|
|
93
|
+
const backgroundColor =
|
|
94
|
+
backgroundColorProp ??
|
|
95
|
+
colorProp ??
|
|
96
|
+
config?.quickIconBackgroundColor ??
|
|
97
|
+
config?.quickIconColor ??
|
|
98
|
+
DEFAULT_BACKGROUND_COLOR;
|
|
99
|
+
const foregroundColor =
|
|
100
|
+
foregroundColorProp ??
|
|
101
|
+
config?.quickIconForegroundColor ??
|
|
102
|
+
DEFAULT_LABEL_COLOR;
|
|
103
|
+
const borderColor =
|
|
104
|
+
borderColorProp ??
|
|
105
|
+
config?.quickIconBorderColor ??
|
|
106
|
+
DEFAULT_BORDER_COLOR;
|
|
107
|
+
const shadowColor =
|
|
108
|
+
shadowColorProp ??
|
|
109
|
+
config?.quickIconShadowColor ??
|
|
110
|
+
DEFAULT_SHADOW_COLOR;
|
|
79
111
|
|
|
80
112
|
const { width, height } = Dimensions.get('window');
|
|
81
113
|
const defaultStart =
|
|
@@ -239,12 +271,24 @@ export const QuickActionIcon: React.FC<QuickActionIconProps> = ({
|
|
|
239
271
|
width: visualSize,
|
|
240
272
|
height: visualSize,
|
|
241
273
|
borderRadius: radius,
|
|
242
|
-
backgroundColor
|
|
274
|
+
backgroundColor,
|
|
275
|
+
borderColor,
|
|
276
|
+
shadowColor,
|
|
243
277
|
opacity: pressed ? 0.85 : 1,
|
|
244
278
|
},
|
|
245
279
|
]}
|
|
246
280
|
>
|
|
247
|
-
<Text
|
|
281
|
+
<Text
|
|
282
|
+
style={[
|
|
283
|
+
styles.iconLabel,
|
|
284
|
+
{
|
|
285
|
+
color: foregroundColor,
|
|
286
|
+
fontSize: Math.round(visualSize * 0.5),
|
|
287
|
+
},
|
|
288
|
+
]}
|
|
289
|
+
>
|
|
290
|
+
y
|
|
291
|
+
</Text>
|
|
248
292
|
</Pressable>
|
|
249
293
|
{menuOpen ? (
|
|
250
294
|
<View style={styles.menu}>
|
|
@@ -286,14 +330,13 @@ const styles = StyleSheet.create({
|
|
|
286
330
|
icon: {
|
|
287
331
|
alignItems: 'center',
|
|
288
332
|
justifyContent: 'center',
|
|
289
|
-
shadowColor: '#000',
|
|
290
333
|
shadowOffset: { width: 0, height: 2 },
|
|
291
|
-
shadowOpacity: 0.
|
|
292
|
-
shadowRadius:
|
|
293
|
-
elevation:
|
|
334
|
+
shadowOpacity: 0.34,
|
|
335
|
+
shadowRadius: 6,
|
|
336
|
+
elevation: 7,
|
|
337
|
+
borderWidth: 2,
|
|
294
338
|
},
|
|
295
339
|
iconLabel: {
|
|
296
|
-
color: '#ffffff',
|
|
297
340
|
fontWeight: '700',
|
|
298
341
|
includeFontPadding: false,
|
|
299
342
|
},
|
package/src/YaverFeedback.ts
CHANGED
|
@@ -92,6 +92,7 @@ export class YaverFeedback {
|
|
|
92
92
|
autoLogin: true,
|
|
93
93
|
...cfg,
|
|
94
94
|
};
|
|
95
|
+
firstShakeFired = false;
|
|
95
96
|
|
|
96
97
|
// Route the in-SDK login screen to prod yaver.io by default; callers may
|
|
97
98
|
// override for staging via authConvexSiteUrl / authWebBaseUrl.
|
|
@@ -869,6 +870,7 @@ export class YaverFeedback {
|
|
|
869
870
|
shakeDetector.stop();
|
|
870
871
|
shakeDetector = null;
|
|
871
872
|
}
|
|
873
|
+
firstShakeFired = false;
|
|
872
874
|
enabled = false;
|
|
873
875
|
config = null;
|
|
874
876
|
p2pClient = null;
|
package/src/capture.ts
CHANGED
|
@@ -38,6 +38,80 @@ export async function captureScreenshot(): Promise<string> {
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
export interface PickedFeedbackFile {
|
|
42
|
+
path: string;
|
|
43
|
+
name: string;
|
|
44
|
+
mimeType?: string;
|
|
45
|
+
kind: 'image' | 'video' | 'audio' | 'unknown';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function classifyPickedFile(name: string, mimeType?: string): PickedFeedbackFile['kind'] {
|
|
49
|
+
const lowerName = name.toLowerCase();
|
|
50
|
+
const lowerMime = (mimeType ?? '').toLowerCase();
|
|
51
|
+
if (
|
|
52
|
+
lowerMime.startsWith('image/') ||
|
|
53
|
+
lowerName.endsWith('.png') ||
|
|
54
|
+
lowerName.endsWith('.jpg') ||
|
|
55
|
+
lowerName.endsWith('.jpeg') ||
|
|
56
|
+
lowerName.endsWith('.webp')
|
|
57
|
+
) {
|
|
58
|
+
return 'image';
|
|
59
|
+
}
|
|
60
|
+
if (
|
|
61
|
+
lowerMime.startsWith('video/') ||
|
|
62
|
+
lowerName.endsWith('.mp4') ||
|
|
63
|
+
lowerName.endsWith('.mov') ||
|
|
64
|
+
lowerName.endsWith('.m4v')
|
|
65
|
+
) {
|
|
66
|
+
return 'video';
|
|
67
|
+
}
|
|
68
|
+
if (
|
|
69
|
+
lowerMime.startsWith('audio/') ||
|
|
70
|
+
lowerName.endsWith('.m4a') ||
|
|
71
|
+
lowerName.endsWith('.aac') ||
|
|
72
|
+
lowerName.endsWith('.wav') ||
|
|
73
|
+
lowerName.endsWith('.mp3')
|
|
74
|
+
) {
|
|
75
|
+
return 'audio';
|
|
76
|
+
}
|
|
77
|
+
return 'unknown';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Pick an existing media file from the device. Requires
|
|
82
|
+
* `expo-document-picker` to be installed.
|
|
83
|
+
*/
|
|
84
|
+
export async function pickFeedbackFile(): Promise<PickedFeedbackFile> {
|
|
85
|
+
try {
|
|
86
|
+
const picker = require('expo-document-picker');
|
|
87
|
+
const result = await picker.getDocumentAsync({
|
|
88
|
+
copyToCacheDirectory: true,
|
|
89
|
+
multiple: false,
|
|
90
|
+
type: ['image/*', 'video/*', 'audio/*'],
|
|
91
|
+
});
|
|
92
|
+
if (result?.canceled) {
|
|
93
|
+
throw new Error('File selection canceled.');
|
|
94
|
+
}
|
|
95
|
+
const asset = result?.assets?.[0];
|
|
96
|
+
if (!asset?.uri) {
|
|
97
|
+
throw new Error('No file selected.');
|
|
98
|
+
}
|
|
99
|
+
const name = asset.name || asset.uri.split('/').pop() || 'attachment';
|
|
100
|
+
const mimeType = asset.mimeType as string | undefined;
|
|
101
|
+
return {
|
|
102
|
+
path: asset.uri,
|
|
103
|
+
name,
|
|
104
|
+
mimeType,
|
|
105
|
+
kind: classifyPickedFile(name, mimeType),
|
|
106
|
+
};
|
|
107
|
+
} catch (err) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
'[YaverFeedback] File upload needs `expo-document-picker` as an optional peer dependency. ' +
|
|
110
|
+
String(err),
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
41
115
|
let videoRecorderModule: any = null;
|
|
42
116
|
let videoRecordingActive = false;
|
|
43
117
|
|
package/src/index.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* yaver-feedback-react-native — Visual feedback SDK for Yaver.
|
|
3
3
|
*
|
|
4
|
-
* Shake-to-report surface with
|
|
4
|
+
* Shake-to-report surface with four core actions:
|
|
5
5
|
* 1. Hot Reload — instant JS reload
|
|
6
|
-
* 2.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* 4.
|
|
10
|
-
*
|
|
6
|
+
* 2. Vibing — open a vibing session on the agent
|
|
7
|
+
* 3. Screenshot / Upload — capture the screen under the modal or
|
|
8
|
+
* upload existing media
|
|
9
|
+
* 4. Screen Recording — start, then stop + upload
|
|
10
|
+
*
|
|
11
|
+
* The small quick-access icon stays hidden until the first shake by
|
|
12
|
+
* default on mobile, then remains available unless the user hides it.
|
|
11
13
|
*
|
|
12
14
|
* @example
|
|
13
15
|
* ```tsx
|
|
@@ -83,6 +85,7 @@ export type {
|
|
|
83
85
|
} from './auth';
|
|
84
86
|
export {
|
|
85
87
|
captureScreenshot,
|
|
88
|
+
pickFeedbackFile,
|
|
86
89
|
startVideoRecording,
|
|
87
90
|
stopVideoRecording,
|
|
88
91
|
isVideoRecording,
|
package/src/types.ts
CHANGED
|
@@ -48,7 +48,7 @@ export interface FeedbackConfig {
|
|
|
48
48
|
* Single tap → open the feedback modal; long-press → menu with
|
|
49
49
|
* "Hide icon" (persisted across launches via AsyncStorage).
|
|
50
50
|
*
|
|
51
|
-
* - `'auto'` (default) → `'
|
|
51
|
+
* - `'auto'` (default) → `'after-shake'` on iOS/Android, `'off'` on web.
|
|
52
52
|
* - `'always'` → visible from app launch.
|
|
53
53
|
* - `'after-shake'` → hidden until the first shake this session.
|
|
54
54
|
* - `'off'` → never rendered. Shake still works.
|
|
@@ -59,11 +59,34 @@ export interface FeedbackConfig {
|
|
|
59
59
|
*/
|
|
60
60
|
quickIcon?: 'auto' | 'always' | 'after-shake' | 'off';
|
|
61
61
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
62
|
+
* Deprecated alias for `quickIconBackgroundColor`.
|
|
63
|
+
*
|
|
64
|
+
* Background color for the quick-action icon. The SDK now defaults
|
|
65
|
+
* to a high-visibility safety orange so the icon reads as a utility
|
|
66
|
+
* affordance instead of blending into the common blue/indigo FAB
|
|
67
|
+
* palette many mobile apps already use.
|
|
65
68
|
*/
|
|
66
69
|
quickIconColor?: string;
|
|
70
|
+
/**
|
|
71
|
+
* Background color for the quick-action icon.
|
|
72
|
+
* Default: '#ff6b2c' (safety orange).
|
|
73
|
+
*/
|
|
74
|
+
quickIconBackgroundColor?: string;
|
|
75
|
+
/**
|
|
76
|
+
* Foreground/text color for the quick-action icon label.
|
|
77
|
+
* Default: '#111111'.
|
|
78
|
+
*/
|
|
79
|
+
quickIconForegroundColor?: string;
|
|
80
|
+
/**
|
|
81
|
+
* Border color for the quick-action icon.
|
|
82
|
+
* Default: 'rgba(255,255,255,0.92)'.
|
|
83
|
+
*/
|
|
84
|
+
quickIconBorderColor?: string;
|
|
85
|
+
/**
|
|
86
|
+
* Shadow color for the quick-action icon.
|
|
87
|
+
* Default: '#000000'.
|
|
88
|
+
*/
|
|
89
|
+
quickIconShadowColor?: string;
|
|
67
90
|
/**
|
|
68
91
|
* Initial position of the quick-action icon, in pixels from the
|
|
69
92
|
* top-left. Default: near the top-right corner of the screen. The
|
|
@@ -189,6 +212,8 @@ export interface FeedbackBundle {
|
|
|
189
212
|
metadata: FeedbackMetadata;
|
|
190
213
|
/** Screen-recording file path, when produced by the "Start Recording" action. */
|
|
191
214
|
video?: string;
|
|
215
|
+
/** Optional audio attachment path. */
|
|
216
|
+
audio?: string;
|
|
192
217
|
screenshots: string[];
|
|
193
218
|
/** Captured errors with stack traces, attached via attachError / wrapErrorHandler. */
|
|
194
219
|
errors?: CapturedError[];
|
package/src/upload.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { FeedbackBundle } from './types';
|
|
|
8
8
|
* - `metadata` (JSON string)
|
|
9
9
|
* - `screenshot_0`, `screenshot_1`, ... (image files)
|
|
10
10
|
* - `video` (video file, if present)
|
|
11
|
+
* - `audio` (audio file, if present)
|
|
11
12
|
*
|
|
12
13
|
* Returns the parsed agent response — typically `{ ok, id, reportId }`.
|
|
13
14
|
* Callers can inspect `.id` / `.reportId` to drive a follow-up
|
|
@@ -43,6 +44,14 @@ export async function uploadFeedback(
|
|
|
43
44
|
} as any);
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
if (bundle.audio) {
|
|
48
|
+
formData.append('audio', {
|
|
49
|
+
uri: Platform.OS === 'android' ? `file://${bundle.audio}` : bundle.audio,
|
|
50
|
+
type: 'audio/m4a',
|
|
51
|
+
name: 'voice_note.m4a',
|
|
52
|
+
} as any);
|
|
53
|
+
}
|
|
54
|
+
|
|
46
55
|
const url = agentUrl.replace(/\/$/, '') + '/feedback';
|
|
47
56
|
|
|
48
57
|
const response = await fetch(url, {
|