yaver-feedback-react-native 0.2.0 → 0.4.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/android/src/main/java/io/yaver/feedback/YaverHotReloadModule.java +188 -0
- package/android/src/main/java/io/yaver/feedback/YaverHotReloadPackage.java +33 -0
- package/app.plugin.js +265 -7
- package/ios/YaverHotReload.m +16 -0
- package/ios/YaverHotReload.swift +112 -0
- package/package.json +3 -1
- package/src/BlackBox.ts +150 -0
- package/src/Discovery.ts +125 -5
- package/src/FeedbackModal.tsx +2 -2
- package/src/FloatingButton.tsx +19 -7
- package/src/P2PClient.ts +26 -0
- package/src/YaverFeedback.ts +130 -0
- package/src/index.ts +1 -1
- package/src/types.ts +24 -0
package/src/Discovery.ts
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
// AsyncStorage is an optional peer dep — gracefully degrade if missing
|
|
2
|
+
let AsyncStorage: {
|
|
3
|
+
getItem: (key: string) => Promise<string | null>;
|
|
4
|
+
setItem: (key: string, value: string) => Promise<void>;
|
|
5
|
+
removeItem: (key: string) => Promise<void>;
|
|
6
|
+
} | null = null;
|
|
7
|
+
try {
|
|
8
|
+
AsyncStorage = require('@react-native-async-storage/async-storage').default;
|
|
9
|
+
} catch {
|
|
10
|
+
// Not installed — discovery caching disabled, auto-discovery still works
|
|
11
|
+
}
|
|
2
12
|
|
|
3
13
|
const STORAGE_KEY = 'yaver_feedback_agent';
|
|
4
14
|
const DEFAULT_PORT = 18080;
|
|
@@ -123,9 +133,116 @@ export class YaverDiscovery {
|
|
|
123
133
|
|
|
124
134
|
if (!target?.quicHost) return null;
|
|
125
135
|
|
|
136
|
+
// Try direct connection first (same LAN)
|
|
126
137
|
const port = target.httpPort ?? DEFAULT_PORT;
|
|
127
|
-
const
|
|
128
|
-
|
|
138
|
+
const directUrl = `http://${target.quicHost}:${port}`;
|
|
139
|
+
const directResult = await YaverDiscovery.probe(directUrl);
|
|
140
|
+
if (directResult) return directResult;
|
|
141
|
+
|
|
142
|
+
// Direct connection failed — try via HTTP relay (off-LAN)
|
|
143
|
+
const relayResult = await YaverDiscovery.discoverViaRelay(
|
|
144
|
+
base, authToken, target.deviceId,
|
|
145
|
+
);
|
|
146
|
+
if (relayResult) return relayResult;
|
|
147
|
+
|
|
148
|
+
return null;
|
|
149
|
+
} catch {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Discover agent via relay HTTP proxy.
|
|
156
|
+
* Fetches relay server list from Convex platformConfig, then probes
|
|
157
|
+
* `{relayHttpUrl}/d/{deviceId}/health` to reach the agent over the internet.
|
|
158
|
+
*/
|
|
159
|
+
static async discoverViaRelay(
|
|
160
|
+
convexUrl: string,
|
|
161
|
+
authToken: string,
|
|
162
|
+
deviceId: string,
|
|
163
|
+
): Promise<DiscoveryResult | null> {
|
|
164
|
+
try {
|
|
165
|
+
// Fetch relay server list from user settings first, then platform config
|
|
166
|
+
const settingsRes = await fetch(`${convexUrl}/auth/validate`, {
|
|
167
|
+
headers: { Authorization: `Bearer ${authToken}` },
|
|
168
|
+
});
|
|
169
|
+
let relayUrl: string | undefined;
|
|
170
|
+
let relayPassword: string | undefined;
|
|
171
|
+
|
|
172
|
+
if (settingsRes.ok) {
|
|
173
|
+
const settingsData = await settingsRes.json();
|
|
174
|
+
relayUrl = settingsData.relayUrl;
|
|
175
|
+
relayPassword = settingsData.relayPassword;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// If no user-level relay, fetch platform relay servers
|
|
179
|
+
if (!relayUrl) {
|
|
180
|
+
const configRes = await fetch(`${convexUrl}/platform-config?key=relay_servers`);
|
|
181
|
+
if (configRes.ok) {
|
|
182
|
+
const configData = await configRes.json();
|
|
183
|
+
const servers = typeof configData.value === 'string'
|
|
184
|
+
? JSON.parse(configData.value)
|
|
185
|
+
: configData.value;
|
|
186
|
+
if (Array.isArray(servers) && servers.length > 0) {
|
|
187
|
+
// Pick the first (highest priority) relay with an httpUrl
|
|
188
|
+
const relay = servers.find((s: { httpUrl?: string }) => s.httpUrl);
|
|
189
|
+
if (relay) {
|
|
190
|
+
relayUrl = relay.httpUrl;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (!relayUrl) return null;
|
|
197
|
+
|
|
198
|
+
// Probe agent through relay: {relayHttpUrl}/d/{deviceId}/health
|
|
199
|
+
const relayBase = `${relayUrl.replace(/\/$/, '')}/d/${deviceId}`;
|
|
200
|
+
const result = await YaverDiscovery.probeWithHeaders(relayBase, {
|
|
201
|
+
'X-Relay-Password': relayPassword || '',
|
|
202
|
+
});
|
|
203
|
+
return result;
|
|
204
|
+
} catch {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Probe with extra headers (e.g. relay password).
|
|
211
|
+
*/
|
|
212
|
+
static async probeWithHeaders(
|
|
213
|
+
url: string,
|
|
214
|
+
headers: Record<string, string>,
|
|
215
|
+
): Promise<DiscoveryResult | null> {
|
|
216
|
+
const base = url.replace(/\/$/, '');
|
|
217
|
+
const start = Date.now();
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
const controller = new AbortController();
|
|
221
|
+
const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS + 3000); // relay adds latency
|
|
222
|
+
|
|
223
|
+
const response = await fetch(`${base}/health`, {
|
|
224
|
+
method: 'GET',
|
|
225
|
+
headers,
|
|
226
|
+
signal: controller.signal,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
clearTimeout(timeoutId);
|
|
230
|
+
|
|
231
|
+
if (!response.ok) return null;
|
|
232
|
+
|
|
233
|
+
const latency = Date.now() - start;
|
|
234
|
+
let hostname = 'Unknown';
|
|
235
|
+
let version = 'unknown';
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
const data = await response.json();
|
|
239
|
+
hostname = data.hostname ?? data.name ?? 'Unknown';
|
|
240
|
+
version = data.version ?? 'unknown';
|
|
241
|
+
} catch {
|
|
242
|
+
// Health endpoint might return plain text
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return { url: base, hostname, version, latency };
|
|
129
246
|
} catch {
|
|
130
247
|
return null;
|
|
131
248
|
}
|
|
@@ -185,8 +302,9 @@ export class YaverDiscovery {
|
|
|
185
302
|
return result;
|
|
186
303
|
}
|
|
187
304
|
|
|
188
|
-
/** Get the cached agent connection from
|
|
305
|
+
/** Get the cached agent connection from storage. */
|
|
189
306
|
static async getStored(): Promise<{ url: string; hostname: string } | null> {
|
|
307
|
+
if (!AsyncStorage) return null;
|
|
190
308
|
try {
|
|
191
309
|
const raw = await AsyncStorage.getItem(STORAGE_KEY);
|
|
192
310
|
if (!raw) return null;
|
|
@@ -200,8 +318,9 @@ export class YaverDiscovery {
|
|
|
200
318
|
}
|
|
201
319
|
}
|
|
202
320
|
|
|
203
|
-
/** Store a successful discovery result
|
|
321
|
+
/** Store a successful discovery result. */
|
|
204
322
|
static async store(result: DiscoveryResult): Promise<void> {
|
|
323
|
+
if (!AsyncStorage) return;
|
|
205
324
|
try {
|
|
206
325
|
await AsyncStorage.setItem(
|
|
207
326
|
STORAGE_KEY,
|
|
@@ -214,6 +333,7 @@ export class YaverDiscovery {
|
|
|
214
333
|
|
|
215
334
|
/** Clear the stored agent connection. */
|
|
216
335
|
static async clear(): Promise<void> {
|
|
336
|
+
if (!AsyncStorage) return;
|
|
217
337
|
try {
|
|
218
338
|
await AsyncStorage.removeItem(STORAGE_KEY);
|
|
219
339
|
} catch {
|
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/FloatingButton.tsx
CHANGED
|
@@ -119,9 +119,13 @@ export const FloatingButton: React.FC<FloatingButtonProps> = ({
|
|
|
119
119
|
const testPollRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
120
120
|
const outputScrollRef = useRef<ScrollView>(null);
|
|
121
121
|
|
|
122
|
-
// Resolve agent URL and token
|
|
122
|
+
// Resolve agent URL and token — re-read from config each render
|
|
123
|
+
// because discoverAgent() may set agentUrl asynchronously after init.
|
|
124
|
+
const [resolvedAgentUrl, setResolvedAgentUrl] = useState<string | undefined>(
|
|
125
|
+
agentUrlProp || YaverFeedback.getConfig()?.agentUrl,
|
|
126
|
+
);
|
|
123
127
|
const config = YaverFeedback.getConfig();
|
|
124
|
-
const agentUrl =
|
|
128
|
+
const agentUrl = resolvedAgentUrl;
|
|
125
129
|
const authToken = authTokenProp || config?.authToken;
|
|
126
130
|
const panelBg = panelBackgroundColor || config?.panelBackgroundColor || DEFAULT_PANEL_BG;
|
|
127
131
|
|
|
@@ -129,19 +133,27 @@ export const FloatingButton: React.FC<FloatingButtonProps> = ({
|
|
|
129
133
|
setOutput((prev) => [...prev.slice(-20), line]);
|
|
130
134
|
}, []);
|
|
131
135
|
|
|
132
|
-
// Connection health polling
|
|
136
|
+
// Connection health polling — also picks up agentUrl from config when
|
|
137
|
+
// it becomes available after background discovery completes.
|
|
133
138
|
useEffect(() => {
|
|
134
|
-
if (!healthCheckInterval
|
|
139
|
+
if (!healthCheckInterval) return;
|
|
135
140
|
|
|
136
141
|
const check = async () => {
|
|
142
|
+
// Re-read config in case discoverAgent() resolved since last check
|
|
143
|
+
const latestUrl = agentUrlProp || YaverFeedback.getConfig()?.agentUrl;
|
|
144
|
+
if (latestUrl && latestUrl !== resolvedAgentUrl) {
|
|
145
|
+
setResolvedAgentUrl(latestUrl);
|
|
146
|
+
}
|
|
147
|
+
if (!latestUrl) return;
|
|
148
|
+
|
|
137
149
|
try {
|
|
138
150
|
const client = YaverFeedback.getP2PClient();
|
|
139
151
|
if (client) {
|
|
140
152
|
setIsConnected(await client.health());
|
|
141
|
-
} else
|
|
153
|
+
} else {
|
|
142
154
|
const controller = new AbortController();
|
|
143
155
|
const timeout = setTimeout(() => controller.abort(), 3000);
|
|
144
|
-
const resp = await fetch(`${
|
|
156
|
+
const resp = await fetch(`${latestUrl.replace(/\/$/, '')}/health`, {
|
|
145
157
|
signal: controller.signal,
|
|
146
158
|
});
|
|
147
159
|
clearTimeout(timeout);
|
|
@@ -155,7 +167,7 @@ export const FloatingButton: React.FC<FloatingButtonProps> = ({
|
|
|
155
167
|
check();
|
|
156
168
|
const interval = setInterval(check, healthCheckInterval);
|
|
157
169
|
return () => clearInterval(interval);
|
|
158
|
-
}, [
|
|
170
|
+
}, [agentUrlProp, healthCheckInterval, resolvedAgentUrl]);
|
|
159
171
|
|
|
160
172
|
const panResponder = useRef(
|
|
161
173
|
PanResponder.create({
|
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
|
@@ -49,6 +49,10 @@ export class YaverFeedback {
|
|
|
49
49
|
p2pClient = new P2PClient(config.agentUrl, config.authToken);
|
|
50
50
|
} else {
|
|
51
51
|
p2pClient = null;
|
|
52
|
+
// Auto-discover agent in the background when convexUrl or LAN is available
|
|
53
|
+
if (enabled) {
|
|
54
|
+
YaverFeedback.discoverAgent();
|
|
55
|
+
}
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
// Set up error capture buffer size
|
|
@@ -71,6 +75,29 @@ export class YaverFeedback {
|
|
|
71
75
|
});
|
|
72
76
|
}
|
|
73
77
|
|
|
78
|
+
// Wire up BlackBox command handlers for reload signals from the agent.
|
|
79
|
+
// This enables the vibe coder to trigger reload from the Yaver mobile app
|
|
80
|
+
// and have the third-party app (with this SDK) automatically reload.
|
|
81
|
+
if (enabled) {
|
|
82
|
+
BlackBox.onCommand((cmd) => {
|
|
83
|
+
if (cmd.command === 'reload') {
|
|
84
|
+
if (cfg.onReload) {
|
|
85
|
+
cfg.onReload();
|
|
86
|
+
} else {
|
|
87
|
+
YaverFeedback.defaultReload();
|
|
88
|
+
}
|
|
89
|
+
} else if (cmd.command === 'reload_bundle' && cmd.data) {
|
|
90
|
+
const bundleUrl = cmd.data.bundleUrl as string;
|
|
91
|
+
const assetsUrl = cmd.data.assetsUrl as string | undefined;
|
|
92
|
+
if (cfg.onReloadBundle) {
|
|
93
|
+
cfg.onReloadBundle(bundleUrl, assetsUrl);
|
|
94
|
+
} else {
|
|
95
|
+
YaverFeedback.defaultReloadBundle(bundleUrl, assetsUrl);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
74
101
|
// NOTE: We intentionally do NOT hook ErrorUtils.setGlobalHandler().
|
|
75
102
|
// Sentry, Crashlytics, Bugsnag, and other tools all compete for that
|
|
76
103
|
// single slot. Hijacking it would break whichever tool the developer
|
|
@@ -82,6 +109,30 @@ export class YaverFeedback {
|
|
|
82
109
|
// pass-through wrapper they insert into their own error chain
|
|
83
110
|
}
|
|
84
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Run agent discovery in the background.
|
|
114
|
+
* Called automatically from init() when no agentUrl is provided.
|
|
115
|
+
* Sets config.agentUrl and creates P2PClient on success.
|
|
116
|
+
*/
|
|
117
|
+
static async discoverAgent(): Promise<void> {
|
|
118
|
+
if (!config || !enabled) return;
|
|
119
|
+
if (config.agentUrl) return; // already have a URL
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const result = await YaverDiscovery.discover({
|
|
123
|
+
convexUrl: config.convexUrl,
|
|
124
|
+
authToken: config.authToken,
|
|
125
|
+
preferredDeviceId: config.preferredDeviceId,
|
|
126
|
+
});
|
|
127
|
+
if (result && config) {
|
|
128
|
+
config.agentUrl = result.url;
|
|
129
|
+
p2pClient = new P2PClient(result.url, config.authToken);
|
|
130
|
+
}
|
|
131
|
+
} catch {
|
|
132
|
+
// Discovery failed — FloatingButton will show disconnected, user can retry
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
85
136
|
/**
|
|
86
137
|
* Manually trigger the feedback collection flow.
|
|
87
138
|
* Opens the feedback modal if the SDK is initialized and enabled.
|
|
@@ -331,6 +382,85 @@ export class YaverFeedback {
|
|
|
331
382
|
}
|
|
332
383
|
}
|
|
333
384
|
|
|
385
|
+
/**
|
|
386
|
+
* Default reload handler. Tries three strategies in order:
|
|
387
|
+
*
|
|
388
|
+
* 1. **YaverBundleLoader** — running inside Yaver's native container.
|
|
389
|
+
* Pulls fresh Hermes bundle from agent and reloads the RN bridge.
|
|
390
|
+
*
|
|
391
|
+
* 2. **YaverHotReload** — standalone app with feedback SDK's native module
|
|
392
|
+
* (added via Expo config plugin). Downloads Hermes bundle from agent,
|
|
393
|
+
* saves to Documents, and reloads the RN bridge.
|
|
394
|
+
*
|
|
395
|
+
* 3. **DevSettings.reload()** — standalone dev build connected to Metro.
|
|
396
|
+
*/
|
|
397
|
+
private static defaultReload(): void {
|
|
398
|
+
if (!config?.agentUrl) return;
|
|
399
|
+
const bundleUrl = `${config.agentUrl}/dev/native-bundle`;
|
|
400
|
+
const headers = { Authorization: `Bearer ${config.authToken}` };
|
|
401
|
+
YaverFeedback.loadBundleAndReload(bundleUrl, headers);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Default reload_bundle handler. Receives a compiled Hermes bundle URL
|
|
406
|
+
* from the agent and loads it via the best available native mechanism.
|
|
407
|
+
*/
|
|
408
|
+
private static defaultReloadBundle(bundleUrl: string, _assetsUrl?: string): void {
|
|
409
|
+
if (!config?.agentUrl) return;
|
|
410
|
+
|
|
411
|
+
const fullUrl = bundleUrl.startsWith('http')
|
|
412
|
+
? bundleUrl
|
|
413
|
+
: `${config.agentUrl}${bundleUrl}`;
|
|
414
|
+
const headers = { Authorization: `Bearer ${config.authToken}` };
|
|
415
|
+
YaverFeedback.loadBundleAndReload(fullUrl, headers);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Core bundle reload logic. Tries native loaders in order:
|
|
420
|
+
*
|
|
421
|
+
* 1. YaverBundleLoader (Yaver container — full validation + bridge reload)
|
|
422
|
+
* 2. YaverHotReload (SDK's own native module — download + bridge reload)
|
|
423
|
+
* 3. DevSettings.reload() (Metro dev server fallback)
|
|
424
|
+
*/
|
|
425
|
+
private static loadBundleAndReload(
|
|
426
|
+
bundleUrl: string,
|
|
427
|
+
headers: Record<string, string>,
|
|
428
|
+
): void {
|
|
429
|
+
const { NativeModules } = require('react-native');
|
|
430
|
+
|
|
431
|
+
// Strategy 1: YaverBundleLoader (running inside Yaver container)
|
|
432
|
+
if (NativeModules.YaverBundleLoader) {
|
|
433
|
+
NativeModules.YaverBundleLoader.loadBundle(bundleUrl, 'main', headers)
|
|
434
|
+
.catch((err: Error) => {
|
|
435
|
+
console.warn('[YaverFeedback] YaverBundleLoader reload failed:', err);
|
|
436
|
+
});
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Strategy 2: YaverHotReload (SDK's native module, added by Expo config plugin)
|
|
441
|
+
if (NativeModules.YaverHotReload) {
|
|
442
|
+
NativeModules.YaverHotReload.loadBundle(bundleUrl, headers)
|
|
443
|
+
.catch((err: Error) => {
|
|
444
|
+
console.warn('[YaverFeedback] YaverHotReload reload failed:', err);
|
|
445
|
+
});
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Strategy 3: DevSettings.reload() for Metro dev builds
|
|
450
|
+
console.warn(
|
|
451
|
+
'[YaverFeedback] No native bundle loader available. ' +
|
|
452
|
+
'Add "yaver-feedback-react-native" to your app.json plugins to enable hot reload.',
|
|
453
|
+
);
|
|
454
|
+
try {
|
|
455
|
+
const { DevSettings } = require('react-native');
|
|
456
|
+
if (typeof DevSettings?.reload === 'function') {
|
|
457
|
+
DevSettings.reload();
|
|
458
|
+
}
|
|
459
|
+
} catch {
|
|
460
|
+
// Not in dev mode
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
334
464
|
/** Tear down the SDK (stop shake detector, clear state). */
|
|
335
465
|
static destroy(): void {
|
|
336
466
|
if (shakeDetector) {
|
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
|