yaver-feedback-react-native 0.7.9 → 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.
@@ -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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yaver-feedback-react-native",
3
- "version": "0.7.9",
3
+ "version": "0.7.10",
4
4
  "description": "Visual feedback SDK for Yaver — bug reports, screen recording, voice annotations, and local-first developer workflows",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -53,6 +53,7 @@ export const FeedbackModal: React.FC = () => {
53
53
  const [action, setAction] = useState<ActionState>('idle');
54
54
  const [error, setError] = useState<string | null>(null);
55
55
  const [toast, setToast] = useState<string | null>(null);
56
+ const [progress, setProgress] = useState<number | null>(null);
56
57
  const [isRecordingVideo, setIsRecordingVideo] = useState(false);
57
58
  const [lastVideo, setLastVideo] = useState<LastVideo | null>(null);
58
59
  const mountedRef = useRef(true);
@@ -74,10 +75,18 @@ export const FeedbackModal: React.FC = () => {
74
75
  // "stuck".
75
76
  const statusSub = DeviceEventEmitter.addListener(
76
77
  'yaverFeedback:status',
77
- (payload: { message?: string; phase?: string }) => {
78
+ (payload: { message?: string; phase?: string; progress?: number }) => {
78
79
  if (!mountedRef.current) return;
79
80
  const msg = payload?.message || payload?.phase || '';
80
81
  if (msg) setToast(msg);
82
+ if (typeof payload?.progress === 'number') {
83
+ setProgress(payload.progress);
84
+ }
85
+ // On final phases, fade the bar to 100% so the user sees
86
+ // completion before the modal auto-dismisses.
87
+ if (payload?.phase === 'done' || payload?.phase === 'error') {
88
+ setProgress(1);
89
+ }
81
90
  },
82
91
  );
83
92
  return () => {
@@ -154,14 +163,25 @@ export const FeedbackModal: React.FC = () => {
154
163
  const handleHotReload = useCallback(async () => {
155
164
  setAction('hot-reloading');
156
165
  setError(null);
166
+ setProgress(0);
167
+ setToast('Sending…');
157
168
  try {
169
+ // Default mode: bundle. Always rebuilds via the agent regardless
170
+ // of Metro state. P2PClient.reloadApp auto-resolves projectName +
171
+ // bundleId from expo-constants / NativeModules so the agent can
172
+ // map this app to its MobileProject scan entry without needing
173
+ // `yaver dev start` to have been run.
158
174
  await runWithReconnect(async (client) => {
159
- await client.reloadApp('dev');
175
+ await client.reloadApp('bundle');
160
176
  });
161
- setToast('Reload sent');
162
- closeSoon(800);
177
+ // We don't auto-close here — the agent's BlackBox status pings
178
+ // will keep the modal updated, and the on-device YaverBundleLoader
179
+ // will reload the JS once the fresh bundle arrives. Modal stays
180
+ // up for a beat so the user sees the final progress state.
181
+ closeSoon(2500);
163
182
  } catch (err: unknown) {
164
183
  setError(err instanceof Error ? err.message : String(err));
184
+ setProgress(null);
165
185
  } finally {
166
186
  if (mountedRef.current) setAction('idle');
167
187
  }
@@ -449,6 +469,16 @@ export const FeedbackModal: React.FC = () => {
449
469
  busy={action === 'sending-video'}
450
470
  />
451
471
 
472
+ {progress !== null && (
473
+ <View style={styles.progressTrack}>
474
+ <View
475
+ style={[
476
+ styles.progressFill,
477
+ { width: `${Math.round(progress * 100)}%` },
478
+ ]}
479
+ />
480
+ </View>
481
+ )}
452
482
  {toast && <Text style={styles.toast}>{toast}</Text>}
453
483
  {error && <Text style={styles.error}>{error}</Text>}
454
484
  </Pressable>
@@ -550,6 +580,18 @@ const styles = StyleSheet.create({
550
580
  fontSize: 15,
551
581
  fontWeight: '700',
552
582
  },
583
+ progressTrack: {
584
+ height: 6,
585
+ borderRadius: 3,
586
+ backgroundColor: 'rgba(255,255,255,0.08)',
587
+ overflow: 'hidden',
588
+ marginTop: 4,
589
+ },
590
+ progressFill: {
591
+ height: '100%',
592
+ backgroundColor: '#818cf8',
593
+ borderRadius: 3,
594
+ },
553
595
  toast: {
554
596
  color: '#22c55e',
555
597
  fontSize: 13,
package/src/P2PClient.ts CHANGED
@@ -7,6 +7,56 @@ export interface FeedbackEvent {
7
7
  data: any;
8
8
  }
9
9
 
10
+ /**
11
+ * Try to resolve `{projectName, bundleId}` for the running app so the
12
+ * agent can map the reload request to a MobileProject in its scan
13
+ * cache. Order: caller-supplied opts → Expo Constants → react-native
14
+ * NativeModules. None of the lookups throw — missing data just means
15
+ * the agent will fall back to its own dev-server resolution.
16
+ */
17
+ function resolveAppIdentity(opts?: {
18
+ projectName?: string;
19
+ bundleId?: string;
20
+ projectPath?: string;
21
+ }): { projectName?: string; bundleId?: string; projectPath?: string } {
22
+ let projectName = opts?.projectName;
23
+ let bundleId = opts?.bundleId;
24
+ const projectPath = opts?.projectPath;
25
+
26
+ if (!projectName || !bundleId) {
27
+ try {
28
+ const Constants = require('expo-constants').default ?? require('expo-constants');
29
+ const cfg = Constants?.expoConfig ?? Constants?.manifest ?? {};
30
+ projectName = projectName || cfg?.name;
31
+ bundleId =
32
+ bundleId ||
33
+ cfg?.ios?.bundleIdentifier ||
34
+ cfg?.android?.package;
35
+ } catch {
36
+ // expo-constants not installed (bare RN). Fall through.
37
+ }
38
+ }
39
+
40
+ if (!bundleId) {
41
+ try {
42
+ const { Platform, NativeModules } = require('react-native');
43
+ if (Platform.OS === 'ios') {
44
+ bundleId = NativeModules?.SettingsManager?.settings?.CFBundleIdentifier;
45
+ } else if (Platform.OS === 'android') {
46
+ bundleId = NativeModules?.PlatformConstants?.Package;
47
+ }
48
+ } catch {
49
+ // SettingsManager/PlatformConstants missing on some RN versions.
50
+ }
51
+ }
52
+
53
+ const out: { projectName?: string; bundleId?: string; projectPath?: string } = {};
54
+ if (projectName) out.projectName = projectName;
55
+ if (bundleId) out.bundleId = bundleId;
56
+ if (projectPath) out.projectPath = projectPath;
57
+ return out;
58
+ }
59
+
10
60
  /**
11
61
  * Translate a raw Go-agent error into something a user can act on.
12
62
  *
@@ -256,7 +306,10 @@ export class P2PClient {
256
306
  * via the BlackBox command channel.
257
307
  * @param mode - "dev" for hot reload, "bundle" for native bundle rebuild
258
308
  */
259
- async reloadApp(mode: 'dev' | 'bundle' = 'bundle'): Promise<{ ok: boolean }> {
309
+ async reloadApp(
310
+ mode: 'dev' | 'bundle' = 'bundle',
311
+ opts?: { projectName?: string; bundleId?: string; projectPath?: string },
312
+ ): Promise<{ ok: boolean }> {
260
313
  // Default path: always rebuild a fresh Hermes bundle.
261
314
  //
262
315
  // Rationale: the SDK's common caller is a phone user who's not
@@ -284,13 +337,26 @@ export class P2PClient {
284
337
  // Metro wasn't running.
285
338
  }
286
339
 
340
+ // Auto-resolve identity if the caller didn't pass it. Reads from
341
+ // expo-constants when present (host can pin via app.json
342
+ // `expo.name` / `ios.bundleIdentifier` / `android.package`); falls
343
+ // back to react-native's NativeModules.SettingsManager.settings
344
+ // (iOS `CFBundleIdentifier`, `CFBundleName`) and Application
345
+ // (Android packageName). On the agent side these resolve to the
346
+ // matching MobileProject in the cached scan, so we don't need
347
+ // `yaver dev start` to have run on the host.
348
+ const identity = resolveAppIdentity(opts);
349
+
287
350
  const res = await fetch(`${this.baseUrl}/dev/reload-app`, {
288
351
  method: 'POST',
289
352
  headers: {
290
353
  Authorization: `Bearer ${this.authToken}`,
291
354
  'Content-Type': 'application/json',
292
355
  },
293
- body: JSON.stringify({ mode: 'bundle' }),
356
+ body: JSON.stringify({
357
+ mode: 'bundle',
358
+ ...identity,
359
+ }),
294
360
  });
295
361
  if (!res.ok) {
296
362
  const text = await res.text().catch(() => '');
@@ -167,8 +167,8 @@ export class YaverFeedback {
167
167
  }
168
168
  } else if (cmd.command === 'status') {
169
169
  // Pipe agent progress pings to the UI. The FeedbackModal
170
- // subscribes to this event and renders the message while a
171
- // reload / build is in flight.
170
+ // subscribes to this event and renders the message + a
171
+ // progress bar while a reload / build is in flight.
172
172
  const message =
173
173
  typeof cmd.data?.message === 'string'
174
174
  ? (cmd.data.message as string)
@@ -177,10 +177,15 @@ export class YaverFeedback {
177
177
  typeof cmd.data?.phase === 'string'
178
178
  ? (cmd.data.phase as string)
179
179
  : '';
180
+ const progress =
181
+ typeof cmd.data?.progress === 'number'
182
+ ? Math.max(0, Math.min(1, cmd.data.progress as number))
183
+ : undefined;
180
184
  const { DeviceEventEmitter } = require('react-native');
181
185
  DeviceEventEmitter.emit('yaverFeedback:status', {
182
186
  message,
183
187
  phase,
188
+ progress,
184
189
  at: Date.now(),
185
190
  });
186
191
  }