yaver-feedback-react-native 0.8.13 → 0.9.2
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/app.plugin.js +126 -9
- package/dist/BlackBox.js +9 -1
- package/dist/DeployPanel.js +65 -0
- package/dist/Discovery.js +13 -2
- package/dist/FeedbackModal.js +477 -66
- package/dist/MachinePickerScreen.js +14 -5
- package/dist/P2PClient.d.ts +94 -1
- package/dist/P2PClient.js +281 -2
- package/dist/ShakeDetector.js +2 -0
- package/dist/VibeChatScreen.d.ts +6 -1
- package/dist/VibeChatScreen.js +204 -1
- package/dist/YaverFeedback.d.ts +98 -0
- package/dist/YaverFeedback.js +509 -46
- package/dist/__tests__/BlackBox.relayPassword.test.d.ts +1 -0
- package/dist/__tests__/BlackBox.relayPassword.test.js +105 -0
- package/dist/__tests__/BlackBoxAutoStart.test.d.ts +1 -0
- package/dist/__tests__/BlackBoxAutoStart.test.js +91 -0
- package/dist/__tests__/BlackBoxAutoStartColdStart.test.d.ts +1 -0
- package/dist/__tests__/BlackBoxAutoStartColdStart.test.js +156 -0
- package/dist/__tests__/BrowserLaneIcon.test.d.ts +3 -0
- package/dist/__tests__/BrowserLaneIcon.test.js +80 -0
- package/dist/__tests__/P2PClient.test.js +2 -2
- package/dist/__tests__/ReportIdentity.test.d.ts +1 -0
- package/dist/__tests__/ReportIdentity.test.js +168 -0
- package/dist/__tests__/SDKToken.test.js +1 -1
- package/dist/__tests__/ShakeToggle.test.d.ts +1 -0
- package/dist/__tests__/ShakeToggle.test.js +121 -0
- package/dist/__tests__/pickTargetDevice.test.d.ts +1 -0
- package/dist/__tests__/pickTargetDevice.test.js +89 -0
- package/dist/__tests__/reloadActions.test.d.ts +1 -0
- package/dist/__tests__/reloadActions.test.js +129 -0
- package/dist/__tests__/reloadActionsParity.test.d.ts +1 -0
- package/dist/__tests__/reloadActionsParity.test.js +42 -0
- package/dist/__tests__/types.test.js +5 -5
- package/dist/_core/device.d.ts +19 -9
- package/dist/_core/device.js +20 -14
- package/dist/capture.d.ts +6 -0
- package/dist/capture.js +53 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +11 -1
- package/dist/reloadActions.d.ts +88 -0
- package/dist/reloadActions.js +200 -0
- package/dist/storeShots.d.ts +67 -0
- package/dist/storeShots.js +137 -0
- package/dist/types.d.ts +215 -3
- package/dist/voice.d.ts +61 -0
- package/dist/voice.js +246 -0
- package/package.json +14 -3
- package/src/BlackBox.ts +10 -1
- package/src/DeployPanel.tsx +74 -0
- package/src/Discovery.ts +13 -2
- package/src/FeedbackModal.tsx +563 -87
- package/src/MachinePickerScreen.tsx +12 -3
- package/src/P2PClient.ts +314 -2
- package/src/ShakeDetector.ts +1 -0
- package/src/VibeChatScreen.tsx +219 -0
- package/src/YaverFeedback.ts +498 -46
- package/src/__tests__/BlackBox.relayPassword.test.ts +129 -0
- package/src/__tests__/BlackBoxAutoStart.test.ts +111 -0
- package/src/__tests__/BlackBoxAutoStartColdStart.test.ts +191 -0
- package/src/__tests__/BrowserLaneIcon.test.ts +85 -0
- package/src/__tests__/P2PClient.test.ts +2 -2
- package/src/__tests__/ReportIdentity.test.ts +203 -0
- package/src/__tests__/SDKToken.test.ts +1 -1
- package/src/__tests__/ShakeToggle.test.ts +153 -0
- package/src/__tests__/pickTargetDevice.test.ts +101 -0
- package/src/__tests__/reloadActions.test.ts +171 -0
- package/src/__tests__/reloadActionsParity.test.ts +49 -0
- package/src/__tests__/types.test.ts +5 -5
- package/src/_core/device.ts +20 -14
- package/src/capture.ts +51 -0
- package/src/index.ts +21 -0
- package/src/reloadActions.ts +273 -0
- package/src/storeShots.ts +189 -0
- package/src/types.ts +217 -3
- package/src/voice.ts +270 -0
package/dist/VibeChatScreen.js
CHANGED
|
@@ -53,7 +53,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
53
53
|
exports.VibeChatScreen = VibeChatScreen;
|
|
54
54
|
const react_1 = __importStar(require("react"));
|
|
55
55
|
const react_native_1 = require("react-native");
|
|
56
|
-
|
|
56
|
+
const voice_1 = require("./voice");
|
|
57
|
+
const capture_1 = require("./capture");
|
|
58
|
+
function VibeChatScreen({ client, initialTaskId, initialUserPrompt, onClose, onReload, project, model, runner, }) {
|
|
57
59
|
const [taskId, setTaskId] = (0, react_1.useState)(initialTaskId);
|
|
58
60
|
const [turns, setTurns] = (0, react_1.useState)(() => [
|
|
59
61
|
{
|
|
@@ -76,6 +78,16 @@ function VibeChatScreen({ client, initialTaskId, initialUserPrompt, onClose, onR
|
|
|
76
78
|
const [isReloading, setIsReloading] = (0, react_1.useState)(false);
|
|
77
79
|
const scrollRef = (0, react_1.useRef)(null);
|
|
78
80
|
const abortRef = (0, react_1.useRef)(null);
|
|
81
|
+
// ── Voice vibe coding ──────────────────────────────────────────────
|
|
82
|
+
const [voiceState, setVoiceState] = (0, react_1.useState)('idle');
|
|
83
|
+
const [voiceAvailable, setVoiceAvailable] = (0, react_1.useState)(false);
|
|
84
|
+
// "local" = whisper.cpp on the host (free, private); "flux" = Deepgram
|
|
85
|
+
// nova-3 streaming. fluxAvailable gates the toggle on whether the agent
|
|
86
|
+
// has a Deepgram key. activeEngine is echoed back by the agent.
|
|
87
|
+
const [voiceMode, setVoiceMode] = (0, react_1.useState)('local');
|
|
88
|
+
const [fluxAvailable, setFluxAvailable] = (0, react_1.useState)(false);
|
|
89
|
+
const [activeEngine, setActiveEngine] = (0, react_1.useState)('');
|
|
90
|
+
const voiceSessionRef = (0, react_1.useRef)(null);
|
|
79
91
|
// Subscribe to the current task's SSE stream. Re-runs whenever the
|
|
80
92
|
// taskId changes (resumeTask reuses the same id, so this only fires
|
|
81
93
|
// once per task — which is fine).
|
|
@@ -195,6 +207,170 @@ function VibeChatScreen({ client, initialTaskId, initialUserPrompt, onClose, onR
|
|
|
195
207
|
setIsReloading(false);
|
|
196
208
|
}
|
|
197
209
|
}, [isReloading, onReload]);
|
|
210
|
+
// Probe whether voice is usable: deps present (expo-av + expo-file-
|
|
211
|
+
// system + buffer) AND the agent reports STT/TTS ready. Hide the mic
|
|
212
|
+
// entirely otherwise so users never tap a dead button.
|
|
213
|
+
(0, react_1.useEffect)(() => {
|
|
214
|
+
let cancelled = false;
|
|
215
|
+
(async () => {
|
|
216
|
+
if (!(0, capture_1.isVoiceCaptureSupported)() || !(0, voice_1.isVoiceStreamSupported)())
|
|
217
|
+
return;
|
|
218
|
+
try {
|
|
219
|
+
const res = await fetch(`${client.agentBaseUrl}/voice/status`, { headers: client.voiceAuthHeaders() });
|
|
220
|
+
if (!res.ok)
|
|
221
|
+
return;
|
|
222
|
+
const body = await res.json();
|
|
223
|
+
if (cancelled)
|
|
224
|
+
return;
|
|
225
|
+
// Local whisper is always usable when voice is enabled; Flux needs
|
|
226
|
+
// a Deepgram key on the agent. Show the mic if either path works.
|
|
227
|
+
const localOk = !!body?.enabled;
|
|
228
|
+
const fluxOk = !!body?.enabled && !!body?.deepgramSet;
|
|
229
|
+
if (localOk || fluxOk)
|
|
230
|
+
setVoiceAvailable(true);
|
|
231
|
+
setFluxAvailable(fluxOk);
|
|
232
|
+
if (!localOk && fluxOk)
|
|
233
|
+
setVoiceMode('flux');
|
|
234
|
+
}
|
|
235
|
+
catch { /* leave hidden */ }
|
|
236
|
+
})();
|
|
237
|
+
return () => { cancelled = true; };
|
|
238
|
+
}, [client]);
|
|
239
|
+
(0, react_1.useEffect)(() => () => { voiceSessionRef.current?.close(); }, []);
|
|
240
|
+
// Local TTS: the agent streams no audio for "local"/"device" engines,
|
|
241
|
+
// so the client speaks the result text with the device synthesizer.
|
|
242
|
+
// expo-speech is optional — if absent, the text is still shown.
|
|
243
|
+
const speakLocalText = (0, react_1.useCallback)((text) => {
|
|
244
|
+
if (!text)
|
|
245
|
+
return;
|
|
246
|
+
try {
|
|
247
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
248
|
+
const Speech = require('expo-speech');
|
|
249
|
+
const headline = text.length > 280 ? `${text.slice(0, 280)} — see screen for the rest.` : text;
|
|
250
|
+
Speech.stop?.();
|
|
251
|
+
Speech.speak?.(headline);
|
|
252
|
+
}
|
|
253
|
+
catch { /* expo-speech not installed — text remains visible */ }
|
|
254
|
+
}, []);
|
|
255
|
+
const playTTS = (0, react_1.useCallback)(async (pcm, sampleRate) => {
|
|
256
|
+
try {
|
|
257
|
+
const wavUri = await (0, voice_1.pcmToTempWavURI)(pcm, sampleRate);
|
|
258
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
259
|
+
const { Audio } = require('expo-av');
|
|
260
|
+
const { sound } = await Audio.Sound.createAsync({ uri: wavUri }, { shouldPlay: true });
|
|
261
|
+
sound.setOnPlaybackStatusUpdate((st) => {
|
|
262
|
+
if (st.didJustFinish)
|
|
263
|
+
sound.unloadAsync().catch(() => { });
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
catch { /* playback best-effort */ }
|
|
267
|
+
}, []);
|
|
268
|
+
const stopVoiceAndProcess = (0, react_1.useCallback)(async () => {
|
|
269
|
+
setVoiceState('uploading');
|
|
270
|
+
let uri = null;
|
|
271
|
+
try {
|
|
272
|
+
uri = await (0, capture_1.stopPcmRecording)();
|
|
273
|
+
}
|
|
274
|
+
catch (e) {
|
|
275
|
+
setVoiceState('idle');
|
|
276
|
+
setTurns((prev) => [...prev, { id: `status-verr-${Date.now()}`, role: 'status', text: `voice: ${e instanceof Error ? e.message : String(e)}`, timestamp: Date.now() }]);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (!uri) {
|
|
280
|
+
setVoiceState('idle');
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const useFlux = voiceMode === 'flux' && fluxAvailable;
|
|
284
|
+
const session = new voice_1.SDKVoiceSession({
|
|
285
|
+
onProviders: (stt, tts) => setActiveEngine(stt === 'deepgram' ? 'Flux (Deepgram)' : stt === 'local' ? 'Local (whisper)' : stt),
|
|
286
|
+
onTranscriptPartial: (t) => {
|
|
287
|
+
setTurns((prev) => {
|
|
288
|
+
const next = prev.filter((x) => x.id !== 'voice-partial');
|
|
289
|
+
next.push({ id: 'voice-partial', role: 'status', text: `🎙 ${t}`, timestamp: Date.now() });
|
|
290
|
+
return next;
|
|
291
|
+
});
|
|
292
|
+
},
|
|
293
|
+
onTranscriptFinal: (t) => {
|
|
294
|
+
setVoiceState('thinking');
|
|
295
|
+
setTurns((prev) => [
|
|
296
|
+
...prev.filter((x) => x.id !== 'voice-partial'),
|
|
297
|
+
{ id: `user-voice-${Date.now()}`, role: 'user', text: t, timestamp: Date.now() },
|
|
298
|
+
{ id: `status-${Date.now()}`, role: 'status', text: 'thinking…', timestamp: Date.now() },
|
|
299
|
+
]);
|
|
300
|
+
},
|
|
301
|
+
onTaskCreated: (id) => {
|
|
302
|
+
// Hand the chat's SSE subscription the new task so its agent
|
|
303
|
+
// output streams into the transcript exactly like a typed turn.
|
|
304
|
+
if (id) {
|
|
305
|
+
setStatus('running');
|
|
306
|
+
setStreamBuffer('');
|
|
307
|
+
setTaskId(id);
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
onTaskResult: (_id, text) => {
|
|
311
|
+
setVoiceState('speaking');
|
|
312
|
+
// Local TTS path: agent sends no audio frames, so speak here.
|
|
313
|
+
if (!useFlux)
|
|
314
|
+
speakLocalText(text);
|
|
315
|
+
},
|
|
316
|
+
onTTSReady: (pcm, sr) => { void playTTS(pcm, sr); },
|
|
317
|
+
onDone: () => setTimeout(() => setVoiceState('idle'), 1200),
|
|
318
|
+
onError: (msg) => {
|
|
319
|
+
setVoiceState('idle');
|
|
320
|
+
setTurns((prev) => [...prev.filter((x) => x.id !== 'voice-partial'), { id: `status-verr-${Date.now()}`, role: 'status', text: `voice: ${msg}`, timestamp: Date.now() }]);
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
voiceSessionRef.current = session;
|
|
324
|
+
try {
|
|
325
|
+
await session.start({
|
|
326
|
+
wsUrl: client.voiceStreamUrl(),
|
|
327
|
+
headers: client.voiceAuthHeaders(),
|
|
328
|
+
project,
|
|
329
|
+
model,
|
|
330
|
+
runner,
|
|
331
|
+
surface: 'feedback-sdk',
|
|
332
|
+
ttsBudget: 280,
|
|
333
|
+
// Local: whisper.cpp on the host + device synth. Flux: Deepgram
|
|
334
|
+
// nova-3 STT + Aura TTS streamed back as PCM.
|
|
335
|
+
sttProvider: useFlux ? 'deepgram' : 'local',
|
|
336
|
+
ttsProvider: useFlux ? 'deepgram' : 'local',
|
|
337
|
+
});
|
|
338
|
+
await session.streamAudioFile(uri, { skipWavHeader: true });
|
|
339
|
+
session.finalize();
|
|
340
|
+
}
|
|
341
|
+
catch (e) {
|
|
342
|
+
setVoiceState('idle');
|
|
343
|
+
session.close();
|
|
344
|
+
setTurns((prev) => [...prev, { id: `status-verr-${Date.now()}`, role: 'status', text: `voice: ${e instanceof Error ? e.message : String(e)}`, timestamp: Date.now() }]);
|
|
345
|
+
}
|
|
346
|
+
}, [client, project, model, runner, playTTS, voiceMode, fluxAvailable, speakLocalText]);
|
|
347
|
+
const handleVoicePress = (0, react_1.useCallback)(async () => {
|
|
348
|
+
if (voiceState === 'recording') {
|
|
349
|
+
void stopVoiceAndProcess();
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (voiceState !== 'idle') {
|
|
353
|
+
// Mid-flow tap cancels.
|
|
354
|
+
voiceSessionRef.current?.close();
|
|
355
|
+
voiceSessionRef.current = null;
|
|
356
|
+
setVoiceState('idle');
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
try {
|
|
360
|
+
await (0, capture_1.startPcmRecording)();
|
|
361
|
+
setVoiceState('recording');
|
|
362
|
+
}
|
|
363
|
+
catch (e) {
|
|
364
|
+
setTurns((prev) => [...prev, { id: `status-verr-${Date.now()}`, role: 'status', text: `voice: ${e instanceof Error ? e.message : String(e)}`, timestamp: Date.now() }]);
|
|
365
|
+
}
|
|
366
|
+
}, [voiceState, stopVoiceAndProcess]);
|
|
367
|
+
const voiceLabel = {
|
|
368
|
+
idle: '🎙 speak',
|
|
369
|
+
recording: '■ stop',
|
|
370
|
+
uploading: 'sending…',
|
|
371
|
+
thinking: 'thinking…',
|
|
372
|
+
speaking: 'speaking…',
|
|
373
|
+
};
|
|
198
374
|
return (<react_native_1.View style={styles.container}>
|
|
199
375
|
<react_native_1.View style={styles.header}>
|
|
200
376
|
<react_native_1.Text style={styles.title}>Vibe</react_native_1.Text>
|
|
@@ -226,8 +402,30 @@ function VibeChatScreen({ client, initialTaskId, initialUserPrompt, onClose, onR
|
|
|
226
402
|
</react_native_1.ScrollView>
|
|
227
403
|
|
|
228
404
|
<react_native_1.View style={styles.footer}>
|
|
405
|
+
{voiceAvailable && voiceState !== 'idle' && (<react_native_1.Text style={styles.engineCaption}>
|
|
406
|
+
{voiceState === 'recording' ? 'listening' : voiceState === 'uploading' ? 'sending' : voiceState === 'thinking' ? 'agent working' : 'speaking'}
|
|
407
|
+
{activeEngine ? ` · ${activeEngine}` : ` · ${voiceMode === 'flux' ? 'Flux (Deepgram)' : 'Local (whisper)'}`}
|
|
408
|
+
</react_native_1.Text>)}
|
|
229
409
|
<react_native_1.TextInput style={styles.input} value={followUp} onChangeText={setFollowUp} placeholder={status === 'running' ? 'wait for the agent…' : 'follow up…'} placeholderTextColor="#666" editable={status !== 'running' && !isResuming} multiline/>
|
|
230
410
|
<react_native_1.View style={styles.actions}>
|
|
411
|
+
{voiceAvailable && (<>
|
|
412
|
+
{/* Local ↔ Flux engine toggle. Only shows Flux when the
|
|
413
|
+
agent has a Deepgram key; otherwise the label just
|
|
414
|
+
states "Local" so the active engine is always clear. */}
|
|
415
|
+
{fluxAvailable ? (<react_native_1.TouchableOpacity style={[styles.actionBtn, styles.engineToggle]} onPress={() => setVoiceMode((m) => (m === 'local' ? 'flux' : 'local'))} disabled={voiceState !== 'idle'} accessibilityLabel="Toggle voice engine">
|
|
416
|
+
<react_native_1.Text style={styles.engineToggleText}>{voiceMode === 'flux' ? '⚡ Flux' : '🔒 Local'}</react_native_1.Text>
|
|
417
|
+
</react_native_1.TouchableOpacity>) : (<react_native_1.View style={[styles.actionBtn, styles.engineToggle]}>
|
|
418
|
+
<react_native_1.Text style={styles.engineToggleText}>🔒 Local</react_native_1.Text>
|
|
419
|
+
</react_native_1.View>)}
|
|
420
|
+
<react_native_1.TouchableOpacity style={[
|
|
421
|
+
styles.actionBtn,
|
|
422
|
+
styles.voiceBtn,
|
|
423
|
+
voiceState === 'recording' && styles.voiceBtnActive,
|
|
424
|
+
(voiceState === 'uploading' || voiceState === 'thinking' || voiceState === 'speaking') && styles.actionBtnDisabled,
|
|
425
|
+
]} onPress={handleVoicePress} disabled={voiceState === 'uploading' || voiceState === 'thinking' || voiceState === 'speaking'} accessibilityLabel="Vibe code by voice">
|
|
426
|
+
<react_native_1.Text style={styles.actionText}>{voiceLabel[voiceState]}</react_native_1.Text>
|
|
427
|
+
</react_native_1.TouchableOpacity>
|
|
428
|
+
</>)}
|
|
231
429
|
{onReload && (<react_native_1.TouchableOpacity style={[
|
|
232
430
|
styles.actionBtn,
|
|
233
431
|
styles.reloadBtn,
|
|
@@ -323,6 +521,11 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
323
521
|
},
|
|
324
522
|
actionBtnDisabled: { opacity: 0.5 },
|
|
325
523
|
reloadBtn: { backgroundColor: 'rgba(255,255,255,0.08)' },
|
|
524
|
+
voiceBtn: { backgroundColor: 'rgba(16,185,129,0.18)' },
|
|
525
|
+
voiceBtnActive: { backgroundColor: '#ef4444' },
|
|
526
|
+
engineToggle: { backgroundColor: 'rgba(255,255,255,0.06)', marginRight: 'auto', marginLeft: 0 },
|
|
527
|
+
engineToggleText: { color: '#cbd5e1', fontSize: 12, fontWeight: '600' },
|
|
528
|
+
engineCaption: { color: '#9ca3af', fontSize: 11, marginBottom: 6, marginLeft: 4 },
|
|
326
529
|
sendBtn: { backgroundColor: '#7582f5' },
|
|
327
530
|
actionText: { color: '#fff', fontSize: 13, fontWeight: '600' },
|
|
328
531
|
});
|
package/dist/YaverFeedback.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FeedbackConfig, CapturedError } from './types';
|
|
2
2
|
import { P2PClient } from './P2PClient';
|
|
3
|
+
import { CaptureStoreScreenshotsOptions, CaptureStoreScreenshotsResult } from './storeShots';
|
|
3
4
|
import { QuickIconColorPreset } from './preferences';
|
|
4
5
|
/**
|
|
5
6
|
* Main entry point for the Yaver Feedback SDK.
|
|
@@ -15,7 +16,26 @@ export declare class YaverFeedback {
|
|
|
15
16
|
* If no `agentUrl` is provided, the SDK will attempt auto-discovery
|
|
16
17
|
* via `YaverDiscovery` on the first `startReport()` call.
|
|
17
18
|
*/
|
|
19
|
+
/** The runtime lane, when running on web inside a Yaver preview WebView. */
|
|
20
|
+
static detectWebLane(): 'browser' | 'webrtc' | null;
|
|
21
|
+
/**
|
|
22
|
+
* Mount a draggable DOM floating "Y" icon for the BROWSER lane (RN-web). It
|
|
23
|
+
* lives in the WebView's document.body (position:fixed, max z-index), so it
|
|
24
|
+
* renders ON TOP of the page inside the WebView and is never occluded by the
|
|
25
|
+
* fullScreen preview modal — the one occlusion-proof affordance on iOS.
|
|
26
|
+
* Tap opens the SDK's existing report flow (the RN FeedbackModal renders in
|
|
27
|
+
* the same WebView DOM, also un-occluded). No-op off-web or off-lane, and
|
|
28
|
+
* idempotent (a re-init reuses the existing node).
|
|
29
|
+
*/
|
|
30
|
+
static mountBrowserLaneIcon(): void;
|
|
18
31
|
static init(cfg: FeedbackConfig): void;
|
|
32
|
+
/**
|
|
33
|
+
* Opt-in global crash handler. This wraps the existing React Native
|
|
34
|
+
* ErrorUtils handler and still calls it, so Sentry/Crashlytics/Bugsnag can
|
|
35
|
+
* remain the system of record. The SDK only uploads a crash report and,
|
|
36
|
+
* when configured, asks the agent to create a fix task.
|
|
37
|
+
*/
|
|
38
|
+
static installCrashHandler(): void;
|
|
19
39
|
/**
|
|
20
40
|
* Run agent discovery in the background.
|
|
21
41
|
* Called automatically from init() when no agentUrl is provided.
|
|
@@ -46,6 +66,29 @@ export declare class YaverFeedback {
|
|
|
46
66
|
static setAuthToken(token: string): Promise<void>;
|
|
47
67
|
/** Returns true once the SDK has a session token it can use. */
|
|
48
68
|
static isAuthed(): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* On-device App Store screenshot capture (Engine 2). Walks the routes
|
|
71
|
+
* the host hands us, screenshots each, and uploads to the agent which
|
|
72
|
+
* runs the App Store Connect backend. agentUrl / authToken / relay
|
|
73
|
+
* password default to the SDK's resolved session; the host only has to
|
|
74
|
+
* supply a navigation ref + the ordered route list.
|
|
75
|
+
*
|
|
76
|
+
* YaverFeedback.captureStoreScreenshots({
|
|
77
|
+
* app: 'sfmg',
|
|
78
|
+
* navigationRef,
|
|
79
|
+
* routes: ['/(tabs)/dashboard', '/(tabs)/messages', '/(tabs)/clients'],
|
|
80
|
+
* submit: true,
|
|
81
|
+
* })
|
|
82
|
+
*/
|
|
83
|
+
static captureStoreScreenshots(opts: Omit<CaptureStoreScreenshotsOptions, 'agentUrl' | 'authToken' | 'relayPassword'> & Partial<Pick<CaptureStoreScreenshotsOptions, 'agentUrl' | 'authToken' | 'relayPassword'>>): Promise<CaptureStoreScreenshotsResult>;
|
|
84
|
+
/**
|
|
85
|
+
* Let the mobile app / CLI kick THIS device into self-capturing. Registers
|
|
86
|
+
* a BlackBox command listener that fires captureStoreScreenshots when the
|
|
87
|
+
* agent pushes a `capture_store_shots` command (its `data` may override
|
|
88
|
+
* `submit` / `locale`). Returns an unsubscribe fn. Call once after init,
|
|
89
|
+
* passing the app's navigation ref + the routes to walk.
|
|
90
|
+
*/
|
|
91
|
+
static enableStoreShotsOnCommand(opts: Omit<CaptureStoreScreenshotsOptions, 'agentUrl' | 'authToken' | 'relayPassword'>): () => void;
|
|
49
92
|
/**
|
|
50
93
|
* Request the embedded FeedbackModal to show the login screen. Works by
|
|
51
94
|
* emitting an event the modal listens for — avoids forcing the host app
|
|
@@ -112,6 +155,46 @@ export declare class YaverFeedback {
|
|
|
112
155
|
* - All methods become active
|
|
113
156
|
*/
|
|
114
157
|
static setEnabled(value: boolean): void;
|
|
158
|
+
/**
|
|
159
|
+
* Turn shake-to-report on or off without tearing the SDK down.
|
|
160
|
+
*
|
|
161
|
+
* `trigger` is read only inside init(), so before this the only ways to
|
|
162
|
+
* stop listening for a shake were setEnabled(false) — which also kills the
|
|
163
|
+
* flight recorder and the agent command channel — or a re-init, which used
|
|
164
|
+
* to stack a duplicate command handler. Neither is what "turn the shake
|
|
165
|
+
* catcher off" should cost.
|
|
166
|
+
*
|
|
167
|
+
* Persists onto config.disableShakeGesture, so a later setEnabled(true)
|
|
168
|
+
* honours it rather than resurrecting the listener.
|
|
169
|
+
*/
|
|
170
|
+
static setShakeEnabled(value: boolean): void;
|
|
171
|
+
/** Whether the shake listener is currently armed. */
|
|
172
|
+
static isShakeEnabled(): boolean;
|
|
173
|
+
/**
|
|
174
|
+
* Bring the shake listener in line with the current config. Idempotent —
|
|
175
|
+
* safe to call whenever `enabled`, `trigger`, or `disableShakeGesture`
|
|
176
|
+
* moves.
|
|
177
|
+
*/
|
|
178
|
+
private static syncShakeDetector;
|
|
179
|
+
private static stopShakeDetector;
|
|
180
|
+
/** Cancel a pending BlackBox auto-start retry chain. */
|
|
181
|
+
private static cancelBlackBoxAutoStart;
|
|
182
|
+
/**
|
|
183
|
+
* Start BlackBox as soon as we have BOTH an agentUrl and a token, polling
|
|
184
|
+
* until then.
|
|
185
|
+
*
|
|
186
|
+
* Both conditions are mandatory and deliberate: starting without a token
|
|
187
|
+
* makes the SSE channel 401 and retry with backoff, which is the tight
|
|
188
|
+
* string-concat + JSON-parse loop that used to SIGSEGV Hermes on iOS 18.3.1
|
|
189
|
+
* during Screenshot & Fix. This only re-checks that same guard over time.
|
|
190
|
+
*
|
|
191
|
+
* Bounded at ~60s: long enough for AsyncStorage hydration plus a Convex
|
|
192
|
+
* discovery round trip on a cold, off-LAN start, short enough that a device
|
|
193
|
+
* whose user never signs in stops polling. Giving up here costs nothing —
|
|
194
|
+
* setAuthToken() and startReport() both reschedule, so signing in later
|
|
195
|
+
* still brings the channel up.
|
|
196
|
+
*/
|
|
197
|
+
private static scheduleBlackBoxAutoStart;
|
|
115
198
|
/** Returns whether the SDK is currently enabled. */
|
|
116
199
|
static isEnabled(): boolean;
|
|
117
200
|
/** Returns the current config, or null if not initialized. */
|
|
@@ -150,6 +233,21 @@ export declare class YaverFeedback {
|
|
|
150
233
|
* // wrapper, and the chain stays intact.
|
|
151
234
|
*/
|
|
152
235
|
static wrapErrorHandler(next?: ((error: Error, isFatal?: boolean) => void) | null): (error: Error, isFatal?: boolean) => void;
|
|
236
|
+
/**
|
|
237
|
+
* Upload a crash-aware feedback bundle. Apps can call this from their own
|
|
238
|
+
* error boundary/global handler. If crashReporting.autoFix is true, the SDK
|
|
239
|
+
* also triggers the agent's feedback-fix task; reload delivery still happens
|
|
240
|
+
* through the existing BlackBox command stream.
|
|
241
|
+
*/
|
|
242
|
+
static reportCrash(error: Error, opts?: {
|
|
243
|
+
isFatal?: boolean;
|
|
244
|
+
source?: 'manual' | 'global-handler';
|
|
245
|
+
metadata?: Record<string, unknown>;
|
|
246
|
+
autoFix?: boolean;
|
|
247
|
+
}): Promise<{
|
|
248
|
+
reportId?: string;
|
|
249
|
+
taskId?: string;
|
|
250
|
+
}>;
|
|
153
251
|
/**
|
|
154
252
|
* Returns the P2P client instance.
|
|
155
253
|
* Available after init if agentUrl is set, or after first successful discovery.
|