From 453c9c9e0771cbfaf500b9e31f62eab9b1bae5a2 Mon Sep 17 00:00:00 2001 From: Pranav Janakiraman Date: Sun, 22 Feb 2026 00:59:59 +0800 Subject: [PATCH 1/2] fix: remove setTimeout/clearTimeout to pass n8n community node scan Remove retry logic and custom timeout in favor of n8n's built-in execution timeout. Removes timeout option from node UI. --- n8n/nodes/Tinyfish/GenericFunctions.ts | 81 +++++------------------ n8n/nodes/Tinyfish/Tinyfish.node.ts | 7 -- n8n/nodes/Tinyfish/TinyfishDescription.ts | 12 ---- n8n/package-lock.json | 4 +- 4 files changed, 17 insertions(+), 87 deletions(-) diff --git a/n8n/nodes/Tinyfish/GenericFunctions.ts b/n8n/nodes/Tinyfish/GenericFunctions.ts index dc767a6..3e6818f 100644 --- a/n8n/nodes/Tinyfish/GenericFunctions.ts +++ b/n8n/nodes/Tinyfish/GenericFunctions.ts @@ -9,9 +9,6 @@ import { NodeApiError, NodeOperationError } from 'n8n-workflow'; const API_BASE_URL = 'https://agent.tinyfish.ai'; -const MAX_RETRIES = 3; -const RETRYABLE_STATUS_CODES = new Set([429, 500, 502, 503, 504]); - /** * Map known TinyFish API error codes to actionable user messages. */ @@ -45,33 +42,14 @@ function getActionableMessage(error: unknown): string | undefined { return `Invalid input: ${message || 'Validation failed'}. Check your URL and goal parameters.`; } case 'RATE_LIMIT_EXCEEDED': - return 'Rate limit exceeded after multiple retries. Wait a few minutes and try again, or reduce request frequency.'; + return 'Rate limit exceeded. Wait a few minutes and try again, or reduce request frequency.'; case 'INTERNAL_ERROR': - return `TinyFish server error: ${message || 'An unexpected error occurred'}. Retries exhausted — try again later.`; + return `TinyFish server error: ${message || 'An unexpected error occurred'}. Try again later.`; default: return undefined; } } -/** - * Check if an error has a retryable HTTP status code. - */ -function isRetryable(error: unknown): boolean { - const httpCode = (error as Record)?.httpCode as number | undefined; - if (httpCode && RETRYABLE_STATUS_CODES.has(httpCode)) return true; - - const cause = (error as Record)?.cause as Record | undefined; - const status = cause?.status as number | undefined; - return status !== undefined && RETRYABLE_STATUS_CODES.has(status); -} - -/** - * Sleep for a given number of milliseconds. - */ -function sleep(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - /** * Make an authenticated request to the TinyFish API. * Retries on 429/5xx with exponential backoff (max 3 retries). @@ -96,35 +74,21 @@ export async function tinyfishApiRequest( requestOptions.body = body; } - let lastError: unknown; - - for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) { - try { - return (await this.helpers.httpRequestWithAuthentication.call( - this, - 'tinyfishApi', - requestOptions, - )) as IDataObject; - } catch (error) { - lastError = error; - - if (attempt < MAX_RETRIES && isRetryable(error)) { - await sleep(Math.pow(2, attempt) * 1000); - continue; - } - - const actionableMessage = getActionableMessage(error); - if (actionableMessage) { - throw new NodeApiError(this.getNode(), error as JsonObject, { - message: actionableMessage, - }); - } - throw new NodeApiError(this.getNode(), error as JsonObject); + try { + return (await this.helpers.httpRequestWithAuthentication.call( + this, + 'tinyfishApi', + requestOptions, + )) as IDataObject; + } catch (error) { + const actionableMessage = getActionableMessage(error); + if (actionableMessage) { + throw new NodeApiError(this.getNode(), error as JsonObject, { + message: actionableMessage, + }); } + throw new NodeApiError(this.getNode(), error as JsonObject); } - - // Should not reach here, but TypeScript needs a return path - throw new NodeApiError(this.getNode(), lastError as JsonObject); } /** @@ -164,13 +128,10 @@ export function buildAutomationPayload( export async function consumeSseStream( this: IExecuteFunctions, payload: IDataObject, - timeoutMs: number, ): Promise { const credentials = await this.getCredentials('tinyfishApi'); const apiKey = credentials.apiKey as string; - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), timeoutMs); let lastProgress = ''; try { @@ -181,7 +142,6 @@ export async function consumeSseStream( 'Content-Type': 'application/json', }, body: JSON.stringify(payload), - signal: controller.signal, }); if (!response.ok) { @@ -261,17 +221,6 @@ export async function consumeSseStream( return finalResult; } catch (error) { - if ((error as Error).name === 'AbortError') { - const progressHint = lastProgress - ? ` Last progress: "${lastProgress}".` - : ''; - throw new NodeOperationError( - this.getNode(), - `Automation timed out after ${Math.round(timeoutMs / 1000)} seconds.${progressHint} Try increasing the timeout or simplifying the goal.`, - ); - } throw error; - } finally { - clearTimeout(timeoutId); } } diff --git a/n8n/nodes/Tinyfish/Tinyfish.node.ts b/n8n/nodes/Tinyfish/Tinyfish.node.ts index 69d6792..8e48ee8 100644 --- a/n8n/nodes/Tinyfish/Tinyfish.node.ts +++ b/n8n/nodes/Tinyfish/Tinyfish.node.ts @@ -57,24 +57,17 @@ export class Tinyfish implements INodeType { if (operation === 'runSse') { const payload = buildAutomationPayload.call(this, i); - const options = this.getNodeParameter('options', i, {}) as IDataObject; - const timeoutSeconds = (options.timeout as number) || 300; responseData = await consumeSseStream.call( this, payload, - timeoutSeconds * 1000, ); } else if (operation === 'runSync') { const payload = buildAutomationPayload.call(this, i); - const options = this.getNodeParameter('options', i, {}) as IDataObject; - const timeoutSeconds = (options.timeout as number) || 300; responseData = await tinyfishApiRequest.call( this, 'POST', '/v1/automation/run', payload, - {}, - { timeout: timeoutSeconds * 1000 }, ); } else if (operation === 'runAsync') { const payload = buildAutomationPayload.call(this, i); diff --git a/n8n/nodes/Tinyfish/TinyfishDescription.ts b/n8n/nodes/Tinyfish/TinyfishDescription.ts index 6aa3742..8254dc4 100644 --- a/n8n/nodes/Tinyfish/TinyfishDescription.ts +++ b/n8n/nodes/Tinyfish/TinyfishDescription.ts @@ -133,18 +133,6 @@ export const runFields: INodeProperties[] = [ { name: 'United States', value: 'US' }, ], }, - { - displayName: 'Timeout (Seconds)', - name: 'timeout', - type: 'number', - default: 300, - description: - 'Maximum time to wait for automation to complete (30-600s). Most tasks complete within 60-120 seconds. Increase for complex multi-step workflows.', - typeOptions: { - minValue: 30, - maxValue: 600, - }, - }, ], }, ]; diff --git a/n8n/package-lock.json b/n8n/package-lock.json index 4713857..651b061 100644 --- a/n8n/package-lock.json +++ b/n8n/package-lock.json @@ -1,12 +1,12 @@ { "name": "n8n-nodes-tinyfish", - "version": "0.1.0", + "version": "0.1.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "n8n-nodes-tinyfish", - "version": "0.1.0", + "version": "0.1.5", "license": "MIT", "devDependencies": { "@types/node": "^22.0.0", From d4c2fc585f71746ef8f9813657279621e5bea63c Mon Sep 17 00:00:00 2001 From: Pranav Janakiraman Date: Sun, 22 Feb 2026 01:19:36 +0800 Subject: [PATCH 2/2] fix: resolve n8n community node pre-check failures - Remove setTimeout/clearTimeout to pass security scan - Fix credential icon format from object to string - Update repo URL to point to n8n subfolder - Remove timeout option from node UI --- n8n/credentials/TinyfishApi.credentials.ts | 2 +- n8n/package.json | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/n8n/credentials/TinyfishApi.credentials.ts b/n8n/credentials/TinyfishApi.credentials.ts index 68dac2a..ce47b09 100644 --- a/n8n/credentials/TinyfishApi.credentials.ts +++ b/n8n/credentials/TinyfishApi.credentials.ts @@ -10,7 +10,7 @@ export class TinyfishApi implements ICredentialType { displayName = 'TinyFish Web Agent API'; - icon = { light: 'file:tinyfish.svg', dark: 'file:tinyfish.svg' } as const; + icon = 'file:tinyfish.svg' as const; documentationUrl = 'https://docs.mino.ai'; diff --git a/n8n/package.json b/n8n/package.json index dde07f7..d640e3d 100644 --- a/n8n/package.json +++ b/n8n/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-tinyfish", - "version": "0.1.5", + "version": "0.1.7", "description": "n8n community node for TinyFish Web Agent - AI-powered web automation using natural language", "license": "MIT", "homepage": "https://docs.mino.ai", @@ -19,8 +19,7 @@ }, "repository": { "type": "git", - "url": "https://github.com/tinyfish-io/tinyfish-web-agent-integrations.git", - "directory": "n8n" + "url": "https://github.com/tinyfish-io/tinyfish-web-agent-integrations/tree/main/n8n" }, "main": "dist/nodes/Tinyfish/Tinyfish.node.js", "scripts": {