@ceki/n8n-nodes-ceki 0.2.7 → 0.2.8
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 +298 -73
- package/dist/index.js.map +3 -3
- package/dist/nodes/BrowserCeki/BrowserCeki.node.js +298 -73
- 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 +65 -9
- package/dist/nodes/recipes/CekiCaptchaScrape/CekiCaptchaScrape.node.js.map +2 -2
- package/dist/nodes/recipes/CekiScreenshotGeo/CekiScreenshotGeo.node.js +65 -9
- 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) {
|
|
@@ -154,7 +190,7 @@ var CekiClient = class {
|
|
|
154
190
|
}
|
|
155
191
|
_awaitResume(sessionId) {
|
|
156
192
|
return new Promise((resolve, reject) => {
|
|
157
|
-
const timeoutSignal = AbortSignal.timeout(
|
|
193
|
+
const timeoutSignal = AbortSignal.timeout(6e4);
|
|
158
194
|
timeoutSignal.addEventListener("abort", () => {
|
|
159
195
|
this._pendingResumes.delete(sessionId);
|
|
160
196
|
reject(new Error("Resume timed out"));
|
|
@@ -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"],
|
|
@@ -402,8 +458,9 @@ var BrowserCeki = class {
|
|
|
402
458
|
displayName: "Operation",
|
|
403
459
|
name: "operation",
|
|
404
460
|
type: "options",
|
|
405
|
-
default: "
|
|
461
|
+
default: "search",
|
|
406
462
|
options: [
|
|
463
|
+
{ name: "Search", value: "search" },
|
|
407
464
|
{ name: "Rent", value: "rent" },
|
|
408
465
|
{ name: "Navigate", value: "navigate" },
|
|
409
466
|
{ name: "Click", value: "click" },
|
|
@@ -414,7 +471,8 @@ var BrowserCeki = class {
|
|
|
414
471
|
{ name: "Wait", value: "wait" },
|
|
415
472
|
{ name: "Wait for Selector", value: "waitForSelector" },
|
|
416
473
|
{ name: "Upload", value: "upload" },
|
|
417
|
-
{ name: "Close", value: "close" }
|
|
474
|
+
{ name: "Close", value: "close" },
|
|
475
|
+
{ name: "Full: Rent \u2192 Navigate \u2192 Screenshot", value: "full" }
|
|
418
476
|
]
|
|
419
477
|
},
|
|
420
478
|
// === Rent: rental parameters ===
|
|
@@ -432,7 +490,7 @@ var BrowserCeki = class {
|
|
|
432
490
|
type: "string",
|
|
433
491
|
default: "",
|
|
434
492
|
placeholder: "RU, EE, US\u2026",
|
|
435
|
-
displayOptions: { show: { operation: ["
|
|
493
|
+
displayOptions: { show: { operation: ["search"] } }
|
|
436
494
|
},
|
|
437
495
|
{
|
|
438
496
|
displayName: "Max $/min",
|
|
@@ -440,32 +498,48 @@ var BrowserCeki = class {
|
|
|
440
498
|
type: "number",
|
|
441
499
|
typeOptions: { numberPrecision: 4 },
|
|
442
500
|
default: 0.02,
|
|
443
|
-
displayOptions: { show: { operation: ["
|
|
501
|
+
displayOptions: { show: { operation: ["search"] } }
|
|
444
502
|
},
|
|
445
503
|
{
|
|
446
504
|
displayName: "Min rating",
|
|
447
505
|
name: "minRating",
|
|
448
506
|
type: "number",
|
|
449
507
|
default: 0,
|
|
450
|
-
displayOptions: { show: { operation: ["
|
|
508
|
+
displayOptions: { show: { operation: ["search"] } }
|
|
451
509
|
},
|
|
452
510
|
{
|
|
453
511
|
displayName: "Profile mode",
|
|
454
512
|
name: "mode",
|
|
455
513
|
type: "options",
|
|
456
|
-
default: "
|
|
514
|
+
default: "incognito",
|
|
457
515
|
options: [
|
|
458
516
|
{ name: "main", value: "main" },
|
|
459
517
|
{ name: "incognito", value: "incognito" }
|
|
460
518
|
],
|
|
461
|
-
displayOptions: { show: { operation: ["rent"] } }
|
|
519
|
+
displayOptions: { show: { operation: ["rent", "full"] } }
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
displayName: "URL",
|
|
523
|
+
name: "url",
|
|
524
|
+
type: "string",
|
|
525
|
+
default: "https://example.com",
|
|
526
|
+
required: true,
|
|
527
|
+
displayOptions: { show: { operation: ["navigate", "full"] } }
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
displayName: "Demo mode (no browser needed)",
|
|
531
|
+
name: "demoMode",
|
|
532
|
+
type: "boolean",
|
|
533
|
+
default: true,
|
|
534
|
+
description: "Skip actual browser rent and generate demo output",
|
|
535
|
+
displayOptions: { show: { operation: ["full"] } }
|
|
462
536
|
},
|
|
463
537
|
// === Operations: session_id ===
|
|
464
538
|
{
|
|
465
539
|
displayName: "Session ID",
|
|
466
540
|
name: "sessionId",
|
|
467
541
|
type: "string",
|
|
468
|
-
default: "
|
|
542
|
+
default: "",
|
|
469
543
|
description: "From the Rent operation",
|
|
470
544
|
required: true,
|
|
471
545
|
displayOptions: {
|
|
@@ -535,7 +609,7 @@ var BrowserCeki = class {
|
|
|
535
609
|
default: 1e3,
|
|
536
610
|
typeOptions: { minValue: 0 },
|
|
537
611
|
description: "Fixed delay on the active session",
|
|
538
|
-
displayOptions: { show: { operation: ["wait"] } }
|
|
612
|
+
displayOptions: { show: { operation: ["wait", "full"] } }
|
|
539
613
|
},
|
|
540
614
|
{
|
|
541
615
|
displayName: "CSS Selector",
|
|
@@ -592,66 +666,168 @@ var BrowserCeki = class {
|
|
|
592
666
|
for (let i = 0; i < items.length; i++) {
|
|
593
667
|
const op = this.getNodeParameter("operation", i);
|
|
594
668
|
const client = new CekiClient(token);
|
|
595
|
-
await client.connect();
|
|
596
669
|
let browser;
|
|
670
|
+
let needFullClose = false;
|
|
597
671
|
try {
|
|
672
|
+
await client.connect();
|
|
598
673
|
if (op === "rent") {
|
|
599
|
-
|
|
674
|
+
try {
|
|
675
|
+
const sid2 = await resolveSid(i, client);
|
|
676
|
+
const mode = this.getNodeParameter("mode", i);
|
|
677
|
+
browser = await client.rent(sid2, { mode });
|
|
678
|
+
out.push({
|
|
679
|
+
json: { session_id: browser.sessionId, schedule_id: sid2, mode }
|
|
680
|
+
});
|
|
681
|
+
} catch (e) {
|
|
682
|
+
throw new Error(`Rent failed: ${e.message}`);
|
|
683
|
+
} finally {
|
|
684
|
+
await client.disconnect();
|
|
685
|
+
}
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
if (op === "search") {
|
|
689
|
+
try {
|
|
690
|
+
const geo = this.getNodeParameter("geo", i);
|
|
691
|
+
const maxPrice = this.getNodeParameter("maxPrice", i);
|
|
692
|
+
const list = await client.search({
|
|
693
|
+
geo: geo || void 0,
|
|
694
|
+
max_price_per_min: maxPrice
|
|
695
|
+
});
|
|
696
|
+
out.push({ json: { browsers: list, count: list.length } });
|
|
697
|
+
} catch (e) {
|
|
698
|
+
throw new Error(`Search failed: ${e.message}`);
|
|
699
|
+
} finally {
|
|
700
|
+
await client.disconnect();
|
|
701
|
+
}
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
if (op === "full") {
|
|
705
|
+
const demoMode = this.getNodeParameter("demoMode", i);
|
|
706
|
+
let sid2;
|
|
707
|
+
if (demoMode) {
|
|
708
|
+
sid2 = 99999;
|
|
709
|
+
} else {
|
|
710
|
+
try {
|
|
711
|
+
sid2 = await resolveSid(i, client);
|
|
712
|
+
} catch (e) {
|
|
713
|
+
throw new Error(`Full: resolve schedule failed: ${e.message}`);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
600
716
|
const mode = this.getNodeParameter("mode", i);
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
717
|
+
const url = this.getNodeParameter("url", i);
|
|
718
|
+
const ms = this.getNodeParameter("ms", i);
|
|
719
|
+
let session_id;
|
|
720
|
+
let binary;
|
|
721
|
+
try {
|
|
722
|
+
if (demoMode) {
|
|
723
|
+
session_id = "demo-" + Date.now();
|
|
724
|
+
const demoPng = Buffer.from("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", "base64");
|
|
725
|
+
binary = await this.helpers.prepareBinaryData(demoPng, "screenshot.png", "image/png");
|
|
726
|
+
} else {
|
|
727
|
+
try {
|
|
728
|
+
browser = await client.rent(sid2, { mode });
|
|
729
|
+
session_id = browser.sessionId;
|
|
730
|
+
await browser.navigate(url);
|
|
731
|
+
await sleep(ms);
|
|
732
|
+
const shot = await browser.screenshot({ format: "png", fullPage: true });
|
|
733
|
+
const data = shot.toString("base64");
|
|
734
|
+
binary = await this.helpers.prepareBinaryData(Buffer.from(data, "base64"), "screenshot.png", "image/png");
|
|
735
|
+
await browser.close();
|
|
736
|
+
} catch (e) {
|
|
737
|
+
throw new Error(`Full op failed at step "${browser?.sessionId ? "navigate/screenshot" : "rent"}": ${e.message}`);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
out.push({
|
|
741
|
+
json: {
|
|
742
|
+
session_id,
|
|
743
|
+
schedule_id: sid2,
|
|
744
|
+
mode,
|
|
745
|
+
url,
|
|
746
|
+
waited: ms
|
|
747
|
+
},
|
|
748
|
+
binary: { data: binary }
|
|
749
|
+
});
|
|
750
|
+
} finally {
|
|
751
|
+
await client.close().catch(() => {
|
|
752
|
+
});
|
|
753
|
+
}
|
|
606
754
|
continue;
|
|
607
755
|
}
|
|
608
756
|
const sessionId = this.getNodeParameter("sessionId", i);
|
|
609
|
-
|
|
757
|
+
try {
|
|
758
|
+
browser = await client.resume(sessionId);
|
|
759
|
+
} catch (e) {
|
|
760
|
+
throw new Error(`Resume session "${sessionId}" failed: ${e.message}. Session may have expired or still be in grace.`);
|
|
761
|
+
}
|
|
610
762
|
const sid = browser.sessionId;
|
|
611
763
|
switch (op) {
|
|
612
764
|
case "navigate": {
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
765
|
+
try {
|
|
766
|
+
const url = this.getNodeParameter("url", i);
|
|
767
|
+
await browser.navigate(url);
|
|
768
|
+
out.push({ json: { session_id: sid, url } });
|
|
769
|
+
} catch (e) {
|
|
770
|
+
throw new Error(`Navigate failed: ${e.message}`);
|
|
771
|
+
}
|
|
616
772
|
break;
|
|
617
773
|
}
|
|
618
774
|
case "click": {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
775
|
+
try {
|
|
776
|
+
const x2 = this.getNodeParameter("x", i);
|
|
777
|
+
const y2 = this.getNodeParameter("y", i);
|
|
778
|
+
await browser.click(x2, y2);
|
|
779
|
+
out.push({ json: { session_id: sid, clicked: [x2, y2] } });
|
|
780
|
+
} catch (e) {
|
|
781
|
+
throw new Error(`Click at (${x},${y}) failed: ${e.message}`);
|
|
782
|
+
}
|
|
623
783
|
break;
|
|
624
784
|
}
|
|
625
785
|
case "type": {
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
786
|
+
try {
|
|
787
|
+
const text = this.getNodeParameter("text", i);
|
|
788
|
+
await browser.type(text);
|
|
789
|
+
out.push({ json: { session_id: sid, typed: text } });
|
|
790
|
+
} catch (e) {
|
|
791
|
+
throw new Error(`Type failed: ${e.message}`);
|
|
792
|
+
}
|
|
629
793
|
break;
|
|
630
794
|
}
|
|
631
795
|
case "scroll": {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
796
|
+
try {
|
|
797
|
+
const deltaY = this.getNodeParameter("deltaY", i);
|
|
798
|
+
await browser.scroll(deltaY);
|
|
799
|
+
out.push({ json: { session_id: sid, scrolled: deltaY } });
|
|
800
|
+
} catch (e) {
|
|
801
|
+
throw new Error(`Scroll failed: ${e.message}`);
|
|
802
|
+
}
|
|
635
803
|
break;
|
|
636
804
|
}
|
|
637
805
|
case "screenshot": {
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
806
|
+
try {
|
|
807
|
+
const format = this.getNodeParameter("format", i);
|
|
808
|
+
const fullPage = this.getNodeParameter("fullPage", i);
|
|
809
|
+
const shot = await browser.screenshot({ format, fullPage });
|
|
810
|
+
const data = format === "base64" ? shot.data : shot.toString("base64");
|
|
811
|
+
const binary = await this.helpers.prepareBinaryData(
|
|
812
|
+
Buffer.from(data, "base64"),
|
|
813
|
+
"screenshot.png",
|
|
814
|
+
"image/png"
|
|
815
|
+
);
|
|
816
|
+
out.push({ json: { session_id: sid }, binary: { data: binary } });
|
|
817
|
+
} catch (e) {
|
|
818
|
+
throw new Error(`Screenshot failed: ${e.message}`);
|
|
819
|
+
}
|
|
648
820
|
break;
|
|
649
821
|
}
|
|
650
822
|
case "snapshot": {
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
823
|
+
try {
|
|
824
|
+
const snap = await browser.snapshot();
|
|
825
|
+
out.push({
|
|
826
|
+
json: { session_id: sid, screenshot: snap.screenshot }
|
|
827
|
+
});
|
|
828
|
+
} catch (e) {
|
|
829
|
+
throw new Error(`Snapshot failed: ${e.message}`);
|
|
830
|
+
}
|
|
655
831
|
break;
|
|
656
832
|
}
|
|
657
833
|
case "wait": {
|
|
@@ -688,30 +864,79 @@ var BrowserCeki = class {
|
|
|
688
864
|
break;
|
|
689
865
|
}
|
|
690
866
|
case "upload": {
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
867
|
+
try {
|
|
868
|
+
const selector = this.getNodeParameter("selector", i);
|
|
869
|
+
const bpn = this.getNodeParameter("binaryPropertyName", i);
|
|
870
|
+
const bin = items[i].binary?.[bpn];
|
|
871
|
+
if (!bin) throw new Error(`Binary property "${bpn}" not found on input`);
|
|
872
|
+
const stream = await this.helpers.getBinaryStream(bin.id);
|
|
873
|
+
const chunks = [];
|
|
874
|
+
for await (const c of stream) chunks.push(c);
|
|
875
|
+
const buf = Buffer.concat(chunks);
|
|
876
|
+
const res = await browser.upload(selector, buf);
|
|
877
|
+
out.push({ json: { session_id: sid, uploaded: res } });
|
|
878
|
+
} catch (e) {
|
|
879
|
+
throw new Error(`Upload failed: ${e.message}`);
|
|
880
|
+
}
|
|
701
881
|
break;
|
|
702
882
|
}
|
|
703
883
|
case "close": {
|
|
704
|
-
|
|
884
|
+
try {
|
|
885
|
+
await browser.close().catch(() => {
|
|
886
|
+
});
|
|
887
|
+
} catch {
|
|
888
|
+
}
|
|
889
|
+
await new Promise((resolve) => {
|
|
890
|
+
const stopWs = new WebSocket("wss://browser.ceki.me/ws/agent", [`bearer.${token}`]);
|
|
891
|
+
const abortTimer = AbortSignal.timeout(1e4);
|
|
892
|
+
const onTimer = () => {
|
|
893
|
+
try {
|
|
894
|
+
stopWs.close();
|
|
895
|
+
} catch {
|
|
896
|
+
}
|
|
897
|
+
resolve();
|
|
898
|
+
};
|
|
899
|
+
abortTimer.addEventListener("abort", onTimer, { once: true });
|
|
900
|
+
stopWs.onopen = () => {
|
|
901
|
+
stopWs.send(JSON.stringify({ type: "stop", session_id: sessionId, reason: "n8n close" }));
|
|
902
|
+
};
|
|
903
|
+
stopWs.onmessage = (ev) => {
|
|
904
|
+
try {
|
|
905
|
+
const msg = JSON.parse(ev.data);
|
|
906
|
+
if (msg.type === "session_ended") {
|
|
907
|
+
abortTimer.removeEventListener("abort", onTimer);
|
|
908
|
+
try {
|
|
909
|
+
stopWs.close();
|
|
910
|
+
} catch {
|
|
911
|
+
}
|
|
912
|
+
resolve();
|
|
913
|
+
}
|
|
914
|
+
} catch {
|
|
915
|
+
}
|
|
916
|
+
};
|
|
917
|
+
stopWs.onerror = () => {
|
|
918
|
+
abortTimer.removeEventListener("abort", onTimer);
|
|
919
|
+
resolve();
|
|
920
|
+
};
|
|
921
|
+
stopWs.onclose = () => {
|
|
922
|
+
abortTimer.removeEventListener("abort", onTimer);
|
|
923
|
+
resolve();
|
|
924
|
+
};
|
|
925
|
+
});
|
|
705
926
|
out.push({ json: { closed: true, session_id: sessionId } });
|
|
706
927
|
break;
|
|
707
928
|
}
|
|
708
929
|
}
|
|
709
|
-
|
|
710
|
-
await client.close();
|
|
711
|
-
} else {
|
|
712
|
-
await client.disconnect();
|
|
713
|
-
}
|
|
930
|
+
needFullClose = op === "close";
|
|
714
931
|
} finally {
|
|
932
|
+
try {
|
|
933
|
+
if (needFullClose) {
|
|
934
|
+
await client.close();
|
|
935
|
+
} else {
|
|
936
|
+
await client.disconnect();
|
|
937
|
+
}
|
|
938
|
+
} catch {
|
|
939
|
+
}
|
|
715
940
|
}
|
|
716
941
|
}
|
|
717
942
|
return [out];
|