@ceki/n8n-nodes-ceki 0.2.9 → 0.2.11
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 +3 -3
- package/dist/index.js +292 -87
- package/dist/index.js.map +3 -3
- package/dist/nodes/BrowserCeki/BrowserCeki.node.js +292 -87
- package/dist/nodes/BrowserCeki/BrowserCeki.node.js.map +3 -3
- package/dist/nodes/CekiContract/CekiContract.node.js.map +2 -2
- package/dist/nodes/recipes/CekiCaptchaScrape/CekiCaptchaScrape.node.js +64 -8
- package/dist/nodes/recipes/CekiCaptchaScrape/CekiCaptchaScrape.node.js.map +2 -2
- package/dist/nodes/recipes/CekiScreenshotGeo/CekiScreenshotGeo.node.js +64 -8
- package/dist/nodes/recipes/CekiScreenshotGeo/CekiScreenshotGeo.node.js.map +2 -2
- package/package.json +1 -1
|
@@ -120,17 +120,53 @@ var CekiClient = class {
|
|
|
120
120
|
this._sendRaw({ type: "resume", session_id: sessionId });
|
|
121
121
|
return this._awaitResume(sessionId);
|
|
122
122
|
}
|
|
123
|
-
/** Close the WS connection — session stays alive in grace.
|
|
124
|
-
|
|
123
|
+
/** Close the WS connection — session stays alive in grace.
|
|
124
|
+
* Waits for the close handshake so the relay processes the close
|
|
125
|
+
* before a new connection with the same renterId is established
|
|
126
|
+
* (avoids the close-handler race condition).
|
|
127
|
+
*/
|
|
128
|
+
async disconnect() {
|
|
125
129
|
this._closed = true;
|
|
126
130
|
this._activeSessions.clear();
|
|
127
131
|
this._pendingRents.clear();
|
|
128
132
|
this._pendingResumes.clear();
|
|
129
|
-
this.
|
|
133
|
+
if (!this._ws) return;
|
|
134
|
+
const ws = this._ws;
|
|
135
|
+
if (ws.readyState === WebSocket.CLOSED) {
|
|
136
|
+
this._ws = null;
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (ws.readyState === WebSocket.CLOSING) {
|
|
140
|
+
await new Promise((r) => {
|
|
141
|
+
ws.onclose = () => {
|
|
142
|
+
this._connected = false;
|
|
143
|
+
r();
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
this._ws = null;
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
await new Promise((r) => {
|
|
150
|
+
const t = setTimeout(() => {
|
|
151
|
+
r();
|
|
152
|
+
}, 5e3);
|
|
153
|
+
ws.onclose = () => {
|
|
154
|
+
this._connected = false;
|
|
155
|
+
clearTimeout(t);
|
|
156
|
+
r();
|
|
157
|
+
};
|
|
158
|
+
try {
|
|
159
|
+
ws.close();
|
|
160
|
+
} catch {
|
|
161
|
+
clearTimeout(t);
|
|
162
|
+
r();
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
this._ws = null;
|
|
130
166
|
}
|
|
131
167
|
/** Close everything. */
|
|
132
|
-
close() {
|
|
133
|
-
this.disconnect();
|
|
168
|
+
async close() {
|
|
169
|
+
await this.disconnect();
|
|
134
170
|
}
|
|
135
171
|
// ── private ────────────────────────────────────────────────
|
|
136
172
|
_awaitRent(key, scheduleId, timeoutMs) {
|
|
@@ -220,13 +256,33 @@ var CekiClient = class {
|
|
|
220
256
|
_onMatch(msg) {
|
|
221
257
|
const scheduleId = Number(msg.schedule_id ?? 0);
|
|
222
258
|
const eventId = msg.event_id ? String(msg.event_id) : null;
|
|
259
|
+
const sessionId = String(msg.session_id ?? "");
|
|
260
|
+
if (sessionId && this._ws?.readyState === WebSocket.OPEN) {
|
|
261
|
+
try {
|
|
262
|
+
this._ws.send(JSON.stringify({ type: "match_ack", session_id: sessionId }));
|
|
263
|
+
} catch {
|
|
264
|
+
}
|
|
265
|
+
}
|
|
223
266
|
let pending = this._pendingRents.get(`event:${eventId}`);
|
|
224
267
|
if (!pending) pending = this._pendingRents.get(`rent:${scheduleId}`);
|
|
225
268
|
if (pending) {
|
|
226
269
|
this._pendingRents.delete(`event:${eventId}`);
|
|
227
270
|
this._pendingRents.delete(`rent:${scheduleId}`);
|
|
228
271
|
pending.resolve({
|
|
229
|
-
session_id:
|
|
272
|
+
session_id: sessionId,
|
|
273
|
+
schedule_id: scheduleId,
|
|
274
|
+
event_id: eventId,
|
|
275
|
+
chat_topic_id: msg.chat_topic_id ? String(msg.chat_topic_id) : null,
|
|
276
|
+
provider_user_id: msg.provider_user_id != null ? Number(msg.provider_user_id) : null,
|
|
277
|
+
browser_info: msg.browser_info ?? {}
|
|
278
|
+
});
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const resumePending = sessionId ? this._pendingResumes.get(sessionId) : null;
|
|
282
|
+
if (resumePending) {
|
|
283
|
+
this._pendingResumes.delete(sessionId);
|
|
284
|
+
resumePending.resolve({
|
|
285
|
+
session_id: sessionId,
|
|
230
286
|
schedule_id: scheduleId,
|
|
231
287
|
event_id: eventId,
|
|
232
288
|
chat_topic_id: msg.chat_topic_id ? String(msg.chat_topic_id) : null,
|
|
@@ -269,9 +325,9 @@ var CekiClient = class {
|
|
|
269
325
|
const browser = this._activeSessions.get(sessionId);
|
|
270
326
|
if (!browser) return;
|
|
271
327
|
const id = Number(msg.id ?? 0);
|
|
272
|
-
const pending =
|
|
328
|
+
const pending = browser._pendingCdp.get(id);
|
|
273
329
|
if (!pending) return;
|
|
274
|
-
|
|
330
|
+
browser._pendingCdp.delete(id);
|
|
275
331
|
if (msg.error) {
|
|
276
332
|
pending.reject(new Error(String(msg.error.message ?? "CDP error")));
|
|
277
333
|
} else {
|
|
@@ -314,9 +370,9 @@ var CekiBrowser = class {
|
|
|
314
370
|
async navigate(url, timeoutMs) {
|
|
315
371
|
await this.send("Page.navigate", { url }, timeoutMs);
|
|
316
372
|
}
|
|
317
|
-
async click(
|
|
318
|
-
await this.send("Input.dispatchMouseEvent", { type: "mousePressed", x, y, button: "left", clickCount: 1 });
|
|
319
|
-
await this.send("Input.dispatchMouseEvent", { type: "mouseReleased", x, y, button: "left", clickCount: 1 });
|
|
373
|
+
async click(x2, y2) {
|
|
374
|
+
await this.send("Input.dispatchMouseEvent", { type: "mousePressed", x: x2, y: y2, button: "left", clickCount: 1 });
|
|
375
|
+
await this.send("Input.dispatchMouseEvent", { type: "mouseReleased", x: x2, y: y2, button: "left", clickCount: 1 });
|
|
320
376
|
}
|
|
321
377
|
async type(text) {
|
|
322
378
|
await this.send("Ceki.typeText", { text });
|
|
@@ -391,7 +447,7 @@ var BrowserCeki = class {
|
|
|
391
447
|
icon: "file:ceki.png",
|
|
392
448
|
group: ["transform"],
|
|
393
449
|
version: 1,
|
|
394
|
-
subtitle: '={{ "Ceki: " + $operation }}',
|
|
450
|
+
subtitle: '={{ "Ceki: " + $parameter.operation }}',
|
|
395
451
|
description: "Rent a real human browser and control it: rent, navigate, click, type, screenshot, solve captchas, and more",
|
|
396
452
|
defaults: { name: "Browser Ceki" },
|
|
397
453
|
inputs: ["main"],
|
|
@@ -416,7 +472,9 @@ var BrowserCeki = class {
|
|
|
416
472
|
{ name: "Wait for Selector", value: "waitForSelector" },
|
|
417
473
|
{ name: "Upload", value: "upload" },
|
|
418
474
|
{ name: "Close", value: "close" },
|
|
419
|
-
{ name: "Full: Rent \u2192 Navigate \u2192 Screenshot", value: "full" }
|
|
475
|
+
{ name: "Full: Rent \u2192 Navigate \u2192 Screenshot", value: "full" },
|
|
476
|
+
{ name: "My Sessions", value: "my_sessions" },
|
|
477
|
+
{ name: "Stop Session", value: "stop_session" }
|
|
420
478
|
]
|
|
421
479
|
},
|
|
422
480
|
// === Rent: rental parameters ===
|
|
@@ -434,7 +492,7 @@ var BrowserCeki = class {
|
|
|
434
492
|
type: "string",
|
|
435
493
|
default: "",
|
|
436
494
|
placeholder: "RU, EE, US\u2026",
|
|
437
|
-
displayOptions: { show: { operation: ["
|
|
495
|
+
displayOptions: { show: { operation: ["search"] } }
|
|
438
496
|
},
|
|
439
497
|
{
|
|
440
498
|
displayName: "Max $/min",
|
|
@@ -442,14 +500,14 @@ var BrowserCeki = class {
|
|
|
442
500
|
type: "number",
|
|
443
501
|
typeOptions: { numberPrecision: 4 },
|
|
444
502
|
default: 0.02,
|
|
445
|
-
displayOptions: { show: { operation: ["
|
|
503
|
+
displayOptions: { show: { operation: ["search"] } }
|
|
446
504
|
},
|
|
447
505
|
{
|
|
448
506
|
displayName: "Min rating",
|
|
449
507
|
name: "minRating",
|
|
450
508
|
type: "number",
|
|
451
509
|
default: 0,
|
|
452
|
-
displayOptions: { show: { operation: ["
|
|
510
|
+
displayOptions: { show: { operation: ["search"] } }
|
|
453
511
|
},
|
|
454
512
|
{
|
|
455
513
|
displayName: "Profile mode",
|
|
@@ -488,7 +546,7 @@ var BrowserCeki = class {
|
|
|
488
546
|
required: true,
|
|
489
547
|
displayOptions: {
|
|
490
548
|
show: {
|
|
491
|
-
operation: ["navigate", "click", "type", "scroll", "screenshot", "snapshot", "wait", "waitForSelector", "upload", "close"]
|
|
549
|
+
operation: ["navigate", "click", "type", "scroll", "screenshot", "snapshot", "wait", "waitForSelector", "upload", "close", "stop_session"]
|
|
492
550
|
}
|
|
493
551
|
}
|
|
494
552
|
},
|
|
@@ -610,28 +668,89 @@ var BrowserCeki = class {
|
|
|
610
668
|
for (let i = 0; i < items.length; i++) {
|
|
611
669
|
const op = this.getNodeParameter("operation", i);
|
|
612
670
|
const client = new CekiClient(token);
|
|
613
|
-
await client.connect();
|
|
614
671
|
let browser;
|
|
672
|
+
let needFullClose = false;
|
|
615
673
|
try {
|
|
674
|
+
await client.connect();
|
|
616
675
|
if (op === "rent") {
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
676
|
+
try {
|
|
677
|
+
const sid2 = await resolveSid(i, client);
|
|
678
|
+
const mode = this.getNodeParameter("mode", i);
|
|
679
|
+
browser = await client.rent(sid2, { mode });
|
|
680
|
+
out.push({
|
|
681
|
+
json: { session_id: browser.sessionId, schedule_id: sid2, mode }
|
|
682
|
+
});
|
|
683
|
+
} catch (e) {
|
|
684
|
+
throw new Error(`Rent failed: ${e.message}`);
|
|
685
|
+
} finally {
|
|
686
|
+
await client.disconnect();
|
|
687
|
+
}
|
|
624
688
|
continue;
|
|
625
689
|
}
|
|
626
690
|
if (op === "search") {
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
691
|
+
try {
|
|
692
|
+
const geo = this.getNodeParameter("geo", i);
|
|
693
|
+
const maxPrice = this.getNodeParameter("maxPrice", i);
|
|
694
|
+
const list = await client.search({
|
|
695
|
+
geo: geo || void 0,
|
|
696
|
+
max_price_per_min: maxPrice
|
|
697
|
+
});
|
|
698
|
+
out.push({ json: { browsers: list, count: list.length } });
|
|
699
|
+
} catch (e) {
|
|
700
|
+
throw new Error(`Search failed: ${e.message}`);
|
|
701
|
+
} finally {
|
|
702
|
+
await client.disconnect();
|
|
703
|
+
}
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
if (op === "my_sessions") {
|
|
707
|
+
try {
|
|
708
|
+
const resp = await fetch("https://api.ceki.me/api/agent/sessions", {
|
|
709
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
710
|
+
});
|
|
711
|
+
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
|
712
|
+
const body = await resp.json();
|
|
713
|
+
const sessions = body.data ?? [];
|
|
714
|
+
out.push({ json: { sessions, count: sessions.length } });
|
|
715
|
+
} catch (e) {
|
|
716
|
+
throw new Error(`My sessions failed: ${e.message}`);
|
|
717
|
+
}
|
|
718
|
+
continue;
|
|
719
|
+
}
|
|
720
|
+
if (op === "stop_session") {
|
|
721
|
+
const sessionId2 = this.getNodeParameter("sessionId", i);
|
|
722
|
+
try {
|
|
723
|
+
await new Promise((resolve, reject) => {
|
|
724
|
+
const ws = new WebSocket("wss://browser.ceki.me/ws/agent", [`bearer.${token}`]);
|
|
725
|
+
const t = AbortSignal.timeout(15e3);
|
|
726
|
+
t.addEventListener("abort", () => {
|
|
727
|
+
try {
|
|
728
|
+
ws.close();
|
|
729
|
+
} catch {
|
|
730
|
+
}
|
|
731
|
+
reject(new Error("Stop session timed out"));
|
|
732
|
+
}, { once: true });
|
|
733
|
+
ws.onopen = () => {
|
|
734
|
+
ws.send(JSON.stringify({ type: "stop", session_id: sessionId2, reason: "n8n stop_session" }));
|
|
735
|
+
};
|
|
736
|
+
ws.onmessage = (ev) => {
|
|
737
|
+
const msg = JSON.parse(ev.data);
|
|
738
|
+
if (msg.type === "session_ended" || msg.type === "pong") {
|
|
739
|
+
try {
|
|
740
|
+
ws.close();
|
|
741
|
+
} catch {
|
|
742
|
+
}
|
|
743
|
+
resolve();
|
|
744
|
+
}
|
|
745
|
+
};
|
|
746
|
+
ws.onerror = () => {
|
|
747
|
+
reject(new Error("WebSocket connection failed"));
|
|
748
|
+
};
|
|
749
|
+
});
|
|
750
|
+
out.push({ json: { stopped: true, session_id: sessionId2 } });
|
|
751
|
+
} catch (e) {
|
|
752
|
+
throw new Error(`Stop session failed: ${e.message}`);
|
|
753
|
+
}
|
|
635
754
|
continue;
|
|
636
755
|
}
|
|
637
756
|
if (op === "full") {
|
|
@@ -640,7 +759,11 @@ var BrowserCeki = class {
|
|
|
640
759
|
if (demoMode) {
|
|
641
760
|
sid2 = 99999;
|
|
642
761
|
} else {
|
|
643
|
-
|
|
762
|
+
try {
|
|
763
|
+
sid2 = await resolveSid(i, client);
|
|
764
|
+
} catch (e) {
|
|
765
|
+
throw new Error(`Full: resolve schedule failed: ${e.message}`);
|
|
766
|
+
}
|
|
644
767
|
}
|
|
645
768
|
const mode = this.getNodeParameter("mode", i);
|
|
646
769
|
const url = this.getNodeParameter("url", i);
|
|
@@ -653,14 +776,18 @@ var BrowserCeki = class {
|
|
|
653
776
|
const demoPng = Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAlgAAAGQCAIAAAD9V4nPAAAnuElEQVR4nO3ceVxU9f748feZhU0RxRUTXEEWFXG3Rc3MJb3mUlrueivzVvebVre+38ql7d5bqZmZlZV4UUvLUtPUUtOKUAREBRVkEVQQxQ2UdWbO74+p+XGZAWlBqs/r+ddhOJ/lHHrw6sCIFhDQRQAAUJWhrjcAAEBdIoQAAKURQgCA0gghAEBphBAAoDRCCABQGiEEACiNEAIAlEYIAQBKI4QAAKURQgCA0gghAEBphBAAoDRCCABQGiEEACiNEAIAlEYIAQBKI4QAAKURQgCA0gghAEBphBAAoDRCCABQGiEEACiNEAIAlEYIAQBKI4QAAKURQgCA0gghAEBphBAAoDRCCABQGiEEACjNVKuzD1odeyXtiGiaybN+2rpl5+P31upydq3uGOs/eJyl5Jq1pOjoey+VXDjr8rSBkdG7p93i+LC+f/tGId1PfbXe8Yp987ouBpPp9K7PcvZsqvWtAwBuuNoNoc1SfmD+X0XEu3XHiKeX3IAQNu7St8Wtw/Y/P8VWVtok4tZOj7wY98KDNRl49VT61VPpFV9xbN7o7hnx9JvW0uK8mK9qZdMAgLpTuyF0KMxO1a1WERkYGX0udldB5vG8mK/CZi0wenhZS4qSl8/rueDD+JdmFp/P7f7sO9dyMo+v/LdvWI9Wg+69nJp40+2jRddT175RkJYcPOMZ94ZNNJM5NWrhlbSkihNmb1srIm3+MvXE2jdtZaUikn/w+2a9BmpGk8nDy3mgnZuPb4/n3zu85Omrp9IrPSM6WEuLU1cvDp7+9MXD+1xuIG//Tt/QHic3RzYMjmjYsWv2to+ytka5N2xS8QJLL+ffmFsNAPhZblAIfTv1Oh75qogYzObc6O0XDv3Q+e//PBu9LefbLS37jQiaPCc/MbpRSPeS/C/FoHm36SgijUK65yd+HzT5ie8fG+7u26zt6Ada9B2Svf2jKyeOeDTxi3h6ScxT4ypOaF+ovn/7wpPHHeseffcFEQmaPMd5oIgYTOYuj796PPLVSs+CzgqzUr1a+Lucx2B2O73z0/RP3um3bNv+Zyelfby010urs7ZGBU15ouIFHln6f7/9bQUA/Gq1G0KDydxz/gcGs1uD9p0uJu0/H79Xt9kuHt4nIr5hPZOXzxORszFfBU58PPmd+c173VGYlVqYedy7dUeTZ71GId1Pff2pb6denR596dSO9UlvPdtv+VdeLfztMxvdPTWDQbfZHBP+tKSLt/80Dr/ZeaCIhPz1/3K/23oxKfa6F6IZjTaLxfU8ul6QnqzbbDZLeUH6UV23Gd09nC/wF99DAECtukG/I6wfENjrhUgR0a1WXbeJiIhW8cyLyQcCJ/y9YVD4peMHrWWljUJ7GMxuZVcuJC17vlFI99bDJ/ndOkwzGuNf/putvFTTDA2DI+wxqzChiEhRbpZ3m45XThwREdG0Tn97MWnZcy4HGszm+gEdROTM7s+veyE+HTpfzT7h3TbYeR6bpfzHg/KyijupdIEAgN+nG/TPJ8oLLxflnar4ysXk2OZ97hSR5n3uvJh8wFZWWnb5QrPed1xOSbx8/GDrEVMuHo0zedXvueDDy6mHjiz9vyYRt11OSWzWa6CINIm4pe3ov7pc6NSOdR3ue9RgdhORFjcPtR+4HGgrL499bqpHs5at7hhb/ebN9RoETXz85OaVNdlAVRd4/XsEAKgLN+JHo7qui8jR916s+KnUqEVhsxa0GnSPtbQ4eflcEclPjL7pjrHlhZevnDjcKKRb2rq3LEVXz8d/2/uV1ZpmyNjw3rkD34TOnOs/+F7dak1+d4HLFc/+sMPLr3Wff31cVnCprODisfdfFpGUyNdcDtR125Elz/R+eXVhVkrFd9D89+bFYDJlbvrwYnJc0dnT191ANRcIAPgd0gICutT1HgAAqDP8ZRkAgNIIIQBAaYQQAKA0QggAUBohBAAojRACAJRGCAEASiOEAAClEUIAgNIIIQBAaYQQAKA0QggAUBohBAAojRACAJRGCAEASiOEAAClEUIAgNIIIQBAaYQQAKA0QggAUBohBAAojRACAJRGCAEASiOEAAClEUIAgNIIIQBAaYQQAKA0QggAUBohBAAojRACAJRGCAEASiOEAAClEUIAgNIIIQBAaYQQAKA0QggAUBohBAAojRACAJRGCAEASiOEAAClEUIAgNIIIQBAaYQQAKC0Wg3hypXLP/305Q0b3l+1atn48Q9169YlJKRbevrJTz9d9cknpfv3i8gvvZzde++9lJZ2UtM0o9HYpUuHL7/8+meN+vTTl1esWKppmtFo7NSp/aefrvq5o/r163P11BlZ5Omnn7pwoXDkyDsNBkPFl2JiYtLTs0SkbdsAg8GQnX1m6tR7R49+8LqjFiyYn5iYLCMmCdf3AAAPyElEQVT3f+DAwZ07v922bZeI3HZbnyeffFi3nU40Xb165dy5Tw4ePBiP7wDwc1MHhFA87LbbQ0S6du3YteufFi58d+3az444+QcPHszNPZeami4izZo1iYlJEBGTyZiTU/rrrwz86QoCAr7//vvffOVeImI0Gnx8GtTWcgAQd7/fI5mYxZEjaUVFRX369GjSpLGIZGZmP/XUI4sXz69mQu2+l+Cdd17+4IM14h7Q6MmXl2/fvnvMmIfsL3333cHMzFMiYjabd+3aLyJ9+nR7660PIiIi3n33lYiIro88cn1iYlJqapr9Fy+/vDgp6Yj9OCkpafbsGfv2JYjIV1/tFZEBA/rExe1v0KC+yWTy8HAvKCgUkccee3DWrKfsyz3++IMi0qCB99tvf/TCC88dOJB4332jHn10prgH/Pe//3z44T/X1h0DgD+EWguhplmGDbvdYrHk5+cnJma4uzfw8/Np1KjhnXf+yc/PR9O0s2fP7tuX4HJCl0+EmzZ9euzYsT17Ymy21/Ly/n/s3XlYU9f+B/BvQhISloAsYVEQkEURQVktiB2XqlZbq+217VufttV6az989T219vq01k5r62Wq1qXa1qcWByqj4gIqyCqLIIggEFaBsCRk+f3hO0eHSSAO1Pm9nn9w8n3OPfckhPNhOZyT3KNHj/v2HQgIGDppUo2mnT6deetbSssriouVpjxNT09PTq0fPHhQe7rOzs78+PHe+/btv+22oICA4e7u7pGRkUql0sPDY+TIQBERXl5eUVExIuLo6CgizZqeys7ONQwVEe3adTwtTbspIi2tFhEx5M+f//f48eOEQhYA1F/nhXDRok9///3wypU/CgiY/PLLi0TELyRkSk5Orqj0PDwmy5YtHjly+px5HzUOV3shiUSiUqk2bvzH2bMXZkyYOG3aLBMTE01XZpc4ObV6ltMq/2xHtNtaW5vv2rXX1ta2Z8+e8+cvs7GxUSgU+/cfMTE28etlL6bt5ua2Y8f/REQlMo/M/K2ZmeXvvycfO3a8cUumUHe2TwBwczQ2m6VnV4lCoaisVFdXV1dUKCsrKysrq66vN1lfX2dpaSkibHhYLJZd3ctXk9lsnnYlEon8/PiPH3761Km/Nm36JiPjT2dn53379nfu3HnFiu91e2ZmntX9WCajjUQiSUhI1Oo4IiLo9/0EADdMo4tD165dPX/+YlpaRufOnboSEYnI2bMXvbw8e/XqXlhY+OOPO/r27T158qiGhgalUq26du3atWtX/fz6i0hBQcHly5ddXbkmJib3W6+2trbW1lYREYvFEhNbc3MLEWlublapVA0NDY6Ojvt+P5iVlTU7a9Z8G5v2IpKZmUlEXl4eIpKRkVlaWhYSMsXFxcXUtJmpqamHh4etrZ2RkVGXLl3u2Z6ISOXlVxoaGkRkyZJFmzZtNzMza2hoyM09EBAQoC1n+fLQo0eP7927f8uWLZ6eHkQ0YMCAsLCf0tLSfH199+7df/nyFXNz82++Wd+5c+dbLgcA0Ag0R8R4RE+dmmAiY+gbG3Xr1iUl5aj2o5GRkY2NjYODQ3Z29syZ0/LzC+7ZZkZG5pQpE4cOHaJSqRITkxYsSFBVVUVExOubmNUq5fj4CT4+3hER4Q0NDUlJqUuWLPrii4QR6voeHh5hYWG9e/e6fPny1q3fh4VN++yzz42MjD5b99nmzVu3bv3e27uXiLBYLBMTk6NHj3355ZezZ88SEXd3d19fXz4/tKio6OjRY8OG+R0/frK6unrlyuX/+9/eF198MTl5v7Oz8/bte955562oqCiNxiI+ft6GDT8FBAR4e/M8PT26des2fHhAXV1dWlraLbf0pkIhRESjocLCQh42bPhvv+3p3bv3lStX0tJ03+Tq1r9//3feeaewsKhXr16pqWmXLl3Ky8srKioODQ01MTHZtu37xMRES0tLLy/PoUOH6o6h4XoBoAVojiAxHu8wGnXLls0/+eQjfWO7dOn0zz93d8nYGB4Z6ddfd/zzn1+o1dd5Y8IMe/bFf/MaBoPh4eFeWVl5C5cdHh5uYmLKMNzevb+amJhIpdJvv/0vEURF3b1hww+TJ0/64Ydt8fGJs2d/lpCQEBcXN3PmTO0SdXVqR0en0NApInL48JH169dv377d39+/qurql1+upN/XEQBoeYxuWSAo/t6nT5+JiopydnY+ePBQWlp6u38X7gMA6hYhBFjx8bELFizm8XhE9MEH78+dG3NnVwEAf1t4RwgAIH/7NQIAALcDIQQAkEIIIQCEEDI+QggAIYQAEEIIACCEEABC2BwYAAAQEEIACCGEABBCAAAJIQQAkEIIASCEAABCCCEEgBACQEL3WAUAAAKEEABCCAEghBAAQggBIYQAEEIIACGEABBCAAAJIYQAEEIIACGEAABCCCEEgBBCAAghhAAAAYQQAEIIASCEEABACCEEgBBCAAghBIAQQgAIIQQAIYQAEEIIACGEAABCCCEEgBBCAAghBIAQQgAIIQQAIYQAEEIIACGEABBCAAAJIYQAEEIIACGEAABCCCEEgBBCAAghBIAQQgAIIQQAIYQAEEIIACGEABBCAAAJIYQAEEIIACGEAABCCCEEgBBCAAghBIAQQgAIIQQAIYQAEEIIACGEABBCAAAJIYQAEEIIACGEAABCCCEEgBBCAAghBIAQQgAIIQQAIYQAEEIIACGEABBCAAAJIYQAEEIIACGEAABCCCEEgBBCAAghBIAQQgAIIQQAIYQAEEIIACGEABBCAAAJIYQAEEIIACGEAABCCCEEgBBCAAghBIAQQgAIIQQAIYQAEEIIACGEABBCAAAJIYSIEHHBggXdunWLiYk5f/78naxl27aYb7/9loho+vTp77//PpPJvPWl2rVr19DQoE8tk8l0d3d//PHHXVxc9KkNAODe1CofER48ePDChQsODg4LFy5kMpmXL1/esGGDnZ3dmjVrbq2Wy5cvE5GLi4s+NenU4Ovr26NHjxMnsg4ePKBPbQAA96zWO0d44cIFNze3119/3c/Pj8ViiUhVVdXjjz9eU1NzC7Xs2LFDJpMJBILy8nJ9yiouLq6srGQymT/99FN2djYAADzaWohIJBIJBAJPT0+VStXS9Rw9elStVs+ZM8fCwkJfQQsWLJgxY4aentovvvhi586d58+f15mZmZk5atQoJycnIpJKpXFxcRqNZtu2bZ07dx40aJBIJNq3b19AQMDly5f3798/bNgwgUDwzTffFBcXBwcHp6SkHDhwYP/+/U5OTmZmZl999VVNTU1wcPCiRYuOHDkSFhZ25MiRFStWhIeHZ2dnL1y4sLq6ms/n9+7de9WqVVwut6GhIT4+Pjo6+rXXXhs9evTZs2d//PHHV1555Y6+OQCAv52ndl+/fi0iREQajcbGxmbHjh3Dhg2rqKj44IMPrK2te/XqtXv3bn1Keuihhw4dOpScnNyrVy99jpiYmOjoaGMjYy6XS0RqtVomkxUWFkokEg8PDyaTmZ6evmfPHoFAIJPJJk+e/Oabb1706Lx9+/b//Oc/iYmJpqamubm5Fy9efPPNN9euXbtq1ar58+f37dv3wIEDNBpNqVQyGAwiyszMnDx5cnFxsZWVVbdu3XJzc4nowIEDOTk5n376KRGpVKq8vLyvvvoqLy/v1KlTq1evjo+PLy4ufvbZZ8vLy+/0+wMA+FtonTlCgUCwb98+lUp19uzZTz75pKamJi4ubsCAAUePHvXy8rp06VJJSYk+Jen2b7e3txcIBLrDWCwWhUIpKSkRCARyuVx3RG1tbXV1tbW1NRFxuVwigUKhMBiMoqKiX3755ZVXXhGLxUS0cePGkSNHOjk5tWvXTluf7nP0/2tqarKzs4lo0qRJGzZsMDY2Xrt2bUBAQHFxMRG5uLgcPnxYLpcvXrz4mWee+fbbb2NjY59//vnBgwc3e4sAAOQ+Yjx9glZUVJSUlBw6dOjSpUv9+vXbt2/fDz/8sH///nfffTcqKqpVq1bTp09vrsl+v5WUlJCSkkJEFRUVUqlUJBIZGRkRUUlJCZfL7dq1K4vFcnFx6dq1K5PJ7NKlS2RkZEBAQFhYGBFJpdLNmze//vrra9euzcnJISLd5f+oOTY2NjU1NSYmJjg4+NixY9bW1hqN5tChQ2PGjPH3928wclxM0w1VqVTJyclvv/22bqG2trZFRUVarbdv3/7UU0+1adOGz+f37duXiGJjY6dOnerp6Xnul1NiYuL3338/efJkR0fHxYsXS6XSbdu2LViwYP78+Z6enn/X7xAAQJ9a70+EYrH4448/NjU1Xbt27fvvv9+7d28Oh+Po6FhUVDRq1Kh169bNmDGD7jN9+PDhDz74gIjuueee9957j8/nazSaUaNGhYWFZWZmzps3LzQ09KWXXiIiFovl5+dnbGzcr1+/rKysRx99VKVSzZgxo7y83MTE5JNPPqHT6UQkFAr9/f3Nzc1XrlzZ0NBw/PjxHj16+Pv7u7u7d+/e3d3d3cPD49y5c2KxuLKy8urVq05OTn369MnPzzczM7ty5YqTk9PAgQOzsrKISLdHmZmZ06dPnzZtmi7DlpaW48ePz8jI+Pbbb0ePHl1cXMxisQICAi5cuMDn8zMyMhob3oxGI51O1+01NjbGZDKnTp168ODB2bNnr1mzpn///hkZGT/99NPo0aPPnz/f3I2j0WhEwwMIAO5VGIIiIiIajebi4pKbm7ty5crQ0NCffvpp9uzZb7/9dlhYmFgsXrlyJYVCeeyxx0JCQmbMmJGRkXHp0iXlj+G/MAAAHONJREFU2RIrK6u//OUvCoVCIpE8/vjjfD6/oqIiLCzMwcGBx+MRUZ8+fVasWBEYGDh+/Phr1649/vjjp0+fjo+P79mzZ3h4eFJSkpmZ2e7duxmN/xD1b7/9xmazg4KCRowY8cILL0yfPj0lJaW2tnbz5s3du3fXarVpaWm1tbVcLvfw4cOPPPLIBx98sGvXLi6XK5FIUlJS0tLSOnToUFZWNnTo0H//+99ExOFw4uLiampqJkyYUFZWRkT5+flisVgsFj/xxBMCgaChoeHy5csSicTc3DwsLIzFYhERjUYrKChISkqaM2eOp6cnnU4nIpVK1bt3706dOq1bt27dunWEH90D4N6GjggR0aRJk4hIqVSKRCIzM7OysjI3NzeJREJEubm5EyZMGDRo0Jtvvvn+++8vX768Xbt2Tz75ZHh4+IIFC2g0mkgk6tmzJ5vNzs/Pd3Fxqa2tbdu2rVar7d+/v6en57Rp0/h8PhHJ5XI7O7sFCxY4Ojq+9NJL5ubm9vb2QqHwkUceOX/+fEFBQW5u7vr162NjY3Uvs99fOp3OZDJ79uzZqVOn4uJiCwuLxx9/nMlkHjp0qLi4WFcGl8utqanp0KGDQqFISUnR1WFkZGRnZ3fgwIH58+enpKQIpjI++eQTIpLL5ZWVlVu2bCktLS0rK0tOTlYsXRIeHi4SiYiopqbmo48+2rlzZ0ZGBofD6dKli5+fX0lJyTfffLN48eIuXbpIpVI+n9+tWzerxq48AACPAouI8IKK6Nq1a9euXXUqeOeddxYvXvzYY4+dP39eX2k7O7vk5OTOnTsfOXJk+/btCxYsIKINGzZ88skn48eP7y9on5aWNn/+fN3QefPmHTp0KDU1dc6cOc2VT6fTN2/e/NJLL+krGwAAPu05Qh5PKBaLjx071rFjx/bt2xORQqGQy+UCgaC6urq6unrz5s1E5O7u3rdv3+rq6ps3b2q12o4dO546dUokEu3bt8/KyuqTTz4hIk9PzwcffLC+vh7vhQCAFqd9R4iUBn+LKBQKCoWSlJSk0Whqa2vPnDlTVlZ26NChkJAQlUp18+bN5OTk3NxcLpd769GlS5dSqVQ6nd69e/cXX3zR19f3ypUrmzZtKioq6tOnT3x8PIPBICYGAAC8IzxtsFmssLCwgIAAHo8nkUguXLiQnZ1tZ2e3cuXKqVOfpv+PYzQajUb70/8vFyIqKiqKjIycMmVKbW1tRUVFbm6uWq32xv8LBQB4tGkUwtmzZxMTE/FPCQHgd/7OS5QAAEA3Vqs/ESEAAIDeEEIAABmEEABABiEEAJBBCAEAZBBCAAAZhBAAQKaNTQfcbwIAQAhCCAAghiEEABBDCAEAxBBCAIBbhhACAOT/xDcAAMB/hj8RAgDI/H+J3gYgOqnyVQAAAABJRU5ErkJggg==", "base64");
|
|
654
777
|
binary = await this.helpers.prepareBinaryData(demoPng, "screenshot.png", "image/png");
|
|
655
778
|
} else {
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
779
|
+
try {
|
|
780
|
+
browser = await client.rent(sid2, { mode });
|
|
781
|
+
session_id = browser.sessionId;
|
|
782
|
+
await browser.navigate(url);
|
|
783
|
+
await sleep(ms);
|
|
784
|
+
const shot = await browser.screenshot({ format: "png", fullPage: true });
|
|
785
|
+
const data = shot.toString("base64");
|
|
786
|
+
binary = await this.helpers.prepareBinaryData(Buffer.from(data, "base64"), "screenshot.png", "image/png");
|
|
787
|
+
await browser.close();
|
|
788
|
+
} catch (e) {
|
|
789
|
+
throw new Error(`Full op failed at step "${browser?.sessionId ? "navigate/screenshot" : "rent"}": ${e.message}`);
|
|
790
|
+
}
|
|
664
791
|
}
|
|
665
792
|
out.push({
|
|
666
793
|
json: {
|
|
@@ -673,57 +800,86 @@ var BrowserCeki = class {
|
|
|
673
800
|
binary: { data: binary }
|
|
674
801
|
});
|
|
675
802
|
} finally {
|
|
676
|
-
await client.close()
|
|
803
|
+
await client.close().catch(() => {
|
|
804
|
+
});
|
|
677
805
|
}
|
|
678
806
|
continue;
|
|
679
807
|
}
|
|
680
808
|
const sessionId = this.getNodeParameter("sessionId", i);
|
|
681
|
-
|
|
809
|
+
try {
|
|
810
|
+
browser = await client.resume(sessionId);
|
|
811
|
+
} catch (e) {
|
|
812
|
+
throw new Error(`Resume session "${sessionId}" failed: ${e.message}. Session may have expired or still be in grace.`);
|
|
813
|
+
}
|
|
682
814
|
const sid = browser.sessionId;
|
|
683
815
|
switch (op) {
|
|
684
816
|
case "navigate": {
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
817
|
+
try {
|
|
818
|
+
const url = this.getNodeParameter("url", i);
|
|
819
|
+
await browser.navigate(url);
|
|
820
|
+
out.push({ json: { session_id: sid, url } });
|
|
821
|
+
} catch (e) {
|
|
822
|
+
throw new Error(`Navigate failed: ${e.message}`);
|
|
823
|
+
}
|
|
688
824
|
break;
|
|
689
825
|
}
|
|
690
826
|
case "click": {
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
827
|
+
try {
|
|
828
|
+
const x2 = this.getNodeParameter("x", i);
|
|
829
|
+
const y2 = this.getNodeParameter("y", i);
|
|
830
|
+
await browser.click(x2, y2);
|
|
831
|
+
out.push({ json: { session_id: sid, clicked: [x2, y2] } });
|
|
832
|
+
} catch (e) {
|
|
833
|
+
throw new Error(`Click at (${x},${y}) failed: ${e.message}`);
|
|
834
|
+
}
|
|
695
835
|
break;
|
|
696
836
|
}
|
|
697
837
|
case "type": {
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
838
|
+
try {
|
|
839
|
+
const text = this.getNodeParameter("text", i);
|
|
840
|
+
await browser.type(text);
|
|
841
|
+
out.push({ json: { session_id: sid, typed: text } });
|
|
842
|
+
} catch (e) {
|
|
843
|
+
throw new Error(`Type failed: ${e.message}`);
|
|
844
|
+
}
|
|
701
845
|
break;
|
|
702
846
|
}
|
|
703
847
|
case "scroll": {
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
848
|
+
try {
|
|
849
|
+
const deltaY = this.getNodeParameter("deltaY", i);
|
|
850
|
+
await browser.scroll(deltaY);
|
|
851
|
+
out.push({ json: { session_id: sid, scrolled: deltaY } });
|
|
852
|
+
} catch (e) {
|
|
853
|
+
throw new Error(`Scroll failed: ${e.message}`);
|
|
854
|
+
}
|
|
707
855
|
break;
|
|
708
856
|
}
|
|
709
857
|
case "screenshot": {
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
858
|
+
try {
|
|
859
|
+
const format = this.getNodeParameter("format", i);
|
|
860
|
+
const fullPage = this.getNodeParameter("fullPage", i);
|
|
861
|
+
const shot = await browser.screenshot({ format, fullPage });
|
|
862
|
+
const data = format === "base64" ? shot.data : shot.toString("base64");
|
|
863
|
+
const binary = await this.helpers.prepareBinaryData(
|
|
864
|
+
Buffer.from(data, "base64"),
|
|
865
|
+
"screenshot.png",
|
|
866
|
+
"image/png"
|
|
867
|
+
);
|
|
868
|
+
out.push({ json: { session_id: sid }, binary: { data: binary } });
|
|
869
|
+
} catch (e) {
|
|
870
|
+
throw new Error(`Screenshot failed: ${e.message}`);
|
|
871
|
+
}
|
|
720
872
|
break;
|
|
721
873
|
}
|
|
722
874
|
case "snapshot": {
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
875
|
+
try {
|
|
876
|
+
const snap = await browser.snapshot();
|
|
877
|
+
out.push({
|
|
878
|
+
json: { session_id: sid, screenshot: snap.screenshot }
|
|
879
|
+
});
|
|
880
|
+
} catch (e) {
|
|
881
|
+
throw new Error(`Snapshot failed: ${e.message}`);
|
|
882
|
+
}
|
|
727
883
|
break;
|
|
728
884
|
}
|
|
729
885
|
case "wait": {
|
|
@@ -760,30 +916,79 @@ var BrowserCeki = class {
|
|
|
760
916
|
break;
|
|
761
917
|
}
|
|
762
918
|
case "upload": {
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
919
|
+
try {
|
|
920
|
+
const selector = this.getNodeParameter("selector", i);
|
|
921
|
+
const bpn = this.getNodeParameter("binaryPropertyName", i);
|
|
922
|
+
const bin = items[i].binary?.[bpn];
|
|
923
|
+
if (!bin) throw new Error(`Binary property "${bpn}" not found on input`);
|
|
924
|
+
const stream = await this.helpers.getBinaryStream(bin.id);
|
|
925
|
+
const chunks = [];
|
|
926
|
+
for await (const c of stream) chunks.push(c);
|
|
927
|
+
const buf = Buffer.concat(chunks);
|
|
928
|
+
const res = await browser.upload(selector, buf);
|
|
929
|
+
out.push({ json: { session_id: sid, uploaded: res } });
|
|
930
|
+
} catch (e) {
|
|
931
|
+
throw new Error(`Upload failed: ${e.message}`);
|
|
932
|
+
}
|
|
773
933
|
break;
|
|
774
934
|
}
|
|
775
935
|
case "close": {
|
|
776
|
-
|
|
936
|
+
try {
|
|
937
|
+
await browser.close().catch(() => {
|
|
938
|
+
});
|
|
939
|
+
} catch {
|
|
940
|
+
}
|
|
941
|
+
await new Promise((resolve) => {
|
|
942
|
+
const stopWs = new WebSocket("wss://browser.ceki.me/ws/agent", [`bearer.${token}`]);
|
|
943
|
+
const abortTimer = AbortSignal.timeout(1e4);
|
|
944
|
+
const onTimer = () => {
|
|
945
|
+
try {
|
|
946
|
+
stopWs.close();
|
|
947
|
+
} catch {
|
|
948
|
+
}
|
|
949
|
+
resolve();
|
|
950
|
+
};
|
|
951
|
+
abortTimer.addEventListener("abort", onTimer, { once: true });
|
|
952
|
+
stopWs.onopen = () => {
|
|
953
|
+
stopWs.send(JSON.stringify({ type: "stop", session_id: sessionId, reason: "n8n close" }));
|
|
954
|
+
};
|
|
955
|
+
stopWs.onmessage = (ev) => {
|
|
956
|
+
try {
|
|
957
|
+
const msg = JSON.parse(ev.data);
|
|
958
|
+
if (msg.type === "session_ended") {
|
|
959
|
+
abortTimer.removeEventListener("abort", onTimer);
|
|
960
|
+
try {
|
|
961
|
+
stopWs.close();
|
|
962
|
+
} catch {
|
|
963
|
+
}
|
|
964
|
+
resolve();
|
|
965
|
+
}
|
|
966
|
+
} catch {
|
|
967
|
+
}
|
|
968
|
+
};
|
|
969
|
+
stopWs.onerror = () => {
|
|
970
|
+
abortTimer.removeEventListener("abort", onTimer);
|
|
971
|
+
resolve();
|
|
972
|
+
};
|
|
973
|
+
stopWs.onclose = () => {
|
|
974
|
+
abortTimer.removeEventListener("abort", onTimer);
|
|
975
|
+
resolve();
|
|
976
|
+
};
|
|
977
|
+
});
|
|
777
978
|
out.push({ json: { closed: true, session_id: sessionId } });
|
|
778
979
|
break;
|
|
779
980
|
}
|
|
780
981
|
}
|
|
781
|
-
|
|
782
|
-
await client.close();
|
|
783
|
-
} else {
|
|
784
|
-
await client.disconnect();
|
|
785
|
-
}
|
|
982
|
+
needFullClose = op === "close";
|
|
786
983
|
} finally {
|
|
984
|
+
try {
|
|
985
|
+
if (needFullClose) {
|
|
986
|
+
await client.close();
|
|
987
|
+
} else {
|
|
988
|
+
await client.disconnect();
|
|
989
|
+
}
|
|
990
|
+
} catch {
|
|
991
|
+
}
|
|
787
992
|
}
|
|
788
993
|
}
|
|
789
994
|
return [out];
|