yaver-feedback-react-native 0.8.4 → 0.8.7
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/README.md +1 -1
- package/dist/AuthOverlay.js +45 -18
- package/dist/FeedbackModal.js +281 -0
- package/dist/P2PClient.d.ts +40 -2
- package/dist/P2PClient.js +115 -4
- package/dist/QuickActionIcon.js +27 -2
- package/dist/YaverFeedback.d.ts +13 -0
- package/dist/YaverFeedback.js +78 -34
- package/dist/__tests__/AuthDevices.test.d.ts +1 -0
- package/dist/__tests__/AuthDevices.test.js +82 -0
- package/dist/types.d.ts +76 -0
- package/package.json +2 -2
- package/src/AuthOverlay.tsx +48 -19
- package/src/FeedbackModal.tsx +320 -0
- package/src/P2PClient.ts +130 -5
- package/src/QuickActionIcon.tsx +37 -2
- package/src/YaverFeedback.ts +83 -34
- package/src/__tests__/AuthDevices.test.ts +93 -0
- package/src/types.ts +81 -0
package/src/YaverFeedback.ts
CHANGED
|
@@ -51,6 +51,7 @@ let enabled = false;
|
|
|
51
51
|
let p2pClient: P2PClient | null = null;
|
|
52
52
|
let shakeDetector: ShakeDetector | null = null;
|
|
53
53
|
let p2pAuthToken: string | null = null;
|
|
54
|
+
let reportLaunchInFlight = false;
|
|
54
55
|
|
|
55
56
|
/** Ring buffer of captured errors. */
|
|
56
57
|
let errorBuffer: CapturedError[] = [];
|
|
@@ -420,6 +421,37 @@ export class YaverFeedback {
|
|
|
420
421
|
return all.find((device) => device.deviceId === preferredDeviceId) ?? null;
|
|
421
422
|
}
|
|
422
423
|
|
|
424
|
+
/**
|
|
425
|
+
* Trigger remote device-auth for a CLI runner on the selected agent
|
|
426
|
+
* (codex login --device-auth / claude auth login --console). Returns
|
|
427
|
+
* the session so the host UI can render the verification URL + code.
|
|
428
|
+
*
|
|
429
|
+
* RN UI layer owns the modal (see FeedbackModal's runner sign-in
|
|
430
|
+
* buttons). This method just proxies into P2PClient — no browser
|
|
431
|
+
* launch, no API keys, works through the relay with an SDK token
|
|
432
|
+
* that carries the runner-auth scope.
|
|
433
|
+
*/
|
|
434
|
+
static async startRunnerBrowserAuth(
|
|
435
|
+
runner: string,
|
|
436
|
+
): Promise<import('./types').RunnerBrowserAuthSession> {
|
|
437
|
+
if (!p2pClient) {
|
|
438
|
+
throw new Error('Not connected to any agent. Select a machine first.');
|
|
439
|
+
}
|
|
440
|
+
return p2pClient.startRunnerBrowserAuth(runner);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
static async getRunnerBrowserAuthStatus(
|
|
444
|
+
sessionId: string,
|
|
445
|
+
): Promise<import('./types').RunnerBrowserAuthSession> {
|
|
446
|
+
if (!p2pClient) throw new Error('Not connected to any agent.');
|
|
447
|
+
return p2pClient.getRunnerBrowserAuthStatus(sessionId);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
static async cancelRunnerBrowserAuth(sessionId: string): Promise<void> {
|
|
451
|
+
if (!p2pClient) return;
|
|
452
|
+
await p2pClient.cancelRunnerBrowserAuth(sessionId);
|
|
453
|
+
}
|
|
454
|
+
|
|
423
455
|
/**
|
|
424
456
|
* Sign out: clear cached token + device, tear down the P2P client. The
|
|
425
457
|
* SDK stays enabled; the next feedback trigger will re-prompt for login.
|
|
@@ -450,47 +482,64 @@ export class YaverFeedback {
|
|
|
450
482
|
if (!enabled) {
|
|
451
483
|
return;
|
|
452
484
|
}
|
|
485
|
+
if (reportLaunchInFlight) {
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
453
488
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
489
|
+
reportLaunchInFlight = true;
|
|
490
|
+
const { DeviceEventEmitter } = require('react-native');
|
|
491
|
+
DeviceEventEmitter.emit('yaverFeedback:reportLaunch', {
|
|
492
|
+
state: 'starting',
|
|
493
|
+
at: Date.now(),
|
|
494
|
+
});
|
|
495
|
+
try {
|
|
496
|
+
|
|
497
|
+
// If the caller has autoLogin enabled and we have no session yet, show
|
|
498
|
+
// the in-SDK login flow instead of a failing discovery + warning spam.
|
|
460
499
|
if (!config.authToken) {
|
|
461
|
-
|
|
462
|
-
|
|
500
|
+
if (config.autoLogin !== false) {
|
|
501
|
+
await YaverFeedback.hydrateSession();
|
|
502
|
+
}
|
|
503
|
+
if (!config.authToken) {
|
|
504
|
+
YaverFeedback.showLogin();
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
463
507
|
}
|
|
464
|
-
}
|
|
465
508
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
509
|
+
// Auto-discover if no agent URL was provided
|
|
510
|
+
if (!config.agentUrl) {
|
|
511
|
+
try {
|
|
512
|
+
const result = await YaverDiscovery.discover({
|
|
513
|
+
convexUrl: config.convexUrl,
|
|
514
|
+
authToken: config.authToken,
|
|
515
|
+
preferredDeviceId: config.preferredDeviceId,
|
|
516
|
+
});
|
|
517
|
+
if (result) {
|
|
518
|
+
config.agentUrl = result.url;
|
|
519
|
+
await YaverFeedback.rebuildP2PClient(result.url);
|
|
520
|
+
} else if (config.autoLogin !== false && !config.preferredDeviceId) {
|
|
521
|
+
// No agent discovered and no device picked yet — prompt the user
|
|
522
|
+
// to pick one of their machines (handles the non-LAN case where
|
|
523
|
+
// relay discovery requires knowing which deviceId to target).
|
|
524
|
+
YaverFeedback.showMachinePicker();
|
|
525
|
+
return;
|
|
526
|
+
} else {
|
|
527
|
+
console.warn('[YaverFeedback] No agent found. Check that `yaver serve` is running on the selected machine.');
|
|
528
|
+
}
|
|
529
|
+
} catch (err) {
|
|
530
|
+
console.warn('[YaverFeedback] Auto-discovery failed:', err);
|
|
485
531
|
}
|
|
486
|
-
} catch (err) {
|
|
487
|
-
console.warn('[YaverFeedback] Auto-discovery failed:', err);
|
|
488
532
|
}
|
|
489
|
-
}
|
|
490
533
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
534
|
+
// Emit event that the FeedbackModal listens for
|
|
535
|
+
DeviceEventEmitter.emit('yaverFeedback:startReport');
|
|
536
|
+
} finally {
|
|
537
|
+
reportLaunchInFlight = false;
|
|
538
|
+
DeviceEventEmitter.emit('yaverFeedback:reportLaunch', {
|
|
539
|
+
state: 'settled',
|
|
540
|
+
at: Date.now(),
|
|
541
|
+
});
|
|
542
|
+
}
|
|
494
543
|
}
|
|
495
544
|
|
|
496
545
|
/** Returns true if the SDK has been initialized. */
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { listReachableDevices } from '../auth';
|
|
2
|
+
|
|
3
|
+
const mockFetch = jest.fn();
|
|
4
|
+
global.fetch = mockFetch as any;
|
|
5
|
+
|
|
6
|
+
describe('auth device listing', () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
jest.clearAllMocks();
|
|
9
|
+
mockFetch.mockReset();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('keeps guest-shared devices in the shared bucket', async () => {
|
|
13
|
+
mockFetch.mockResolvedValue({
|
|
14
|
+
ok: true,
|
|
15
|
+
json: () =>
|
|
16
|
+
Promise.resolve({
|
|
17
|
+
devices: [
|
|
18
|
+
{
|
|
19
|
+
deviceId: 'own-1',
|
|
20
|
+
name: 'My Mac',
|
|
21
|
+
platform: 'macos',
|
|
22
|
+
isOnline: true,
|
|
23
|
+
isGuest: false,
|
|
24
|
+
quicHost: '10.0.0.10',
|
|
25
|
+
quicPort: 18080,
|
|
26
|
+
lastHeartbeat: 123,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
deviceId: 'guest-1',
|
|
30
|
+
name: 'yaver-test-ephemeral',
|
|
31
|
+
platform: 'linux',
|
|
32
|
+
isOnline: true,
|
|
33
|
+
isGuest: true,
|
|
34
|
+
hostName: 'Kivanc Cakmak',
|
|
35
|
+
hostEmail: 'kivanc.cakmak@icloud.com',
|
|
36
|
+
accessScope: 'shared-scoped',
|
|
37
|
+
quicHost: '157.180.114.179',
|
|
38
|
+
quicPort: 18080,
|
|
39
|
+
lastHeartbeat: 456,
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
}),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const result = await listReachableDevices('sdk-user-token');
|
|
46
|
+
|
|
47
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
48
|
+
expect.stringContaining('/devices/list'),
|
|
49
|
+
expect.objectContaining({
|
|
50
|
+
headers: { Authorization: 'Bearer sdk-user-token' },
|
|
51
|
+
}),
|
|
52
|
+
);
|
|
53
|
+
expect(result.owned).toHaveLength(1);
|
|
54
|
+
expect(result.shared).toHaveLength(1);
|
|
55
|
+
expect(result.owned[0].deviceId).toBe('own-1');
|
|
56
|
+
expect(result.shared[0]).toMatchObject({
|
|
57
|
+
deviceId: 'guest-1',
|
|
58
|
+
isGuest: true,
|
|
59
|
+
hostEmail: 'kivanc.cakmak@icloud.com',
|
|
60
|
+
accessScope: 'shared-scoped',
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('shows shared devices even when the guest owns no machines', async () => {
|
|
65
|
+
mockFetch.mockResolvedValue({
|
|
66
|
+
ok: true,
|
|
67
|
+
json: () =>
|
|
68
|
+
Promise.resolve({
|
|
69
|
+
devices: [
|
|
70
|
+
{
|
|
71
|
+
deviceId: 'guest-only',
|
|
72
|
+
name: 'yaver-test-ephemeral',
|
|
73
|
+
platform: 'linux',
|
|
74
|
+
isOnline: true,
|
|
75
|
+
isGuest: true,
|
|
76
|
+
hostName: 'Kivanc Cakmak',
|
|
77
|
+
hostEmail: 'kivanc.cakmak@icloud.com',
|
|
78
|
+
accessScope: 'shared-scoped',
|
|
79
|
+
quicHost: '157.180.114.179',
|
|
80
|
+
quicPort: 18080,
|
|
81
|
+
lastHeartbeat: 789,
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
}),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const result = await listReachableDevices('guest-only-token');
|
|
88
|
+
|
|
89
|
+
expect(result.owned).toEqual([]);
|
|
90
|
+
expect(result.shared).toHaveLength(1);
|
|
91
|
+
expect(result.shared[0].deviceId).toBe('guest-only');
|
|
92
|
+
});
|
|
93
|
+
});
|
package/src/types.ts
CHANGED
|
@@ -1,3 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote browser-style sign-in session for a coding-agent CLI on the
|
|
3
|
+
* connected yaver host. Mirrors runnerBrowserAuthSession on the agent
|
|
4
|
+
* Go side. Progression: starting → awaiting_browser (openUrl + code
|
|
5
|
+
* filled) → completed | failed | cancelled.
|
|
6
|
+
*/
|
|
7
|
+
export interface RunnerBrowserAuthSession {
|
|
8
|
+
id: string;
|
|
9
|
+
runner: string;
|
|
10
|
+
method: string;
|
|
11
|
+
status: 'starting' | 'awaiting_browser' | 'completed' | 'failed' | 'cancelled';
|
|
12
|
+
openUrl?: string;
|
|
13
|
+
code?: string;
|
|
14
|
+
detail?: string;
|
|
15
|
+
authConfigured?: boolean;
|
|
16
|
+
authSource?: string;
|
|
17
|
+
error?: string;
|
|
18
|
+
startedAt: number;
|
|
19
|
+
updatedAt: number;
|
|
20
|
+
completedAt?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface IncidentEvent {
|
|
24
|
+
id: string;
|
|
25
|
+
timestamp: number;
|
|
26
|
+
severity: 'info' | 'warn' | 'error' | 'fatal';
|
|
27
|
+
category: string;
|
|
28
|
+
code: string;
|
|
29
|
+
source: string;
|
|
30
|
+
title: string;
|
|
31
|
+
userMessage: string;
|
|
32
|
+
technicalInfo?: string;
|
|
33
|
+
suggestedAction?: string;
|
|
34
|
+
operationId?: string;
|
|
35
|
+
deviceId?: string;
|
|
36
|
+
projectPath?: string;
|
|
37
|
+
target?: string;
|
|
38
|
+
logsAvailable: boolean;
|
|
39
|
+
logRefs?: string[];
|
|
40
|
+
correlationId?: string;
|
|
41
|
+
recoverable: boolean;
|
|
42
|
+
metadata?: Record<string, unknown>;
|
|
43
|
+
resolved?: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface OperationState {
|
|
47
|
+
id: string;
|
|
48
|
+
kind: string;
|
|
49
|
+
status: string;
|
|
50
|
+
phase?: string;
|
|
51
|
+
message?: string;
|
|
52
|
+
progress?: number;
|
|
53
|
+
deviceId?: string;
|
|
54
|
+
projectPath?: string;
|
|
55
|
+
startedAt: number;
|
|
56
|
+
updatedAt: number;
|
|
57
|
+
incidentIds?: string[];
|
|
58
|
+
metadata?: Record<string, unknown>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface CapabilityTargetReadiness {
|
|
62
|
+
enabled: boolean;
|
|
63
|
+
reasonCode?: string;
|
|
64
|
+
reason?: string;
|
|
65
|
+
suggestedAction?: string;
|
|
66
|
+
notes?: string[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface CapabilitySnapshot {
|
|
70
|
+
generatedAt: string;
|
|
71
|
+
machine?: Record<string, unknown>;
|
|
72
|
+
infra?: Record<string, unknown>;
|
|
73
|
+
connectivity?: {
|
|
74
|
+
directAvailable?: boolean;
|
|
75
|
+
relayConfigured?: boolean;
|
|
76
|
+
tunnelConfigured?: boolean;
|
|
77
|
+
tailscaleAvailable?: boolean;
|
|
78
|
+
};
|
|
79
|
+
targets: Record<string, CapabilityTargetReadiness>;
|
|
80
|
+
}
|
|
81
|
+
|
|
1
82
|
export interface FeedbackConfig {
|
|
2
83
|
/** URL of the Yaver agent (e.g. "http://192.168.1.10:18080"). If omitted, auto-discovery is used. */
|
|
3
84
|
agentUrl?: string;
|