@scalebrowser/sdk 0.2.0 → 0.4.0
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 +29 -23
- package/dist/index.cjs +109 -22
- package/dist/index.d.cts +272 -116
- package/dist/index.d.ts +272 -116
- package/dist/index.js +109 -22
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -15,7 +15,17 @@ var ErrorCode = {
|
|
|
15
15
|
* Distinct from Capacity (4003 = this machine is full, wait): the caller's move
|
|
16
16
|
* is to stop a running browser anywhere or upgrade the plan. */
|
|
17
17
|
ConcurrencyLimit: 4008,
|
|
18
|
-
Unauthorized: 4010
|
|
18
|
+
Unauthorized: 4010,
|
|
19
|
+
/** No account is connected to this machine, so nothing may run (DEC-014).
|
|
20
|
+
* Distinct from Unauthorized (4010 = the daemon's own bearer token is wrong, so
|
|
21
|
+
* re-read the token): 4011 means the machine needs to be signed in, and no token
|
|
22
|
+
* a client holds can substitute for that. Connect it in the desktop app. */
|
|
23
|
+
NotSignedIn: 4011,
|
|
24
|
+
/** This ONE profile is open on another machine of the same account (DEC-015).
|
|
25
|
+
* Distinct from ConcurrencyLimit (4008 = the plan is full, buy more or free any
|
|
26
|
+
* browser): the caller's move here costs nothing. Close it on that machine, or
|
|
27
|
+
* take it over from the app running there. */
|
|
28
|
+
ProfileInUse: 4012
|
|
19
29
|
};
|
|
20
30
|
var CODE_LABELS = {
|
|
21
31
|
4001: "Not found",
|
|
@@ -25,7 +35,9 @@ var CODE_LABELS = {
|
|
|
25
35
|
4005: "Launch preflight failed",
|
|
26
36
|
4006: "Profile already running",
|
|
27
37
|
4008: "Concurrent-browser limit reached (subscription-wide)",
|
|
28
|
-
4010: "Unauthorized"
|
|
38
|
+
4010: "Unauthorized",
|
|
39
|
+
4011: "No account connected to this machine",
|
|
40
|
+
4012: "This profile is open on another machine"
|
|
29
41
|
};
|
|
30
42
|
var ScalebrowserError = class extends Error {
|
|
31
43
|
constructor(message) {
|
|
@@ -127,7 +139,7 @@ var ClientCore = class {
|
|
|
127
139
|
return text ? JSON.parse(text) : void 0;
|
|
128
140
|
}
|
|
129
141
|
/**
|
|
130
|
-
* A route that answers with bytes, not JSON
|
|
142
|
+
* A route that answers with bytes, not JSON: artifacts and run stills.
|
|
131
143
|
*
|
|
132
144
|
* Separate from {@link ClientCore.request} rather than a flag on it, because the two
|
|
133
145
|
* differ in the error path as well: a failed byte fetch still answers JSON,
|
|
@@ -203,11 +215,12 @@ function enc(id) {
|
|
|
203
215
|
}
|
|
204
216
|
|
|
205
217
|
// src/client-control.ts
|
|
206
|
-
var
|
|
218
|
+
var NO_ACCOUNT = {
|
|
207
219
|
licensed: false,
|
|
220
|
+
signed_in: false,
|
|
208
221
|
running_local: 0,
|
|
209
222
|
profiles_local: 0,
|
|
210
|
-
permits_launch:
|
|
223
|
+
permits_launch: false
|
|
211
224
|
};
|
|
212
225
|
var ControlApi = class extends ClientCore {
|
|
213
226
|
// ── Interruptions ──────────────────────────────────────────────────────────
|
|
@@ -244,7 +257,7 @@ var ControlApi = class extends ClientCore {
|
|
|
244
257
|
});
|
|
245
258
|
}
|
|
246
259
|
/**
|
|
247
|
-
* Fetch one artifact's bytes
|
|
260
|
+
* Fetch one artifact's bytes: a screenshot, a download, a saved PDF.
|
|
248
261
|
*
|
|
249
262
|
* A miss is a plain 404 whether the id never existed, expired, or belonged to
|
|
250
263
|
* a released lease: the caller's next move is the same in all three cases.
|
|
@@ -257,14 +270,14 @@ var ControlApi = class extends ClientCore {
|
|
|
257
270
|
* Licence state for display. The launch gate stays the authority on starts.
|
|
258
271
|
*
|
|
259
272
|
* A daemon predating the route answers 404, and that is not an error worth
|
|
260
|
-
* throwing
|
|
261
|
-
*
|
|
273
|
+
* throwing, but it is read pessimistically: nothing can be shown to be
|
|
274
|
+
* connected or permitted. Same shape of tolerance as {@link getMetrics}.
|
|
262
275
|
*/
|
|
263
276
|
async getAccount(signal) {
|
|
264
277
|
try {
|
|
265
278
|
return await this.request("GET", "/v1/account", void 0, { signal });
|
|
266
279
|
} catch (err) {
|
|
267
|
-
if (err instanceof ApiError && err.status === 404) return { ...
|
|
280
|
+
if (err instanceof ApiError && err.status === 404) return { ...NO_ACCOUNT };
|
|
268
281
|
throw err;
|
|
269
282
|
}
|
|
270
283
|
}
|
|
@@ -272,7 +285,7 @@ var ControlApi = class extends ClientCore {
|
|
|
272
285
|
health(signal) {
|
|
273
286
|
return this.request("GET", "/health", void 0, { signal });
|
|
274
287
|
}
|
|
275
|
-
/** "Can it serve?" Local facts only
|
|
288
|
+
/** "Can it serve?" Local facts only; the control plane is deliberately not consulted. */
|
|
276
289
|
ready(signal) {
|
|
277
290
|
return this.request("GET", "/health/ready", void 0, { signal });
|
|
278
291
|
}
|
|
@@ -284,7 +297,7 @@ var ExtensionsApi = class extends ClientCore {
|
|
|
284
297
|
//
|
|
285
298
|
// Upload a `.crx`; the daemon lifts its public key out of the CRX header and
|
|
286
299
|
// writes it into the unpacked `manifest.json`, so every profile loads the
|
|
287
|
-
// package under its CANONICAL Web-Store id. A key-less package is refused
|
|
300
|
+
// package under its CANONICAL Web-Store id. A key-less package is refused, since it
|
|
288
301
|
// would take a path-derived id, identical across the fleet.
|
|
289
302
|
/** The library, newest first. */
|
|
290
303
|
listLibraryExtensions(signal) {
|
|
@@ -297,7 +310,7 @@ var ExtensionsApi = class extends ClientCore {
|
|
|
297
310
|
uploadExtension(crx) {
|
|
298
311
|
return this.request("POST", "/v1/extensions", crx);
|
|
299
312
|
}
|
|
300
|
-
/** One library entry
|
|
313
|
+
/** One library entry: the most recently added version. */
|
|
301
314
|
getLibraryExtension(id, signal) {
|
|
302
315
|
return this.request("GET", `/v1/extensions/${enc(id)}`, void 0, { signal });
|
|
303
316
|
}
|
|
@@ -308,7 +321,7 @@ var ExtensionsApi = class extends ClientCore {
|
|
|
308
321
|
/**
|
|
309
322
|
* Remove ONE stocked version.
|
|
310
323
|
*
|
|
311
|
-
* Assignments survive
|
|
324
|
+
* Assignments survive, because they name the extension and not the version, unless this
|
|
312
325
|
* was the last version, in which case they go with it.
|
|
313
326
|
*/
|
|
314
327
|
deleteLibraryExtensionVersion(id, version) {
|
|
@@ -347,14 +360,14 @@ var IdentityApi = class extends ClientCore {
|
|
|
347
360
|
// ── Platform credentials ────────────────────────────────────────────────────
|
|
348
361
|
//
|
|
349
362
|
// Stored logins, so an agent can sign a profile into a platform WITHOUT ever
|
|
350
|
-
// holding the password
|
|
363
|
+
// holding the password: the daemon decrypts and types it. Only `revealCredential`
|
|
351
364
|
// and the bundle calls return plaintext, and every one of them needs the vault
|
|
352
365
|
// password: the bearer token alone is not enough, because the MCP agent layer
|
|
353
366
|
// authenticates with exactly the same one.
|
|
354
367
|
//
|
|
355
368
|
// `platform` is whatever you have: the service's domain ("discord.com"), its
|
|
356
369
|
// name ("Discord") or a host under it ("www.discord.com"). The daemon resolves
|
|
357
|
-
// all of them to ONE key
|
|
370
|
+
// all of them to ONE key (the registrable domain), and every response echoes
|
|
358
371
|
// that key, so `listCredentials` and a later `deleteCredential` agree. Pass a
|
|
359
372
|
// whole sign-in URL only to the MCP tools: here the platform is a path segment,
|
|
360
373
|
// and a `/` in it is refused rather than read as a path.
|
|
@@ -370,7 +383,7 @@ var IdentityApi = class extends ClientCore {
|
|
|
370
383
|
/**
|
|
371
384
|
* Store or update one login.
|
|
372
385
|
*
|
|
373
|
-
* An omitted secret keeps its stored value rather than clearing it
|
|
386
|
+
* An omitted secret keeps its stored value rather than clearing it, because an edit form
|
|
374
387
|
* never saw the password in the clear, so it cannot resend one; delete the row to
|
|
375
388
|
* remove a credential. `totp_secret` may be pasted exactly as a site presents it
|
|
376
389
|
* (grouped, lower case): it is normalised and test-decoded by the daemon, so a
|
|
@@ -403,7 +416,7 @@ var IdentityApi = class extends ClientCore {
|
|
|
403
416
|
*
|
|
404
417
|
* `password` is deliberately separate from the vault password: a backup gets
|
|
405
418
|
* handed to another machine, and that must not also hand over daemon access. This
|
|
406
|
-
* is the answer to a lost master key
|
|
419
|
+
* is the answer to a lost master key: a daemon-generated password exists nowhere
|
|
407
420
|
* else.
|
|
408
421
|
*/
|
|
409
422
|
exportCredentials(profileId, vaultPassword, password) {
|
|
@@ -475,6 +488,72 @@ var IdentityApi = class extends ClientCore {
|
|
|
475
488
|
deletePasskey(profileId, credentialId) {
|
|
476
489
|
return this.request("DELETE", `/v1/profiles/${enc(profileId)}/passkeys/${enc(credentialId)}`);
|
|
477
490
|
}
|
|
491
|
+
// ── Secret Layer ───────────────────────────────────────────────────────────
|
|
492
|
+
//
|
|
493
|
+
// Values a profile holds that no agent ever reads. Nothing here returns a
|
|
494
|
+
// value, and that is not an oversight: the wrapper below starts the command
|
|
495
|
+
// INSIDE the daemon precisely so that no route has to hand plaintext out.
|
|
496
|
+
/** The entries this profile keeps, with the placeholder that stands for each. */
|
|
497
|
+
listSecrets(profileId, signal) {
|
|
498
|
+
return this.request(
|
|
499
|
+
"GET",
|
|
500
|
+
`/v1/profiles/${enc(profileId)}/secrets`,
|
|
501
|
+
void 0,
|
|
502
|
+
{ signal }
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
/** Change a title, a binding, or the state. Never the label. */
|
|
506
|
+
updateSecret(profileId, secretId, body) {
|
|
507
|
+
return this.request(
|
|
508
|
+
"PATCH",
|
|
509
|
+
`/v1/profiles/${enc(profileId)}/secrets/${enc(secretId)}`,
|
|
510
|
+
body
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
/** Delete an entry. The reply names the runs it appeared in. */
|
|
514
|
+
deleteSecret(profileId, secretId) {
|
|
515
|
+
return this.request(
|
|
516
|
+
"DELETE",
|
|
517
|
+
`/v1/profiles/${enc(profileId)}/secrets/${enc(secretId)}`
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Filter a text you hold against this profile's values.
|
|
522
|
+
*
|
|
523
|
+
* The SDK path exists because censoring only makes sense where a MODEL is
|
|
524
|
+
* reading: your own code is not, and a placeholder in the middle of it would
|
|
525
|
+
* break parsing. So the daemon offers the filter as a call rather than
|
|
526
|
+
* applying it to a channel it does not own.
|
|
527
|
+
*/
|
|
528
|
+
scrubText(profileId, text) {
|
|
529
|
+
return this.request(
|
|
530
|
+
"POST",
|
|
531
|
+
`/v1/profiles/${enc(profileId)}/secrets/scrub`,
|
|
532
|
+
{ text }
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Run a command with named values in its environment.
|
|
537
|
+
*
|
|
538
|
+
* The daemon starts the child, so no value crosses a socket. At least one
|
|
539
|
+
* `env` entry is required: without the rule an `npm publish` would carry every
|
|
540
|
+
* value of the profile.
|
|
541
|
+
*/
|
|
542
|
+
runWithSecrets(profileId, body) {
|
|
543
|
+
return this.request(
|
|
544
|
+
"POST",
|
|
545
|
+
`/v1/profiles/${enc(profileId)}/secrets/run`,
|
|
546
|
+
body
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
/** Is the secret layer on? It is by default. */
|
|
550
|
+
getSecretLayer(signal) {
|
|
551
|
+
return this.request("GET", "/v1/secret-layer", void 0, { signal });
|
|
552
|
+
}
|
|
553
|
+
/** Turn the secret layer on or off for the whole daemon. */
|
|
554
|
+
setSecretLayer(enabled) {
|
|
555
|
+
return this.request("POST", "/v1/secret-layer", { enabled });
|
|
556
|
+
}
|
|
478
557
|
// ── Cookies ────────────────────────────────────────────────────────────────
|
|
479
558
|
/**
|
|
480
559
|
* The one door a cookie VALUE leaves through, and it needs the vault password.
|
|
@@ -501,7 +580,7 @@ var RunsApi = class extends ClientCore {
|
|
|
501
580
|
getRun(runId, signal) {
|
|
502
581
|
return this.request("GET", `/v1/runs/${enc(runId)}`, void 0, { signal });
|
|
503
582
|
}
|
|
504
|
-
/** A run's steps, oldest first
|
|
583
|
+
/** A run's steps, oldest first: the order they happened in. */
|
|
505
584
|
listRunSteps(runId, params = {}, signal) {
|
|
506
585
|
return this.request("GET", `/v1/runs/${enc(runId)}/steps`, void 0, {
|
|
507
586
|
query: { limit: params.limit, offset: params.offset },
|
|
@@ -706,7 +785,7 @@ var CdpConnection = class {
|
|
|
706
785
|
return () => this.#subscribers.delete(cb);
|
|
707
786
|
}
|
|
708
787
|
/** Register a callback fired once when the connection closes (immediately if
|
|
709
|
-
* it already has)
|
|
788
|
+
* it already has); event streams use this to end instead of hanging. */
|
|
710
789
|
onClose(cb) {
|
|
711
790
|
if (this.#closeReason) {
|
|
712
791
|
cb();
|
|
@@ -990,7 +1069,7 @@ var ScalebrowserClient = class extends ClientCore {
|
|
|
990
1069
|
});
|
|
991
1070
|
}
|
|
992
1071
|
/**
|
|
993
|
-
* Every id matching `params`, unpaged
|
|
1072
|
+
* Every id matching `params`, unpaged: what "act on all matches" needs.
|
|
994
1073
|
*
|
|
995
1074
|
* `listProfiles` is paged, so acting on "everything" from it only ever covers the
|
|
996
1075
|
* page you fetched. Asking for ids costs one request and no persona payloads.
|
|
@@ -1026,6 +1105,14 @@ var ScalebrowserClient = class extends ClientCore {
|
|
|
1026
1105
|
stopProfile(id) {
|
|
1027
1106
|
return this.request("POST", `/v1/profiles/${enc(id)}/stop`, {});
|
|
1028
1107
|
}
|
|
1108
|
+
/** Ask for a profile that is open on another machine of this account.
|
|
1109
|
+
*
|
|
1110
|
+
* Closes nothing here and nothing there: the control plane revokes the holder's
|
|
1111
|
+
* right, the holder closes its own browser at its next heartbeat. Start only
|
|
1112
|
+
* after this answered `free`. Errors: 4012 when it is held elsewhere. */
|
|
1113
|
+
takeoverProfile(id) {
|
|
1114
|
+
return this.request("POST", `/v1/profiles/${enc(id)}/takeover`, {});
|
|
1115
|
+
}
|
|
1029
1116
|
// ── Bulk (PRD §D2) ───────────────────────────────────────────────────────
|
|
1030
1117
|
bulkCreateProfiles(body) {
|
|
1031
1118
|
return this.request("POST", "/v1/bulk/profiles", body);
|
|
@@ -1123,7 +1210,7 @@ var ScalebrowserClient = class extends ClientCore {
|
|
|
1123
1210
|
return this.request("POST", `/v1/proxies/${enc(id)}/check`, {});
|
|
1124
1211
|
}
|
|
1125
1212
|
/**
|
|
1126
|
-
* Probe a proxy config WITHOUT persisting it
|
|
1213
|
+
* Probe a proxy config WITHOUT persisting it: validate before you commit.
|
|
1127
1214
|
* Pass `id` (and omit the credentials) to probe an existing proxy's stored,
|
|
1128
1215
|
* write-only credentials in an edit form.
|
|
1129
1216
|
*/
|
|
@@ -1234,6 +1321,6 @@ var PROXY_KINDS = ["http", "socks5", "gateway"];
|
|
|
1234
1321
|
var ROTATIONS = ["sticky", "rotating"];
|
|
1235
1322
|
|
|
1236
1323
|
// src/index.ts
|
|
1237
|
-
var VERSION = "0.
|
|
1324
|
+
var VERSION = "0.3.0";
|
|
1238
1325
|
|
|
1239
1326
|
export { ApiError, CdpError, CdpSession, DEFAULT_CAPACITY, ErrorCode, GEO_MODES, HOST_MODES, NetworkError, PROXY_KINDS, ROTATIONS, RUNTIME_STATES, ScalebrowserClient, ScalebrowserError, VERSION, deriveMetricsFromProfiles, errorMessage, eventStream, openCdpSession, parseSseFrame, subscribeEvents };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scalebrowser/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Official Node/TypeScript SDK for the Scalebrowser daemon — typed REST client + direct-CDP driver plane (nodriver-style, not Playwright).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|