first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import * as create from './create.operation';
|
||||
import * as save from './save.operation';
|
||||
import * as terminate from './terminate.operation';
|
||||
import * as waitForDownload from './waitForDownload.operation';
|
||||
|
||||
export { create, save, terminate, waitForDownload };
|
||||
|
||||
export const description: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['session'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create Session',
|
||||
value: 'create',
|
||||
description: 'Create an Airtop browser session',
|
||||
action: 'Create a session',
|
||||
},
|
||||
{
|
||||
name: 'Save Profile on Termination',
|
||||
value: 'save',
|
||||
description:
|
||||
'Save in a profile changes made in your browsing session such as cookies and local storage',
|
||||
action: 'Save a profile on session termination',
|
||||
},
|
||||
{
|
||||
name: 'Terminate Session',
|
||||
value: 'terminate',
|
||||
description: 'Terminate a session',
|
||||
action: 'Terminate a session',
|
||||
},
|
||||
{
|
||||
name: 'Wait for Download',
|
||||
value: 'waitForDownload',
|
||||
description: 'Wait for a file download to become available',
|
||||
action: 'Wait for a download',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
},
|
||||
...create.description,
|
||||
...save.description,
|
||||
...terminate.description,
|
||||
...waitForDownload.description,
|
||||
];
|
||||
@@ -0,0 +1,205 @@
|
||||
import {
|
||||
type IDataObject,
|
||||
type IExecuteFunctions,
|
||||
type INodeExecutionData,
|
||||
type INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { COUNTRIES } from '../../countries';
|
||||
import {
|
||||
createSession,
|
||||
validateProfileName,
|
||||
validateProxy,
|
||||
validateSaveProfileOnTermination,
|
||||
validateTimeoutMinutes,
|
||||
} from '../../GenericFunctions';
|
||||
import { apiRequest } from '../../transport';
|
||||
import { profileNameField } from '../common/fields';
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['session'],
|
||||
operation: ['create'],
|
||||
},
|
||||
};
|
||||
|
||||
const countryOptions = COUNTRIES.map(({ name, value }) => ({ name, value }));
|
||||
|
||||
export const description: INodeProperties[] = [
|
||||
{
|
||||
...profileNameField,
|
||||
displayOptions,
|
||||
},
|
||||
{
|
||||
displayName: 'Save Profile',
|
||||
name: 'saveProfileOnTermination',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether to automatically save the <a href="https://docs.airtop.ai/guides/how-to/saving-a-profile" target="_blank">Airtop profile</a> for this session upon termination',
|
||||
displayOptions,
|
||||
},
|
||||
/* Session Recording */
|
||||
{
|
||||
displayName: 'Record Session',
|
||||
name: 'record',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether to record the browser session. <a href="https://docs.airtop.ai/guides/how-to/recording-a-session" target="_blank">More details</a>.',
|
||||
displayOptions,
|
||||
},
|
||||
{
|
||||
displayName: 'Idle Timeout',
|
||||
name: 'timeoutMinutes',
|
||||
type: 'number',
|
||||
default: 10,
|
||||
validateType: 'number',
|
||||
description: 'Minutes to wait before the session is terminated due to inactivity',
|
||||
displayOptions,
|
||||
},
|
||||
/**
|
||||
* Proxy Configuration
|
||||
*/
|
||||
{
|
||||
displayName: 'Proxy',
|
||||
name: 'proxy',
|
||||
type: 'options',
|
||||
default: 'none',
|
||||
description: 'Choose how to configure the proxy for this session',
|
||||
options: [
|
||||
{
|
||||
name: 'None',
|
||||
value: 'none',
|
||||
description: 'No proxy will be used',
|
||||
},
|
||||
{
|
||||
name: 'Integrated',
|
||||
value: 'integrated',
|
||||
description: 'Use Airtop-provided proxy',
|
||||
},
|
||||
{
|
||||
name: 'Proxy URL',
|
||||
value: 'proxyUrl',
|
||||
description: 'Use a proxy URL to configure the proxy',
|
||||
},
|
||||
],
|
||||
displayOptions,
|
||||
},
|
||||
{
|
||||
displayName: 'Proxy Configuration',
|
||||
name: 'proxyConfig',
|
||||
type: 'collection',
|
||||
default: { country: 'US', sticky: true },
|
||||
description: 'The Airtop-provided configuration to use for the proxy',
|
||||
placeholder: 'Add Attribute',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Country',
|
||||
name: 'country',
|
||||
type: 'options',
|
||||
default: 'US',
|
||||
description:
|
||||
'The country to use for the proxy. Not all countries are guaranteed to provide a proxy. Learn more <a href="https://docs.airtop.ai/api-reference/airtop-api/sessions/create#request.body.configuration.proxy.Proxy.Airtop-Proxy-Configuration.country" target="_blank">here</a>.',
|
||||
options: countryOptions,
|
||||
},
|
||||
{
|
||||
displayName: 'Keep Same IP',
|
||||
name: 'sticky',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description:
|
||||
'Whether to try to maintain the same IP address for the duration of the session. Airtop can guarantee that the same IP address will be available for up to a maximum of 30 minutes.',
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
...displayOptions.show,
|
||||
proxy: ['integrated'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Proxy URL',
|
||||
name: 'proxyUrl',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The URL of the proxy to use',
|
||||
validateType: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
...displayOptions.show,
|
||||
proxy: ['proxyUrl'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions,
|
||||
options: [
|
||||
{
|
||||
displayName: 'Auto Solve Captchas',
|
||||
name: 'solveCaptcha',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether to automatically solve <a href="https://docs.airtop.ai/guides/how-to/solving-captchas" target="_blank">captcha challenges</a>',
|
||||
},
|
||||
{
|
||||
displayName: 'Extension IDs',
|
||||
name: 'extensionIds',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'e.g. extId1, extId2, ...',
|
||||
description:
|
||||
'Comma-separated extension IDs from the Google Web Store to be loaded into the session. Learn more <a href="https://docs.airtop.ai/guides/how-to/using-chrome-extensions" target="_blank">here</a>.',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
index: number,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const profileName = validateProfileName.call(this, index);
|
||||
const record = this.getNodeParameter('record', index, false);
|
||||
const timeoutMinutes = validateTimeoutMinutes.call(this, index);
|
||||
const saveProfileOnTermination = validateSaveProfileOnTermination.call(this, index, profileName);
|
||||
const { proxy } = validateProxy.call(this, index);
|
||||
const solveCaptcha = this.getNodeParameter(
|
||||
'additionalFields.solveCaptcha',
|
||||
index,
|
||||
false,
|
||||
) as boolean;
|
||||
|
||||
const extensions = this.getNodeParameter('additionalFields.extensionIds', index, '') as string;
|
||||
const extensionIds = extensions ? extensions.split(',').map((id) => id.trim()) : [];
|
||||
|
||||
const body: IDataObject = {
|
||||
configuration: {
|
||||
profileName,
|
||||
timeoutMinutes,
|
||||
proxy,
|
||||
solveCaptcha,
|
||||
record,
|
||||
...(extensionIds.length > 0 ? { extensionIds } : {}),
|
||||
},
|
||||
};
|
||||
|
||||
const { sessionId, data } = await createSession.call(this, body);
|
||||
|
||||
if (saveProfileOnTermination) {
|
||||
await apiRequest.call(
|
||||
this,
|
||||
'PUT',
|
||||
`/sessions/${sessionId}/save-profile-on-termination/${profileName}`,
|
||||
);
|
||||
}
|
||||
|
||||
return this.helpers.returnJsonArray({ sessionId, ...data });
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import {
|
||||
type IDataObject,
|
||||
type IExecuteFunctions,
|
||||
type INodeExecutionData,
|
||||
type INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
validateAirtopApiResponse,
|
||||
validateProfileName,
|
||||
validateRequiredStringField,
|
||||
validateSessionId,
|
||||
} from '../../GenericFunctions';
|
||||
import { apiRequest } from '../../transport';
|
||||
import { sessionIdField, profileNameField } from '../common/fields';
|
||||
|
||||
export const description: INodeProperties[] = [
|
||||
{
|
||||
displayName:
|
||||
"Note: This operation is not needed if you enabled 'Save Profile' in the 'Create Session' operation",
|
||||
name: 'notice',
|
||||
type: 'notice',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['session'],
|
||||
operation: ['save'],
|
||||
},
|
||||
},
|
||||
default: 'This operation will save the profile on session termination',
|
||||
},
|
||||
{
|
||||
...sessionIdField,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['session'],
|
||||
operation: ['save'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
...profileNameField,
|
||||
required: true,
|
||||
description:
|
||||
'The name of the <a href="https://docs.airtop.ai/guides/how-to/saving-a-profile" target="_blank">Profile</a> to save',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['session'],
|
||||
operation: ['save'],
|
||||
},
|
||||
},
|
||||
hint: 'Name of the profile you want to save. Must consist only of alphanumeric characters and hyphens "-"',
|
||||
},
|
||||
];
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
index: number,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const sessionId = validateSessionId.call(this, index);
|
||||
let profileName = validateRequiredStringField.call(this, index, 'profileName', 'Profile Name');
|
||||
profileName = validateProfileName.call(this, index);
|
||||
|
||||
const response = await apiRequest.call(
|
||||
this,
|
||||
'PUT',
|
||||
`/sessions/${sessionId}/save-profile-on-termination/${profileName}`,
|
||||
);
|
||||
|
||||
// validate response
|
||||
validateAirtopApiResponse(this.getNode(), response);
|
||||
|
||||
return this.helpers.returnJsonArray({ sessionId, profileName, ...response } as IDataObject);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import {
|
||||
type IDataObject,
|
||||
type IExecuteFunctions,
|
||||
type INodeExecutionData,
|
||||
type INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { validateAirtopApiResponse, validateSessionId } from '../../GenericFunctions';
|
||||
import { apiRequest } from '../../transport';
|
||||
import { sessionIdField } from '../common/fields';
|
||||
|
||||
export const description: INodeProperties[] = [
|
||||
{
|
||||
...sessionIdField,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['session'],
|
||||
operation: ['terminate'],
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
index: number,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const sessionId = validateSessionId.call(this, index);
|
||||
const response = await apiRequest.call(this, 'DELETE', `/sessions/${sessionId}`);
|
||||
|
||||
// validate response
|
||||
validateAirtopApiResponse(this.getNode(), response);
|
||||
|
||||
return this.helpers.returnJsonArray({ success: true } as IDataObject);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import {
|
||||
type IDataObject,
|
||||
type IExecuteFunctions,
|
||||
type INodeExecutionData,
|
||||
type INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { DEFAULT_DOWNLOAD_TIMEOUT_SECONDS } from '../../constants';
|
||||
import { validateSessionId, waitForSessionEvent } from '../../GenericFunctions';
|
||||
import { sessionIdField } from '../common/fields';
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['session'],
|
||||
operation: ['waitForDownload'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description: INodeProperties[] = [
|
||||
{
|
||||
...sessionIdField,
|
||||
displayOptions,
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions,
|
||||
options: [
|
||||
{
|
||||
displayName: 'Timeout',
|
||||
description: 'Time in seconds to wait for the download to become available',
|
||||
name: 'timeout',
|
||||
type: 'number',
|
||||
default: DEFAULT_DOWNLOAD_TIMEOUT_SECONDS,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
index: number,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const sessionId = validateSessionId.call(this, index);
|
||||
const timeout = this.getNodeParameter(
|
||||
'timeout',
|
||||
index,
|
||||
DEFAULT_DOWNLOAD_TIMEOUT_SECONDS,
|
||||
) as number;
|
||||
|
||||
// Wait for a file_status event with status 'available'
|
||||
const event = await waitForSessionEvent.call(
|
||||
this,
|
||||
sessionId,
|
||||
(sessionEvent) => sessionEvent.event === 'file_status' && sessionEvent.status === 'available',
|
||||
timeout,
|
||||
);
|
||||
|
||||
// Extract fileId and downloadUrl from the event
|
||||
const result: IDataObject = {
|
||||
fileId: event.fileId,
|
||||
downloadUrl: event.downloadUrl,
|
||||
};
|
||||
|
||||
return this.helpers.returnJsonArray({
|
||||
sessionId,
|
||||
data: result,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user