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 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
- if (!urls || urls.length === 0)
205
- return null;
206
- const attempts = urls.map((url) => YaverDiscovery.probe(url).then((r) => {
207
- if (!r)
208
- throw new Error('no-200');
209
- return r;
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
@@ -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('dev');
162
+ await client.reloadApp('bundle');
147
163
  });
148
- setToast('Reload sent');
149
- closeSoon(800);
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,
@@ -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'): Promise<{
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({ mode: 'bundle' }),
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(() => '');
@@ -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 while a
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>;