yaver-feedback-react-native 0.8.1 → 0.8.3
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 +2 -2
- package/dist/AuthOverlay.js +33 -3
- package/dist/Discovery.js +2 -0
- package/dist/FeedbackModal.js +438 -215
- package/dist/GuestOnboardingScreen.d.ts +8 -0
- package/dist/GuestOnboardingScreen.js +282 -0
- package/dist/LoginScreen.d.ts +5 -1
- package/dist/LoginScreen.js +5 -2
- package/dist/MachinePickerScreen.js +1 -1
- package/dist/P2PClient.d.ts +22 -3
- package/dist/P2PClient.js +45 -2
- package/dist/QuickActionIcon.js +16 -4
- package/dist/YaverFeedback.d.ts +7 -0
- package/dist/YaverFeedback.js +68 -4
- package/dist/__tests__/P2PClient.test.js +30 -0
- package/dist/__tests__/YaverFeedback.test.js +39 -0
- package/dist/auth.d.ts +52 -0
- package/dist/auth.js +75 -0
- package/dist/index.d.ts +7 -6
- package/dist/index.js +10 -5
- package/dist/preferences.d.ts +11 -0
- package/dist/preferences.js +82 -0
- package/dist/types.d.ts +7 -0
- package/package.json +6 -4
- package/src/AuthOverlay.tsx +47 -2
- package/src/Discovery.ts +2 -0
- package/src/FeedbackModal.tsx +507 -258
- package/src/GuestOnboardingScreen.tsx +307 -0
- package/src/LoginScreen.tsx +21 -2
- package/src/MachinePickerScreen.tsx +3 -1
- package/src/P2PClient.ts +69 -3
- package/src/QuickActionIcon.tsx +25 -5
- package/src/YaverFeedback.ts +79 -4
- package/src/__tests__/P2PClient.test.ts +40 -0
- package/src/__tests__/YaverFeedback.test.ts +42 -0
- package/src/auth.ts +137 -0
- package/src/index.ts +14 -4
- package/src/preferences.ts +96 -0
- package/src/types.ts +7 -0
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,8 +384,18 @@ 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
|
}
|
|
390
|
+
/** Resolve the currently selected remote machine from the authenticated device list. */
|
|
391
|
+
static async getSelectedRemoteDevice() {
|
|
392
|
+
if (!config?.authToken || !config.preferredDeviceId)
|
|
393
|
+
return null;
|
|
394
|
+
const preferredDeviceId = config.preferredDeviceId;
|
|
395
|
+
const devices = await (0, auth_1.listReachableDevices)(config.authToken);
|
|
396
|
+
const all = [...devices.owned, ...devices.shared];
|
|
397
|
+
return all.find((device) => device.deviceId === preferredDeviceId) ?? null;
|
|
398
|
+
}
|
|
349
399
|
/**
|
|
350
400
|
* Sign out: clear cached token + device, tear down the P2P client. The
|
|
351
401
|
* SDK stays enabled; the next feedback trigger will re-prompt for login.
|
|
@@ -359,6 +409,7 @@ class YaverFeedback {
|
|
|
359
409
|
config.agentUrl = undefined;
|
|
360
410
|
}
|
|
361
411
|
p2pClient = null;
|
|
412
|
+
p2pAuthToken = null;
|
|
362
413
|
}
|
|
363
414
|
/**
|
|
364
415
|
* Manually trigger the feedback collection flow.
|
|
@@ -395,7 +446,7 @@ class YaverFeedback {
|
|
|
395
446
|
});
|
|
396
447
|
if (result) {
|
|
397
448
|
config.agentUrl = result.url;
|
|
398
|
-
|
|
449
|
+
await YaverFeedback.rebuildP2PClient(result.url);
|
|
399
450
|
}
|
|
400
451
|
else if (config.autoLogin !== false && !config.preferredDeviceId) {
|
|
401
452
|
// No agent discovered and no device picked yet — prompt the user
|
|
@@ -809,6 +860,19 @@ class YaverFeedback {
|
|
|
809
860
|
static async isQuickIconHidden() {
|
|
810
861
|
return (0, preferences_1.getQuickIconDisabled)();
|
|
811
862
|
}
|
|
863
|
+
static async setQuickIconColorPreset(preset) {
|
|
864
|
+
await (0, preferences_1.setQuickIconColorPreset)(preset);
|
|
865
|
+
try {
|
|
866
|
+
const { DeviceEventEmitter } = require('react-native');
|
|
867
|
+
DeviceEventEmitter.emit('yaverFeedback:quickIconColorChange', { preset });
|
|
868
|
+
}
|
|
869
|
+
catch {
|
|
870
|
+
// emitter unavailable — preference is still persisted
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
static async getQuickIconColorPreset() {
|
|
874
|
+
return (0, preferences_1.getQuickIconColorPreset)();
|
|
875
|
+
}
|
|
812
876
|
/** Clear the persisted "user hid the icon" flag. */
|
|
813
877
|
static async resetQuickIconPreference() {
|
|
814
878
|
await YaverFeedback.setQuickIconVisible(true);
|
|
@@ -166,4 +166,34 @@ describe('P2PClient', () => {
|
|
|
166
166
|
expect(result).toEqual(builds);
|
|
167
167
|
});
|
|
168
168
|
});
|
|
169
|
+
describe('reloadApp()', () => {
|
|
170
|
+
it('returns an acknowledgement for dev reloads', async () => {
|
|
171
|
+
mockFetch.mockResolvedValue({
|
|
172
|
+
ok: true,
|
|
173
|
+
json: () => Promise.resolve({ ok: true, changeClass: 'js_only' }),
|
|
174
|
+
});
|
|
175
|
+
const client = new P2PClient_1.P2PClient('http://localhost:18080', 'tok');
|
|
176
|
+
const result = await client.reloadApp('dev');
|
|
177
|
+
expect(result).toEqual(expect.objectContaining({
|
|
178
|
+
ok: true,
|
|
179
|
+
mode: 'dev',
|
|
180
|
+
acknowledged: true,
|
|
181
|
+
message: 'Hot reload request accepted.',
|
|
182
|
+
}));
|
|
183
|
+
});
|
|
184
|
+
it('returns an acknowledgement for bundle reloads', async () => {
|
|
185
|
+
mockFetch.mockResolvedValue({
|
|
186
|
+
ok: true,
|
|
187
|
+
json: () => Promise.resolve({ ok: true }),
|
|
188
|
+
});
|
|
189
|
+
const client = new P2PClient_1.P2PClient('http://localhost:18080', 'tok');
|
|
190
|
+
const result = await client.reloadApp('bundle');
|
|
191
|
+
expect(result).toEqual(expect.objectContaining({
|
|
192
|
+
ok: true,
|
|
193
|
+
mode: 'bundle',
|
|
194
|
+
acknowledged: true,
|
|
195
|
+
message: 'Reload request acknowledged. Agent is rebuilding the bundle.',
|
|
196
|
+
}));
|
|
197
|
+
});
|
|
198
|
+
});
|
|
169
199
|
});
|
|
@@ -16,6 +16,33 @@ jest.mock('../Discovery', () => ({
|
|
|
16
16
|
discover: jest.fn(),
|
|
17
17
|
},
|
|
18
18
|
}));
|
|
19
|
+
jest.mock('../auth', () => ({
|
|
20
|
+
configureAuthEndpoints: jest.fn(),
|
|
21
|
+
setStrictNativeAuth: jest.fn(),
|
|
22
|
+
getToken: jest.fn(async () => null),
|
|
23
|
+
getSelectedDeviceId: jest.fn(async () => null),
|
|
24
|
+
clearToken: jest.fn(async () => { }),
|
|
25
|
+
clearSelectedDeviceId: jest.fn(async () => { }),
|
|
26
|
+
listReachableDevices: jest.fn(async () => ({
|
|
27
|
+
owned: [
|
|
28
|
+
{
|
|
29
|
+
deviceId: 'device-1',
|
|
30
|
+
name: 'Dev Mac',
|
|
31
|
+
platform: 'darwin',
|
|
32
|
+
isOnline: true,
|
|
33
|
+
needsAuth: false,
|
|
34
|
+
runnerDown: false,
|
|
35
|
+
lastHeartbeat: Date.now(),
|
|
36
|
+
isGuest: false,
|
|
37
|
+
accessScope: 'owner',
|
|
38
|
+
quicHost: '127.0.0.1',
|
|
39
|
+
quicPort: 18080,
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
shared: [],
|
|
43
|
+
})),
|
|
44
|
+
DEFAULT_CONVEX_SITE_URL: 'https://example.convex.site',
|
|
45
|
+
}));
|
|
19
46
|
// Reset module-level state between tests by re-requiring
|
|
20
47
|
beforeEach(() => {
|
|
21
48
|
// YaverFeedback uses module-level variables (config, enabled, p2pClient).
|
|
@@ -106,6 +133,18 @@ describe('YaverFeedback', () => {
|
|
|
106
133
|
expect(cfg.agentUrl).toBe('http://10.0.0.1:18080');
|
|
107
134
|
});
|
|
108
135
|
});
|
|
136
|
+
describe('getSelectedRemoteDevice()', () => {
|
|
137
|
+
it('returns the selected device from the reachable device list', async () => {
|
|
138
|
+
YaverFeedback_1.YaverFeedback.init({
|
|
139
|
+
authToken: 'tok',
|
|
140
|
+
preferredDeviceId: 'device-1',
|
|
141
|
+
enabled: true,
|
|
142
|
+
});
|
|
143
|
+
const device = await YaverFeedback_1.YaverFeedback.getSelectedRemoteDevice();
|
|
144
|
+
expect(device?.deviceId).toBe('device-1');
|
|
145
|
+
expect(device?.name).toBe('Dev Mac');
|
|
146
|
+
});
|
|
147
|
+
});
|
|
109
148
|
describe('startReport()', () => {
|
|
110
149
|
it('does nothing when not enabled', async () => {
|
|
111
150
|
YaverFeedback_1.YaverFeedback.init({ authToken: 'tok', enabled: false });
|
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,44 @@ export interface DeviceList {
|
|
|
121
123
|
owned: RemoteDevice[];
|
|
122
124
|
shared: RemoteDevice[];
|
|
123
125
|
}
|
|
126
|
+
export interface GuestInvitation {
|
|
127
|
+
hostUserId: string;
|
|
128
|
+
hostName: string;
|
|
129
|
+
hostEmail: string;
|
|
130
|
+
hostUserIdString?: string;
|
|
131
|
+
createdAt: number;
|
|
132
|
+
expiresAt: number;
|
|
133
|
+
inviteCode?: string;
|
|
134
|
+
}
|
|
135
|
+
export interface ActiveGuestHost {
|
|
136
|
+
hostUserId: string;
|
|
137
|
+
hostName: string;
|
|
138
|
+
hostEmail: string;
|
|
139
|
+
grantedAt: number;
|
|
140
|
+
}
|
|
141
|
+
export interface GuestHostsResponse {
|
|
142
|
+
pending: GuestInvitation[];
|
|
143
|
+
active: ActiveGuestHost[];
|
|
144
|
+
}
|
|
145
|
+
export interface InvitationHostDevice {
|
|
146
|
+
deviceId: string;
|
|
147
|
+
name: string;
|
|
148
|
+
platform: string;
|
|
149
|
+
lastHeartbeat?: number;
|
|
150
|
+
proposed: boolean;
|
|
151
|
+
}
|
|
152
|
+
export interface InvitationPreview {
|
|
153
|
+
inviteCode: string;
|
|
154
|
+
hostUserId: string;
|
|
155
|
+
hostName: string;
|
|
156
|
+
hostEmail: string;
|
|
157
|
+
hostUserIdString?: string;
|
|
158
|
+
proposedDeviceIds?: string[];
|
|
159
|
+
hostDevices: InvitationHostDevice[];
|
|
160
|
+
invitedByUserId?: boolean;
|
|
161
|
+
expiresAt: number;
|
|
162
|
+
createdAt: number;
|
|
163
|
+
}
|
|
124
164
|
/**
|
|
125
165
|
* Fetch the set of remote dev machines this user can reach. Splits into
|
|
126
166
|
* owned (user is the host) vs shared (host invited them as a guest).
|
|
@@ -130,3 +170,15 @@ export interface DeviceList {
|
|
|
130
170
|
* raw list used to render as "Kvancs-MacBook-Air.local ×3" in the picker.
|
|
131
171
|
*/
|
|
132
172
|
export declare function listReachableDevices(token: string): Promise<DeviceList>;
|
|
173
|
+
export declare function mintGuestSdkToken(token: string, hostUserId: string, targetDeviceId: string): Promise<{
|
|
174
|
+
token: string;
|
|
175
|
+
expiresAt: number;
|
|
176
|
+
allowedProjects?: string[];
|
|
177
|
+
}>;
|
|
178
|
+
export declare function fetchGuestHosts(token: string): Promise<GuestHostsResponse>;
|
|
179
|
+
export declare function findInviteByCode(token: string, code: string): Promise<InvitationPreview | null>;
|
|
180
|
+
export declare function acceptGuestByCode(token: string, code: string, approvedDeviceIds?: string[]): Promise<{
|
|
181
|
+
hostName: string;
|
|
182
|
+
hostEmail: string;
|
|
183
|
+
}>;
|
|
184
|
+
export declare function acceptGuestInvitation(token: string, hostUserId: string, approvedDeviceIds?: string[]): Promise<void>;
|
package/dist/auth.js
CHANGED
|
@@ -37,6 +37,11 @@ exports.signInWithOAuth = signInWithOAuth;
|
|
|
37
37
|
exports.signupWithEmail = signupWithEmail;
|
|
38
38
|
exports.loginWithEmail = loginWithEmail;
|
|
39
39
|
exports.listReachableDevices = listReachableDevices;
|
|
40
|
+
exports.mintGuestSdkToken = mintGuestSdkToken;
|
|
41
|
+
exports.fetchGuestHosts = fetchGuestHosts;
|
|
42
|
+
exports.findInviteByCode = findInviteByCode;
|
|
43
|
+
exports.acceptGuestByCode = acceptGuestByCode;
|
|
44
|
+
exports.acceptGuestInvitation = acceptGuestInvitation;
|
|
40
45
|
// AsyncStorage is an optional peer dep — degrade gracefully if missing.
|
|
41
46
|
let AsyncStorage = null;
|
|
42
47
|
try {
|
|
@@ -379,8 +384,10 @@ async function listReachableDevices(token) {
|
|
|
379
384
|
runnerDown: !!d.runnerDown,
|
|
380
385
|
lastHeartbeat: d.lastHeartbeat ?? 0,
|
|
381
386
|
isGuest: !!d.isGuest,
|
|
387
|
+
hostUserId: d.hostUserId,
|
|
382
388
|
hostName: d.hostName,
|
|
383
389
|
hostEmail: d.hostEmail,
|
|
390
|
+
hostUserIdString: d.hostUserIdString,
|
|
384
391
|
accessScope: d.accessScope ?? 'owner',
|
|
385
392
|
quicHost: d.quicHost ?? d.host ?? '',
|
|
386
393
|
quicPort: d.quicPort ?? 0,
|
|
@@ -405,3 +412,71 @@ async function listReachableDevices(token) {
|
|
|
405
412
|
return { owned: [], shared: [] };
|
|
406
413
|
}
|
|
407
414
|
}
|
|
415
|
+
async function mintGuestSdkToken(token, hostUserId, targetDeviceId) {
|
|
416
|
+
const res = await fetch(`${convexSiteUrl}/guests/sdk-token`, {
|
|
417
|
+
method: 'POST',
|
|
418
|
+
headers: {
|
|
419
|
+
Authorization: `Bearer ${token}`,
|
|
420
|
+
'Content-Type': 'application/json',
|
|
421
|
+
},
|
|
422
|
+
body: JSON.stringify({ hostUserId, targetDeviceId }),
|
|
423
|
+
});
|
|
424
|
+
if (!res.ok) {
|
|
425
|
+
const data = await res.json().catch(() => ({}));
|
|
426
|
+
throw new Error(data.error || 'Failed to mint delegated SDK token');
|
|
427
|
+
}
|
|
428
|
+
return res.json();
|
|
429
|
+
}
|
|
430
|
+
async function fetchGuestHosts(token) {
|
|
431
|
+
const res = await fetch(`${convexSiteUrl}/guests/hosts`, {
|
|
432
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
433
|
+
});
|
|
434
|
+
if (!res.ok) {
|
|
435
|
+
const data = await res.json().catch(() => ({}));
|
|
436
|
+
throw new Error(data.error || 'Failed to fetch guest hosts');
|
|
437
|
+
}
|
|
438
|
+
return res.json();
|
|
439
|
+
}
|
|
440
|
+
async function findInviteByCode(token, code) {
|
|
441
|
+
const cleaned = code.toUpperCase().trim();
|
|
442
|
+
const res = await fetch(`${convexSiteUrl}/guests/find-by-code?code=${encodeURIComponent(cleaned)}`, { headers: { Authorization: `Bearer ${token}` } });
|
|
443
|
+
if (res.status === 404)
|
|
444
|
+
return null;
|
|
445
|
+
if (!res.ok) {
|
|
446
|
+
const data = await res.json().catch(() => ({}));
|
|
447
|
+
throw new Error(data.error || 'Failed to load invite');
|
|
448
|
+
}
|
|
449
|
+
return res.json();
|
|
450
|
+
}
|
|
451
|
+
async function acceptGuestByCode(token, code, approvedDeviceIds) {
|
|
452
|
+
const res = await fetch(`${convexSiteUrl}/guests/accept-code`, {
|
|
453
|
+
method: 'POST',
|
|
454
|
+
headers: {
|
|
455
|
+
Authorization: `Bearer ${token}`,
|
|
456
|
+
'Content-Type': 'application/json',
|
|
457
|
+
},
|
|
458
|
+
body: JSON.stringify({
|
|
459
|
+
code: code.toUpperCase().trim(),
|
|
460
|
+
approvedDeviceIds,
|
|
461
|
+
}),
|
|
462
|
+
});
|
|
463
|
+
if (!res.ok) {
|
|
464
|
+
const data = await res.json().catch(() => ({}));
|
|
465
|
+
throw new Error(data.error || 'Invalid invite code');
|
|
466
|
+
}
|
|
467
|
+
return res.json();
|
|
468
|
+
}
|
|
469
|
+
async function acceptGuestInvitation(token, hostUserId, approvedDeviceIds) {
|
|
470
|
+
const res = await fetch(`${convexSiteUrl}/guests/accept`, {
|
|
471
|
+
method: 'POST',
|
|
472
|
+
headers: {
|
|
473
|
+
Authorization: `Bearer ${token}`,
|
|
474
|
+
'Content-Type': 'application/json',
|
|
475
|
+
},
|
|
476
|
+
body: JSON.stringify({ hostUserId, approvedDeviceIds }),
|
|
477
|
+
});
|
|
478
|
+
if (!res.ok) {
|
|
479
|
+
const data = await res.json().catch(() => ({}));
|
|
480
|
+
throw new Error(data.error || 'Failed to accept invitation');
|
|
481
|
+
}
|
|
482
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* yaver-feedback-react-native — Visual feedback SDK for Yaver.
|
|
3
3
|
*
|
|
4
|
-
* Shake-to-report surface with
|
|
4
|
+
* Shake-to-report surface with three launch actions:
|
|
5
5
|
* 1. Hot Reload — instant JS reload
|
|
6
6
|
* 2. Vibing — open a vibing session on the agent
|
|
7
|
-
* 3. Screenshot
|
|
8
|
-
*
|
|
9
|
-
* 4. Screen Recording — start, then stop + upload
|
|
7
|
+
* 3. Screenshot & Fix — capture the current screen and trigger
|
|
8
|
+
* the fix loop
|
|
10
9
|
*
|
|
11
10
|
* The small quick-access icon stays hidden until the first shake by
|
|
12
11
|
* default on mobile, then remains available unless the user hides it.
|
|
@@ -40,6 +39,8 @@ export { YaverLoginScreen } from './LoginScreen';
|
|
|
40
39
|
export type { YaverLoginScreenProps } from './LoginScreen';
|
|
41
40
|
export { YaverMachinePickerScreen } from './MachinePickerScreen';
|
|
42
41
|
export type { YaverMachinePickerProps } from './MachinePickerScreen';
|
|
42
|
+
export { YaverGuestOnboardingScreen } from './GuestOnboardingScreen';
|
|
43
|
+
export type { YaverGuestOnboardingScreenProps } from './GuestOnboardingScreen';
|
|
43
44
|
export { PairDeviceModal } from './PairDeviceModal';
|
|
44
45
|
export type { PairDeviceModalProps } from './PairDeviceModal';
|
|
45
46
|
export { AuthOverlay } from './AuthOverlay';
|
|
@@ -50,8 +51,8 @@ export { QuickActionIcon } from './QuickActionIcon';
|
|
|
50
51
|
export type { QuickActionIconProps } from './QuickActionIcon';
|
|
51
52
|
export { FixReport } from './FixReport';
|
|
52
53
|
export { getQuickIconDisabled, setQuickIconDisabled, clearQuickIconDisabled, } from './preferences';
|
|
53
|
-
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';
|
|
54
|
-
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';
|
|
55
56
|
export { captureScreenshot, pickFeedbackFile, startVideoRecording, stopVideoRecording, isVideoRecording, } from './capture';
|
|
56
57
|
export { uploadFeedback } from './upload';
|
|
57
58
|
export type { FeedbackConfig, FeedbackBundle, FeedbackMetadata, DeviceInfo, AppInfo, TimelineEvent, FeedbackReport, FeedbackStreamEvent, VoiceCapability, CapturedError, TestFix, TestSession, } from './types';
|
package/dist/index.js
CHANGED
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* yaver-feedback-react-native — Visual feedback SDK for Yaver.
|
|
4
4
|
*
|
|
5
|
-
* Shake-to-report surface with
|
|
5
|
+
* Shake-to-report surface with three launch actions:
|
|
6
6
|
* 1. Hot Reload — instant JS reload
|
|
7
7
|
* 2. Vibing — open a vibing session on the agent
|
|
8
|
-
* 3. Screenshot
|
|
9
|
-
*
|
|
10
|
-
* 4. Screen Recording — start, then stop + upload
|
|
8
|
+
* 3. Screenshot & Fix — capture the current screen and trigger
|
|
9
|
+
* the fix loop
|
|
11
10
|
*
|
|
12
11
|
* The small quick-access icon stays hidden until the first shake by
|
|
13
12
|
* default on mobile, then remains available unless the user hides it.
|
|
@@ -30,7 +29,7 @@
|
|
|
30
29
|
* ```
|
|
31
30
|
*/
|
|
32
31
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
|
-
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;
|
|
34
33
|
var YaverFeedback_1 = require("./YaverFeedback");
|
|
35
34
|
Object.defineProperty(exports, "YaverFeedback", { enumerable: true, get: function () { return YaverFeedback_1.YaverFeedback; } });
|
|
36
35
|
var BlackBox_1 = require("./BlackBox");
|
|
@@ -49,6 +48,8 @@ var LoginScreen_1 = require("./LoginScreen");
|
|
|
49
48
|
Object.defineProperty(exports, "YaverLoginScreen", { enumerable: true, get: function () { return LoginScreen_1.YaverLoginScreen; } });
|
|
50
49
|
var MachinePickerScreen_1 = require("./MachinePickerScreen");
|
|
51
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; } });
|
|
52
53
|
var PairDeviceModal_1 = require("./PairDeviceModal");
|
|
53
54
|
Object.defineProperty(exports, "PairDeviceModal", { enumerable: true, get: function () { return PairDeviceModal_1.PairDeviceModal; } });
|
|
54
55
|
var AuthOverlay_1 = require("./AuthOverlay");
|
|
@@ -85,6 +86,10 @@ Object.defineProperty(exports, "signInWithOAuth", { enumerable: true, get: funct
|
|
|
85
86
|
Object.defineProperty(exports, "signupWithEmail", { enumerable: true, get: function () { return auth_1.signupWithEmail; } });
|
|
86
87
|
Object.defineProperty(exports, "loginWithEmail", { enumerable: true, get: function () { return auth_1.loginWithEmail; } });
|
|
87
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; } });
|
|
88
93
|
Object.defineProperty(exports, "DEFAULT_CONVEX_SITE_URL", { enumerable: true, get: function () { return auth_1.DEFAULT_CONVEX_SITE_URL; } });
|
|
89
94
|
Object.defineProperty(exports, "DEFAULT_WEB_BASE_URL", { enumerable: true, get: function () { return auth_1.DEFAULT_WEB_BASE_URL; } });
|
|
90
95
|
Object.defineProperty(exports, "DEFAULT_OAUTH_REDIRECT", { enumerable: true, get: function () { return auth_1.DEFAULT_OAUTH_REDIRECT; } });
|
package/dist/preferences.d.ts
CHANGED
|
@@ -12,7 +12,18 @@
|
|
|
12
12
|
* still works (it just can't remember the disable beyond the
|
|
13
13
|
* in-memory session).
|
|
14
14
|
*/
|
|
15
|
+
export type QuickIconColorPreset = 'orange' | 'lime' | 'cyan' | 'pink' | 'yellow' | 'slate';
|
|
16
|
+
export declare const QUICK_ICON_COLOR_PRESETS: Record<QuickIconColorPreset, {
|
|
17
|
+
label: string;
|
|
18
|
+
backgroundColor: string;
|
|
19
|
+
foregroundColor: string;
|
|
20
|
+
borderColor: string;
|
|
21
|
+
shadowColor: string;
|
|
22
|
+
}>;
|
|
15
23
|
/** True if the user has long-pressed the icon and chosen "Hide". */
|
|
16
24
|
export declare function getQuickIconDisabled(): Promise<boolean>;
|
|
17
25
|
export declare function setQuickIconDisabled(disabled: boolean): Promise<void>;
|
|
18
26
|
export declare function clearQuickIconDisabled(): Promise<void>;
|
|
27
|
+
export declare function getQuickIconColorPreset(): Promise<QuickIconColorPreset | null>;
|
|
28
|
+
export declare function setQuickIconColorPreset(preset: QuickIconColorPreset | null): Promise<void>;
|
|
29
|
+
export declare function clearQuickIconColorPreset(): Promise<void>;
|
package/dist/preferences.js
CHANGED
|
@@ -14,9 +14,13 @@
|
|
|
14
14
|
* in-memory session).
|
|
15
15
|
*/
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.QUICK_ICON_COLOR_PRESETS = void 0;
|
|
17
18
|
exports.getQuickIconDisabled = getQuickIconDisabled;
|
|
18
19
|
exports.setQuickIconDisabled = setQuickIconDisabled;
|
|
19
20
|
exports.clearQuickIconDisabled = clearQuickIconDisabled;
|
|
21
|
+
exports.getQuickIconColorPreset = getQuickIconColorPreset;
|
|
22
|
+
exports.setQuickIconColorPreset = setQuickIconColorPreset;
|
|
23
|
+
exports.clearQuickIconColorPreset = clearQuickIconColorPreset;
|
|
20
24
|
let AsyncStorage = null;
|
|
21
25
|
try {
|
|
22
26
|
AsyncStorage = require('@react-native-async-storage/async-storage').default;
|
|
@@ -25,6 +29,51 @@ catch {
|
|
|
25
29
|
// not installed — degrade gracefully
|
|
26
30
|
}
|
|
27
31
|
const QUICK_ICON_DISABLED_KEY = 'yaver_feedback_quickicon_disabled';
|
|
32
|
+
const QUICK_ICON_COLOR_KEY = 'yaver_feedback_quickicon_color';
|
|
33
|
+
exports.QUICK_ICON_COLOR_PRESETS = {
|
|
34
|
+
orange: {
|
|
35
|
+
label: 'Orange',
|
|
36
|
+
backgroundColor: '#ff6b2c',
|
|
37
|
+
foregroundColor: '#111111',
|
|
38
|
+
borderColor: 'rgba(255,255,255,0.92)',
|
|
39
|
+
shadowColor: '#000000',
|
|
40
|
+
},
|
|
41
|
+
lime: {
|
|
42
|
+
label: 'Lime',
|
|
43
|
+
backgroundColor: '#a3e635',
|
|
44
|
+
foregroundColor: '#111111',
|
|
45
|
+
borderColor: 'rgba(255,255,255,0.85)',
|
|
46
|
+
shadowColor: '#365314',
|
|
47
|
+
},
|
|
48
|
+
cyan: {
|
|
49
|
+
label: 'Cyan',
|
|
50
|
+
backgroundColor: '#22d3ee',
|
|
51
|
+
foregroundColor: '#082f49',
|
|
52
|
+
borderColor: 'rgba(255,255,255,0.82)',
|
|
53
|
+
shadowColor: '#083344',
|
|
54
|
+
},
|
|
55
|
+
pink: {
|
|
56
|
+
label: 'Pink',
|
|
57
|
+
backgroundColor: '#fb7185',
|
|
58
|
+
foregroundColor: '#fff1f2',
|
|
59
|
+
borderColor: 'rgba(255,255,255,0.78)',
|
|
60
|
+
shadowColor: '#4c0519',
|
|
61
|
+
},
|
|
62
|
+
yellow: {
|
|
63
|
+
label: 'Yellow',
|
|
64
|
+
backgroundColor: '#facc15',
|
|
65
|
+
foregroundColor: '#1c1917',
|
|
66
|
+
borderColor: 'rgba(255,255,255,0.88)',
|
|
67
|
+
shadowColor: '#713f12',
|
|
68
|
+
},
|
|
69
|
+
slate: {
|
|
70
|
+
label: 'Slate',
|
|
71
|
+
backgroundColor: '#475569',
|
|
72
|
+
foregroundColor: '#f8fafc',
|
|
73
|
+
borderColor: 'rgba(255,255,255,0.68)',
|
|
74
|
+
shadowColor: '#020617',
|
|
75
|
+
},
|
|
76
|
+
};
|
|
28
77
|
/** True if the user has long-pressed the icon and chosen "Hide". */
|
|
29
78
|
async function getQuickIconDisabled() {
|
|
30
79
|
if (!AsyncStorage)
|
|
@@ -55,3 +104,36 @@ async function setQuickIconDisabled(disabled) {
|
|
|
55
104
|
async function clearQuickIconDisabled() {
|
|
56
105
|
await setQuickIconDisabled(false);
|
|
57
106
|
}
|
|
107
|
+
async function getQuickIconColorPreset() {
|
|
108
|
+
if (!AsyncStorage)
|
|
109
|
+
return null;
|
|
110
|
+
try {
|
|
111
|
+
const v = await AsyncStorage.getItem(QUICK_ICON_COLOR_KEY);
|
|
112
|
+
if (!v)
|
|
113
|
+
return null;
|
|
114
|
+
if (Object.prototype.hasOwnProperty.call(exports.QUICK_ICON_COLOR_PRESETS, v)) {
|
|
115
|
+
return v;
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async function setQuickIconColorPreset(preset) {
|
|
124
|
+
if (!AsyncStorage)
|
|
125
|
+
return;
|
|
126
|
+
try {
|
|
127
|
+
if (!preset) {
|
|
128
|
+
await AsyncStorage.removeItem(QUICK_ICON_COLOR_KEY);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
await AsyncStorage.setItem(QUICK_ICON_COLOR_KEY, preset);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// best-effort
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async function clearQuickIconColorPreset() {
|
|
138
|
+
await setQuickIconColorPreset(null);
|
|
139
|
+
}
|
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.3",
|
|
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",
|
|
@@ -40,15 +40,17 @@
|
|
|
40
40
|
}
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"jest": "^29.0.0",
|
|
44
43
|
"@types/jest": "^29.0.0",
|
|
44
|
+
"@types/react": "^19.2.2",
|
|
45
|
+
"jest": "^29.0.0",
|
|
45
46
|
"ts-jest": "^29.1.0",
|
|
46
47
|
"typescript": "^5.0.0"
|
|
47
48
|
},
|
|
48
49
|
"scripts": {
|
|
49
|
-
"build": "rm -rf dist && (tsc || true) && test -f dist/index.js",
|
|
50
|
+
"build": "rm -rf dist && (tsc -p tsconfig.json || true) && test -f dist/index.js",
|
|
50
51
|
"prepublishOnly": "npm run build",
|
|
51
|
-
"test": "jest"
|
|
52
|
+
"test": "jest --runInBand",
|
|
53
|
+
"test:ci": "npm run build && npm test"
|
|
52
54
|
},
|
|
53
55
|
"jest": {
|
|
54
56
|
"preset": "ts-jest",
|