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/dist/AuthOverlay.js +32 -2
- package/dist/Discovery.js +2 -0
- package/dist/FeedbackModal.js +48 -3
- package/dist/GuestOnboardingScreen.d.ts +8 -0
- package/dist/GuestOnboardingScreen.js +282 -0
- package/dist/LoginScreen.d.ts +5 -1
- package/dist/LoginScreen.js +24 -7
- package/dist/MachinePickerScreen.js +45 -7
- package/dist/P2PClient.d.ts +13 -0
- package/dist/P2PClient.js +22 -0
- package/dist/YaverFeedback.d.ts +2 -0
- package/dist/YaverFeedback.js +46 -4
- package/dist/auth.d.ts +58 -0
- package/dist/auth.js +116 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +7 -1
- package/dist/types.d.ts +7 -0
- package/package.json +1 -1
- package/src/AuthOverlay.tsx +46 -2
- package/src/Discovery.ts +2 -0
- package/src/FeedbackModal.tsx +51 -2
- package/src/GuestOnboardingScreen.tsx +307 -0
- package/src/LoginScreen.tsx +40 -7
- package/src/MachinePickerScreen.tsx +48 -7
- package/src/P2PClient.ts +33 -0
- package/src/YaverFeedback.ts +50 -4
- package/src/auth.ts +183 -0
- package/src/index.ts +11 -0
- package/src/types.ts +7 -0
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 {
|