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
|
@@ -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
|
-
|
|
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.
|
|
4
|
-
"description": "Visual feedback SDK for Yaver
|
|
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
|
},
|
package/src/AuthOverlay.tsx
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
62
|
-
const cached = await getToken();
|
|
35
|
+
void getToken().then((cached) => {
|
|
63
36
|
if (mounted && cached) setToken(cached);
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
|
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
|
-
|
|
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 = (
|
|
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 ────────────────────────────────────────────────────
|