@ceki/sdk 1.9.2 → 1.11.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 +9 -9
- package/dist/cli.js +50 -31
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +36 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +36 -19
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ import { connect } from '@ceki/sdk';
|
|
|
21
21
|
|
|
22
22
|
const client = await connect(process.env.CEKI_API_KEY!);
|
|
23
23
|
const options = await client.search({ geo: 'US', language: 'en' });
|
|
24
|
-
const browser = await client.rent(options[0].
|
|
24
|
+
const browser = await client.rent(options[0].browser_id);
|
|
25
25
|
|
|
26
26
|
await browser.navigate('https://example.com');
|
|
27
27
|
const snap = await browser.snapshot();
|
|
@@ -53,9 +53,9 @@ Establish a WebSocket connection to the relay. Returns a `Client` instance.
|
|
|
53
53
|
|
|
54
54
|
Search for available browsers. Filters: `geo`, `language`, etc.
|
|
55
55
|
|
|
56
|
-
### `client.rent(
|
|
56
|
+
### `client.rent(browserId, opts?) -> Browser`
|
|
57
57
|
|
|
58
|
-
Rent a browser by
|
|
58
|
+
Rent a browser by browser ID. Waits up to 60s for a match. Options:
|
|
59
59
|
- `human` — `'natural'` (default), `'careful'`, or `null` (no humanization)
|
|
60
60
|
- `maskingMode` — enable masking
|
|
61
61
|
- `fingerprint` — `true`, `false`, or fingerprint object
|
|
@@ -115,13 +115,13 @@ Browser actions include human-like timing by default — delays before/after act
|
|
|
115
115
|
|
|
116
116
|
```typescript
|
|
117
117
|
// Default: natural profile (enabled by default)
|
|
118
|
-
const browser = await client.rent(
|
|
118
|
+
const browser = await client.rent(browserId);
|
|
119
119
|
|
|
120
120
|
// Explicit profile
|
|
121
|
-
const browser = await client.rent(
|
|
121
|
+
const browser = await client.rent(browserId, { human: 'careful' });
|
|
122
122
|
|
|
123
123
|
// Disable humanization
|
|
124
|
-
const browser = await client.rent(
|
|
124
|
+
const browser = await client.rent(browserId, { human: null });
|
|
125
125
|
```
|
|
126
126
|
|
|
127
127
|
### Environment overrides
|
|
@@ -168,8 +168,8 @@ npm install -g @ceki/sdk
|
|
|
168
168
|
```bash
|
|
169
169
|
export CEKI_API_KEY=ag_...
|
|
170
170
|
|
|
171
|
-
|
|
172
|
-
SID=$(ceki rent --
|
|
171
|
+
BROWSER=$(ceki search --limit 1 | jq -r '.[0].browser_id')
|
|
172
|
+
SID=$(ceki rent --browser $BROWSER | jq -r .session_id)
|
|
173
173
|
ceki navigate $SID https://example.com
|
|
174
174
|
ceki snapshot $SID -o snap.png
|
|
175
175
|
ceki stop $SID
|
|
@@ -185,7 +185,7 @@ The CLI persists session state locally — after `rent` it saves the session ID
|
|
|
185
185
|
|---|---|
|
|
186
186
|
| `search [--limit N] [--filter K=V]…` | List available browsers |
|
|
187
187
|
| `my-browsers` | List browsers with pre-arranged rent contracts |
|
|
188
|
-
| `rent --
|
|
188
|
+
| `rent --browser ID [--mode incognito\|main] [--fingerprint-from FILE]` | Rent a browser |
|
|
189
189
|
| `sessions [--all] [--limit N] [--json]` | List your sessions |
|
|
190
190
|
| `stop SID` | End a session |
|
|
191
191
|
| `wait SID` | Block until the session ends |
|
package/dist/cli.js
CHANGED
|
@@ -125,6 +125,9 @@ var ChatSendFailed = class extends CekiBrowserError {
|
|
|
125
125
|
}
|
|
126
126
|
};
|
|
127
127
|
|
|
128
|
+
// src/browser.ts
|
|
129
|
+
import mime from "mime-types";
|
|
130
|
+
|
|
128
131
|
// src/chat.ts
|
|
129
132
|
import * as crypto from "crypto";
|
|
130
133
|
import * as fs from "fs";
|
|
@@ -190,7 +193,7 @@ var BrowserChat = class {
|
|
|
190
193
|
if (buf.length > MAX_IMAGE_SIZE) {
|
|
191
194
|
throw new Error(`Image too large: ${buf.length} bytes (max ${MAX_IMAGE_SIZE})`);
|
|
192
195
|
}
|
|
193
|
-
const { mime, ext } = detectMime(buf);
|
|
196
|
+
const { mime: mime2, ext } = detectMime(buf);
|
|
194
197
|
if (!filename.includes(".")) {
|
|
195
198
|
filename = `${filename}.${ext}`;
|
|
196
199
|
}
|
|
@@ -201,7 +204,7 @@ var BrowserChat = class {
|
|
|
201
204
|
session_id: this._browser.sessionId,
|
|
202
205
|
client_msg_id: clientMsgId,
|
|
203
206
|
filename,
|
|
204
|
-
mime,
|
|
207
|
+
mime: mime2,
|
|
205
208
|
data_b64
|
|
206
209
|
};
|
|
207
210
|
if (text) msg.text = text;
|
|
@@ -557,10 +560,9 @@ function keymapForChar(char) {
|
|
|
557
560
|
}
|
|
558
561
|
|
|
559
562
|
// src/browser.ts
|
|
560
|
-
var Browser = class {
|
|
563
|
+
var Browser = class _Browser {
|
|
561
564
|
sessionId;
|
|
562
565
|
browserId;
|
|
563
|
-
scheduleId;
|
|
564
566
|
chatTopicId;
|
|
565
567
|
browserInfo;
|
|
566
568
|
providerUserId;
|
|
@@ -606,8 +608,7 @@ var Browser = class {
|
|
|
606
608
|
constructor(client, match, humanizer) {
|
|
607
609
|
this._client = client;
|
|
608
610
|
this.sessionId = match.session_id;
|
|
609
|
-
this.browserId = match.
|
|
610
|
-
this.scheduleId = match.schedule_id;
|
|
611
|
+
this.browserId = match.browser_id;
|
|
611
612
|
this.chatTopicId = match.chat_topic_id ?? null;
|
|
612
613
|
this.browserInfo = match.browser_info ?? {};
|
|
613
614
|
this.providerUserId = match.provider_user_id ?? null;
|
|
@@ -622,7 +623,7 @@ var Browser = class {
|
|
|
622
623
|
saveSession(this.sessionId, {
|
|
623
624
|
session_id: this.sessionId,
|
|
624
625
|
chat_topic_id: this.chatTopicId,
|
|
625
|
-
|
|
626
|
+
browser_id: this.browserId,
|
|
626
627
|
last_seen_ts: this._lastSeenTs
|
|
627
628
|
});
|
|
628
629
|
}
|
|
@@ -797,7 +798,10 @@ var Browser = class {
|
|
|
797
798
|
ts: /* @__PURE__ */ new Date()
|
|
798
799
|
};
|
|
799
800
|
}
|
|
800
|
-
|
|
801
|
+
static _detectMime(filename) {
|
|
802
|
+
return mime.lookup(filename) || "application/octet-stream";
|
|
803
|
+
}
|
|
804
|
+
async upload(selector, source, filename, mime2) {
|
|
801
805
|
let buf;
|
|
802
806
|
let resolvedFilename;
|
|
803
807
|
if (typeof source === "string") {
|
|
@@ -809,6 +813,8 @@ var Browser = class {
|
|
|
809
813
|
buf = Buffer.isBuffer(source) ? source : Buffer.from(source);
|
|
810
814
|
resolvedFilename = filename ?? "file";
|
|
811
815
|
}
|
|
816
|
+
const mimeType = mime2 ?? _Browser._detectMime(resolvedFilename);
|
|
817
|
+
console.info(`upload: file=${resolvedFilename} mime=${mimeType} size=${buf.length}`);
|
|
812
818
|
const b64 = buf.toString("base64");
|
|
813
819
|
const size = buf.length;
|
|
814
820
|
const expression = `
|
|
@@ -819,7 +825,7 @@ var Browser = class {
|
|
|
819
825
|
var binary = atob(b64);
|
|
820
826
|
var bytes = new Uint8Array(binary.length);
|
|
821
827
|
for (var i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
822
|
-
var file = new File([bytes], ${JSON.stringify(resolvedFilename)}, {type:
|
|
828
|
+
var file = new File([bytes], ${JSON.stringify(resolvedFilename)}, {type: ${JSON.stringify(mimeType)}});
|
|
823
829
|
var dt = new DataTransfer();
|
|
824
830
|
dt.items.add(file);
|
|
825
831
|
input.files = dt.files;
|
|
@@ -832,6 +838,17 @@ var Browser = class {
|
|
|
832
838
|
params: { expression, returnByValue: true }
|
|
833
839
|
});
|
|
834
840
|
const resultObj = result?.result;
|
|
841
|
+
try {
|
|
842
|
+
await this.send({
|
|
843
|
+
method: "Input.dispatchKeyEvent",
|
|
844
|
+
params: { type: "keyDown", key: "Escape", code: "Escape", windowsVirtualKeyCode: 27, nativeVirtualKeyCode: 27 }
|
|
845
|
+
});
|
|
846
|
+
await this.send({
|
|
847
|
+
method: "Input.dispatchKeyEvent",
|
|
848
|
+
params: { type: "keyUp", key: "Escape", code: "Escape", windowsVirtualKeyCode: 27, nativeVirtualKeyCode: 27 }
|
|
849
|
+
});
|
|
850
|
+
} catch {
|
|
851
|
+
}
|
|
835
852
|
if (resultObj?.value) {
|
|
836
853
|
return JSON.parse(String(resultObj.value));
|
|
837
854
|
}
|
|
@@ -1375,7 +1392,7 @@ var Client = class _Client {
|
|
|
1375
1392
|
_pongTimer = null;
|
|
1376
1393
|
_lastPongAt = 0;
|
|
1377
1394
|
_pendingRents = /* @__PURE__ */ new Map();
|
|
1378
|
-
// keyed by `rent:<
|
|
1395
|
+
// keyed by `rent:<browserId>` or eventId
|
|
1379
1396
|
_pendingResumes = /* @__PURE__ */ new Map();
|
|
1380
1397
|
// keyed by sessionId
|
|
1381
1398
|
_connectResolve = null;
|
|
@@ -1466,18 +1483,18 @@ var Client = class _Client {
|
|
|
1466
1483
|
const items = body.browsers ?? body.data ?? body;
|
|
1467
1484
|
return Array.isArray(items) ? items : [];
|
|
1468
1485
|
}
|
|
1469
|
-
async rent(
|
|
1470
|
-
const rentMsg = { type: "rent", browser_id:
|
|
1486
|
+
async rent(browserId, opts) {
|
|
1487
|
+
const rentMsg = { type: "rent", browser_id: browserId };
|
|
1471
1488
|
if (opts?.mode) rentMsg.mode = opts.mode;
|
|
1472
1489
|
this._wsSend(rentMsg);
|
|
1473
|
-
const key = `rent:${
|
|
1490
|
+
const key = `rent:${browserId}`;
|
|
1474
1491
|
return new Promise((resolve2, reject) => {
|
|
1475
1492
|
const timer = setTimeout(() => {
|
|
1476
1493
|
this._pendingRents.delete(key);
|
|
1477
1494
|
reject(new TimeoutError("Rent timed out after 90s"));
|
|
1478
1495
|
}, 9e4);
|
|
1479
1496
|
this._pendingRents.set(key, {
|
|
1480
|
-
|
|
1497
|
+
browserId,
|
|
1481
1498
|
eventId: null,
|
|
1482
1499
|
opts,
|
|
1483
1500
|
resolve: (match) => {
|
|
@@ -1782,7 +1799,7 @@ var Client = class _Client {
|
|
|
1782
1799
|
}
|
|
1783
1800
|
_onMatch(msg) {
|
|
1784
1801
|
const eventId = String(msg.event_id ?? "");
|
|
1785
|
-
const
|
|
1802
|
+
const browserId = Number(msg.browser_id ?? 0);
|
|
1786
1803
|
const sessionId = String(msg.session_id ?? "");
|
|
1787
1804
|
if (msg.requires_ack) {
|
|
1788
1805
|
try {
|
|
@@ -1792,15 +1809,15 @@ var Client = class _Client {
|
|
|
1792
1809
|
}
|
|
1793
1810
|
let pending = this._pendingRents.get(`event:${eventId}`);
|
|
1794
1811
|
if (!pending) {
|
|
1795
|
-
pending = this._pendingRents.get(`rent:${
|
|
1812
|
+
pending = this._pendingRents.get(`rent:${browserId}`);
|
|
1796
1813
|
}
|
|
1797
1814
|
if (pending) {
|
|
1798
1815
|
clearTimeout(pending.timer);
|
|
1799
|
-
const key = pending.eventId ? `event:${pending.eventId}` : `rent:${pending.
|
|
1816
|
+
const key = pending.eventId ? `event:${pending.eventId}` : `rent:${pending.browserId}`;
|
|
1800
1817
|
this._pendingRents.delete(key);
|
|
1801
1818
|
const match = {
|
|
1802
1819
|
session_id: sessionId,
|
|
1803
|
-
|
|
1820
|
+
browser_id: browserId,
|
|
1804
1821
|
event_id: eventId || null,
|
|
1805
1822
|
chat_topic_id: msg.chat_topic_id ? String(msg.chat_topic_id) : null,
|
|
1806
1823
|
provider_user_id: msg.provider_user_id != null ? Number(msg.provider_user_id) : null,
|
|
@@ -1842,7 +1859,7 @@ var Client = class _Client {
|
|
|
1842
1859
|
const match = {
|
|
1843
1860
|
session_id: sessionId,
|
|
1844
1861
|
event_id: msg.event_id != null ? String(msg.event_id) : null,
|
|
1845
|
-
|
|
1862
|
+
browser_id: Number(msg.browser_id ?? 0),
|
|
1846
1863
|
chat_topic_id: msg.chat_topic_id ? String(msg.chat_topic_id) : null,
|
|
1847
1864
|
provider_user_id: msg.provider_user_id != null ? Number(msg.provider_user_id) : null,
|
|
1848
1865
|
started_at: Date.now(),
|
|
@@ -1997,11 +2014,11 @@ function parseBool(val) {
|
|
|
1997
2014
|
return val === "true" || val === "1" || val === "yes";
|
|
1998
2015
|
}
|
|
1999
2016
|
async function cmdRent(args) {
|
|
2000
|
-
let
|
|
2017
|
+
let browserId = null;
|
|
2001
2018
|
let fingerprintFrom = null;
|
|
2002
2019
|
let mode = "incognito";
|
|
2003
2020
|
for (let i = 0; i < args.length; i++) {
|
|
2004
|
-
if (args[i] === "--
|
|
2021
|
+
if (args[i] === "--browser" && args[i + 1]) browserId = parseInt(args[++i], 10);
|
|
2005
2022
|
if (args[i] === "--fingerprint-from" && args[i + 1]) fingerprintFrom = args[++i];
|
|
2006
2023
|
if (args[i] === "--mode" && args[i + 1]) {
|
|
2007
2024
|
const v = args[++i];
|
|
@@ -2012,8 +2029,8 @@ async function cmdRent(args) {
|
|
|
2012
2029
|
mode = v;
|
|
2013
2030
|
}
|
|
2014
2031
|
}
|
|
2015
|
-
if (
|
|
2016
|
-
err("--
|
|
2032
|
+
if (browserId == null) {
|
|
2033
|
+
err("--browser is required", "args");
|
|
2017
2034
|
process.exit(1);
|
|
2018
2035
|
}
|
|
2019
2036
|
const apiKey = getApiKey();
|
|
@@ -2024,17 +2041,17 @@ async function cmdRent(args) {
|
|
|
2024
2041
|
}
|
|
2025
2042
|
const client = await connect(apiKey, connectOptions());
|
|
2026
2043
|
try {
|
|
2027
|
-
const browser = await client.rent(
|
|
2044
|
+
const browser = await client.rent(browserId, { human: null, fingerprint: fpData, mode });
|
|
2028
2045
|
saveSession(browser.sessionId, {
|
|
2029
2046
|
session_id: browser.sessionId,
|
|
2030
2047
|
chat_topic_id: browser.chatTopicId,
|
|
2031
|
-
|
|
2048
|
+
browser_id: browser.browserId,
|
|
2032
2049
|
last_seen_ts: null
|
|
2033
2050
|
});
|
|
2034
2051
|
out({
|
|
2035
2052
|
session_id: browser.sessionId,
|
|
2036
2053
|
chat_topic_id: browser.chatTopicId,
|
|
2037
|
-
|
|
2054
|
+
browser_id: browser.browserId
|
|
2038
2055
|
});
|
|
2039
2056
|
} finally {
|
|
2040
2057
|
await closeClient(client);
|
|
@@ -2079,7 +2096,7 @@ async function cmdSessions(args) {
|
|
|
2079
2096
|
process.stdout.write("No sessions found.\n");
|
|
2080
2097
|
return;
|
|
2081
2098
|
}
|
|
2082
|
-
const header = "SID".padEnd(8) + "
|
|
2099
|
+
const header = "SID".padEnd(8) + "BROWSER".padEnd(10) + "STARTED".padEnd(22) + "DURATION".padEnd(10) + "EARNED".padEnd(9) + "STATUS".padEnd(10) + "RENTER".padEnd(16) + "PROVIDER";
|
|
2083
2100
|
process.stdout.write(header + "\n");
|
|
2084
2101
|
for (const s of results) {
|
|
2085
2102
|
const started = s.started_at ?? "\u2014";
|
|
@@ -2089,7 +2106,7 @@ async function cmdSessions(args) {
|
|
|
2089
2106
|
const earned = `$${s.earned.toFixed(2)}`;
|
|
2090
2107
|
const renter = s.renter?.name ?? "\u2014";
|
|
2091
2108
|
const provider = s.provider?.name ?? "\u2014";
|
|
2092
|
-
const line = String(s.id).padEnd(8) + String(s.
|
|
2109
|
+
const line = String(s.id).padEnd(8) + String(s.browser_id).padEnd(10) + started.padEnd(22) + dur.padEnd(10) + earned.padEnd(9) + s.status.padEnd(10) + renter.padEnd(16) + provider;
|
|
2093
2110
|
process.stdout.write(line + "\n");
|
|
2094
2111
|
}
|
|
2095
2112
|
}
|
|
@@ -2482,10 +2499,12 @@ async function cmdUpload(sid, args) {
|
|
|
2482
2499
|
let selector = null;
|
|
2483
2500
|
let filePath = null;
|
|
2484
2501
|
let filename;
|
|
2502
|
+
let mime2;
|
|
2485
2503
|
for (let i = 0; i < args.length; i++) {
|
|
2486
2504
|
if (args[i] === "--selector" && args[i + 1]) selector = args[++i];
|
|
2487
2505
|
if (args[i] === "--file" && args[i + 1]) filePath = args[++i];
|
|
2488
2506
|
if (args[i] === "--filename" && args[i + 1]) filename = args[++i];
|
|
2507
|
+
if (args[i] === "--mime" && args[i + 1]) mime2 = args[++i];
|
|
2489
2508
|
}
|
|
2490
2509
|
if (!selector || !filePath) {
|
|
2491
2510
|
err("--selector and --file are required", "args");
|
|
@@ -2498,7 +2517,7 @@ async function cmdUpload(sid, args) {
|
|
|
2498
2517
|
const apiKey = getApiKey();
|
|
2499
2518
|
const [client, browser] = await resumeBrowser(apiKey, sid);
|
|
2500
2519
|
try {
|
|
2501
|
-
const result = await browser.upload(selector, filePath, filename);
|
|
2520
|
+
const result = await browser.upload(selector, filePath, filename, mime2);
|
|
2502
2521
|
out(result);
|
|
2503
2522
|
} finally {
|
|
2504
2523
|
await closeClient(client);
|
|
@@ -2510,7 +2529,7 @@ function printHelp() {
|
|
|
2510
2529
|
Usage: ceki <command> [options]
|
|
2511
2530
|
|
|
2512
2531
|
Commands:
|
|
2513
|
-
rent --
|
|
2532
|
+
rent --browser N [--fingerprint-from PATH]
|
|
2514
2533
|
my-browsers
|
|
2515
2534
|
search [--limit N] [--filter k=v]...
|
|
2516
2535
|
snapshot <sid> -o PATH
|
|
@@ -2522,7 +2541,7 @@ Commands:
|
|
|
2522
2541
|
switch-tab <sid>
|
|
2523
2542
|
configure <sid> [--masking-mode true|false] [--fingerprint true|false]
|
|
2524
2543
|
cdp <sid> --method <M> [--params JSON]
|
|
2525
|
-
upload <sid> --selector CSS --file PATH [--filename NAME]
|
|
2544
|
+
upload <sid> --selector CSS --file PATH [--filename NAME] [--mime TYPE]
|
|
2526
2545
|
request-captcha <sid> [--acceptance N] [--completion M] [--manual]
|
|
2527
2546
|
wait <sid>
|
|
2528
2547
|
chat <sid> send "<text>"
|