yaver-feedback-react-native 0.8.2 → 0.8.4

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/src/auth.ts CHANGED
@@ -410,8 +410,10 @@ export interface RemoteDevice {
410
410
  runnerDown: boolean;
411
411
  lastHeartbeat: number;
412
412
  isGuest: boolean;
413
+ hostUserId?: string;
413
414
  hostName?: string;
414
415
  hostEmail?: string;
416
+ hostUserIdString?: string;
415
417
  accessScope: 'owner' | 'shared-scoped' | 'shared-legacy';
416
418
  quicHost: string;
417
419
  quicPort: number;
@@ -433,6 +435,54 @@ export interface DeviceList {
433
435
  shared: RemoteDevice[];
434
436
  }
435
437
 
438
+ export interface DeviceReachability {
439
+ reachable: boolean;
440
+ url?: string;
441
+ }
442
+
443
+ export interface GuestInvitation {
444
+ hostUserId: string;
445
+ hostName: string;
446
+ hostEmail: string;
447
+ hostUserIdString?: string;
448
+ createdAt: number;
449
+ expiresAt: number;
450
+ inviteCode?: string;
451
+ }
452
+
453
+ export interface ActiveGuestHost {
454
+ hostUserId: string;
455
+ hostName: string;
456
+ hostEmail: string;
457
+ grantedAt: number;
458
+ }
459
+
460
+ export interface GuestHostsResponse {
461
+ pending: GuestInvitation[];
462
+ active: ActiveGuestHost[];
463
+ }
464
+
465
+ export interface InvitationHostDevice {
466
+ deviceId: string;
467
+ name: string;
468
+ platform: string;
469
+ lastHeartbeat?: number;
470
+ proposed: boolean;
471
+ }
472
+
473
+ export interface InvitationPreview {
474
+ inviteCode: string;
475
+ hostUserId: string;
476
+ hostName: string;
477
+ hostEmail: string;
478
+ hostUserIdString?: string;
479
+ proposedDeviceIds?: string[];
480
+ hostDevices: InvitationHostDevice[];
481
+ invitedByUserId?: boolean;
482
+ expiresAt: number;
483
+ createdAt: number;
484
+ }
485
+
436
486
  /**
437
487
  * Fetch the set of remote dev machines this user can reach. Splits into
438
488
  * owned (user is the host) vs shared (host invited them as a guest).
@@ -463,8 +513,10 @@ export async function listReachableDevices(
463
513
  runnerDown: !!d.runnerDown,
464
514
  lastHeartbeat: d.lastHeartbeat ?? 0,
465
515
  isGuest: !!d.isGuest,
516
+ hostUserId: d.hostUserId,
466
517
  hostName: d.hostName,
467
518
  hostEmail: d.hostEmail,
519
+ hostUserIdString: d.hostUserIdString,
468
520
  accessScope: d.accessScope ?? 'owner',
469
521
  quicHost: d.quicHost ?? d.host ?? '',
470
522
  quicPort: d.quicPort ?? 0,
@@ -488,3 +540,134 @@ export async function listReachableDevices(
488
540
  return { owned: [], shared: [] };
489
541
  }
490
542
  }
543
+
544
+ export function buildDeviceCandidateUrls(device: RemoteDevice): string[] {
545
+ const port = device.httpPort ?? device.quicPort ?? 18080;
546
+ const hosts = new Set<string>();
547
+ if (device.quicHost) hosts.add(device.quicHost);
548
+ for (const ip of device.localIps ?? []) {
549
+ if (ip) hosts.add(ip);
550
+ }
551
+ return Array.from(hosts).map((host) => `http://${host}:${port}`);
552
+ }
553
+
554
+ export async function probeDeviceReachability(
555
+ device: RemoteDevice,
556
+ timeoutMs = 2500,
557
+ ): Promise<DeviceReachability> {
558
+ const candidates = buildDeviceCandidateUrls(device);
559
+ if (candidates.length === 0) return { reachable: false };
560
+
561
+ const probeOne = async (baseUrl: string): Promise<string> => {
562
+ const controller = new AbortController();
563
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
564
+ try {
565
+ const response = await fetch(`${baseUrl}/health`, {
566
+ method: 'GET',
567
+ signal: controller.signal,
568
+ });
569
+ if (!response.ok) throw new Error(`health ${response.status}`);
570
+ return baseUrl;
571
+ } finally {
572
+ clearTimeout(timer);
573
+ }
574
+ };
575
+
576
+ const settled = await Promise.allSettled(candidates.map((url) => probeOne(url)));
577
+ for (const result of settled) {
578
+ if (result.status === 'fulfilled') {
579
+ return { reachable: true, url: result.value };
580
+ }
581
+ }
582
+ return { reachable: false };
583
+ }
584
+
585
+ export async function mintGuestSdkToken(
586
+ token: string,
587
+ hostUserId: string,
588
+ targetDeviceId: string,
589
+ ): Promise<{ token: string; expiresAt: number; allowedProjects?: string[] }> {
590
+ const res = await fetch(`${convexSiteUrl}/guests/sdk-token`, {
591
+ method: 'POST',
592
+ headers: {
593
+ Authorization: `Bearer ${token}`,
594
+ 'Content-Type': 'application/json',
595
+ },
596
+ body: JSON.stringify({ hostUserId, targetDeviceId }),
597
+ });
598
+ if (!res.ok) {
599
+ const data = await res.json().catch(() => ({}));
600
+ throw new Error(data.error || 'Failed to mint delegated SDK token');
601
+ }
602
+ return res.json();
603
+ }
604
+
605
+ export async function fetchGuestHosts(token: string): Promise<GuestHostsResponse> {
606
+ const res = await fetch(`${convexSiteUrl}/guests/hosts`, {
607
+ headers: { Authorization: `Bearer ${token}` },
608
+ });
609
+ if (!res.ok) {
610
+ const data = await res.json().catch(() => ({}));
611
+ throw new Error(data.error || 'Failed to fetch guest hosts');
612
+ }
613
+ return res.json();
614
+ }
615
+
616
+ export async function findInviteByCode(
617
+ token: string,
618
+ code: string,
619
+ ): Promise<InvitationPreview | null> {
620
+ const cleaned = code.toUpperCase().trim();
621
+ const res = await fetch(
622
+ `${convexSiteUrl}/guests/find-by-code?code=${encodeURIComponent(cleaned)}`,
623
+ { headers: { Authorization: `Bearer ${token}` } },
624
+ );
625
+ if (res.status === 404) return null;
626
+ if (!res.ok) {
627
+ const data = await res.json().catch(() => ({}));
628
+ throw new Error(data.error || 'Failed to load invite');
629
+ }
630
+ return res.json();
631
+ }
632
+
633
+ export async function acceptGuestByCode(
634
+ token: string,
635
+ code: string,
636
+ approvedDeviceIds?: string[],
637
+ ): Promise<{ hostName: string; hostEmail: string }> {
638
+ const res = await fetch(`${convexSiteUrl}/guests/accept-code`, {
639
+ method: 'POST',
640
+ headers: {
641
+ Authorization: `Bearer ${token}`,
642
+ 'Content-Type': 'application/json',
643
+ },
644
+ body: JSON.stringify({
645
+ code: code.toUpperCase().trim(),
646
+ approvedDeviceIds,
647
+ }),
648
+ });
649
+ if (!res.ok) {
650
+ const data = await res.json().catch(() => ({}));
651
+ throw new Error(data.error || 'Invalid invite code');
652
+ }
653
+ return res.json();
654
+ }
655
+
656
+ export async function acceptGuestInvitation(
657
+ token: string,
658
+ hostUserId: string,
659
+ approvedDeviceIds?: string[],
660
+ ): Promise<void> {
661
+ const res = await fetch(`${convexSiteUrl}/guests/accept`, {
662
+ method: 'POST',
663
+ headers: {
664
+ Authorization: `Bearer ${token}`,
665
+ 'Content-Type': 'application/json',
666
+ },
667
+ body: JSON.stringify({ hostUserId, approvedDeviceIds }),
668
+ });
669
+ if (!res.ok) {
670
+ const data = await res.json().catch(() => ({}));
671
+ throw new Error(data.error || 'Failed to accept invitation');
672
+ }
673
+ }
package/src/index.ts CHANGED
@@ -40,6 +40,8 @@ export { YaverLoginScreen } from './LoginScreen';
40
40
  export type { YaverLoginScreenProps } from './LoginScreen';
41
41
  export { YaverMachinePickerScreen } from './MachinePickerScreen';
42
42
  export type { YaverMachinePickerProps } from './MachinePickerScreen';
43
+ export { YaverGuestOnboardingScreen } from './GuestOnboardingScreen';
44
+ export type { YaverGuestOnboardingScreenProps } from './GuestOnboardingScreen';
43
45
  export { PairDeviceModal } from './PairDeviceModal';
44
46
  export type { PairDeviceModalProps } from './PairDeviceModal';
45
47
  export { AuthOverlay } from './AuthOverlay';
@@ -72,6 +74,10 @@ export {
72
74
  signupWithEmail,
73
75
  loginWithEmail,
74
76
  listReachableDevices,
77
+ fetchGuestHosts,
78
+ findInviteByCode,
79
+ acceptGuestByCode,
80
+ acceptGuestInvitation,
75
81
  DEFAULT_CONVEX_SITE_URL,
76
82
  DEFAULT_WEB_BASE_URL,
77
83
  DEFAULT_OAUTH_REDIRECT,
@@ -81,6 +87,11 @@ export type {
81
87
  User,
82
88
  RemoteDevice,
83
89
  DeviceList,
90
+ GuestInvitation,
91
+ ActiveGuestHost,
92
+ GuestHostsResponse,
93
+ InvitationHostDevice,
94
+ InvitationPreview,
84
95
  } from './auth';
85
96
  export {
86
97
  captureScreenshot,
package/src/types.ts CHANGED
@@ -206,6 +206,13 @@ export interface FeedbackConfig {
206
206
  * Default: false (preserve historical behavior).
207
207
  */
208
208
  strictNativeAuth?: boolean;
209
+ /**
210
+ * Optional host invite code to prefill into the in-SDK guest onboarding
211
+ * flow. Useful when your app receives the code from a deep link, QR flow,
212
+ * or an out-of-band host handoff and you want the user to redeem it
213
+ * without typing.
214
+ */
215
+ guestInviteCode?: string;
209
216
  }
210
217
 
211
218
  export interface FeedbackBundle {