yaver-feedback-react-native 0.2.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/README.md +690 -0
- package/app.plugin.js +70 -0
- package/package.json +39 -0
- package/src/BlackBox.ts +317 -0
- package/src/ConnectionScreen.tsx +400 -0
- package/src/Discovery.ts +223 -0
- package/src/FeedbackModal.tsx +678 -0
- package/src/FixReport.tsx +313 -0
- package/src/FloatingButton.tsx +860 -0
- package/src/P2PClient.ts +303 -0
- package/src/ShakeDetector.ts +57 -0
- package/src/YaverFeedback.ts +345 -0
- package/src/__tests__/Discovery.test.ts +187 -0
- package/src/__tests__/P2PClient.test.ts +218 -0
- package/src/__tests__/SDKToken.test.ts +268 -0
- package/src/__tests__/YaverFeedback.test.ts +189 -0
- package/src/__tests__/types.test.ts +247 -0
- package/src/capture.ts +84 -0
- package/src/expo.ts +62 -0
- package/src/index.ts +54 -0
- package/src/types.ts +251 -0
- package/src/upload.ts +74 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
export interface FeedbackConfig {
|
|
2
|
+
/** URL of the Yaver agent (e.g. "http://192.168.1.10:18080"). If omitted, auto-discovery is used. */
|
|
3
|
+
agentUrl?: string;
|
|
4
|
+
/** Auth token for the Yaver agent */
|
|
5
|
+
authToken: string;
|
|
6
|
+
/**
|
|
7
|
+
* Convex site URL for cloud IP resolution.
|
|
8
|
+
* When set, the SDK fetches the agent's IP from Convex instead of
|
|
9
|
+
* requiring a hardcoded agentUrl. Works with cloud machines (CPU/GPU)
|
|
10
|
+
* where the IP is managed by Yaver.
|
|
11
|
+
*
|
|
12
|
+
* Set this OR agentUrl — not both. If both are set, agentUrl wins.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* convexUrl: 'https://your-app.convex.site'
|
|
16
|
+
*/
|
|
17
|
+
convexUrl?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Preferred device ID to connect to (from Convex device list).
|
|
20
|
+
* If omitted with convexUrl, connects to the first online device.
|
|
21
|
+
*/
|
|
22
|
+
preferredDeviceId?: string;
|
|
23
|
+
/** How feedback collection is triggered */
|
|
24
|
+
trigger?: 'shake' | 'floating-button' | 'manual';
|
|
25
|
+
/** Enable/disable the SDK. Defaults to __DEV__ */
|
|
26
|
+
enabled?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Reporting-only mode. When true:
|
|
29
|
+
* - Shake auto-captures screenshot + errors and sends to the agent
|
|
30
|
+
* - No floating button, no feedback modal, no auto-test UI
|
|
31
|
+
* - Agent receives and stores the report but does NOT auto-trigger a fix
|
|
32
|
+
* - Developer reviews reports in CLI and decides what to fix
|
|
33
|
+
*
|
|
34
|
+
* Use case: enable for QA testers / beta users who should report bugs
|
|
35
|
+
* but not trigger code changes. The developer configures their own device
|
|
36
|
+
* with `reportingOnly: false` to get the full SDK with fix triggers.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* // For beta testers:
|
|
40
|
+
* YaverFeedback.init({ reportingOnly: true, trigger: 'shake', ... });
|
|
41
|
+
*
|
|
42
|
+
* // For the developer:
|
|
43
|
+
* YaverFeedback.init({ reportingOnly: false, trigger: 'floating-button', ... });
|
|
44
|
+
*/
|
|
45
|
+
reportingOnly?: boolean;
|
|
46
|
+
/** Max screen recording duration in seconds. Default: 120 */
|
|
47
|
+
maxRecordingDuration?: number;
|
|
48
|
+
/**
|
|
49
|
+
* Feedback mode:
|
|
50
|
+
* - 'live': stream events to the agent as they happen
|
|
51
|
+
* - 'narrated': record everything, send on stop
|
|
52
|
+
* - 'batch': dump everything at end (default)
|
|
53
|
+
*/
|
|
54
|
+
feedbackMode?: 'live' | 'narrated' | 'batch';
|
|
55
|
+
/**
|
|
56
|
+
* Agent commentary level (0-10).
|
|
57
|
+
* 0 = silent, 10 = agent comments on everything it sees.
|
|
58
|
+
* Only relevant in live mode. Default: 0.
|
|
59
|
+
*/
|
|
60
|
+
agentCommentaryLevel?: number;
|
|
61
|
+
/**
|
|
62
|
+
* Enable voice input for feedback annotations. Always true by default.
|
|
63
|
+
* Audio is recorded on the device and sent to the agent for transcription.
|
|
64
|
+
* Works regardless of whether a speech-to-speech provider is configured —
|
|
65
|
+
* if STT is available on the agent, audio is auto-transcribed; otherwise
|
|
66
|
+
* raw audio is attached to the feedback report.
|
|
67
|
+
*/
|
|
68
|
+
voiceEnabled?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Maximum number of captured errors to keep in memory (ring buffer).
|
|
71
|
+
* Oldest errors are evicted when the buffer is full.
|
|
72
|
+
* Default: 5.
|
|
73
|
+
*
|
|
74
|
+
* Errors are captured via `YaverFeedback.attachError()` or
|
|
75
|
+
* `YaverFeedback.wrapErrorHandler()`. The SDK never auto-hooks global
|
|
76
|
+
* error handlers — this avoids conflicts with Sentry, Crashlytics,
|
|
77
|
+
* Bugsnag, or any other error tracking tool.
|
|
78
|
+
*/
|
|
79
|
+
maxCapturedErrors?: number;
|
|
80
|
+
/**
|
|
81
|
+
* Which platforms the Build button targets.
|
|
82
|
+
* - 'ios' — build iOS only
|
|
83
|
+
* - 'android' — build Android only
|
|
84
|
+
* - 'both' — build iOS and Android sequentially (default)
|
|
85
|
+
* - 'web' — build web app
|
|
86
|
+
*/
|
|
87
|
+
buildPlatforms?: 'ios' | 'android' | 'both' | 'web';
|
|
88
|
+
/**
|
|
89
|
+
* Auto-deploy builds after successful build.
|
|
90
|
+
* When true (default), iOS builds are uploaded to TestFlight
|
|
91
|
+
* and Android builds are uploaded to Google Play internal testing.
|
|
92
|
+
* When false, builds are created locally without uploading.
|
|
93
|
+
*/
|
|
94
|
+
autoDeploy?: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Background color of the debug console panel.
|
|
97
|
+
* Default: "#2d2d2d" (dark gray). Set to any color to match your app's theme
|
|
98
|
+
* and avoid visual overlap.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* panelBackgroundColor: '#1a1a2e' // dark blue-gray
|
|
102
|
+
* panelBackgroundColor: '#333333' // neutral dark gray
|
|
103
|
+
*/
|
|
104
|
+
panelBackgroundColor?: string;
|
|
105
|
+
/**
|
|
106
|
+
* TLS certificate fingerprint (SHA256) for HTTPS on LAN.
|
|
107
|
+
* When set, the SDK prefers HTTPS connections and verifies the agent's
|
|
108
|
+
* self-signed cert matches this fingerprint.
|
|
109
|
+
*/
|
|
110
|
+
tlsFingerprint?: string;
|
|
111
|
+
/**
|
|
112
|
+
* Require TLS for all connections. When true, refuses HTTP connections.
|
|
113
|
+
* Default: false (HTTPS preferred when fingerprint available, HTTP fallback).
|
|
114
|
+
*/
|
|
115
|
+
requireTLS?: boolean;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface FeedbackBundle {
|
|
119
|
+
metadata: FeedbackMetadata;
|
|
120
|
+
video?: string;
|
|
121
|
+
/** Voice annotation audio file path (WAV). Always available when voiceEnabled. */
|
|
122
|
+
audio?: string;
|
|
123
|
+
/** Transcribed text from voice annotation (if STT/S2S provider is available on agent). */
|
|
124
|
+
audioTranscript?: string;
|
|
125
|
+
screenshots: string[];
|
|
126
|
+
/** Captured errors with stack traces, attached automatically when captureErrors is enabled. */
|
|
127
|
+
errors?: CapturedError[];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** An error captured by the SDK's global error handler. */
|
|
131
|
+
export interface CapturedError {
|
|
132
|
+
/** Error message. */
|
|
133
|
+
message: string;
|
|
134
|
+
/** Parsed stack frames (e.g. "at CheckoutButton.handlePress (CheckoutScreen.tsx:47)"). */
|
|
135
|
+
stack: string[];
|
|
136
|
+
/** Whether this was a fatal (unrecoverable) error. */
|
|
137
|
+
isFatal: boolean;
|
|
138
|
+
/** Unix timestamp in milliseconds when the error occurred. */
|
|
139
|
+
timestamp: number;
|
|
140
|
+
/** Optional developer-attached context via YaverFeedback.attachError(). */
|
|
141
|
+
metadata?: Record<string, unknown>;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface FeedbackMetadata {
|
|
145
|
+
timestamp: string;
|
|
146
|
+
device: DeviceInfo;
|
|
147
|
+
app: AppInfo;
|
|
148
|
+
userNote?: string;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface DeviceInfo {
|
|
152
|
+
platform: string;
|
|
153
|
+
osVersion: string;
|
|
154
|
+
model: string;
|
|
155
|
+
screenWidth: number;
|
|
156
|
+
screenHeight: number;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface AppInfo {
|
|
160
|
+
bundleId?: string;
|
|
161
|
+
version?: string;
|
|
162
|
+
buildNumber?: string;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export interface TimelineEvent {
|
|
166
|
+
type: 'screenshot' | 'audio' | 'video';
|
|
167
|
+
path: string;
|
|
168
|
+
timestamp: string;
|
|
169
|
+
duration?: number;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface FeedbackReport {
|
|
173
|
+
id: string;
|
|
174
|
+
bundle: FeedbackBundle;
|
|
175
|
+
status: 'pending' | 'uploading' | 'uploaded' | 'failed';
|
|
176
|
+
error?: string;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export interface AgentCommentary {
|
|
180
|
+
id: string;
|
|
181
|
+
timestamp: string;
|
|
182
|
+
message: string;
|
|
183
|
+
type: 'observation' | 'suggestion' | 'question' | 'action';
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export interface FeedbackStreamEvent {
|
|
187
|
+
type: string;
|
|
188
|
+
timestamp: string;
|
|
189
|
+
data: any;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* A single fix applied by the AI agent during a test session.
|
|
194
|
+
* Displayed in the FixReport component as a markdown-style list.
|
|
195
|
+
*/
|
|
196
|
+
export interface TestFix {
|
|
197
|
+
/** Unique fix ID */
|
|
198
|
+
id: string;
|
|
199
|
+
/** File that was modified */
|
|
200
|
+
file: string;
|
|
201
|
+
/** Line number of the change */
|
|
202
|
+
line?: number;
|
|
203
|
+
/** Short description of what was fixed */
|
|
204
|
+
description: string;
|
|
205
|
+
/** The error/exception that triggered this fix */
|
|
206
|
+
error?: string;
|
|
207
|
+
/** Diff or code snippet (shown on expand) */
|
|
208
|
+
diff?: string;
|
|
209
|
+
/** Timestamp when the fix was applied */
|
|
210
|
+
timestamp: string;
|
|
211
|
+
/** Whether the fix resolved the issue (verified after hot reload) */
|
|
212
|
+
verified?: boolean;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Test session state returned by the agent's /test-app/status endpoint.
|
|
217
|
+
*/
|
|
218
|
+
export interface TestSession {
|
|
219
|
+
/** Whether a test session is currently running */
|
|
220
|
+
active: boolean;
|
|
221
|
+
/** Current screen being tested */
|
|
222
|
+
currentScreen?: string;
|
|
223
|
+
/** Total screens discovered */
|
|
224
|
+
screensDiscovered: number;
|
|
225
|
+
/** Total screens tested */
|
|
226
|
+
screensTested: number;
|
|
227
|
+
/** Errors found during this session */
|
|
228
|
+
errorsFound: number;
|
|
229
|
+
/** Fixes applied (not committed — staged only) */
|
|
230
|
+
fixes: TestFix[];
|
|
231
|
+
/** Session start time */
|
|
232
|
+
startedAt?: string;
|
|
233
|
+
/** Elapsed seconds */
|
|
234
|
+
elapsedSeconds?: number;
|
|
235
|
+
/** Status message */
|
|
236
|
+
status: string;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Voice capability info returned by the agent's /voice/status endpoint. */
|
|
240
|
+
export interface VoiceCapability {
|
|
241
|
+
/** Always true — mobile can always record and send audio. */
|
|
242
|
+
voiceInputEnabled: boolean;
|
|
243
|
+
/** Speech-to-speech provider (e.g. "personaplex", "openai"), or null. */
|
|
244
|
+
s2sProvider?: string;
|
|
245
|
+
/** Whether the S2S provider is ready for real-time sessions. */
|
|
246
|
+
s2sReady?: boolean;
|
|
247
|
+
/** Speech-to-text provider for transcription (e.g. "whisper", "openai"). */
|
|
248
|
+
sttProvider?: string;
|
|
249
|
+
/** Whether STT is ready (auto-transcription of voice input). */
|
|
250
|
+
sttReady?: boolean;
|
|
251
|
+
}
|
package/src/upload.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
import { FeedbackBundle } from './types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Upload a feedback bundle to the Yaver agent via multipart POST.
|
|
6
|
+
*
|
|
7
|
+
* The agent receives the bundle at POST /feedback with:
|
|
8
|
+
* - `metadata` (JSON string)
|
|
9
|
+
* - `screenshot_0`, `screenshot_1`, ... (image files)
|
|
10
|
+
* - `audio` (audio file, if present)
|
|
11
|
+
* - `video` (video file, if present)
|
|
12
|
+
*
|
|
13
|
+
* @returns The feedback report ID from the agent response.
|
|
14
|
+
*/
|
|
15
|
+
export async function uploadFeedback(
|
|
16
|
+
agentUrl: string,
|
|
17
|
+
authToken: string,
|
|
18
|
+
bundle: FeedbackBundle,
|
|
19
|
+
): Promise<string> {
|
|
20
|
+
const formData = new FormData();
|
|
21
|
+
|
|
22
|
+
// Attach metadata as JSON
|
|
23
|
+
formData.append('metadata', JSON.stringify(bundle.metadata));
|
|
24
|
+
|
|
25
|
+
// Attach screenshots
|
|
26
|
+
for (let i = 0; i < bundle.screenshots.length; i++) {
|
|
27
|
+
const path = bundle.screenshots[i];
|
|
28
|
+
formData.append(`screenshot_${i}`, {
|
|
29
|
+
uri: Platform.OS === 'android' ? `file://${path}` : path,
|
|
30
|
+
type: 'image/png',
|
|
31
|
+
name: `screenshot_${i}.png`,
|
|
32
|
+
} as any);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Attach audio
|
|
36
|
+
if (bundle.audio) {
|
|
37
|
+
formData.append('audio', {
|
|
38
|
+
uri:
|
|
39
|
+
Platform.OS === 'android' ? `file://${bundle.audio}` : bundle.audio,
|
|
40
|
+
type: 'audio/m4a',
|
|
41
|
+
name: 'voice_note.m4a',
|
|
42
|
+
} as any);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Attach video
|
|
46
|
+
if (bundle.video) {
|
|
47
|
+
formData.append('video', {
|
|
48
|
+
uri:
|
|
49
|
+
Platform.OS === 'android' ? `file://${bundle.video}` : bundle.video,
|
|
50
|
+
type: 'video/mp4',
|
|
51
|
+
name: 'screen_recording.mp4',
|
|
52
|
+
} as any);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const url = agentUrl.replace(/\/$/, '') + '/feedback';
|
|
56
|
+
|
|
57
|
+
const response = await fetch(url, {
|
|
58
|
+
method: 'POST',
|
|
59
|
+
headers: {
|
|
60
|
+
Authorization: `Bearer ${authToken}`,
|
|
61
|
+
},
|
|
62
|
+
body: formData,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
const text = await response.text().catch(() => '');
|
|
67
|
+
throw new Error(
|
|
68
|
+
`[YaverFeedback] Upload failed (${response.status}): ${text}`,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const result = await response.json();
|
|
73
|
+
return result.id ?? result.reportId ?? 'unknown';
|
|
74
|
+
}
|