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.
Files changed (144) hide show
  1. package/README.md +102 -1
  2. package/app.plugin.js +126 -9
  3. package/dist/AuthOverlay.d.ts +1 -14
  4. package/dist/AuthOverlay.js +9 -62
  5. package/dist/BlackBox.js +9 -1
  6. package/dist/DeployPanel.js +65 -0
  7. package/dist/Discovery.js +13 -8
  8. package/dist/DogfoodRuntime.d.ts +124 -0
  9. package/dist/DogfoodRuntime.js +273 -0
  10. package/dist/FeedbackModal.js +505 -65
  11. package/dist/LoginScreen.d.ts +1 -5
  12. package/dist/LoginScreen.js +2 -6
  13. package/dist/MachinePickerScreen.d.ts +1 -3
  14. package/dist/MachinePickerScreen.js +6 -18
  15. package/dist/P2PClient.d.ts +178 -2
  16. package/dist/P2PClient.js +493 -3
  17. package/dist/P2PDogfoodDriver.d.ts +12 -0
  18. package/dist/P2PDogfoodDriver.js +118 -0
  19. package/dist/PairDeviceModal.d.ts +2 -3
  20. package/dist/ShakeDetector.js +2 -0
  21. package/dist/VibeChatScreen.d.ts +11 -1
  22. package/dist/VibeChatScreen.js +200 -41
  23. package/dist/YaverFeedback.d.ts +132 -0
  24. package/dist/YaverFeedback.js +633 -65
  25. package/dist/YaverModeBadge.d.ts +22 -0
  26. package/dist/YaverModeBadge.js +219 -0
  27. package/dist/__tests__/AuthDevices.test.js +1 -48
  28. package/dist/__tests__/BlackBox.relayPassword.test.d.ts +1 -0
  29. package/dist/__tests__/BlackBox.relayPassword.test.js +105 -0
  30. package/dist/__tests__/BlackBoxAutoStart.test.d.ts +1 -0
  31. package/dist/__tests__/BlackBoxAutoStart.test.js +91 -0
  32. package/dist/__tests__/BlackBoxAutoStartColdStart.test.d.ts +1 -0
  33. package/dist/__tests__/BlackBoxAutoStartColdStart.test.js +156 -0
  34. package/dist/__tests__/BrowserLaneIcon.test.d.ts +3 -0
  35. package/dist/__tests__/BrowserLaneIcon.test.js +80 -0
  36. package/dist/__tests__/DogfoodRuntime.test.d.ts +1 -0
  37. package/dist/__tests__/DogfoodRuntime.test.js +116 -0
  38. package/dist/__tests__/P2PClient.test.js +2 -2
  39. package/dist/__tests__/P2PDogfoodDriver.test.d.ts +1 -0
  40. package/dist/__tests__/P2PDogfoodDriver.test.js +64 -0
  41. package/dist/__tests__/ReportIdentity.test.d.ts +25 -0
  42. package/dist/__tests__/ReportIdentity.test.js +180 -0
  43. package/dist/__tests__/SDKToken.test.js +1 -1
  44. package/dist/__tests__/ShakeToggle.test.d.ts +1 -0
  45. package/dist/__tests__/ShakeToggle.test.js +121 -0
  46. package/dist/__tests__/YaverFeedback.test.js +13 -3
  47. package/dist/__tests__/deviceDogfood.test.d.ts +1 -0
  48. package/dist/__tests__/deviceDogfood.test.js +86 -0
  49. package/dist/__tests__/dogfoodPolicy.test.d.ts +1 -0
  50. package/dist/__tests__/dogfoodPolicy.test.js +33 -0
  51. package/dist/__tests__/pickTargetDevice.test.d.ts +1 -0
  52. package/dist/__tests__/pickTargetDevice.test.js +89 -0
  53. package/dist/__tests__/reloadActions.test.d.ts +1 -0
  54. package/dist/__tests__/reloadActions.test.js +129 -0
  55. package/dist/__tests__/reloadActionsParity.test.d.ts +1 -0
  56. package/dist/__tests__/reloadActionsParity.test.js +42 -0
  57. package/dist/__tests__/types.test.js +5 -5
  58. package/dist/_core/ansi.d.ts +117 -0
  59. package/dist/_core/ansi.js +468 -0
  60. package/dist/_core/ansi.test.d.ts +1 -0
  61. package/dist/_core/ansi.test.js +225 -0
  62. package/dist/_core/buildFeedbackPrompt.d.ts +4 -9
  63. package/dist/_core/buildFeedbackPrompt.js +18 -72
  64. package/dist/_core/constants.d.ts +19 -6
  65. package/dist/_core/constants.js +20 -7
  66. package/dist/_core/device.d.ts +1 -5
  67. package/dist/_core/device.js +5 -14
  68. package/dist/_core/endpoints.d.ts +0 -7
  69. package/dist/_core/endpoints.js +0 -7
  70. package/dist/_core/index.d.ts +4 -0
  71. package/dist/_core/index.js +4 -0
  72. package/dist/_core/remoteless.d.ts +44 -0
  73. package/dist/_core/remoteless.js +75 -0
  74. package/dist/_core/trace.d.ts +47 -0
  75. package/dist/_core/trace.js +38 -0
  76. package/dist/_core/trace.test.d.ts +1 -0
  77. package/dist/_core/trace.test.js +60 -0
  78. package/dist/auth.d.ts +3 -60
  79. package/dist/auth.js +6 -88
  80. package/dist/deviceDogfood.d.ts +53 -0
  81. package/dist/deviceDogfood.js +137 -0
  82. package/dist/dogfoodPolicy.d.ts +25 -0
  83. package/dist/dogfoodPolicy.js +24 -0
  84. package/dist/index.d.ts +17 -4
  85. package/dist/index.js +33 -7
  86. package/dist/reloadActions.d.ts +88 -0
  87. package/dist/reloadActions.js +200 -0
  88. package/dist/storeShots.d.ts +67 -0
  89. package/dist/storeShots.js +137 -0
  90. package/dist/types.d.ts +208 -8
  91. package/package.json +13 -4
  92. package/src/AuthOverlay.tsx +20 -106
  93. package/src/BlackBox.ts +10 -1
  94. package/src/DeployPanel.tsx +74 -0
  95. package/src/Discovery.ts +13 -8
  96. package/src/DogfoodRuntime.ts +373 -0
  97. package/src/FeedbackModal.tsx +619 -79
  98. package/src/LoginScreen.tsx +2 -22
  99. package/src/MachinePickerScreen.tsx +6 -21
  100. package/src/P2PClient.ts +580 -4
  101. package/src/P2PDogfoodDriver.ts +132 -0
  102. package/src/PairDeviceModal.tsx +2 -3
  103. package/src/ShakeDetector.ts +1 -0
  104. package/src/VibeChatScreen.tsx +232 -42
  105. package/src/YaverFeedback.ts +631 -68
  106. package/src/YaverModeBadge.tsx +234 -0
  107. package/src/__tests__/AuthDevices.test.ts +1 -52
  108. package/src/__tests__/BlackBox.relayPassword.test.ts +129 -0
  109. package/src/__tests__/BlackBoxAutoStart.test.ts +111 -0
  110. package/src/__tests__/BlackBoxAutoStartColdStart.test.ts +191 -0
  111. package/src/__tests__/BrowserLaneIcon.test.ts +85 -0
  112. package/src/__tests__/DogfoodRuntime.test.ts +135 -0
  113. package/src/__tests__/P2PClient.test.ts +2 -2
  114. package/src/__tests__/P2PDogfoodDriver.test.ts +80 -0
  115. package/src/__tests__/ReportIdentity.test.ts +212 -0
  116. package/src/__tests__/SDKToken.test.ts +1 -1
  117. package/src/__tests__/ShakeToggle.test.ts +153 -0
  118. package/src/__tests__/YaverFeedback.test.ts +15 -3
  119. package/src/__tests__/deviceDogfood.test.ts +77 -0
  120. package/src/__tests__/dogfoodPolicy.test.ts +34 -0
  121. package/src/__tests__/pickTargetDevice.test.ts +101 -0
  122. package/src/__tests__/reloadActions.test.ts +171 -0
  123. package/src/__tests__/reloadActionsParity.test.ts +49 -0
  124. package/src/__tests__/types.test.ts +5 -5
  125. package/src/_core/ansi.test.ts +250 -0
  126. package/src/_core/ansi.ts +475 -0
  127. package/src/_core/buildFeedbackPrompt.ts +18 -95
  128. package/src/_core/constants.ts +20 -6
  129. package/src/_core/device.ts +5 -16
  130. package/src/_core/endpoints.ts +0 -7
  131. package/src/_core/index.ts +4 -0
  132. package/src/_core/remoteless.ts +110 -0
  133. package/src/_core/trace.test.ts +58 -0
  134. package/src/_core/trace.ts +75 -0
  135. package/src/auth.ts +7 -156
  136. package/src/deviceDogfood.ts +171 -0
  137. package/src/dogfoodPolicy.ts +40 -0
  138. package/src/index.ts +61 -11
  139. package/src/reloadActions.ts +273 -0
  140. package/src/storeShots.ts +189 -0
  141. package/src/types.ts +209 -8
  142. package/dist/GuestOnboardingScreen.d.ts +0 -8
  143. package/dist/GuestOnboardingScreen.js +0 -282
  144. package/src/GuestOnboardingScreen.tsx +0 -307
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ /**
3
+ * On-device App Store screenshot capture (Engine 2).
4
+ *
5
+ * The desktop `yaver shots` flow drives a simulator with Maestro. This is
6
+ * the in-app counterpart: the SDK, living inside the real running app,
7
+ * walks the app's own routes and screenshots each one with
8
+ * `react-native-view-shot`, then uploads the frames to the Yaver agent
9
+ * (POST /shots/upload). The agent normalizes them and runs the same App
10
+ * Store Connect backend (upload → metadata → submit).
11
+ *
12
+ * Why this exists alongside the simulator path: it captures *real device*
13
+ * pixels, knows the exact route map (no heuristics), and the user is
14
+ * already authenticated — so it sidesteps the i18n / no-testID fragility
15
+ * that makes a blind simulator walk hard.
16
+ *
17
+ * The host wires it once by handing us a navigation ref (react-navigation
18
+ * or expo-router router) plus the ordered list of routes to visit.
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.captureStoreScreenshots = captureStoreScreenshots;
22
+ const capture_1 = require("./capture");
23
+ function sleep(ms) {
24
+ return new Promise((r) => setTimeout(r, ms));
25
+ }
26
+ function navigateTo(navRef, route) {
27
+ if (!navRef)
28
+ return;
29
+ // react-navigation ref
30
+ if (typeof navRef.navigate === 'function') {
31
+ navRef.navigate(route);
32
+ return;
33
+ }
34
+ // expo-router router
35
+ if (typeof navRef.push === 'function') {
36
+ navRef.push(route);
37
+ return;
38
+ }
39
+ if (navRef.current && typeof navRef.current.navigate === 'function') {
40
+ navRef.current.navigate(route);
41
+ return;
42
+ }
43
+ }
44
+ function shotName(route, index, override) {
45
+ if (override)
46
+ return override;
47
+ const clean = route
48
+ .replace(/^\/+/, '')
49
+ .replace(/[()[\]/]+/g, '_')
50
+ .replace(/[^a-zA-Z0-9_]+/g, '')
51
+ .replace(/^_+|_+$/g, '');
52
+ const n = String(index + 1).padStart(2, '0');
53
+ return `${n}_${clean || 'screen'}`;
54
+ }
55
+ /**
56
+ * Walk the app's routes, screenshot each, and upload the batch to the
57
+ * agent. Returns a summary; never throws for a single missed route —
58
+ * it captures what it can and reports the count.
59
+ */
60
+ async function captureStoreScreenshots(opts) {
61
+ if (!opts.agentUrl || !opts.authToken) {
62
+ return { ok: false, captured: 0, uploaded: 0, message: 'agentUrl + authToken required' };
63
+ }
64
+ if (!opts.routes?.length) {
65
+ return { ok: false, captured: 0, uploaded: 0, message: 'no routes to capture' };
66
+ }
67
+ const settleMs = opts.settleMs ?? 900;
68
+ const frames = [];
69
+ for (let i = 0; i < opts.routes.length; i++) {
70
+ const route = opts.routes[i];
71
+ try {
72
+ navigateTo(opts.navigationRef, route);
73
+ await sleep(settleMs);
74
+ if (opts.onBeforeCapture)
75
+ await opts.onBeforeCapture(route, i);
76
+ const shot = await (0, capture_1.captureScreenshotBase64)();
77
+ if (shot?.base64) {
78
+ frames.push({
79
+ route: shotName(route, i, opts.screens?.[i]),
80
+ base64: shot.base64,
81
+ mimeType: shot.mimeType,
82
+ });
83
+ }
84
+ }
85
+ catch {
86
+ // Skip a route that failed to render — keep walking.
87
+ }
88
+ }
89
+ if (frames.length === 0) {
90
+ return { ok: false, captured: 0, uploaded: 0, message: 'captured no frames' };
91
+ }
92
+ const headers = {
93
+ Authorization: `Bearer ${opts.authToken}`,
94
+ 'Content-Type': 'application/json',
95
+ };
96
+ if (opts.relayPassword)
97
+ headers['X-Relay-Password'] = opts.relayPassword;
98
+ const base = opts.agentUrl.replace(/\/$/, '');
99
+ try {
100
+ const resp = await fetch(`${base}/shots/upload`, {
101
+ method: 'POST',
102
+ headers,
103
+ body: JSON.stringify({
104
+ app: opts.app,
105
+ bundleId: opts.bundleId ?? '',
106
+ locale: opts.locale ?? 'en-US',
107
+ submit: !!opts.submit,
108
+ frames,
109
+ }),
110
+ });
111
+ const j = await resp.json().catch(() => ({}));
112
+ if (!resp.ok || j?.ok === false) {
113
+ return {
114
+ ok: false,
115
+ captured: frames.length,
116
+ uploaded: 0,
117
+ message: j?.error || `agent /shots/upload HTTP ${resp.status}`,
118
+ };
119
+ }
120
+ return {
121
+ ok: true,
122
+ captured: frames.length,
123
+ uploaded: typeof j?.uploaded === 'number' ? j.uploaded : frames.length,
124
+ submitted: j?.submitted,
125
+ staged: j?.staged,
126
+ message: j?.message,
127
+ };
128
+ }
129
+ catch (e) {
130
+ return {
131
+ ok: false,
132
+ captured: frames.length,
133
+ uploaded: 0,
134
+ message: `upload failed: ${e?.message ?? 'network error'}`,
135
+ };
136
+ }
137
+ }
package/dist/types.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { BlackBoxConfig } from './BlackBox';
2
+ import type { SDKDogfoodConfig } from './dogfoodPolicy';
1
3
  /**
2
4
  * Remote browser-style sign-in session for a coding-agent CLI on the
3
5
  * connected yaver host. Mirrors runnerBrowserAuthSession on the agent
@@ -31,6 +33,13 @@ export interface RunnerAuthStatusRow {
31
33
  path?: string;
32
34
  detail?: string;
33
35
  version?: string;
36
+ isDefault?: boolean;
37
+ models?: Array<{
38
+ id: string;
39
+ name?: string;
40
+ provider?: string;
41
+ isDefault?: boolean;
42
+ }>;
34
43
  }
35
44
  export interface OpenCodeProviderSummary {
36
45
  id: string;
@@ -121,9 +130,66 @@ export interface CapabilitySnapshot {
121
130
  };
122
131
  targets: Record<string, CapabilityTargetReadiness>;
123
132
  }
133
+ /**
134
+ * Opt-in config for the overlay's "App Store screenshots" action and the
135
+ * `capture_store_shots` remote command. When set, the SDK can walk the
136
+ * app's routes on-device, screenshot each, and upload them to the agent
137
+ * (which runs the App Store Connect backend).
138
+ */
139
+ export interface StoreShotsConfig {
140
+ /** Ordered routes to visit + screenshot (e.g. ['/(tabs)/home', ...]). */
141
+ routes: string[];
142
+ /** Navigation handle: a react-navigation ref or expo-router `router`. */
143
+ navigationRef?: any;
144
+ /** Also set metadata + attempt submit-for-review after upload. */
145
+ submit?: boolean;
146
+ /** Optional per-route screenshot names (defaults to NN_<route>). */
147
+ screens?: string[];
148
+ }
124
149
  export interface FeedbackConfig {
125
150
  /** URL of the Yaver agent (e.g. "http://192.168.1.10:18080"). If omitted, auto-discovery is used. */
126
151
  agentUrl?: string;
152
+ /** Optional coding route. Defaults to agentUrl. */
153
+ codingAgentUrl?: string;
154
+ /** Optional render/reload route. Defaults to codingAgentUrl/agentUrl. */
155
+ renderAgentUrl?: string;
156
+ /** Device identities shown in the unified Settings card when routes differ. */
157
+ codingDeviceId?: string;
158
+ renderDeviceId?: string;
159
+ /**
160
+ * Show the small "Y" mark while this app is running INSIDE Yaver's container
161
+ * (a Hermes-pushed development bundle), so a tester can tell it apart from
162
+ * the build they installed — and can find the way back.
163
+ *
164
+ * DEFAULT TRUE. The failure it prevents is silent and lands on a tester
165
+ * rather than a developer: an app that looks installed but is someone's
166
+ * branch produces "bug reports" against unfinished work, and a tester with
167
+ * no visible way out is simply stuck. That class should not be opt-in.
168
+ *
169
+ * Costs nothing in a standalone build — outside Yaver's container the badge
170
+ * renders null (the YaverInfo native module is absent), so shipping with it
171
+ * enabled has no effect on what your users see.
172
+ *
173
+ * Set false if your app already shows its own in-container indicator, or if
174
+ * a pixel-exact surface cannot spare 22 points in a corner.
175
+ */
176
+ modeBadge?: boolean;
177
+ /** Corner for the mode badge. Default 'bottom-left' — bottom-right is where
178
+ * most apps put a FAB, and the badge must never compete with the app's own
179
+ * primary action. */
180
+ modeBadgePosition?: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right';
181
+ /** Account-scoped direct Dogfood UX for an approved third-party app.
182
+ * When the current app account is allowlisted, the small Y opens Vibing
183
+ * directly instead of the normal feedback actions. This is a client UX gate,
184
+ * never an authentication mechanism; agent calls still require Yaver auth. */
185
+ dogfood?: SDKDogfoodConfig;
186
+ /**
187
+ * Enables the overlay's "App Store screenshots" action + the
188
+ * `capture_store_shots` remote command. The host supplies the route
189
+ * list (and a navigation ref) once; the SDK captures the real running
190
+ * app — no simulator needed.
191
+ */
192
+ storeShots?: StoreShotsConfig;
127
193
  /**
128
194
  * Auth token for the Yaver agent. Optional in 0.5+: if omitted, the SDK
129
195
  * will hydrate one from AsyncStorage or show its in-app login screen
@@ -163,6 +229,47 @@ export interface FeedbackConfig {
163
229
  * If omitted with convexUrl, connects to the first online device.
164
230
  */
165
231
  preferredDeviceId?: string;
232
+ /**
233
+ * Identity of the app this SDK instance reports for. Both fields are
234
+ * resolved automatically from expo-constants / native modules when
235
+ * omitted, so most apps never set them.
236
+ *
237
+ * Set them when the ambient answer would be wrong — above all when the
238
+ * app's JS is pushed into Yaver's container as a Hermes guest. There the
239
+ * runtime IS Yaver: `expo-constants` and `SettingsManager` describe the
240
+ * host (`Yaver` / `io.yaver.mobile`), not the guest, so auto-resolution
241
+ * would file every report against Yaver's own repo and the fix task would
242
+ * edit the SDK instead of the app. Declaring identity here is the only
243
+ * answer that holds in both a standalone build and a guest bundle.
244
+ *
245
+ * `bundleId` is the key that actually routes: the agent matches it against
246
+ * each project's pbxproj / build.gradle / app.json. `projectName` is a
247
+ * fallback for platforms with no bundle id.
248
+ */
249
+ projectName?: string;
250
+ bundleId?: string;
251
+ /** Primary Yaver product surface for this app/package. */
252
+ surface?: YaverSurface;
253
+ /** All product surfaces this app/package owns. */
254
+ surfaces?: YaverSurface[];
255
+ /** Canonical development stack labels, e.g. react-native-expo, yaver-xml. */
256
+ stack?: string;
257
+ stacks?: string[];
258
+ /** Yaver preview/runtime targets that can exercise this app. */
259
+ testSurfaces?: string[];
260
+ feedbackSdk?: string;
261
+ feedbackTransport?: string;
262
+ voiceCapabilities?: string[];
263
+ sttProvider?: string;
264
+ ttsProvider?: string;
265
+ /**
266
+ * Opt in to the microphone/STT controls in the feedback Vibing card.
267
+ *
268
+ * This is deliberately false unless explicitly enabled: ordinary phone
269
+ * and tablet surfaces stay keyboard-first. Car/watch-style hosts that are
270
+ * inherently voice-driven may set this to true.
271
+ */
272
+ voiceInputEnabled?: boolean;
166
273
  /** How feedback collection is triggered */
167
274
  trigger?: 'shake' | 'floating-button' | 'manual';
168
275
  /**
@@ -203,6 +310,22 @@ export interface FeedbackConfig {
203
310
  * Calling `BlackBox.start()` manually is still safe — it's idempotent.
204
311
  */
205
312
  autoStartBlackBox?: boolean;
313
+ /**
314
+ * Config handed to BlackBox when the SDK auto-starts it.
315
+ *
316
+ * Without this there was no way to configure the flight recorder at all.
317
+ * The obvious move — and the one the README's own snippet makes — is:
318
+ *
319
+ * YaverFeedback.init({ trigger: 'shake' });
320
+ * BlackBox.start({ appName: 'my-app' });
321
+ *
322
+ * but that start() early-returns, because a zero-config init has no
323
+ * agentUrl yet (discovery resolves it asynchronously). The auto-start then
324
+ * fires ~500ms later once the agent and token exist and calls `start()`
325
+ * with NO arguments — so the host's config is silently dropped and appName
326
+ * is ''. Pass it here instead and the auto-start will honour it.
327
+ */
328
+ blackBox?: BlackBoxConfig;
206
329
  /**
207
330
  * Small tap-to-open icon that floats above the app so the user
208
331
  * doesn't have to shake every time they want to open feedback.
@@ -291,6 +414,26 @@ export interface FeedbackConfig {
291
414
  * Bugsnag, or any other error tracking tool.
292
415
  */
293
416
  maxCapturedErrors?: number;
417
+ /**
418
+ * Crash-aware reporting/fixing. Defaults to disabled.
419
+ *
420
+ * - enabled: records fatal errors into the SDK error buffer and enables
421
+ * explicit `YaverFeedback.reportCrash(error)`.
422
+ * - installGlobalHandler: opt-in only. Wraps React Native's global ErrorUtils
423
+ * handler without suppressing the existing handler. Leave false when Sentry,
424
+ * Crashlytics, Bugsnag, or another crash tool owns the global handler.
425
+ * - autoFix: after uploading a crash report, ask the connected Yaver agent to
426
+ * create a fix task. The normal BlackBox command stream can then deliver a
427
+ * reload/reload_bundle command after the agent rebuilds.
428
+ * - captureScreenshot: attach a best-effort screenshot to the crash report.
429
+ */
430
+ crashReporting?: {
431
+ enabled?: boolean;
432
+ installGlobalHandler?: boolean;
433
+ autoFix?: boolean;
434
+ captureScreenshot?: boolean;
435
+ metadata?: Record<string, unknown>;
436
+ };
294
437
  /**
295
438
  * Which platforms the Build button targets.
296
439
  * - 'ios' — build iOS only
@@ -370,13 +513,6 @@ export interface FeedbackConfig {
370
513
  * Default: false (preserve historical behavior).
371
514
  */
372
515
  strictNativeAuth?: boolean;
373
- /**
374
- * Optional host invite code to prefill into the in-SDK guest onboarding
375
- * flow. Useful when your app receives the code from a deep link, QR flow,
376
- * or an out-of-band host handoff and you want the user to redeem it
377
- * without typing.
378
- */
379
- guestInviteCode?: string;
380
516
  }
381
517
  export interface FeedbackBundle {
382
518
  metadata: FeedbackMetadata;
@@ -403,9 +539,36 @@ export interface CapturedError {
403
539
  }
404
540
  export interface FeedbackMetadata {
405
541
  timestamp: string;
406
- device: DeviceInfo;
542
+ /**
543
+ * Wire key is `deviceInfo`, matching the Flutter + web SDKs and the
544
+ * agent's `FeedbackReport.DeviceInfo` (`json:"deviceInfo"`). This was
545
+ * `device` until 0.9.2 — a key the agent never read, so the whole block
546
+ * (platform, model, and the app name the fix router needs) was silently
547
+ * dropped on every React Native report. The agent still accepts the old
548
+ * key so builds already in the field keep working.
549
+ */
550
+ deviceInfo: DeviceInfo;
407
551
  app: AppInfo;
552
+ /**
553
+ * Which of the host's projects this report is about. The agent resolves
554
+ * a feedback→fix task's working directory from this; without it the task
555
+ * falls back to whatever directory the agent happens to be sitting in.
556
+ * Populated automatically by `resolveReportIdentity()`.
557
+ */
558
+ project?: FeedbackProjectRef;
408
559
  userNote?: string;
560
+ reportKind?: 'feedback' | 'auto-report' | 'crash';
561
+ crash?: {
562
+ message: string;
563
+ isFatal: boolean;
564
+ source: 'manual' | 'global-handler';
565
+ autoFixRequested?: boolean;
566
+ };
567
+ twin?: {
568
+ sessionId?: string;
569
+ surface?: string;
570
+ artifactDir?: string;
571
+ };
409
572
  }
410
573
  export interface DeviceInfo {
411
574
  platform: string;
@@ -413,12 +576,49 @@ export interface DeviceInfo {
413
576
  model: string;
414
577
  screenWidth: number;
415
578
  screenHeight: number;
579
+ /** Host app name. First key the agent's fix router checks. */
580
+ appName?: string;
416
581
  }
417
582
  export interface AppInfo {
418
583
  bundleId?: string;
419
584
  version?: string;
420
585
  buildNumber?: string;
421
586
  }
587
+ /**
588
+ * Identifies the host project a report belongs to. Mirrors the web SDK's
589
+ * `FeedbackProjectRef` and the agent's `FeedbackProject`.
590
+ */
591
+ export interface FeedbackProjectRef {
592
+ appName?: string;
593
+ projectName?: string;
594
+ /**
595
+ * Bundle identifier / applicationId. The only unambiguous key: an app's
596
+ * display name ("Talos") does not match the agent's registry name for it
597
+ * ("talos / mobile"), and one repo can hold several mobile projects that
598
+ * all share a name prefix. The agent matches this against the project's
599
+ * pbxproj, build.gradle, and app.json.
600
+ */
601
+ bundleId?: string;
602
+ /**
603
+ * Deliberately never set by this SDK. The agent ignores client-supplied
604
+ * paths for feedback reports (an untrusted guest could otherwise point the
605
+ * fix task's CWD at ~/.ssh) and resolves the path server-side. Present only
606
+ * so the type matches the wire format the agent parses.
607
+ */
608
+ projectPath?: string;
609
+ surface?: YaverSurface;
610
+ surfaces?: YaverSurface[];
611
+ stack?: string;
612
+ stacks?: string[];
613
+ testSurfaces?: string[];
614
+ feedbackSdk?: string;
615
+ feedbackTransport?: string;
616
+ voiceCapabilities?: string[];
617
+ sttProvider?: string;
618
+ ttsProvider?: string;
619
+ releaseChannel?: 'production' | 'candidate' | 'development';
620
+ }
621
+ export type YaverSurface = 'web' | 'mobile' | 'backend' | 'watch' | 'tv' | 'car' | 'vision' | 'desktop' | 'cli';
422
622
  export interface TimelineEvent {
423
623
  type: 'screenshot' | 'audio' | 'video';
424
624
  path: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "yaver-feedback-react-native",
3
- "version": "0.9.0",
4
- "description": "Visual feedback SDK for Yaver \u2014 bug reports, screen recording, voice vibe coding, and local-first developer workflows",
3
+ "version": "0.9.3",
4
+ "description": "Visual feedback SDK for Yaver bug reports, screen recording, voice vibe coding, and local-first developer workflows",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "files": [
@@ -28,10 +28,13 @@
28
28
  "react-native-record-screen": ">=0.0.10",
29
29
  "expo-av": ">=14.0.0",
30
30
  "expo-file-system": ">=16.0.0",
31
- "expo-speech": ">=12.0.0"
31
+ "expo-speech": ">=12.0.0",
32
+ "expo-secure-store": ">=13.0.0",
33
+ "expo-crypto": ">=13.0.0"
32
34
  },
33
35
  "dependencies": {
34
- "buffer": "^6.0.3"
36
+ "buffer": "^6.0.3",
37
+ "tweetnacl": "^1.0.3"
35
38
  },
36
39
  "peerDependenciesMeta": {
37
40
  "expo-file-system": {
@@ -40,6 +43,12 @@
40
43
  "expo-speech": {
41
44
  "optional": true
42
45
  },
46
+ "expo-secure-store": {
47
+ "optional": true
48
+ },
49
+ "expo-crypto": {
50
+ "optional": true
51
+ },
43
52
  "@expo/config-plugins": {
44
53
  "optional": true
45
54
  },
@@ -2,83 +2,50 @@ import React, { useCallback, useEffect, useRef, useState } from 'react';
2
2
  import { DeviceEventEmitter, Modal } from 'react-native';
3
3
  import { YaverLoginScreen } from './LoginScreen';
4
4
  import { YaverMachinePickerScreen } from './MachinePickerScreen';
5
- import { YaverGuestOnboardingScreen } from './GuestOnboardingScreen';
6
5
  import { YaverFeedback } from './YaverFeedback';
7
- import { getToken, RemoteDevice, listReachableDevices } from './auth';
6
+ import { getToken, RemoteDevice } from './auth';
8
7
 
9
- /**
10
- * Presentation layer for the SDK's auth + machine-picker modals.
11
- *
12
- * Mounts automatically inside `<FeedbackModal />`, so consumers of the SDK
13
- * get in-app login with no extra wiring. It listens for events emitted by
14
- * `YaverFeedback.showLogin()` / `showMachinePicker()`:
15
- *
16
- * yaverFeedback:startLogin → show login modal
17
- * yaverFeedback:startMachinePicker → show machine picker
18
- *
19
- * The overlay closes itself once login/pick succeeds, then re-emits
20
- * `yaverFeedback:startReport` so the user continues straight into the
21
- * feedback flow they originally triggered.
22
- */
8
+ /** Owner sign-in and owner-machine selection for the Feedback SDK. */
23
9
  export const AuthOverlay: React.FC = () => {
24
10
  const [loginVisible, setLoginVisible] = useState(false);
25
- const [guestVisible, setGuestVisible] = useState(false);
26
11
  const [pickerVisible, setPickerVisible] = useState(false);
27
12
  const [token, setToken] = useState<string | null>(null);
28
- const [pendingInviteCode, setPendingInviteCode] = useState<string | null>(null);
29
- const activeOverlayRef = useRef<'none' | 'login' | 'guest' | 'picker'>('none');
13
+ const activeOverlayRef = useRef<'none' | 'login' | 'picker'>('none');
30
14
 
31
15
  const openLogin = useCallback(() => {
32
16
  activeOverlayRef.current = 'login';
33
- setGuestVisible(false);
34
17
  setPickerVisible(false);
35
18
  setLoginVisible(true);
36
19
  }, []);
37
20
 
38
- const openGuest = useCallback(() => {
39
- activeOverlayRef.current = 'guest';
40
- setLoginVisible(false);
41
- setPickerVisible(false);
42
- setGuestVisible(true);
43
- }, []);
44
-
45
21
  const openPicker = useCallback(() => {
46
22
  activeOverlayRef.current = 'picker';
47
23
  setLoginVisible(false);
48
- setGuestVisible(false);
49
24
  setPickerVisible(true);
50
25
  }, []);
51
26
 
52
27
  const closeAll = useCallback(() => {
53
28
  activeOverlayRef.current = 'none';
54
29
  setLoginVisible(false);
55
- setGuestVisible(false);
56
30
  setPickerVisible(false);
57
31
  }, []);
58
32
 
59
33
  useEffect(() => {
60
34
  let mounted = true;
61
- (async () => {
62
- const cached = await getToken();
35
+ void getToken().then((cached) => {
63
36
  if (mounted && cached) setToken(cached);
64
- })();
65
-
66
- const loginSub = DeviceEventEmitter.addListener(
67
- 'yaverFeedback:startLogin',
68
- () => {
69
- if (activeOverlayRef.current !== 'none') return;
70
- openLogin();
71
- },
72
- );
73
- const pickerSub = DeviceEventEmitter.addListener(
74
- 'yaverFeedback:startMachinePicker',
75
- async () => {
76
- if (activeOverlayRef.current !== 'none') return;
77
- const cached = await getToken();
78
- if (cached) setToken(cached);
79
- if (cached) openPicker();
80
- },
81
- );
37
+ });
38
+ const loginSub = DeviceEventEmitter.addListener('yaverFeedback:startLogin', () => {
39
+ if (activeOverlayRef.current === 'none') openLogin();
40
+ });
41
+ const pickerSub = DeviceEventEmitter.addListener('yaverFeedback:startMachinePicker', async () => {
42
+ if (activeOverlayRef.current !== 'none') return;
43
+ const cached = await getToken();
44
+ if (cached) {
45
+ setToken(cached);
46
+ openPicker();
47
+ }
48
+ });
82
49
  return () => {
83
50
  mounted = false;
84
51
  loginSub.remove();
@@ -86,55 +53,24 @@ export const AuthOverlay: React.FC = () => {
86
53
  };
87
54
  }, [openLogin, openPicker]);
88
55
 
89
- const continueAfterAuth = async (newToken: string, inviteCode?: string) => {
56
+ const handleLoggedIn = async (newToken: string) => {
90
57
  setToken(newToken);
91
58
  await YaverFeedback.setAuthToken(newToken);
92
- const devices = await listReachableDevices(newToken).catch(() => ({ owned: [], shared: [] }));
93
- const cleanedInviteCode = (inviteCode ?? '').trim().toUpperCase();
94
- if (cleanedInviteCode) {
95
- setPendingInviteCode(cleanedInviteCode);
96
- openGuest();
97
- return;
98
- }
99
- if (devices.owned.length === 0 && devices.shared.length === 0) {
100
- openGuest();
101
- return;
102
- }
103
59
  openPicker();
104
60
  };
105
61
 
106
- const handleLoggedIn = async (newToken: string, opts?: { inviteCode?: string }) => {
107
- await continueAfterAuth(newToken, opts?.inviteCode);
108
- };
109
-
110
62
  const handleDevicePicked = async (device: RemoteDevice) => {
111
63
  await YaverFeedback.setPreferredDevice(device.deviceId);
112
64
  closeAll();
113
- // Continue straight into the feedback flow the user originally triggered.
114
65
  DeviceEventEmitter.emit('yaverFeedback:startReport');
115
66
  };
116
67
 
117
68
  return (
118
69
  <>
119
- <Modal
120
- visible={loginVisible}
121
- animationType="slide"
122
- presentationStyle="fullScreen"
123
- onRequestClose={closeAll}
124
- >
125
- <YaverLoginScreen
126
- onLoggedIn={handleLoggedIn}
127
- onCancel={closeAll}
128
- initialInviteCode={pendingInviteCode ?? YaverFeedback.getConfig()?.guestInviteCode}
129
- />
70
+ <Modal visible={loginVisible} animationType="slide" presentationStyle="fullScreen" onRequestClose={closeAll}>
71
+ <YaverLoginScreen onLoggedIn={handleLoggedIn} onCancel={closeAll} />
130
72
  </Modal>
131
-
132
- <Modal
133
- visible={pickerVisible && !!token}
134
- animationType="slide"
135
- presentationStyle="fullScreen"
136
- onRequestClose={closeAll}
137
- >
73
+ <Modal visible={pickerVisible && !!token} animationType="slide" presentationStyle="fullScreen" onRequestClose={closeAll}>
138
74
  {token && (
139
75
  <YaverMachinePickerScreen
140
76
  token={token}
@@ -144,28 +80,6 @@ export const AuthOverlay: React.FC = () => {
144
80
  />
145
81
  )}
146
82
  </Modal>
147
-
148
- <Modal
149
- visible={guestVisible && !!token}
150
- animationType="slide"
151
- presentationStyle="fullScreen"
152
- onRequestClose={closeAll}
153
- >
154
- {token && (
155
- <YaverGuestOnboardingScreen
156
- token={token}
157
- initialInviteCode={pendingInviteCode ?? YaverFeedback.getConfig()?.guestInviteCode}
158
- onContinue={() => {
159
- setPendingInviteCode(null);
160
- openPicker();
161
- }}
162
- onCancel={() => {
163
- setPendingInviteCode(null);
164
- openPicker();
165
- }}
166
- />
167
- )}
168
- </Modal>
169
83
  </>
170
84
  );
171
85
  };
package/src/BlackBox.ts CHANGED
@@ -66,6 +66,13 @@ export interface BlackBoxCommand {
66
66
  /** Callback type for handling agent commands. */
67
67
  export type CommandHandler = (cmd: BlackBoxCommand) => void;
68
68
 
69
+ function unrefTimer(timer: ReturnType<typeof setTimeout> | ReturnType<typeof setInterval>): void {
70
+ const maybeNodeTimer = timer as unknown as { unref?: () => void };
71
+ if (typeof maybeNodeTimer.unref === 'function') {
72
+ maybeNodeTimer.unref();
73
+ }
74
+ }
75
+
69
76
  export class BlackBox {
70
77
  private static baseUrl: string | null = null;
71
78
  private static authToken: string | null = null;
@@ -109,7 +116,7 @@ export class BlackBox {
109
116
 
110
117
  BlackBox.baseUrl = feedbackConfig.agentUrl.replace(/\/$/, '');
111
118
  BlackBox.authToken = feedbackConfig.authToken ?? null;
112
- BlackBox.relayPassword = (feedbackConfig as { relayPassword?: string }).relayPassword ?? '';
119
+ BlackBox.relayPassword = YaverFeedback.getRelayPassword();
113
120
  BlackBox.deviceId = config?.deviceId ?? BlackBox.generateDeviceId();
114
121
  BlackBox.appName = config?.appName ?? '';
115
122
  BlackBox.flushInterval = config?.flushInterval ?? 2000;
@@ -120,6 +127,7 @@ export class BlackBox {
120
127
  // Start periodic flush
121
128
  if (BlackBox.flushTimer) clearInterval(BlackBox.flushTimer);
122
129
  BlackBox.flushTimer = setInterval(() => BlackBox.flush(), BlackBox.flushInterval);
130
+ unrefTimer(BlackBox.flushTimer);
123
131
 
124
132
  // Log the session start
125
133
  BlackBox.push({
@@ -471,6 +479,7 @@ export class BlackBox {
471
479
  BlackBox.sseReconnectTimer = null;
472
480
  if (BlackBox.started) BlackBox.connectSSE();
473
481
  }, 5000);
482
+ unrefTimer(BlackBox.sseReconnectTimer);
474
483
  }
475
484
 
476
485
  // ─── Internal ────────────────────────────────────────────────────