@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 +6 -6
- package/dist/index.js +85 -238
- package/dist/index.js.map +3 -3
- package/dist/nodes/BrowserCeki/BrowserCeki.node.js +85 -238
- 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 +8 -64
- package/dist/nodes/recipes/CekiCaptchaScrape/CekiCaptchaScrape.node.js.map +2 -2
- package/dist/nodes/recipes/CekiScreenshotGeo/CekiScreenshotGeo.node.js +8 -64
- package/dist/nodes/recipes/CekiScreenshotGeo/CekiScreenshotGeo.node.js.map +2 -2
- package/package.json +1 -1
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://
|
|
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://
|
|
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://
|
|
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://
|
|
115
|
-
- [MCP](https://
|
|
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://
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
170
|
-
|
|
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:
|
|
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 =
|
|
273
|
+
const pending = this._pendingCdp.get(id);
|
|
330
274
|
if (!pending) return;
|
|
331
|
-
|
|
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(
|
|
375
|
-
await this.send("Input.dispatchMouseEvent", { type: "mousePressed", x
|
|
376
|
-
await this.send("Input.dispatchMouseEvent", { type: "mouseReleased", x
|
|
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: " + $
|
|
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: ["
|
|
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
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
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
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
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
|
-
|
|
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
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
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()
|
|
753
|
-
});
|
|
677
|
+
await client.close();
|
|
754
678
|
}
|
|
755
679
|
continue;
|
|
756
680
|
}
|
|
757
681
|
const sessionId = this.getNodeParameter("sessionId", i);
|
|
758
|
-
|
|
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
|
-
|
|
767
|
-
|
|
768
|
-
|
|
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
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
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
|
-
|
|
788
|
-
|
|
789
|
-
|
|
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
|
-
|
|
798
|
-
|
|
799
|
-
|
|
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
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
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
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
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
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
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
|
-
|
|
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
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
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];
|