yaver-feedback-react-native 0.7.8 → 0.7.10
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/dist/Discovery.js +46 -31
- package/dist/FeedbackModal.js +41 -3
- package/dist/P2PClient.d.ts +5 -1
- package/dist/P2PClient.js +62 -2
- package/dist/YaverFeedback.js +6 -2
- package/dist/_core/device.d.ts +79 -0
- package/dist/_core/device.js +310 -0
- package/dist/_core/index.d.ts +1 -0
- package/dist/_core/index.js +1 -0
- package/dist/deviceDedup.d.ts +13 -37
- package/dist/deviceDedup.js +19 -169
- package/package.json +1 -1
- package/src/Discovery.ts +13 -30
- package/src/FeedbackModal.tsx +46 -4
- package/src/P2PClient.ts +68 -2
- package/src/YaverFeedback.ts +7 -2
- package/src/_core/device.ts +352 -0
- package/src/_core/index.ts +1 -0
- package/src/deviceDedup.ts +26 -166
package/dist/Discovery.js
CHANGED
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.YaverDiscovery = void 0;
|
|
4
37
|
function getAsyncStorage() {
|
|
@@ -201,38 +234,20 @@ class YaverDiscovery {
|
|
|
201
234
|
* does on same-LAN.
|
|
202
235
|
*/
|
|
203
236
|
static async raceProbe(urls) {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
try {
|
|
212
|
-
// `Promise.any` isn't on every RN runtime yet (older Hermes).
|
|
213
|
-
// Hand-roll the same behaviour so we don't need a polyfill.
|
|
214
|
-
return await new Promise((resolve) => {
|
|
215
|
-
let remaining = attempts.length;
|
|
216
|
-
let settled = false;
|
|
217
|
-
attempts.forEach((p) => {
|
|
218
|
-
p.then((r) => {
|
|
219
|
-
if (settled)
|
|
220
|
-
return;
|
|
221
|
-
settled = true;
|
|
222
|
-
resolve(r);
|
|
223
|
-
}).catch(() => {
|
|
224
|
-
remaining -= 1;
|
|
225
|
-
if (remaining <= 0 && !settled) {
|
|
226
|
-
settled = true;
|
|
227
|
-
resolve(null);
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
});
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
catch {
|
|
237
|
+
// Delegate to the canonical client-core implementation so mobile +
|
|
238
|
+
// SDK run the same race logic. Returned shape is `ProbeResult` —
|
|
239
|
+
// structurally compatible with DiscoveryResult minus the required
|
|
240
|
+
// hostname/version fields, which we backfill with sane defaults.
|
|
241
|
+
const { raceHealthProbes } = await Promise.resolve().then(() => __importStar(require('./_core/device')));
|
|
242
|
+
const res = await raceHealthProbes(urls, { timeoutMs: PROBE_TIMEOUT_MS });
|
|
243
|
+
if (!res)
|
|
234
244
|
return null;
|
|
235
|
-
|
|
245
|
+
return {
|
|
246
|
+
url: res.url,
|
|
247
|
+
hostname: res.hostname ?? 'Unknown',
|
|
248
|
+
version: res.version ?? 'unknown',
|
|
249
|
+
latency: res.latency ?? 0,
|
|
250
|
+
};
|
|
236
251
|
}
|
|
237
252
|
/**
|
|
238
253
|
* Discover agent via relay HTTP proxy. Uses the user's configured
|
package/dist/FeedbackModal.js
CHANGED
|
@@ -45,6 +45,7 @@ const FeedbackModal = () => {
|
|
|
45
45
|
const [action, setAction] = (0, react_1.useState)('idle');
|
|
46
46
|
const [error, setError] = (0, react_1.useState)(null);
|
|
47
47
|
const [toast, setToast] = (0, react_1.useState)(null);
|
|
48
|
+
const [progress, setProgress] = (0, react_1.useState)(null);
|
|
48
49
|
const [isRecordingVideo, setIsRecordingVideo] = (0, react_1.useState)(false);
|
|
49
50
|
const [lastVideo, setLastVideo] = (0, react_1.useState)(null);
|
|
50
51
|
const mountedRef = (0, react_1.useRef)(true);
|
|
@@ -69,6 +70,14 @@ const FeedbackModal = () => {
|
|
|
69
70
|
const msg = payload?.message || payload?.phase || '';
|
|
70
71
|
if (msg)
|
|
71
72
|
setToast(msg);
|
|
73
|
+
if (typeof payload?.progress === 'number') {
|
|
74
|
+
setProgress(payload.progress);
|
|
75
|
+
}
|
|
76
|
+
// On final phases, fade the bar to 100% so the user sees
|
|
77
|
+
// completion before the modal auto-dismisses.
|
|
78
|
+
if (payload?.phase === 'done' || payload?.phase === 'error') {
|
|
79
|
+
setProgress(1);
|
|
80
|
+
}
|
|
72
81
|
});
|
|
73
82
|
return () => {
|
|
74
83
|
mountedRef.current = false;
|
|
@@ -141,15 +150,26 @@ const FeedbackModal = () => {
|
|
|
141
150
|
const handleHotReload = (0, react_1.useCallback)(async () => {
|
|
142
151
|
setAction('hot-reloading');
|
|
143
152
|
setError(null);
|
|
153
|
+
setProgress(0);
|
|
154
|
+
setToast('Sending…');
|
|
144
155
|
try {
|
|
156
|
+
// Default mode: bundle. Always rebuilds via the agent regardless
|
|
157
|
+
// of Metro state. P2PClient.reloadApp auto-resolves projectName +
|
|
158
|
+
// bundleId from expo-constants / NativeModules so the agent can
|
|
159
|
+
// map this app to its MobileProject scan entry without needing
|
|
160
|
+
// `yaver dev start` to have been run.
|
|
145
161
|
await runWithReconnect(async (client) => {
|
|
146
|
-
await client.reloadApp('
|
|
162
|
+
await client.reloadApp('bundle');
|
|
147
163
|
});
|
|
148
|
-
|
|
149
|
-
|
|
164
|
+
// We don't auto-close here — the agent's BlackBox status pings
|
|
165
|
+
// will keep the modal updated, and the on-device YaverBundleLoader
|
|
166
|
+
// will reload the JS once the fresh bundle arrives. Modal stays
|
|
167
|
+
// up for a beat so the user sees the final progress state.
|
|
168
|
+
closeSoon(2500);
|
|
150
169
|
}
|
|
151
170
|
catch (err) {
|
|
152
171
|
setError(err instanceof Error ? err.message : String(err));
|
|
172
|
+
setProgress(null);
|
|
153
173
|
}
|
|
154
174
|
finally {
|
|
155
175
|
if (mountedRef.current)
|
|
@@ -387,6 +407,12 @@ const FeedbackModal = () => {
|
|
|
387
407
|
? `Send Video · ${Math.round(lastVideo.duration)}s`
|
|
388
408
|
: 'Send Video'} tint="#a78bfa" onPress={handleSendVideo} disabled={busy || !lastVideo} busy={action === 'sending-video'}/>
|
|
389
409
|
|
|
410
|
+
{progress !== null && (<react_native_1.View style={styles.progressTrack}>
|
|
411
|
+
<react_native_1.View style={[
|
|
412
|
+
styles.progressFill,
|
|
413
|
+
{ width: `${Math.round(progress * 100)}%` },
|
|
414
|
+
]}/>
|
|
415
|
+
</react_native_1.View>)}
|
|
390
416
|
{toast && <react_native_1.Text style={styles.toast}>{toast}</react_native_1.Text>}
|
|
391
417
|
{error && <react_native_1.Text style={styles.error}>{error}</react_native_1.Text>}
|
|
392
418
|
</react_native_1.Pressable>
|
|
@@ -459,6 +485,18 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
459
485
|
fontSize: 15,
|
|
460
486
|
fontWeight: '700',
|
|
461
487
|
},
|
|
488
|
+
progressTrack: {
|
|
489
|
+
height: 6,
|
|
490
|
+
borderRadius: 3,
|
|
491
|
+
backgroundColor: 'rgba(255,255,255,0.08)',
|
|
492
|
+
overflow: 'hidden',
|
|
493
|
+
marginTop: 4,
|
|
494
|
+
},
|
|
495
|
+
progressFill: {
|
|
496
|
+
height: '100%',
|
|
497
|
+
backgroundColor: '#818cf8',
|
|
498
|
+
borderRadius: 3,
|
|
499
|
+
},
|
|
462
500
|
toast: {
|
|
463
501
|
color: '#22c55e',
|
|
464
502
|
fontSize: 13,
|
package/dist/P2PClient.d.ts
CHANGED
|
@@ -65,7 +65,11 @@ export declare class P2PClient {
|
|
|
65
65
|
* via the BlackBox command channel.
|
|
66
66
|
* @param mode - "dev" for hot reload, "bundle" for native bundle rebuild
|
|
67
67
|
*/
|
|
68
|
-
reloadApp(mode?: 'dev' | 'bundle'
|
|
68
|
+
reloadApp(mode?: 'dev' | 'bundle', opts?: {
|
|
69
|
+
projectName?: string;
|
|
70
|
+
bundleId?: string;
|
|
71
|
+
projectPath?: string;
|
|
72
|
+
}): Promise<{
|
|
69
73
|
ok: boolean;
|
|
70
74
|
}>;
|
|
71
75
|
/**
|
package/dist/P2PClient.js
CHANGED
|
@@ -2,6 +2,54 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.P2PClient = void 0;
|
|
4
4
|
const react_native_1 = require("react-native");
|
|
5
|
+
/**
|
|
6
|
+
* Try to resolve `{projectName, bundleId}` for the running app so the
|
|
7
|
+
* agent can map the reload request to a MobileProject in its scan
|
|
8
|
+
* cache. Order: caller-supplied opts → Expo Constants → react-native
|
|
9
|
+
* NativeModules. None of the lookups throw — missing data just means
|
|
10
|
+
* the agent will fall back to its own dev-server resolution.
|
|
11
|
+
*/
|
|
12
|
+
function resolveAppIdentity(opts) {
|
|
13
|
+
let projectName = opts?.projectName;
|
|
14
|
+
let bundleId = opts?.bundleId;
|
|
15
|
+
const projectPath = opts?.projectPath;
|
|
16
|
+
if (!projectName || !bundleId) {
|
|
17
|
+
try {
|
|
18
|
+
const Constants = require('expo-constants').default ?? require('expo-constants');
|
|
19
|
+
const cfg = Constants?.expoConfig ?? Constants?.manifest ?? {};
|
|
20
|
+
projectName = projectName || cfg?.name;
|
|
21
|
+
bundleId =
|
|
22
|
+
bundleId ||
|
|
23
|
+
cfg?.ios?.bundleIdentifier ||
|
|
24
|
+
cfg?.android?.package;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// expo-constants not installed (bare RN). Fall through.
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (!bundleId) {
|
|
31
|
+
try {
|
|
32
|
+
const { Platform, NativeModules } = require('react-native');
|
|
33
|
+
if (Platform.OS === 'ios') {
|
|
34
|
+
bundleId = NativeModules?.SettingsManager?.settings?.CFBundleIdentifier;
|
|
35
|
+
}
|
|
36
|
+
else if (Platform.OS === 'android') {
|
|
37
|
+
bundleId = NativeModules?.PlatformConstants?.Package;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// SettingsManager/PlatformConstants missing on some RN versions.
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const out = {};
|
|
45
|
+
if (projectName)
|
|
46
|
+
out.projectName = projectName;
|
|
47
|
+
if (bundleId)
|
|
48
|
+
out.bundleId = bundleId;
|
|
49
|
+
if (projectPath)
|
|
50
|
+
out.projectPath = projectPath;
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
5
53
|
/**
|
|
6
54
|
* Translate a raw Go-agent error into something a user can act on.
|
|
7
55
|
*
|
|
@@ -215,7 +263,7 @@ class P2PClient {
|
|
|
215
263
|
* via the BlackBox command channel.
|
|
216
264
|
* @param mode - "dev" for hot reload, "bundle" for native bundle rebuild
|
|
217
265
|
*/
|
|
218
|
-
async reloadApp(mode = 'bundle') {
|
|
266
|
+
async reloadApp(mode = 'bundle', opts) {
|
|
219
267
|
// Default path: always rebuild a fresh Hermes bundle.
|
|
220
268
|
//
|
|
221
269
|
// Rationale: the SDK's common caller is a phone user who's not
|
|
@@ -242,13 +290,25 @@ class P2PClient {
|
|
|
242
290
|
// than surfacing the raw error, so the user never has to know
|
|
243
291
|
// Metro wasn't running.
|
|
244
292
|
}
|
|
293
|
+
// Auto-resolve identity if the caller didn't pass it. Reads from
|
|
294
|
+
// expo-constants when present (host can pin via app.json
|
|
295
|
+
// `expo.name` / `ios.bundleIdentifier` / `android.package`); falls
|
|
296
|
+
// back to react-native's NativeModules.SettingsManager.settings
|
|
297
|
+
// (iOS `CFBundleIdentifier`, `CFBundleName`) and Application
|
|
298
|
+
// (Android packageName). On the agent side these resolve to the
|
|
299
|
+
// matching MobileProject in the cached scan, so we don't need
|
|
300
|
+
// `yaver dev start` to have run on the host.
|
|
301
|
+
const identity = resolveAppIdentity(opts);
|
|
245
302
|
const res = await fetch(`${this.baseUrl}/dev/reload-app`, {
|
|
246
303
|
method: 'POST',
|
|
247
304
|
headers: {
|
|
248
305
|
Authorization: `Bearer ${this.authToken}`,
|
|
249
306
|
'Content-Type': 'application/json',
|
|
250
307
|
},
|
|
251
|
-
body: JSON.stringify({
|
|
308
|
+
body: JSON.stringify({
|
|
309
|
+
mode: 'bundle',
|
|
310
|
+
...identity,
|
|
311
|
+
}),
|
|
252
312
|
});
|
|
253
313
|
if (!res.ok) {
|
|
254
314
|
const text = await res.text().catch(() => '');
|
package/dist/YaverFeedback.js
CHANGED
|
@@ -155,18 +155,22 @@ class YaverFeedback {
|
|
|
155
155
|
}
|
|
156
156
|
else if (cmd.command === 'status') {
|
|
157
157
|
// Pipe agent progress pings to the UI. The FeedbackModal
|
|
158
|
-
// subscribes to this event and renders the message
|
|
159
|
-
// reload / build is in flight.
|
|
158
|
+
// subscribes to this event and renders the message + a
|
|
159
|
+
// progress bar while a reload / build is in flight.
|
|
160
160
|
const message = typeof cmd.data?.message === 'string'
|
|
161
161
|
? cmd.data.message
|
|
162
162
|
: '';
|
|
163
163
|
const phase = typeof cmd.data?.phase === 'string'
|
|
164
164
|
? cmd.data.phase
|
|
165
165
|
: '';
|
|
166
|
+
const progress = typeof cmd.data?.progress === 'number'
|
|
167
|
+
? Math.max(0, Math.min(1, cmd.data.progress))
|
|
168
|
+
: undefined;
|
|
166
169
|
const { DeviceEventEmitter } = require('react-native');
|
|
167
170
|
DeviceEventEmitter.emit('yaverFeedback:status', {
|
|
168
171
|
message,
|
|
169
172
|
phase,
|
|
173
|
+
progress,
|
|
170
174
|
at: Date.now(),
|
|
171
175
|
});
|
|
172
176
|
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export interface CoreDevice {
|
|
2
|
+
/** Convex-issued device id. */
|
|
3
|
+
deviceId: string;
|
|
4
|
+
/** Display name — usually hostname. */
|
|
5
|
+
name: string;
|
|
6
|
+
/** OS family — "darwin", "linux", "windows". */
|
|
7
|
+
platform: string;
|
|
8
|
+
isOnline: boolean;
|
|
9
|
+
needsAuth: boolean;
|
|
10
|
+
runnerDown: boolean;
|
|
11
|
+
/** Unix ms of the latest heartbeat the agent sent to Convex. */
|
|
12
|
+
lastHeartbeat: number;
|
|
13
|
+
isGuest: boolean;
|
|
14
|
+
hostName?: string;
|
|
15
|
+
hostEmail?: string;
|
|
16
|
+
accessScope?: 'owner' | 'shared-scoped' | 'shared-legacy';
|
|
17
|
+
/** Primary LAN IP (or tunnel host) the agent advertised. */
|
|
18
|
+
quicHost: string;
|
|
19
|
+
quicPort: number;
|
|
20
|
+
/** HTTP port the agent listens on (usually 18080). */
|
|
21
|
+
httpPort?: number;
|
|
22
|
+
publicKey?: string;
|
|
23
|
+
/** Stable hardware identifier — dedup key for re-pair events. */
|
|
24
|
+
hwid?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Every LAN IP the agent reported in its last heartbeat. Used by
|
|
27
|
+
* Discovery.raceProbe to try all interfaces in parallel.
|
|
28
|
+
*/
|
|
29
|
+
localIps?: string[];
|
|
30
|
+
}
|
|
31
|
+
export declare function deviceIdentityKey(d: CoreDevice): string;
|
|
32
|
+
export declare function deviceAliasKey(d: CoreDevice): string | null;
|
|
33
|
+
export declare function deviceEndpointKey(d: CoreDevice): string | null;
|
|
34
|
+
export declare function mergeDeviceEntries(a: CoreDevice, b: CoreDevice): CoreDevice;
|
|
35
|
+
/**
|
|
36
|
+
* Collapse duplicate Convex rows so each physical machine appears
|
|
37
|
+
* exactly once. Three passes — identity key → alias key → endpoint key.
|
|
38
|
+
* Safe on empty lists; idempotent on already-deduped input.
|
|
39
|
+
*/
|
|
40
|
+
export declare function collapseDevices(devices: CoreDevice[]): CoreDevice[];
|
|
41
|
+
/**
|
|
42
|
+
* "Fresh" matches the mobile app: online + heartbeat < 90 s. Clients
|
|
43
|
+
* read Convex's `isOnline` first (backend already applies its own 90 s
|
|
44
|
+
* gate from the server clock), then use this helper when they need the
|
|
45
|
+
* phone-side freshness opinion too — e.g. for auto-connect picks.
|
|
46
|
+
*/
|
|
47
|
+
export declare function isDeviceFresh(d: CoreDevice, now?: number): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Choose the best candidate for an auto-connect attempt. Preference:
|
|
50
|
+
* 1. explicit `preferredDeviceId` that's still fresh
|
|
51
|
+
* 2. fresh (online + recent heartbeat) + has a quicHost
|
|
52
|
+
* 3. online + has a quicHost
|
|
53
|
+
* 4. first with a quicHost
|
|
54
|
+
*/
|
|
55
|
+
export declare function pickTargetDevice(devices: CoreDevice[], preferredDeviceId?: string): CoreDevice | null;
|
|
56
|
+
/**
|
|
57
|
+
* Build the set of `/health` candidate URLs for a target device —
|
|
58
|
+
* `quicHost` plus every LAN IP the agent reported in `localIps`,
|
|
59
|
+
* uniqued and formatted. This is the thing that makes direct LAN
|
|
60
|
+
* reloads "just work" on multi-homed hosts (en0 + utun tailscale +
|
|
61
|
+
* docker0 etc.) — the mobile app races them in parallel via
|
|
62
|
+
* Promise.any.
|
|
63
|
+
*/
|
|
64
|
+
export declare function buildProbeCandidates(target: CoreDevice, defaultHttpPort?: number): string[];
|
|
65
|
+
export interface ProbeResult {
|
|
66
|
+
url: string;
|
|
67
|
+
hostname?: string;
|
|
68
|
+
version?: string;
|
|
69
|
+
latency?: number;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Race `/health` probes across N URLs. First 200 wins; everything else
|
|
73
|
+
* is abandoned. Older Hermes doesn't have Promise.any, so we hand-roll
|
|
74
|
+
* the same semantic.
|
|
75
|
+
*/
|
|
76
|
+
export declare function raceHealthProbes(urls: string[], opts?: {
|
|
77
|
+
timeoutMs?: number;
|
|
78
|
+
headers?: Record<string, string>;
|
|
79
|
+
}): Promise<ProbeResult | null>;
|