@ceki/n8n-nodes-ceki 0.2.9 → 0.2.10
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/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
package/dist/index.js
CHANGED
|
@@ -121,17 +121,53 @@ 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
|
-
|
|
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() {
|
|
126
130
|
this._closed = true;
|
|
127
131
|
this._activeSessions.clear();
|
|
128
132
|
this._pendingRents.clear();
|
|
129
133
|
this._pendingResumes.clear();
|
|
130
|
-
this.
|
|
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;
|
|
131
167
|
}
|
|
132
168
|
/** Close everything. */
|
|
133
|
-
close() {
|
|
134
|
-
this.disconnect();
|
|
169
|
+
async close() {
|
|
170
|
+
await this.disconnect();
|
|
135
171
|
}
|
|
136
172
|
// ── private ────────────────────────────────────────────────
|
|
137
173
|
_awaitRent(key, scheduleId, timeoutMs) {
|
|
@@ -221,13 +257,33 @@ var CekiClient = class {
|
|
|
221
257
|
_onMatch(msg) {
|
|
222
258
|
const scheduleId = Number(msg.schedule_id ?? 0);
|
|
223
259
|
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
|
+
}
|
|
224
267
|
let pending = this._pendingRents.get(`event:${eventId}`);
|
|
225
268
|
if (!pending) pending = this._pendingRents.get(`rent:${scheduleId}`);
|
|
226
269
|
if (pending) {
|
|
227
270
|
this._pendingRents.delete(`event:${eventId}`);
|
|
228
271
|
this._pendingRents.delete(`rent:${scheduleId}`);
|
|
229
272
|
pending.resolve({
|
|
230
|
-
session_id:
|
|
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,
|
|
231
287
|
schedule_id: scheduleId,
|
|
232
288
|
event_id: eventId,
|
|
233
289
|
chat_topic_id: msg.chat_topic_id ? String(msg.chat_topic_id) : null,
|
|
@@ -270,9 +326,9 @@ var CekiClient = class {
|
|
|
270
326
|
const browser = this._activeSessions.get(sessionId);
|
|
271
327
|
if (!browser) return;
|
|
272
328
|
const id = Number(msg.id ?? 0);
|
|
273
|
-
const pending =
|
|
329
|
+
const pending = browser._pendingCdp.get(id);
|
|
274
330
|
if (!pending) return;
|
|
275
|
-
|
|
331
|
+
browser._pendingCdp.delete(id);
|
|
276
332
|
if (msg.error) {
|
|
277
333
|
pending.reject(new Error(String(msg.error.message ?? "CDP error")));
|
|
278
334
|
} else {
|
|
@@ -315,9 +371,9 @@ var CekiBrowser = class {
|
|
|
315
371
|
async navigate(url, timeoutMs) {
|
|
316
372
|
await this.send("Page.navigate", { url }, timeoutMs);
|
|
317
373
|
}
|
|
318
|
-
async click(
|
|
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 });
|
|
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 });
|
|
321
377
|
}
|
|
322
378
|
async type(text) {
|
|
323
379
|
await this.send("Ceki.typeText", { text });
|
|
@@ -392,7 +448,7 @@ var BrowserCeki = class {
|
|
|
392
448
|
icon: "file:ceki.png",
|
|
393
449
|
group: ["transform"],
|
|
394
450
|
version: 1,
|
|
395
|
-
subtitle: '={{ "Ceki: " + $operation }}',
|
|
451
|
+
subtitle: '={{ "Ceki: " + $parameter.operation }}',
|
|
396
452
|
description: "Rent a real human browser and control it: rent, navigate, click, type, screenshot, solve captchas, and more",
|
|
397
453
|
defaults: { name: "Browser Ceki" },
|
|
398
454
|
inputs: ["main"],
|
|
@@ -417,7 +473,9 @@ var BrowserCeki = class {
|
|
|
417
473
|
{ name: "Wait for Selector", value: "waitForSelector" },
|
|
418
474
|
{ name: "Upload", value: "upload" },
|
|
419
475
|
{ name: "Close", value: "close" },
|
|
420
|
-
{ name: "Full: Rent \u2192 Navigate \u2192 Screenshot", value: "full" }
|
|
476
|
+
{ name: "Full: Rent \u2192 Navigate \u2192 Screenshot", value: "full" },
|
|
477
|
+
{ name: "My Sessions", value: "my_sessions" },
|
|
478
|
+
{ name: "Stop Session", value: "stop_session" }
|
|
421
479
|
]
|
|
422
480
|
},
|
|
423
481
|
// === Rent: rental parameters ===
|
|
@@ -435,7 +493,7 @@ var BrowserCeki = class {
|
|
|
435
493
|
type: "string",
|
|
436
494
|
default: "",
|
|
437
495
|
placeholder: "RU, EE, US\u2026",
|
|
438
|
-
displayOptions: { show: { operation: ["
|
|
496
|
+
displayOptions: { show: { operation: ["search"] } }
|
|
439
497
|
},
|
|
440
498
|
{
|
|
441
499
|
displayName: "Max $/min",
|
|
@@ -443,14 +501,14 @@ var BrowserCeki = class {
|
|
|
443
501
|
type: "number",
|
|
444
502
|
typeOptions: { numberPrecision: 4 },
|
|
445
503
|
default: 0.02,
|
|
446
|
-
displayOptions: { show: { operation: ["
|
|
504
|
+
displayOptions: { show: { operation: ["search"] } }
|
|
447
505
|
},
|
|
448
506
|
{
|
|
449
507
|
displayName: "Min rating",
|
|
450
508
|
name: "minRating",
|
|
451
509
|
type: "number",
|
|
452
510
|
default: 0,
|
|
453
|
-
displayOptions: { show: { operation: ["
|
|
511
|
+
displayOptions: { show: { operation: ["search"] } }
|
|
454
512
|
},
|
|
455
513
|
{
|
|
456
514
|
displayName: "Profile mode",
|
|
@@ -489,7 +547,7 @@ var BrowserCeki = class {
|
|
|
489
547
|
required: true,
|
|
490
548
|
displayOptions: {
|
|
491
549
|
show: {
|
|
492
|
-
operation: ["navigate", "click", "type", "scroll", "screenshot", "snapshot", "wait", "waitForSelector", "upload", "close"]
|
|
550
|
+
operation: ["navigate", "click", "type", "scroll", "screenshot", "snapshot", "wait", "waitForSelector", "upload", "close", "stop_session"]
|
|
493
551
|
}
|
|
494
552
|
}
|
|
495
553
|
},
|
|
@@ -611,28 +669,89 @@ var BrowserCeki = class {
|
|
|
611
669
|
for (let i = 0; i < items.length; i++) {
|
|
612
670
|
const op = this.getNodeParameter("operation", i);
|
|
613
671
|
const client = new CekiClient(token);
|
|
614
|
-
await client.connect();
|
|
615
672
|
let browser;
|
|
673
|
+
let needFullClose = false;
|
|
616
674
|
try {
|
|
675
|
+
await client.connect();
|
|
617
676
|
if (op === "rent") {
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
677
|
+
try {
|
|
678
|
+
const sid2 = await resolveSid(i, client);
|
|
679
|
+
const mode = this.getNodeParameter("mode", i);
|
|
680
|
+
browser = await client.rent(sid2, { mode });
|
|
681
|
+
out.push({
|
|
682
|
+
json: { session_id: browser.sessionId, schedule_id: sid2, mode }
|
|
683
|
+
});
|
|
684
|
+
} catch (e) {
|
|
685
|
+
throw new Error(`Rent failed: ${e.message}`);
|
|
686
|
+
} finally {
|
|
687
|
+
await client.disconnect();
|
|
688
|
+
}
|
|
625
689
|
continue;
|
|
626
690
|
}
|
|
627
691
|
if (op === "search") {
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
692
|
+
try {
|
|
693
|
+
const geo = this.getNodeParameter("geo", i);
|
|
694
|
+
const maxPrice = this.getNodeParameter("maxPrice", i);
|
|
695
|
+
const list = await client.search({
|
|
696
|
+
geo: geo || void 0,
|
|
697
|
+
max_price_per_min: maxPrice
|
|
698
|
+
});
|
|
699
|
+
out.push({ json: { browsers: list, count: list.length } });
|
|
700
|
+
} catch (e) {
|
|
701
|
+
throw new Error(`Search failed: ${e.message}`);
|
|
702
|
+
} finally {
|
|
703
|
+
await client.disconnect();
|
|
704
|
+
}
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
if (op === "my_sessions") {
|
|
708
|
+
try {
|
|
709
|
+
const resp = await fetch("https://api.ceki.me/api/agent/sessions", {
|
|
710
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
711
|
+
});
|
|
712
|
+
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
|
713
|
+
const body = await resp.json();
|
|
714
|
+
const sessions = body.data ?? [];
|
|
715
|
+
out.push({ json: { sessions, count: sessions.length } });
|
|
716
|
+
} catch (e) {
|
|
717
|
+
throw new Error(`My sessions failed: ${e.message}`);
|
|
718
|
+
}
|
|
719
|
+
continue;
|
|
720
|
+
}
|
|
721
|
+
if (op === "stop_session") {
|
|
722
|
+
const sessionId2 = this.getNodeParameter("sessionId", i);
|
|
723
|
+
try {
|
|
724
|
+
await new Promise((resolve, reject) => {
|
|
725
|
+
const ws = new WebSocket("wss://browser.ceki.me/ws/agent", [`bearer.${token}`]);
|
|
726
|
+
const t = AbortSignal.timeout(15e3);
|
|
727
|
+
t.addEventListener("abort", () => {
|
|
728
|
+
try {
|
|
729
|
+
ws.close();
|
|
730
|
+
} catch {
|
|
731
|
+
}
|
|
732
|
+
reject(new Error("Stop session timed out"));
|
|
733
|
+
}, { once: true });
|
|
734
|
+
ws.onopen = () => {
|
|
735
|
+
ws.send(JSON.stringify({ type: "stop", session_id: sessionId2, reason: "n8n stop_session" }));
|
|
736
|
+
};
|
|
737
|
+
ws.onmessage = (ev) => {
|
|
738
|
+
const msg = JSON.parse(ev.data);
|
|
739
|
+
if (msg.type === "session_ended" || msg.type === "pong") {
|
|
740
|
+
try {
|
|
741
|
+
ws.close();
|
|
742
|
+
} catch {
|
|
743
|
+
}
|
|
744
|
+
resolve();
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
ws.onerror = () => {
|
|
748
|
+
reject(new Error("WebSocket connection failed"));
|
|
749
|
+
};
|
|
750
|
+
});
|
|
751
|
+
out.push({ json: { stopped: true, session_id: sessionId2 } });
|
|
752
|
+
} catch (e) {
|
|
753
|
+
throw new Error(`Stop session failed: ${e.message}`);
|
|
754
|
+
}
|
|
636
755
|
continue;
|
|
637
756
|
}
|
|
638
757
|
if (op === "full") {
|
|
@@ -641,7 +760,11 @@ var BrowserCeki = class {
|
|
|
641
760
|
if (demoMode) {
|
|
642
761
|
sid2 = 99999;
|
|
643
762
|
} else {
|
|
644
|
-
|
|
763
|
+
try {
|
|
764
|
+
sid2 = await resolveSid(i, client);
|
|
765
|
+
} catch (e) {
|
|
766
|
+
throw new Error(`Full: resolve schedule failed: ${e.message}`);
|
|
767
|
+
}
|
|
645
768
|
}
|
|
646
769
|
const mode = this.getNodeParameter("mode", i);
|
|
647
770
|
const url = this.getNodeParameter("url", i);
|
|
@@ -654,14 +777,18 @@ var BrowserCeki = class {
|
|
|
654
777
|
const demoPng = Buffer.from("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", "base64");
|
|
655
778
|
binary = await this.helpers.prepareBinaryData(demoPng, "screenshot.png", "image/png");
|
|
656
779
|
} else {
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
780
|
+
try {
|
|
781
|
+
browser = await client.rent(sid2, { mode });
|
|
782
|
+
session_id = browser.sessionId;
|
|
783
|
+
await browser.navigate(url);
|
|
784
|
+
await sleep(ms);
|
|
785
|
+
const shot = await browser.screenshot({ format: "png", fullPage: true });
|
|
786
|
+
const data = shot.toString("base64");
|
|
787
|
+
binary = await this.helpers.prepareBinaryData(Buffer.from(data, "base64"), "screenshot.png", "image/png");
|
|
788
|
+
await browser.close();
|
|
789
|
+
} catch (e) {
|
|
790
|
+
throw new Error(`Full op failed at step "${browser?.sessionId ? "navigate/screenshot" : "rent"}": ${e.message}`);
|
|
791
|
+
}
|
|
665
792
|
}
|
|
666
793
|
out.push({
|
|
667
794
|
json: {
|
|
@@ -674,57 +801,86 @@ var BrowserCeki = class {
|
|
|
674
801
|
binary: { data: binary }
|
|
675
802
|
});
|
|
676
803
|
} finally {
|
|
677
|
-
await client.close()
|
|
804
|
+
await client.close().catch(() => {
|
|
805
|
+
});
|
|
678
806
|
}
|
|
679
807
|
continue;
|
|
680
808
|
}
|
|
681
809
|
const sessionId = this.getNodeParameter("sessionId", i);
|
|
682
|
-
|
|
810
|
+
try {
|
|
811
|
+
browser = await client.resume(sessionId);
|
|
812
|
+
} catch (e) {
|
|
813
|
+
throw new Error(`Resume session "${sessionId}" failed: ${e.message}. Session may have expired or still be in grace.`);
|
|
814
|
+
}
|
|
683
815
|
const sid = browser.sessionId;
|
|
684
816
|
switch (op) {
|
|
685
817
|
case "navigate": {
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
818
|
+
try {
|
|
819
|
+
const url = this.getNodeParameter("url", i);
|
|
820
|
+
await browser.navigate(url);
|
|
821
|
+
out.push({ json: { session_id: sid, url } });
|
|
822
|
+
} catch (e) {
|
|
823
|
+
throw new Error(`Navigate failed: ${e.message}`);
|
|
824
|
+
}
|
|
689
825
|
break;
|
|
690
826
|
}
|
|
691
827
|
case "click": {
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
828
|
+
try {
|
|
829
|
+
const x2 = this.getNodeParameter("x", i);
|
|
830
|
+
const y2 = this.getNodeParameter("y", i);
|
|
831
|
+
await browser.click(x2, y2);
|
|
832
|
+
out.push({ json: { session_id: sid, clicked: [x2, y2] } });
|
|
833
|
+
} catch (e) {
|
|
834
|
+
throw new Error(`Click at (${x},${y}) failed: ${e.message}`);
|
|
835
|
+
}
|
|
696
836
|
break;
|
|
697
837
|
}
|
|
698
838
|
case "type": {
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
839
|
+
try {
|
|
840
|
+
const text = this.getNodeParameter("text", i);
|
|
841
|
+
await browser.type(text);
|
|
842
|
+
out.push({ json: { session_id: sid, typed: text } });
|
|
843
|
+
} catch (e) {
|
|
844
|
+
throw new Error(`Type failed: ${e.message}`);
|
|
845
|
+
}
|
|
702
846
|
break;
|
|
703
847
|
}
|
|
704
848
|
case "scroll": {
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
849
|
+
try {
|
|
850
|
+
const deltaY = this.getNodeParameter("deltaY", i);
|
|
851
|
+
await browser.scroll(deltaY);
|
|
852
|
+
out.push({ json: { session_id: sid, scrolled: deltaY } });
|
|
853
|
+
} catch (e) {
|
|
854
|
+
throw new Error(`Scroll failed: ${e.message}`);
|
|
855
|
+
}
|
|
708
856
|
break;
|
|
709
857
|
}
|
|
710
858
|
case "screenshot": {
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
859
|
+
try {
|
|
860
|
+
const format = this.getNodeParameter("format", i);
|
|
861
|
+
const fullPage = this.getNodeParameter("fullPage", i);
|
|
862
|
+
const shot = await browser.screenshot({ format, fullPage });
|
|
863
|
+
const data = format === "base64" ? shot.data : shot.toString("base64");
|
|
864
|
+
const binary = await this.helpers.prepareBinaryData(
|
|
865
|
+
Buffer.from(data, "base64"),
|
|
866
|
+
"screenshot.png",
|
|
867
|
+
"image/png"
|
|
868
|
+
);
|
|
869
|
+
out.push({ json: { session_id: sid }, binary: { data: binary } });
|
|
870
|
+
} catch (e) {
|
|
871
|
+
throw new Error(`Screenshot failed: ${e.message}`);
|
|
872
|
+
}
|
|
721
873
|
break;
|
|
722
874
|
}
|
|
723
875
|
case "snapshot": {
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
876
|
+
try {
|
|
877
|
+
const snap = await browser.snapshot();
|
|
878
|
+
out.push({
|
|
879
|
+
json: { session_id: sid, screenshot: snap.screenshot }
|
|
880
|
+
});
|
|
881
|
+
} catch (e) {
|
|
882
|
+
throw new Error(`Snapshot failed: ${e.message}`);
|
|
883
|
+
}
|
|
728
884
|
break;
|
|
729
885
|
}
|
|
730
886
|
case "wait": {
|
|
@@ -761,30 +917,79 @@ var BrowserCeki = class {
|
|
|
761
917
|
break;
|
|
762
918
|
}
|
|
763
919
|
case "upload": {
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
920
|
+
try {
|
|
921
|
+
const selector = this.getNodeParameter("selector", i);
|
|
922
|
+
const bpn = this.getNodeParameter("binaryPropertyName", i);
|
|
923
|
+
const bin = items[i].binary?.[bpn];
|
|
924
|
+
if (!bin) throw new Error(`Binary property "${bpn}" not found on input`);
|
|
925
|
+
const stream = await this.helpers.getBinaryStream(bin.id);
|
|
926
|
+
const chunks = [];
|
|
927
|
+
for await (const c of stream) chunks.push(c);
|
|
928
|
+
const buf = Buffer.concat(chunks);
|
|
929
|
+
const res = await browser.upload(selector, buf);
|
|
930
|
+
out.push({ json: { session_id: sid, uploaded: res } });
|
|
931
|
+
} catch (e) {
|
|
932
|
+
throw new Error(`Upload failed: ${e.message}`);
|
|
933
|
+
}
|
|
774
934
|
break;
|
|
775
935
|
}
|
|
776
936
|
case "close": {
|
|
777
|
-
|
|
937
|
+
try {
|
|
938
|
+
await browser.close().catch(() => {
|
|
939
|
+
});
|
|
940
|
+
} catch {
|
|
941
|
+
}
|
|
942
|
+
await new Promise((resolve) => {
|
|
943
|
+
const stopWs = new WebSocket("wss://browser.ceki.me/ws/agent", [`bearer.${token}`]);
|
|
944
|
+
const abortTimer = AbortSignal.timeout(1e4);
|
|
945
|
+
const onTimer = () => {
|
|
946
|
+
try {
|
|
947
|
+
stopWs.close();
|
|
948
|
+
} catch {
|
|
949
|
+
}
|
|
950
|
+
resolve();
|
|
951
|
+
};
|
|
952
|
+
abortTimer.addEventListener("abort", onTimer, { once: true });
|
|
953
|
+
stopWs.onopen = () => {
|
|
954
|
+
stopWs.send(JSON.stringify({ type: "stop", session_id: sessionId, reason: "n8n close" }));
|
|
955
|
+
};
|
|
956
|
+
stopWs.onmessage = (ev) => {
|
|
957
|
+
try {
|
|
958
|
+
const msg = JSON.parse(ev.data);
|
|
959
|
+
if (msg.type === "session_ended") {
|
|
960
|
+
abortTimer.removeEventListener("abort", onTimer);
|
|
961
|
+
try {
|
|
962
|
+
stopWs.close();
|
|
963
|
+
} catch {
|
|
964
|
+
}
|
|
965
|
+
resolve();
|
|
966
|
+
}
|
|
967
|
+
} catch {
|
|
968
|
+
}
|
|
969
|
+
};
|
|
970
|
+
stopWs.onerror = () => {
|
|
971
|
+
abortTimer.removeEventListener("abort", onTimer);
|
|
972
|
+
resolve();
|
|
973
|
+
};
|
|
974
|
+
stopWs.onclose = () => {
|
|
975
|
+
abortTimer.removeEventListener("abort", onTimer);
|
|
976
|
+
resolve();
|
|
977
|
+
};
|
|
978
|
+
});
|
|
778
979
|
out.push({ json: { closed: true, session_id: sessionId } });
|
|
779
980
|
break;
|
|
780
981
|
}
|
|
781
982
|
}
|
|
782
|
-
|
|
783
|
-
await client.close();
|
|
784
|
-
} else {
|
|
785
|
-
await client.disconnect();
|
|
786
|
-
}
|
|
983
|
+
needFullClose = op === "close";
|
|
787
984
|
} finally {
|
|
985
|
+
try {
|
|
986
|
+
if (needFullClose) {
|
|
987
|
+
await client.close();
|
|
988
|
+
} else {
|
|
989
|
+
await client.disconnect();
|
|
990
|
+
}
|
|
991
|
+
} catch {
|
|
992
|
+
}
|
|
788
993
|
}
|
|
789
994
|
}
|
|
790
995
|
return [out];
|