From abcbfbb7d8ba1af31799bd84fffe87abd61af0cd Mon Sep 17 00:00:00 2001 From: GTFalcao Date: Fri, 3 Oct 2025 18:23:27 -0300 Subject: [PATCH 1/7] Initial implementations for actions --- .../actions/create-session/create-session.mjs | 93 +++++++++++ .../actions/create-window/create-window.mjs | 44 +++++ .../airtop/actions/load-url/load-url.mjs | 76 +++++++++ .../airtop/actions/query-page/query-page.mjs | 86 ++++++++++ .../actions/scrape-content/scrape-content.mjs | 86 ++++++++++ .../terminate-session/terminate-session.mjs | 30 ++++ components/airtop/airtop.app.mjs | 152 +++++++++++++++++- components/airtop/common/utils.mjs | 23 +++ 8 files changed, 585 insertions(+), 5 deletions(-) create mode 100644 components/airtop/actions/create-session/create-session.mjs create mode 100644 components/airtop/actions/create-window/create-window.mjs create mode 100644 components/airtop/actions/load-url/load-url.mjs create mode 100644 components/airtop/actions/query-page/query-page.mjs create mode 100644 components/airtop/actions/scrape-content/scrape-content.mjs create mode 100644 components/airtop/actions/terminate-session/terminate-session.mjs create mode 100644 components/airtop/common/utils.mjs diff --git a/components/airtop/actions/create-session/create-session.mjs b/components/airtop/actions/create-session/create-session.mjs new file mode 100644 index 0000000000000..58ff2e6b61fcb --- /dev/null +++ b/components/airtop/actions/create-session/create-session.mjs @@ -0,0 +1,93 @@ +import { parseObjectEntries } from "../../common/utils.mjs"; +import app from "../../airtop.app.mjs"; + +export default { + key: "airtop-create-session", + name: "Create Session", + description: "Create a new cloud browser session. [See the documentation](https://docs.airtop.ai/api-reference/airtop-api/sessions/create)", + version: "0.0.1", + type: "action", + props: { + app, + profileName: { + type: "string", + label: "Profile Name", + description: "Name of a profile to load into the session", + optional: true, + }, + saveProfileOnTermination: { + type: "boolean", + label: "Save Profile on Termination", + description: "If enabled, [the profile will be saved when the session terminates](https://docs.airtop.ai/api-reference/airtop-api/sessions/save-profile-on-termination). Only relevant if `Profile Name` is provided", + optional: true, + }, + timeoutMinutes: { + type: "integer", + label: "Timeout (Minutes)", + description: "Number of minutes of inactivity (idle timeout) after which the session will terminate", + optional: true, + }, + record: { + type: "boolean", + label: "Record", + description: "Whether to enable session recording", + optional: true, + }, + solveCaptcha: { + type: "boolean", + label: "Solve Captcha", + description: "Whether to automatically solve captcha challenges", + optional: true, + }, + additionalOptions: { + type: "object", + label: "Additional Options", + description: "Additional configuration parameters to send in the request. [See the documentation](https://docs.airtop.ai/api-reference/airtop-api/sessions/create) for available parameters (e.g., `proxy`). Values will be parsed as JSON where applicable.", + optional: true, + }, + }, + async run({ $ }) { + const { + profileName, + saveProfileOnTermination, + timeoutMinutes, + record, + solveCaptcha, + additionalOptions, + } = this; + + const data = { + configuration: { + profileName, + timeoutMinutes, + record, + solveCaptcha, + ...parseObjectEntries(additionalOptions), + }, + }; + + const response = await this.app.createSession({ + $, + data, + }); + + const sessionId = response.data?.id; + const status = response.data?.status; + + let saveProfileOnTerminationResponse; + if (saveProfileOnTermination && profileName) { + saveProfileOnTerminationResponse = await this.app.saveProfileOnTermination({ + $, + sessionId, + profileName, + }); + } + + $.export("$summary", `Successfully created session \`${sessionId}\` with status: ${status}`); + return { + response, + saveProfileOnTerminationResponse, + }; + }, +}; + diff --git a/components/airtop/actions/create-window/create-window.mjs b/components/airtop/actions/create-window/create-window.mjs new file mode 100644 index 0000000000000..59f4fa43fdac6 --- /dev/null +++ b/components/airtop/actions/create-window/create-window.mjs @@ -0,0 +1,44 @@ +import app from "../../airtop.app.mjs"; + +export default { + key: "airtop-create-window", + name: "Create Window", + description: "Create a new browser window in an active session. [See the documentation](https://docs.airtop.ai/api-reference/airtop-api/windows/create)", + version: "0.0.1", + type: "action", + props: { + app, + sessionId: { + propDefinition: [ + app, + "sessionId", + ], + }, + url: { + type: "string", + label: "Initial URL", + description: "Optional URL to navigate to immediately after creating the window", + optional: true, + }, + }, + async run({ $ }) { + const { + sessionId, + url, + } = this; + + const response = await this.app.createWindow({ + $, + sessionId, + data: { + url, + }, + }); + + const windowId = response.data?.id; + + $.export("$summary", `Successfully created window \`${windowId}\` in session \`${sessionId}\``); + return response; + }, +}; + diff --git a/components/airtop/actions/load-url/load-url.mjs b/components/airtop/actions/load-url/load-url.mjs new file mode 100644 index 0000000000000..e9b0b84560e64 --- /dev/null +++ b/components/airtop/actions/load-url/load-url.mjs @@ -0,0 +1,76 @@ +import app from "../../airtop.app.mjs"; + +export default { + key: "airtop-load-url", + name: "Load URL", + description: "Navigate a browser window to a specific URL. [See the documentation](https://docs.airtop.ai/api-reference/airtop-api/windows/load-url)", + version: "0.0.1", + type: "action", + props: { + app, + sessionId: { + propDefinition: [ + app, + "sessionId", + ], + }, + windowId: { + propDefinition: [ + app, + "windowId", + ({ sessionId }) => ({ + sessionId, + }), + ], + }, + url: { + type: "string", + label: "URL", + description: "The URL to navigate to (must be a valid URI)", + }, + waitUntil: { + type: "string", + label: "Wait Until", + description: "Wait until the specified loading event occurs. `load` waits until the page DOM and assets have loaded. `domContentLoaded` waits until the DOM has loaded. `complete` waits until the page and all its iframes have loaded. `noWait` returns immediately.", + optional: true, + options: [ + "load", + "domContentLoaded", + "complete", + "noWait", + ], + default: "load", + }, + waitUntilTimeoutSeconds: { + type: "integer", + label: "Wait Timeout (Seconds)", + description: "Maximum time in seconds to wait for the specified loading event. If the timeout is reached, the operation will still succeed but return a warning.", + optional: true, + default: 30, + }, + }, + async run({ $ }) { + const { + sessionId, + windowId, + url, + waitUntil, + waitUntilTimeoutSeconds, + } = this; + + const response = await this.app.loadUrl({ + $, + sessionId, + windowId, + data: { + url, + waitUntil, + waitUntilTimeoutSeconds, + }, + }); + + $.export("$summary", `Successfully navigated to ${url}`); + return response; + }, +}; + diff --git a/components/airtop/actions/query-page/query-page.mjs b/components/airtop/actions/query-page/query-page.mjs new file mode 100644 index 0000000000000..fe42e338ba6ef --- /dev/null +++ b/components/airtop/actions/query-page/query-page.mjs @@ -0,0 +1,86 @@ +import app from "../../airtop.app.mjs"; + +export default { + key: "airtop-query-page", + name: "Query Page", + description: "Extract data or ask questions about page content using AI. Submit a natural language prompt to query the content of a browser window. [See the documentation](https://docs.airtop.ai/api-reference/airtop-api/windows/page-query)", + version: "0.0.1", + type: "action", + props: { + app, + sessionId: { + propDefinition: [ + app, + "sessionId", + ], + }, + windowId: { + propDefinition: [ + app, + "windowId", + ({ sessionId }) => ({ + sessionId, + }), + ], + }, + prompt: { + type: "string", + label: "Prompt", + description: "Natural language prompt to submit about the page content. For example: 'Extract all product names and prices' or 'Is the user logged in?'", + }, + followPaginationLinks: { + type: "boolean", + label: "Follow Pagination Links", + description: "Make a best effort attempt to load more content items than are originally displayed on the page by following pagination links, clicking controls to load more content, or utilizing infinite scrolling. This can be more costly but may be necessary for sites requiring additional interaction.", + optional: true, + default: false, + }, + costThresholdCredits: { + type: "integer", + label: "Cost Threshold (Credits)", + description: "A credit threshold that, once exceeded, will cause the operation to be cancelled. This is not a hard limit but checked periodically. Set to 0 to disable (not recommended).", + optional: true, + }, + timeThresholdSeconds: { + type: "integer", + label: "Time Threshold (Seconds)", + description: "A time threshold that, once exceeded, will cause the operation to be cancelled. This is checked periodically. Set to 0 to disable (not recommended).", + optional: true, + }, + }, + async run({ $ }) { + const { + sessionId, + windowId, + prompt, + followPaginationLinks, + costThresholdCredits, + timeThresholdSeconds, + } = this; + + const response = await this.app.queryPage({ + $, + sessionId, + windowId, + data: { + prompt, + configuration: { + followPaginationLinks, + costThresholdCredits, + timeThresholdSeconds, + }, + }, + }); + + const creditsUsed = response.meta?.usage?.credits; + + let summary = "Successfully queried page"; + if (creditsUsed) { + summary += ` (${creditsUsed} credits used)`; + } + + $.export("$summary", summary); + return response; + }, +}; + diff --git a/components/airtop/actions/scrape-content/scrape-content.mjs b/components/airtop/actions/scrape-content/scrape-content.mjs new file mode 100644 index 0000000000000..9008d0a5469ef --- /dev/null +++ b/components/airtop/actions/scrape-content/scrape-content.mjs @@ -0,0 +1,86 @@ +import app from "../../airtop.app.mjs"; + +export default { + key: "airtop-scrape-content", + name: "Scrape Content", + description: "Scrape structured content from a web page using AI. [See the documentation](https://docs.airtop.ai/api-reference/airtop-api/windows/scrape-content)", + version: "0.0.1", + type: "action", + props: { + app, + sessionId: { + propDefinition: [ + app, + "sessionId", + ], + }, + windowId: { + propDefinition: [ + app, + "windowId", + ({ sessionId }) => ({ + sessionId, + }), + ], + }, + prompt: { + type: "string", + label: "Scraping Prompt", + description: "Natural language prompt describing what content to scrape. For example: 'Scrape all article titles and publication dates' or 'Extract contact information from this page'", + }, + followPaginationLinks: { + type: "boolean", + label: "Follow Pagination Links", + description: "Automatically navigate through paginated content to scrape data from multiple pages", + optional: true, + default: false, + }, + costThresholdCredits: { + type: "integer", + label: "Cost Threshold (Credits)", + description: "Maximum credits to spend on this operation before cancellation. Set to 0 to disable (not recommended).", + optional: true, + }, + timeThresholdSeconds: { + type: "integer", + label: "Time Threshold (Seconds)", + description: "Maximum time in seconds before the operation is cancelled. Set to 0 to disable (not recommended).", + optional: true, + }, + }, + async run({ $ }) { + const { + sessionId, + windowId, + prompt, + followPaginationLinks, + costThresholdCredits, + timeThresholdSeconds, + } = this; + + const response = await this.app.scrapeContent({ + $, + sessionId, + windowId, + data: { + prompt, + configuration: { + followPaginationLinks, + costThresholdCredits, + timeThresholdSeconds, + }, + }, + }); + + const creditsUsed = response.meta?.usage?.credits; + + let summary = "Successfully scraped content"; + if (creditsUsed) { + summary += ` (${creditsUsed} credits used)`; + } + + $.export("$summary", summary); + return response; + }, +}; + diff --git a/components/airtop/actions/terminate-session/terminate-session.mjs b/components/airtop/actions/terminate-session/terminate-session.mjs new file mode 100644 index 0000000000000..1bcb6b0fdedaa --- /dev/null +++ b/components/airtop/actions/terminate-session/terminate-session.mjs @@ -0,0 +1,30 @@ +import app from "../../airtop.app.mjs"; + +export default { + key: "airtop-end-session", + name: "End Session", + description: "End a browser session. [See the documentation](https://docs.airtop.ai/api-reference/airtop-api/sessions/terminate)", + version: "0.0.1", + type: "action", + props: { + app, + sessionId: { + propDefinition: [ + app, + "sessionId", + ], + }, + }, + async run({ $ }) { + const { sessionId } = this; + + const response = await this.app.terminateSession({ + $, + sessionId, + }); + + $.export("$summary", `Successfully terminated session \`${sessionId}\``); + return response; + }, +}; + diff --git a/components/airtop/airtop.app.mjs b/components/airtop/airtop.app.mjs index 2b7c770aaa445..91f858f2b39c2 100644 --- a/components/airtop/airtop.app.mjs +++ b/components/airtop/airtop.app.mjs @@ -1,11 +1,153 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "airtop", - propDefinitions: {}, + propDefinitions: { + sessionId: { + type: "string", + label: "Session ID", + description: "Select a browser session or provide a custom session ID", + async options() { + const { data } = await this.listSessions(); + console.log(data); + return data.map((session) => ({ + label: `${session.id.substring(0, 8)}... (${session.status})`, + value: session.id, + })); + }, + }, + windowId: { + type: "string", + label: "Window ID", + description: "Select a browser window or provide a custom window ID", + async options({ sessionId }) { + if (!sessionId) { + return []; + } + const { data } = await this.listWindows({ + sessionId, + }); + return data.map((window) => ({ + label: `Window ${window.id.substring(0, 8)}...`, + value: window.id, + })); + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://api.airtop.ai/api/v1"; + }, + _headers() { + return { + "Authorization": `Bearer ${this.$auth.api_key}`, + "Content-Type": "application/json", + }; + }, + async _makeRequest({ + $ = this, path, ...opts + }) { + return axios($, { + url: `${this._baseUrl()}${path}`, + headers: this._headers(), + ...opts, + }); + }, + async createSession(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/sessions", + ...opts, + }); + }, + async listSessions(opts = {}) { + return this._makeRequest({ + method: "GET", + path: "/sessions", + ...opts, + }); + }, + async getSession({ + sessionId, ...opts + }) { + return this._makeRequest({ + method: "GET", + path: `/sessions/${sessionId}`, + ...opts, + }); + }, + async terminateSession({ + sessionId, ...opts + }) { + return this._makeRequest({ + method: "DELETE", + path: `/sessions/${sessionId}`, + ...opts, + }); + }, + async createWindow({ + sessionId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/sessions/${sessionId}/windows`, + ...opts, + }); + }, + async listWindows({ + sessionId, ...opts + }) { + return this._makeRequest({ + method: "GET", + path: `/sessions/${sessionId}/windows`, + ...opts, + }); + }, + async getWindow({ + sessionId, windowId, ...opts + }) { + return this._makeRequest({ + method: "GET", + path: `/sessions/${sessionId}/windows/${windowId}`, + ...opts, + }); + }, + async loadUrl({ + sessionId, windowId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/sessions/${sessionId}/windows/${windowId}`, + ...opts, + }); + }, + async queryPage({ + sessionId, windowId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/sessions/${sessionId}/windows/${windowId}/page-query`, + ...opts, + }); + }, + async scrapeContent({ + sessionId, windowId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/sessions/${sessionId}/windows/${windowId}/scrape-content`, + ...opts, + }); + }, + async saveProfileOnTermination({ + sessionId, profileName, ...opts + }) { + return this._makeRequest({ + method: "PUT", + path: `/sessions/${sessionId}/save-profile-on-termination/${profileName}`, + ...opts, + }); }, }, -}; \ No newline at end of file +}; diff --git a/components/airtop/common/utils.mjs b/components/airtop/common/utils.mjs new file mode 100644 index 0000000000000..0ebd463f03bdc --- /dev/null +++ b/components/airtop/common/utils.mjs @@ -0,0 +1,23 @@ +function optionalParseAsJSON(value) { + try { + return JSON.parse(value); + } catch (e) { + return value; + } +} + +export function parseObjectEntries(value = {}) { + const obj = typeof value === "string" + ? JSON.parse(value) + : value; + return Object.fromEntries( + Object.entries(obj).map(([ + key, + value, + ]) => [ + key, + optionalParseAsJSON(value), + ]), + ); +} + From e93e7934b9e7f19f47e537114bd9ac240a713801 Mon Sep 17 00:00:00 2001 From: GTFalcao Date: Fri, 3 Oct 2025 18:41:33 -0300 Subject: [PATCH 2/7] package/app updates --- .../end-session.mjs} | 2 +- components/airtop/airtop.app.mjs | 76 +++++++++++-------- components/airtop/package.json | 5 +- 3 files changed, 48 insertions(+), 35 deletions(-) rename components/airtop/actions/{terminate-session/terminate-session.mjs => end-session/end-session.mjs} (91%) diff --git a/components/airtop/actions/terminate-session/terminate-session.mjs b/components/airtop/actions/end-session/end-session.mjs similarity index 91% rename from components/airtop/actions/terminate-session/terminate-session.mjs rename to components/airtop/actions/end-session/end-session.mjs index 1bcb6b0fdedaa..741f5b86515de 100644 --- a/components/airtop/actions/terminate-session/terminate-session.mjs +++ b/components/airtop/actions/end-session/end-session.mjs @@ -18,7 +18,7 @@ export default { async run({ $ }) { const { sessionId } = this; - const response = await this.app.terminateSession({ + const response = await this.app.endSession({ $, sessionId, }); diff --git a/components/airtop/airtop.app.mjs b/components/airtop/airtop.app.mjs index 91f858f2b39c2..c199447be3386 100644 --- a/components/airtop/airtop.app.mjs +++ b/components/airtop/airtop.app.mjs @@ -8,13 +8,26 @@ export default { type: "string", label: "Session ID", description: "Select a browser session or provide a custom session ID", - async options() { - const { data } = await this.listSessions(); - console.log(data); - return data.map((session) => ({ - label: `${session.id.substring(0, 8)}... (${session.status})`, - value: session.id, - })); + async options({ page }) { + const limit = 10; + const offset = page * limit; + + const data = await this.listSessions({ + params: { + limit, + offset, + }, + }); + + return { + options: data.sessions.map((session) => ({ + label: `${session.id.substring(0, 8)}... (${session.status})`, + value: session.id, + })), + context: { + hasMore: data.pagination.hasMore, + }, + }; }, }, windowId: { @@ -25,20 +38,17 @@ export default { if (!sessionId) { return []; } - const { data } = await this.listWindows({ + const data = await this.listWindows({ sessionId, }); - return data.map((window) => ({ - label: `Window ${window.id.substring(0, 8)}...`, - value: window.id, + return data.windows.map((window) => ({ + label: `Window ${window.windowId.substring(0, 8)}...`, + value: window.windowId, })); }, }, }, methods: { - _baseUrl() { - return "https://api.airtop.ai/api/v1"; - }, _headers() { return { "Authorization": `Bearer ${this.$auth.api_key}`, @@ -46,25 +56,28 @@ export default { }; }, async _makeRequest({ - $ = this, path, ...opts + $ = this, headers, ...opts }) { - return axios($, { - url: `${this._baseUrl()}${path}`, - headers: this._headers(), + const response = await axios($, { + baseURL: "https://api.airtop.ai/api/v1", + headers: { + ...this._headers(), + ...headers, + }, ...opts, }); + return response.data; }, async createSession(opts = {}) { return this._makeRequest({ method: "POST", - path: "/sessions", + url: "/sessions", ...opts, }); }, async listSessions(opts = {}) { return this._makeRequest({ - method: "GET", - path: "/sessions", + url: "/sessions", ...opts, }); }, @@ -72,8 +85,7 @@ export default { sessionId, ...opts }) { return this._makeRequest({ - method: "GET", - path: `/sessions/${sessionId}`, + url: `/sessions/${sessionId}`, ...opts, }); }, @@ -82,7 +94,7 @@ export default { }) { return this._makeRequest({ method: "DELETE", - path: `/sessions/${sessionId}`, + url: `/sessions/${sessionId}`, ...opts, }); }, @@ -91,7 +103,7 @@ export default { }) { return this._makeRequest({ method: "POST", - path: `/sessions/${sessionId}/windows`, + url: `/sessions/${sessionId}/windows`, ...opts, }); }, @@ -99,8 +111,7 @@ export default { sessionId, ...opts }) { return this._makeRequest({ - method: "GET", - path: `/sessions/${sessionId}/windows`, + url: `/sessions/${sessionId}/windows`, ...opts, }); }, @@ -108,8 +119,7 @@ export default { sessionId, windowId, ...opts }) { return this._makeRequest({ - method: "GET", - path: `/sessions/${sessionId}/windows/${windowId}`, + url: `/sessions/${sessionId}/windows/${windowId}`, ...opts, }); }, @@ -118,7 +128,7 @@ export default { }) { return this._makeRequest({ method: "POST", - path: `/sessions/${sessionId}/windows/${windowId}`, + url: `/sessions/${sessionId}/windows/${windowId}`, ...opts, }); }, @@ -127,7 +137,7 @@ export default { }) { return this._makeRequest({ method: "POST", - path: `/sessions/${sessionId}/windows/${windowId}/page-query`, + url: `/sessions/${sessionId}/windows/${windowId}/page-query`, ...opts, }); }, @@ -136,7 +146,7 @@ export default { }) { return this._makeRequest({ method: "POST", - path: `/sessions/${sessionId}/windows/${windowId}/scrape-content`, + url: `/sessions/${sessionId}/windows/${windowId}/scrape-content`, ...opts, }); }, @@ -145,7 +155,7 @@ export default { }) { return this._makeRequest({ method: "PUT", - path: `/sessions/${sessionId}/save-profile-on-termination/${profileName}`, + url: `/sessions/${sessionId}/save-profile-on-termination/${profileName}`, ...opts, }); }, diff --git a/components/airtop/package.json b/components/airtop/package.json index 844d7e23d77da..13f8991435ec2 100644 --- a/components/airtop/package.json +++ b/components/airtop/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/airtop", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Airtop Components", "main": "airtop.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.1.0" } } \ No newline at end of file From bf59efc8309d3464bf82aaf126b95a011e5fb8d0 Mon Sep 17 00:00:00 2001 From: GTFalcao Date: Fri, 3 Oct 2025 18:42:40 -0300 Subject: [PATCH 3/7] pnpm lock --- pnpm-lock.yaml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 22215e9439823..3a6e4eb4e2c5d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -624,7 +624,10 @@ importers: version: 2.30.1 components/airtop: - specifiers: {} + dependencies: + '@pipedream/platform': + specifier: ^3.1.0 + version: 3.1.0 components/aitable_ai: dependencies: @@ -7684,8 +7687,7 @@ importers: specifier: ^3.1.0 version: 3.1.0 - components/kudosity: - specifiers: {} + components/kudosity: {} components/kustomer: dependencies: @@ -13294,8 +13296,7 @@ importers: specifier: ^3.0.0 version: 3.0.3 - components/sinch: - specifiers: {} + components/sinch: {} components/sinch_messagemedia: {} @@ -31361,22 +31362,22 @@ packages: superagent@3.8.1: resolution: {integrity: sha512-VMBFLYgFuRdfeNQSMLbxGSLfmXL/xc+OO+BZp41Za/NRDBet/BNbkRJrYzCUu0u4GU0i/ml2dtT8b9qgkw9z6Q==} engines: {node: '>= 4.0'} - deprecated: Please upgrade to superagent v10.2.2+, see release notes at https://github.com/forwardemail/superagent/releases/tag/v10.2.2 - maintenance is supported by Forward Email @ https://forwardemail.net + deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net superagent@4.1.0: resolution: {integrity: sha512-FT3QLMasz0YyCd4uIi5HNe+3t/onxMyEho7C3PSqmti3Twgy2rXT4fmkTz6wRL6bTF4uzPcfkUCa8u4JWHw8Ag==} engines: {node: '>= 6.0'} - deprecated: Please upgrade to superagent v10.2.2+, see release notes at https://github.com/forwardemail/superagent/releases/tag/v10.2.2 - maintenance is supported by Forward Email @ https://forwardemail.net + deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net superagent@5.3.1: resolution: {integrity: sha512-wjJ/MoTid2/RuGCOFtlacyGNxN9QLMgcpYLDQlWFIhhdJ93kNscFonGvrpAHSCVjRVj++DGCglocF7Aej1KHvQ==} engines: {node: '>= 7.0.0'} - deprecated: Please upgrade to superagent v10.2.2+, see release notes at https://github.com/forwardemail/superagent/releases/tag/v10.2.2 - maintenance is supported by Forward Email @ https://forwardemail.net + deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net superagent@7.1.6: resolution: {integrity: sha512-gZkVCQR1gy/oUXr+kxJMLDjla434KmSOKbx5iGD30Ql+AkJQ/YlPKECJy2nhqOsHLjGHzoDTXNSjhnvWhzKk7g==} engines: {node: '>=6.4.0 <13 || >=14'} - deprecated: Please upgrade to superagent v10.2.2+, see release notes at https://github.com/forwardemail/superagent/releases/tag/v10.2.2 - maintenance is supported by Forward Email @ https://forwardemail.net + deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net supports-color@10.0.0: resolution: {integrity: sha512-HRVVSbCCMbj7/kdWF9Q+bbckjBHLtHMEoJWlkmYzzdwhYMkjkOwubLM6t7NbWKjgKamGDrWL1++KrjUO1t9oAQ==} From 1b67a1ea6604132bf067819d70a9ca9bb1f37893 Mon Sep 17 00:00:00 2001 From: GTFalcao Date: Fri, 3 Oct 2025 20:01:56 -0300 Subject: [PATCH 4/7] general adjustments --- .../actions/create-session/create-session.mjs | 5 +- .../actions/create-window/create-window.mjs | 32 ++++- .../actions/end-session/end-session.mjs | 2 +- .../airtop/actions/load-url/load-url.mjs | 24 ++-- .../airtop/actions/query-page/query-page.mjs | 38 +++--- .../actions/scrape-content/scrape-content.mjs | 49 +++---- components/airtop/airtop.app.mjs | 122 +++++++++++++----- 7 files changed, 158 insertions(+), 114 deletions(-) diff --git a/components/airtop/actions/create-session/create-session.mjs b/components/airtop/actions/create-session/create-session.mjs index 58ff2e6b61fcb..57be5e0c49376 100644 --- a/components/airtop/actions/create-session/create-session.mjs +++ b/components/airtop/actions/create-session/create-session.mjs @@ -71,8 +71,7 @@ export default { data, }); - const sessionId = response.data?.id; - const status = response.data?.status; + const sessionId = response.id; let saveProfileOnTerminationResponse; if (saveProfileOnTermination && profileName) { @@ -83,7 +82,7 @@ export default { }); } - $.export("$summary", `Successfully created session \`${sessionId}\` with status: ${status}`); + $.export("$summary", `Successfully created session \`${sessionId}\` with status: ${response.status}`); return { response, saveProfileOnTerminationResponse, diff --git a/components/airtop/actions/create-window/create-window.mjs b/components/airtop/actions/create-window/create-window.mjs index 59f4fa43fdac6..ca7c2fd086cbe 100644 --- a/components/airtop/actions/create-window/create-window.mjs +++ b/components/airtop/actions/create-window/create-window.mjs @@ -17,14 +17,37 @@ export default { url: { type: "string", label: "Initial URL", - description: "Optional URL to navigate to immediately after creating the window", + description: "Optional URL to navigate to immediately after creating the window.", optional: true, + default: "https://www.pipedream.com", + }, + screenResolution: { + type: "string", + label: "Screen Resolution", + description: "Affects the live view configuration. By default, a live view will fill the parent frame when initially loaded. This parameter can be used to configure fixed dimensions (e.g. `1280x720`).", + optional: true, + default: "1280x720", + }, + waitUntil: { + propDefinition: [ + app, + "waitUntil", + ], + }, + waitUntilTimeoutSeconds: { + propDefinition: [ + app, + "waitUntilTimeoutSeconds", + ], }, }, async run({ $ }) { const { sessionId, url, + screenResolution, + waitUntil, + waitUntilTimeoutSeconds, } = this; const response = await this.app.createWindow({ @@ -32,12 +55,15 @@ export default { sessionId, data: { url, + screenResolution, + waitUntil, + waitUntilTimeoutSeconds, }, }); - const windowId = response.data?.id; + const windowId = response.id; - $.export("$summary", `Successfully created window \`${windowId}\` in session \`${sessionId}\``); + $.export("$summary", `Successfully created window ${windowId}`); return response; }, }; diff --git a/components/airtop/actions/end-session/end-session.mjs b/components/airtop/actions/end-session/end-session.mjs index 741f5b86515de..a08d90a0b63a3 100644 --- a/components/airtop/actions/end-session/end-session.mjs +++ b/components/airtop/actions/end-session/end-session.mjs @@ -23,7 +23,7 @@ export default { sessionId, }); - $.export("$summary", `Successfully terminated session \`${sessionId}\``); + $.export("$summary", `Successfully terminated session ${sessionId}`); return response; }, }; diff --git a/components/airtop/actions/load-url/load-url.mjs b/components/airtop/actions/load-url/load-url.mjs index e9b0b84560e64..26a03d30df843 100644 --- a/components/airtop/actions/load-url/load-url.mjs +++ b/components/airtop/actions/load-url/load-url.mjs @@ -26,27 +26,19 @@ export default { url: { type: "string", label: "URL", - description: "The URL to navigate to (must be a valid URI)", + description: "The URL to navigate to (e.g. `https://www.pipedream.com`)", }, waitUntil: { - type: "string", - label: "Wait Until", - description: "Wait until the specified loading event occurs. `load` waits until the page DOM and assets have loaded. `domContentLoaded` waits until the DOM has loaded. `complete` waits until the page and all its iframes have loaded. `noWait` returns immediately.", - optional: true, - options: [ - "load", - "domContentLoaded", - "complete", - "noWait", + propDefinition: [ + app, + "waitUntil", ], - default: "load", }, waitUntilTimeoutSeconds: { - type: "integer", - label: "Wait Timeout (Seconds)", - description: "Maximum time in seconds to wait for the specified loading event. If the timeout is reached, the operation will still succeed but return a warning.", - optional: true, - default: 30, + propDefinition: [ + app, + "waitUntilTimeoutSeconds", + ], }, }, async run({ $ }) { diff --git a/components/airtop/actions/query-page/query-page.mjs b/components/airtop/actions/query-page/query-page.mjs index fe42e338ba6ef..2824c4c36bc30 100644 --- a/components/airtop/actions/query-page/query-page.mjs +++ b/components/airtop/actions/query-page/query-page.mjs @@ -3,7 +3,7 @@ import app from "../../airtop.app.mjs"; export default { key: "airtop-query-page", name: "Query Page", - description: "Extract data or ask questions about page content using AI. Submit a natural language prompt to query the content of a browser window. [See the documentation](https://docs.airtop.ai/api-reference/airtop-api/windows/page-query)", + description: "Extract data or ask questions about page content using AI. [See the documentation](https://docs.airtop.ai/api-reference/airtop-api/windows/page-query)", version: "0.0.1", type: "action", props: { @@ -26,26 +26,25 @@ export default { prompt: { type: "string", label: "Prompt", - description: "Natural language prompt to submit about the page content. For example: 'Extract all product names and prices' or 'Is the user logged in?'", + description: "The prompt to submit about the content in the browser window.", }, followPaginationLinks: { - type: "boolean", - label: "Follow Pagination Links", - description: "Make a best effort attempt to load more content items than are originally displayed on the page by following pagination links, clicking controls to load more content, or utilizing infinite scrolling. This can be more costly but may be necessary for sites requiring additional interaction.", - optional: true, - default: false, + propDefinition: [ + app, + "followPaginationLinks", + ], }, costThresholdCredits: { - type: "integer", - label: "Cost Threshold (Credits)", - description: "A credit threshold that, once exceeded, will cause the operation to be cancelled. This is not a hard limit but checked periodically. Set to 0 to disable (not recommended).", - optional: true, + propDefinition: [ + app, + "costThresholdCredits", + ], }, timeThresholdSeconds: { - type: "integer", - label: "Time Threshold (Seconds)", - description: "A time threshold that, once exceeded, will cause the operation to be cancelled. This is checked periodically. Set to 0 to disable (not recommended).", - optional: true, + propDefinition: [ + app, + "timeThresholdSeconds", + ], }, }, async run({ $ }) { @@ -72,14 +71,7 @@ export default { }, }); - const creditsUsed = response.meta?.usage?.credits; - - let summary = "Successfully queried page"; - if (creditsUsed) { - summary += ` (${creditsUsed} credits used)`; - } - - $.export("$summary", summary); + $.export("$summary", "Successfully queried page"); return response; }, }; diff --git a/components/airtop/actions/scrape-content/scrape-content.mjs b/components/airtop/actions/scrape-content/scrape-content.mjs index 9008d0a5469ef..663d6a2eca484 100644 --- a/components/airtop/actions/scrape-content/scrape-content.mjs +++ b/components/airtop/actions/scrape-content/scrape-content.mjs @@ -23,37 +23,30 @@ export default { }), ], }, - prompt: { + clientRequestId: { type: "string", - label: "Scraping Prompt", - description: "Natural language prompt describing what content to scrape. For example: 'Scrape all article titles and publication dates' or 'Extract contact information from this page'", - }, - followPaginationLinks: { - type: "boolean", - label: "Follow Pagination Links", - description: "Automatically navigate through paginated content to scrape data from multiple pages", + label: "Client Request ID", + description: "Optional client-provided request identifier", optional: true, - default: false, }, costThresholdCredits: { - type: "integer", - label: "Cost Threshold (Credits)", - description: "Maximum credits to spend on this operation before cancellation. Set to 0 to disable (not recommended).", - optional: true, + propDefinition: [ + app, + "costThresholdCredits", + ], }, timeThresholdSeconds: { - type: "integer", - label: "Time Threshold (Seconds)", - description: "Maximum time in seconds before the operation is cancelled. Set to 0 to disable (not recommended).", - optional: true, + propDefinition: [ + app, + "timeThresholdSeconds", + ], }, }, async run({ $ }) { const { sessionId, windowId, - prompt, - followPaginationLinks, + clientRequestId, costThresholdCredits, timeThresholdSeconds, } = this; @@ -63,23 +56,13 @@ export default { sessionId, windowId, data: { - prompt, - configuration: { - followPaginationLinks, - costThresholdCredits, - timeThresholdSeconds, - }, + clientRequestId, + costThresholdCredits, + timeThresholdSeconds, }, }); - const creditsUsed = response.meta?.usage?.credits; - - let summary = "Successfully scraped content"; - if (creditsUsed) { - summary += ` (${creditsUsed} credits used)`; - } - - $.export("$summary", summary); + $.export("$summary", "Successfully scraped content from the page"); return response; }, }; diff --git a/components/airtop/airtop.app.mjs b/components/airtop/airtop.app.mjs index c199447be3386..5fcd987dea2a5 100644 --- a/components/airtop/airtop.app.mjs +++ b/components/airtop/airtop.app.mjs @@ -8,7 +8,7 @@ export default { type: "string", label: "Session ID", description: "Select a browser session or provide a custom session ID", - async options({ page }) { + async options({ page = 0 }) { const limit = 10; const offset = page * limit; @@ -19,15 +19,10 @@ export default { }, }); - return { - options: data.sessions.map((session) => ({ - label: `${session.id.substring(0, 8)}... (${session.status})`, - value: session.id, - })), - context: { - hasMore: data.pagination.hasMore, - }, - }; + return data.sessions.map((session) => ({ + label: `${session.id.substring(0, 8)}... (${session.status})`, + value: session.id, + })); }, }, windowId: { @@ -47,6 +42,57 @@ export default { })); }, }, + waitUntil: { + type: "string", + label: "Wait Until", + description: "Wait until the specified loading event occurs. Defaults to `load`.", + options: [ + { + label: "Load - Wait until the page DOM and its assets have loaded", + value: "load", + }, + { + label: "DOM Content Loaded - Wait until the DOM has loaded", + value: "domContentLoaded", + }, + { + label: "Complete - Wait until the page and all its iframes have loaded", + value: "complete", + }, + { + label: "No Wait - Return immediately without waiting", + value: "noWait", + }, + ], + optional: true, + default: "load", + }, + waitUntilTimeoutSeconds: { + type: "integer", + label: "\"Wait Until\" Timeout (seconds)", + description: "Maximum time in seconds to wait for the specified loading event to occur. If the timeout is reached, the operation will still succeed but return a warning.", + optional: true, + default: 30, + }, + followPaginationLinks: { + type: "boolean", + label: "Follow Pagination Links", + description: "If true, a best effort attempt to load more content items than are originally displayed on the page will be made. [See the documentation](https://docs.airtop.ai/api-reference/airtop-api/windows/page-query) for more information", + optional: true, + default: false, + }, + costThresholdCredits: { + type: "integer", + label: "Cost Threshold (Credits)", + description: "A credit threshold that, once exceeded, will cause the operation to be cancelled. [See the documentation](https://docs.airtop.ai/guides/misc/faq#how-does-the-credit-system-work) for more information.", + optional: true, + }, + timeThresholdSeconds: { + type: "integer", + label: "Time Threshold (Seconds)", + description: "A time threshold that, once exceeded, will cause the operation to be cancelled. This is checked periodically. Set to 0 to disable (not recommended).", + optional: true, + }, }, methods: { _headers() { @@ -56,7 +102,7 @@ export default { }; }, async _makeRequest({ - $ = this, headers, ...opts + $ = this, headers, ...args }) { const response = await axios($, { baseURL: "https://api.airtop.ai/api/v1", @@ -64,99 +110,105 @@ export default { ...this._headers(), ...headers, }, - ...opts, + ...args, }); return response.data; }, - async createSession(opts = {}) { + async createSession(args = {}) { return this._makeRequest({ method: "POST", url: "/sessions", - ...opts, + ...args, }); }, - async listSessions(opts = {}) { + async listSessions({ + params, ...args + }) { return this._makeRequest({ url: "/sessions", - ...opts, + params: { + status: "running", + ...params, + }, + ...args, }); }, async getSession({ - sessionId, ...opts + sessionId, ...args }) { return this._makeRequest({ url: `/sessions/${sessionId}`, - ...opts, + ...args, }); }, - async terminateSession({ - sessionId, ...opts + async endSession({ + sessionId, ...args }) { return this._makeRequest({ method: "DELETE", url: `/sessions/${sessionId}`, - ...opts, + ...args, }); }, async createWindow({ - sessionId, ...opts + sessionId, ...args }) { return this._makeRequest({ method: "POST", url: `/sessions/${sessionId}/windows`, - ...opts, + ...args, }); }, async listWindows({ - sessionId, ...opts + sessionId, ...args }) { return this._makeRequest({ url: `/sessions/${sessionId}/windows`, - ...opts, + ...args, }); }, async getWindow({ - sessionId, windowId, ...opts + sessionId, windowId, ...args }) { return this._makeRequest({ url: `/sessions/${sessionId}/windows/${windowId}`, - ...opts, + ...args, }); }, async loadUrl({ - sessionId, windowId, ...opts + sessionId, windowId, ...args }) { return this._makeRequest({ method: "POST", url: `/sessions/${sessionId}/windows/${windowId}`, - ...opts, + ...args, }); }, async queryPage({ - sessionId, windowId, ...opts + sessionId, windowId, ...args }) { return this._makeRequest({ method: "POST", url: `/sessions/${sessionId}/windows/${windowId}/page-query`, - ...opts, + ...args, }); }, async scrapeContent({ - sessionId, windowId, ...opts + sessionId, windowId, ...args }) { return this._makeRequest({ method: "POST", url: `/sessions/${sessionId}/windows/${windowId}/scrape-content`, - ...opts, + ...args, }); }, async saveProfileOnTermination({ - sessionId, profileName, ...opts + sessionId, profileName, ...args }) { return this._makeRequest({ method: "PUT", url: `/sessions/${sessionId}/save-profile-on-termination/${profileName}`, - ...opts, + ...args, }); }, }, From c586933e929791bb3035a08e9269675838862662 Mon Sep 17 00:00:00 2001 From: GTFalcao Date: Fri, 3 Oct 2025 20:43:18 -0300 Subject: [PATCH 5/7] Improvements --- .../actions/create-session/create-session.mjs | 7 ++++- components/airtop/airtop.app.mjs | 13 +++++---- components/airtop/common/utils.mjs | 29 +++++++++++++++++++ 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/components/airtop/actions/create-session/create-session.mjs b/components/airtop/actions/create-session/create-session.mjs index 57be5e0c49376..405f4bd7e3545 100644 --- a/components/airtop/actions/create-session/create-session.mjs +++ b/components/airtop/actions/create-session/create-session.mjs @@ -1,5 +1,6 @@ import { parseObjectEntries } from "../../common/utils.mjs"; import app from "../../airtop.app.mjs"; +import { ConfigurationError } from "@pipedream/platform"; export default { key: "airtop-create-session", @@ -12,7 +13,7 @@ export default { profileName: { type: "string", label: "Profile Name", - description: "Name of a profile to load into the session", + description: "Name of a profile to load into the session. Only letters, numbers and hyphens are allowed. [See the documentation](https://docs.airtop.ai/guides/how-to/saving-a-profile) for more information", optional: true, }, saveProfileOnTermination: { @@ -56,6 +57,10 @@ export default { additionalOptions, } = this; + if (profileName && !/^[a-zA-Z0-9-]+$/.test(profileName)) { + throw new ConfigurationError(`Profile name \`${profileName}\` must contain only letters, numbers and hyphens`); + } + const data = { configuration: { profileName, diff --git a/components/airtop/airtop.app.mjs b/components/airtop/airtop.app.mjs index 5fcd987dea2a5..dde67159afad6 100644 --- a/components/airtop/airtop.app.mjs +++ b/components/airtop/airtop.app.mjs @@ -1,4 +1,5 @@ import { axios } from "@pipedream/platform"; +import { formatTimeAgo } from "./common/utils.mjs"; export default { type: "app", @@ -7,7 +8,7 @@ export default { sessionId: { type: "string", label: "Session ID", - description: "Select a browser session or provide a custom session ID", + description: "Select a session or provide a session ID. Note that only actively running sessions are listed in the options", async options({ page = 0 }) { const limit = 10; const offset = page * limit; @@ -19,9 +20,11 @@ export default { }, }); - return data.sessions.map((session) => ({ - label: `${session.id.substring(0, 8)}... (${session.status})`, - value: session.id, + return data.sessions.map(({ + id, dateCreated, lastActivity, + }) => ({ + label: `${id.slice(0, 8)} last active ${formatTimeAgo(lastActivity)} (created ${formatTimeAgo(dateCreated)})`, + value: id, })); }, }, @@ -207,7 +210,7 @@ export default { }) { return this._makeRequest({ method: "PUT", - url: `/sessions/${sessionId}/save-profile-on-termination/${profileName}`, + url: `/sessions/${sessionId}/save-profile-on-termination/${encodeURIComponent(profileName)}`, ...args, }); }, diff --git a/components/airtop/common/utils.mjs b/components/airtop/common/utils.mjs index 0ebd463f03bdc..f53750e91bd6e 100644 --- a/components/airtop/common/utils.mjs +++ b/components/airtop/common/utils.mjs @@ -21,3 +21,32 @@ export function parseObjectEntries(value = {}) { ); } +export function formatTimeAgo(dateString) { + const date = new Date(dateString); + const now = new Date(); + const diffInSeconds = Math.floor((now - date) / 1000); + + if (diffInSeconds < 60) { + return "just now"; + } + + const diffInMinutes = Math.floor(diffInSeconds / 60); + if (diffInMinutes < 60) { + return `${diffInMinutes} minute${diffInMinutes !== 1 + ? "s" + : ""} ago`; + } + + const diffInHours = Math.floor(diffInMinutes / 60); + if (diffInHours < 24) { + return `${diffInHours} hour${diffInHours !== 1 + ? "s" + : ""} ago`; + } + + const diffInDays = Math.floor(diffInHours / 24); + return `${diffInDays} day${diffInDays !== 1 + ? "s" + : ""} ago`; +} + From 731d0f77987054a8241893a87a94a3a0a2462256 Mon Sep 17 00:00:00 2001 From: GTFalcao Date: Fri, 3 Oct 2025 21:57:58 -0300 Subject: [PATCH 6/7] Adding timer source --- components/airtop/airtop.app.mjs | 9 +-- components/airtop/sources/common/polling.mjs | 53 +++++++++++++++++ .../new-session-created.mjs | 59 +++++++++++++++++++ 3 files changed, 114 insertions(+), 7 deletions(-) create mode 100644 components/airtop/sources/common/polling.mjs create mode 100644 components/airtop/sources/new-session-created/new-session-created.mjs diff --git a/components/airtop/airtop.app.mjs b/components/airtop/airtop.app.mjs index dde67159afad6..abaacfb6252f2 100644 --- a/components/airtop/airtop.app.mjs +++ b/components/airtop/airtop.app.mjs @@ -15,6 +15,7 @@ export default { const data = await this.listSessions({ params: { + status: "running", limit, offset, }, @@ -124,15 +125,9 @@ export default { ...args, }); }, - async listSessions({ - params, ...args - }) { + async listSessions(args = {}) { return this._makeRequest({ url: "/sessions", - params: { - status: "running", - ...params, - }, ...args, }); }, diff --git a/components/airtop/sources/common/polling.mjs b/components/airtop/sources/common/polling.mjs new file mode 100644 index 0000000000000..7ebf776a81c7f --- /dev/null +++ b/components/airtop/sources/common/polling.mjs @@ -0,0 +1,53 @@ +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; +import airtop from "../../airtop.app.mjs"; + +export default { + props: { + airtop, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + methods: { + _getLastTs() { + return this.db.get("lastTs"); + }, + _setLastTs(lastTs) { + this.db.set("lastTs", lastTs); + }, + isNew(resource, lastTs) { + if (!resource.dateCreated || !lastTs) { + return true; + } + return new Date(resource.dateCreated).getTime() > lastTs; + }, + getResources() { + throw new Error("getResources is not implemented"); + }, + generateMeta() { + throw new Error("generateMeta is not implemented"); + }, + }, + async run() { + let lastTs = this._getLastTs(); + + const resources = await this.getResources(lastTs); + for (const resource of resources) { + const { dateCreated } = resource; + if (!lastTs || (dateCreated && new Date(dateCreated).getTime() > lastTs)) { + lastTs = new Date(dateCreated).getTime(); + } + const meta = this.generateMeta(resource); + this.$emit(resource, meta); + } + + if (lastTs) { + this._setLastTs(lastTs); + } + }, +}; + diff --git a/components/airtop/sources/new-session-created/new-session-created.mjs b/components/airtop/sources/new-session-created/new-session-created.mjs new file mode 100644 index 0000000000000..5271fde001a20 --- /dev/null +++ b/components/airtop/sources/new-session-created/new-session-created.mjs @@ -0,0 +1,59 @@ +import common from "../common/polling.mjs"; + +export default { + ...common, + key: "airtop-new-session-created", + name: "New Session Created", + description: "Emit new event when a new session is created in Airtop. [See the documentation](https://docs.airtop.ai/api-reference/airtop-api/sessions/list)", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + async getResources(lastTs) { + const resources = []; + let hasMore = true; + let offset = 0; + const limit = 25; + const isFirstRun = !lastTs; + + while (hasMore) { + const data = await this.airtop.listSessions({ + params: { + limit, + offset, + }, + }); + + if (!data?.sessions?.length) { + break; + } + + for (const resource of data.sessions) { + const isNewResource = this.isNew(resource, lastTs); + if (isNewResource) { + resources.push(resource); + } + } + + hasMore = data.pagination?.hasMore; + offset = data.pagination?.nextOffset; + + // Stop on first run or on last page + if (isFirstRun || !hasMore) { + break; + } + } + + return resources; + }, + generateMeta(session) { + return { + id: session.id, + summary: `New Session: ${session.id.slice(0, 8)}`, + ts: new Date(session.dateCreated).getTime(), + }; + }, + }, +}; + From 4e53f685bb735809a35783c043363eac2ddf9135 Mon Sep 17 00:00:00 2001 From: GTFalcao Date: Fri, 3 Oct 2025 22:23:17 -0300 Subject: [PATCH 7/7] add newline to packagejson --- components/airtop/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/airtop/package.json b/components/airtop/package.json index 13f8991435ec2..a5c0920e5a800 100644 --- a/components/airtop/package.json +++ b/components/airtop/package.json @@ -15,4 +15,4 @@ "dependencies": { "@pipedream/platform": "^3.1.0" } -} \ No newline at end of file +}