@ceki/n8n-nodes-ceki 0.2.30 → 0.2.31

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.
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../../nodes/recipes/CekiCaptchaScrape/CekiCaptchaScrape.node.ts", "../../../../lib/ceki-client.ts"],
4
- "sourcesContent": ["import type { IExecuteFunctions, INodeType, INodeTypeDescription, INodeExecutionData } from 'n8n-workflow';\nimport { CekiClient } from '../../../lib/ceki-client';\n\nconst sleep = (ms: number) => new Promise<void>((resolve) => {\n\tAbortSignal.timeout(ms).addEventListener('abort', () => resolve(), { once: true });\n});\n\nasync function waitForSelector(browser: any, selector: string, timeoutMs: number, intervalMs = 500) {\n\tconst expr = `!!document.querySelector(${JSON.stringify(selector)})`;\n\tconst deadline = Date.now() + timeoutMs;\n\tlet lastErr: unknown = null;\n\twhile (Date.now() < deadline) {\n\t\ttry {\n\t\t\tconst res = await browser.send('Runtime.evaluate', { expression: expr, returnByValue: true }) as any;\n\t\t\tif (res?.result?.value === true) return true;\n\t\t} catch (e) {\n\t\t\tlastErr = e;\n\t\t}\n\t\tawait sleep(intervalMs);\n\t}\n\tthrow new Error(\n\t\t`waitForSelector(\"${selector}\") timed out after ${timeoutMs}ms${lastErr ? `: ${(lastErr as Error).message}` : ''}`,\n\t);\n}\n\nasync function extractHtml(browser: any, selector: string): Promise<string> {\n\tconst expr =\n\t\tselector.trim() === '' || selector === 'body'\n\t\t\t? `document.body ? document.body.outerHTML : ''`\n\t\t\t: `(function(){ var el = document.querySelector(${JSON.stringify(selector)}); return el ? el.outerHTML : ''; })()`;\n\tconst res = await browser.send('Runtime.evaluate', { expression: expr, returnByValue: true }) as any;\n\treturn (res?.result?.value as string) ?? '';\n}\n\n/**\n * Recipe: Captcha-protected Scrape.\n * Rent a human browser \u2192 open a URL \u2192 (optional) wait for a selector \u2192\n * snapshot/HTML \u2192 release.\n *\n * The real human fingerprint bypasses anti-bot protection.\n */\nexport class CekiCaptchaScrape implements INodeType {\n\tdescription: INodeTypeDescription = {\n\t\tdisplayName: 'Browser Ceki: Captcha-protected Scrape',\n\t\tname: 'cekiCaptchaScrape',\n\t\tdescription: 'Rent a human browser, wait, screenshot/HTML, release',\n\t\ticon: 'file:ceki.png',\n\t\tgroup: ['transform'],\n\t\tversion: 1,\n\t\tsubtitle: '=rent in {{ $geo }} \u2192 snapshot',\n\t\tdefaults: { name: 'Browser Ceki: Captcha-protected Scrape' },\n\t\tinputs: ['main'],\n\t\toutputs: ['main'],\n\t\tcredentials: [{ name: 'cekiApi', required: true }],\n\t\tproperties: [\n\t\t\t{\n\t\t\t\tdisplayName: 'URL',\n\t\t\t\tname: 'url',\n\t\t\t\ttype: 'string',\n\t\t\t\tdefault: 'https://example.com',\n\t\t\t\trequired: true,\n\t\t\t\tdescription: 'Page protected by anti-bot / captcha',\n\t\t\t},\n\t\t\t{\n\t\t\t\tdisplayName: 'Geo',\n\t\t\t\tname: 'geo',\n\t\t\t\ttype: 'string',\n\t\t\t\tdefault: 'RU',\n\t\t\t\tplaceholder: 'RU, EE, US\u2026',\n\t\t\t},\n\t\t\t{ displayName: 'Max $/min', name: 'maxPrice', type: 'number', typeOptions: { numberPrecision: 4 }, default: 0.02 },\n\t\t\t{\n\t\t\t\tdisplayName: 'Wait for Selector',\n\t\t\t\tname: 'waitSelector',\n\t\t\t\ttype: 'string',\n\t\t\t\tdefault: '',\n\t\t\t\tplaceholder: 'CSS selector (optional)',\n\t\t\t\tdescription: 'Wait until this selector appears in the DOM',\n\t\t\t},\n\t\t\t{\n\t\t\t\tdisplayName: 'Wait Timeout (ms)',\n\t\t\t\tname: 'waitTimeout',\n\t\t\t\ttype: 'number',\n\t\t\t\tdefault: 30000,\n\t\t\t},\n\t\t\t{\n\t\t\t\tdisplayName: 'Extract HTML',\n\t\t\t\tname: 'extractHtml',\n\t\t\t\ttype: 'boolean',\n\t\t\t\tdefault: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tdisplayName: 'HTML Selector',\n\t\t\t\tname: 'htmlSelector',\n\t\t\t\ttype: 'string',\n\t\t\t\tdefault: 'body',\n\t\t\t\tplaceholder: 'CSS selector or \"body\"',\n\t\t\t\tdescription: 'outerHTML of this selector is returned as `html`',\n\t\t\t\tdisplayOptions: { show: { extractHtml: [true] } },\n\t\t\t},\n\t\t\t{\n\t\t\t\tdisplayName: 'Full Page Screenshot',\n\t\t\t\tname: 'fullPage',\n\t\t\t\ttype: 'boolean',\n\t\t\t\tdefault: false,\n\t\t\t},\n\t\t],\n\t};\n\n\tasync execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {\n\t\tconst items = this.getInputData();\n\t\tconst out: INodeExecutionData[] = [];\n\t\tconst creds = await this.getCredentials('cekiApi');\n\n\t\tfor (let i = 0; i < items.length; i++) {\n\t\t\tconst url = this.getNodeParameter('url', i) as string;\n\t\t\tconst geo = this.getNodeParameter('geo', i) as string;\n\t\t\tconst maxPrice = this.getNodeParameter('maxPrice', i) as number;\n\t\t\tconst waitSelector = (this.getNodeParameter('waitSelector', i) as string) || '';\n\t\t\tconst waitTimeout = this.getNodeParameter('waitTimeout', i) as number;\n\t\t\tconst extractHtmlFlag = this.getNodeParameter('extractHtml', i) as boolean;\n\t\t\tconst htmlSelector = (this.getNodeParameter('htmlSelector', i) as string) || 'body';\n\t\t\tconst fullPage = this.getNodeParameter('fullPage', i) as boolean;\n\n\t\t\tconst client = new CekiClient(creds.token as string);\n\t\t\tawait client.connect();\n\t\t\tlet browser: any;\n\t\t\tlet scheduleId = 0;\n\t\t\ttry {\n\t\t\t\tconst list = await client.search({ geo: geo || undefined, max_price_per_min: maxPrice });\n\t\t\t\tif (!list.length) throw new Error(`No browsers in geo ${geo || '*'}`);\n\t\t\t\tscheduleId = list[0].schedule_id;\n\t\t\t\tbrowser = await client.rent(scheduleId);\n\n\t\t\t\tawait browser.navigate(url);\n\n\t\t\t\tif (waitSelector) {\n\t\t\t\t\tawait waitForSelector(browser, waitSelector, waitTimeout);\n\t\t\t\t}\n\n\t\t\t\tconst shot = await browser.screenshot({ format: 'base64', fullPage }) as any;\n\t\t\t\tconst data = shot.data ?? (shot instanceof Buffer ? shot.toString('base64') : '');\n\t\t\t\tconst binary = await this.helpers.prepareBinaryData(\n\t\t\t\t\tBuffer.from(data, 'base64'),\n\t\t\t\t\t'captcha-scrape.png',\n\t\t\t\t\t'image/png',\n\t\t\t\t);\n\n\t\t\t\tconst json: Record<string, unknown> = {\n\t\t\t\t\turl,\n\t\t\t\t\tgeo,\n\t\t\t\t\tschedule_id: scheduleId,\n\t\t\t\t};\n\t\t\t\tif (extractHtmlFlag) {\n\t\t\t\t\tjson.html = await extractHtml(browser, htmlSelector);\n\t\t\t\t}\n\n\t\t\t\tout.push({ json: json as any, binary: { data: binary } });\n\t\t\t} finally {\n\t\t\t\tif (browser) {\n\t\t\t\t\ttry { await browser.close(); } catch { /* ignore */ }\n\t\t\t\t}\n\t\t\t\ttry { await client.close(); } catch { /* ignore */ }\n\t\t\t}\n\t\t}\n\t\treturn [out];\n\t}\n}\n", "/**\n * Minimal Ceki browser client \u2014 uses only native WebSocket + fetch (Node 22 globals).\n * Zero npm imports (no ws, no mime-types, no node built-in modules).\n *\n * Timeout management: uses AbortSignal.timeout() instead of restricted\n * setTimeout/clearTimeout globals to satisfy the n8n community-nodes\n * package scanner (no-restricted-globals rule).\n */\n\n// \u2500\u2500 types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface BrowserOption {\n\tschedule_id: number;\n\tgeo?: string;\n\tprice_per_min: number;\n\tonline: boolean;\n\trating?: number | null;\n}\n\nexport interface Match {\n\tsession_id: string;\n\tschedule_id: number;\n\tevent_id?: string | null;\n\tchat_topic_id?: string | null;\n\tprovider_user_id?: number | null;\n\tbrowser_info?: Record<string, unknown>;\n}\n\nexport interface RentOptions {\n\tmode?: 'main' | 'incognito';\n}\n\nexport interface ScreenshotOptions {\n\tformat?: 'png' | 'base64';\n\tfullPage?: boolean;\n}\n\n// \u2500\u2500 helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction jsonParse(raw: string): unknown {\n\ttry { return JSON.parse(raw); } catch { return null; }\n}\n\n// \u2500\u2500 Client \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class CekiClient {\n\tprivate _ws: WebSocket | null = null;\n\tprivate _connected = false;\n\tprivate _pendingRents = new Map<string, {\n\t\tresolve: (m: Match) => void;\n\t\treject: (e: Error) => void;\n\t}>();\n\tprivate _pendingResumes = new Map<string, {\n\t\tresolve: (m: Match) => void;\n\t\treject: (e: Error) => void;\n\t}>();\n\tprivate _pendingCdp = new Map<number, {\n\t\tresolve: (v: unknown) => void;\n\t\treject: (e: Error) => void;\n\t}>();\n\tprivate _cdpCounter = 1;\n\t_activeSessions = new Map<string, CekiBrowser>();\n\tprivate _connectReject: ((e: Error) => void) | null = null;\n\tprivate _closed = false;\n\n\tconstructor(\n\t\tprivate _token: string,\n\t\tprivate _relayUrl = 'wss://browser.ceki.me/ws/agent',\n\t\tprivate _apiUrl = 'https://api.ceki.me',\n\t) {}\n\n\t/** Connect to the relay WebSocket. */\n\tasync connect(): Promise<void> {\n\t\tif (this._connected) return;\n\t\tconst protocols = [`bearer.${this._token}`];\n\t\tthis._ws = new WebSocket(this._relayUrl, protocols);\n\t\tthis._ws.onopen = () => {\n\t\t\tthis._connected = true;\n\t\t};\n\t\tthis._ws.onmessage = (ev: MessageEvent) => {\n\t\t\tthis._handleMessage(ev.data);\n\t\t};\n\t\tthis._ws.onclose = (ev: CloseEvent) => {\n\t\t\tthis._connected = false;\n\t\t\tif ((ev.code === 4401 || ev.code === 4403) && this._connectReject) {\n\t\t\t\tthis._connectReject(new Error(`Auth failed: ${ev.reason || String(ev.code)}`));\n\t\t\t\tthis._connectReject = null;\n\t\t\t}\n\t\t};\n\t\tthis._ws.onerror = () => {\n\t\t\tif (!this._connected && this._connectReject) {\n\t\t\t\tthis._connectReject(new Error('WebSocket connection failed'));\n\t\t\t\tthis._connectReject = null;\n\t\t\t}\n\t\t};\n\t\tif (this._ws.readyState === WebSocket.CONNECTING) {\n\t\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\t\tif (!this._ws) { reject(new Error('No WebSocket')); return; }\n\t\t\t\tthis._connectReject = reject;\n\t\t\t\tthis._ws!.onopen = () => {\n\t\t\t\t\tthis._connected = true;\n\t\t\t\t\tresolve();\n\t\t\t\t};\n\t\t\t});\n\t\t}\n\t}\n\n\t_sendRaw(msg: Record<string, unknown>): void {\n\t\tif (!this._ws || this._ws.readyState !== WebSocket.OPEN) {\n\t\t\tthrow new Error('WebSocket not connected');\n\t\t}\n\t\tthis._ws.send(JSON.stringify(msg));\n\t}\n\n\t/** Search for available browser providers (HTTP GET). */\n\tasync search(filters?: Record<string, unknown>, limit?: number): Promise<BrowserOption[]> {\n\t\tconst params = new URLSearchParams();\n\t\tif (limit != null) params.set('limit', String(limit));\n\t\tif (filters) {\n\t\t\tfor (const [k, v] of Object.entries(filters)) {\n\t\t\t\tif (v != null) params.set(k, String(v));\n\t\t\t}\n\t\t}\n\t\tconst resp = await fetch(`${this._apiUrl}/api/browsers/search?${params}`, {\n\t\t\theaders: { Authorization: `Bearer ${this._token}` },\n\t\t});\n\t\tif (!resp.ok) throw new Error(`Search failed: ${resp.status}`);\n\t\tconst body = (await resp.json()) as Record<string, unknown>;\n\t\tconst data = (body.data ?? body) as unknown[];\n\t\treturn Array.isArray(data) ? data as BrowserOption[] : [];\n\t}\n\n\t/** Rent a browser by schedule_id. */\n\tasync rent(scheduleId: number, opts?: RentOptions): Promise<CekiBrowser> {\n\t\tconst msg: Record<string, unknown> = { type: 'rent', browser_id: scheduleId };\n\t\tif (opts?.mode) msg.mode = opts.mode;\n\t\tthis._sendRaw(msg);\n\t\treturn this._awaitRent(`rent:${scheduleId}`, scheduleId, 90000);\n\t}\n\n\t/** Resume an existing session. */\n\tasync resume(sessionId: string): Promise<CekiBrowser> {\n\t\tthis._sendRaw({ type: 'resume', session_id: sessionId });\n\t\treturn this._awaitResume(sessionId);\n\t}\n\n\t/** Close the WS connection \u2014 session stays alive in grace.\n\t * Waits for the close handshake so the relay processes the close\n\t * before a new connection with the same renterId is established\n\t * (avoids the close-handler race condition).\n\t */\n\tasync disconnect(): Promise<void> {\n\t\tthis._closed = true;\n\t\tthis._activeSessions.clear();\n\t\tthis._pendingRents.clear();\n\t\tthis._pendingResumes.clear();\n\t\tif (!this._ws) return;\n\t\tconst ws = this._ws;\n\t\tif (ws.readyState === WebSocket.CLOSED) {\n\t\t\tthis._ws = null;\n\t\t\treturn;\n\t\t}\n\t\tif (ws.readyState === WebSocket.CLOSING) {\n\t\t\tawait new Promise<void>(r => { ws.onclose = () => { this._connected = false; r(); }; });\n\t\t\tthis._ws = null;\n\t\t\treturn;\n\t\t}\n\t\t// OPEN \u2192 initiate close and wait for handshake (up to 5s safety timeout)\n\t\tawait new Promise<void>(r => {\n\t\t\tconst t = setTimeout(() => { r(); }, 5000);\n\t\t\tws.onclose = () => { this._connected = false; clearTimeout(t); r(); };\n\t\t\ttry { ws.close(); } catch { clearTimeout(t); r(); }\n\t\t});\n\t\tthis._ws = null;\n\t}\n\n\t/** Close everything. */\n\tasync close(): Promise<void> {\n\t\tawait this.disconnect();\n\t}\n\n\t// \u2500\u2500 private \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n\tprivate _awaitRent(key: string, scheduleId: number, timeoutMs: number): Promise<CekiBrowser> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst timeoutSignal = AbortSignal.timeout(timeoutMs);\n\t\t\ttimeoutSignal.addEventListener('abort', () => {\n\t\t\t\tthis._pendingRents.delete(key);\n\t\t\t\treject(new Error('Rent timed out'));\n\t\t\t}, { once: true });\n\t\t\tthis._pendingRents.set(key, {\n\t\t\t\tresolve: (match: Match) => {\n\t\t\t\t\tconst browser = new CekiBrowser(this, match);\n\t\t\t\t\tthis._activeSessions.set(browser.sessionId, browser);\n\t\t\t\t\tresolve(browser);\n\t\t\t\t},\n\t\t\t\treject: (err: Error) => {\n\t\t\t\t\treject(err);\n\t\t\t\t},\n\t\t\t});\n\t\t});\n\t}\n\n\tprivate _awaitResume(sessionId: string): Promise<CekiBrowser> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst timeoutSignal = AbortSignal.timeout(60000);\n\t\t\ttimeoutSignal.addEventListener('abort', () => {\n\t\t\t\tthis._pendingResumes.delete(sessionId);\n\t\t\t\treject(new Error('Resume timed out'));\n\t\t\t}, { once: true });\n\t\t\tthis._pendingResumes.set(sessionId, {\n\t\t\t\tresolve: (match: Match) => {\n\t\t\t\t\tconst browser = new CekiBrowser(this, match);\n\t\t\t\t\tthis._activeSessions.set(browser.sessionId, browser);\n\t\t\t\t\tresolve(browser);\n\t\t\t\t},\n\t\t\t\treject: (err: Error) => {\n\t\t\t\t\treject(err);\n\t\t\t\t},\n\t\t\t});\n\t\t});\n\t}\n\n\tprivate _closeWs(): void {\n\t\tif (this._ws) {\n\t\t\ttry { this._ws.onopen = null; this._ws.onmessage = null; this._ws.onclose = null; this._ws.onerror = null; this._ws.close(); } catch { /* ignore */ }\n\t\t\tthis._ws = null;\n\t\t}\n\t}\n\n\tprivate _handleMessage(data: unknown): void {\n\t\tconst msg = jsonParse(String(data)) as Record<string, unknown> | null;\n\t\tif (!msg || typeof msg !== 'object') return;\n\t\tconst type = String(msg.type ?? '');\n\t\tconst sid = msg.session_id ? String(msg.session_id) : null;\n\n\t\tswitch (type) {\n\t\t\tcase 'pong':\n\t\t\tcase 'rent_pending':\n\t\t\t\tbreak;\n\t\t\tcase 'match':\n\t\t\t\tthis._onMatch(msg);\n\t\t\t\tbreak;\n\t\t\tcase 'rent.error':\n\t\t\t\tthis._onRentError(msg);\n\t\t\t\tbreak;\n\t\t\tcase 'resume_ok':\n\t\t\t\tthis._onResumeOk(msg);\n\t\t\t\tbreak;\n\t\t\tcase 'resume_failed':\n\t\t\t\tthis._onResumeFailed(msg);\n\t\t\t\tbreak;\n\t\t\tcase 'cdp_response':\n\t\t\t\tif (sid) this._onCdpResponse(sid, msg);\n\t\t\t\tbreak;\n\t\t\tcase 'session.ended':\n\t\t\t\tif (sid) {\n\t\t\t\t\tthis._activeSessions.delete(sid);\n\t\t\t\t\tconst b = this._activeSessions.get(sid);\n\t\t\t\t\tif (b) (b as any)._ended = String(msg.reason ?? 'ended');\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tprivate _onMatch(msg: Record<string, unknown>): void {\n\t\tconst scheduleId = Number(msg.schedule_id ?? 0);\n\t\tconst eventId = msg.event_id ? String(msg.event_id) : null;\n\t\tconst sessionId = String(msg.session_id ?? '');\n\t\t// Send match_ack to transition session from ACCEPTED to ACTIVE.\n\t\t// Without this, subsequent resume from other n8n nodes would receive\n\t\t// \"match\" instead of \"resume_ok\" (session stays ACCEPTED).\n\t\tif (sessionId && this._ws?.readyState === WebSocket.OPEN) {\n\t\t\ttry { this._ws.send(JSON.stringify({ type: 'match_ack', session_id: sessionId })); } catch { /* ignore */ }\n\t\t}\n\t\t// Check pending rents (normal rent flow)\n\t\tlet pending = this._pendingRents.get(`event:${eventId}`);\n\t\tif (!pending) pending = this._pendingRents.get(`rent:${scheduleId}`);\n\t\tif (pending) {\n\t\t\tthis._pendingRents.delete(`event:${eventId}`);\n\t\t\tthis._pendingRents.delete(`rent:${scheduleId}`);\n\t\t\tpending.resolve({\n\t\t\t\tsession_id: sessionId,\n\t\t\t\tschedule_id: scheduleId,\n\t\t\t\tevent_id: eventId,\n\t\t\t\tchat_topic_id: msg.chat_topic_id ? String(msg.chat_topic_id) : null,\n\t\t\t\tprovider_user_id: msg.provider_user_id != null ? Number(msg.provider_user_id) : null,\n\t\t\t\tbrowser_info: (msg.browser_info ?? {}) as Record<string, unknown>,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\t// Fallback: relay may send \"match\" during resume if session is ACCEPTED\n\t\t// (e.g. when rent node finished without sending match_ack).\n\t\t// Treat it like resume_ok so the next node doesn't hang.\n\t\tconst resumePending = sessionId ? this._pendingResumes.get(sessionId) : null;\n\t\tif (resumePending) {\n\t\t\tthis._pendingResumes.delete(sessionId);\n\t\t\tresumePending.resolve({\n\t\t\t\tsession_id: sessionId,\n\t\t\t\tschedule_id: scheduleId,\n\t\t\t\tevent_id: eventId,\n\t\t\t\tchat_topic_id: msg.chat_topic_id ? String(msg.chat_topic_id) : null,\n\t\t\t\tprovider_user_id: msg.provider_user_id != null ? Number(msg.provider_user_id) : null,\n\t\t\t\tbrowser_info: (msg.browser_info ?? {}) as Record<string, unknown>,\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate _onRentError(msg: Record<string, unknown>): void {\n\t\tconst code = String(msg.code ?? '');\n\t\tconst message = String(msg.message ?? '');\n\t\tfor (const [key, pending] of this._pendingRents) {\n\t\t\tthis._pendingRents.delete(key);\n\t\t\tpending.reject(new Error(message || `Rent error: ${code}`));\n\t\t\treturn;\n\t\t}\n\t}\n\n\tprivate _onResumeOk(msg: Record<string, unknown>): void {\n\t\tconst sessionId = String(msg.session_id ?? '');\n\t\tconst pending = this._pendingResumes.get(sessionId);\n\t\tif (!pending) return;\n\t\tthis._pendingResumes.delete(sessionId);\n\t\tpending.resolve({\n\t\t\tsession_id: sessionId,\n\t\t\tschedule_id: Number(msg.schedule_id ?? 0),\n\t\t\tevent_id: msg.event_id ? String(msg.event_id) : null,\n\t\t\tchat_topic_id: msg.chat_topic_id ? String(msg.chat_topic_id) : null,\n\t\t\tprovider_user_id: msg.provider_user_id != null ? Number(msg.provider_user_id) : null,\n\t\t\tbrowser_info: (msg.browser_info ?? {}) as Record<string, unknown>,\n\t\t});\n\t}\n\n\tprivate _onResumeFailed(msg: Record<string, unknown>): void {\n\t\tconst sessionId = String(msg.session_id ?? '');\n\t\tconst pending = this._pendingResumes.get(sessionId);\n\t\tif (!pending) return;\n\t\tthis._pendingResumes.delete(sessionId);\n\t\tpending.reject(new Error(String(msg.reason ?? 'Resume failed')));\n\t}\n\n\tprivate _onCdpResponse(sessionId: string, msg: Record<string, unknown>): void {\n\t\tconst browser = this._activeSessions.get(sessionId);\n\t\tif (!browser) return;\n\t\tconst id = Number(msg.id ?? 0);\n\t\t// NOTE: pending CDP is stored on CekiBrowser._pendingCdp (set by CekiBrowser.send()),\n\t\t// NOT on CekiClient._pendingCdp \u2014 they are different maps.\n\t\tconst pending = browser._pendingCdp.get(id);\n\t\tif (!pending) return;\n\t\tbrowser._pendingCdp.delete(id);\n\t\tif (msg.error) {\n\t\t\tpending.reject(new Error(String((msg.error as Record<string, unknown>).message ?? 'CDP error')));\n\t\t} else {\n\t\t\tpending.resolve(msg.result);\n\t\t}\n\t}\n}\n\n// \u2500\u2500 Browser \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class CekiBrowser {\n\treadonly sessionId: string;\n\treadonly scheduleId: number;\n\treadonly chatTopicId: string | null;\n\treadonly browserInfo: Record<string, unknown>;\n\treadonly providerUserId: number | null;\n\n\tprivate _client: CekiClient;\n\tprivate _cdpId = 1;\n\t_pendingCdp = new Map<number, { resolve: (v: unknown) => void; reject: (e: Error) => void }>();\n\n\tconstructor(client: CekiClient, match: Match) {\n\t\tthis._client = client;\n\t\tthis.sessionId = match.session_id;\n\t\tthis.scheduleId = match.schedule_id;\n\t\tthis.chatTopicId = match.chat_topic_id ?? null;\n\t\tthis.browserInfo = match.browser_info ?? {};\n\t\tthis.providerUserId = match.provider_user_id ?? null;\n\t}\n\n\t/** Send a CDP command. Uses AbortSignal.timeout() (no restricted setTimeout global). */\n\tasync send(method: string, params?: Record<string, unknown>, timeoutMs = 30000): Promise<unknown> {\n\t\tconst id = this._cdpId++;\n\t\tconst msg = { type: 'cdp', session_id: this.sessionId, id, method, params: params ?? {} };\n\t\tthis._client._sendRaw(msg);\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst timeoutSignal = AbortSignal.timeout(timeoutMs);\n\t\t\ttimeoutSignal.addEventListener('abort', () => {\n\t\t\t\tthis._pendingCdp.delete(id);\n\t\t\t\treject(new Error(`CDP ${method} timed out after ${timeoutMs}ms`));\n\t\t\t}, { once: true });\n\t\t\tthis._pendingCdp.set(id, {\n\t\t\t\tresolve: (v) => { resolve(v); },\n\t\t\t\treject: (e) => { reject(e); },\n\t\t\t});\n\t\t});\n\t}\n\n\tasync navigate(url: string, timeoutMs?: number): Promise<void> {\n\t\tawait this.send('Page.navigate', { url }, timeoutMs);\n\t}\n\n\tasync click(x: number, y: number): Promise<void> {\n\t\tawait this.send('Input.dispatchMouseEvent', { type: 'mousePressed', x, y, button: 'left', clickCount: 1 });\n\t\tawait this.send('Input.dispatchMouseEvent', { type: 'mouseReleased', x, y, button: 'left', clickCount: 1 });\n\t}\n\n\tasync type(text: string): Promise<void> {\n\t\tawait this.send('Ceki.typeText', { text });\n\t}\n\n\tasync scroll(deltaY: number): Promise<void> {\n\t\tawait this.send('Input.dispatchMouseEvent', { type: 'mouseWheel', x: 0, y: 0, deltaX: 0, deltaY });\n\t}\n\n\tasync screenshot(opts?: ScreenshotOptions): Promise<{ data: string } | Buffer> {\n\t\tconst format = opts?.format ?? 'base64';\n\t\tconst fullPage = opts?.fullPage ?? false;\n\t\tlet clip: Record<string, number> | undefined;\n\t\tif (fullPage) {\n\t\t\tconst metrics = await this.send('Page.getLayoutMetrics') as Record<string, unknown> | null;\n\t\t\tconst contentSize = metrics?.contentSize as Record<string, number> | undefined;\n\t\t\tif (contentSize) {\n\t\t\t\tclip = { x: 0, y: 0, width: Number(contentSize.width ?? 1920), height: Math.min(Number(contentSize.height ?? 1080), 16384), scale: 1 };\n\t\t\t}\n\t\t}\n\t\tconst result = await this.send('Page.captureScreenshot', { format: 'png', ...(clip ? { clip } : {}) }) as Record<string, unknown> | null;\n\t\tconst data = String(result?.data ?? '');\n\t\tif (format === 'png') {\n\t\t\treturn Buffer.from(data, 'base64');\n\t\t}\n\t\treturn { data };\n\t}\n\n\tasync snapshot(): Promise<{ screenshot: string; ts: Date }> {\n\t\tconst ssResult = await this.screenshot({ format: 'base64' }) as { data: string };\n\t\treturn { screenshot: ssResult.data, ts: new Date() };\n\t}\n\n\tasync upload(selector: string, buf: Buffer, filename = 'file'): Promise<Record<string, unknown>> {\n\t\tconst b64 = buf.toString('base64');\n\t\tconst size = buf.length;\n\t\tconst mime = 'application/octet-stream';\n\t\tconst expression = `(function(){\n\t\t\tvar input = document.querySelector(${JSON.stringify(selector)});\n\t\t\tif (!input) return JSON.stringify({ok:false,error:'Element not found'});\n\t\t\tvar b = atob(${JSON.stringify(b64)});\n\t\t\tvar u = new Uint8Array(b.length);\n\t\t\tfor (var i = 0; i < b.length; i++) u[i] = b.charCodeAt(i);\n\t\t\tvar f = new File([u], ${JSON.stringify(filename)}, {type: ${JSON.stringify(mime)}});\n\t\t\tvar dt = new DataTransfer(); dt.items.add(f);\n\t\t\tinput.files = dt.files;\n\t\t\tinput.dispatchEvent(new Event('change',{bubbles:true}));\n\t\t\treturn JSON.stringify({ok:true,filename:${JSON.stringify(filename)},size:${size}});\n\t\t})()`;\n\t\tconst result = await this.send('Runtime.evaluate', { expression, returnByValue: true }) as Record<string, unknown> | null;\n\t\ttry {\n\t\t\tawait this.send('Input.dispatchKeyEvent', { type: 'keyDown', key: 'Escape', code: 'Escape', windowsVirtualKeyCode: 27, nativeVirtualKeyCode: 27 });\n\t\t\tawait this.send('Input.dispatchKeyEvent', { type: 'keyUp', key: 'Escape', code: 'Escape', windowsVirtualKeyCode: 27, nativeVirtualKeyCode: 27 });\n\t\t} catch { /* ignore */ }\n\t\tconst resultObj = result?.result as Record<string, unknown> | undefined;\n\t\tif (resultObj?.value) return JSON.parse(String(resultObj.value)) as Record<string, unknown>;\n\t\treturn { ok: true, filename, size };\n\t}\n\n\tasync close(): Promise<void> {\n\t\tawait this.send('Ceki.close', {}).catch(() => {});\n\t\tthis._client._activeSessions.delete(this.sessionId);\n\t}\n}\n"],
5
- "mappings": "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",
6
- "names": []
7
- }
@@ -1,9 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
2
- <circle cx="12" cy="12" r="11" fill="#E36E4B"/>
3
- <circle cx="16.2" cy="8.3" r="8.5" fill="#1a1a2e"/>
4
- <g transform="translate(17, 7) rotate(45)">
5
- <line x1="0" y1="-5" x2="0" y2="6" stroke="#E36E4B" stroke-width="1.8" stroke-linecap="round"/>
6
- <line x1="-2.5" y1="0" x2="2.5" y2="0" stroke="#E36E4B" stroke-width="1.8" stroke-linecap="round"/>
7
- <line x1="-1.2" y1="-3.5" x2="1.2" y2="-3.5" stroke="#E36E4B" stroke-width="1.4" stroke-linecap="round"/>
8
- </g>
9
- </svg>
@@ -1,9 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
2
- <circle cx="12" cy="12" r="11" fill="#E36E4B"/>
3
- <circle cx="16.2" cy="8.3" r="8.5" fill="#fff"/>
4
- <g transform="translate(17, 7) rotate(45)">
5
- <line x1="0" y1="-5" x2="0" y2="6" stroke="#E36E4B" stroke-width="1.8" stroke-linecap="round"/>
6
- <line x1="-2.5" y1="0" x2="2.5" y2="0" stroke="#E36E4B" stroke-width="1.8" stroke-linecap="round"/>
7
- <line x1="-1.2" y1="-3.5" x2="1.2" y2="-3.5" stroke="#E36E4B" stroke-width="1.4" stroke-linecap="round"/>
8
- </g>
9
- </svg>
@@ -1,514 +0,0 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // nodes/recipes/CekiScreenshotGeo/CekiScreenshotGeo.node.ts
21
- var CekiScreenshotGeo_node_exports = {};
22
- __export(CekiScreenshotGeo_node_exports, {
23
- CekiScreenshotGeo: () => CekiScreenshotGeo
24
- });
25
- module.exports = __toCommonJS(CekiScreenshotGeo_node_exports);
26
-
27
- // lib/ceki-client.ts
28
- function jsonParse(raw) {
29
- try {
30
- return JSON.parse(raw);
31
- } catch {
32
- return null;
33
- }
34
- }
35
- var CekiClient = class {
36
- constructor(_token, _relayUrl = "wss://browser.ceki.me/ws/agent", _apiUrl = "https://api.ceki.me") {
37
- this._token = _token;
38
- this._relayUrl = _relayUrl;
39
- this._apiUrl = _apiUrl;
40
- this._ws = null;
41
- this._connected = false;
42
- this._pendingRents = /* @__PURE__ */ new Map();
43
- this._pendingResumes = /* @__PURE__ */ new Map();
44
- this._pendingCdp = /* @__PURE__ */ new Map();
45
- this._cdpCounter = 1;
46
- this._activeSessions = /* @__PURE__ */ new Map();
47
- this._connectReject = null;
48
- this._closed = false;
49
- }
50
- /** Connect to the relay WebSocket. */
51
- async connect() {
52
- if (this._connected) return;
53
- const protocols = [`bearer.${this._token}`];
54
- this._ws = new WebSocket(this._relayUrl, protocols);
55
- this._ws.onopen = () => {
56
- this._connected = true;
57
- };
58
- this._ws.onmessage = (ev) => {
59
- this._handleMessage(ev.data);
60
- };
61
- this._ws.onclose = (ev) => {
62
- this._connected = false;
63
- if ((ev.code === 4401 || ev.code === 4403) && this._connectReject) {
64
- this._connectReject(new Error(`Auth failed: ${ev.reason || String(ev.code)}`));
65
- this._connectReject = null;
66
- }
67
- };
68
- this._ws.onerror = () => {
69
- if (!this._connected && this._connectReject) {
70
- this._connectReject(new Error("WebSocket connection failed"));
71
- this._connectReject = null;
72
- }
73
- };
74
- if (this._ws.readyState === WebSocket.CONNECTING) {
75
- await new Promise((resolve, reject) => {
76
- if (!this._ws) {
77
- reject(new Error("No WebSocket"));
78
- return;
79
- }
80
- this._connectReject = reject;
81
- this._ws.onopen = () => {
82
- this._connected = true;
83
- resolve();
84
- };
85
- });
86
- }
87
- }
88
- _sendRaw(msg) {
89
- if (!this._ws || this._ws.readyState !== WebSocket.OPEN) {
90
- throw new Error("WebSocket not connected");
91
- }
92
- this._ws.send(JSON.stringify(msg));
93
- }
94
- /** Search for available browser providers (HTTP GET). */
95
- async search(filters, limit) {
96
- const params = new URLSearchParams();
97
- if (limit != null) params.set("limit", String(limit));
98
- if (filters) {
99
- for (const [k, v] of Object.entries(filters)) {
100
- if (v != null) params.set(k, String(v));
101
- }
102
- }
103
- const resp = await fetch(`${this._apiUrl}/api/browsers/search?${params}`, {
104
- headers: { Authorization: `Bearer ${this._token}` }
105
- });
106
- if (!resp.ok) throw new Error(`Search failed: ${resp.status}`);
107
- const body = await resp.json();
108
- const data = body.data ?? body;
109
- return Array.isArray(data) ? data : [];
110
- }
111
- /** Rent a browser by schedule_id. */
112
- async rent(scheduleId, opts) {
113
- const msg = { type: "rent", browser_id: scheduleId };
114
- if (opts?.mode) msg.mode = opts.mode;
115
- this._sendRaw(msg);
116
- return this._awaitRent(`rent:${scheduleId}`, scheduleId, 9e4);
117
- }
118
- /** Resume an existing session. */
119
- async resume(sessionId) {
120
- this._sendRaw({ type: "resume", session_id: sessionId });
121
- return this._awaitResume(sessionId);
122
- }
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() {
129
- this._closed = true;
130
- this._activeSessions.clear();
131
- this._pendingRents.clear();
132
- this._pendingResumes.clear();
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;
166
- }
167
- /** Close everything. */
168
- async close() {
169
- await this.disconnect();
170
- }
171
- // ── private ────────────────────────────────────────────────
172
- _awaitRent(key, scheduleId, timeoutMs) {
173
- return new Promise((resolve, reject) => {
174
- const timeoutSignal = AbortSignal.timeout(timeoutMs);
175
- timeoutSignal.addEventListener("abort", () => {
176
- this._pendingRents.delete(key);
177
- reject(new Error("Rent timed out"));
178
- }, { once: true });
179
- this._pendingRents.set(key, {
180
- resolve: (match) => {
181
- const browser = new CekiBrowser(this, match);
182
- this._activeSessions.set(browser.sessionId, browser);
183
- resolve(browser);
184
- },
185
- reject: (err) => {
186
- reject(err);
187
- }
188
- });
189
- });
190
- }
191
- _awaitResume(sessionId) {
192
- return new Promise((resolve, reject) => {
193
- const timeoutSignal = AbortSignal.timeout(6e4);
194
- timeoutSignal.addEventListener("abort", () => {
195
- this._pendingResumes.delete(sessionId);
196
- reject(new Error("Resume timed out"));
197
- }, { once: true });
198
- this._pendingResumes.set(sessionId, {
199
- resolve: (match) => {
200
- const browser = new CekiBrowser(this, match);
201
- this._activeSessions.set(browser.sessionId, browser);
202
- resolve(browser);
203
- },
204
- reject: (err) => {
205
- reject(err);
206
- }
207
- });
208
- });
209
- }
210
- _closeWs() {
211
- if (this._ws) {
212
- try {
213
- this._ws.onopen = null;
214
- this._ws.onmessage = null;
215
- this._ws.onclose = null;
216
- this._ws.onerror = null;
217
- this._ws.close();
218
- } catch {
219
- }
220
- this._ws = null;
221
- }
222
- }
223
- _handleMessage(data) {
224
- const msg = jsonParse(String(data));
225
- if (!msg || typeof msg !== "object") return;
226
- const type = String(msg.type ?? "");
227
- const sid = msg.session_id ? String(msg.session_id) : null;
228
- switch (type) {
229
- case "pong":
230
- case "rent_pending":
231
- break;
232
- case "match":
233
- this._onMatch(msg);
234
- break;
235
- case "rent.error":
236
- this._onRentError(msg);
237
- break;
238
- case "resume_ok":
239
- this._onResumeOk(msg);
240
- break;
241
- case "resume_failed":
242
- this._onResumeFailed(msg);
243
- break;
244
- case "cdp_response":
245
- if (sid) this._onCdpResponse(sid, msg);
246
- break;
247
- case "session.ended":
248
- if (sid) {
249
- this._activeSessions.delete(sid);
250
- const b = this._activeSessions.get(sid);
251
- if (b) b._ended = String(msg.reason ?? "ended");
252
- }
253
- break;
254
- }
255
- }
256
- _onMatch(msg) {
257
- const scheduleId = Number(msg.schedule_id ?? 0);
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
- }
266
- let pending = this._pendingRents.get(`event:${eventId}`);
267
- if (!pending) pending = this._pendingRents.get(`rent:${scheduleId}`);
268
- if (pending) {
269
- this._pendingRents.delete(`event:${eventId}`);
270
- this._pendingRents.delete(`rent:${scheduleId}`);
271
- pending.resolve({
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,
286
- schedule_id: scheduleId,
287
- event_id: eventId,
288
- chat_topic_id: msg.chat_topic_id ? String(msg.chat_topic_id) : null,
289
- provider_user_id: msg.provider_user_id != null ? Number(msg.provider_user_id) : null,
290
- browser_info: msg.browser_info ?? {}
291
- });
292
- }
293
- }
294
- _onRentError(msg) {
295
- const code = String(msg.code ?? "");
296
- const message = String(msg.message ?? "");
297
- for (const [key, pending] of this._pendingRents) {
298
- this._pendingRents.delete(key);
299
- pending.reject(new Error(message || `Rent error: ${code}`));
300
- return;
301
- }
302
- }
303
- _onResumeOk(msg) {
304
- const sessionId = String(msg.session_id ?? "");
305
- const pending = this._pendingResumes.get(sessionId);
306
- if (!pending) return;
307
- this._pendingResumes.delete(sessionId);
308
- pending.resolve({
309
- session_id: sessionId,
310
- schedule_id: Number(msg.schedule_id ?? 0),
311
- event_id: msg.event_id ? String(msg.event_id) : null,
312
- chat_topic_id: msg.chat_topic_id ? String(msg.chat_topic_id) : null,
313
- provider_user_id: msg.provider_user_id != null ? Number(msg.provider_user_id) : null,
314
- browser_info: msg.browser_info ?? {}
315
- });
316
- }
317
- _onResumeFailed(msg) {
318
- const sessionId = String(msg.session_id ?? "");
319
- const pending = this._pendingResumes.get(sessionId);
320
- if (!pending) return;
321
- this._pendingResumes.delete(sessionId);
322
- pending.reject(new Error(String(msg.reason ?? "Resume failed")));
323
- }
324
- _onCdpResponse(sessionId, msg) {
325
- const browser = this._activeSessions.get(sessionId);
326
- if (!browser) return;
327
- const id = Number(msg.id ?? 0);
328
- const pending = browser._pendingCdp.get(id);
329
- if (!pending) return;
330
- browser._pendingCdp.delete(id);
331
- if (msg.error) {
332
- pending.reject(new Error(String(msg.error.message ?? "CDP error")));
333
- } else {
334
- pending.resolve(msg.result);
335
- }
336
- }
337
- };
338
- var CekiBrowser = class {
339
- constructor(client, match) {
340
- this._cdpId = 1;
341
- this._pendingCdp = /* @__PURE__ */ new Map();
342
- this._client = client;
343
- this.sessionId = match.session_id;
344
- this.scheduleId = match.schedule_id;
345
- this.chatTopicId = match.chat_topic_id ?? null;
346
- this.browserInfo = match.browser_info ?? {};
347
- this.providerUserId = match.provider_user_id ?? null;
348
- }
349
- /** Send a CDP command. Uses AbortSignal.timeout() (no restricted setTimeout global). */
350
- async send(method, params, timeoutMs = 3e4) {
351
- const id = this._cdpId++;
352
- const msg = { type: "cdp", session_id: this.sessionId, id, method, params: params ?? {} };
353
- this._client._sendRaw(msg);
354
- return new Promise((resolve, reject) => {
355
- const timeoutSignal = AbortSignal.timeout(timeoutMs);
356
- timeoutSignal.addEventListener("abort", () => {
357
- this._pendingCdp.delete(id);
358
- reject(new Error(`CDP ${method} timed out after ${timeoutMs}ms`));
359
- }, { once: true });
360
- this._pendingCdp.set(id, {
361
- resolve: (v) => {
362
- resolve(v);
363
- },
364
- reject: (e) => {
365
- reject(e);
366
- }
367
- });
368
- });
369
- }
370
- async navigate(url, timeoutMs) {
371
- await this.send("Page.navigate", { url }, timeoutMs);
372
- }
373
- async click(x, y) {
374
- await this.send("Input.dispatchMouseEvent", { type: "mousePressed", x, y, button: "left", clickCount: 1 });
375
- await this.send("Input.dispatchMouseEvent", { type: "mouseReleased", x, y, button: "left", clickCount: 1 });
376
- }
377
- async type(text) {
378
- await this.send("Ceki.typeText", { text });
379
- }
380
- async scroll(deltaY) {
381
- await this.send("Input.dispatchMouseEvent", { type: "mouseWheel", x: 0, y: 0, deltaX: 0, deltaY });
382
- }
383
- async screenshot(opts) {
384
- const format = opts?.format ?? "base64";
385
- const fullPage = opts?.fullPage ?? false;
386
- let clip;
387
- if (fullPage) {
388
- const metrics = await this.send("Page.getLayoutMetrics");
389
- const contentSize = metrics?.contentSize;
390
- if (contentSize) {
391
- clip = { x: 0, y: 0, width: Number(contentSize.width ?? 1920), height: Math.min(Number(contentSize.height ?? 1080), 16384), scale: 1 };
392
- }
393
- }
394
- const result = await this.send("Page.captureScreenshot", { format: "png", ...clip ? { clip } : {} });
395
- const data = String(result?.data ?? "");
396
- if (format === "png") {
397
- return Buffer.from(data, "base64");
398
- }
399
- return { data };
400
- }
401
- async snapshot() {
402
- const ssResult = await this.screenshot({ format: "base64" });
403
- return { screenshot: ssResult.data, ts: /* @__PURE__ */ new Date() };
404
- }
405
- async upload(selector, buf, filename = "file") {
406
- const b64 = buf.toString("base64");
407
- const size = buf.length;
408
- const mime = "application/octet-stream";
409
- const expression = `(function(){
410
- var input = document.querySelector(${JSON.stringify(selector)});
411
- if (!input) return JSON.stringify({ok:false,error:'Element not found'});
412
- var b = atob(${JSON.stringify(b64)});
413
- var u = new Uint8Array(b.length);
414
- for (var i = 0; i < b.length; i++) u[i] = b.charCodeAt(i);
415
- var f = new File([u], ${JSON.stringify(filename)}, {type: ${JSON.stringify(mime)}});
416
- var dt = new DataTransfer(); dt.items.add(f);
417
- input.files = dt.files;
418
- input.dispatchEvent(new Event('change',{bubbles:true}));
419
- return JSON.stringify({ok:true,filename:${JSON.stringify(filename)},size:${size}});
420
- })()`;
421
- const result = await this.send("Runtime.evaluate", { expression, returnByValue: true });
422
- try {
423
- await this.send("Input.dispatchKeyEvent", { type: "keyDown", key: "Escape", code: "Escape", windowsVirtualKeyCode: 27, nativeVirtualKeyCode: 27 });
424
- await this.send("Input.dispatchKeyEvent", { type: "keyUp", key: "Escape", code: "Escape", windowsVirtualKeyCode: 27, nativeVirtualKeyCode: 27 });
425
- } catch {
426
- }
427
- const resultObj = result?.result;
428
- if (resultObj?.value) return JSON.parse(String(resultObj.value));
429
- return { ok: true, filename, size };
430
- }
431
- async close() {
432
- await this.send("Ceki.close", {}).catch(() => {
433
- });
434
- this._client._activeSessions.delete(this.sessionId);
435
- }
436
- };
437
-
438
- // nodes/recipes/CekiScreenshotGeo/CekiScreenshotGeo.node.ts
439
- var CekiScreenshotGeo = class {
440
- constructor() {
441
- this.description = {
442
- displayName: "Browser Ceki: Screenshot in Geo",
443
- name: "cekiScreenshotGeo",
444
- description: "Rent a browser in a given geo, screenshot a page, release",
445
- icon: "file:ceki.png",
446
- group: ["transform"],
447
- version: 1,
448
- subtitle: "=rent in {{ $geo }} \u2192 screenshot \u2192 release",
449
- defaults: { name: "Browser Ceki: Screenshot in Geo" },
450
- inputs: ["main"],
451
- outputs: ["main"],
452
- credentials: [{ name: "cekiApi", required: true }],
453
- properties: [
454
- {
455
- displayName: "URL",
456
- name: "url",
457
- type: "string",
458
- default: "https://ifconfig.me",
459
- required: true
460
- },
461
- { displayName: "Geo", name: "geo", type: "string", default: "RU", placeholder: "RU, EE, US\u2026" },
462
- { displayName: "Full page", name: "fullPage", type: "boolean", default: false },
463
- { displayName: "Max $/min", name: "maxPrice", type: "number", default: 0.02 }
464
- ]
465
- };
466
- }
467
- async execute() {
468
- const items = this.getInputData();
469
- const out = [];
470
- const creds = await this.getCredentials("cekiApi");
471
- for (let i = 0; i < items.length; i++) {
472
- const url = this.getNodeParameter("url", i);
473
- const geo = this.getNodeParameter("geo", i);
474
- const fullPage = this.getNodeParameter("fullPage", i);
475
- const maxPrice = this.getNodeParameter("maxPrice", i);
476
- const client = new CekiClient(creds.token);
477
- await client.connect();
478
- let browser;
479
- try {
480
- const list = await client.search({ geo, max_price_per_min: maxPrice });
481
- if (!list.length) throw new Error(`No browsers in geo ${geo}`);
482
- browser = await client.rent(list[0].schedule_id);
483
- await browser.navigate(url);
484
- const shot = await browser.screenshot({ format: "base64", fullPage });
485
- const binary = await this.helpers.prepareBinaryData(
486
- Buffer.from(shot.data, "base64"),
487
- "screenshot.png",
488
- "image/png"
489
- );
490
- out.push({
491
- json: { url, geo, schedule_id: list[0].schedule_id },
492
- binary: { data: binary }
493
- });
494
- } finally {
495
- if (browser) {
496
- try {
497
- await browser.close();
498
- } catch {
499
- }
500
- }
501
- try {
502
- await client.close();
503
- } catch {
504
- }
505
- }
506
- }
507
- return [out];
508
- }
509
- };
510
- // Annotate the CommonJS export names for ESM import in node:
511
- 0 && (module.exports = {
512
- CekiScreenshotGeo
513
- });
514
- //# sourceMappingURL=CekiScreenshotGeo.node.js.map