@ceki/n8n-nodes-ceki 0.2.8 → 0.2.9

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Rent real human browsers inside your n8n workflows.**
4
4
 
5
- [Ceki](https://browser.ceki.me) lets AI agents and automations rent live, human-operated browsers by the minute — real fingerprints, real IPs, real humans solving captchas. This package brings Ceki into n8n as native nodes: drive a rented browser, scrape anti-bot pages, screenshot from any geo, and orchestrate Ceki's contract system where a workflow can hire agents and people.
5
+ [Ceki](https://ceki.me) lets AI agents and automations rent live, human-operated browsers by the minute — real fingerprints, real IPs, real humans solving captchas. This package brings Ceki into n8n as native nodes: drive a rented browser, scrape anti-bot pages, screenshot from any geo, and orchestrate Ceki's contract system where a workflow can hire agents and people.
6
6
 
7
7
  - **Real human browsers** — not headless, not emulation. Actual Chrome sessions with genuine fingerprints that pass anti-bot checks.
8
8
  - **Geo-targeting** — appear from RU, EE, US, or wherever a human host is online.
@@ -10,7 +10,7 @@
10
10
  - **Pay-as-you-go** — from **$0.01/min**. Pay only for the minutes you use; close the session and billing stops.
11
11
  - **Hire from a workflow** — the Ceki Contract node lets your automation create tasks, assign them to agents or humans, and escalate when it's stuck.
12
12
 
13
- All of the logic lives in [`@ceki/sdk`](https://browser.ceki.me/docs) — these nodes are a thin, typed UI on top.
13
+ All of the logic lives in [`@ceki/sdk`](https://github.com/Ceki-me/js-sdk) — these nodes are a thin, typed UI on top.
14
14
 
15
15
  ## Install
16
16
 
@@ -23,7 +23,7 @@ npm install n8n-nodes-ceki
23
23
 
24
24
  ## Credential
25
25
 
26
- Create it once: **Ceki API** → set `token` to your agent token (`ag_...`) from the [Ceki panel](https://browser.ceki.me). One credential powers every node.
26
+ Create it once: **Ceki API** → set `token` to your agent token (`ag_...`) from the [Ceki panel](https://ceki.me/dashboard/agents). One credential powers every node.
27
27
 
28
28
  ## Nodes
29
29
 
@@ -111,9 +111,9 @@ To use locally in n8n, link `dist/` via `N8N_CUSTOM_EXTENSIONS` or `NODE_PATH`.
111
111
 
112
112
  - [browser.ceki.me](https://browser.ceki.me) — rent a browser or become a host
113
113
  - [Docs](https://browser.ceki.me/docs) — API key, SDK reference, recipes
114
- - [`@ceki/sdk`](https://browser.ceki.me/docs) — the engine behind these nodes
115
- - [MCP](https://browser.ceki.me/mcp) — drive Ceki from any MCP-compatible agent
114
+ - [`@ceki/sdk`](https://github.com/Ceki-me/js-sdk) — the engine behind these nodes
115
+ - [MCP](https://ceki.me/mcp) — drive Ceki from any MCP-compatible agent
116
116
 
117
117
  ## License
118
118
 
119
- MIT. Depends on [`@ceki/sdk`](https://browser.ceki.me/docs).
119
+ MIT. Depends on [`@ceki/sdk`](https://github.com/Ceki-me/js-sdk).
package/dist/index.js CHANGED
@@ -121,53 +121,17 @@ var CekiClient = class {
121
121
  this._sendRaw({ type: "resume", session_id: sessionId });
122
122
  return this._awaitResume(sessionId);
123
123
  }
124
- /** Close the WS connection — session stays alive in grace.
125
- * Waits for the close handshake so the relay processes the close
126
- * before a new connection with the same renterId is established
127
- * (avoids the close-handler race condition).
128
- */
129
- async disconnect() {
124
+ /** Close the WS connection — session stays alive in grace. */
125
+ disconnect() {
130
126
  this._closed = true;
131
127
  this._activeSessions.clear();
132
128
  this._pendingRents.clear();
133
129
  this._pendingResumes.clear();
134
- if (!this._ws) return;
135
- const ws = this._ws;
136
- if (ws.readyState === WebSocket.CLOSED) {
137
- this._ws = null;
138
- return;
139
- }
140
- if (ws.readyState === WebSocket.CLOSING) {
141
- await new Promise((r) => {
142
- ws.onclose = () => {
143
- this._connected = false;
144
- r();
145
- };
146
- });
147
- this._ws = null;
148
- return;
149
- }
150
- await new Promise((r) => {
151
- const t = setTimeout(() => {
152
- r();
153
- }, 5e3);
154
- ws.onclose = () => {
155
- this._connected = false;
156
- clearTimeout(t);
157
- r();
158
- };
159
- try {
160
- ws.close();
161
- } catch {
162
- clearTimeout(t);
163
- r();
164
- }
165
- });
166
- this._ws = null;
130
+ this._closeWs();
167
131
  }
168
132
  /** Close everything. */
169
- async close() {
170
- await this.disconnect();
133
+ close() {
134
+ this.disconnect();
171
135
  }
172
136
  // ── private ────────────────────────────────────────────────
173
137
  _awaitRent(key, scheduleId, timeoutMs) {
@@ -257,33 +221,13 @@ var CekiClient = class {
257
221
  _onMatch(msg) {
258
222
  const scheduleId = Number(msg.schedule_id ?? 0);
259
223
  const eventId = msg.event_id ? String(msg.event_id) : null;
260
- const sessionId = String(msg.session_id ?? "");
261
- if (sessionId && this._ws?.readyState === WebSocket.OPEN) {
262
- try {
263
- this._ws.send(JSON.stringify({ type: "match_ack", session_id: sessionId }));
264
- } catch {
265
- }
266
- }
267
224
  let pending = this._pendingRents.get(`event:${eventId}`);
268
225
  if (!pending) pending = this._pendingRents.get(`rent:${scheduleId}`);
269
226
  if (pending) {
270
227
  this._pendingRents.delete(`event:${eventId}`);
271
228
  this._pendingRents.delete(`rent:${scheduleId}`);
272
229
  pending.resolve({
273
- session_id: sessionId,
274
- schedule_id: scheduleId,
275
- event_id: eventId,
276
- chat_topic_id: msg.chat_topic_id ? String(msg.chat_topic_id) : null,
277
- provider_user_id: msg.provider_user_id != null ? Number(msg.provider_user_id) : null,
278
- browser_info: msg.browser_info ?? {}
279
- });
280
- return;
281
- }
282
- const resumePending = sessionId ? this._pendingResumes.get(sessionId) : null;
283
- if (resumePending) {
284
- this._pendingResumes.delete(sessionId);
285
- resumePending.resolve({
286
- session_id: sessionId,
230
+ session_id: String(msg.session_id ?? ""),
287
231
  schedule_id: scheduleId,
288
232
  event_id: eventId,
289
233
  chat_topic_id: msg.chat_topic_id ? String(msg.chat_topic_id) : null,
@@ -326,9 +270,9 @@ var CekiClient = class {
326
270
  const browser = this._activeSessions.get(sessionId);
327
271
  if (!browser) return;
328
272
  const id = Number(msg.id ?? 0);
329
- const pending = browser._pendingCdp.get(id);
273
+ const pending = this._pendingCdp.get(id);
330
274
  if (!pending) return;
331
- browser._pendingCdp.delete(id);
275
+ this._pendingCdp.delete(id);
332
276
  if (msg.error) {
333
277
  pending.reject(new Error(String(msg.error.message ?? "CDP error")));
334
278
  } else {
@@ -371,9 +315,9 @@ var CekiBrowser = class {
371
315
  async navigate(url, timeoutMs) {
372
316
  await this.send("Page.navigate", { url }, timeoutMs);
373
317
  }
374
- async click(x2, y2) {
375
- await this.send("Input.dispatchMouseEvent", { type: "mousePressed", x: x2, y: y2, button: "left", clickCount: 1 });
376
- await this.send("Input.dispatchMouseEvent", { type: "mouseReleased", x: x2, y: y2, button: "left", clickCount: 1 });
318
+ async click(x, y) {
319
+ await this.send("Input.dispatchMouseEvent", { type: "mousePressed", x, y, button: "left", clickCount: 1 });
320
+ await this.send("Input.dispatchMouseEvent", { type: "mouseReleased", x, y, button: "left", clickCount: 1 });
377
321
  }
378
322
  async type(text) {
379
323
  await this.send("Ceki.typeText", { text });
@@ -448,7 +392,7 @@ var BrowserCeki = class {
448
392
  icon: "file:ceki.png",
449
393
  group: ["transform"],
450
394
  version: 1,
451
- subtitle: '={{ "Ceki: " + $parameter.operation }}',
395
+ subtitle: '={{ "Ceki: " + $operation }}',
452
396
  description: "Rent a real human browser and control it: rent, navigate, click, type, screenshot, solve captchas, and more",
453
397
  defaults: { name: "Browser Ceki" },
454
398
  inputs: ["main"],
@@ -491,7 +435,7 @@ var BrowserCeki = class {
491
435
  type: "string",
492
436
  default: "",
493
437
  placeholder: "RU, EE, US\u2026",
494
- displayOptions: { show: { operation: ["search"] } }
438
+ displayOptions: { show: { operation: ["rent", "search"] } }
495
439
  },
496
440
  {
497
441
  displayName: "Max $/min",
@@ -499,14 +443,14 @@ var BrowserCeki = class {
499
443
  type: "number",
500
444
  typeOptions: { numberPrecision: 4 },
501
445
  default: 0.02,
502
- displayOptions: { show: { operation: ["search"] } }
446
+ displayOptions: { show: { operation: ["rent", "search"] } }
503
447
  },
504
448
  {
505
449
  displayName: "Min rating",
506
450
  name: "minRating",
507
451
  type: "number",
508
452
  default: 0,
509
- displayOptions: { show: { operation: ["search"] } }
453
+ displayOptions: { show: { operation: ["rent"] } }
510
454
  },
511
455
  {
512
456
  displayName: "Profile mode",
@@ -667,39 +611,28 @@ var BrowserCeki = class {
667
611
  for (let i = 0; i < items.length; i++) {
668
612
  const op = this.getNodeParameter("operation", i);
669
613
  const client = new CekiClient(token);
614
+ await client.connect();
670
615
  let browser;
671
- let needFullClose = false;
672
616
  try {
673
- await client.connect();
674
617
  if (op === "rent") {
675
- try {
676
- const sid2 = await resolveSid(i, client);
677
- const mode = this.getNodeParameter("mode", i);
678
- browser = await client.rent(sid2, { mode });
679
- out.push({
680
- json: { session_id: browser.sessionId, schedule_id: sid2, mode }
681
- });
682
- } catch (e) {
683
- throw new Error(`Rent failed: ${e.message}`);
684
- } finally {
685
- await client.disconnect();
686
- }
618
+ const sid2 = await resolveSid(i, client);
619
+ const mode = this.getNodeParameter("mode", i);
620
+ browser = await client.rent(sid2, { mode });
621
+ out.push({
622
+ json: { session_id: browser.sessionId, schedule_id: sid2, mode }
623
+ });
624
+ await client.disconnect();
687
625
  continue;
688
626
  }
689
627
  if (op === "search") {
690
- try {
691
- const geo = this.getNodeParameter("geo", i);
692
- const maxPrice = this.getNodeParameter("maxPrice", i);
693
- const list = await client.search({
694
- geo: geo || void 0,
695
- max_price_per_min: maxPrice
696
- });
697
- out.push({ json: { browsers: list, count: list.length } });
698
- } catch (e) {
699
- throw new Error(`Search failed: ${e.message}`);
700
- } finally {
701
- await client.disconnect();
702
- }
628
+ const geo = this.getNodeParameter("geo", i);
629
+ const maxPrice = this.getNodeParameter("maxPrice", i);
630
+ const list = await client.search({
631
+ geo: geo || void 0,
632
+ max_price_per_min: maxPrice
633
+ });
634
+ out.push({ json: { browsers: list, count: list.length } });
635
+ await client.disconnect();
703
636
  continue;
704
637
  }
705
638
  if (op === "full") {
@@ -708,11 +641,7 @@ var BrowserCeki = class {
708
641
  if (demoMode) {
709
642
  sid2 = 99999;
710
643
  } else {
711
- try {
712
- sid2 = await resolveSid(i, client);
713
- } catch (e) {
714
- throw new Error(`Full: resolve schedule failed: ${e.message}`);
715
- }
644
+ sid2 = await resolveSid(i, client);
716
645
  }
717
646
  const mode = this.getNodeParameter("mode", i);
718
647
  const url = this.getNodeParameter("url", i);
@@ -725,18 +654,14 @@ var BrowserCeki = class {
725
654
  const demoPng = Buffer.from("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", "base64");
726
655
  binary = await this.helpers.prepareBinaryData(demoPng, "screenshot.png", "image/png");
727
656
  } else {
728
- try {
729
- browser = await client.rent(sid2, { mode });
730
- session_id = browser.sessionId;
731
- await browser.navigate(url);
732
- await sleep(ms);
733
- const shot = await browser.screenshot({ format: "png", fullPage: true });
734
- const data = shot.toString("base64");
735
- binary = await this.helpers.prepareBinaryData(Buffer.from(data, "base64"), "screenshot.png", "image/png");
736
- await browser.close();
737
- } catch (e) {
738
- throw new Error(`Full op failed at step "${browser?.sessionId ? "navigate/screenshot" : "rent"}": ${e.message}`);
739
- }
657
+ browser = await client.rent(sid2, { mode });
658
+ session_id = browser.sessionId;
659
+ await browser.navigate(url);
660
+ await sleep(ms);
661
+ const shot = await browser.screenshot({ format: "png", fullPage: true });
662
+ const data = shot.toString("base64");
663
+ binary = await this.helpers.prepareBinaryData(Buffer.from(data, "base64"), "screenshot.png", "image/png");
664
+ await browser.close();
740
665
  }
741
666
  out.push({
742
667
  json: {
@@ -749,86 +674,57 @@ var BrowserCeki = class {
749
674
  binary: { data: binary }
750
675
  });
751
676
  } finally {
752
- await client.close().catch(() => {
753
- });
677
+ await client.close();
754
678
  }
755
679
  continue;
756
680
  }
757
681
  const sessionId = this.getNodeParameter("sessionId", i);
758
- try {
759
- browser = await client.resume(sessionId);
760
- } catch (e) {
761
- throw new Error(`Resume session "${sessionId}" failed: ${e.message}. Session may have expired or still be in grace.`);
762
- }
682
+ browser = await client.resume(sessionId);
763
683
  const sid = browser.sessionId;
764
684
  switch (op) {
765
685
  case "navigate": {
766
- try {
767
- const url = this.getNodeParameter("url", i);
768
- await browser.navigate(url);
769
- out.push({ json: { session_id: sid, url } });
770
- } catch (e) {
771
- throw new Error(`Navigate failed: ${e.message}`);
772
- }
686
+ const url = this.getNodeParameter("url", i);
687
+ await browser.navigate(url);
688
+ out.push({ json: { session_id: sid, url } });
773
689
  break;
774
690
  }
775
691
  case "click": {
776
- try {
777
- const x2 = this.getNodeParameter("x", i);
778
- const y2 = this.getNodeParameter("y", i);
779
- await browser.click(x2, y2);
780
- out.push({ json: { session_id: sid, clicked: [x2, y2] } });
781
- } catch (e) {
782
- throw new Error(`Click at (${x},${y}) failed: ${e.message}`);
783
- }
692
+ const x = this.getNodeParameter("x", i);
693
+ const y = this.getNodeParameter("y", i);
694
+ await browser.click(x, y);
695
+ out.push({ json: { session_id: sid, clicked: [x, y] } });
784
696
  break;
785
697
  }
786
698
  case "type": {
787
- try {
788
- const text = this.getNodeParameter("text", i);
789
- await browser.type(text);
790
- out.push({ json: { session_id: sid, typed: text } });
791
- } catch (e) {
792
- throw new Error(`Type failed: ${e.message}`);
793
- }
699
+ const text = this.getNodeParameter("text", i);
700
+ await browser.type(text);
701
+ out.push({ json: { session_id: sid, typed: text } });
794
702
  break;
795
703
  }
796
704
  case "scroll": {
797
- try {
798
- const deltaY = this.getNodeParameter("deltaY", i);
799
- await browser.scroll(deltaY);
800
- out.push({ json: { session_id: sid, scrolled: deltaY } });
801
- } catch (e) {
802
- throw new Error(`Scroll failed: ${e.message}`);
803
- }
705
+ const deltaY = this.getNodeParameter("deltaY", i);
706
+ await browser.scroll(deltaY);
707
+ out.push({ json: { session_id: sid, scrolled: deltaY } });
804
708
  break;
805
709
  }
806
710
  case "screenshot": {
807
- try {
808
- const format = this.getNodeParameter("format", i);
809
- const fullPage = this.getNodeParameter("fullPage", i);
810
- const shot = await browser.screenshot({ format, fullPage });
811
- const data = format === "base64" ? shot.data : shot.toString("base64");
812
- const binary = await this.helpers.prepareBinaryData(
813
- Buffer.from(data, "base64"),
814
- "screenshot.png",
815
- "image/png"
816
- );
817
- out.push({ json: { session_id: sid }, binary: { data: binary } });
818
- } catch (e) {
819
- throw new Error(`Screenshot failed: ${e.message}`);
820
- }
711
+ const format = this.getNodeParameter("format", i);
712
+ const fullPage = this.getNodeParameter("fullPage", i);
713
+ const shot = await browser.screenshot({ format, fullPage });
714
+ const data = format === "base64" ? shot.data : shot.toString("base64");
715
+ const binary = await this.helpers.prepareBinaryData(
716
+ Buffer.from(data, "base64"),
717
+ "screenshot.png",
718
+ "image/png"
719
+ );
720
+ out.push({ json: { session_id: sid }, binary: { data: binary } });
821
721
  break;
822
722
  }
823
723
  case "snapshot": {
824
- try {
825
- const snap = await browser.snapshot();
826
- out.push({
827
- json: { session_id: sid, screenshot: snap.screenshot }
828
- });
829
- } catch (e) {
830
- throw new Error(`Snapshot failed: ${e.message}`);
831
- }
724
+ const snap = await browser.snapshot();
725
+ out.push({
726
+ json: { session_id: sid, screenshot: snap.screenshot }
727
+ });
832
728
  break;
833
729
  }
834
730
  case "wait": {
@@ -865,79 +761,30 @@ var BrowserCeki = class {
865
761
  break;
866
762
  }
867
763
  case "upload": {
868
- try {
869
- const selector = this.getNodeParameter("selector", i);
870
- const bpn = this.getNodeParameter("binaryPropertyName", i);
871
- const bin = items[i].binary?.[bpn];
872
- if (!bin) throw new Error(`Binary property "${bpn}" not found on input`);
873
- const stream = await this.helpers.getBinaryStream(bin.id);
874
- const chunks = [];
875
- for await (const c of stream) chunks.push(c);
876
- const buf = Buffer.concat(chunks);
877
- const res = await browser.upload(selector, buf);
878
- out.push({ json: { session_id: sid, uploaded: res } });
879
- } catch (e) {
880
- throw new Error(`Upload failed: ${e.message}`);
881
- }
764
+ const selector = this.getNodeParameter("selector", i);
765
+ const bpn = this.getNodeParameter("binaryPropertyName", i);
766
+ const bin = items[i].binary?.[bpn];
767
+ if (!bin) throw new Error(`Binary property "${bpn}" not found on input`);
768
+ const stream = await this.helpers.getBinaryStream(bin.id);
769
+ const chunks = [];
770
+ for await (const c of stream) chunks.push(c);
771
+ const buf = Buffer.concat(chunks);
772
+ const res = await browser.upload(selector, buf);
773
+ out.push({ json: { session_id: sid, uploaded: res } });
882
774
  break;
883
775
  }
884
776
  case "close": {
885
- try {
886
- await browser.close().catch(() => {
887
- });
888
- } catch {
889
- }
890
- await new Promise((resolve) => {
891
- const stopWs = new WebSocket("wss://browser.ceki.me/ws/agent", [`bearer.${token}`]);
892
- const abortTimer = AbortSignal.timeout(1e4);
893
- const onTimer = () => {
894
- try {
895
- stopWs.close();
896
- } catch {
897
- }
898
- resolve();
899
- };
900
- abortTimer.addEventListener("abort", onTimer, { once: true });
901
- stopWs.onopen = () => {
902
- stopWs.send(JSON.stringify({ type: "stop", session_id: sessionId, reason: "n8n close" }));
903
- };
904
- stopWs.onmessage = (ev) => {
905
- try {
906
- const msg = JSON.parse(ev.data);
907
- if (msg.type === "session_ended") {
908
- abortTimer.removeEventListener("abort", onTimer);
909
- try {
910
- stopWs.close();
911
- } catch {
912
- }
913
- resolve();
914
- }
915
- } catch {
916
- }
917
- };
918
- stopWs.onerror = () => {
919
- abortTimer.removeEventListener("abort", onTimer);
920
- resolve();
921
- };
922
- stopWs.onclose = () => {
923
- abortTimer.removeEventListener("abort", onTimer);
924
- resolve();
925
- };
926
- });
777
+ await browser.close();
927
778
  out.push({ json: { closed: true, session_id: sessionId } });
928
779
  break;
929
780
  }
930
781
  }
931
- needFullClose = op === "close";
932
- } finally {
933
- try {
934
- if (needFullClose) {
935
- await client.close();
936
- } else {
937
- await client.disconnect();
938
- }
939
- } catch {
782
+ if (op === "close") {
783
+ await client.close();
784
+ } else {
785
+ await client.disconnect();
940
786
  }
787
+ } finally {
941
788
  }
942
789
  }
943
790
  return [out];