@solarisdk/browser 0.5.0 → 0.5.1
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/index.d.ts +12 -0
- package/dist/index.js +55 -10
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -230,6 +230,18 @@ export declare class SolariError extends Error {
|
|
|
230
230
|
readonly code?: (SolariErrorCode | string) | undefined;
|
|
231
231
|
constructor(message: string, status?: number | undefined, cause?: unknown | undefined, code?: (SolariErrorCode | string) | undefined);
|
|
232
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* `text` with each of `secrets` (the session's ws / cdp endpoints) replaced by its redacted form, then any other
|
|
235
|
+
* URL-looking token cut to `scheme://host/…`: the Playwright driver prints the URL it dialled ("Call log: <ws
|
|
236
|
+
* connecting> ws://…/ws/<id>"), and that URL carries the session's credential.
|
|
237
|
+
*/
|
|
238
|
+
declare function scrubUrls(text: string, secrets?: string[]): string;
|
|
239
|
+
/** @internal Exposed for tests only. */
|
|
240
|
+
export declare const __testing: {
|
|
241
|
+
scrubUrls: typeof scrubUrls;
|
|
242
|
+
redactSessionPath: typeof redactSessionPath;
|
|
243
|
+
};
|
|
244
|
+
declare function redactSessionPath(text: string): string;
|
|
233
245
|
/**
|
|
234
246
|
* A profile editor launched by `solari.profiles.edit(id)`. Give `url` to the person who should
|
|
235
247
|
* sign in (or open it yourself). The editor starts when the person clicks "Open editor" on that page; it is only usable
|
package/dist/index.js
CHANGED
|
@@ -69,6 +69,51 @@ export class SolariError extends Error {
|
|
|
69
69
|
* `code`. Every REST call uses this so a caller can branch on `e.code` no matter
|
|
70
70
|
* which endpoint failed.
|
|
71
71
|
*/
|
|
72
|
+
/**
|
|
73
|
+
* `text` with the session id in every `/sessions/<id>` cut to its first 8 characters. The session id is
|
|
74
|
+
* the credential inside the ws/cdp URLs, so it must not appear in error messages or logs.
|
|
75
|
+
*/
|
|
76
|
+
/** Field names of a gateway response, never its values (they hold the session id and the ws / cdp URLs). */
|
|
77
|
+
function responseFields(data) {
|
|
78
|
+
return data && typeof data === "object" ? Object.keys(data).join(", ") || "none" : typeof data;
|
|
79
|
+
}
|
|
80
|
+
function redactUrl(url) {
|
|
81
|
+
try {
|
|
82
|
+
const u = new URL(url);
|
|
83
|
+
return `${u.protocol}//${u.host}/\u2026`;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return "<url>";
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* `text` with each of `secrets` (the session's ws / cdp endpoints) replaced by its redacted form, then any other
|
|
91
|
+
* URL-looking token cut to `scheme://host/…`: the Playwright driver prints the URL it dialled ("Call log: <ws
|
|
92
|
+
* connecting> ws://…/ws/<id>"), and that URL carries the session's credential.
|
|
93
|
+
*/
|
|
94
|
+
function scrubUrls(text, secrets = []) {
|
|
95
|
+
let out = text;
|
|
96
|
+
for (const s of secrets)
|
|
97
|
+
if (s)
|
|
98
|
+
out = out.split(s).join(redactUrl(s));
|
|
99
|
+
return out.replace(/[a-z][a-z0-9+.-]*:\/\/[^\s"'<>)\],]+/gi, (m) => redactUrl(m));
|
|
100
|
+
}
|
|
101
|
+
/** @internal Exposed for tests only. */
|
|
102
|
+
export const __testing = { scrubUrls, redactSessionPath };
|
|
103
|
+
/** The error to surface from launch(): a foreign error (patchright's connect failure) gets its message scrubbed and its
|
|
104
|
+
* cause dropped, since both print the endpoint; SolariErrors only get a scrub of their message. */
|
|
105
|
+
function scrubbedLaunchError(err, secrets) {
|
|
106
|
+
if (err instanceof SolariError) {
|
|
107
|
+
err.message = scrubUrls(err.message, secrets);
|
|
108
|
+
return err;
|
|
109
|
+
}
|
|
110
|
+
if (err instanceof Error)
|
|
111
|
+
return new SolariError(scrubUrls(err.message, secrets));
|
|
112
|
+
return err;
|
|
113
|
+
}
|
|
114
|
+
function redactSessionPath(text) {
|
|
115
|
+
return text.replace(/(\/sessions\/)([^/?#\s]+)/g, (_m, prefix, id) => `${prefix}${id.length > 8 ? `${id.slice(0, 8)}\u2026` : id}`);
|
|
116
|
+
}
|
|
72
117
|
async function apiError(what, res) {
|
|
73
118
|
const body = await res.text().catch(() => "");
|
|
74
119
|
let code;
|
|
@@ -78,7 +123,7 @@ async function apiError(what, res) {
|
|
|
78
123
|
code = parsed.code;
|
|
79
124
|
}
|
|
80
125
|
catch { }
|
|
81
|
-
return new SolariError(`Solari ${what} failed: ${res.status} ${body}`, res.status, undefined, code);
|
|
126
|
+
return new SolariError(`Solari ${redactSessionPath(what)} failed: ${res.status} ${body}`, res.status, undefined, code);
|
|
82
127
|
}
|
|
83
128
|
const REGION_URLS = {
|
|
84
129
|
"us-west": "https://api.getsolari.com",
|
|
@@ -437,7 +482,7 @@ class SessionsResource {
|
|
|
437
482
|
}
|
|
438
483
|
const data = (await res.json());
|
|
439
484
|
if (!data.sessionId || !data.wsEndpoint) {
|
|
440
|
-
throw new SolariError(`Solari: unexpected session response: ${
|
|
485
|
+
throw new SolariError(`Solari: unexpected session response (fields: ${responseFields(data)})`);
|
|
441
486
|
}
|
|
442
487
|
const cdpUpstream = data.cdpEndpoint ?? deriveCdpFromWs(data.wsEndpoint);
|
|
443
488
|
const localWsEndpoint = await this.client._wrapSessionEndpoint(data.sessionId, data.wsEndpoint, "ws");
|
|
@@ -472,12 +517,12 @@ class SessionsResource {
|
|
|
472
517
|
void this.client.request("DELETE", `/sessions/${encodeURIComponent(id)}`).then(async (res) => {
|
|
473
518
|
const rejection = await releaseRejection(res);
|
|
474
519
|
if (rejection) {
|
|
475
|
-
console.warn(`[Solari] DELETE /sessions/${id} returned ${res.status}: ` +
|
|
520
|
+
console.warn(`[Solari] ${redactSessionPath(`DELETE /sessions/${id}`)} returned ${res.status}: ` +
|
|
476
521
|
`${rejection.body} — session was NOT released; its pool ` +
|
|
477
522
|
`slot stays held until the server-side grace timer reaps it`);
|
|
478
523
|
}
|
|
479
524
|
}, (err) => {
|
|
480
|
-
console.warn(`[Solari] DELETE /sessions/${id} failed (fire-and-forget):`, err);
|
|
525
|
+
console.warn(`[Solari] ${redactSessionPath(`DELETE /sessions/${id}`)} failed (fire-and-forget):`, err);
|
|
481
526
|
});
|
|
482
527
|
}
|
|
483
528
|
async releaseAndWait(id) {
|
|
@@ -485,7 +530,7 @@ class SessionsResource {
|
|
|
485
530
|
const res = await this.client.request("DELETE", `/sessions/${encodeURIComponent(id)}`);
|
|
486
531
|
const rejection = await releaseRejection(res);
|
|
487
532
|
if (rejection) {
|
|
488
|
-
throw new SolariError(`Solari DELETE /sessions/${id} failed: ${res.status} ${rejection.body}`, res.status, undefined, rejection.code);
|
|
533
|
+
throw new SolariError(`Solari ${redactSessionPath(`DELETE /sessions/${id}`)} failed: ${res.status} ${rejection.body}`, res.status, undefined, rejection.code);
|
|
489
534
|
}
|
|
490
535
|
}
|
|
491
536
|
/** The gateway's current view of one session (`GET /sessions/:id`). */
|
|
@@ -524,7 +569,7 @@ class SessionsResource {
|
|
|
524
569
|
throw await apiError(`GET /sessions/${id}/replay-url`, res);
|
|
525
570
|
const data = (await res.json());
|
|
526
571
|
if (!data.url) {
|
|
527
|
-
throw new SolariError(`Solari: unexpected replay-url response: ${
|
|
572
|
+
throw new SolariError(`Solari: unexpected replay-url response (fields: ${responseFields(data)})`);
|
|
528
573
|
}
|
|
529
574
|
return {
|
|
530
575
|
url: data.url,
|
|
@@ -662,7 +707,7 @@ export class Solari {
|
|
|
662
707
|
await sleep(100 * attempt);
|
|
663
708
|
continue;
|
|
664
709
|
}
|
|
665
|
-
throw err;
|
|
710
|
+
throw scrubbedLaunchError(err, [session.wsEndpoint, session.cdpEndpoint]);
|
|
666
711
|
}
|
|
667
712
|
}
|
|
668
713
|
throw lastErr ?? new SolariError("Solari.launch: exhausted attempts");
|
|
@@ -704,7 +749,7 @@ export class Solari {
|
|
|
704
749
|
upstream = new URL(upstreamEndpoint);
|
|
705
750
|
}
|
|
706
751
|
catch (e) {
|
|
707
|
-
throw new SolariError(`Solari: invalid upstream ${kind}Endpoint: ${upstreamEndpoint}`, undefined, e);
|
|
752
|
+
throw new SolariError(`Solari: invalid upstream ${kind}Endpoint: ${redactUrl(upstreamEndpoint)}`, undefined, e);
|
|
708
753
|
}
|
|
709
754
|
await this.localProxy.start();
|
|
710
755
|
this.localProxy.register(sessionId, upstream, kind);
|
|
@@ -752,7 +797,7 @@ export class Solari {
|
|
|
752
797
|
if (!retryThis || attempt === maxAttempts)
|
|
753
798
|
return res;
|
|
754
799
|
await res.text().catch(() => { });
|
|
755
|
-
lastErr = new SolariError(`Solari ${method} ${path}: ${res.status}`, res.status);
|
|
800
|
+
lastErr = new SolariError(`Solari ${method} ${redactSessionPath(path)}: ${res.status}`, res.status);
|
|
756
801
|
}
|
|
757
802
|
catch (e) {
|
|
758
803
|
clearTimeout(timer);
|
|
@@ -764,6 +809,6 @@ export class Solari {
|
|
|
764
809
|
await sleep(this.backoffMs);
|
|
765
810
|
}
|
|
766
811
|
}
|
|
767
|
-
throw new SolariError(`Solari ${method} ${path}: exhausted ${maxAttempts} attempts`, undefined, lastErr);
|
|
812
|
+
throw new SolariError(`Solari ${method} ${redactSessionPath(path)}: exhausted ${maxAttempts} attempts`, undefined, lastErr);
|
|
768
813
|
}
|
|
769
814
|
}
|