yaver-feedback-react-native 0.8.10 → 0.8.12
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/BlackBox.d.ts +1 -0
- package/dist/BlackBox.js +29 -14
- package/dist/Discovery.js +9 -3
- package/dist/FeedbackModal.js +1 -1
- package/dist/P2PClient.js +20 -42
- package/dist/YaverFeedback.d.ts +7 -0
- package/dist/YaverFeedback.js +63 -4
- package/dist/upload.d.ts +1 -1
- package/dist/upload.js +8 -4
- package/package.json +1 -1
- package/src/BlackBox.ts +29 -14
- package/src/Discovery.ts +9 -3
- package/src/FeedbackModal.tsx +1 -0
- package/src/P2PClient.ts +20 -42
- package/src/YaverFeedback.ts +64 -4
- package/src/upload.ts +8 -3
package/dist/BlackBox.d.ts
CHANGED
|
@@ -53,6 +53,7 @@ export type CommandHandler = (cmd: BlackBoxCommand) => void;
|
|
|
53
53
|
export declare class BlackBox {
|
|
54
54
|
private static baseUrl;
|
|
55
55
|
private static authToken;
|
|
56
|
+
private static relayPassword;
|
|
56
57
|
private static deviceId;
|
|
57
58
|
private static appName;
|
|
58
59
|
private static buffer;
|
package/dist/BlackBox.js
CHANGED
|
@@ -18,6 +18,7 @@ class BlackBox {
|
|
|
18
18
|
}
|
|
19
19
|
BlackBox.baseUrl = feedbackConfig.agentUrl.replace(/\/$/, '');
|
|
20
20
|
BlackBox.authToken = feedbackConfig.authToken ?? null;
|
|
21
|
+
BlackBox.relayPassword = feedbackConfig.relayPassword ?? '';
|
|
21
22
|
BlackBox.deviceId = config?.deviceId ?? BlackBox.generateDeviceId();
|
|
22
23
|
BlackBox.appName = config?.appName ?? '';
|
|
23
24
|
BlackBox.flushInterval = config?.flushInterval ?? 2000;
|
|
@@ -252,15 +253,19 @@ class BlackBox {
|
|
|
252
253
|
BlackBox.sseAbortController = controller;
|
|
253
254
|
const url = `${BlackBox.baseUrl}/blackbox/command-stream?device=${encodeURIComponent(BlackBox.deviceId)}`;
|
|
254
255
|
try {
|
|
256
|
+
const sseHeaders = {
|
|
257
|
+
Authorization: `Bearer ${BlackBox.authToken}`,
|
|
258
|
+
'X-Device-ID': BlackBox.deviceId,
|
|
259
|
+
'X-Platform': react_native_1.Platform.OS,
|
|
260
|
+
'X-App-Name': BlackBox.appName,
|
|
261
|
+
Accept: 'text/event-stream',
|
|
262
|
+
};
|
|
263
|
+
if (BlackBox.relayPassword) {
|
|
264
|
+
sseHeaders['X-Relay-Password'] = BlackBox.relayPassword;
|
|
265
|
+
}
|
|
255
266
|
const response = await fetch(url, {
|
|
256
267
|
method: 'GET',
|
|
257
|
-
headers:
|
|
258
|
-
Authorization: `Bearer ${BlackBox.authToken}`,
|
|
259
|
-
'X-Device-ID': BlackBox.deviceId,
|
|
260
|
-
'X-Platform': react_native_1.Platform.OS,
|
|
261
|
-
'X-App-Name': BlackBox.appName,
|
|
262
|
-
Accept: 'text/event-stream',
|
|
263
|
-
},
|
|
268
|
+
headers: sseHeaders,
|
|
264
269
|
// @ts-ignore — React Native supports signal on fetch
|
|
265
270
|
signal: controller.signal,
|
|
266
271
|
});
|
|
@@ -354,15 +359,19 @@ class BlackBox {
|
|
|
354
359
|
const events = BlackBox.buffer;
|
|
355
360
|
BlackBox.buffer = [];
|
|
356
361
|
try {
|
|
362
|
+
const flushHeaders = {
|
|
363
|
+
Authorization: `Bearer ${BlackBox.authToken}`,
|
|
364
|
+
'Content-Type': 'application/json',
|
|
365
|
+
'X-Device-ID': BlackBox.deviceId,
|
|
366
|
+
'X-Platform': react_native_1.Platform.OS,
|
|
367
|
+
'X-App-Name': BlackBox.appName,
|
|
368
|
+
};
|
|
369
|
+
if (BlackBox.relayPassword) {
|
|
370
|
+
flushHeaders['X-Relay-Password'] = BlackBox.relayPassword;
|
|
371
|
+
}
|
|
357
372
|
await fetch(`${BlackBox.baseUrl}/blackbox/events`, {
|
|
358
373
|
method: 'POST',
|
|
359
|
-
headers:
|
|
360
|
-
Authorization: `Bearer ${BlackBox.authToken}`,
|
|
361
|
-
'Content-Type': 'application/json',
|
|
362
|
-
'X-Device-ID': BlackBox.deviceId,
|
|
363
|
-
'X-Platform': react_native_1.Platform.OS,
|
|
364
|
-
'X-App-Name': BlackBox.appName,
|
|
365
|
-
},
|
|
374
|
+
headers: flushHeaders,
|
|
366
375
|
body: JSON.stringify(events),
|
|
367
376
|
});
|
|
368
377
|
}
|
|
@@ -380,6 +389,12 @@ class BlackBox {
|
|
|
380
389
|
exports.BlackBox = BlackBox;
|
|
381
390
|
BlackBox.baseUrl = null;
|
|
382
391
|
BlackBox.authToken = null;
|
|
392
|
+
// Mirror of the user's relay password — required for EVERY request that
|
|
393
|
+
// crosses the relay (`https://public.yaver.io/d/<id>/...`). Without it
|
|
394
|
+
// the relay rejects with HTTP 401 "invalid relay password" and the
|
|
395
|
+
// event stream silently fails. Set on start() from YaverFeedback's
|
|
396
|
+
// resolved relay password (same source the P2PClient uses).
|
|
397
|
+
BlackBox.relayPassword = '';
|
|
383
398
|
BlackBox.deviceId = '';
|
|
384
399
|
BlackBox.appName = '';
|
|
385
400
|
BlackBox.buffer = [];
|
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/FeedbackModal.js
CHANGED
|
@@ -376,7 +376,7 @@ const FeedbackModal = () => {
|
|
|
376
376
|
return;
|
|
377
377
|
}
|
|
378
378
|
try {
|
|
379
|
-
const uploaded = await (0, upload_1.uploadFeedback)(config.agentUrl, config.authToken ?? '', bundle);
|
|
379
|
+
const uploaded = await (0, upload_1.uploadFeedback)(config.agentUrl, config.authToken ?? '', bundle, YaverFeedback_1.YaverFeedback.getRelayPassword());
|
|
380
380
|
// The agent returns the new report id as `id` (see
|
|
381
381
|
// feedback_http.go::ReceiveFeedback). Trigger the fix loop if we got
|
|
382
382
|
// one back; otherwise just ack the upload.
|
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.d.ts
CHANGED
|
@@ -116,6 +116,13 @@ export declare class YaverFeedback {
|
|
|
116
116
|
static isEnabled(): boolean;
|
|
117
117
|
/** Returns the current config, or null if not initialized. */
|
|
118
118
|
static getConfig(): FeedbackConfig | null;
|
|
119
|
+
/** Returns the resolved relay password the SDK is currently using.
|
|
120
|
+
* Empty string when no relay routing is in play (direct LAN agent
|
|
121
|
+
* URLs need no password). Callers attaching it to relay-routed
|
|
122
|
+
* HTTP requests must check for empty before setting the header,
|
|
123
|
+
* since "X-Relay-Password: " is treated as invalid by the relay
|
|
124
|
+
* and would 401 the request. */
|
|
125
|
+
static getRelayPassword(): string;
|
|
119
126
|
/**
|
|
120
127
|
* Manually attach an error with optional metadata.
|
|
121
128
|
* Use this in catch blocks to give the agent extra context.
|
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
|
}
|
|
@@ -732,6 +781,15 @@ class YaverFeedback {
|
|
|
732
781
|
static getConfig() {
|
|
733
782
|
return config;
|
|
734
783
|
}
|
|
784
|
+
/** Returns the resolved relay password the SDK is currently using.
|
|
785
|
+
* Empty string when no relay routing is in play (direct LAN agent
|
|
786
|
+
* URLs need no password). Callers attaching it to relay-routed
|
|
787
|
+
* HTTP requests must check for empty before setting the header,
|
|
788
|
+
* since "X-Relay-Password: " is treated as invalid by the relay
|
|
789
|
+
* and would 401 the request. */
|
|
790
|
+
static getRelayPassword() {
|
|
791
|
+
return p2pRelayPassword;
|
|
792
|
+
}
|
|
735
793
|
/**
|
|
736
794
|
* Manually attach an error with optional metadata.
|
|
737
795
|
* Use this in catch blocks to give the agent extra context.
|
|
@@ -900,7 +958,8 @@ class YaverFeedback {
|
|
|
900
958
|
});
|
|
901
959
|
if (result) {
|
|
902
960
|
config.agentUrl = result.url;
|
|
903
|
-
|
|
961
|
+
const rp = await resolveRelayPassword(config.authToken ?? '');
|
|
962
|
+
p2pClient = new P2PClient_1.P2PClient(result.url, config.authToken ?? '', rp);
|
|
904
963
|
}
|
|
905
964
|
}
|
|
906
965
|
catch { }
|
|
@@ -938,7 +997,7 @@ class YaverFeedback {
|
|
|
938
997
|
screenshots: screenshotPath ? [screenshotPath] : [],
|
|
939
998
|
errors: errorBuffer.length > 0 ? [...errorBuffer] : undefined,
|
|
940
999
|
};
|
|
941
|
-
await uploadFeedback(config.agentUrl, config.authToken ?? '', bundle);
|
|
1000
|
+
await uploadFeedback(config.agentUrl, config.authToken ?? '', bundle, p2pRelayPassword);
|
|
942
1001
|
console.log('[YaverFeedback] Auto-report sent');
|
|
943
1002
|
}
|
|
944
1003
|
catch (err) {
|
package/dist/upload.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { FeedbackBundle } from './types';
|
|
|
12
12
|
* Callers can inspect `.id` / `.reportId` to drive a follow-up
|
|
13
13
|
* `/feedback/{id}/fix` kick.
|
|
14
14
|
*/
|
|
15
|
-
export declare function uploadFeedback(agentUrl: string, authToken: string, bundle: FeedbackBundle): Promise<{
|
|
15
|
+
export declare function uploadFeedback(agentUrl: string, authToken: string, bundle: FeedbackBundle, relayPassword?: string): Promise<{
|
|
16
16
|
id?: string;
|
|
17
17
|
reportId?: string;
|
|
18
18
|
[k: string]: unknown;
|
package/dist/upload.js
CHANGED
|
@@ -15,7 +15,7 @@ const react_native_1 = require("react-native");
|
|
|
15
15
|
* Callers can inspect `.id` / `.reportId` to drive a follow-up
|
|
16
16
|
* `/feedback/{id}/fix` kick.
|
|
17
17
|
*/
|
|
18
|
-
async function uploadFeedback(agentUrl, authToken, bundle) {
|
|
18
|
+
async function uploadFeedback(agentUrl, authToken, bundle, relayPassword = '') {
|
|
19
19
|
const formData = new FormData();
|
|
20
20
|
// Attach metadata as JSON
|
|
21
21
|
formData.append('metadata', JSON.stringify(bundle.metadata));
|
|
@@ -44,11 +44,15 @@ async function uploadFeedback(agentUrl, authToken, bundle) {
|
|
|
44
44
|
});
|
|
45
45
|
}
|
|
46
46
|
const url = agentUrl.replace(/\/$/, '') + '/feedback';
|
|
47
|
+
const headers = {
|
|
48
|
+
Authorization: `Bearer ${authToken}`,
|
|
49
|
+
};
|
|
50
|
+
if (relayPassword) {
|
|
51
|
+
headers['X-Relay-Password'] = relayPassword;
|
|
52
|
+
}
|
|
47
53
|
const response = await fetch(url, {
|
|
48
54
|
method: 'POST',
|
|
49
|
-
headers
|
|
50
|
-
Authorization: `Bearer ${authToken}`,
|
|
51
|
-
},
|
|
55
|
+
headers,
|
|
52
56
|
body: formData,
|
|
53
57
|
});
|
|
54
58
|
if (!response.ok) {
|
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.12",
|
|
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/BlackBox.ts
CHANGED
|
@@ -69,6 +69,12 @@ export type CommandHandler = (cmd: BlackBoxCommand) => void;
|
|
|
69
69
|
export class BlackBox {
|
|
70
70
|
private static baseUrl: string | null = null;
|
|
71
71
|
private static authToken: string | null = null;
|
|
72
|
+
// Mirror of the user's relay password — required for EVERY request that
|
|
73
|
+
// crosses the relay (`https://public.yaver.io/d/<id>/...`). Without it
|
|
74
|
+
// the relay rejects with HTTP 401 "invalid relay password" and the
|
|
75
|
+
// event stream silently fails. Set on start() from YaverFeedback's
|
|
76
|
+
// resolved relay password (same source the P2PClient uses).
|
|
77
|
+
private static relayPassword: string = '';
|
|
72
78
|
private static deviceId: string = '';
|
|
73
79
|
private static appName: string = '';
|
|
74
80
|
private static buffer: BlackBoxEvent[] = [];
|
|
@@ -103,6 +109,7 @@ export class BlackBox {
|
|
|
103
109
|
|
|
104
110
|
BlackBox.baseUrl = feedbackConfig.agentUrl.replace(/\/$/, '');
|
|
105
111
|
BlackBox.authToken = feedbackConfig.authToken ?? null;
|
|
112
|
+
BlackBox.relayPassword = (feedbackConfig as { relayPassword?: string }).relayPassword ?? '';
|
|
106
113
|
BlackBox.deviceId = config?.deviceId ?? BlackBox.generateDeviceId();
|
|
107
114
|
BlackBox.appName = config?.appName ?? '';
|
|
108
115
|
BlackBox.flushInterval = config?.flushInterval ?? 2000;
|
|
@@ -375,15 +382,19 @@ export class BlackBox {
|
|
|
375
382
|
const url = `${BlackBox.baseUrl}/blackbox/command-stream?device=${encodeURIComponent(BlackBox.deviceId)}`;
|
|
376
383
|
|
|
377
384
|
try {
|
|
385
|
+
const sseHeaders: Record<string, string> = {
|
|
386
|
+
Authorization: `Bearer ${BlackBox.authToken}`,
|
|
387
|
+
'X-Device-ID': BlackBox.deviceId,
|
|
388
|
+
'X-Platform': Platform.OS,
|
|
389
|
+
'X-App-Name': BlackBox.appName,
|
|
390
|
+
Accept: 'text/event-stream',
|
|
391
|
+
};
|
|
392
|
+
if (BlackBox.relayPassword) {
|
|
393
|
+
sseHeaders['X-Relay-Password'] = BlackBox.relayPassword;
|
|
394
|
+
}
|
|
378
395
|
const response = await fetch(url, {
|
|
379
396
|
method: 'GET',
|
|
380
|
-
headers:
|
|
381
|
-
Authorization: `Bearer ${BlackBox.authToken}`,
|
|
382
|
-
'X-Device-ID': BlackBox.deviceId,
|
|
383
|
-
'X-Platform': Platform.OS,
|
|
384
|
-
'X-App-Name': BlackBox.appName,
|
|
385
|
-
Accept: 'text/event-stream',
|
|
386
|
-
},
|
|
397
|
+
headers: sseHeaders,
|
|
387
398
|
// @ts-ignore — React Native supports signal on fetch
|
|
388
399
|
signal: controller.signal,
|
|
389
400
|
});
|
|
@@ -479,15 +490,19 @@ export class BlackBox {
|
|
|
479
490
|
BlackBox.buffer = [];
|
|
480
491
|
|
|
481
492
|
try {
|
|
493
|
+
const flushHeaders: Record<string, string> = {
|
|
494
|
+
Authorization: `Bearer ${BlackBox.authToken}`,
|
|
495
|
+
'Content-Type': 'application/json',
|
|
496
|
+
'X-Device-ID': BlackBox.deviceId,
|
|
497
|
+
'X-Platform': Platform.OS,
|
|
498
|
+
'X-App-Name': BlackBox.appName,
|
|
499
|
+
};
|
|
500
|
+
if (BlackBox.relayPassword) {
|
|
501
|
+
flushHeaders['X-Relay-Password'] = BlackBox.relayPassword;
|
|
502
|
+
}
|
|
482
503
|
await fetch(`${BlackBox.baseUrl}/blackbox/events`, {
|
|
483
504
|
method: 'POST',
|
|
484
|
-
headers:
|
|
485
|
-
Authorization: `Bearer ${BlackBox.authToken}`,
|
|
486
|
-
'Content-Type': 'application/json',
|
|
487
|
-
'X-Device-ID': BlackBox.deviceId,
|
|
488
|
-
'X-Platform': Platform.OS,
|
|
489
|
-
'X-App-Name': BlackBox.appName,
|
|
490
|
-
},
|
|
505
|
+
headers: flushHeaders,
|
|
491
506
|
body: JSON.stringify(events),
|
|
492
507
|
});
|
|
493
508
|
} catch {
|
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/FeedbackModal.tsx
CHANGED
|
@@ -415,6 +415,7 @@ export const FeedbackModal: React.FC = () => {
|
|
|
415
415
|
config.agentUrl,
|
|
416
416
|
config.authToken ?? '',
|
|
417
417
|
bundle,
|
|
418
|
+
YaverFeedback.getRelayPassword(),
|
|
418
419
|
);
|
|
419
420
|
// The agent returns the new report id as `id` (see
|
|
420
421
|
// feedback_http.go::ReceiveFeedback). Trigger the fix loop if we got
|
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
|
}
|
|
@@ -766,6 +815,16 @@ export class YaverFeedback {
|
|
|
766
815
|
return config;
|
|
767
816
|
}
|
|
768
817
|
|
|
818
|
+
/** Returns the resolved relay password the SDK is currently using.
|
|
819
|
+
* Empty string when no relay routing is in play (direct LAN agent
|
|
820
|
+
* URLs need no password). Callers attaching it to relay-routed
|
|
821
|
+
* HTTP requests must check for empty before setting the header,
|
|
822
|
+
* since "X-Relay-Password: " is treated as invalid by the relay
|
|
823
|
+
* and would 401 the request. */
|
|
824
|
+
static getRelayPassword(): string {
|
|
825
|
+
return p2pRelayPassword;
|
|
826
|
+
}
|
|
827
|
+
|
|
769
828
|
/**
|
|
770
829
|
* Manually attach an error with optional metadata.
|
|
771
830
|
* Use this in catch blocks to give the agent extra context.
|
|
@@ -949,7 +1008,8 @@ export class YaverFeedback {
|
|
|
949
1008
|
});
|
|
950
1009
|
if (result) {
|
|
951
1010
|
config.agentUrl = result.url;
|
|
952
|
-
|
|
1011
|
+
const rp = await resolveRelayPassword(config.authToken ?? '');
|
|
1012
|
+
p2pClient = new P2PClient(result.url, config.authToken ?? '', rp);
|
|
953
1013
|
}
|
|
954
1014
|
} catch {}
|
|
955
1015
|
}
|
|
@@ -990,7 +1050,7 @@ export class YaverFeedback {
|
|
|
990
1050
|
errors: errorBuffer.length > 0 ? [...errorBuffer] : undefined,
|
|
991
1051
|
};
|
|
992
1052
|
|
|
993
|
-
await uploadFeedback(config.agentUrl, config.authToken ?? '', bundle);
|
|
1053
|
+
await uploadFeedback(config.agentUrl, config.authToken ?? '', bundle, p2pRelayPassword);
|
|
994
1054
|
console.log('[YaverFeedback] Auto-report sent');
|
|
995
1055
|
} catch (err) {
|
|
996
1056
|
console.warn('[YaverFeedback] Auto-report failed:', err);
|
package/src/upload.ts
CHANGED
|
@@ -18,6 +18,7 @@ export async function uploadFeedback(
|
|
|
18
18
|
agentUrl: string,
|
|
19
19
|
authToken: string,
|
|
20
20
|
bundle: FeedbackBundle,
|
|
21
|
+
relayPassword: string = '',
|
|
21
22
|
): Promise<{ id?: string; reportId?: string; [k: string]: unknown }> {
|
|
22
23
|
const formData = new FormData();
|
|
23
24
|
|
|
@@ -54,11 +55,15 @@ export async function uploadFeedback(
|
|
|
54
55
|
|
|
55
56
|
const url = agentUrl.replace(/\/$/, '') + '/feedback';
|
|
56
57
|
|
|
58
|
+
const headers: Record<string, string> = {
|
|
59
|
+
Authorization: `Bearer ${authToken}`,
|
|
60
|
+
};
|
|
61
|
+
if (relayPassword) {
|
|
62
|
+
headers['X-Relay-Password'] = relayPassword;
|
|
63
|
+
}
|
|
57
64
|
const response = await fetch(url, {
|
|
58
65
|
method: 'POST',
|
|
59
|
-
headers
|
|
60
|
-
Authorization: `Bearer ${authToken}`,
|
|
61
|
-
},
|
|
66
|
+
headers,
|
|
62
67
|
body: formData,
|
|
63
68
|
});
|
|
64
69
|
|