yaver-feedback-react-native 0.9.0 → 0.9.2
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/app.plugin.js +126 -9
- package/dist/BlackBox.js +9 -1
- package/dist/DeployPanel.js +65 -0
- package/dist/Discovery.js +13 -2
- package/dist/FeedbackModal.js +160 -6
- package/dist/P2PClient.d.ts +64 -1
- package/dist/P2PClient.js +222 -2
- package/dist/ShakeDetector.js +2 -0
- package/dist/YaverFeedback.d.ts +98 -0
- package/dist/YaverFeedback.js +509 -46
- 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__/P2PClient.test.js +2 -2
- package/dist/__tests__/ReportIdentity.test.d.ts +1 -0
- package/dist/__tests__/ReportIdentity.test.js +168 -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__/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/device.d.ts +19 -9
- package/dist/_core/device.js +20 -14
- package/dist/index.d.ts +4 -0
- package/dist/index.js +11 -1
- 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 +158 -1
- package/package.json +2 -2
- package/src/BlackBox.ts +10 -1
- package/src/DeployPanel.tsx +74 -0
- package/src/Discovery.ts +13 -2
- package/src/FeedbackModal.tsx +176 -14
- package/src/P2PClient.ts +235 -2
- package/src/ShakeDetector.ts +1 -0
- package/src/YaverFeedback.ts +498 -46
- 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__/P2PClient.test.ts +2 -2
- package/src/__tests__/ReportIdentity.test.ts +203 -0
- package/src/__tests__/SDKToken.test.ts +1 -1
- package/src/__tests__/ShakeToggle.test.ts +153 -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/device.ts +20 -14
- package/src/index.ts +21 -0
- package/src/reloadActions.ts +273 -0
- package/src/storeShots.ts +189 -0
- package/src/types.ts +164 -1
package/src/P2PClient.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Platform } from 'react-native';
|
|
2
2
|
import {
|
|
3
|
+
AppInfo,
|
|
3
4
|
CapabilitySnapshot,
|
|
4
5
|
FeedbackBundle,
|
|
6
|
+
FeedbackProjectRef,
|
|
5
7
|
IncidentEvent,
|
|
6
8
|
OpenCodeConfigSummary,
|
|
7
9
|
OperationState,
|
|
@@ -10,6 +12,16 @@ import {
|
|
|
10
12
|
TestSession,
|
|
11
13
|
VoiceCapability,
|
|
12
14
|
} from './types';
|
|
15
|
+
import { AGENT_ENDPOINTS } from './_core/endpoints';
|
|
16
|
+
import { RELOAD_PATH, describeReloadFailure } from './reloadActions';
|
|
17
|
+
import type { DevServerSnapshot, ReloadWireMode } from './reloadActions';
|
|
18
|
+
|
|
19
|
+
function unrefTimer(timer: ReturnType<typeof setTimeout>): void {
|
|
20
|
+
const maybeNodeTimer = timer as unknown as { unref?: () => void };
|
|
21
|
+
if (typeof maybeNodeTimer.unref === 'function') {
|
|
22
|
+
maybeNodeTimer.unref();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
13
25
|
|
|
14
26
|
export interface FeedbackEvent {
|
|
15
27
|
type: string;
|
|
@@ -76,6 +88,148 @@ export function resolveAppIdentity(opts?: {
|
|
|
76
88
|
return out;
|
|
77
89
|
}
|
|
78
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Is this JS running inside Yaver's own container as a pushed Hermes guest?
|
|
93
|
+
*
|
|
94
|
+
* The YaverInfo native module is registered only by Yaver's app, so its
|
|
95
|
+
* presence means the ambient runtime describes Yaver rather than the app
|
|
96
|
+
* whose code is executing. Mirrors the checks in YaverFeedback.ts,
|
|
97
|
+
* ShakeDetector.ts and QuickActionIcon.tsx.
|
|
98
|
+
*/
|
|
99
|
+
function isInsideYaverContainer(): boolean {
|
|
100
|
+
try {
|
|
101
|
+
const { NativeModules } = require('react-native');
|
|
102
|
+
return !!NativeModules?.YaverInfo;
|
|
103
|
+
} catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Read the guest project Yaver's host shell pinned for this bundle.
|
|
110
|
+
*
|
|
111
|
+
* When an app's JS is pushed into Yaver's container as a Hermes guest, the
|
|
112
|
+
* ambient runtime describes the HOST — `expo-constants` and `SettingsManager`
|
|
113
|
+
* both answer `Yaver` / `io.yaver.mobile`. Auto-resolution would therefore
|
|
114
|
+
* file every guest report against Yaver's own repo, and the fix task would
|
|
115
|
+
* edit the SDK instead of the app under test.
|
|
116
|
+
*
|
|
117
|
+
* Yaver already knows the right answer: picking a project in the Hot Reload
|
|
118
|
+
* tab calls `YaverInfo.setInheritedGuestProject(name, path)`. Only the name
|
|
119
|
+
* is used here — the agent resolves the path itself and deliberately ignores
|
|
120
|
+
* client-supplied ones on feedback reports.
|
|
121
|
+
*/
|
|
122
|
+
function resolveInheritedGuestProjectName(): string | undefined {
|
|
123
|
+
try {
|
|
124
|
+
const { NativeModules } = require('react-native');
|
|
125
|
+
const name = String(NativeModules?.YaverInfo?.inheritedGuestProjectName || '').trim();
|
|
126
|
+
return name || undefined;
|
|
127
|
+
} catch {
|
|
128
|
+
// Not inside Yaver's container, or react-native unavailable (unit tests).
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Build the app-identity half of a feedback report's metadata.
|
|
135
|
+
*
|
|
136
|
+
* `resolveAppIdentity()` has always fed /vibing/execute and /dev/reload-app,
|
|
137
|
+
* so the agent could route a "vibe on THIS app" request to the right repo —
|
|
138
|
+
* but nothing fed /feedback. Reports arrived with no identity at all, so the
|
|
139
|
+
* agent's fix router fell through to its own working directory and edited
|
|
140
|
+
* whichever repo it happened to be sitting in. This closes that gap by
|
|
141
|
+
* reusing the same resolver for the feedback path.
|
|
142
|
+
*
|
|
143
|
+
* Precedence, most to least trustworthy:
|
|
144
|
+
* 1. What the host app declared (`FeedbackConfig.projectName`/`bundleId`).
|
|
145
|
+
* An app knows its own identity; nothing should override it.
|
|
146
|
+
* 2. The guest project Yaver's host shell pinned, when running as a Hermes
|
|
147
|
+
* guest. Ambient lookups describe Yaver there, not the guest.
|
|
148
|
+
* 3. Ambient expo-constants / native modules — correct for a standalone
|
|
149
|
+
* build, which is the common case.
|
|
150
|
+
*
|
|
151
|
+
* Every lookup is best-effort: a bare RN app with no expo-constants still
|
|
152
|
+
* produces a report, just one the agent resolves by its own means.
|
|
153
|
+
*/
|
|
154
|
+
export function resolveReportIdentity(opts?: {
|
|
155
|
+
projectName?: string;
|
|
156
|
+
bundleId?: string;
|
|
157
|
+
surface?: FeedbackProjectRef['surface'];
|
|
158
|
+
surfaces?: FeedbackProjectRef['surfaces'];
|
|
159
|
+
stack?: string;
|
|
160
|
+
stacks?: string[];
|
|
161
|
+
testSurfaces?: string[];
|
|
162
|
+
feedbackSdk?: string;
|
|
163
|
+
feedbackTransport?: string;
|
|
164
|
+
voiceCapabilities?: string[];
|
|
165
|
+
sttProvider?: string;
|
|
166
|
+
ttsProvider?: string;
|
|
167
|
+
}): {
|
|
168
|
+
appName?: string;
|
|
169
|
+
app: AppInfo;
|
|
170
|
+
project?: FeedbackProjectRef;
|
|
171
|
+
} {
|
|
172
|
+
let projectName = (opts?.projectName || '').trim() || undefined;
|
|
173
|
+
let bundleId = (opts?.bundleId || '').trim() || undefined;
|
|
174
|
+
let version: string | undefined;
|
|
175
|
+
let buildNumber: string | undefined;
|
|
176
|
+
|
|
177
|
+
if (isInsideYaverContainer()) {
|
|
178
|
+
// Every ambient lookup answers for the HOST here. Because the agent
|
|
179
|
+
// routes on bundle id first, letting Yaver's id through would resolve
|
|
180
|
+
// straight to Yaver's own repo and the guest's name would never be
|
|
181
|
+
// consulted — so nothing ambient is trusted in the container. The
|
|
182
|
+
// version is Yaver's too, and reporting it would only mislabel which
|
|
183
|
+
// build the report came from.
|
|
184
|
+
projectName = projectName || resolveInheritedGuestProjectName();
|
|
185
|
+
} else {
|
|
186
|
+
const ambient = resolveAppIdentity({ projectName, bundleId });
|
|
187
|
+
projectName = ambient.projectName;
|
|
188
|
+
bundleId = ambient.bundleId;
|
|
189
|
+
try {
|
|
190
|
+
const Constants = require('expo-constants').default ?? require('expo-constants');
|
|
191
|
+
const cfg = Constants?.expoConfig ?? Constants?.manifest ?? {};
|
|
192
|
+
version = Constants?.nativeAppVersion || cfg?.version;
|
|
193
|
+
buildNumber =
|
|
194
|
+
Constants?.nativeBuildVersion ||
|
|
195
|
+
cfg?.ios?.buildNumber ||
|
|
196
|
+
(cfg?.android?.versionCode != null ? String(cfg.android.versionCode) : undefined);
|
|
197
|
+
} catch {
|
|
198
|
+
// expo-constants not installed (bare RN). Version is cosmetic — the
|
|
199
|
+
// router keys off appName/bundleId, both of which survive without it.
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const app: AppInfo = {};
|
|
204
|
+
if (bundleId) app.bundleId = bundleId;
|
|
205
|
+
if (version) app.version = version;
|
|
206
|
+
if (buildNumber) app.buildNumber = buildNumber;
|
|
207
|
+
|
|
208
|
+
if (!projectName && !bundleId) {
|
|
209
|
+
return { app };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const project: FeedbackProjectRef = { surface: opts?.surface ?? 'mobile' };
|
|
213
|
+
if (projectName) {
|
|
214
|
+
project.projectName = projectName;
|
|
215
|
+
project.appName = projectName;
|
|
216
|
+
}
|
|
217
|
+
// Note: projectPath is intentionally omitted — the agent ignores
|
|
218
|
+
// client-supplied paths on feedback reports and resolves them itself.
|
|
219
|
+
if (bundleId) project.bundleId = bundleId;
|
|
220
|
+
if (opts?.surfaces) project.surfaces = opts.surfaces;
|
|
221
|
+
if (opts?.stack) project.stack = opts.stack;
|
|
222
|
+
if (opts?.stacks) project.stacks = opts.stacks;
|
|
223
|
+
if (opts?.testSurfaces) project.testSurfaces = opts.testSurfaces;
|
|
224
|
+
if (opts?.feedbackSdk) project.feedbackSdk = opts.feedbackSdk;
|
|
225
|
+
if (opts?.feedbackTransport) project.feedbackTransport = opts.feedbackTransport;
|
|
226
|
+
if (opts?.voiceCapabilities) project.voiceCapabilities = opts.voiceCapabilities;
|
|
227
|
+
if (opts?.sttProvider) project.sttProvider = opts.sttProvider;
|
|
228
|
+
if (opts?.ttsProvider) project.ttsProvider = opts.ttsProvider;
|
|
229
|
+
|
|
230
|
+
return { appName: projectName, app, project };
|
|
231
|
+
}
|
|
232
|
+
|
|
79
233
|
/**
|
|
80
234
|
* Translate a raw Go-agent error into something a user can act on.
|
|
81
235
|
*
|
|
@@ -360,19 +514,22 @@ export class P2PClient {
|
|
|
360
514
|
|
|
361
515
|
/** Health check — returns true if the agent is reachable. */
|
|
362
516
|
async health(): Promise<boolean> {
|
|
517
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
363
518
|
try {
|
|
364
519
|
const controller = new AbortController();
|
|
365
|
-
|
|
520
|
+
timeoutId = setTimeout(() => controller.abort(), 3000);
|
|
521
|
+
unrefTimer(timeoutId);
|
|
366
522
|
|
|
367
523
|
const response = await fetch(`${this.baseUrl}/health`, {
|
|
368
524
|
method: 'GET',
|
|
369
525
|
signal: controller.signal,
|
|
370
526
|
});
|
|
371
527
|
|
|
372
|
-
clearTimeout(timeoutId);
|
|
373
528
|
return response.ok;
|
|
374
529
|
} catch {
|
|
375
530
|
return false;
|
|
531
|
+
} finally {
|
|
532
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
376
533
|
}
|
|
377
534
|
}
|
|
378
535
|
|
|
@@ -545,6 +702,82 @@ export class P2PClient {
|
|
|
545
702
|
* via the BlackBox command channel.
|
|
546
703
|
* @param mode - "dev" for hot reload, "bundle" for native bundle rebuild
|
|
547
704
|
*/
|
|
705
|
+
/**
|
|
706
|
+
* Read the dev server's state so the overlay can decide WHICH reload
|
|
707
|
+
* actions to offer, and disable the rest with a reason.
|
|
708
|
+
*
|
|
709
|
+
* Returns null when the machine cannot be reached at all — which the
|
|
710
|
+
* caller must render as "not connected", never as "no dev server".
|
|
711
|
+
* Those are two different problems with two different fixes.
|
|
712
|
+
*/
|
|
713
|
+
async getDevServerStatus(): Promise<DevServerSnapshot | null> {
|
|
714
|
+
try {
|
|
715
|
+
const resp = await fetch(`${this.baseUrl}${AGENT_ENDPOINTS.devStatus}`, {
|
|
716
|
+
headers: this.authHeaders(),
|
|
717
|
+
});
|
|
718
|
+
if (!resp.ok) return null;
|
|
719
|
+
const data = await resp.json().catch(() => ({} as Record<string, unknown>));
|
|
720
|
+
return {
|
|
721
|
+
running: data.running === true,
|
|
722
|
+
building: data.building === true,
|
|
723
|
+
framework: typeof data.framework === 'string' ? data.framework : undefined,
|
|
724
|
+
};
|
|
725
|
+
} catch {
|
|
726
|
+
return null;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Trigger a reload with an EXPLICIT fast/full mode — no bundle fallback.
|
|
732
|
+
*
|
|
733
|
+
* `reloadApp('dev')` silently falls through to a bundle rebuild when the
|
|
734
|
+
* dev server is down, which is right for a one-button UX and wrong the
|
|
735
|
+
* moment the user picks between two named actions: someone who pressed
|
|
736
|
+
* "Full Reload" must not get a 60-second bundle rebuild without being
|
|
737
|
+
* told. So this method reports the failure instead, with a named cause.
|
|
738
|
+
*
|
|
739
|
+
* Auth: the SAME bearer used for the feedback POST. `/dev/reload` is
|
|
740
|
+
* registered under `authSDKOrGuest` in desktop/agent/httpserver.go and is
|
|
741
|
+
* already in the `guest-reload` SDK-token scope list — nothing widens.
|
|
742
|
+
*/
|
|
743
|
+
async reloadWithMode(
|
|
744
|
+
mode: ReloadWireMode,
|
|
745
|
+
snapshot?: DevServerSnapshot | null,
|
|
746
|
+
): Promise<ReloadAck> {
|
|
747
|
+
if (mode === 'bundle') return this.reloadApp('bundle');
|
|
748
|
+
|
|
749
|
+
let resp: Response;
|
|
750
|
+
try {
|
|
751
|
+
resp = await fetch(`${this.baseUrl}${RELOAD_PATH}`, {
|
|
752
|
+
method: 'POST',
|
|
753
|
+
headers: this.authHeaders({ 'Content-Type': 'application/json' }),
|
|
754
|
+
body: JSON.stringify({ mode }),
|
|
755
|
+
});
|
|
756
|
+
} catch (err) {
|
|
757
|
+
throw new Error(
|
|
758
|
+
describeReloadFailure(0, err instanceof Error ? err.message : '', snapshot),
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
if (!resp.ok) {
|
|
762
|
+
const text = await resp.text().catch(() => '');
|
|
763
|
+
throw new Error(describeReloadFailure(resp.status, text, snapshot));
|
|
764
|
+
}
|
|
765
|
+
const payload = await resp.json().catch(() => ({} as Record<string, unknown>));
|
|
766
|
+
const nativeChangesDetected = payload.nativeChangesDetected === true;
|
|
767
|
+
return {
|
|
768
|
+
ok: true,
|
|
769
|
+
mode: 'dev',
|
|
770
|
+
acknowledged: true,
|
|
771
|
+
nativeChangesDetected,
|
|
772
|
+
changeClass: typeof payload.changeClass === 'string' ? payload.changeClass : undefined,
|
|
773
|
+
message: nativeChangesDetected
|
|
774
|
+
? 'Reload accepted, but native files changed — a rebuild is required.'
|
|
775
|
+
: mode === 'full'
|
|
776
|
+
? 'Full reload requested.'
|
|
777
|
+
: 'Hot reload requested.',
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
|
|
548
781
|
async reloadApp(
|
|
549
782
|
mode: 'dev' | 'bundle' = 'bundle',
|
|
550
783
|
opts?: { projectName?: string; bundleId?: string; projectPath?: string },
|
package/src/ShakeDetector.ts
CHANGED
|
@@ -83,6 +83,7 @@ export class ShakeDetector {
|
|
|
83
83
|
* the `ShakeEvent` name a handful of third-party shake libraries emit.
|
|
84
84
|
*/
|
|
85
85
|
private subscribeDevMenu(onShake: () => void): void {
|
|
86
|
+
if (typeof DeviceEventEmitter?.addListener !== 'function') return;
|
|
86
87
|
const eventName = Platform.OS === 'ios' ? 'shakeEvent' : 'ShakeEvent';
|
|
87
88
|
this.devMenuSub = DeviceEventEmitter.addListener(eventName, () => {
|
|
88
89
|
this.fire(onShake);
|