@oya-ai/browser 1.0.110 → 1.0.113
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 +49 -33
- package/dist/index.cjs +206 -105
- package/dist/index.d.cts +28 -8
- package/dist/index.d.ts +28 -8
- package/dist/index.js +206 -105
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,3 +1,44 @@
|
|
|
1
|
+
// src/constants.ts
|
|
2
|
+
var DEFAULT_BASE_URL = "https://oyabrowser.com";
|
|
3
|
+
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
4
|
+
var START_TIMEOUT_MS = 12e4;
|
|
5
|
+
var READY_TIMEOUT_MS = 12e4;
|
|
6
|
+
var READY_POLL_MS = 2e3;
|
|
7
|
+
var NAVIGATE_TIMEOUT_MS = 12e4;
|
|
8
|
+
var CHALLENGE_TIMEOUT_MS = 18e4;
|
|
9
|
+
var AGENT_TIMEOUT_MS = 6e5;
|
|
10
|
+
var PLAYBOOK_TIMEOUT_MS = 12e4;
|
|
11
|
+
var STOP_TIMEOUT_MS = 6e4;
|
|
12
|
+
var WAIT_FOR_DEFAULT_MS = 3e4;
|
|
13
|
+
var WAIT_FOR_GRACE_MS = 5e3;
|
|
14
|
+
var AIMED_SCROLL_AMOUNT = 500;
|
|
15
|
+
var RUN_POLL_MS = 2e3;
|
|
16
|
+
var MAX_RUN_POLL_ERRORS = 5;
|
|
17
|
+
var BYTES_PER_MB = 1048576;
|
|
18
|
+
var MAX_FILE_MB = 10;
|
|
19
|
+
var BASE64_CHUNK_BYTES = 8192;
|
|
20
|
+
var MS_PER_SECOND = 1e3;
|
|
21
|
+
var LATIN1_MAX = 255;
|
|
22
|
+
var SPACE = 32;
|
|
23
|
+
var TAB = 9;
|
|
24
|
+
var DEL = 127;
|
|
25
|
+
var HEX = 16;
|
|
26
|
+
var CODE_POINT_DIGITS = 4;
|
|
27
|
+
var Status = {
|
|
28
|
+
/** The caller passed something unusable, such as a non-numeric element id. */
|
|
29
|
+
BAD_REQUEST: 400,
|
|
30
|
+
/** The server does not know that session. */
|
|
31
|
+
NOT_FOUND: 404,
|
|
32
|
+
/** The browser is in a state that needs attention first. */
|
|
33
|
+
CONFLICT: 409,
|
|
34
|
+
/** A browser command ran and failed. */
|
|
35
|
+
UNPROCESSABLE: 422,
|
|
36
|
+
/** A run failed without saying why. */
|
|
37
|
+
SERVER_ERROR: 500,
|
|
38
|
+
/** A browser did not come up in time. */
|
|
39
|
+
GATEWAY_TIMEOUT: 504
|
|
40
|
+
};
|
|
41
|
+
|
|
1
42
|
// src/errors.ts
|
|
2
43
|
var OyaError = class extends Error {
|
|
3
44
|
/** The HTTP status, or the SDK's own status for errors it raises itself. */
|
|
@@ -12,6 +53,10 @@ var OyaError = class extends Error {
|
|
|
12
53
|
this.body = body;
|
|
13
54
|
}
|
|
14
55
|
};
|
|
56
|
+
function refusal(message, field, suggestion) {
|
|
57
|
+
const body = { error: message, code: "invalid_request", field, ...suggestion ? { suggestion } : {} };
|
|
58
|
+
return new OyaError(message, Status.BAD_REQUEST, body);
|
|
59
|
+
}
|
|
15
60
|
|
|
16
61
|
// src/cli-config.ts
|
|
17
62
|
var node = () => globalThis.process;
|
|
@@ -35,58 +80,26 @@ function savedConfig() {
|
|
|
35
80
|
}
|
|
36
81
|
}
|
|
37
82
|
|
|
38
|
-
// src/constants.ts
|
|
39
|
-
var DEFAULT_BASE_URL = "https://oyabrowser.com";
|
|
40
|
-
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
41
|
-
var START_TIMEOUT_MS = 12e4;
|
|
42
|
-
var READY_TIMEOUT_MS = 12e4;
|
|
43
|
-
var READY_POLL_MS = 2e3;
|
|
44
|
-
var NAVIGATE_TIMEOUT_MS = 12e4;
|
|
45
|
-
var CHALLENGE_TIMEOUT_MS = 18e4;
|
|
46
|
-
var AGENT_TIMEOUT_MS = 6e5;
|
|
47
|
-
var PLAYBOOK_TIMEOUT_MS = 12e4;
|
|
48
|
-
var STOP_TIMEOUT_MS = 6e4;
|
|
49
|
-
var WAIT_FOR_DEFAULT_MS = 3e4;
|
|
50
|
-
var WAIT_FOR_GRACE_MS = 5e3;
|
|
51
|
-
var AIMED_SCROLL_AMOUNT = 500;
|
|
52
|
-
var RUN_POLL_MS = 2e3;
|
|
53
|
-
var MAX_RUN_POLL_ERRORS = 5;
|
|
54
|
-
var BYTES_PER_MB = 1048576;
|
|
55
|
-
var MAX_FILE_MB = 10;
|
|
56
|
-
var BASE64_CHUNK_BYTES = 8192;
|
|
57
|
-
var MS_PER_SECOND = 1e3;
|
|
58
|
-
var Status = {
|
|
59
|
-
/** The caller passed something unusable, such as a non-numeric element id. */
|
|
60
|
-
BAD_REQUEST: 400,
|
|
61
|
-
/** The server does not know that session. */
|
|
62
|
-
NOT_FOUND: 404,
|
|
63
|
-
/** The browser is in a state that needs attention first. */
|
|
64
|
-
CONFLICT: 409,
|
|
65
|
-
/** A browser command ran and failed. */
|
|
66
|
-
UNPROCESSABLE: 422,
|
|
67
|
-
/** A run failed without saying why. */
|
|
68
|
-
SERVER_ERROR: 500,
|
|
69
|
-
/** A browser did not come up in time. */
|
|
70
|
-
GATEWAY_TIMEOUT: 504
|
|
71
|
-
};
|
|
72
|
-
|
|
73
83
|
// src/client.ts
|
|
84
|
+
var OPTIONS_ORIGIN = { apiKeyFrom: "the apiKey option", baseUrlFrom: "the baseUrl option", savedKey: false };
|
|
74
85
|
var Http = class {
|
|
75
|
-
/** Stores where to call, with which key, how long to wait
|
|
76
|
-
constructor(baseUrl, apiKey, timeoutMs, fetchImpl) {
|
|
86
|
+
/** Stores where to call, with which key, how long to wait, which fetch to use, and where the key and address came from. */
|
|
87
|
+
constructor(baseUrl, apiKey, timeoutMs, fetchImpl, origin = OPTIONS_ORIGIN) {
|
|
77
88
|
this.baseUrl = baseUrl;
|
|
78
89
|
this.apiKey = apiKey;
|
|
79
90
|
this.timeoutMs = timeoutMs;
|
|
80
91
|
this.fetchImpl = fetchImpl;
|
|
92
|
+
this.origin = origin;
|
|
81
93
|
}
|
|
82
94
|
baseUrl;
|
|
83
95
|
apiKey;
|
|
84
96
|
timeoutMs;
|
|
85
97
|
fetchImpl;
|
|
98
|
+
origin;
|
|
86
99
|
/** Sends one request and returns the parsed answer, or throws an OyaError when it failed. */
|
|
87
100
|
async request(method, path, body, timeoutMs = this.timeoutMs, headers = {}) {
|
|
88
101
|
const res = await this.send(`${this.baseUrl}${path}`, this.init(method, body, timeoutMs, headers), timeoutMs);
|
|
89
|
-
return readAnswer(res, `${method} ${path}`, this
|
|
102
|
+
return readAnswer(res, `${method} ${path}`, this);
|
|
90
103
|
}
|
|
91
104
|
/** The fetch options for one call, with this client's key on them. */
|
|
92
105
|
init(method, body, timeoutMs, headers) {
|
|
@@ -101,23 +114,34 @@ var Http = class {
|
|
|
101
114
|
try {
|
|
102
115
|
return await this.fetchImpl(url, init);
|
|
103
116
|
} catch (err) {
|
|
104
|
-
throw
|
|
117
|
+
throw noAnswer(this, timeoutMs, err);
|
|
105
118
|
}
|
|
106
119
|
}
|
|
107
120
|
};
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return
|
|
121
|
+
var timedOut = (err) => err?.name === "TimeoutError" || /abort/i.test(String(err?.message));
|
|
122
|
+
function causeOf(err) {
|
|
123
|
+
const cause = err?.cause;
|
|
124
|
+
return cause?.code ?? cause?.errors?.[0]?.code;
|
|
125
|
+
}
|
|
126
|
+
function noAnswer(http, timeoutMs, err) {
|
|
127
|
+
const code = timedOut(err) ? "timeout" : "unreachable";
|
|
128
|
+
const body = { error: String(err?.message), code, ...causeOf(err) ? { cause: causeOf(err) } : {} };
|
|
129
|
+
return new OyaError(unreachable(http, timeoutMs, code), 0, body);
|
|
130
|
+
}
|
|
131
|
+
function unreachable({ baseUrl, origin }, timeoutMs, code) {
|
|
132
|
+
if (code === "timeout")
|
|
133
|
+
return `No answer from ${baseUrl} within ${timeoutMs}ms. Is it reachable, and is the call this slow?`;
|
|
134
|
+
if (origin.baseUrlFrom === "default") return `Could not reach ${baseUrl}. Check your connection.`;
|
|
135
|
+
return `Could not reach ${baseUrl}. Is the server running, and is ${origin.baseUrlFrom} right?`;
|
|
112
136
|
}
|
|
113
137
|
function requestInit(method, body, timeoutMs, headers) {
|
|
114
138
|
const json = body === void 0 ? {} : { "Content-Type": "application/json" };
|
|
115
139
|
const payload = body === void 0 ? void 0 : JSON.stringify(body);
|
|
116
140
|
return { method, headers: { ...headers, ...json }, body: payload, signal: AbortSignal.timeout(timeoutMs) };
|
|
117
141
|
}
|
|
118
|
-
async function readAnswer(res, call2,
|
|
142
|
+
async function readAnswer(res, call2, http) {
|
|
119
143
|
const payload = parseBody(await res.text());
|
|
120
|
-
if (!res.ok) throw failure(call2, res.status, payload,
|
|
144
|
+
if (!res.ok) throw failure(call2, res.status, payload, http);
|
|
121
145
|
return payload;
|
|
122
146
|
}
|
|
123
147
|
function parseBody(text) {
|
|
@@ -127,22 +151,74 @@ function parseBody(text) {
|
|
|
127
151
|
return text;
|
|
128
152
|
}
|
|
129
153
|
}
|
|
130
|
-
function failure(call2, status, payload,
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
154
|
+
function failure(call2, status, payload, http) {
|
|
155
|
+
const message = payload?.error || `${call2} failed (${status})`;
|
|
156
|
+
return new OyaError(message === "Invalid API key" ? invalidKey(http) : message, status, payload);
|
|
157
|
+
}
|
|
158
|
+
function invalidKey({ baseUrl, origin }) {
|
|
159
|
+
const overrides = origin.apiKeyFrom === "OYA_API_KEY" && origin.savedKey;
|
|
160
|
+
const rest = overrides ? ", which overrides the one saved in ~/.oya/config.json" : "";
|
|
161
|
+
return `Invalid API key for ${baseUrl}. The key came from ${origin.apiKeyFrom}${rest}.`;
|
|
136
162
|
}
|
|
137
163
|
var env = (name) => globalThis.process?.env?.[name];
|
|
164
|
+
function firstOf(option, name, saved, optionName) {
|
|
165
|
+
if (option) return { value: option, from: `the ${optionName} option` };
|
|
166
|
+
if (env(name)) return { value: env(name), from: name };
|
|
167
|
+
return { value: saved, from: "~/.oya/config.json" };
|
|
168
|
+
}
|
|
169
|
+
function addressOf(options, saved) {
|
|
170
|
+
const found = firstOf(options.baseUrl, "OYA_BASE_URL", saved, "baseUrl");
|
|
171
|
+
return found.value ? found : { value: DEFAULT_BASE_URL, from: "default" };
|
|
172
|
+
}
|
|
173
|
+
function keyOf(options, saved) {
|
|
174
|
+
const found = firstOf(options.apiKey, "OYA_API_KEY", saved, "apiKey");
|
|
175
|
+
if (!found.value) throw new Error("No API key. Pass { apiKey }, set OYA_API_KEY, or run `npx @oya-ai/cli login`.");
|
|
176
|
+
return { ...found, value: headerSafe(found.value, "apiKey", "Copy the key again.") };
|
|
177
|
+
}
|
|
138
178
|
function createHttp(options) {
|
|
139
179
|
const saved = savedConfig();
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
const baseUrl = (options.baseUrl || env("OYA_BASE_URL") || saved.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
180
|
+
const key = keyOf(options, saved.apiKey);
|
|
181
|
+
const address = addressOf(options, saved.baseUrl);
|
|
143
182
|
const fetchImpl = options.fetch || globalThis.fetch;
|
|
144
183
|
if (!fetchImpl) throw new Error("No fetch available, pass { fetch } or use Node 18+.");
|
|
145
|
-
|
|
184
|
+
const origin = { apiKeyFrom: key.from, baseUrlFrom: address.from, savedKey: !!saved.apiKey };
|
|
185
|
+
const timeout = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
186
|
+
return new Http(webAddress(address), key.value, timeout, fetchImpl.bind(globalThis), origin);
|
|
187
|
+
}
|
|
188
|
+
var LOCAL_HOST = /^(localhost|127\.)/i;
|
|
189
|
+
function webAddress({ value, from }) {
|
|
190
|
+
const url = value.trim().replace(/\/+$/, "");
|
|
191
|
+
if (/^https?:\/\//i.test(url)) return url;
|
|
192
|
+
const name = from.startsWith("the ") ? "baseUrl" : from === "OYA_BASE_URL" ? from : `baseUrl in ${from}`;
|
|
193
|
+
const suggestion = suggestionFor(url);
|
|
194
|
+
const tryIt = suggestion ? ` Try ${suggestion}.` : "";
|
|
195
|
+
throw refusal(`${name} must start with http:// or https://, not "${url}".${tryIt}`, "baseUrl", suggestion);
|
|
196
|
+
}
|
|
197
|
+
function suggestionFor(url) {
|
|
198
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(url) && !/^[^:/]+:\d+(\/|$)/.test(url)) return void 0;
|
|
199
|
+
return `${LOCAL_HOST.test(url) ? "http" : "https"}://${url}`;
|
|
200
|
+
}
|
|
201
|
+
function notInAHeader(ch) {
|
|
202
|
+
const code = ch.codePointAt(0);
|
|
203
|
+
return code < SPACE && code !== TAB || code === DEL || code > LATIN1_MAX;
|
|
204
|
+
}
|
|
205
|
+
function headerSafe(value, field, ending) {
|
|
206
|
+
const trimmed = value.trim();
|
|
207
|
+
const chars = Array.from(trimmed);
|
|
208
|
+
const at = chars.findIndex(notInAHeader);
|
|
209
|
+
if (at < 0) return trimmed;
|
|
210
|
+
const leading = Array.from(value).length - Array.from(value.trimStart()).length;
|
|
211
|
+
const where = `position ${leading + at + 1} (${codePoint(chars[at])})`;
|
|
212
|
+
throw refusal(`${field} has a character HTTP headers cannot carry at ${where}. ${ending}`, field);
|
|
213
|
+
}
|
|
214
|
+
var codePoint = (ch) => `U+${ch.codePointAt(0).toString(HEX).toUpperCase().padStart(CODE_POINT_DIGITS, "0")}`;
|
|
215
|
+
var shown = (value) => typeof value === "string" ? JSON.stringify(value) : String(value);
|
|
216
|
+
function segment(value, field = "id") {
|
|
217
|
+
if (typeof value !== "string" || !value)
|
|
218
|
+
throw refusal(`${field} must be a non-empty string, not ${shown(value)}`, field);
|
|
219
|
+
if (value.includes("/") || value === "." || value === "..")
|
|
220
|
+
throw refusal(`${field} must be a single path segment (no "/" and not "." or ".."), not ${shown(value)}`, field);
|
|
221
|
+
return encodeURIComponent(value);
|
|
146
222
|
}
|
|
147
223
|
|
|
148
224
|
// src/run-watch.ts
|
|
@@ -364,6 +440,17 @@ var Browser = class {
|
|
|
364
440
|
const path = `/api/browsers/${this.id}/chat`;
|
|
365
441
|
return agentAnswer(await this.http.request("POST", path, body, AGENT_TIMEOUT_MS)).text;
|
|
366
442
|
}
|
|
443
|
+
/**
|
|
444
|
+
* `ask()` for data: the agent does the task and answers in the shape of `schema`
|
|
445
|
+
* (a JSON schema), instead of in words. Throws when the agent reports it could not.
|
|
446
|
+
*/
|
|
447
|
+
async extract(prompt, schema, values = {}) {
|
|
448
|
+
const body = { messages: [{ role: "user", content: prompt }], ...values, schema };
|
|
449
|
+
const path = `/api/browsers/${this.id}/chat`;
|
|
450
|
+
const res = agentAnswer(await this.http.request("POST", path, body, AGENT_TIMEOUT_MS));
|
|
451
|
+
if (res.failed || res.data === void 0) throw new OyaError(res.text, Status.UNPROCESSABLE, res);
|
|
452
|
+
return res.data;
|
|
453
|
+
}
|
|
367
454
|
/**
|
|
368
455
|
* Save the last `ask()` on this browser as a named playbook. Every value that was
|
|
369
456
|
* typed, picked or clicked becomes a variable, with what the run used kept in
|
|
@@ -381,7 +468,7 @@ var Browser = class {
|
|
|
381
468
|
* Play `'<name>:draft'` to try a draft before promoting it.
|
|
382
469
|
*/
|
|
383
470
|
async play(name, data = {}, { autoHeal = true } = {}) {
|
|
384
|
-
const path = `/api/browsers/${this.id}/playbooks/${
|
|
471
|
+
const path = `/api/browsers/${this.id}/playbooks/${segment(name, "name")}/play`;
|
|
385
472
|
return agentAnswer(
|
|
386
473
|
await this.http.request("POST", path, { variables: data, autoHeal }, AGENT_TIMEOUT_MS)
|
|
387
474
|
);
|
|
@@ -432,7 +519,7 @@ var Browser = class {
|
|
|
432
519
|
}
|
|
433
520
|
/** Revoke a link from `shareUrl()` before it expires, by the id it returned. */
|
|
434
521
|
async revokeShare(id) {
|
|
435
|
-
await this.http.request("DELETE", `/api/control/credentials/${
|
|
522
|
+
await this.http.request("DELETE", `/api/control/credentials/${segment(id)}`);
|
|
436
523
|
}
|
|
437
524
|
/** Counters, health and the last 50 things this browser did. */
|
|
438
525
|
status() {
|
|
@@ -488,8 +575,9 @@ function startBody(options) {
|
|
|
488
575
|
const { profile, persona, provider, wsUrl, name, queueMs, priority, budgetUsd, governed, policy } = options;
|
|
489
576
|
return { profile: profile || persona, provider, wsUrl, name, queueMs, priority, budgetUsd, governed, policy };
|
|
490
577
|
}
|
|
578
|
+
var idempotencyKeyOf = ({ idempotencyKey }) => idempotencyKey ? headerSafe(idempotencyKey, "idempotencyKey", 'Use letters, digits, "-" and "_".') : globalThis.crypto.randomUUID();
|
|
491
579
|
async function start(http, wait, options) {
|
|
492
|
-
const headers = { "Idempotency-Key": options
|
|
580
|
+
const headers = { "Idempotency-Key": idempotencyKeyOf(options) };
|
|
493
581
|
const path = "/api/browsers/start";
|
|
494
582
|
const started = await http().request("POST", path, startBody(options), START_TIMEOUT_MS, headers);
|
|
495
583
|
if (started.status === "starting") {
|
|
@@ -498,7 +586,7 @@ async function start(http, wait, options) {
|
|
|
498
586
|
}
|
|
499
587
|
return new Browser(http(), started, options.captcha === "auto");
|
|
500
588
|
}
|
|
501
|
-
var fetchBrowser = (http, id) => http().request("GET", `/api/browsers/${
|
|
589
|
+
var fetchBrowser = (http, id) => http().request("GET", `/api/browsers/${segment(id)}`);
|
|
502
590
|
async function reattach(http, id) {
|
|
503
591
|
const found = await fetchBrowser(http, id);
|
|
504
592
|
return new Browser(http(), asStarted(found), false);
|
|
@@ -510,69 +598,79 @@ function asStarted(found) {
|
|
|
510
598
|
var stopBrowsers = (http, ids) => http().request("POST", "/api/browsers/stop", ids === "all" ? { all: true } : { ids }, START_TIMEOUT_MS);
|
|
511
599
|
var browserApi = (http, wait) => ({
|
|
512
600
|
/** Start a browser and wait until it can take commands. */
|
|
513
|
-
start: (options = {}) => start(http, wait, options),
|
|
601
|
+
start: async (options = {}) => start(http, wait, options),
|
|
514
602
|
/** Reattach to a browser that is already running. */
|
|
515
|
-
get: (id) => reattach(http, id),
|
|
603
|
+
get: async (id) => reattach(http, id),
|
|
516
604
|
/** Every browser on this key. */
|
|
517
|
-
list: () => http().request("GET", "/api/browsers"),
|
|
605
|
+
list: async () => http().request("GET", "/api/browsers"),
|
|
518
606
|
/** Stop some (`ids`) or every browser on this key. Each reports separately. */
|
|
519
|
-
stop: (ids) => stopBrowsers(http, ids),
|
|
607
|
+
stop: async (ids) => stopBrowsers(http, ids),
|
|
520
608
|
/** Stop every browser on this key; returns how many stopped. */
|
|
521
609
|
stopAll: async () => (await stopBrowsers(http, "all")).stopped
|
|
522
610
|
});
|
|
523
611
|
|
|
524
612
|
// src/api/control.ts
|
|
525
|
-
var session = (id, action = "") => `/api/control/sessions/${
|
|
526
|
-
var item = (kind, id) => `/api/control/${kind}/${
|
|
613
|
+
var session = (id, action = "") => `/api/control/sessions/${segment(id)}${action}`;
|
|
614
|
+
var item = (kind, id) => `/api/control/${kind}/${segment(id)}`;
|
|
527
615
|
var sessionCalls = (http) => ({
|
|
528
616
|
/** Settings, sessions and recent events at a glance. */
|
|
529
|
-
overview: () => http().request("GET", "/api/control"),
|
|
617
|
+
overview: async () => http().request("GET", "/api/control"),
|
|
530
618
|
/** Every session, including disconnected and cleanup-pending ones. */
|
|
531
|
-
sessions: () => http().request("GET", "/api/control/sessions"),
|
|
619
|
+
sessions: async () => http().request("GET", "/api/control/sessions"),
|
|
532
620
|
/** One session. */
|
|
533
|
-
session: (id) => http().request("GET", session(id)),
|
|
621
|
+
session: async (id) => http().request("GET", session(id)),
|
|
534
622
|
/** Update limits, rate cards and retention. */
|
|
535
|
-
settings: (changes) => http().request("PATCH", "/api/control/project", changes)
|
|
623
|
+
settings: async (changes) => http().request("PATCH", "/api/control/project", changes)
|
|
536
624
|
});
|
|
537
625
|
var lifecycleCalls = (http) => ({
|
|
538
|
-
/**
|
|
539
|
-
cancel: (id) => http().request("POST", session(id, "/cancel"), {}),
|
|
626
|
+
/** Stop a session in any state; answers its final state. */
|
|
627
|
+
cancel: async (id) => http().request("POST", session(id, "/cancel"), {}),
|
|
540
628
|
/** Stop a session; `force` stops despite a profile-save error, or reconciles. */
|
|
541
|
-
stop: (id, force = false) => http().request("POST", session(id, "/stop"), { force }),
|
|
629
|
+
stop: async (id, force = false) => http().request("POST", session(id, "/stop"), { force }),
|
|
542
630
|
/** Acquire or release human control, or acknowledge the agent's resume. */
|
|
543
|
-
takeover: (id, action) => http().request("POST", session(id, "/control"), { action }),
|
|
631
|
+
takeover: async (id, action) => http().request("POST", session(id, "/control"), { action }),
|
|
544
632
|
/** Send one input as the human holding the control lease. */
|
|
545
|
-
input: (id, action, params) => http().request("POST", session(id, "/input"), { action, params })
|
|
633
|
+
input: async (id, action, params) => http().request("POST", session(id, "/input"), { action, params })
|
|
546
634
|
});
|
|
635
|
+
function recoverBody(options) {
|
|
636
|
+
const { replace = false, wsUrl } = typeof options === "boolean" ? { replace: options } : options ?? {};
|
|
637
|
+
if (wsUrl && !replace)
|
|
638
|
+
throw refusal("wsUrl is only used with replace: true; recovering in place keeps the endpoint it had.", "wsUrl");
|
|
639
|
+
return wsUrl ? { replace, wsUrl } : { replace };
|
|
640
|
+
}
|
|
547
641
|
var recoveryCalls = (http) => ({
|
|
548
|
-
/**
|
|
549
|
-
|
|
642
|
+
/**
|
|
643
|
+
* Explicitly recover a session, or replace it with a fresh one. A cdp
|
|
644
|
+
* session's replacement needs the Chrome it runs on: `{ replace: true, wsUrl }`.
|
|
645
|
+
* `recover(id, true)` still means replace.
|
|
646
|
+
*/
|
|
647
|
+
recover: async (id, options = {}) => http().request("POST", session(id, "/recover"), recoverBody(options)),
|
|
550
648
|
/** A single-use ticket for the live stream. */
|
|
551
|
-
ticket: (id) => http().request("POST", session(id, "/ticket"), {}),
|
|
649
|
+
ticket: async (id) => http().request("POST", session(id, "/ticket"), {}),
|
|
552
650
|
/** Durable lifecycle events after a cursor. */
|
|
553
|
-
events: (after = 0) => http().request("GET", `/api/control/events?after=${after}`)
|
|
651
|
+
events: async (after = 0) => http().request("GET", `/api/control/events?after=${after}`)
|
|
554
652
|
});
|
|
555
653
|
var credentialCalls = (http) => ({
|
|
556
654
|
/** Mint a service credential. Its token is returned this once. */
|
|
557
|
-
createCredential: (options) => http().request("POST", "/api/control/credentials", options),
|
|
655
|
+
createCredential: async (options) => http().request("POST", "/api/control/credentials", options),
|
|
558
656
|
/** Revoke a service credential. */
|
|
559
|
-
revokeCredential: (id) => http().request("DELETE", item("credentials", id))
|
|
657
|
+
revokeCredential: async (id) => http().request("DELETE", item("credentials", id))
|
|
560
658
|
});
|
|
561
659
|
var memberCalls = (http) => ({
|
|
562
660
|
/** The owner and every member. */
|
|
563
|
-
members: () => http().request("GET", "/api/control/members"),
|
|
661
|
+
members: async () => http().request("GET", "/api/control/members"),
|
|
564
662
|
/** An invitation code for a new member. */
|
|
565
|
-
inviteMember: (role = "operator") => http().request("POST", "/api/control/members/invite", { role }),
|
|
663
|
+
inviteMember: async (role = "operator") => http().request("POST", "/api/control/members/invite", { role }),
|
|
566
664
|
/** Remove a member. */
|
|
567
|
-
removeMember: (userId) => http().request("DELETE", item("members", userId))
|
|
665
|
+
removeMember: async (userId) => http().request("DELETE", item("members", userId))
|
|
568
666
|
});
|
|
569
667
|
var webhookCalls = (http) => ({
|
|
570
668
|
/** Register a webhook; `types` limits which events it receives. */
|
|
571
|
-
createWebhook: (url, types = []) => http().request("POST", "/api/control/webhooks", { url, types }),
|
|
669
|
+
createWebhook: async (url, types = []) => http().request("POST", "/api/control/webhooks", { url, types }),
|
|
572
670
|
/** Disable a webhook. */
|
|
573
|
-
removeWebhook: (id) => http().request("DELETE", item("webhooks", id)),
|
|
671
|
+
removeWebhook: async (id) => http().request("DELETE", item("webhooks", id)),
|
|
574
672
|
/** Send a delivery again. */
|
|
575
|
-
replayDelivery: (id) => http().request("POST", `${item("deliveries", id)}/replay`, {})
|
|
673
|
+
replayDelivery: async (id) => http().request("POST", `${item("deliveries", id)}/replay`, {})
|
|
576
674
|
});
|
|
577
675
|
var controlApi = (http) => ({
|
|
578
676
|
...sessionCalls(http),
|
|
@@ -584,7 +682,7 @@ var controlApi = (http) => ({
|
|
|
584
682
|
});
|
|
585
683
|
|
|
586
684
|
// src/api/playbooks.ts
|
|
587
|
-
var playbook = (name) => `/api/playbooks/${
|
|
685
|
+
var playbook = (name) => `/api/playbooks/${segment(name, "name")}`;
|
|
588
686
|
var playbookApi = (http) => ({
|
|
589
687
|
/** Every saved playbook, with any draft waiting on it. */
|
|
590
688
|
list: async () => (await http().request("GET", "/api/playbooks")).playbooks,
|
|
@@ -593,7 +691,7 @@ var playbookApi = (http) => ({
|
|
|
593
691
|
await http().request("DELETE", playbook(name));
|
|
594
692
|
},
|
|
595
693
|
/** Replace a playbook with the draft a healed replay saved. Try it first with `browser.play('<name>:draft')`. */
|
|
596
|
-
promote: (name) => http().request("POST", `${playbook(name)}/promote`, {})
|
|
694
|
+
promote: async (name) => http().request("POST", `${playbook(name)}/promote`, {})
|
|
597
695
|
});
|
|
598
696
|
|
|
599
697
|
// src/api/proxies.ts
|
|
@@ -601,10 +699,10 @@ var proxyApi = (http) => ({
|
|
|
601
699
|
/** Every proxy this key can use, shared ones included. */
|
|
602
700
|
list: async () => (await http().request("GET", "/api/proxies")).proxies,
|
|
603
701
|
/** Add a proxy. Its credentials are never read back. */
|
|
604
|
-
create: (proxy) => http().request("POST", "/api/proxies", proxy),
|
|
702
|
+
create: async (proxy) => http().request("POST", "/api/proxies", proxy),
|
|
605
703
|
/** Remove one of this key's proxies. */
|
|
606
704
|
remove: async (id) => {
|
|
607
|
-
await http().request("DELETE", `/api/proxies/${
|
|
705
|
+
await http().request("DELETE", `/api/proxies/${segment(id)}`);
|
|
608
706
|
},
|
|
609
707
|
/** Dial each proxy and learn its real exit IP. Failing ones cool down and are skipped. */
|
|
610
708
|
check: async () => (await http().request("POST", "/api/proxies/check", {})).results
|
|
@@ -615,35 +713,38 @@ var identityCalls = (http) => ({
|
|
|
615
713
|
/** Every persona on this key. */
|
|
616
714
|
list: async () => (await http().request("GET", "/api/personas")).personas,
|
|
617
715
|
/** One persona. */
|
|
618
|
-
get: (id) => http().request("GET", `/api/personas/${id}`),
|
|
716
|
+
get: async (id) => http().request("GET", `/api/personas/${segment(id)}`),
|
|
619
717
|
/**
|
|
620
718
|
* Create an identity. The device, platform, timezone, locale, is chosen
|
|
621
719
|
* here and fixed for its life; `preview()` shows what a choice produces.
|
|
622
720
|
*/
|
|
623
|
-
create: (options = {}) => http().request("POST", "/api/personas", options),
|
|
721
|
+
create: async (options = {}) => http().request("POST", "/api/personas", options),
|
|
624
722
|
/** Name, concurrency cap and proxy hint. Never the device, clone for that. */
|
|
625
|
-
update: (id, changes) => http().request("PUT", `/api/personas/${id}`, changes),
|
|
723
|
+
update: async (id, changes) => http().request("PUT", `/api/personas/${segment(id)}`, changes),
|
|
626
724
|
/** A new persona of the same kind of device: same choices, fresh identity, empty jar. */
|
|
627
|
-
clone: (id, options = {}) => http().request("POST", `/api/personas/${id}/clone`, options)
|
|
725
|
+
clone: async (id, options = {}) => http().request("POST", `/api/personas/${segment(id)}/clone`, options)
|
|
628
726
|
});
|
|
629
727
|
var deviceCalls = (http) => ({
|
|
630
728
|
/** The fingerprint these choices would produce. Persists nothing. */
|
|
631
729
|
preview: async (prefs = {}) => (await http().request("POST", "/api/personas/preview", { prefs })).fingerprint,
|
|
632
730
|
/** Platforms, and the timezones and locales each may coherently claim. */
|
|
633
|
-
options: () => http().request("GET", "/api/personas/options"),
|
|
731
|
+
options: async () => http().request("GET", "/api/personas/options"),
|
|
634
732
|
/** Pin the persona to one of your proxies, or `null` to let assignment happen at connect. */
|
|
635
|
-
pinProxy: (id, proxyId) => http().request("PUT", `/api/personas/${id}/proxy`, { proxyId }),
|
|
733
|
+
pinProxy: async (id, proxyId) => http().request("PUT", `/api/personas/${segment(id)}/proxy`, { proxyId }),
|
|
636
734
|
/** Delete a persona. */
|
|
637
735
|
remove: async (id) => {
|
|
638
|
-
await http().request("DELETE", `/api/personas/${id}`);
|
|
736
|
+
await http().request("DELETE", `/api/personas/${segment(id)}`);
|
|
639
737
|
}
|
|
640
738
|
});
|
|
641
739
|
var mfaCalls = (http) => ({
|
|
642
740
|
/** Store the second factor for this identity. Sealed at rest, never read back. */
|
|
643
|
-
setMfa: (id, config) => http().request("PUT", `/api/personas/${id}/mfa`, config),
|
|
741
|
+
setMfa: async (id, config) => http().request("PUT", `/api/personas/${segment(id)}/mfa`, config),
|
|
644
742
|
/** Remove the persona-wide factor, or the one filed against `domain`. */
|
|
645
743
|
clearMfa: async (id, domain) => {
|
|
646
|
-
await http().request(
|
|
744
|
+
await http().request(
|
|
745
|
+
"DELETE",
|
|
746
|
+
`/api/personas/${segment(id)}/mfa${domain ? `?domain=${encodeURIComponent(domain)}` : ""}`
|
|
747
|
+
);
|
|
647
748
|
}
|
|
648
749
|
});
|
|
649
750
|
var loginCalls = (http) => ({
|
|
@@ -654,20 +755,20 @@ var loginCalls = (http) => ({
|
|
|
654
755
|
* better path. This is for portals that expire a session server-side
|
|
655
756
|
* between runs, where an unattended run has nothing else to recover with.
|
|
656
757
|
*/
|
|
657
|
-
setCredentials: (id, config) => http().request("PUT", `/api/personas/${id}/credentials`, config),
|
|
758
|
+
setCredentials: async (id, config) => http().request("PUT", `/api/personas/${segment(id)}/credentials`, config),
|
|
658
759
|
/** Which sites this identity can sign in to. Usernames only. */
|
|
659
|
-
credentials: (id) => http().request("GET", `/api/personas/${id}/credentials`),
|
|
760
|
+
credentials: async (id) => http().request("GET", `/api/personas/${segment(id)}/credentials`),
|
|
660
761
|
/** Remove the login stored for one site. */
|
|
661
762
|
clearCredentials: async (id, domain) => {
|
|
662
|
-
await http().request("DELETE", `/api/personas/${id}/credentials?domain=${encodeURIComponent(domain)}`);
|
|
763
|
+
await http().request("DELETE", `/api/personas/${segment(id)}/credentials?domain=${encodeURIComponent(domain)}`);
|
|
663
764
|
}
|
|
664
765
|
});
|
|
665
|
-
var jarPath = (id) => `/api/pool/cookies?persona=${
|
|
766
|
+
var jarPath = (id) => `/api/pool/cookies?persona=${segment(id)}`;
|
|
666
767
|
var jarCalls = (http) => ({
|
|
667
768
|
/** Every cookie in the jar; `format: 'playwright'` is ready for `context.addCookies()`. */
|
|
668
|
-
cookies: async (id, format = "json") => (await http().request("GET", `${jarPath(id)}&format=${format}`)).cookies,
|
|
769
|
+
cookies: async (id, format = "json") => (await http().request("GET", `${jarPath(id)}&format=${encodeURIComponent(format)}`)).cookies,
|
|
669
770
|
/** Merge cookies into the jar. The persona's browsers pick them up on their next visit to each site. */
|
|
670
|
-
importCookies: (id, list) => http().request("PUT", jarPath(id), { cookies: list })
|
|
771
|
+
importCookies: async (id, list) => http().request("PUT", jarPath(id), { cookies: list })
|
|
671
772
|
});
|
|
672
773
|
var cookieCalls = (http) => ({
|
|
673
774
|
...jarCalls(http),
|
package/package.json
CHANGED