yaver-feedback-react-native 0.9.0 → 0.9.3
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 +102 -1
- package/app.plugin.js +126 -9
- package/dist/AuthOverlay.d.ts +1 -14
- package/dist/AuthOverlay.js +9 -62
- package/dist/BlackBox.js +9 -1
- package/dist/DeployPanel.js +65 -0
- package/dist/Discovery.js +13 -8
- package/dist/DogfoodRuntime.d.ts +124 -0
- package/dist/DogfoodRuntime.js +273 -0
- package/dist/FeedbackModal.js +505 -65
- package/dist/LoginScreen.d.ts +1 -5
- package/dist/LoginScreen.js +2 -6
- package/dist/MachinePickerScreen.d.ts +1 -3
- package/dist/MachinePickerScreen.js +6 -18
- package/dist/P2PClient.d.ts +178 -2
- package/dist/P2PClient.js +493 -3
- package/dist/P2PDogfoodDriver.d.ts +12 -0
- package/dist/P2PDogfoodDriver.js +118 -0
- package/dist/PairDeviceModal.d.ts +2 -3
- package/dist/ShakeDetector.js +2 -0
- package/dist/VibeChatScreen.d.ts +11 -1
- package/dist/VibeChatScreen.js +200 -41
- package/dist/YaverFeedback.d.ts +132 -0
- package/dist/YaverFeedback.js +633 -65
- package/dist/YaverModeBadge.d.ts +22 -0
- package/dist/YaverModeBadge.js +219 -0
- package/dist/__tests__/AuthDevices.test.js +1 -48
- 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__/DogfoodRuntime.test.d.ts +1 -0
- package/dist/__tests__/DogfoodRuntime.test.js +116 -0
- package/dist/__tests__/P2PClient.test.js +2 -2
- package/dist/__tests__/P2PDogfoodDriver.test.d.ts +1 -0
- package/dist/__tests__/P2PDogfoodDriver.test.js +64 -0
- package/dist/__tests__/ReportIdentity.test.d.ts +25 -0
- package/dist/__tests__/ReportIdentity.test.js +180 -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__/YaverFeedback.test.js +13 -3
- package/dist/__tests__/deviceDogfood.test.d.ts +1 -0
- package/dist/__tests__/deviceDogfood.test.js +86 -0
- package/dist/__tests__/dogfoodPolicy.test.d.ts +1 -0
- package/dist/__tests__/dogfoodPolicy.test.js +33 -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/ansi.d.ts +117 -0
- package/dist/_core/ansi.js +468 -0
- package/dist/_core/ansi.test.d.ts +1 -0
- package/dist/_core/ansi.test.js +225 -0
- package/dist/_core/buildFeedbackPrompt.d.ts +4 -9
- package/dist/_core/buildFeedbackPrompt.js +18 -72
- package/dist/_core/constants.d.ts +19 -6
- package/dist/_core/constants.js +20 -7
- package/dist/_core/device.d.ts +1 -5
- package/dist/_core/device.js +5 -14
- package/dist/_core/endpoints.d.ts +0 -7
- package/dist/_core/endpoints.js +0 -7
- package/dist/_core/index.d.ts +4 -0
- package/dist/_core/index.js +4 -0
- package/dist/_core/remoteless.d.ts +44 -0
- package/dist/_core/remoteless.js +75 -0
- package/dist/_core/trace.d.ts +47 -0
- package/dist/_core/trace.js +38 -0
- package/dist/_core/trace.test.d.ts +1 -0
- package/dist/_core/trace.test.js +60 -0
- package/dist/auth.d.ts +3 -60
- package/dist/auth.js +6 -88
- package/dist/deviceDogfood.d.ts +53 -0
- package/dist/deviceDogfood.js +137 -0
- package/dist/dogfoodPolicy.d.ts +25 -0
- package/dist/dogfoodPolicy.js +24 -0
- package/dist/index.d.ts +17 -4
- package/dist/index.js +33 -7
- 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 +208 -8
- package/package.json +13 -4
- package/src/AuthOverlay.tsx +20 -106
- package/src/BlackBox.ts +10 -1
- package/src/DeployPanel.tsx +74 -0
- package/src/Discovery.ts +13 -8
- package/src/DogfoodRuntime.ts +373 -0
- package/src/FeedbackModal.tsx +619 -79
- package/src/LoginScreen.tsx +2 -22
- package/src/MachinePickerScreen.tsx +6 -21
- package/src/P2PClient.ts +580 -4
- package/src/P2PDogfoodDriver.ts +132 -0
- package/src/PairDeviceModal.tsx +2 -3
- package/src/ShakeDetector.ts +1 -0
- package/src/VibeChatScreen.tsx +232 -42
- package/src/YaverFeedback.ts +631 -68
- package/src/YaverModeBadge.tsx +234 -0
- package/src/__tests__/AuthDevices.test.ts +1 -52
- 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__/DogfoodRuntime.test.ts +135 -0
- package/src/__tests__/P2PClient.test.ts +2 -2
- package/src/__tests__/P2PDogfoodDriver.test.ts +80 -0
- package/src/__tests__/ReportIdentity.test.ts +212 -0
- package/src/__tests__/SDKToken.test.ts +1 -1
- package/src/__tests__/ShakeToggle.test.ts +153 -0
- package/src/__tests__/YaverFeedback.test.ts +15 -3
- package/src/__tests__/deviceDogfood.test.ts +77 -0
- package/src/__tests__/dogfoodPolicy.test.ts +34 -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/ansi.test.ts +250 -0
- package/src/_core/ansi.ts +475 -0
- package/src/_core/buildFeedbackPrompt.ts +18 -95
- package/src/_core/constants.ts +20 -6
- package/src/_core/device.ts +5 -16
- package/src/_core/endpoints.ts +0 -7
- package/src/_core/index.ts +4 -0
- package/src/_core/remoteless.ts +110 -0
- package/src/_core/trace.test.ts +58 -0
- package/src/_core/trace.ts +75 -0
- package/src/auth.ts +7 -156
- package/src/deviceDogfood.ts +171 -0
- package/src/dogfoodPolicy.ts +40 -0
- package/src/index.ts +61 -11
- package/src/reloadActions.ts +273 -0
- package/src/storeShots.ts +189 -0
- package/src/types.ts +209 -8
- package/dist/GuestOnboardingScreen.d.ts +0 -8
- package/dist/GuestOnboardingScreen.js +0 -282
- package/src/GuestOnboardingScreen.tsx +0 -307
package/dist/YaverFeedback.js
CHANGED
|
@@ -6,8 +6,17 @@ const Discovery_1 = require("./Discovery");
|
|
|
6
6
|
const BlackBox_1 = require("./BlackBox");
|
|
7
7
|
const P2PClient_1 = require("./P2PClient");
|
|
8
8
|
const ShakeDetector_1 = require("./ShakeDetector");
|
|
9
|
+
const storeShots_1 = require("./storeShots");
|
|
9
10
|
const auth_1 = require("./auth");
|
|
10
11
|
const preferences_1 = require("./preferences");
|
|
12
|
+
const dogfoodPolicy_1 = require("./dogfoodPolicy");
|
|
13
|
+
const deviceDogfood_1 = require("./deviceDogfood");
|
|
14
|
+
function unrefTimer(timer) {
|
|
15
|
+
const maybeNodeTimer = timer;
|
|
16
|
+
if (typeof maybeNodeTimer.unref === 'function') {
|
|
17
|
+
maybeNodeTimer.unref();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
11
20
|
// Is this JS runtime the Yaver mobile app's super-host bridge? The
|
|
12
21
|
// YaverInfo native module is only registered inside Yaver's container
|
|
13
22
|
// (mobile/ios/Yaver/YaverInfo.{swift,m} + Android counterpart); a
|
|
@@ -133,10 +142,19 @@ if (IS_HOST_MODE) {
|
|
|
133
142
|
let config = null;
|
|
134
143
|
let enabled = false;
|
|
135
144
|
let p2pClient = null;
|
|
145
|
+
let renderP2PClient = null;
|
|
136
146
|
let shakeDetector = null;
|
|
147
|
+
/** Unsubscribe for the BlackBox command handler registered by init(). Held so
|
|
148
|
+
* a re-init (or destroy) can drop the old one instead of stacking. */
|
|
149
|
+
let commandUnsubscribe = null;
|
|
137
150
|
let p2pAuthToken = null;
|
|
138
151
|
let p2pRelayPassword = '';
|
|
152
|
+
/** Pending BlackBox auto-start retry. Held so destroy()/re-init can cancel it
|
|
153
|
+
* instead of leaving a timer chain running against a torn-down config. */
|
|
154
|
+
let autoStartTimer = null;
|
|
139
155
|
let reportLaunchInFlight = false;
|
|
156
|
+
let crashReportInFlight = false;
|
|
157
|
+
let dogfoodOnboarding = null;
|
|
140
158
|
/** Resolve the user's relay password by validating their auth token
|
|
141
159
|
* against Convex. Used whenever we (re)build the P2PClient so a
|
|
142
160
|
* relay-routed agentUrl carries a valid X-Relay-Password — without
|
|
@@ -182,6 +200,7 @@ async function resolveRelayPassword(authToken, convexUrl) {
|
|
|
182
200
|
/** Ring buffer of captured errors. */
|
|
183
201
|
let errorBuffer = [];
|
|
184
202
|
let maxErrors = 5;
|
|
203
|
+
let crashHandlerInstalled = false;
|
|
185
204
|
/** Track whether BlackBox was running before disable (to restart on enable). */
|
|
186
205
|
let blackBoxWasStreaming = false;
|
|
187
206
|
/**
|
|
@@ -203,40 +222,37 @@ const flagCache = new Map();
|
|
|
203
222
|
*/
|
|
204
223
|
class YaverFeedback {
|
|
205
224
|
static async resolveP2PAuthToken() {
|
|
206
|
-
|
|
207
|
-
return null;
|
|
208
|
-
if (!config.preferredDeviceId)
|
|
209
|
-
return config.authToken;
|
|
210
|
-
const devices = await (0, auth_1.listReachableDevices)(config.authToken);
|
|
211
|
-
const all = [...devices.owned, ...devices.shared];
|
|
212
|
-
const selected = all.find((device) => device.deviceId === config?.preferredDeviceId);
|
|
213
|
-
if (!selected || !selected.isGuest || selected.accessScope !== 'shared-scoped') {
|
|
214
|
-
return config.authToken;
|
|
215
|
-
}
|
|
216
|
-
if (!selected.hostUserId) {
|
|
217
|
-
return config.authToken;
|
|
218
|
-
}
|
|
219
|
-
const delegated = await (0, auth_1.mintGuestSdkToken)(config.authToken, selected.hostUserId, selected.deviceId);
|
|
220
|
-
return delegated.token;
|
|
225
|
+
return config?.authToken ?? null;
|
|
221
226
|
}
|
|
222
227
|
static async rebuildP2PClient(agentUrl) {
|
|
223
|
-
|
|
228
|
+
const currentConfig = config;
|
|
229
|
+
if (!currentConfig)
|
|
224
230
|
return;
|
|
225
|
-
const effectiveUrl = agentUrl ??
|
|
231
|
+
const effectiveUrl = agentUrl ?? currentConfig.agentUrl;
|
|
226
232
|
if (!effectiveUrl) {
|
|
227
233
|
p2pClient = null;
|
|
234
|
+
renderP2PClient = null;
|
|
228
235
|
p2pAuthToken = null;
|
|
229
236
|
return;
|
|
230
237
|
}
|
|
231
238
|
const token = await YaverFeedback.resolveP2PAuthToken();
|
|
239
|
+
// reset() can clear config while relay-password resolution is awaiting.
|
|
240
|
+
// Do not resurrect a client from that stale init generation.
|
|
241
|
+
if (config !== currentConfig)
|
|
242
|
+
return;
|
|
232
243
|
if (!token) {
|
|
233
244
|
p2pClient = null;
|
|
245
|
+
renderP2PClient = null;
|
|
234
246
|
p2pAuthToken = null;
|
|
235
247
|
return;
|
|
236
248
|
}
|
|
237
249
|
p2pAuthToken = token;
|
|
238
250
|
const rp = await resolveRelayPassword(token);
|
|
251
|
+
if (config !== currentConfig)
|
|
252
|
+
return;
|
|
239
253
|
p2pClient = new P2PClient_1.P2PClient(effectiveUrl, token, rp);
|
|
254
|
+
const renderUrl = currentConfig.renderAgentUrl || effectiveUrl;
|
|
255
|
+
renderP2PClient = renderUrl === effectiveUrl ? p2pClient : new P2PClient_1.P2PClient(renderUrl, token, rp);
|
|
240
256
|
}
|
|
241
257
|
/**
|
|
242
258
|
* Initialize the feedback SDK with the given configuration.
|
|
@@ -245,12 +261,127 @@ class YaverFeedback {
|
|
|
245
261
|
* If no `agentUrl` is provided, the SDK will attempt auto-discovery
|
|
246
262
|
* via `YaverDiscovery` on the first `startReport()` call.
|
|
247
263
|
*/
|
|
264
|
+
/** The runtime lane, when running on web inside a Yaver preview WebView. */
|
|
265
|
+
static detectWebLane() {
|
|
266
|
+
try {
|
|
267
|
+
if (react_native_1.Platform.OS !== 'web' || typeof window === 'undefined')
|
|
268
|
+
return null;
|
|
269
|
+
const l = String(window.__yaverLane || '').toLowerCase();
|
|
270
|
+
if (l === 'browser' || l === 'webrtc')
|
|
271
|
+
return l;
|
|
272
|
+
}
|
|
273
|
+
catch { /* noop */ }
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Mount a draggable DOM floating "Y" icon for the BROWSER lane (RN-web). It
|
|
278
|
+
* lives in the WebView's document.body (position:fixed, max z-index), so it
|
|
279
|
+
* renders ON TOP of the page inside the WebView and is never occluded by the
|
|
280
|
+
* fullScreen preview modal — the one occlusion-proof affordance on iOS.
|
|
281
|
+
* Tap opens the SDK's existing report flow (the RN FeedbackModal renders in
|
|
282
|
+
* the same WebView DOM, also un-occluded). No-op off-web or off-lane, and
|
|
283
|
+
* idempotent (a re-init reuses the existing node).
|
|
284
|
+
*/
|
|
285
|
+
static mountBrowserLaneIcon() {
|
|
286
|
+
if (YaverFeedback.detectWebLane() !== 'browser')
|
|
287
|
+
return;
|
|
288
|
+
try {
|
|
289
|
+
const doc = globalThis.document;
|
|
290
|
+
if (!doc || !doc.body)
|
|
291
|
+
return;
|
|
292
|
+
if (doc.getElementById('yaver-feedback-btn'))
|
|
293
|
+
return; // idempotent
|
|
294
|
+
const btn = doc.createElement('div');
|
|
295
|
+
btn.id = 'yaver-feedback-btn';
|
|
296
|
+
btn.textContent = 'Y';
|
|
297
|
+
btn.title = 'Yaver Feedback — drag to move · tap to report';
|
|
298
|
+
btn.style.cssText =
|
|
299
|
+
'position:fixed;bottom:20px;right:20px;width:44px;height:44px;border-radius:50%;' +
|
|
300
|
+
'background:#6366f1;color:#fff;display:flex;align-items:center;justify-content:center;' +
|
|
301
|
+
'cursor:pointer;z-index:99999;font-size:18px;font-weight:bold;touch-action:none;' +
|
|
302
|
+
'box-shadow:0 4px 12px rgba(99,102,241,0.4);transition:transform 0.2s;';
|
|
303
|
+
const drag = { on: false, moved: false, sx: 0, sy: 0, ox: 0, oy: 0 };
|
|
304
|
+
const w = window;
|
|
305
|
+
const applyPos = (x, y) => {
|
|
306
|
+
const m = 8;
|
|
307
|
+
const nx = Math.max(m, Math.min(w.innerWidth - 44 - m, x));
|
|
308
|
+
const ny = Math.max(m, Math.min(w.innerHeight - 44 - m, y));
|
|
309
|
+
btn.style.left = nx + 'px';
|
|
310
|
+
btn.style.top = ny + 'px';
|
|
311
|
+
btn.style.right = 'auto';
|
|
312
|
+
btn.style.bottom = 'auto';
|
|
313
|
+
try {
|
|
314
|
+
localStorage.setItem('yaver-feedback-btn-pos', JSON.stringify({ x: nx, y: ny }));
|
|
315
|
+
}
|
|
316
|
+
catch { /* noop */ }
|
|
317
|
+
};
|
|
318
|
+
try {
|
|
319
|
+
const s = localStorage.getItem('yaver-feedback-btn-pos');
|
|
320
|
+
if (s) {
|
|
321
|
+
const p = JSON.parse(s);
|
|
322
|
+
if (typeof p?.x === 'number')
|
|
323
|
+
applyPos(p.x, p.y);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
catch { /* noop */ }
|
|
327
|
+
btn.addEventListener('pointerdown', (e) => {
|
|
328
|
+
drag.on = true;
|
|
329
|
+
drag.moved = false;
|
|
330
|
+
const r = btn.getBoundingClientRect();
|
|
331
|
+
drag.sx = e.clientX;
|
|
332
|
+
drag.sy = e.clientY;
|
|
333
|
+
drag.ox = r.left;
|
|
334
|
+
drag.oy = r.top;
|
|
335
|
+
btn.setPointerCapture(e.pointerId);
|
|
336
|
+
btn.style.transition = 'none';
|
|
337
|
+
});
|
|
338
|
+
btn.addEventListener('pointermove', (e) => {
|
|
339
|
+
if (!drag.on)
|
|
340
|
+
return;
|
|
341
|
+
const dx = e.clientX - drag.sx, dy = e.clientY - drag.sy;
|
|
342
|
+
if (Math.abs(dx) + Math.abs(dy) > 5)
|
|
343
|
+
drag.moved = true;
|
|
344
|
+
if (drag.moved)
|
|
345
|
+
applyPos(drag.ox + dx, drag.oy + dy);
|
|
346
|
+
});
|
|
347
|
+
const end = (e) => {
|
|
348
|
+
if (!drag.on)
|
|
349
|
+
return;
|
|
350
|
+
drag.on = false;
|
|
351
|
+
btn.style.transition = 'transform 0.2s';
|
|
352
|
+
try {
|
|
353
|
+
btn.releasePointerCapture(e.pointerId);
|
|
354
|
+
}
|
|
355
|
+
catch { /* noop */ }
|
|
356
|
+
if (!drag.moved) {
|
|
357
|
+
try {
|
|
358
|
+
void YaverFeedback.startReport();
|
|
359
|
+
}
|
|
360
|
+
catch { /* noop */ }
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
btn.addEventListener('pointerup', end);
|
|
364
|
+
btn.addEventListener('pointercancel', end);
|
|
365
|
+
// Also answer the host's forwarded shake (Yaver injects yaver-feedback:launch).
|
|
366
|
+
const launch = () => { try {
|
|
367
|
+
void YaverFeedback.startReport();
|
|
368
|
+
}
|
|
369
|
+
catch { /* noop */ } };
|
|
370
|
+
window.addEventListener('yaver-feedback:launch', launch);
|
|
371
|
+
window.__yaverFeedbackLaunch = launch;
|
|
372
|
+
doc.body.appendChild(btn);
|
|
373
|
+
}
|
|
374
|
+
catch { /* noop — never break the guest app over a feedback affordance */ }
|
|
375
|
+
}
|
|
248
376
|
static init(cfg) {
|
|
249
377
|
config = {
|
|
250
378
|
trigger: 'shake',
|
|
251
379
|
maxRecordingDuration: 120,
|
|
252
380
|
autoLogin: true,
|
|
253
381
|
...cfg,
|
|
382
|
+
agentUrl: cfg.codingAgentUrl || cfg.agentUrl,
|
|
383
|
+
codingDeviceId: cfg.codingDeviceId || cfg.preferredDeviceId,
|
|
384
|
+
renderDeviceId: cfg.renderDeviceId || cfg.codingDeviceId || cfg.preferredDeviceId,
|
|
254
385
|
};
|
|
255
386
|
if (IS_HOST_MODE) {
|
|
256
387
|
// sfmg / talos / etc. running inside Yaver mobile (compile-time
|
|
@@ -272,6 +403,9 @@ class YaverFeedback {
|
|
|
272
403
|
}
|
|
273
404
|
maxErrors = cfg.maxCapturedErrors ?? 5;
|
|
274
405
|
errorBuffer = [];
|
|
406
|
+
if (cfg.crashReporting?.enabled && cfg.crashReporting?.installGlobalHandler) {
|
|
407
|
+
YaverFeedback.installCrashHandler();
|
|
408
|
+
}
|
|
275
409
|
return;
|
|
276
410
|
}
|
|
277
411
|
if (config.disableShakeGesture && (!config.quickIcon || config.quickIcon === 'auto')) {
|
|
@@ -321,12 +455,17 @@ class YaverFeedback {
|
|
|
321
455
|
// is set, so set a placeholder header here that won't 401 a
|
|
322
456
|
// direct-LAN url and will be overwritten before any relay hop.
|
|
323
457
|
p2pClient = new P2PClient_1.P2PClient(config.agentUrl, config.authToken ?? '', p2pRelayPassword);
|
|
458
|
+
const renderUrl = config.renderAgentUrl || config.agentUrl;
|
|
459
|
+
renderP2PClient = renderUrl === config.agentUrl
|
|
460
|
+
? p2pClient
|
|
461
|
+
: new P2PClient_1.P2PClient(renderUrl, config.authToken ?? '', p2pRelayPassword);
|
|
324
462
|
if (config.authToken) {
|
|
325
463
|
void YaverFeedback.rebuildP2PClient(config.agentUrl);
|
|
326
464
|
}
|
|
327
465
|
}
|
|
328
466
|
else {
|
|
329
467
|
p2pClient = null;
|
|
468
|
+
renderP2PClient = null;
|
|
330
469
|
// Auto-discover agent in the background when convexUrl or LAN is available
|
|
331
470
|
if (enabled && (config.authToken || config.preferredDeviceId)) {
|
|
332
471
|
YaverFeedback.discoverAgent();
|
|
@@ -335,23 +474,23 @@ class YaverFeedback {
|
|
|
335
474
|
// Set up error capture buffer size
|
|
336
475
|
maxErrors = cfg.maxCapturedErrors ?? 5;
|
|
337
476
|
errorBuffer = [];
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
shakeDetector.stop();
|
|
341
|
-
shakeDetector = null;
|
|
342
|
-
}
|
|
343
|
-
if (enabled && config.trigger === 'shake' && !config.disableShakeGesture) {
|
|
344
|
-
shakeDetector = new ShakeDetector_1.ShakeDetector();
|
|
345
|
-
shakeDetector.start(() => {
|
|
346
|
-
YaverFeedback.notifyShake();
|
|
347
|
-
if (config?.reportingOnly) {
|
|
348
|
-
YaverFeedback.sendAutoReport();
|
|
349
|
-
}
|
|
350
|
-
else {
|
|
351
|
-
YaverFeedback.startReport();
|
|
352
|
-
}
|
|
353
|
-
});
|
|
477
|
+
if (cfg.crashReporting?.enabled && cfg.crashReporting?.installGlobalHandler) {
|
|
478
|
+
YaverFeedback.installCrashHandler();
|
|
354
479
|
}
|
|
480
|
+
// Wire up shake detection when trigger is 'shake'. Rebuild from scratch:
|
|
481
|
+
// config was just replaced, so the old detector may be closing over stale
|
|
482
|
+
// settings.
|
|
483
|
+
YaverFeedback.stopShakeDetector();
|
|
484
|
+
YaverFeedback.syncShakeDetector();
|
|
485
|
+
// Browser lane (RN-web inside Yaver's fullScreen WebView preview): the
|
|
486
|
+
// native shake module doesn't exist on web, and Yaver's own container
|
|
487
|
+
// overlay is OCCLUDED behind the WebView modal on iOS. So mount an
|
|
488
|
+
// occlusion-proof DRAGGABLE DOM icon INSIDE the WebView — same pattern as
|
|
489
|
+
// yaver-feedback-web. Lane is stamped by Yaver via
|
|
490
|
+
// window.__yaverLane='browser' (mobile PREVIEW_LANE_SCRIPT). See
|
|
491
|
+
// docs/audits/feedback-sdk-lanes-audit-2026-07-28.md.
|
|
492
|
+
if (enabled)
|
|
493
|
+
YaverFeedback.mountBrowserLaneIcon();
|
|
355
494
|
// Wire up BlackBox command handlers for reload + status signals from
|
|
356
495
|
// the agent. Opens an SSE channel the agent uses to:
|
|
357
496
|
// - broadcast reload_bundle so the phone picks up a fresh Hermes
|
|
@@ -360,7 +499,14 @@ class YaverFeedback {
|
|
|
360
499
|
// assets…", "Done") so the SDK can surface it like a normal
|
|
361
500
|
// "Working…" spinner instead of a silent 60-second freeze.
|
|
362
501
|
if (enabled) {
|
|
363
|
-
|
|
502
|
+
// onCommand appends to a static array and hands back an unsubscribe.
|
|
503
|
+
// Dropping that unsubscribe meant every re-init stacked another handler
|
|
504
|
+
// on top of the last, so an app that re-initialised (say, a settings
|
|
505
|
+
// toggle) reloaded twice per agent command, then three times, and so on.
|
|
506
|
+
// destroy() never cleared them either. Drop the previous registration
|
|
507
|
+
// first so init() is idempotent here.
|
|
508
|
+
commandUnsubscribe?.();
|
|
509
|
+
commandUnsubscribe = BlackBox_1.BlackBox.onCommand((cmd) => {
|
|
364
510
|
if (cmd.command === 'reload') {
|
|
365
511
|
if (cfg.onReload) {
|
|
366
512
|
cfg.onReload();
|
|
@@ -423,17 +569,24 @@ class YaverFeedback {
|
|
|
423
569
|
// SFMG used to call BlackBox.start() inside YaverFeedbackWidget
|
|
424
570
|
// after auth — that path still works (start() is idempotent), so
|
|
425
571
|
// upgrading SDK without removing the manual call is safe.
|
|
572
|
+
// 4. RETRY, rather than checking once. This was a single 500ms
|
|
573
|
+
// timeout, and on a cold start it could not succeed: an app that
|
|
574
|
+
// passes neither agentUrl nor authToken to init() (the normal
|
|
575
|
+
// case — they're restored from storage) gets its session from
|
|
576
|
+
// `void hydrateSession()`, which reads two AsyncStorage keys and
|
|
577
|
+
// then awaits discoverAgent(), a Convex round trip plus LAN
|
|
578
|
+
// probing. That does not finish in 500ms, so the check found no
|
|
579
|
+
// agentUrl, gave up, and BlackBox never started — meaning no SSE
|
|
580
|
+
// channel, so the agent's `reload_bundle` command had nowhere to
|
|
581
|
+
// land and Hermes hot reload silently did nothing until the user
|
|
582
|
+
// shook the device and drove discovery by hand.
|
|
583
|
+
//
|
|
584
|
+
// The both-conditions guard is what keeps the 401 retry storm
|
|
585
|
+
// from coming back, so it is preserved exactly; we only re-check
|
|
586
|
+
// it over time instead of once. Bounded so a device that never
|
|
587
|
+
// signs in doesn't poll forever.
|
|
426
588
|
if (cfg.autoStartBlackBox !== false) {
|
|
427
|
-
|
|
428
|
-
if (config?.agentUrl && (config?.authToken || p2pAuthToken)) {
|
|
429
|
-
try {
|
|
430
|
-
BlackBox_1.BlackBox.start();
|
|
431
|
-
}
|
|
432
|
-
catch (err) {
|
|
433
|
-
console.warn('[YaverFeedback] BlackBox auto-start failed:', err);
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
}, 500);
|
|
589
|
+
YaverFeedback.scheduleBlackBoxAutoStart();
|
|
437
590
|
}
|
|
438
591
|
}
|
|
439
592
|
// NOTE: We intentionally do NOT hook ErrorUtils.setGlobalHandler().
|
|
@@ -446,6 +599,41 @@ class YaverFeedback {
|
|
|
446
599
|
// - YaverFeedback.wrapErrorHandler(existingHandler) to create a
|
|
447
600
|
// pass-through wrapper they insert into their own error chain
|
|
448
601
|
}
|
|
602
|
+
/**
|
|
603
|
+
* Opt-in global crash handler. This wraps the existing React Native
|
|
604
|
+
* ErrorUtils handler and still calls it, so Sentry/Crashlytics/Bugsnag can
|
|
605
|
+
* remain the system of record. The SDK only uploads a crash report and,
|
|
606
|
+
* when configured, asks the agent to create a fix task.
|
|
607
|
+
*/
|
|
608
|
+
static installCrashHandler() {
|
|
609
|
+
if (crashHandlerInstalled)
|
|
610
|
+
return;
|
|
611
|
+
try {
|
|
612
|
+
const errorUtils = globalThis?.ErrorUtils;
|
|
613
|
+
if (!errorUtils?.getGlobalHandler || !errorUtils?.setGlobalHandler) {
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
const next = errorUtils.getGlobalHandler();
|
|
617
|
+
errorUtils.setGlobalHandler((error, isFatal) => {
|
|
618
|
+
YaverFeedback.attachError(error, {
|
|
619
|
+
source: 'global-handler',
|
|
620
|
+
crashAware: true,
|
|
621
|
+
});
|
|
622
|
+
if (errorBuffer.length > 0) {
|
|
623
|
+
errorBuffer[errorBuffer.length - 1].isFatal = isFatal ?? true;
|
|
624
|
+
}
|
|
625
|
+
void YaverFeedback.reportCrash(error, {
|
|
626
|
+
isFatal: isFatal ?? true,
|
|
627
|
+
source: 'global-handler',
|
|
628
|
+
});
|
|
629
|
+
next?.(error, isFatal);
|
|
630
|
+
});
|
|
631
|
+
crashHandlerInstalled = true;
|
|
632
|
+
}
|
|
633
|
+
catch (err) {
|
|
634
|
+
console.warn('[YaverFeedback] installCrashHandler failed:', err);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
449
637
|
/**
|
|
450
638
|
* Run agent discovery in the background.
|
|
451
639
|
* Called automatically from init() when no agentUrl is provided.
|
|
@@ -547,11 +735,64 @@ class YaverFeedback {
|
|
|
547
735
|
else {
|
|
548
736
|
await YaverFeedback.discoverAgent();
|
|
549
737
|
}
|
|
738
|
+
// Signing in is the event the auto-start was waiting for. If its bounded
|
|
739
|
+
// retry already gave up (device left signed out past the window), this is
|
|
740
|
+
// what brings the command channel up without requiring an app restart.
|
|
741
|
+
if (config.autoStartBlackBox !== false && !BlackBox_1.BlackBox.isStreaming) {
|
|
742
|
+
YaverFeedback.scheduleBlackBoxAutoStart();
|
|
743
|
+
}
|
|
550
744
|
}
|
|
551
745
|
/** Returns true once the SDK has a session token it can use. */
|
|
552
746
|
static isAuthed() {
|
|
553
747
|
return Boolean(config?.authToken);
|
|
554
748
|
}
|
|
749
|
+
/**
|
|
750
|
+
* On-device App Store screenshot capture (Engine 2). Walks the routes
|
|
751
|
+
* the host hands us, screenshots each, and uploads to the agent which
|
|
752
|
+
* runs the App Store Connect backend. agentUrl / authToken / relay
|
|
753
|
+
* password default to the SDK's resolved session; the host only has to
|
|
754
|
+
* supply a navigation ref + the ordered route list.
|
|
755
|
+
*
|
|
756
|
+
* YaverFeedback.captureStoreScreenshots({
|
|
757
|
+
* app: 'sfmg',
|
|
758
|
+
* navigationRef,
|
|
759
|
+
* routes: ['/(tabs)/dashboard', '/(tabs)/messages', '/(tabs)/clients'],
|
|
760
|
+
* submit: true,
|
|
761
|
+
* })
|
|
762
|
+
*/
|
|
763
|
+
static async captureStoreScreenshots(opts) {
|
|
764
|
+
const agentUrl = opts.agentUrl ?? config?.agentUrl ?? '';
|
|
765
|
+
const authToken = opts.authToken ?? config?.authToken ?? p2pAuthToken ?? '';
|
|
766
|
+
const relayPassword = opts.relayPassword ?? p2pRelayPassword;
|
|
767
|
+
if (!agentUrl || !authToken) {
|
|
768
|
+
return {
|
|
769
|
+
ok: false,
|
|
770
|
+
captured: 0,
|
|
771
|
+
uploaded: 0,
|
|
772
|
+
message: 'YaverFeedback not connected to an agent — init() with agentUrl + authToken first.',
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
return (0, storeShots_1.captureStoreScreenshots)({ ...opts, agentUrl, authToken, relayPassword });
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Let the mobile app / CLI kick THIS device into self-capturing. Registers
|
|
779
|
+
* a BlackBox command listener that fires captureStoreScreenshots when the
|
|
780
|
+
* agent pushes a `capture_store_shots` command (its `data` may override
|
|
781
|
+
* `submit` / `locale`). Returns an unsubscribe fn. Call once after init,
|
|
782
|
+
* passing the app's navigation ref + the routes to walk.
|
|
783
|
+
*/
|
|
784
|
+
static enableStoreShotsOnCommand(opts) {
|
|
785
|
+
return BlackBox_1.BlackBox.onCommand((cmd) => {
|
|
786
|
+
if (cmd?.command !== 'capture_store_shots')
|
|
787
|
+
return;
|
|
788
|
+
const data = cmd.data ?? {};
|
|
789
|
+
void YaverFeedback.captureStoreScreenshots({
|
|
790
|
+
...opts,
|
|
791
|
+
submit: typeof data.submit === 'boolean' ? data.submit : opts.submit,
|
|
792
|
+
locale: typeof data.locale === 'string' ? data.locale : opts.locale,
|
|
793
|
+
});
|
|
794
|
+
});
|
|
795
|
+
}
|
|
555
796
|
/**
|
|
556
797
|
* Request the embedded FeedbackModal to show the login screen. Works by
|
|
557
798
|
* emitting an event the modal listens for — avoids forcing the host app
|
|
@@ -571,6 +812,25 @@ class YaverFeedback {
|
|
|
571
812
|
const { DeviceEventEmitter } = require('react-native');
|
|
572
813
|
DeviceEventEmitter.emit('yaverFeedback:startMachinePicker');
|
|
573
814
|
}
|
|
815
|
+
/** Begin the reusable host-app Dogfood wizard. Owner OAuth and machine
|
|
816
|
+
* selection intentionally happen before installation enrollment/runtime
|
|
817
|
+
* controls because starting builds or runners is owner-level authority. */
|
|
818
|
+
static beginDogfoodOnboarding(options) {
|
|
819
|
+
dogfoodOnboarding = options;
|
|
820
|
+
if (!config)
|
|
821
|
+
YaverFeedback.init({ autoLogin: true });
|
|
822
|
+
if (!YaverFeedback.isAuthed()) {
|
|
823
|
+
YaverFeedback.showLogin();
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
YaverFeedback.showMachinePicker();
|
|
827
|
+
}
|
|
828
|
+
static getDogfoodOnboarding() {
|
|
829
|
+
return dogfoodOnboarding;
|
|
830
|
+
}
|
|
831
|
+
static clearDogfoodOnboarding() {
|
|
832
|
+
dogfoodOnboarding = null;
|
|
833
|
+
}
|
|
574
834
|
/**
|
|
575
835
|
* Update the selected remote device. Resets the cached agent URL so the
|
|
576
836
|
* next `startReport()` (or FloatingButton press) rediscovers against the
|
|
@@ -579,9 +839,19 @@ class YaverFeedback {
|
|
|
579
839
|
static async setPreferredDevice(deviceId) {
|
|
580
840
|
if (!config)
|
|
581
841
|
return;
|
|
842
|
+
const wasUnified = !config.renderDeviceId
|
|
843
|
+
|| config.renderDeviceId === config.codingDeviceId
|
|
844
|
+
|| config.renderDeviceId === config.preferredDeviceId;
|
|
582
845
|
config.preferredDeviceId = deviceId;
|
|
846
|
+
config.codingDeviceId = deviceId;
|
|
847
|
+
if (wasUnified)
|
|
848
|
+
config.renderDeviceId = deviceId;
|
|
583
849
|
config.agentUrl = undefined;
|
|
850
|
+
config.codingAgentUrl = undefined;
|
|
851
|
+
if (wasUnified)
|
|
852
|
+
config.renderAgentUrl = undefined;
|
|
584
853
|
p2pClient = null;
|
|
854
|
+
renderP2PClient = null;
|
|
585
855
|
p2pAuthToken = null;
|
|
586
856
|
await YaverFeedback.discoverAgent();
|
|
587
857
|
}
|
|
@@ -591,8 +861,7 @@ class YaverFeedback {
|
|
|
591
861
|
return null;
|
|
592
862
|
const preferredDeviceId = config.preferredDeviceId;
|
|
593
863
|
const devices = await (0, auth_1.listReachableDevices)(config.authToken);
|
|
594
|
-
|
|
595
|
-
return all.find((device) => device.deviceId === preferredDeviceId) ?? null;
|
|
864
|
+
return devices.owned.find((device) => device.deviceId === preferredDeviceId) ?? null;
|
|
596
865
|
}
|
|
597
866
|
/**
|
|
598
867
|
* Trigger remote device-auth for a CLI runner on the selected agent
|
|
@@ -642,6 +911,7 @@ class YaverFeedback {
|
|
|
642
911
|
config.agentUrl = undefined;
|
|
643
912
|
}
|
|
644
913
|
p2pClient = null;
|
|
914
|
+
renderP2PClient = null;
|
|
645
915
|
p2pAuthToken = null;
|
|
646
916
|
}
|
|
647
917
|
/**
|
|
@@ -747,32 +1017,138 @@ class YaverFeedback {
|
|
|
747
1017
|
}
|
|
748
1018
|
BlackBox_1.BlackBox.unwrapConsole(); // ensure console is restored even if BlackBox wasn't started
|
|
749
1019
|
errorBuffer = [];
|
|
750
|
-
|
|
751
|
-
shakeDetector.stop();
|
|
752
|
-
shakeDetector = null;
|
|
753
|
-
}
|
|
1020
|
+
YaverFeedback.stopShakeDetector();
|
|
754
1021
|
}
|
|
755
1022
|
else {
|
|
756
1023
|
// === ENABLE ===
|
|
757
1024
|
if (blackBoxWasStreaming) {
|
|
758
1025
|
BlackBox_1.BlackBox.start(); // restart with previous config
|
|
759
1026
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
shakeDetector = new ShakeDetector_1.ShakeDetector();
|
|
763
|
-
shakeDetector.start(() => {
|
|
764
|
-
YaverFeedback.notifyShake();
|
|
765
|
-
if (config?.reportingOnly) {
|
|
766
|
-
YaverFeedback.sendAutoReport();
|
|
767
|
-
}
|
|
768
|
-
else {
|
|
769
|
-
YaverFeedback.startReport();
|
|
770
|
-
}
|
|
771
|
-
});
|
|
772
|
-
}
|
|
1027
|
+
enabled = true; // syncShakeDetector reads this
|
|
1028
|
+
YaverFeedback.syncShakeDetector();
|
|
773
1029
|
}
|
|
774
1030
|
enabled = value;
|
|
775
1031
|
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Turn shake-to-report on or off without tearing the SDK down.
|
|
1034
|
+
*
|
|
1035
|
+
* `trigger` is read only inside init(), so before this the only ways to
|
|
1036
|
+
* stop listening for a shake were setEnabled(false) — which also kills the
|
|
1037
|
+
* flight recorder and the agent command channel — or a re-init, which used
|
|
1038
|
+
* to stack a duplicate command handler. Neither is what "turn the shake
|
|
1039
|
+
* catcher off" should cost.
|
|
1040
|
+
*
|
|
1041
|
+
* Persists onto config.disableShakeGesture, so a later setEnabled(true)
|
|
1042
|
+
* honours it rather than resurrecting the listener.
|
|
1043
|
+
*/
|
|
1044
|
+
static setShakeEnabled(value) {
|
|
1045
|
+
if (!config)
|
|
1046
|
+
return;
|
|
1047
|
+
config.disableShakeGesture = !value;
|
|
1048
|
+
YaverFeedback.syncShakeDetector();
|
|
1049
|
+
}
|
|
1050
|
+
/** Whether the shake listener is currently armed. */
|
|
1051
|
+
static isShakeEnabled() {
|
|
1052
|
+
return shakeDetector !== null;
|
|
1053
|
+
}
|
|
1054
|
+
/**
|
|
1055
|
+
* Bring the shake listener in line with the current config. Idempotent —
|
|
1056
|
+
* safe to call whenever `enabled`, `trigger`, or `disableShakeGesture`
|
|
1057
|
+
* moves.
|
|
1058
|
+
*/
|
|
1059
|
+
static syncShakeDetector() {
|
|
1060
|
+
const want = enabled && config?.trigger === 'shake' && !config?.disableShakeGesture;
|
|
1061
|
+
if (want && !shakeDetector) {
|
|
1062
|
+
shakeDetector = new ShakeDetector_1.ShakeDetector();
|
|
1063
|
+
shakeDetector.start(() => {
|
|
1064
|
+
YaverFeedback.notifyShake();
|
|
1065
|
+
if (config?.reportingOnly) {
|
|
1066
|
+
YaverFeedback.sendAutoReport();
|
|
1067
|
+
}
|
|
1068
|
+
else {
|
|
1069
|
+
YaverFeedback.startReport();
|
|
1070
|
+
}
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
else if (!want && shakeDetector) {
|
|
1074
|
+
YaverFeedback.stopShakeDetector();
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
static stopShakeDetector() {
|
|
1078
|
+
if (shakeDetector) {
|
|
1079
|
+
shakeDetector.stop();
|
|
1080
|
+
shakeDetector = null;
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
/** Cancel a pending BlackBox auto-start retry chain. */
|
|
1084
|
+
static cancelBlackBoxAutoStart() {
|
|
1085
|
+
if (autoStartTimer) {
|
|
1086
|
+
clearTimeout(autoStartTimer);
|
|
1087
|
+
autoStartTimer = null;
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Start BlackBox as soon as we have BOTH an agentUrl and a token, polling
|
|
1092
|
+
* until then.
|
|
1093
|
+
*
|
|
1094
|
+
* Both conditions are mandatory and deliberate: starting without a token
|
|
1095
|
+
* makes the SSE channel 401 and retry with backoff, which is the tight
|
|
1096
|
+
* string-concat + JSON-parse loop that used to SIGSEGV Hermes on iOS 18.3.1
|
|
1097
|
+
* during Screenshot & Fix. This only re-checks that same guard over time.
|
|
1098
|
+
*
|
|
1099
|
+
* Bounded at ~60s: long enough for AsyncStorage hydration plus a Convex
|
|
1100
|
+
* discovery round trip on a cold, off-LAN start, short enough that a device
|
|
1101
|
+
* whose user never signs in stops polling. Giving up here costs nothing —
|
|
1102
|
+
* setAuthToken() and startReport() both reschedule, so signing in later
|
|
1103
|
+
* still brings the channel up.
|
|
1104
|
+
*/
|
|
1105
|
+
static scheduleBlackBoxAutoStart(attempt = 0) {
|
|
1106
|
+
const MAX_ATTEMPTS = 60;
|
|
1107
|
+
const FIRST_DELAY_MS = 500;
|
|
1108
|
+
const RETRY_DELAY_MS = 1000;
|
|
1109
|
+
// Re-run discovery every Nth tick rather than every tick: it is a Convex
|
|
1110
|
+
// round trip plus LAN probing, so once a second would hammer both. Every
|
|
1111
|
+
// 3s is responsive enough that waking the dev machine reconnects the phone
|
|
1112
|
+
// on its own, without the user wondering whether to restart the app.
|
|
1113
|
+
const REDISCOVER_EVERY = 3;
|
|
1114
|
+
YaverFeedback.cancelBlackBoxAutoStart();
|
|
1115
|
+
if (!enabled || attempt >= MAX_ATTEMPTS)
|
|
1116
|
+
return;
|
|
1117
|
+
const timer = setTimeout(() => {
|
|
1118
|
+
autoStartTimer = null;
|
|
1119
|
+
// A destroy() or setEnabled(false) between scheduling and firing.
|
|
1120
|
+
if (!enabled || !config)
|
|
1121
|
+
return;
|
|
1122
|
+
if (BlackBox_1.BlackBox.isStreaming)
|
|
1123
|
+
return;
|
|
1124
|
+
if (config.agentUrl && (config.authToken || p2pAuthToken)) {
|
|
1125
|
+
try {
|
|
1126
|
+
// Pass the host's BlackBox config through. This used to call
|
|
1127
|
+
// start() bare, so a host that configured the flight recorder
|
|
1128
|
+
// — as the README itself shows — had it silently replaced by
|
|
1129
|
+
// defaults, and appName came out ''.
|
|
1130
|
+
BlackBox_1.BlackBox.start(config.blackBox);
|
|
1131
|
+
}
|
|
1132
|
+
catch (err) {
|
|
1133
|
+
console.warn('[YaverFeedback] BlackBox auto-start failed:', err);
|
|
1134
|
+
}
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
// No agent yet. Re-attempt discovery instead of only re-reading the
|
|
1138
|
+
// flag: hydrateSession() runs discoverAgent() exactly once at init, so
|
|
1139
|
+
// if the dev machine was unreachable at that moment — asleep, phone
|
|
1140
|
+
// still on cellular before the relay came up, agent not yet serving —
|
|
1141
|
+
// agentUrl stayed null for the whole process lifetime and only an app
|
|
1142
|
+
// restart could recover it. discoverAgent() self-guards on "already
|
|
1143
|
+
// have a URL" and "no token", so calling it again is cheap and safe.
|
|
1144
|
+
if (!config.agentUrl && attempt > 0 && attempt % REDISCOVER_EVERY === 0) {
|
|
1145
|
+
void YaverFeedback.discoverAgent();
|
|
1146
|
+
}
|
|
1147
|
+
YaverFeedback.scheduleBlackBoxAutoStart(attempt + 1);
|
|
1148
|
+
}, attempt === 0 ? FIRST_DELAY_MS : RETRY_DELAY_MS);
|
|
1149
|
+
autoStartTimer = timer;
|
|
1150
|
+
unrefTimer(timer);
|
|
1151
|
+
}
|
|
776
1152
|
/** Returns whether the SDK is currently enabled. */
|
|
777
1153
|
static isEnabled() {
|
|
778
1154
|
return enabled;
|
|
@@ -781,6 +1157,61 @@ class YaverFeedback {
|
|
|
781
1157
|
static getConfig() {
|
|
782
1158
|
return config;
|
|
783
1159
|
}
|
|
1160
|
+
/** Current third-party SDK mode. Fails closed unless enabled + account match. */
|
|
1161
|
+
static getDogfoodStatus() {
|
|
1162
|
+
return (0, dogfoodPolicy_1.resolveSDKDogfood)(config?.dogfood);
|
|
1163
|
+
}
|
|
1164
|
+
/** One-call, account-free Dogfood bootstrap for third-party apps. On first
|
|
1165
|
+
* launch it creates/proves the installation key and returns pending; after
|
|
1166
|
+
* owner approval the same call obtains a short-lived scoped Yaver session
|
|
1167
|
+
* and enables Dogfood UX without the host app implementing OAuth. */
|
|
1168
|
+
static async enableDeviceDogfood(options) {
|
|
1169
|
+
const client = new deviceDogfood_1.YaverDeviceDogfood(options);
|
|
1170
|
+
let status = await client.status();
|
|
1171
|
+
if (status === 'unregistered' || status === 'pending' || status === 'cancelled' || status === 'revoked' || status === 'superseded') {
|
|
1172
|
+
const enrolled = status === 'unregistered' || status === 'pending'
|
|
1173
|
+
? await client.enroll(react_native_1.Platform.OS)
|
|
1174
|
+
: await client.reRegister(react_native_1.Platform.OS);
|
|
1175
|
+
status = enrolled.status;
|
|
1176
|
+
}
|
|
1177
|
+
const info = await client.enrollmentInfo();
|
|
1178
|
+
const session = status === 'active' ? await client.session() : null;
|
|
1179
|
+
if (!config)
|
|
1180
|
+
YaverFeedback.init({ autoLogin: false });
|
|
1181
|
+
if (config) {
|
|
1182
|
+
config.dogfood = {
|
|
1183
|
+
enabled: status === 'active' && !!session,
|
|
1184
|
+
appId: options.appId,
|
|
1185
|
+
installationId: info.installationId,
|
|
1186
|
+
installationStatus: status === 'active' && session ? 'active' : status === 'unregistered' ? 'pending' : status,
|
|
1187
|
+
label: options.label,
|
|
1188
|
+
};
|
|
1189
|
+
// A normal Yaver OAuth session remains authoritative for the runtime
|
|
1190
|
+
// wizard. Only account-free hosts adopt the narrow installation token.
|
|
1191
|
+
if (session && !config.authToken)
|
|
1192
|
+
await YaverFeedback.setAuthToken(session.token);
|
|
1193
|
+
}
|
|
1194
|
+
try {
|
|
1195
|
+
const { DeviceEventEmitter } = require('react-native');
|
|
1196
|
+
DeviceEventEmitter.emit('yaverFeedback:dogfoodChanged', { active: !!session, status });
|
|
1197
|
+
}
|
|
1198
|
+
catch { /* noop */ }
|
|
1199
|
+
return { status, installationId: info.installationId, session };
|
|
1200
|
+
}
|
|
1201
|
+
/** Confirmed by the Y badge/UI before this is called. Reverts this SDK to
|
|
1202
|
+
* normal Feedback mode for the remainder of the app run. */
|
|
1203
|
+
static async exitDogfoodMode() {
|
|
1204
|
+
const cfg = config?.dogfood;
|
|
1205
|
+
if (!cfg)
|
|
1206
|
+
return;
|
|
1207
|
+
cfg.enabled = false;
|
|
1208
|
+
await cfg.onExit?.();
|
|
1209
|
+
try {
|
|
1210
|
+
const { DeviceEventEmitter } = require('react-native');
|
|
1211
|
+
DeviceEventEmitter.emit('yaverFeedback:dogfoodChanged', { active: false, exited: true });
|
|
1212
|
+
}
|
|
1213
|
+
catch { /* noop */ }
|
|
1214
|
+
}
|
|
784
1215
|
/** Returns the resolved relay password the SDK is currently using.
|
|
785
1216
|
* Empty string when no relay routing is in play (direct LAN agent
|
|
786
1217
|
* URLs need no password). Callers attaching it to relay-routed
|
|
@@ -843,6 +1274,104 @@ class YaverFeedback {
|
|
|
843
1274
|
next?.(error, isFatal);
|
|
844
1275
|
};
|
|
845
1276
|
}
|
|
1277
|
+
/**
|
|
1278
|
+
* Upload a crash-aware feedback bundle. Apps can call this from their own
|
|
1279
|
+
* error boundary/global handler. If crashReporting.autoFix is true, the SDK
|
|
1280
|
+
* also triggers the agent's feedback-fix task; reload delivery still happens
|
|
1281
|
+
* through the existing BlackBox command stream.
|
|
1282
|
+
*/
|
|
1283
|
+
static async reportCrash(error, opts) {
|
|
1284
|
+
if (!config || !enabled || !config.crashReporting?.enabled)
|
|
1285
|
+
return {};
|
|
1286
|
+
if (crashReportInFlight)
|
|
1287
|
+
return {};
|
|
1288
|
+
crashReportInFlight = true;
|
|
1289
|
+
try {
|
|
1290
|
+
YaverFeedback.attachError(error, {
|
|
1291
|
+
...(config.crashReporting.metadata || {}),
|
|
1292
|
+
...(opts?.metadata || {}),
|
|
1293
|
+
crashAware: true,
|
|
1294
|
+
});
|
|
1295
|
+
if (errorBuffer.length > 0) {
|
|
1296
|
+
errorBuffer[errorBuffer.length - 1].isFatal = opts?.isFatal ?? true;
|
|
1297
|
+
}
|
|
1298
|
+
if (!config.agentUrl) {
|
|
1299
|
+
await YaverFeedback.discoverAgent();
|
|
1300
|
+
}
|
|
1301
|
+
if (!config.agentUrl) {
|
|
1302
|
+
console.warn('[YaverFeedback] No agent URL — cannot send crash report.');
|
|
1303
|
+
return {};
|
|
1304
|
+
}
|
|
1305
|
+
const { Platform, Dimensions } = require('react-native');
|
|
1306
|
+
const { uploadFeedback } = require('./upload');
|
|
1307
|
+
const { width, height } = Dimensions.get('window');
|
|
1308
|
+
let screenshotPath;
|
|
1309
|
+
if (config.crashReporting.captureScreenshot !== false) {
|
|
1310
|
+
try {
|
|
1311
|
+
const { captureScreenshot } = require('./capture');
|
|
1312
|
+
screenshotPath = await captureScreenshot();
|
|
1313
|
+
}
|
|
1314
|
+
catch { }
|
|
1315
|
+
}
|
|
1316
|
+
const autoFix = opts?.autoFix ?? config.crashReporting.autoFix === true;
|
|
1317
|
+
const identity = (0, P2PClient_1.resolveReportIdentity)({
|
|
1318
|
+
projectName: config?.projectName,
|
|
1319
|
+
bundleId: config?.bundleId,
|
|
1320
|
+
surface: config?.surface,
|
|
1321
|
+
surfaces: config?.surfaces,
|
|
1322
|
+
stack: config?.stack,
|
|
1323
|
+
stacks: config?.stacks,
|
|
1324
|
+
testSurfaces: config?.testSurfaces,
|
|
1325
|
+
feedbackSdk: config?.feedbackSdk,
|
|
1326
|
+
feedbackTransport: config?.feedbackTransport,
|
|
1327
|
+
voiceCapabilities: config?.voiceCapabilities,
|
|
1328
|
+
sttProvider: config?.sttProvider,
|
|
1329
|
+
ttsProvider: config?.ttsProvider,
|
|
1330
|
+
});
|
|
1331
|
+
const bundle = {
|
|
1332
|
+
metadata: {
|
|
1333
|
+
timestamp: new Date().toISOString(),
|
|
1334
|
+
reportKind: 'crash',
|
|
1335
|
+
deviceInfo: {
|
|
1336
|
+
platform: Platform.OS,
|
|
1337
|
+
osVersion: String(Platform.Version),
|
|
1338
|
+
model: Platform.OS === 'ios' ? 'iOS Device' : 'Android Device',
|
|
1339
|
+
screenWidth: width,
|
|
1340
|
+
screenHeight: height,
|
|
1341
|
+
appName: identity.appName,
|
|
1342
|
+
},
|
|
1343
|
+
app: identity.app,
|
|
1344
|
+
project: identity.project,
|
|
1345
|
+
userNote: '[Crash report]',
|
|
1346
|
+
crash: {
|
|
1347
|
+
message: error.message,
|
|
1348
|
+
isFatal: opts?.isFatal ?? true,
|
|
1349
|
+
source: opts?.source ?? 'manual',
|
|
1350
|
+
autoFixRequested: autoFix,
|
|
1351
|
+
},
|
|
1352
|
+
},
|
|
1353
|
+
screenshots: screenshotPath ? [screenshotPath] : [],
|
|
1354
|
+
errors: errorBuffer.length > 0 ? [...errorBuffer] : undefined,
|
|
1355
|
+
};
|
|
1356
|
+
const uploaded = await uploadFeedback(config.agentUrl, config.authToken ?? '', bundle, p2pRelayPassword);
|
|
1357
|
+
const reportId = uploaded?.id ??
|
|
1358
|
+
uploaded?.reportId;
|
|
1359
|
+
let taskId;
|
|
1360
|
+
if (autoFix && reportId) {
|
|
1361
|
+
const client = p2pClient ?? new P2PClient_1.P2PClient(config.agentUrl, config.authToken ?? '', p2pRelayPassword);
|
|
1362
|
+
const fix = await client.triggerFix(reportId);
|
|
1363
|
+
taskId = fix?.taskId;
|
|
1364
|
+
}
|
|
1365
|
+
return { reportId, taskId };
|
|
1366
|
+
}
|
|
1367
|
+
catch (err) {
|
|
1368
|
+
console.warn('[YaverFeedback] Crash report failed:', err);
|
|
1369
|
+
return {};
|
|
1370
|
+
}
|
|
1371
|
+
finally {
|
|
1372
|
+
crashReportInFlight = false;
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
846
1375
|
/**
|
|
847
1376
|
* Returns the P2P client instance.
|
|
848
1377
|
* Available after init if agentUrl is set, or after first successful discovery.
|
|
@@ -850,6 +1379,16 @@ class YaverFeedback {
|
|
|
850
1379
|
static getP2PClient() {
|
|
851
1380
|
return p2pClient;
|
|
852
1381
|
}
|
|
1382
|
+
/** Renderer/reload route; intentionally distinct from the coding client. */
|
|
1383
|
+
static getRenderP2PClient() {
|
|
1384
|
+
return renderP2PClient ?? p2pClient;
|
|
1385
|
+
}
|
|
1386
|
+
static getMachineRouting() {
|
|
1387
|
+
return {
|
|
1388
|
+
codingDeviceId: config?.codingDeviceId || config?.preferredDeviceId,
|
|
1389
|
+
renderDeviceId: config?.renderDeviceId || config?.codingDeviceId || config?.preferredDeviceId,
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
853
1392
|
// ─── One-stop SaaS replacement methods ─────────────────────────
|
|
854
1393
|
//
|
|
855
1394
|
// These are the three solo-dev SaaS-replacement entry points
|
|
@@ -960,6 +1499,7 @@ class YaverFeedback {
|
|
|
960
1499
|
config.agentUrl = result.url;
|
|
961
1500
|
const rp = await resolveRelayPassword(config.authToken ?? '');
|
|
962
1501
|
p2pClient = new P2PClient_1.P2PClient(result.url, config.authToken ?? '', rp);
|
|
1502
|
+
renderP2PClient = p2pClient;
|
|
963
1503
|
}
|
|
964
1504
|
}
|
|
965
1505
|
catch { }
|
|
@@ -981,17 +1521,33 @@ class YaverFeedback {
|
|
|
981
1521
|
catch {
|
|
982
1522
|
// Screenshot capture may fail (e.g. no view ref) — continue without it
|
|
983
1523
|
}
|
|
1524
|
+
const identity = (0, P2PClient_1.resolveReportIdentity)({
|
|
1525
|
+
projectName: config?.projectName,
|
|
1526
|
+
bundleId: config?.bundleId,
|
|
1527
|
+
surface: config?.surface,
|
|
1528
|
+
surfaces: config?.surfaces,
|
|
1529
|
+
stack: config?.stack,
|
|
1530
|
+
stacks: config?.stacks,
|
|
1531
|
+
testSurfaces: config?.testSurfaces,
|
|
1532
|
+
feedbackSdk: config?.feedbackSdk,
|
|
1533
|
+
feedbackTransport: config?.feedbackTransport,
|
|
1534
|
+
voiceCapabilities: config?.voiceCapabilities,
|
|
1535
|
+
sttProvider: config?.sttProvider,
|
|
1536
|
+
ttsProvider: config?.ttsProvider,
|
|
1537
|
+
});
|
|
984
1538
|
const bundle = {
|
|
985
1539
|
metadata: {
|
|
986
1540
|
timestamp: new Date().toISOString(),
|
|
987
|
-
|
|
1541
|
+
deviceInfo: {
|
|
988
1542
|
platform: Platform.OS,
|
|
989
1543
|
osVersion: String(Platform.Version),
|
|
990
1544
|
model: Platform.OS === 'ios' ? 'iOS Device' : 'Android Device',
|
|
991
1545
|
screenWidth: width,
|
|
992
1546
|
screenHeight: height,
|
|
1547
|
+
appName: identity.appName,
|
|
993
1548
|
},
|
|
994
|
-
app:
|
|
1549
|
+
app: identity.app,
|
|
1550
|
+
project: identity.project,
|
|
995
1551
|
userNote: '[Auto-report via shake]',
|
|
996
1552
|
},
|
|
997
1553
|
screenshots: screenshotPath ? [screenshotPath] : [],
|
|
@@ -1143,10 +1699,22 @@ class YaverFeedback {
|
|
|
1143
1699
|
shakeDetector.stop();
|
|
1144
1700
|
shakeDetector = null;
|
|
1145
1701
|
}
|
|
1702
|
+
// Drop the agent command handler and un-monkey-patch console. Without
|
|
1703
|
+
// these, destroy() left the SDK half-alive: console stayed wrapped, and
|
|
1704
|
+
// the reload handler kept firing on a "destroyed" SDK — then a later
|
|
1705
|
+
// init() stacked a second one on top.
|
|
1706
|
+
commandUnsubscribe?.();
|
|
1707
|
+
commandUnsubscribe = null;
|
|
1708
|
+
// Before `enabled = false` / `config = null` below, so an in-flight retry
|
|
1709
|
+
// can't fire against a torn-down config.
|
|
1710
|
+
YaverFeedback.cancelBlackBoxAutoStart();
|
|
1711
|
+
BlackBox_1.BlackBox.stop();
|
|
1712
|
+
BlackBox_1.BlackBox.unwrapConsole();
|
|
1146
1713
|
firstShakeFired = false;
|
|
1147
1714
|
enabled = false;
|
|
1148
1715
|
config = null;
|
|
1149
1716
|
p2pClient = null;
|
|
1717
|
+
renderP2PClient = null;
|
|
1150
1718
|
errorBuffer = [];
|
|
1151
1719
|
}
|
|
1152
1720
|
}
|