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/dist/P2PClient.d.ts
CHANGED
|
@@ -96,6 +96,19 @@ export declare class P2PClient {
|
|
|
96
96
|
}): Promise<{
|
|
97
97
|
taskId: string;
|
|
98
98
|
}>;
|
|
99
|
+
getVibingEligibility(opts?: {
|
|
100
|
+
projectName?: string;
|
|
101
|
+
bundleId?: string;
|
|
102
|
+
projectPath?: string;
|
|
103
|
+
}): Promise<{
|
|
104
|
+
canVibe: boolean;
|
|
105
|
+
reason?: string;
|
|
106
|
+
guidance?: string;
|
|
107
|
+
projectName?: string;
|
|
108
|
+
projectPath?: string;
|
|
109
|
+
provider?: string;
|
|
110
|
+
repoFullName?: string;
|
|
111
|
+
}>;
|
|
99
112
|
/**
|
|
100
113
|
* After uploading a feedback bundle with `uploadFeedback`, call this
|
|
101
114
|
* with the returned report id to create a fix task on the agent. The
|
package/dist/P2PClient.js
CHANGED
|
@@ -383,6 +383,28 @@ class P2PClient {
|
|
|
383
383
|
}
|
|
384
384
|
return response.json();
|
|
385
385
|
}
|
|
386
|
+
async getVibingEligibility(opts) {
|
|
387
|
+
const identity = resolveAppIdentity(opts);
|
|
388
|
+
const params = new URLSearchParams();
|
|
389
|
+
if (identity.projectName ?? opts?.projectName) {
|
|
390
|
+
params.set('projectName', identity.projectName ?? opts?.projectName ?? '');
|
|
391
|
+
}
|
|
392
|
+
if (identity.bundleId ?? opts?.bundleId) {
|
|
393
|
+
params.set('bundleId', identity.bundleId ?? opts?.bundleId ?? '');
|
|
394
|
+
}
|
|
395
|
+
if (identity.projectPath ?? opts?.projectPath) {
|
|
396
|
+
params.set('projectPath', identity.projectPath ?? opts?.projectPath ?? '');
|
|
397
|
+
}
|
|
398
|
+
const response = await fetch(`${this.baseUrl}/vibing/eligibility?${params.toString()}`, {
|
|
399
|
+
method: 'GET',
|
|
400
|
+
headers: { Authorization: `Bearer ${this.authToken}` },
|
|
401
|
+
});
|
|
402
|
+
if (!response.ok) {
|
|
403
|
+
const text = await response.text().catch(() => '');
|
|
404
|
+
throw new Error(`[P2PClient] Vibing eligibility failed (${response.status}): ${text}`);
|
|
405
|
+
}
|
|
406
|
+
return response.json();
|
|
407
|
+
}
|
|
386
408
|
/**
|
|
387
409
|
* After uploading a feedback bundle with `uploadFeedback`, call this
|
|
388
410
|
* with the returned report id to create a fix task on the agent. The
|
package/dist/YaverFeedback.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ import { QuickIconColorPreset } from './preferences';
|
|
|
6
6
|
* Call `YaverFeedback.init()` once at app startup.
|
|
7
7
|
*/
|
|
8
8
|
export declare class YaverFeedback {
|
|
9
|
+
private static resolveP2PAuthToken;
|
|
10
|
+
private static rebuildP2PClient;
|
|
9
11
|
/**
|
|
10
12
|
* Initialize the feedback SDK with the given configuration.
|
|
11
13
|
* Typically called in your app's root component or entry file.
|
package/dist/YaverFeedback.js
CHANGED
|
@@ -34,6 +34,7 @@ let config = null;
|
|
|
34
34
|
let enabled = false;
|
|
35
35
|
let p2pClient = null;
|
|
36
36
|
let shakeDetector = null;
|
|
37
|
+
let p2pAuthToken = null;
|
|
37
38
|
/** Ring buffer of captured errors. */
|
|
38
39
|
let errorBuffer = [];
|
|
39
40
|
let maxErrors = 5;
|
|
@@ -57,6 +58,41 @@ const flagCache = new Map();
|
|
|
57
58
|
* Call `YaverFeedback.init()` once at app startup.
|
|
58
59
|
*/
|
|
59
60
|
class YaverFeedback {
|
|
61
|
+
static async resolveP2PAuthToken() {
|
|
62
|
+
if (!config?.authToken)
|
|
63
|
+
return null;
|
|
64
|
+
if (!config.preferredDeviceId)
|
|
65
|
+
return config.authToken;
|
|
66
|
+
const devices = await (0, auth_1.listReachableDevices)(config.authToken);
|
|
67
|
+
const all = [...devices.owned, ...devices.shared];
|
|
68
|
+
const selected = all.find((device) => device.deviceId === config?.preferredDeviceId);
|
|
69
|
+
if (!selected || !selected.isGuest || selected.accessScope !== 'shared-scoped') {
|
|
70
|
+
return config.authToken;
|
|
71
|
+
}
|
|
72
|
+
if (!selected.hostUserId) {
|
|
73
|
+
return config.authToken;
|
|
74
|
+
}
|
|
75
|
+
const delegated = await (0, auth_1.mintGuestSdkToken)(config.authToken, selected.hostUserId, selected.deviceId);
|
|
76
|
+
return delegated.token;
|
|
77
|
+
}
|
|
78
|
+
static async rebuildP2PClient(agentUrl) {
|
|
79
|
+
if (!config)
|
|
80
|
+
return;
|
|
81
|
+
const effectiveUrl = agentUrl ?? config.agentUrl;
|
|
82
|
+
if (!effectiveUrl) {
|
|
83
|
+
p2pClient = null;
|
|
84
|
+
p2pAuthToken = null;
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const token = await YaverFeedback.resolveP2PAuthToken();
|
|
88
|
+
if (!token) {
|
|
89
|
+
p2pClient = null;
|
|
90
|
+
p2pAuthToken = null;
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
p2pAuthToken = token;
|
|
94
|
+
p2pClient = new P2PClient_1.P2PClient(effectiveUrl, token);
|
|
95
|
+
}
|
|
60
96
|
/**
|
|
61
97
|
* Initialize the feedback SDK with the given configuration.
|
|
62
98
|
* Typically called in your app's root component or entry file.
|
|
@@ -107,7 +143,11 @@ class YaverFeedback {
|
|
|
107
143
|
}
|
|
108
144
|
// Create P2P client if we have a URL
|
|
109
145
|
if (config.agentUrl) {
|
|
146
|
+
p2pAuthToken = config.authToken ?? null;
|
|
110
147
|
p2pClient = new P2PClient_1.P2PClient(config.agentUrl, config.authToken ?? '');
|
|
148
|
+
if (config.authToken) {
|
|
149
|
+
void YaverFeedback.rebuildP2PClient(config.agentUrl);
|
|
150
|
+
}
|
|
111
151
|
}
|
|
112
152
|
else {
|
|
113
153
|
p2pClient = null;
|
|
@@ -228,7 +268,7 @@ class YaverFeedback {
|
|
|
228
268
|
});
|
|
229
269
|
if (result && config) {
|
|
230
270
|
config.agentUrl = result.url;
|
|
231
|
-
|
|
271
|
+
await YaverFeedback.rebuildP2PClient(result.url);
|
|
232
272
|
}
|
|
233
273
|
}
|
|
234
274
|
catch {
|
|
@@ -258,7 +298,7 @@ class YaverFeedback {
|
|
|
258
298
|
if (!result)
|
|
259
299
|
return false;
|
|
260
300
|
config.agentUrl = result.url;
|
|
261
|
-
|
|
301
|
+
await YaverFeedback.rebuildP2PClient(result.url);
|
|
262
302
|
return true;
|
|
263
303
|
}
|
|
264
304
|
catch {
|
|
@@ -304,7 +344,7 @@ class YaverFeedback {
|
|
|
304
344
|
return;
|
|
305
345
|
config.authToken = token;
|
|
306
346
|
if (config.agentUrl) {
|
|
307
|
-
|
|
347
|
+
await YaverFeedback.rebuildP2PClient(config.agentUrl);
|
|
308
348
|
}
|
|
309
349
|
else {
|
|
310
350
|
await YaverFeedback.discoverAgent();
|
|
@@ -344,6 +384,7 @@ class YaverFeedback {
|
|
|
344
384
|
config.preferredDeviceId = deviceId;
|
|
345
385
|
config.agentUrl = undefined;
|
|
346
386
|
p2pClient = null;
|
|
387
|
+
p2pAuthToken = null;
|
|
347
388
|
await YaverFeedback.discoverAgent();
|
|
348
389
|
}
|
|
349
390
|
/** Resolve the currently selected remote machine from the authenticated device list. */
|
|
@@ -368,6 +409,7 @@ class YaverFeedback {
|
|
|
368
409
|
config.agentUrl = undefined;
|
|
369
410
|
}
|
|
370
411
|
p2pClient = null;
|
|
412
|
+
p2pAuthToken = null;
|
|
371
413
|
}
|
|
372
414
|
/**
|
|
373
415
|
* Manually trigger the feedback collection flow.
|
|
@@ -404,7 +446,7 @@ class YaverFeedback {
|
|
|
404
446
|
});
|
|
405
447
|
if (result) {
|
|
406
448
|
config.agentUrl = result.url;
|
|
407
|
-
|
|
449
|
+
await YaverFeedback.rebuildP2PClient(result.url);
|
|
408
450
|
}
|
|
409
451
|
else if (config.autoLogin !== false && !config.preferredDeviceId) {
|
|
410
452
|
// No agent discovered and no device picked yet — prompt the user
|
package/dist/auth.d.ts
CHANGED
|
@@ -100,8 +100,10 @@ export interface RemoteDevice {
|
|
|
100
100
|
runnerDown: boolean;
|
|
101
101
|
lastHeartbeat: number;
|
|
102
102
|
isGuest: boolean;
|
|
103
|
+
hostUserId?: string;
|
|
103
104
|
hostName?: string;
|
|
104
105
|
hostEmail?: string;
|
|
106
|
+
hostUserIdString?: string;
|
|
105
107
|
accessScope: 'owner' | 'shared-scoped' | 'shared-legacy';
|
|
106
108
|
quicHost: string;
|
|
107
109
|
quicPort: number;
|
|
@@ -121,6 +123,48 @@ export interface DeviceList {
|
|
|
121
123
|
owned: RemoteDevice[];
|
|
122
124
|
shared: RemoteDevice[];
|
|
123
125
|
}
|
|
126
|
+
export interface DeviceReachability {
|
|
127
|
+
reachable: boolean;
|
|
128
|
+
url?: string;
|
|
129
|
+
}
|
|
130
|
+
export interface GuestInvitation {
|
|
131
|
+
hostUserId: string;
|
|
132
|
+
hostName: string;
|
|
133
|
+
hostEmail: string;
|
|
134
|
+
hostUserIdString?: string;
|
|
135
|
+
createdAt: number;
|
|
136
|
+
expiresAt: number;
|
|
137
|
+
inviteCode?: string;
|
|
138
|
+
}
|
|
139
|
+
export interface ActiveGuestHost {
|
|
140
|
+
hostUserId: string;
|
|
141
|
+
hostName: string;
|
|
142
|
+
hostEmail: string;
|
|
143
|
+
grantedAt: number;
|
|
144
|
+
}
|
|
145
|
+
export interface GuestHostsResponse {
|
|
146
|
+
pending: GuestInvitation[];
|
|
147
|
+
active: ActiveGuestHost[];
|
|
148
|
+
}
|
|
149
|
+
export interface InvitationHostDevice {
|
|
150
|
+
deviceId: string;
|
|
151
|
+
name: string;
|
|
152
|
+
platform: string;
|
|
153
|
+
lastHeartbeat?: number;
|
|
154
|
+
proposed: boolean;
|
|
155
|
+
}
|
|
156
|
+
export interface InvitationPreview {
|
|
157
|
+
inviteCode: string;
|
|
158
|
+
hostUserId: string;
|
|
159
|
+
hostName: string;
|
|
160
|
+
hostEmail: string;
|
|
161
|
+
hostUserIdString?: string;
|
|
162
|
+
proposedDeviceIds?: string[];
|
|
163
|
+
hostDevices: InvitationHostDevice[];
|
|
164
|
+
invitedByUserId?: boolean;
|
|
165
|
+
expiresAt: number;
|
|
166
|
+
createdAt: number;
|
|
167
|
+
}
|
|
124
168
|
/**
|
|
125
169
|
* Fetch the set of remote dev machines this user can reach. Splits into
|
|
126
170
|
* owned (user is the host) vs shared (host invited them as a guest).
|
|
@@ -130,3 +174,17 @@ export interface DeviceList {
|
|
|
130
174
|
* raw list used to render as "Kvancs-MacBook-Air.local ×3" in the picker.
|
|
131
175
|
*/
|
|
132
176
|
export declare function listReachableDevices(token: string): Promise<DeviceList>;
|
|
177
|
+
export declare function buildDeviceCandidateUrls(device: RemoteDevice): string[];
|
|
178
|
+
export declare function probeDeviceReachability(device: RemoteDevice, timeoutMs?: number): Promise<DeviceReachability>;
|
|
179
|
+
export declare function mintGuestSdkToken(token: string, hostUserId: string, targetDeviceId: string): Promise<{
|
|
180
|
+
token: string;
|
|
181
|
+
expiresAt: number;
|
|
182
|
+
allowedProjects?: string[];
|
|
183
|
+
}>;
|
|
184
|
+
export declare function fetchGuestHosts(token: string): Promise<GuestHostsResponse>;
|
|
185
|
+
export declare function findInviteByCode(token: string, code: string): Promise<InvitationPreview | null>;
|
|
186
|
+
export declare function acceptGuestByCode(token: string, code: string, approvedDeviceIds?: string[]): Promise<{
|
|
187
|
+
hostName: string;
|
|
188
|
+
hostEmail: string;
|
|
189
|
+
}>;
|
|
190
|
+
export declare function acceptGuestInvitation(token: string, hostUserId: string, approvedDeviceIds?: string[]): Promise<void>;
|
package/dist/auth.js
CHANGED
|
@@ -37,6 +37,13 @@ exports.signInWithOAuth = signInWithOAuth;
|
|
|
37
37
|
exports.signupWithEmail = signupWithEmail;
|
|
38
38
|
exports.loginWithEmail = loginWithEmail;
|
|
39
39
|
exports.listReachableDevices = listReachableDevices;
|
|
40
|
+
exports.buildDeviceCandidateUrls = buildDeviceCandidateUrls;
|
|
41
|
+
exports.probeDeviceReachability = probeDeviceReachability;
|
|
42
|
+
exports.mintGuestSdkToken = mintGuestSdkToken;
|
|
43
|
+
exports.fetchGuestHosts = fetchGuestHosts;
|
|
44
|
+
exports.findInviteByCode = findInviteByCode;
|
|
45
|
+
exports.acceptGuestByCode = acceptGuestByCode;
|
|
46
|
+
exports.acceptGuestInvitation = acceptGuestInvitation;
|
|
40
47
|
// AsyncStorage is an optional peer dep — degrade gracefully if missing.
|
|
41
48
|
let AsyncStorage = null;
|
|
42
49
|
try {
|
|
@@ -379,8 +386,10 @@ async function listReachableDevices(token) {
|
|
|
379
386
|
runnerDown: !!d.runnerDown,
|
|
380
387
|
lastHeartbeat: d.lastHeartbeat ?? 0,
|
|
381
388
|
isGuest: !!d.isGuest,
|
|
389
|
+
hostUserId: d.hostUserId,
|
|
382
390
|
hostName: d.hostName,
|
|
383
391
|
hostEmail: d.hostEmail,
|
|
392
|
+
hostUserIdString: d.hostUserIdString,
|
|
384
393
|
accessScope: d.accessScope ?? 'owner',
|
|
385
394
|
quicHost: d.quicHost ?? d.host ?? '',
|
|
386
395
|
quicPort: d.quicPort ?? 0,
|
|
@@ -405,3 +414,110 @@ async function listReachableDevices(token) {
|
|
|
405
414
|
return { owned: [], shared: [] };
|
|
406
415
|
}
|
|
407
416
|
}
|
|
417
|
+
function buildDeviceCandidateUrls(device) {
|
|
418
|
+
const port = device.httpPort ?? device.quicPort ?? 18080;
|
|
419
|
+
const hosts = new Set();
|
|
420
|
+
if (device.quicHost)
|
|
421
|
+
hosts.add(device.quicHost);
|
|
422
|
+
for (const ip of device.localIps ?? []) {
|
|
423
|
+
if (ip)
|
|
424
|
+
hosts.add(ip);
|
|
425
|
+
}
|
|
426
|
+
return Array.from(hosts).map((host) => `http://${host}:${port}`);
|
|
427
|
+
}
|
|
428
|
+
async function probeDeviceReachability(device, timeoutMs = 2500) {
|
|
429
|
+
const candidates = buildDeviceCandidateUrls(device);
|
|
430
|
+
if (candidates.length === 0)
|
|
431
|
+
return { reachable: false };
|
|
432
|
+
const probeOne = async (baseUrl) => {
|
|
433
|
+
const controller = new AbortController();
|
|
434
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
435
|
+
try {
|
|
436
|
+
const response = await fetch(`${baseUrl}/health`, {
|
|
437
|
+
method: 'GET',
|
|
438
|
+
signal: controller.signal,
|
|
439
|
+
});
|
|
440
|
+
if (!response.ok)
|
|
441
|
+
throw new Error(`health ${response.status}`);
|
|
442
|
+
return baseUrl;
|
|
443
|
+
}
|
|
444
|
+
finally {
|
|
445
|
+
clearTimeout(timer);
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
const settled = await Promise.allSettled(candidates.map((url) => probeOne(url)));
|
|
449
|
+
for (const result of settled) {
|
|
450
|
+
if (result.status === 'fulfilled') {
|
|
451
|
+
return { reachable: true, url: result.value };
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return { reachable: false };
|
|
455
|
+
}
|
|
456
|
+
async function mintGuestSdkToken(token, hostUserId, targetDeviceId) {
|
|
457
|
+
const res = await fetch(`${convexSiteUrl}/guests/sdk-token`, {
|
|
458
|
+
method: 'POST',
|
|
459
|
+
headers: {
|
|
460
|
+
Authorization: `Bearer ${token}`,
|
|
461
|
+
'Content-Type': 'application/json',
|
|
462
|
+
},
|
|
463
|
+
body: JSON.stringify({ hostUserId, targetDeviceId }),
|
|
464
|
+
});
|
|
465
|
+
if (!res.ok) {
|
|
466
|
+
const data = await res.json().catch(() => ({}));
|
|
467
|
+
throw new Error(data.error || 'Failed to mint delegated SDK token');
|
|
468
|
+
}
|
|
469
|
+
return res.json();
|
|
470
|
+
}
|
|
471
|
+
async function fetchGuestHosts(token) {
|
|
472
|
+
const res = await fetch(`${convexSiteUrl}/guests/hosts`, {
|
|
473
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
474
|
+
});
|
|
475
|
+
if (!res.ok) {
|
|
476
|
+
const data = await res.json().catch(() => ({}));
|
|
477
|
+
throw new Error(data.error || 'Failed to fetch guest hosts');
|
|
478
|
+
}
|
|
479
|
+
return res.json();
|
|
480
|
+
}
|
|
481
|
+
async function findInviteByCode(token, code) {
|
|
482
|
+
const cleaned = code.toUpperCase().trim();
|
|
483
|
+
const res = await fetch(`${convexSiteUrl}/guests/find-by-code?code=${encodeURIComponent(cleaned)}`, { headers: { Authorization: `Bearer ${token}` } });
|
|
484
|
+
if (res.status === 404)
|
|
485
|
+
return null;
|
|
486
|
+
if (!res.ok) {
|
|
487
|
+
const data = await res.json().catch(() => ({}));
|
|
488
|
+
throw new Error(data.error || 'Failed to load invite');
|
|
489
|
+
}
|
|
490
|
+
return res.json();
|
|
491
|
+
}
|
|
492
|
+
async function acceptGuestByCode(token, code, approvedDeviceIds) {
|
|
493
|
+
const res = await fetch(`${convexSiteUrl}/guests/accept-code`, {
|
|
494
|
+
method: 'POST',
|
|
495
|
+
headers: {
|
|
496
|
+
Authorization: `Bearer ${token}`,
|
|
497
|
+
'Content-Type': 'application/json',
|
|
498
|
+
},
|
|
499
|
+
body: JSON.stringify({
|
|
500
|
+
code: code.toUpperCase().trim(),
|
|
501
|
+
approvedDeviceIds,
|
|
502
|
+
}),
|
|
503
|
+
});
|
|
504
|
+
if (!res.ok) {
|
|
505
|
+
const data = await res.json().catch(() => ({}));
|
|
506
|
+
throw new Error(data.error || 'Invalid invite code');
|
|
507
|
+
}
|
|
508
|
+
return res.json();
|
|
509
|
+
}
|
|
510
|
+
async function acceptGuestInvitation(token, hostUserId, approvedDeviceIds) {
|
|
511
|
+
const res = await fetch(`${convexSiteUrl}/guests/accept`, {
|
|
512
|
+
method: 'POST',
|
|
513
|
+
headers: {
|
|
514
|
+
Authorization: `Bearer ${token}`,
|
|
515
|
+
'Content-Type': 'application/json',
|
|
516
|
+
},
|
|
517
|
+
body: JSON.stringify({ hostUserId, approvedDeviceIds }),
|
|
518
|
+
});
|
|
519
|
+
if (!res.ok) {
|
|
520
|
+
const data = await res.json().catch(() => ({}));
|
|
521
|
+
throw new Error(data.error || 'Failed to accept invitation');
|
|
522
|
+
}
|
|
523
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -39,6 +39,8 @@ export { YaverLoginScreen } from './LoginScreen';
|
|
|
39
39
|
export type { YaverLoginScreenProps } from './LoginScreen';
|
|
40
40
|
export { YaverMachinePickerScreen } from './MachinePickerScreen';
|
|
41
41
|
export type { YaverMachinePickerProps } from './MachinePickerScreen';
|
|
42
|
+
export { YaverGuestOnboardingScreen } from './GuestOnboardingScreen';
|
|
43
|
+
export type { YaverGuestOnboardingScreenProps } from './GuestOnboardingScreen';
|
|
42
44
|
export { PairDeviceModal } from './PairDeviceModal';
|
|
43
45
|
export type { PairDeviceModalProps } from './PairDeviceModal';
|
|
44
46
|
export { AuthOverlay } from './AuthOverlay';
|
|
@@ -49,8 +51,8 @@ export { QuickActionIcon } from './QuickActionIcon';
|
|
|
49
51
|
export type { QuickActionIconProps } from './QuickActionIcon';
|
|
50
52
|
export { FixReport } from './FixReport';
|
|
51
53
|
export { getQuickIconDisabled, setQuickIconDisabled, clearQuickIconDisabled, } from './preferences';
|
|
52
|
-
export { configureAuthEndpoints, getConvexSiteUrl, getWebBaseUrl, getToken, saveToken, clearToken, getUser, saveUser, getSelectedDeviceId, saveSelectedDeviceId, clearSelectedDeviceId, validateToken, signInWithApple, signInWithOAuth, signupWithEmail, loginWithEmail, listReachableDevices, DEFAULT_CONVEX_SITE_URL, DEFAULT_WEB_BASE_URL, DEFAULT_OAUTH_REDIRECT, } from './auth';
|
|
53
|
-
export type { OAuthProvider, User, RemoteDevice, DeviceList, } from './auth';
|
|
54
|
+
export { configureAuthEndpoints, getConvexSiteUrl, getWebBaseUrl, getToken, saveToken, clearToken, getUser, saveUser, getSelectedDeviceId, saveSelectedDeviceId, clearSelectedDeviceId, validateToken, signInWithApple, signInWithOAuth, signupWithEmail, loginWithEmail, listReachableDevices, fetchGuestHosts, findInviteByCode, acceptGuestByCode, acceptGuestInvitation, DEFAULT_CONVEX_SITE_URL, DEFAULT_WEB_BASE_URL, DEFAULT_OAUTH_REDIRECT, } from './auth';
|
|
55
|
+
export type { OAuthProvider, User, RemoteDevice, DeviceList, GuestInvitation, ActiveGuestHost, GuestHostsResponse, InvitationHostDevice, InvitationPreview, } from './auth';
|
|
54
56
|
export { captureScreenshot, pickFeedbackFile, startVideoRecording, stopVideoRecording, isVideoRecording, } from './capture';
|
|
55
57
|
export { uploadFeedback } from './upload';
|
|
56
58
|
export type { FeedbackConfig, FeedbackBundle, FeedbackMetadata, DeviceInfo, AppInfo, TimelineEvent, FeedbackReport, FeedbackStreamEvent, VoiceCapability, CapturedError, TestFix, TestSession, } from './types';
|
package/dist/index.js
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* ```
|
|
30
30
|
*/
|
|
31
31
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
|
-
exports.uploadFeedback = exports.isVideoRecording = exports.stopVideoRecording = exports.startVideoRecording = exports.pickFeedbackFile = exports.captureScreenshot = exports.DEFAULT_OAUTH_REDIRECT = exports.DEFAULT_WEB_BASE_URL = exports.DEFAULT_CONVEX_SITE_URL = exports.listReachableDevices = exports.loginWithEmail = exports.signupWithEmail = exports.signInWithOAuth = exports.signInWithApple = exports.validateToken = exports.clearSelectedDeviceId = exports.saveSelectedDeviceId = exports.getSelectedDeviceId = exports.saveUser = exports.getUser = exports.clearToken = exports.saveToken = exports.getToken = exports.getWebBaseUrl = exports.getConvexSiteUrl = exports.configureAuthEndpoints = exports.clearQuickIconDisabled = exports.setQuickIconDisabled = exports.getQuickIconDisabled = exports.FixReport = exports.QuickActionIcon = exports.FeedbackModal = exports.FloatingButton = exports.ShakeDetector = exports.AuthOverlay = exports.PairDeviceModal = exports.YaverMachinePickerScreen = exports.YaverLoginScreen = exports.YaverConnectionScreen = exports.P2PClient = exports.YaverDiscovery = exports.initExpo = exports.YaverUpdates = exports.BlackBox = exports.YaverFeedback = void 0;
|
|
32
|
+
exports.uploadFeedback = exports.isVideoRecording = exports.stopVideoRecording = exports.startVideoRecording = exports.pickFeedbackFile = exports.captureScreenshot = exports.DEFAULT_OAUTH_REDIRECT = exports.DEFAULT_WEB_BASE_URL = exports.DEFAULT_CONVEX_SITE_URL = exports.acceptGuestInvitation = exports.acceptGuestByCode = exports.findInviteByCode = exports.fetchGuestHosts = exports.listReachableDevices = exports.loginWithEmail = exports.signupWithEmail = exports.signInWithOAuth = exports.signInWithApple = exports.validateToken = exports.clearSelectedDeviceId = exports.saveSelectedDeviceId = exports.getSelectedDeviceId = exports.saveUser = exports.getUser = exports.clearToken = exports.saveToken = exports.getToken = exports.getWebBaseUrl = exports.getConvexSiteUrl = exports.configureAuthEndpoints = exports.clearQuickIconDisabled = exports.setQuickIconDisabled = exports.getQuickIconDisabled = exports.FixReport = exports.QuickActionIcon = exports.FeedbackModal = exports.FloatingButton = exports.ShakeDetector = exports.AuthOverlay = exports.PairDeviceModal = exports.YaverGuestOnboardingScreen = exports.YaverMachinePickerScreen = exports.YaverLoginScreen = exports.YaverConnectionScreen = exports.P2PClient = exports.YaverDiscovery = exports.initExpo = exports.YaverUpdates = exports.BlackBox = exports.YaverFeedback = void 0;
|
|
33
33
|
var YaverFeedback_1 = require("./YaverFeedback");
|
|
34
34
|
Object.defineProperty(exports, "YaverFeedback", { enumerable: true, get: function () { return YaverFeedback_1.YaverFeedback; } });
|
|
35
35
|
var BlackBox_1 = require("./BlackBox");
|
|
@@ -48,6 +48,8 @@ var LoginScreen_1 = require("./LoginScreen");
|
|
|
48
48
|
Object.defineProperty(exports, "YaverLoginScreen", { enumerable: true, get: function () { return LoginScreen_1.YaverLoginScreen; } });
|
|
49
49
|
var MachinePickerScreen_1 = require("./MachinePickerScreen");
|
|
50
50
|
Object.defineProperty(exports, "YaverMachinePickerScreen", { enumerable: true, get: function () { return MachinePickerScreen_1.YaverMachinePickerScreen; } });
|
|
51
|
+
var GuestOnboardingScreen_1 = require("./GuestOnboardingScreen");
|
|
52
|
+
Object.defineProperty(exports, "YaverGuestOnboardingScreen", { enumerable: true, get: function () { return GuestOnboardingScreen_1.YaverGuestOnboardingScreen; } });
|
|
51
53
|
var PairDeviceModal_1 = require("./PairDeviceModal");
|
|
52
54
|
Object.defineProperty(exports, "PairDeviceModal", { enumerable: true, get: function () { return PairDeviceModal_1.PairDeviceModal; } });
|
|
53
55
|
var AuthOverlay_1 = require("./AuthOverlay");
|
|
@@ -84,6 +86,10 @@ Object.defineProperty(exports, "signInWithOAuth", { enumerable: true, get: funct
|
|
|
84
86
|
Object.defineProperty(exports, "signupWithEmail", { enumerable: true, get: function () { return auth_1.signupWithEmail; } });
|
|
85
87
|
Object.defineProperty(exports, "loginWithEmail", { enumerable: true, get: function () { return auth_1.loginWithEmail; } });
|
|
86
88
|
Object.defineProperty(exports, "listReachableDevices", { enumerable: true, get: function () { return auth_1.listReachableDevices; } });
|
|
89
|
+
Object.defineProperty(exports, "fetchGuestHosts", { enumerable: true, get: function () { return auth_1.fetchGuestHosts; } });
|
|
90
|
+
Object.defineProperty(exports, "findInviteByCode", { enumerable: true, get: function () { return auth_1.findInviteByCode; } });
|
|
91
|
+
Object.defineProperty(exports, "acceptGuestByCode", { enumerable: true, get: function () { return auth_1.acceptGuestByCode; } });
|
|
92
|
+
Object.defineProperty(exports, "acceptGuestInvitation", { enumerable: true, get: function () { return auth_1.acceptGuestInvitation; } });
|
|
87
93
|
Object.defineProperty(exports, "DEFAULT_CONVEX_SITE_URL", { enumerable: true, get: function () { return auth_1.DEFAULT_CONVEX_SITE_URL; } });
|
|
88
94
|
Object.defineProperty(exports, "DEFAULT_WEB_BASE_URL", { enumerable: true, get: function () { return auth_1.DEFAULT_WEB_BASE_URL; } });
|
|
89
95
|
Object.defineProperty(exports, "DEFAULT_OAUTH_REDIRECT", { enumerable: true, get: function () { return auth_1.DEFAULT_OAUTH_REDIRECT; } });
|
package/dist/types.d.ts
CHANGED
|
@@ -209,6 +209,13 @@ export interface FeedbackConfig {
|
|
|
209
209
|
* Default: false (preserve historical behavior).
|
|
210
210
|
*/
|
|
211
211
|
strictNativeAuth?: boolean;
|
|
212
|
+
/**
|
|
213
|
+
* Optional host invite code to prefill into the in-SDK guest onboarding
|
|
214
|
+
* flow. Useful when your app receives the code from a deep link, QR flow,
|
|
215
|
+
* or an out-of-band host handoff and you want the user to redeem it
|
|
216
|
+
* without typing.
|
|
217
|
+
*/
|
|
218
|
+
guestInviteCode?: string;
|
|
212
219
|
}
|
|
213
220
|
export interface FeedbackBundle {
|
|
214
221
|
metadata: FeedbackMetadata;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yaver-feedback-react-native",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4",
|
|
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",
|
package/src/AuthOverlay.tsx
CHANGED
|
@@ -2,8 +2,9 @@ import React, { useEffect, useState } from 'react';
|
|
|
2
2
|
import { DeviceEventEmitter, Modal } from 'react-native';
|
|
3
3
|
import { YaverLoginScreen } from './LoginScreen';
|
|
4
4
|
import { YaverMachinePickerScreen } from './MachinePickerScreen';
|
|
5
|
+
import { YaverGuestOnboardingScreen } from './GuestOnboardingScreen';
|
|
5
6
|
import { YaverFeedback } from './YaverFeedback';
|
|
6
|
-
import { getToken, RemoteDevice } from './auth';
|
|
7
|
+
import { getToken, RemoteDevice, listReachableDevices } from './auth';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Presentation layer for the SDK's auth + machine-picker modals.
|
|
@@ -21,8 +22,10 @@ import { getToken, RemoteDevice } from './auth';
|
|
|
21
22
|
*/
|
|
22
23
|
export const AuthOverlay: React.FC = () => {
|
|
23
24
|
const [loginVisible, setLoginVisible] = useState(false);
|
|
25
|
+
const [guestVisible, setGuestVisible] = useState(false);
|
|
24
26
|
const [pickerVisible, setPickerVisible] = useState(false);
|
|
25
27
|
const [token, setToken] = useState<string | null>(null);
|
|
28
|
+
const [pendingInviteCode, setPendingInviteCode] = useState<string | null>(null);
|
|
26
29
|
|
|
27
30
|
useEffect(() => {
|
|
28
31
|
let mounted = true;
|
|
@@ -50,16 +53,32 @@ export const AuthOverlay: React.FC = () => {
|
|
|
50
53
|
};
|
|
51
54
|
}, []);
|
|
52
55
|
|
|
53
|
-
const
|
|
56
|
+
const continueAfterAuth = async (newToken: string, inviteCode?: string) => {
|
|
54
57
|
setToken(newToken);
|
|
55
58
|
await YaverFeedback.setAuthToken(newToken);
|
|
59
|
+
const devices = await listReachableDevices(newToken).catch(() => ({ owned: [], shared: [] }));
|
|
56
60
|
setLoginVisible(false);
|
|
61
|
+
const cleanedInviteCode = (inviteCode ?? '').trim().toUpperCase();
|
|
62
|
+
if (cleanedInviteCode) {
|
|
63
|
+
setPendingInviteCode(cleanedInviteCode);
|
|
64
|
+
setGuestVisible(true);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (devices.owned.length === 0 && devices.shared.length === 0) {
|
|
68
|
+
setGuestVisible(true);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
57
71
|
setPickerVisible(true);
|
|
58
72
|
};
|
|
59
73
|
|
|
74
|
+
const handleLoggedIn = async (newToken: string, opts?: { inviteCode?: string }) => {
|
|
75
|
+
await continueAfterAuth(newToken, opts?.inviteCode);
|
|
76
|
+
};
|
|
77
|
+
|
|
60
78
|
const handleDevicePicked = async (device: RemoteDevice) => {
|
|
61
79
|
await YaverFeedback.setPreferredDevice(device.deviceId);
|
|
62
80
|
setPickerVisible(false);
|
|
81
|
+
setGuestVisible(false);
|
|
63
82
|
// Continue straight into the feedback flow the user originally triggered.
|
|
64
83
|
DeviceEventEmitter.emit('yaverFeedback:startReport');
|
|
65
84
|
};
|
|
@@ -75,6 +94,7 @@ export const AuthOverlay: React.FC = () => {
|
|
|
75
94
|
<YaverLoginScreen
|
|
76
95
|
onLoggedIn={handleLoggedIn}
|
|
77
96
|
onCancel={() => setLoginVisible(false)}
|
|
97
|
+
initialInviteCode={pendingInviteCode ?? YaverFeedback.getConfig()?.guestInviteCode}
|
|
78
98
|
/>
|
|
79
99
|
</Modal>
|
|
80
100
|
|
|
@@ -93,6 +113,30 @@ export const AuthOverlay: React.FC = () => {
|
|
|
93
113
|
/>
|
|
94
114
|
)}
|
|
95
115
|
</Modal>
|
|
116
|
+
|
|
117
|
+
<Modal
|
|
118
|
+
visible={guestVisible && !!token}
|
|
119
|
+
animationType="slide"
|
|
120
|
+
presentationStyle="fullScreen"
|
|
121
|
+
onRequestClose={() => setGuestVisible(false)}
|
|
122
|
+
>
|
|
123
|
+
{token && (
|
|
124
|
+
<YaverGuestOnboardingScreen
|
|
125
|
+
token={token}
|
|
126
|
+
initialInviteCode={pendingInviteCode ?? YaverFeedback.getConfig()?.guestInviteCode}
|
|
127
|
+
onContinue={() => {
|
|
128
|
+
setGuestVisible(false);
|
|
129
|
+
setPendingInviteCode(null);
|
|
130
|
+
setPickerVisible(true);
|
|
131
|
+
}}
|
|
132
|
+
onCancel={() => {
|
|
133
|
+
setGuestVisible(false);
|
|
134
|
+
setPendingInviteCode(null);
|
|
135
|
+
setPickerVisible(true);
|
|
136
|
+
}}
|
|
137
|
+
/>
|
|
138
|
+
)}
|
|
139
|
+
</Modal>
|
|
96
140
|
</>
|
|
97
141
|
);
|
|
98
142
|
};
|
package/src/Discovery.ts
CHANGED
|
@@ -188,8 +188,10 @@ export class YaverDiscovery {
|
|
|
188
188
|
runnerDown: !!d.runnerDown,
|
|
189
189
|
lastHeartbeat: d.lastHeartbeat ?? 0,
|
|
190
190
|
isGuest: !!d.isGuest,
|
|
191
|
+
hostUserId: d.hostUserId,
|
|
191
192
|
hostName: d.hostName,
|
|
192
193
|
hostEmail: d.hostEmail,
|
|
194
|
+
hostUserIdString: d.hostUserIdString,
|
|
193
195
|
accessScope: d.accessScope ?? 'owner',
|
|
194
196
|
quicHost: d.quicHost ?? d.host ?? '',
|
|
195
197
|
quicPort: d.quicPort ?? 0,
|
package/src/FeedbackModal.tsx
CHANGED
|
@@ -82,6 +82,7 @@ export const FeedbackModal: React.FC = () => {
|
|
|
82
82
|
const [lastVibeTaskId, setLastVibeTaskId] = useState<string | null>(null);
|
|
83
83
|
const [quickIconColorPreset, setQuickIconColorPreset] =
|
|
84
84
|
useState<QuickIconColorPreset | null>(null);
|
|
85
|
+
const [keyboardInset, setKeyboardInset] = useState(0);
|
|
85
86
|
const [machineCard, setMachineCard] = useState<MachineCardState>({
|
|
86
87
|
device: null,
|
|
87
88
|
reachable: null,
|
|
@@ -256,6 +257,26 @@ export const FeedbackModal: React.FC = () => {
|
|
|
256
257
|
return () => clearInterval(interval);
|
|
257
258
|
}, [loadSelectedMachine, visible]);
|
|
258
259
|
|
|
260
|
+
useEffect(() => {
|
|
261
|
+
if (!visible) {
|
|
262
|
+
setKeyboardInset(0);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const showEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
|
|
267
|
+
const hideEvent = Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';
|
|
268
|
+
const showSub = Keyboard.addListener(showEvent, (event) => {
|
|
269
|
+
setKeyboardInset(event.endCoordinates?.height ?? 0);
|
|
270
|
+
});
|
|
271
|
+
const hideSub = Keyboard.addListener(hideEvent, () => {
|
|
272
|
+
setKeyboardInset(0);
|
|
273
|
+
});
|
|
274
|
+
return () => {
|
|
275
|
+
showSub.remove();
|
|
276
|
+
hideSub.remove();
|
|
277
|
+
};
|
|
278
|
+
}, [visible]);
|
|
279
|
+
|
|
259
280
|
const closeSoon = useCallback((delayMs = 1200) => {
|
|
260
281
|
setTimeout(() => {
|
|
261
282
|
if (mountedRef.current) setVisible(false);
|
|
@@ -480,8 +501,29 @@ export const FeedbackModal: React.FC = () => {
|
|
|
480
501
|
// user types what they want, hits Send, sees the task id back. If
|
|
481
502
|
// left blank, we default to "pick the next small improvement"
|
|
482
503
|
// so a one-tap workflow still works for lazy days.
|
|
483
|
-
const handleVibingButton = useCallback(() => {
|
|
504
|
+
const handleVibingButton = useCallback(async () => {
|
|
484
505
|
if (!showVibeInput) {
|
|
506
|
+
const client = YaverFeedback.getP2PClient();
|
|
507
|
+
if (!client) {
|
|
508
|
+
setError('Not connected to the agent yet.');
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
setError(null);
|
|
512
|
+
try {
|
|
513
|
+
const eligibility = await client.getVibingEligibility();
|
|
514
|
+
if (!eligibility.canVibe) {
|
|
515
|
+
const message =
|
|
516
|
+
eligibility.guidance && eligibility.guidance.trim()
|
|
517
|
+
? `${eligibility.reason ?? 'Vibe coding is unavailable.'} ${eligibility.guidance}`
|
|
518
|
+
: eligibility.reason ?? 'Vibe coding is unavailable.';
|
|
519
|
+
setError(message);
|
|
520
|
+
setToast('Vibe coding unavailable for this project.');
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
} catch (err: unknown) {
|
|
524
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
485
527
|
setShowVibeInput(true);
|
|
486
528
|
return;
|
|
487
529
|
}
|
|
@@ -548,6 +590,7 @@ export const FeedbackModal: React.FC = () => {
|
|
|
548
590
|
<Pressable style={styles.overlay} onPress={handleClose}>
|
|
549
591
|
<KeyboardAvoidingView
|
|
550
592
|
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
|
593
|
+
keyboardVerticalOffset={Platform.OS === 'ios' ? 12 : 0}
|
|
551
594
|
style={styles.kbAvoider}
|
|
552
595
|
pointerEvents="box-none"
|
|
553
596
|
>
|
|
@@ -560,8 +603,14 @@ export const FeedbackModal: React.FC = () => {
|
|
|
560
603
|
>
|
|
561
604
|
<ScrollView
|
|
562
605
|
style={styles.scroll}
|
|
563
|
-
contentContainerStyle={
|
|
606
|
+
contentContainerStyle={[
|
|
607
|
+
styles.scrollContent,
|
|
608
|
+
showVibeInput && keyboardInset > 0
|
|
609
|
+
? { paddingBottom: 8 + keyboardInset }
|
|
610
|
+
: null,
|
|
611
|
+
]}
|
|
564
612
|
keyboardShouldPersistTaps="handled"
|
|
613
|
+
keyboardDismissMode={Platform.OS === 'ios' ? 'interactive' : 'on-drag'}
|
|
565
614
|
>
|
|
566
615
|
<View style={styles.header}>
|
|
567
616
|
<Text style={styles.title}>Send Feedback</Text>
|