yaver-feedback-react-native 0.8.10 → 0.8.11
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/Discovery.js +9 -3
- package/dist/P2PClient.js +20 -42
- package/dist/YaverFeedback.js +53 -3
- package/package.json +1 -1
- package/src/Discovery.ts +9 -3
- package/src/P2PClient.ts +20 -42
- package/src/YaverFeedback.ts +53 -3
package/dist/Discovery.js
CHANGED
|
@@ -257,15 +257,21 @@ class YaverDiscovery {
|
|
|
257
257
|
*/
|
|
258
258
|
static async discoverViaRelay(convexUrl, authToken, deviceId) {
|
|
259
259
|
try {
|
|
260
|
-
|
|
260
|
+
// /settings returns {ok, settings: {relayUrl, relayPassword, ...}}
|
|
261
|
+
// — `/auth/validate` only returns the user record. Using the
|
|
262
|
+
// wrong endpoint historically meant relayPassword stayed
|
|
263
|
+
// undefined and every relay-routed probe rejected with 401
|
|
264
|
+
// "invalid relay password" (relay/server.go:957).
|
|
265
|
+
const settingsRes = await fetch(`${convexUrl}/settings`, {
|
|
261
266
|
headers: { Authorization: `Bearer ${authToken}` },
|
|
262
267
|
});
|
|
263
268
|
let relayUrl;
|
|
264
269
|
let relayPassword;
|
|
265
270
|
if (settingsRes.ok) {
|
|
266
271
|
const settingsData = await settingsRes.json();
|
|
267
|
-
|
|
268
|
-
|
|
272
|
+
const inner = settingsData?.settings;
|
|
273
|
+
relayUrl = inner?.relayUrl ?? settingsData?.relayUrl;
|
|
274
|
+
relayPassword = inner?.relayPassword ?? settingsData?.relayPassword;
|
|
269
275
|
}
|
|
270
276
|
if (!relayUrl) {
|
|
271
277
|
const configRes = await fetch(`${convexUrl}/platform-config?key=relay_servers`);
|
package/dist/P2PClient.js
CHANGED
|
@@ -291,11 +291,12 @@ class P2PClient {
|
|
|
291
291
|
name: 'voice_note.m4a',
|
|
292
292
|
});
|
|
293
293
|
}
|
|
294
|
+
// Use authHeaders() so a relay-routed baseUrl carries
|
|
295
|
+
// X-Relay-Password — without it the relay rejects with 401
|
|
296
|
+
// "invalid relay password" before the agent ever sees the form.
|
|
294
297
|
const response = await fetch(`${this.baseUrl}/feedback`, {
|
|
295
298
|
method: 'POST',
|
|
296
|
-
headers:
|
|
297
|
-
Authorization: `Bearer ${this.authToken}`,
|
|
298
|
-
},
|
|
299
|
+
headers: this.authHeaders(),
|
|
299
300
|
body: formData,
|
|
300
301
|
});
|
|
301
302
|
if (!response.ok) {
|
|
@@ -313,10 +314,7 @@ class P2PClient {
|
|
|
313
314
|
for await (const event of events) {
|
|
314
315
|
const response = await fetch(`${this.baseUrl}/feedback/stream`, {
|
|
315
316
|
method: 'POST',
|
|
316
|
-
headers: {
|
|
317
|
-
Authorization: `Bearer ${this.authToken}`,
|
|
318
|
-
'Content-Type': 'application/json',
|
|
319
|
-
},
|
|
317
|
+
headers: this.authHeaders({ 'Content-Type': 'application/json' }),
|
|
320
318
|
body: JSON.stringify(event),
|
|
321
319
|
});
|
|
322
320
|
if (!response.ok) {
|
|
@@ -335,10 +333,7 @@ class P2PClient {
|
|
|
335
333
|
async startBuild(platform) {
|
|
336
334
|
const response = await fetch(`${this.baseUrl}/builds`, {
|
|
337
335
|
method: 'POST',
|
|
338
|
-
headers: {
|
|
339
|
-
Authorization: `Bearer ${this.authToken}`,
|
|
340
|
-
'Content-Type': 'application/json',
|
|
341
|
-
},
|
|
336
|
+
headers: this.authHeaders({ "Content-Type": "application/json" }),
|
|
342
337
|
body: JSON.stringify({ platform }),
|
|
343
338
|
});
|
|
344
339
|
if (!response.ok) {
|
|
@@ -378,9 +373,7 @@ class P2PClient {
|
|
|
378
373
|
});
|
|
379
374
|
const response = await fetch(`${this.baseUrl}/voice/transcribe`, {
|
|
380
375
|
method: 'POST',
|
|
381
|
-
headers:
|
|
382
|
-
Authorization: `Bearer ${this.authToken}`,
|
|
383
|
-
},
|
|
376
|
+
headers: this.authHeaders(),
|
|
384
377
|
body: formData,
|
|
385
378
|
});
|
|
386
379
|
if (!response.ok) {
|
|
@@ -420,7 +413,7 @@ class P2PClient {
|
|
|
420
413
|
if (mode === 'dev') {
|
|
421
414
|
const primary = await fetch(`${this.baseUrl}/dev/reload`, {
|
|
422
415
|
method: 'POST',
|
|
423
|
-
headers:
|
|
416
|
+
headers: this.authHeaders(),
|
|
424
417
|
});
|
|
425
418
|
if (primary.ok) {
|
|
426
419
|
const payload = await primary.json().catch(() => ({}));
|
|
@@ -451,10 +444,7 @@ class P2PClient {
|
|
|
451
444
|
const identity = resolveAppIdentity(opts);
|
|
452
445
|
const res = await fetch(`${this.baseUrl}/dev/reload-app`, {
|
|
453
446
|
method: 'POST',
|
|
454
|
-
headers: {
|
|
455
|
-
Authorization: `Bearer ${this.authToken}`,
|
|
456
|
-
'Content-Type': 'application/json',
|
|
457
|
-
},
|
|
447
|
+
headers: this.authHeaders({ "Content-Type": "application/json" }),
|
|
458
448
|
body: JSON.stringify({
|
|
459
449
|
mode: 'bundle',
|
|
460
450
|
...identity,
|
|
@@ -498,10 +488,7 @@ class P2PClient {
|
|
|
498
488
|
const identity = resolveAppIdentity(opts);
|
|
499
489
|
const response = await fetch(`${this.baseUrl}/vibing/execute`, {
|
|
500
490
|
method: 'POST',
|
|
501
|
-
headers: {
|
|
502
|
-
Authorization: `Bearer ${this.authToken}`,
|
|
503
|
-
'Content-Type': 'application/json',
|
|
504
|
-
},
|
|
491
|
+
headers: this.authHeaders({ "Content-Type": "application/json" }),
|
|
505
492
|
body: JSON.stringify({
|
|
506
493
|
prompt,
|
|
507
494
|
projectPath: identity.projectPath ?? opts?.projectPath ?? '',
|
|
@@ -529,7 +516,7 @@ class P2PClient {
|
|
|
529
516
|
}
|
|
530
517
|
const response = await fetch(`${this.baseUrl}/vibing/eligibility?${params.toString()}`, {
|
|
531
518
|
method: 'GET',
|
|
532
|
-
headers:
|
|
519
|
+
headers: this.authHeaders(),
|
|
533
520
|
});
|
|
534
521
|
if (!response.ok) {
|
|
535
522
|
const text = await response.text().catch(() => '');
|
|
@@ -546,7 +533,7 @@ class P2PClient {
|
|
|
546
533
|
async triggerFix(feedbackId) {
|
|
547
534
|
const response = await fetch(`${this.baseUrl}/feedback/${encodeURIComponent(feedbackId)}/fix`, {
|
|
548
535
|
method: 'POST',
|
|
549
|
-
headers:
|
|
536
|
+
headers: this.authHeaders(),
|
|
550
537
|
});
|
|
551
538
|
if (!response.ok) {
|
|
552
539
|
const text = await response.text().catch(() => '');
|
|
@@ -567,10 +554,7 @@ class P2PClient {
|
|
|
567
554
|
async startTestSession() {
|
|
568
555
|
const response = await fetch(`${this.baseUrl}/test-app/start`, {
|
|
569
556
|
method: 'POST',
|
|
570
|
-
headers: {
|
|
571
|
-
Authorization: `Bearer ${this.authToken}`,
|
|
572
|
-
'Content-Type': 'application/json',
|
|
573
|
-
},
|
|
557
|
+
headers: this.authHeaders({ "Content-Type": "application/json" }),
|
|
574
558
|
body: JSON.stringify({ source: 'feedback-sdk' }),
|
|
575
559
|
});
|
|
576
560
|
if (!response.ok) {
|
|
@@ -583,7 +567,7 @@ class P2PClient {
|
|
|
583
567
|
async stopTestSession() {
|
|
584
568
|
await fetch(`${this.baseUrl}/test-app/stop`, {
|
|
585
569
|
method: 'POST',
|
|
586
|
-
headers:
|
|
570
|
+
headers: this.authHeaders(),
|
|
587
571
|
});
|
|
588
572
|
}
|
|
589
573
|
/** Get the current test session status and list of fixes. */
|
|
@@ -599,10 +583,7 @@ class P2PClient {
|
|
|
599
583
|
async rotateToken() {
|
|
600
584
|
const response = await fetch(`${this.baseUrl}/sdk/token/rotate`, {
|
|
601
585
|
method: 'POST',
|
|
602
|
-
headers: {
|
|
603
|
-
Authorization: `Bearer ${this.authToken}`,
|
|
604
|
-
'Content-Type': 'application/json',
|
|
605
|
-
},
|
|
586
|
+
headers: this.authHeaders({ "Content-Type": "application/json" }),
|
|
606
587
|
});
|
|
607
588
|
if (!response.ok) {
|
|
608
589
|
const text = await response.text().catch(() => '');
|
|
@@ -621,7 +602,7 @@ class P2PClient {
|
|
|
621
602
|
* for 30s in getFlagsCached().
|
|
622
603
|
*/
|
|
623
604
|
async flagsEvaluate(userId = 'anonymous') {
|
|
624
|
-
const res = await fetch(`${this.baseUrl}/flags/eval?userId=${encodeURIComponent(userId)}`, { headers:
|
|
605
|
+
const res = await fetch(`${this.baseUrl}/flags/eval?userId=${encodeURIComponent(userId)}`, { headers: this.authHeaders() });
|
|
625
606
|
if (!res.ok)
|
|
626
607
|
return {};
|
|
627
608
|
const data = await res.json();
|
|
@@ -629,7 +610,7 @@ class P2PClient {
|
|
|
629
610
|
}
|
|
630
611
|
/** Evaluate a single flag by key — shortcut when you only need one. */
|
|
631
612
|
async flagsEvaluateOne(key, userId = 'anonymous') {
|
|
632
|
-
const res = await fetch(`${this.baseUrl}/flags/eval?userId=${encodeURIComponent(userId)}&flag=${encodeURIComponent(key)}`, { headers:
|
|
613
|
+
const res = await fetch(`${this.baseUrl}/flags/eval?userId=${encodeURIComponent(userId)}&flag=${encodeURIComponent(key)}`, { headers: this.authHeaders() });
|
|
633
614
|
if (!res.ok)
|
|
634
615
|
return undefined;
|
|
635
616
|
const data = await res.json();
|
|
@@ -647,7 +628,7 @@ class P2PClient {
|
|
|
647
628
|
if (deviceId)
|
|
648
629
|
params.set('device', deviceId);
|
|
649
630
|
const res = await fetch(`${this.baseUrl}/releases/latest?${params.toString()}`, {
|
|
650
|
-
headers:
|
|
631
|
+
headers: this.authHeaders(),
|
|
651
632
|
});
|
|
652
633
|
if (!res.ok)
|
|
653
634
|
return null;
|
|
@@ -657,7 +638,7 @@ class P2PClient {
|
|
|
657
638
|
async releasesDownload(channel, semver) {
|
|
658
639
|
const params = new URLSearchParams({ channel, semver });
|
|
659
640
|
const res = await fetch(`${this.baseUrl}/releases/bundle?${params.toString()}`, {
|
|
660
|
-
headers:
|
|
641
|
+
headers: this.authHeaders(),
|
|
661
642
|
});
|
|
662
643
|
if (!res.ok)
|
|
663
644
|
return null;
|
|
@@ -673,10 +654,7 @@ class P2PClient {
|
|
|
673
654
|
try {
|
|
674
655
|
const res = await fetch(`${this.baseUrl}/analytics/ingest`, {
|
|
675
656
|
method: 'POST',
|
|
676
|
-
headers: {
|
|
677
|
-
Authorization: `Bearer ${this.authToken}`,
|
|
678
|
-
'Content-Type': 'application/json',
|
|
679
|
-
},
|
|
657
|
+
headers: this.authHeaders({ "Content-Type": "application/json" }),
|
|
680
658
|
body: JSON.stringify({
|
|
681
659
|
name,
|
|
682
660
|
props,
|
package/dist/YaverFeedback.js
CHANGED
|
@@ -135,7 +135,50 @@ let enabled = false;
|
|
|
135
135
|
let p2pClient = null;
|
|
136
136
|
let shakeDetector = null;
|
|
137
137
|
let p2pAuthToken = null;
|
|
138
|
+
let p2pRelayPassword = '';
|
|
138
139
|
let reportLaunchInFlight = false;
|
|
140
|
+
/** Resolve the user's relay password by validating their auth token
|
|
141
|
+
* against Convex. Used whenever we (re)build the P2PClient so a
|
|
142
|
+
* relay-routed agentUrl carries a valid X-Relay-Password — without
|
|
143
|
+
* this, every relay-tunneled request rejects with HTTP 401
|
|
144
|
+
* "invalid relay password" (relay/server.go:957).
|
|
145
|
+
*
|
|
146
|
+
* Cached on `p2pRelayPassword` so we only round-trip Convex when the
|
|
147
|
+
* user's auth token actually changes. Returns "" on any failure so
|
|
148
|
+
* direct LAN agentUrls (which need no password) keep working.
|
|
149
|
+
*/
|
|
150
|
+
async function resolveRelayPassword(authToken, convexUrl) {
|
|
151
|
+
const trimmed = (authToken || '').trim();
|
|
152
|
+
if (!trimmed) {
|
|
153
|
+
p2pRelayPassword = '';
|
|
154
|
+
return '';
|
|
155
|
+
}
|
|
156
|
+
const url = (convexUrl || config?.convexUrl || auth_1.DEFAULT_CONVEX_SITE_URL).replace(/\/+$/, '');
|
|
157
|
+
try {
|
|
158
|
+
// /settings returns {ok, settings: {relayPassword, relayUrl, ...}}
|
|
159
|
+
// Older accounts may flatten relayPassword to the top — match the
|
|
160
|
+
// tolerance the web shell already uses (route.ts:77).
|
|
161
|
+
const res = await fetch(`${url}/settings`, {
|
|
162
|
+
headers: { Authorization: `Bearer ${trimmed}` },
|
|
163
|
+
});
|
|
164
|
+
if (!res.ok)
|
|
165
|
+
return p2pRelayPassword;
|
|
166
|
+
const data = await res.json().catch(() => ({}));
|
|
167
|
+
const settings = data?.settings;
|
|
168
|
+
const pw = (typeof settings?.relayPassword === 'string' && settings.relayPassword) ||
|
|
169
|
+
(typeof data?.relayPassword === 'string'
|
|
170
|
+
? data.relayPassword
|
|
171
|
+
: '') ||
|
|
172
|
+
'';
|
|
173
|
+
p2pRelayPassword = pw;
|
|
174
|
+
return pw;
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
// Network failure on a passive Convex round-trip shouldn't break
|
|
178
|
+
// direct-LAN flows. Fall through with whatever we already cached.
|
|
179
|
+
return p2pRelayPassword;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
139
182
|
/** Ring buffer of captured errors. */
|
|
140
183
|
let errorBuffer = [];
|
|
141
184
|
let maxErrors = 5;
|
|
@@ -192,7 +235,8 @@ class YaverFeedback {
|
|
|
192
235
|
return;
|
|
193
236
|
}
|
|
194
237
|
p2pAuthToken = token;
|
|
195
|
-
|
|
238
|
+
const rp = await resolveRelayPassword(token);
|
|
239
|
+
p2pClient = new P2PClient_1.P2PClient(effectiveUrl, token, rp);
|
|
196
240
|
}
|
|
197
241
|
/**
|
|
198
242
|
* Initialize the feedback SDK with the given configuration.
|
|
@@ -271,7 +315,12 @@ class YaverFeedback {
|
|
|
271
315
|
// Create P2P client if we have a URL
|
|
272
316
|
if (config.agentUrl) {
|
|
273
317
|
p2pAuthToken = config.authToken ?? null;
|
|
274
|
-
|
|
318
|
+
// Initial construction uses the cached p2pRelayPassword (empty on
|
|
319
|
+
// first init). rebuildP2PClient below resolves the real password
|
|
320
|
+
// from Convex and replaces this client — but only when authToken
|
|
321
|
+
// is set, so set a placeholder header here that won't 401 a
|
|
322
|
+
// direct-LAN url and will be overwritten before any relay hop.
|
|
323
|
+
p2pClient = new P2PClient_1.P2PClient(config.agentUrl, config.authToken ?? '', p2pRelayPassword);
|
|
275
324
|
if (config.authToken) {
|
|
276
325
|
void YaverFeedback.rebuildP2PClient(config.agentUrl);
|
|
277
326
|
}
|
|
@@ -900,7 +949,8 @@ class YaverFeedback {
|
|
|
900
949
|
});
|
|
901
950
|
if (result) {
|
|
902
951
|
config.agentUrl = result.url;
|
|
903
|
-
|
|
952
|
+
const rp = await resolveRelayPassword(config.authToken ?? '');
|
|
953
|
+
p2pClient = new P2PClient_1.P2PClient(result.url, config.authToken ?? '', rp);
|
|
904
954
|
}
|
|
905
955
|
}
|
|
906
956
|
catch { }
|
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.11",
|
|
4
4
|
"description": "Visual feedback SDK for Yaver \u2014 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/Discovery.ts
CHANGED
|
@@ -283,7 +283,12 @@ export class YaverDiscovery {
|
|
|
283
283
|
deviceId: string,
|
|
284
284
|
): Promise<DiscoveryResult | null> {
|
|
285
285
|
try {
|
|
286
|
-
|
|
286
|
+
// /settings returns {ok, settings: {relayUrl, relayPassword, ...}}
|
|
287
|
+
// — `/auth/validate` only returns the user record. Using the
|
|
288
|
+
// wrong endpoint historically meant relayPassword stayed
|
|
289
|
+
// undefined and every relay-routed probe rejected with 401
|
|
290
|
+
// "invalid relay password" (relay/server.go:957).
|
|
291
|
+
const settingsRes = await fetch(`${convexUrl}/settings`, {
|
|
287
292
|
headers: { Authorization: `Bearer ${authToken}` },
|
|
288
293
|
});
|
|
289
294
|
let relayUrl: string | undefined;
|
|
@@ -291,8 +296,9 @@ export class YaverDiscovery {
|
|
|
291
296
|
|
|
292
297
|
if (settingsRes.ok) {
|
|
293
298
|
const settingsData = await settingsRes.json();
|
|
294
|
-
|
|
295
|
-
|
|
299
|
+
const inner = settingsData?.settings;
|
|
300
|
+
relayUrl = inner?.relayUrl ?? settingsData?.relayUrl;
|
|
301
|
+
relayPassword = inner?.relayPassword ?? settingsData?.relayPassword;
|
|
296
302
|
}
|
|
297
303
|
|
|
298
304
|
if (!relayUrl) {
|
package/src/P2PClient.ts
CHANGED
|
@@ -346,11 +346,12 @@ export class P2PClient {
|
|
|
346
346
|
} as any);
|
|
347
347
|
}
|
|
348
348
|
|
|
349
|
+
// Use authHeaders() so a relay-routed baseUrl carries
|
|
350
|
+
// X-Relay-Password — without it the relay rejects with 401
|
|
351
|
+
// "invalid relay password" before the agent ever sees the form.
|
|
349
352
|
const response = await fetch(`${this.baseUrl}/feedback`, {
|
|
350
353
|
method: 'POST',
|
|
351
|
-
headers:
|
|
352
|
-
Authorization: `Bearer ${this.authToken}`,
|
|
353
|
-
},
|
|
354
|
+
headers: this.authHeaders(),
|
|
354
355
|
body: formData,
|
|
355
356
|
});
|
|
356
357
|
|
|
@@ -371,10 +372,7 @@ export class P2PClient {
|
|
|
371
372
|
for await (const event of events) {
|
|
372
373
|
const response = await fetch(`${this.baseUrl}/feedback/stream`, {
|
|
373
374
|
method: 'POST',
|
|
374
|
-
headers: {
|
|
375
|
-
Authorization: `Bearer ${this.authToken}`,
|
|
376
|
-
'Content-Type': 'application/json',
|
|
377
|
-
},
|
|
375
|
+
headers: this.authHeaders({ 'Content-Type': 'application/json' }),
|
|
378
376
|
body: JSON.stringify(event),
|
|
379
377
|
});
|
|
380
378
|
|
|
@@ -398,10 +396,7 @@ export class P2PClient {
|
|
|
398
396
|
async startBuild(platform: string): Promise<any> {
|
|
399
397
|
const response = await fetch(`${this.baseUrl}/builds`, {
|
|
400
398
|
method: 'POST',
|
|
401
|
-
headers: {
|
|
402
|
-
Authorization: `Bearer ${this.authToken}`,
|
|
403
|
-
'Content-Type': 'application/json',
|
|
404
|
-
},
|
|
399
|
+
headers: this.authHeaders({ "Content-Type": "application/json" }),
|
|
405
400
|
body: JSON.stringify({ platform }),
|
|
406
401
|
});
|
|
407
402
|
|
|
@@ -446,9 +441,7 @@ export class P2PClient {
|
|
|
446
441
|
|
|
447
442
|
const response = await fetch(`${this.baseUrl}/voice/transcribe`, {
|
|
448
443
|
method: 'POST',
|
|
449
|
-
headers:
|
|
450
|
-
Authorization: `Bearer ${this.authToken}`,
|
|
451
|
-
},
|
|
444
|
+
headers: this.authHeaders(),
|
|
452
445
|
body: formData,
|
|
453
446
|
});
|
|
454
447
|
|
|
@@ -494,7 +487,7 @@ export class P2PClient {
|
|
|
494
487
|
if (mode === 'dev') {
|
|
495
488
|
const primary = await fetch(`${this.baseUrl}/dev/reload`, {
|
|
496
489
|
method: 'POST',
|
|
497
|
-
headers:
|
|
490
|
+
headers: this.authHeaders(),
|
|
498
491
|
});
|
|
499
492
|
if (primary.ok) {
|
|
500
493
|
const payload = await primary.json().catch(() => ({} as Record<string, unknown>));
|
|
@@ -528,10 +521,7 @@ export class P2PClient {
|
|
|
528
521
|
|
|
529
522
|
const res = await fetch(`${this.baseUrl}/dev/reload-app`, {
|
|
530
523
|
method: 'POST',
|
|
531
|
-
headers: {
|
|
532
|
-
Authorization: `Bearer ${this.authToken}`,
|
|
533
|
-
'Content-Type': 'application/json',
|
|
534
|
-
},
|
|
524
|
+
headers: this.authHeaders({ "Content-Type": "application/json" }),
|
|
535
525
|
body: JSON.stringify({
|
|
536
526
|
mode: 'bundle',
|
|
537
527
|
...identity,
|
|
@@ -581,10 +571,7 @@ export class P2PClient {
|
|
|
581
571
|
const identity = resolveAppIdentity(opts);
|
|
582
572
|
const response = await fetch(`${this.baseUrl}/vibing/execute`, {
|
|
583
573
|
method: 'POST',
|
|
584
|
-
headers: {
|
|
585
|
-
Authorization: `Bearer ${this.authToken}`,
|
|
586
|
-
'Content-Type': 'application/json',
|
|
587
|
-
},
|
|
574
|
+
headers: this.authHeaders({ "Content-Type": "application/json" }),
|
|
588
575
|
body: JSON.stringify({
|
|
589
576
|
prompt,
|
|
590
577
|
projectPath: identity.projectPath ?? opts?.projectPath ?? '',
|
|
@@ -623,7 +610,7 @@ export class P2PClient {
|
|
|
623
610
|
}
|
|
624
611
|
const response = await fetch(`${this.baseUrl}/vibing/eligibility?${params.toString()}`, {
|
|
625
612
|
method: 'GET',
|
|
626
|
-
headers:
|
|
613
|
+
headers: this.authHeaders(),
|
|
627
614
|
});
|
|
628
615
|
if (!response.ok) {
|
|
629
616
|
const text = await response.text().catch(() => '');
|
|
@@ -641,7 +628,7 @@ export class P2PClient {
|
|
|
641
628
|
async triggerFix(feedbackId: string): Promise<{ taskId: string; prompt: string }> {
|
|
642
629
|
const response = await fetch(`${this.baseUrl}/feedback/${encodeURIComponent(feedbackId)}/fix`, {
|
|
643
630
|
method: 'POST',
|
|
644
|
-
headers:
|
|
631
|
+
headers: this.authHeaders(),
|
|
645
632
|
});
|
|
646
633
|
if (!response.ok) {
|
|
647
634
|
const text = await response.text().catch(() => '');
|
|
@@ -664,10 +651,7 @@ export class P2PClient {
|
|
|
664
651
|
async startTestSession(): Promise<{ sessionId: string }> {
|
|
665
652
|
const response = await fetch(`${this.baseUrl}/test-app/start`, {
|
|
666
653
|
method: 'POST',
|
|
667
|
-
headers: {
|
|
668
|
-
Authorization: `Bearer ${this.authToken}`,
|
|
669
|
-
'Content-Type': 'application/json',
|
|
670
|
-
},
|
|
654
|
+
headers: this.authHeaders({ "Content-Type": "application/json" }),
|
|
671
655
|
body: JSON.stringify({ source: 'feedback-sdk' }),
|
|
672
656
|
});
|
|
673
657
|
|
|
@@ -683,7 +667,7 @@ export class P2PClient {
|
|
|
683
667
|
async stopTestSession(): Promise<void> {
|
|
684
668
|
await fetch(`${this.baseUrl}/test-app/stop`, {
|
|
685
669
|
method: 'POST',
|
|
686
|
-
headers:
|
|
670
|
+
headers: this.authHeaders(),
|
|
687
671
|
});
|
|
688
672
|
}
|
|
689
673
|
|
|
@@ -701,10 +685,7 @@ export class P2PClient {
|
|
|
701
685
|
async rotateToken(): Promise<{ token: string; expiresAt: number }> {
|
|
702
686
|
const response = await fetch(`${this.baseUrl}/sdk/token/rotate`, {
|
|
703
687
|
method: 'POST',
|
|
704
|
-
headers: {
|
|
705
|
-
Authorization: `Bearer ${this.authToken}`,
|
|
706
|
-
'Content-Type': 'application/json',
|
|
707
|
-
},
|
|
688
|
+
headers: this.authHeaders({ "Content-Type": "application/json" }),
|
|
708
689
|
});
|
|
709
690
|
|
|
710
691
|
if (!response.ok) {
|
|
@@ -729,7 +710,7 @@ export class P2PClient {
|
|
|
729
710
|
async flagsEvaluate(userId: string = 'anonymous'): Promise<Record<string, unknown>> {
|
|
730
711
|
const res = await fetch(
|
|
731
712
|
`${this.baseUrl}/flags/eval?userId=${encodeURIComponent(userId)}`,
|
|
732
|
-
{ headers:
|
|
713
|
+
{ headers: this.authHeaders() },
|
|
733
714
|
);
|
|
734
715
|
if (!res.ok) return {};
|
|
735
716
|
const data = await res.json();
|
|
@@ -743,7 +724,7 @@ export class P2PClient {
|
|
|
743
724
|
): Promise<T | undefined> {
|
|
744
725
|
const res = await fetch(
|
|
745
726
|
`${this.baseUrl}/flags/eval?userId=${encodeURIComponent(userId)}&flag=${encodeURIComponent(key)}`,
|
|
746
|
-
{ headers:
|
|
727
|
+
{ headers: this.authHeaders() },
|
|
747
728
|
);
|
|
748
729
|
if (!res.ok) return undefined;
|
|
749
730
|
const data = await res.json();
|
|
@@ -776,7 +757,7 @@ export class P2PClient {
|
|
|
776
757
|
const params = new URLSearchParams({ channel });
|
|
777
758
|
if (deviceId) params.set('device', deviceId);
|
|
778
759
|
const res = await fetch(`${this.baseUrl}/releases/latest?${params.toString()}`, {
|
|
779
|
-
headers:
|
|
760
|
+
headers: this.authHeaders(),
|
|
780
761
|
});
|
|
781
762
|
if (!res.ok) return null;
|
|
782
763
|
return res.json();
|
|
@@ -789,7 +770,7 @@ export class P2PClient {
|
|
|
789
770
|
): Promise<ArrayBuffer | null> {
|
|
790
771
|
const params = new URLSearchParams({ channel, semver });
|
|
791
772
|
const res = await fetch(`${this.baseUrl}/releases/bundle?${params.toString()}`, {
|
|
792
|
-
headers:
|
|
773
|
+
headers: this.authHeaders(),
|
|
793
774
|
});
|
|
794
775
|
if (!res.ok) return null;
|
|
795
776
|
return res.arrayBuffer();
|
|
@@ -810,10 +791,7 @@ export class P2PClient {
|
|
|
810
791
|
try {
|
|
811
792
|
const res = await fetch(`${this.baseUrl}/analytics/ingest`, {
|
|
812
793
|
method: 'POST',
|
|
813
|
-
headers: {
|
|
814
|
-
Authorization: `Bearer ${this.authToken}`,
|
|
815
|
-
'Content-Type': 'application/json',
|
|
816
|
-
},
|
|
794
|
+
headers: this.authHeaders({ "Content-Type": "application/json" }),
|
|
817
795
|
body: JSON.stringify({
|
|
818
796
|
name,
|
|
819
797
|
props,
|
package/src/YaverFeedback.ts
CHANGED
|
@@ -148,8 +148,51 @@ let enabled = false;
|
|
|
148
148
|
let p2pClient: P2PClient | null = null;
|
|
149
149
|
let shakeDetector: ShakeDetector | null = null;
|
|
150
150
|
let p2pAuthToken: string | null = null;
|
|
151
|
+
let p2pRelayPassword: string = '';
|
|
151
152
|
let reportLaunchInFlight = false;
|
|
152
153
|
|
|
154
|
+
/** Resolve the user's relay password by validating their auth token
|
|
155
|
+
* against Convex. Used whenever we (re)build the P2PClient so a
|
|
156
|
+
* relay-routed agentUrl carries a valid X-Relay-Password — without
|
|
157
|
+
* this, every relay-tunneled request rejects with HTTP 401
|
|
158
|
+
* "invalid relay password" (relay/server.go:957).
|
|
159
|
+
*
|
|
160
|
+
* Cached on `p2pRelayPassword` so we only round-trip Convex when the
|
|
161
|
+
* user's auth token actually changes. Returns "" on any failure so
|
|
162
|
+
* direct LAN agentUrls (which need no password) keep working.
|
|
163
|
+
*/
|
|
164
|
+
async function resolveRelayPassword(authToken: string, convexUrl?: string): Promise<string> {
|
|
165
|
+
const trimmed = (authToken || '').trim();
|
|
166
|
+
if (!trimmed) {
|
|
167
|
+
p2pRelayPassword = '';
|
|
168
|
+
return '';
|
|
169
|
+
}
|
|
170
|
+
const url = (convexUrl || config?.convexUrl || DEFAULT_CONVEX_SITE_URL).replace(/\/+$/, '');
|
|
171
|
+
try {
|
|
172
|
+
// /settings returns {ok, settings: {relayPassword, relayUrl, ...}}
|
|
173
|
+
// Older accounts may flatten relayPassword to the top — match the
|
|
174
|
+
// tolerance the web shell already uses (route.ts:77).
|
|
175
|
+
const res = await fetch(`${url}/settings`, {
|
|
176
|
+
headers: { Authorization: `Bearer ${trimmed}` },
|
|
177
|
+
});
|
|
178
|
+
if (!res.ok) return p2pRelayPassword;
|
|
179
|
+
const data = await res.json().catch(() => ({} as Record<string, unknown>));
|
|
180
|
+
const settings = (data as { settings?: { relayPassword?: string } })?.settings;
|
|
181
|
+
const pw =
|
|
182
|
+
(typeof settings?.relayPassword === 'string' && settings.relayPassword) ||
|
|
183
|
+
(typeof (data as { relayPassword?: string })?.relayPassword === 'string'
|
|
184
|
+
? (data as { relayPassword?: string }).relayPassword
|
|
185
|
+
: '') ||
|
|
186
|
+
'';
|
|
187
|
+
p2pRelayPassword = pw;
|
|
188
|
+
return pw;
|
|
189
|
+
} catch {
|
|
190
|
+
// Network failure on a passive Convex round-trip shouldn't break
|
|
191
|
+
// direct-LAN flows. Fall through with whatever we already cached.
|
|
192
|
+
return p2pRelayPassword;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
153
196
|
/** Ring buffer of captured errors. */
|
|
154
197
|
let errorBuffer: CapturedError[] = [];
|
|
155
198
|
let maxErrors = 5;
|
|
@@ -212,7 +255,8 @@ export class YaverFeedback {
|
|
|
212
255
|
return;
|
|
213
256
|
}
|
|
214
257
|
p2pAuthToken = token;
|
|
215
|
-
|
|
258
|
+
const rp = await resolveRelayPassword(token);
|
|
259
|
+
p2pClient = new P2PClient(effectiveUrl, token, rp);
|
|
216
260
|
}
|
|
217
261
|
|
|
218
262
|
/**
|
|
@@ -295,7 +339,12 @@ export class YaverFeedback {
|
|
|
295
339
|
// Create P2P client if we have a URL
|
|
296
340
|
if (config.agentUrl) {
|
|
297
341
|
p2pAuthToken = config.authToken ?? null;
|
|
298
|
-
|
|
342
|
+
// Initial construction uses the cached p2pRelayPassword (empty on
|
|
343
|
+
// first init). rebuildP2PClient below resolves the real password
|
|
344
|
+
// from Convex and replaces this client — but only when authToken
|
|
345
|
+
// is set, so set a placeholder header here that won't 401 a
|
|
346
|
+
// direct-LAN url and will be overwritten before any relay hop.
|
|
347
|
+
p2pClient = new P2PClient(config.agentUrl, config.authToken ?? '', p2pRelayPassword);
|
|
299
348
|
if (config.authToken) {
|
|
300
349
|
void YaverFeedback.rebuildP2PClient(config.agentUrl);
|
|
301
350
|
}
|
|
@@ -949,7 +998,8 @@ export class YaverFeedback {
|
|
|
949
998
|
});
|
|
950
999
|
if (result) {
|
|
951
1000
|
config.agentUrl = result.url;
|
|
952
|
-
|
|
1001
|
+
const rp = await resolveRelayPassword(config.authToken ?? '');
|
|
1002
|
+
p2pClient = new P2PClient(result.url, config.authToken ?? '', rp);
|
|
953
1003
|
}
|
|
954
1004
|
} catch {}
|
|
955
1005
|
}
|