@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 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].schedule_id);
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(scheduleId, opts?) -> Browser`
56
+ ### `client.rent(browserId, opts?) -> Browser`
57
57
 
58
- Rent a browser by schedule ID. Waits up to 60s for a match. Options:
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(scheduleId);
118
+ const browser = await client.rent(browserId);
119
119
 
120
120
  // Explicit profile
121
- const browser = await client.rent(scheduleId, { human: 'careful' });
121
+ const browser = await client.rent(browserId, { human: 'careful' });
122
122
 
123
123
  // Disable humanization
124
- const browser = await client.rent(scheduleId, { human: null });
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
- SCHEDULE=$(ceki search --limit 1 | jq -r '.[0].schedule_id')
172
- SID=$(ceki rent --schedule $SCHEDULE | jq -r .session_id)
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 --schedule ID [--mode incognito\|main] [--fingerprint-from FILE]` | Rent a browser |
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.schedule_id;
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
- schedule_id: this.scheduleId,
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
- async upload(selector, source, filename) {
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: 'application/octet-stream'});
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:<scheduleId>` or eventId
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(scheduleId, opts) {
1470
- const rentMsg = { type: "rent", browser_id: scheduleId };
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:${scheduleId}`;
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
- scheduleId,
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 scheduleId = Number(msg.schedule_id ?? 0);
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:${scheduleId}`);
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.scheduleId}`;
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
- schedule_id: scheduleId,
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
- schedule_id: Number(msg.schedule_id ?? 0),
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 scheduleId = null;
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] === "--schedule" && args[i + 1]) scheduleId = parseInt(args[++i], 10);
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 (scheduleId == null) {
2016
- err("--schedule is required", "args");
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(scheduleId, { human: null, fingerprint: fpData, mode });
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
- schedule_id: browser.scheduleId,
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
- schedule_id: browser.scheduleId
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) + "SCHEDULE".padEnd(10) + "STARTED".padEnd(22) + "DURATION".padEnd(10) + "EARNED".padEnd(9) + "STATUS".padEnd(10) + "RENTER".padEnd(16) + "PROVIDER";
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.schedule_id).padEnd(10) + started.padEnd(22) + dur.padEnd(10) + earned.padEnd(9) + s.status.padEnd(10) + renter.padEnd(16) + provider;
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 --schedule N [--fingerprint-from PATH]
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>"