yaver-feedback-react-native 0.2.0 → 0.3.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 +132 -26
- package/app.plugin.js +2 -2
- package/package.json +1 -1
- package/src/BlackBox.ts +150 -0
- package/src/FeedbackModal.tsx +2 -2
- package/src/P2PClient.ts +26 -0
- package/src/YaverFeedback.ts +29 -0
- package/src/index.ts +1 -1
- package/src/types.ts +24 -0
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
#
|
|
1
|
+
# yaver-feedback-react-native
|
|
2
2
|
|
|
3
3
|
Visual feedback SDK for Yaver. Lets testers and developers shake their phone (or tap a floating button) to capture screenshots, record voice notes, and send bug reports directly to a Yaver agent running on a dev machine. Built for vibe coding workflows where feedback needs to flow fast.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install
|
|
8
|
+
npm install yaver-feedback-react-native
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
### Peer dependencies
|
|
@@ -26,7 +26,7 @@ npm install react-native-audio-recorder-player
|
|
|
26
26
|
## Quick Start
|
|
27
27
|
|
|
28
28
|
```tsx
|
|
29
|
-
import { YaverFeedback, BlackBox, FeedbackModal } from '
|
|
29
|
+
import { YaverFeedback, BlackBox, FeedbackModal } from 'yaver-feedback-react-native';
|
|
30
30
|
|
|
31
31
|
// Initialize once at app startup
|
|
32
32
|
YaverFeedback.init({
|
|
@@ -164,7 +164,7 @@ yaver sdk-token create --expires 24h
|
|
|
164
164
|
Continuous streaming of app events to the agent. The agent keeps a ring buffer (last 1000 events per device) and injects context into fix prompts — so the AI agent already knows what the app was doing when you ask for a fix.
|
|
165
165
|
|
|
166
166
|
```tsx
|
|
167
|
-
import { BlackBox } from '
|
|
167
|
+
import { BlackBox } from 'yaver-feedback-react-native';
|
|
168
168
|
|
|
169
169
|
// Start streaming (call after YaverFeedback.init)
|
|
170
170
|
BlackBox.start({
|
|
@@ -219,6 +219,102 @@ BlackBox.isStreaming; // check if active
|
|
|
219
219
|
| `state` | State mutations | `BlackBox.stateChange('theme toggled')` |
|
|
220
220
|
| `render` | Component render with duration | `BlackBox.render('FlatList', 32.1)` |
|
|
221
221
|
|
|
222
|
+
## Remote Reload (Agent Command Channel)
|
|
223
|
+
|
|
224
|
+
The SDK maintains a persistent SSE connection to the agent that receives commands. The primary use case: a vibe coder is away from their desk, coding on their phone via the Yaver mobile app, and wants to hot-reload the third-party app (with this SDK) running on the same or a different device.
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
Yaver Mobile App Agent Third-Party App (SDK)
|
|
228
|
+
tap "Reload"
|
|
229
|
+
───POST /dev/reload-app──► process reload
|
|
230
|
+
├─ dev server reload
|
|
231
|
+
└─ BroadcastCommand("reload")
|
|
232
|
+
───SSE push──────────────► BlackBox receives command
|
|
233
|
+
├─ onReload() callback
|
|
234
|
+
└─ DevSettings.reload()
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Works over both direct LAN and relay connections since it's standard HTTP/SSE.
|
|
238
|
+
|
|
239
|
+
### Setup
|
|
240
|
+
|
|
241
|
+
```tsx
|
|
242
|
+
import { YaverFeedback, BlackBox } from 'yaver-feedback-react-native';
|
|
243
|
+
|
|
244
|
+
YaverFeedback.init({
|
|
245
|
+
authToken: 'your-sdk-token',
|
|
246
|
+
agentUrl: 'http://192.168.1.10:18080',
|
|
247
|
+
|
|
248
|
+
// Called when the vibe coder triggers reload from Yaver mobile app
|
|
249
|
+
onReload: () => {
|
|
250
|
+
console.log('Remote reload triggered!');
|
|
251
|
+
// Default behavior if omitted: DevSettings.reload() in dev mode
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
// Called when agent pushes a new native bundle
|
|
255
|
+
onReloadBundle: (bundleUrl, assetsUrl) => {
|
|
256
|
+
console.log('New bundle available at:', bundleUrl);
|
|
257
|
+
// Default: no-op. Implement custom bundle loading if needed.
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Start BlackBox — this also connects the command channel
|
|
262
|
+
BlackBox.start({ appName: 'AcmeStore' });
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Custom Command Handlers
|
|
266
|
+
|
|
267
|
+
For advanced use cases, register handlers directly on BlackBox:
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
// Listen for any command from the agent
|
|
271
|
+
const unsubscribe = BlackBox.onCommand((cmd) => {
|
|
272
|
+
switch (cmd.command) {
|
|
273
|
+
case 'reload':
|
|
274
|
+
// Hot reload from dev server
|
|
275
|
+
DevSettings.reload();
|
|
276
|
+
break;
|
|
277
|
+
case 'reload_bundle':
|
|
278
|
+
// Fetch new native bundle
|
|
279
|
+
const { bundleUrl, assetsUrl } = cmd.data;
|
|
280
|
+
loadNewBundle(bundleUrl, assetsUrl);
|
|
281
|
+
break;
|
|
282
|
+
default:
|
|
283
|
+
console.log('Unknown command:', cmd.command);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// Check connection status
|
|
288
|
+
BlackBox.isCommandChannelConnected; // boolean
|
|
289
|
+
|
|
290
|
+
// Clean up
|
|
291
|
+
unsubscribe();
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Triggering Reload from P2PClient
|
|
295
|
+
|
|
296
|
+
The SDK can also trigger reload programmatically (e.g., from a "Reload" button in the Feedback modal):
|
|
297
|
+
|
|
298
|
+
```tsx
|
|
299
|
+
import { P2PClient } from 'yaver-feedback-react-native';
|
|
300
|
+
|
|
301
|
+
const client = new P2PClient('http://192.168.1.10:18080', 'your-token');
|
|
302
|
+
|
|
303
|
+
// Hot reload (dev server restart)
|
|
304
|
+
await client.reloadApp('dev');
|
|
305
|
+
|
|
306
|
+
// Rebuild native bundle + push to all connected SDK devices
|
|
307
|
+
await client.reloadApp('bundle');
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### How It Works
|
|
311
|
+
|
|
312
|
+
1. `BlackBox.start()` connects to `/blackbox/command-stream` via SSE
|
|
313
|
+
2. The connection auto-reconnects on disconnect (5s backoff)
|
|
314
|
+
3. When the agent receives `POST /dev/reload` or `POST /dev/reload-app`, it broadcasts a command to all connected SDK sessions
|
|
315
|
+
4. The SDK invokes `onReload` / `onReloadBundle` callbacks, or falls back to `DevSettings.reload()` in dev mode
|
|
316
|
+
5. Events are still sent via batch POST to `/blackbox/events` (unchanged)
|
|
317
|
+
|
|
222
318
|
### Resilience
|
|
223
319
|
|
|
224
320
|
- Failed flushes re-add events to the buffer (capped at 2x maxBufferSize)
|
|
@@ -258,7 +354,7 @@ YaverFeedback.init({
|
|
|
258
354
|
### Manual discovery
|
|
259
355
|
|
|
260
356
|
```typescript
|
|
261
|
-
import { YaverDiscovery } from '
|
|
357
|
+
import { YaverDiscovery } from 'yaver-feedback-react-native';
|
|
262
358
|
|
|
263
359
|
// Full discovery (Convex → stored → LAN scan)
|
|
264
360
|
const result = await YaverDiscovery.discover({
|
|
@@ -281,7 +377,7 @@ await YaverDiscovery.clear();
|
|
|
281
377
|
A full-screen UI for discovering and connecting to a Yaver agent. Shows connection status, URL/token inputs, auto-discover button, and a Start/Stop testing toggle with recording timer.
|
|
282
378
|
|
|
283
379
|
```tsx
|
|
284
|
-
import { YaverConnectionScreen } from '
|
|
380
|
+
import { YaverConnectionScreen } from 'yaver-feedback-react-native';
|
|
285
381
|
|
|
286
382
|
function App() {
|
|
287
383
|
return (
|
|
@@ -445,6 +541,10 @@ YaverFeedback.init({
|
|
|
445
541
|
feedbackMode: 'batch', // 'live' | 'narrated' | 'batch' (default: 'batch')
|
|
446
542
|
agentCommentaryLevel: 0, // 0-10 (default: 0, only relevant in live mode)
|
|
447
543
|
maxCapturedErrors: 5, // Error ring buffer size (default: 5)
|
|
544
|
+
|
|
545
|
+
// Remote reload callbacks (from Yaver mobile app or another SDK device)
|
|
546
|
+
onReload: () => { ... }, // Called on hot reload command (default: DevSettings.reload())
|
|
547
|
+
onReloadBundle: (url, assets) => { ... },// Called on native bundle rebuild command
|
|
448
548
|
});
|
|
449
549
|
```
|
|
450
550
|
|
|
@@ -463,7 +563,7 @@ YaverFeedback.init({ authToken, trigger: 'shake' });
|
|
|
463
563
|
A small draggable "Y" button overlays the app. Tap to open the feedback modal.
|
|
464
564
|
|
|
465
565
|
```tsx
|
|
466
|
-
import { FloatingButton, FeedbackModal, YaverFeedback } from '
|
|
566
|
+
import { FloatingButton, FeedbackModal, YaverFeedback } from 'yaver-feedback-react-native';
|
|
467
567
|
|
|
468
568
|
function App() {
|
|
469
569
|
return (
|
|
@@ -481,7 +581,7 @@ function App() {
|
|
|
481
581
|
Trigger feedback collection programmatically from anywhere in your app.
|
|
482
582
|
|
|
483
583
|
```typescript
|
|
484
|
-
import { YaverFeedback } from '
|
|
584
|
+
import { YaverFeedback } from 'yaver-feedback-react-native';
|
|
485
585
|
|
|
486
586
|
// In a button handler, debug menu, etc.
|
|
487
587
|
YaverFeedback.startReport();
|
|
@@ -492,7 +592,7 @@ YaverFeedback.startReport();
|
|
|
492
592
|
For direct communication with the Yaver agent beyond feedback:
|
|
493
593
|
|
|
494
594
|
```typescript
|
|
495
|
-
import { P2PClient } from '
|
|
595
|
+
import { P2PClient } from 'yaver-feedback-react-native';
|
|
496
596
|
|
|
497
597
|
const client = new P2PClient('http://192.168.1.10:18080', 'your-token');
|
|
498
598
|
|
|
@@ -608,6 +708,8 @@ YaverFeedback.setEnabled(false);
|
|
|
608
708
|
| `wrapConsole()` | Intercept console.log/warn/error |
|
|
609
709
|
| `unwrapConsole()` | Restore original console methods |
|
|
610
710
|
| `wrapErrorHandler(next?)` | Pass-through error handler with real-time streaming |
|
|
711
|
+
| `onCommand(handler)` | Register handler for agent commands (reload, etc.). Returns unsubscribe fn |
|
|
712
|
+
| `isCommandChannelConnected` | Whether the SSE command channel is connected (getter) |
|
|
611
713
|
|
|
612
714
|
### P2PClient
|
|
613
715
|
|
|
@@ -622,6 +724,7 @@ YaverFeedback.setEnabled(false);
|
|
|
622
724
|
| `getArtifactUrl(buildId)` | Get download URL for a build artifact |
|
|
623
725
|
| `voiceStatus()` | Get voice capability info |
|
|
624
726
|
| `transcribeVoice(audioUri)` | Send audio for transcription |
|
|
727
|
+
| `reloadApp(mode)` | Trigger remote reload: `'dev'` (hot reload) or `'bundle'` (rebuild + push) |
|
|
625
728
|
| `startTestSession()` | Start autonomous test session |
|
|
626
729
|
| `stopTestSession()` | Stop test session |
|
|
627
730
|
| `getTestSession()` | Get test session status + fixes |
|
|
@@ -655,8 +758,10 @@ The SDK communicates with these agent HTTP endpoints:
|
|
|
655
758
|
| `/feedback` | POST | Upload feedback bundle (multipart) |
|
|
656
759
|
| `/feedback/stream` | POST | Stream live feedback events |
|
|
657
760
|
| `/blackbox/events` | POST | Batch stream Black Box events |
|
|
761
|
+
| `/blackbox/command-stream` | GET | SSE command channel (agent pushes reload, etc.) |
|
|
658
762
|
| `/blackbox/subscribe` | GET | SSE live log stream |
|
|
659
763
|
| `/blackbox/context` | GET | Get generated prompt context |
|
|
764
|
+
| `/dev/reload-app` | POST | Trigger remote reload of SDK-connected apps |
|
|
660
765
|
| `/builds` | GET/POST | List or start builds |
|
|
661
766
|
| `/voice/status` | GET | Voice capability info |
|
|
662
767
|
| `/voice/transcribe` | POST | Send audio for transcription |
|
|
@@ -667,23 +772,24 @@ The SDK communicates with these agent HTTP endpoints:
|
|
|
667
772
|
## Architecture
|
|
668
773
|
|
|
669
774
|
```
|
|
670
|
-
┌──────────────────┐ HTTP (Bearer auth) ┌──────────────────┐
|
|
671
|
-
│ Your App │────────────────────────────►│ Yaver Agent │
|
|
672
|
-
│ + Feedback SDK │ feedback, blackbox events │ (Go CLI) │
|
|
673
|
-
│ + BlackBox │ screenshots, voice, video │ on your machine │
|
|
674
|
-
│ │◄────────────────────────────│ │
|
|
675
|
-
│ │
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
│
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
│
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
775
|
+
┌──────────────────┐ HTTP (Bearer auth) ┌──────────────────┐ HTTP/SSE ┌──────────────────┐
|
|
776
|
+
│ Your App │────────────────────────────►│ Yaver Agent │◄────────────────── │ Yaver Mobile │
|
|
777
|
+
│ + Feedback SDK │ feedback, blackbox events │ (Go CLI) │ tasks, reload │ App (phone) │
|
|
778
|
+
│ + BlackBox │ screenshots, voice, video │ on your machine │ commands │ vibe coding │
|
|
779
|
+
│ │◄────────────────────────────│ │ │ │
|
|
780
|
+
│ │ reload commands (SSE), │ runs AI agent │ │ │
|
|
781
|
+
│ │ fixes, build status, voice │ │ │ │
|
|
782
|
+
└──────────────────┘ └──────────────────┘ └──────────────────┘
|
|
783
|
+
│ │ │
|
|
784
|
+
│ Auth only │ Auth only │
|
|
785
|
+
▼ ▼ ▼
|
|
786
|
+
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
787
|
+
│ Convex Backend │
|
|
788
|
+
│ Token validation + device registry (no task data stored) │
|
|
789
|
+
└──────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
All feedback data flows P2P between your app and the agent. The Yaver mobile app can trigger remote reloads that reach your app via the agent's command channel. Convex handles only auth and device discovery.
|
|
687
793
|
|
|
688
794
|
## License
|
|
689
795
|
|
package/app.plugin.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Expo config plugin for
|
|
2
|
+
* Expo config plugin for yaver-feedback-react-native.
|
|
3
3
|
*
|
|
4
4
|
* Adds required native permissions for the feedback SDK:
|
|
5
5
|
* - iOS: Camera + Microphone usage descriptions
|
|
6
6
|
* - Android: CAMERA + RECORD_AUDIO permissions
|
|
7
7
|
*
|
|
8
8
|
* Usage in app.json:
|
|
9
|
-
* { "expo": { "plugins": ["
|
|
9
|
+
* { "expo": { "plugins": ["yaver-feedback-react-native"] } }
|
|
10
10
|
*/
|
|
11
11
|
const {
|
|
12
12
|
withInfoPlist,
|
package/package.json
CHANGED
package/src/BlackBox.ts
CHANGED
|
@@ -49,6 +49,15 @@ export interface BlackBoxConfig {
|
|
|
49
49
|
* - Use `BlackBox.wrapConsole()` to intercept console.log/warn/error
|
|
50
50
|
* (only if you explicitly opt in — no auto-hooking)
|
|
51
51
|
*/
|
|
52
|
+
/** Command received from the agent via the SSE command channel. */
|
|
53
|
+
export interface BlackBoxCommand {
|
|
54
|
+
command: string; // "reload", "reload_bundle"
|
|
55
|
+
data?: Record<string, unknown>; // e.g. { bundleUrl: "/dev/native-bundle" }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Callback type for handling agent commands. */
|
|
59
|
+
export type CommandHandler = (cmd: BlackBoxCommand) => void;
|
|
60
|
+
|
|
52
61
|
export class BlackBox {
|
|
53
62
|
private static baseUrl: string | null = null;
|
|
54
63
|
private static authToken: string | null = null;
|
|
@@ -65,6 +74,12 @@ export class BlackBox {
|
|
|
65
74
|
error: typeof console.error;
|
|
66
75
|
} | null = null;
|
|
67
76
|
|
|
77
|
+
// SSE command channel — persistent connection to /blackbox/stream
|
|
78
|
+
private static sseAbortController: AbortController | null = null;
|
|
79
|
+
private static sseReconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
|
80
|
+
private static commandHandlers: CommandHandler[] = [];
|
|
81
|
+
private static sseConnected = false;
|
|
82
|
+
|
|
68
83
|
/**
|
|
69
84
|
* Start the black box stream. Call after `YaverFeedback.init()`.
|
|
70
85
|
*
|
|
@@ -97,6 +112,9 @@ export class BlackBox {
|
|
|
97
112
|
message: 'Black box streaming started',
|
|
98
113
|
timestamp: Date.now(),
|
|
99
114
|
});
|
|
115
|
+
|
|
116
|
+
// Connect SSE command channel for receiving agent commands (reload, etc.)
|
|
117
|
+
BlackBox.connectSSE();
|
|
100
118
|
}
|
|
101
119
|
|
|
102
120
|
/** Stop the black box stream and flush remaining events. */
|
|
@@ -112,6 +130,7 @@ export class BlackBox {
|
|
|
112
130
|
clearInterval(BlackBox.flushTimer);
|
|
113
131
|
BlackBox.flushTimer = null;
|
|
114
132
|
}
|
|
133
|
+
BlackBox.disconnectSSE();
|
|
115
134
|
BlackBox.started = false;
|
|
116
135
|
}
|
|
117
136
|
|
|
@@ -273,6 +292,137 @@ export class BlackBox {
|
|
|
273
292
|
};
|
|
274
293
|
}
|
|
275
294
|
|
|
295
|
+
// ─── Command channel (agent → SDK) ──────────────────────────────
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Register a handler for commands pushed by the agent.
|
|
299
|
+
* The primary use case is receiving "reload" commands when the vibe coder
|
|
300
|
+
* triggers a reload from the Yaver mobile app.
|
|
301
|
+
*
|
|
302
|
+
* @example
|
|
303
|
+
* BlackBox.onCommand((cmd) => {
|
|
304
|
+
* if (cmd.command === 'reload') {
|
|
305
|
+
* DevSettings.reload(); // or Updates.reloadAsync()
|
|
306
|
+
* }
|
|
307
|
+
* });
|
|
308
|
+
*/
|
|
309
|
+
static onCommand(handler: CommandHandler): () => void {
|
|
310
|
+
BlackBox.commandHandlers.push(handler);
|
|
311
|
+
// Return unsubscribe function
|
|
312
|
+
return () => {
|
|
313
|
+
BlackBox.commandHandlers = BlackBox.commandHandlers.filter(h => h !== handler);
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/** Whether the SSE command channel is connected. */
|
|
318
|
+
static get isCommandChannelConnected(): boolean {
|
|
319
|
+
return BlackBox.sseConnected;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Connect to the agent's /blackbox/stream SSE endpoint.
|
|
324
|
+
* This persistent connection allows the agent to push commands (reload, etc.)
|
|
325
|
+
* back to the SDK. Events are still sent via batch POST /blackbox/events.
|
|
326
|
+
*/
|
|
327
|
+
private static async connectSSE(): Promise<void> {
|
|
328
|
+
if (!BlackBox.baseUrl || !BlackBox.authToken) return;
|
|
329
|
+
|
|
330
|
+
// Disconnect any existing connection
|
|
331
|
+
BlackBox.disconnectSSE();
|
|
332
|
+
|
|
333
|
+
const controller = new AbortController();
|
|
334
|
+
BlackBox.sseAbortController = controller;
|
|
335
|
+
|
|
336
|
+
const url = `${BlackBox.baseUrl}/blackbox/command-stream?device=${encodeURIComponent(BlackBox.deviceId)}`;
|
|
337
|
+
|
|
338
|
+
try {
|
|
339
|
+
const response = await fetch(url, {
|
|
340
|
+
method: 'GET',
|
|
341
|
+
headers: {
|
|
342
|
+
Authorization: `Bearer ${BlackBox.authToken}`,
|
|
343
|
+
'X-Device-ID': BlackBox.deviceId,
|
|
344
|
+
'X-Platform': Platform.OS,
|
|
345
|
+
'X-App-Name': BlackBox.appName,
|
|
346
|
+
Accept: 'text/event-stream',
|
|
347
|
+
},
|
|
348
|
+
// @ts-ignore — React Native supports signal on fetch
|
|
349
|
+
signal: controller.signal,
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
if (!response.ok || !response.body) {
|
|
353
|
+
BlackBox.scheduleSSEReconnect();
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
BlackBox.sseConnected = true;
|
|
358
|
+
|
|
359
|
+
// Read SSE stream
|
|
360
|
+
const reader = response.body.getReader();
|
|
361
|
+
const decoder = new TextDecoder();
|
|
362
|
+
let buffer = '';
|
|
363
|
+
|
|
364
|
+
while (true) {
|
|
365
|
+
const { done, value } = await reader.read();
|
|
366
|
+
if (done) break;
|
|
367
|
+
|
|
368
|
+
buffer += decoder.decode(value, { stream: true });
|
|
369
|
+
const lines = buffer.split('\n');
|
|
370
|
+
buffer = lines.pop() ?? '';
|
|
371
|
+
|
|
372
|
+
for (const line of lines) {
|
|
373
|
+
if (!line.startsWith('data: ')) continue;
|
|
374
|
+
try {
|
|
375
|
+
const msg = JSON.parse(line.slice(6));
|
|
376
|
+
if (msg.type === 'command' && msg.command) {
|
|
377
|
+
const cmd: BlackBoxCommand = msg.command;
|
|
378
|
+
for (const handler of BlackBox.commandHandlers) {
|
|
379
|
+
try {
|
|
380
|
+
handler(cmd);
|
|
381
|
+
} catch {
|
|
382
|
+
// Handler error — don't break the loop
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
} catch {
|
|
387
|
+
// Malformed SSE data — skip
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
} catch (err: unknown) {
|
|
392
|
+
// AbortError is expected on disconnect
|
|
393
|
+
if (err instanceof Error && err.name === 'AbortError') return;
|
|
394
|
+
} finally {
|
|
395
|
+
BlackBox.sseConnected = false;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Reconnect if still running
|
|
399
|
+
if (BlackBox.started) {
|
|
400
|
+
BlackBox.scheduleSSEReconnect();
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
private static disconnectSSE(): void {
|
|
405
|
+
if (BlackBox.sseAbortController) {
|
|
406
|
+
BlackBox.sseAbortController.abort();
|
|
407
|
+
BlackBox.sseAbortController = null;
|
|
408
|
+
}
|
|
409
|
+
if (BlackBox.sseReconnectTimer) {
|
|
410
|
+
clearTimeout(BlackBox.sseReconnectTimer);
|
|
411
|
+
BlackBox.sseReconnectTimer = null;
|
|
412
|
+
}
|
|
413
|
+
BlackBox.sseConnected = false;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
private static scheduleSSEReconnect(): void {
|
|
417
|
+
if (!BlackBox.started) return;
|
|
418
|
+
if (BlackBox.sseReconnectTimer) return;
|
|
419
|
+
// Reconnect after 5s
|
|
420
|
+
BlackBox.sseReconnectTimer = setTimeout(() => {
|
|
421
|
+
BlackBox.sseReconnectTimer = null;
|
|
422
|
+
if (BlackBox.started) BlackBox.connectSSE();
|
|
423
|
+
}, 5000);
|
|
424
|
+
}
|
|
425
|
+
|
|
276
426
|
// ─── Internal ────────────────────────────────────────────────────
|
|
277
427
|
|
|
278
428
|
private static push(event: BlackBoxEvent): void {
|
package/src/FeedbackModal.tsx
CHANGED
|
@@ -224,13 +224,13 @@ export const FeedbackModal: React.FC = () => {
|
|
|
224
224
|
|
|
225
225
|
setIsReloading(true);
|
|
226
226
|
try {
|
|
227
|
-
const response = await fetch(`${config.agentUrl.replace(/\/$/, '')}/
|
|
227
|
+
const response = await fetch(`${config.agentUrl.replace(/\/$/, '')}/dev/reload-app`, {
|
|
228
228
|
method: 'POST',
|
|
229
229
|
headers: {
|
|
230
230
|
Authorization: `Bearer ${config.authToken}`,
|
|
231
231
|
'Content-Type': 'application/json',
|
|
232
232
|
},
|
|
233
|
-
body: JSON.stringify({
|
|
233
|
+
body: JSON.stringify({ mode: 'dev' }),
|
|
234
234
|
});
|
|
235
235
|
if (response.ok) {
|
|
236
236
|
BlackBox.lifecycle('Hot reload triggered from feedback SDK');
|
package/src/P2PClient.ts
CHANGED
|
@@ -214,6 +214,32 @@ export class P2PClient {
|
|
|
214
214
|
};
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Trigger a reload of the third-party app.
|
|
219
|
+
* In dev mode, this tells the dev server to hot-reload.
|
|
220
|
+
* In bundle mode, this rebuilds the native bundle and pushes it.
|
|
221
|
+
* The reload command is also broadcast to all connected SDK devices
|
|
222
|
+
* via the BlackBox command channel.
|
|
223
|
+
* @param mode - "dev" for hot reload, "bundle" for native bundle rebuild
|
|
224
|
+
*/
|
|
225
|
+
async reloadApp(mode: 'dev' | 'bundle' = 'dev'): Promise<{ ok: boolean }> {
|
|
226
|
+
const response = await fetch(`${this.baseUrl}/dev/reload-app`, {
|
|
227
|
+
method: 'POST',
|
|
228
|
+
headers: {
|
|
229
|
+
Authorization: `Bearer ${this.authToken}`,
|
|
230
|
+
'Content-Type': 'application/json',
|
|
231
|
+
},
|
|
232
|
+
body: JSON.stringify({ mode }),
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
if (!response.ok) {
|
|
236
|
+
const text = await response.text().catch(() => '');
|
|
237
|
+
throw new Error(`[P2PClient] Reload app failed (${response.status}): ${text}`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return response.json();
|
|
241
|
+
}
|
|
242
|
+
|
|
217
243
|
/** Get the download URL for a build artifact. */
|
|
218
244
|
getArtifactUrl(buildId: string): string {
|
|
219
245
|
return `${this.baseUrl}/builds/${buildId}/artifact`;
|
package/src/YaverFeedback.ts
CHANGED
|
@@ -71,6 +71,35 @@ export class YaverFeedback {
|
|
|
71
71
|
});
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
// Wire up BlackBox command handlers for reload signals from the agent.
|
|
75
|
+
// This enables the vibe coder to trigger reload from the Yaver mobile app
|
|
76
|
+
// and have the third-party app (with this SDK) automatically reload.
|
|
77
|
+
if (enabled) {
|
|
78
|
+
BlackBox.onCommand((cmd) => {
|
|
79
|
+
if (cmd.command === 'reload') {
|
|
80
|
+
if (cfg.onReload) {
|
|
81
|
+
cfg.onReload();
|
|
82
|
+
} else {
|
|
83
|
+
// Default: try DevSettings.reload() in dev mode
|
|
84
|
+
try {
|
|
85
|
+
const { DevSettings } = require('react-native');
|
|
86
|
+
if (typeof DevSettings?.reload === 'function') {
|
|
87
|
+
DevSettings.reload();
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
// Not in dev mode or DevSettings unavailable
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
} else if (cmd.command === 'reload_bundle' && cmd.data) {
|
|
94
|
+
const bundleUrl = cmd.data.bundleUrl as string;
|
|
95
|
+
const assetsUrl = cmd.data.assetsUrl as string | undefined;
|
|
96
|
+
if (cfg.onReloadBundle) {
|
|
97
|
+
cfg.onReloadBundle(bundleUrl, assetsUrl);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
74
103
|
// NOTE: We intentionally do NOT hook ErrorUtils.setGlobalHandler().
|
|
75
104
|
// Sentry, Crashlytics, Bugsnag, and other tools all compete for that
|
|
76
105
|
// single slot. Hijacking it would break whichever tool the developer
|
package/src/index.ts
CHANGED
|
@@ -49,6 +49,6 @@ export type {
|
|
|
49
49
|
TestFix,
|
|
50
50
|
TestSession,
|
|
51
51
|
} from './types';
|
|
52
|
-
export type { BlackBoxEvent, BlackBoxConfig } from './BlackBox';
|
|
52
|
+
export type { BlackBoxEvent, BlackBoxConfig, BlackBoxCommand, CommandHandler } from './BlackBox';
|
|
53
53
|
export type { DiscoveryResult } from './Discovery';
|
|
54
54
|
export type { FeedbackEvent } from './P2PClient';
|
package/src/types.ts
CHANGED
|
@@ -102,6 +102,30 @@ export interface FeedbackConfig {
|
|
|
102
102
|
* panelBackgroundColor: '#333333' // neutral dark gray
|
|
103
103
|
*/
|
|
104
104
|
panelBackgroundColor?: string;
|
|
105
|
+
/**
|
|
106
|
+
* Callback invoked when the agent pushes a reload command.
|
|
107
|
+
* This happens when the vibe coder triggers a reload from the Yaver mobile app
|
|
108
|
+
* or from another connected device.
|
|
109
|
+
*
|
|
110
|
+
* If not provided, the SDK will attempt `DevSettings.reload()` in dev mode
|
|
111
|
+
* or ignore the command in production.
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* onReload: () => {
|
|
115
|
+
* // Custom reload logic, e.g. re-fetch bundle from agent
|
|
116
|
+
* Updates.reloadAsync();
|
|
117
|
+
* }
|
|
118
|
+
*/
|
|
119
|
+
onReload?: () => void;
|
|
120
|
+
/**
|
|
121
|
+
* Callback invoked when the agent pushes a reload_bundle command with a new
|
|
122
|
+
* native bundle URL. The SDK passes the bundle URL and assets URL so the app
|
|
123
|
+
* can fetch and load the new bundle.
|
|
124
|
+
*
|
|
125
|
+
* If not provided, the SDK will attempt to POST the bundle to localhost:8347
|
|
126
|
+
* (Yaver's on-device HTTP server) for native container reload.
|
|
127
|
+
*/
|
|
128
|
+
onReloadBundle?: (bundleUrl: string, assetsUrl?: string) => void;
|
|
105
129
|
/**
|
|
106
130
|
* TLS certificate fingerprint (SHA256) for HTTPS on LAN.
|
|
107
131
|
* When set, the SDK prefers HTTPS connections and verifies the agent's
|