Issues with importing package in custom node

Hello,

I’m new to n8n and am trying to develop a custom wrapper around the hyperbrowser sdk. When I try to import the custom node into n8n, I get an error saying that

evalmachine.<anonymous>:1
new (require('/home/<USER>/.n8n/custom/n8n-nodes-hyperbrowser/nodes/Hyperbrowser/Hyperbrowser.node.js').Hyperbrowser)()
^

TypeError: require(...).Hyperbrowser is not a constructor
    at evalmachine.<anonymous>:1:1
    at Script.runInContext (node:vm:149:12)
    at loadClassInIsolation (/home/<USER>/.local/share/fnm/node-versions/v22.14.0/installation/lib/node_modules/n8n/node_modules/n8n-core/src/nodes-loader/load-class-in-isolation.ts:9:16)
    at CustomDirectoryLoader.loadClass (/home/<USER>/.local/share/fnm/node-versions/v22.14.0/installation/lib/node_modules/n8n/node_modules/n8n-core/src/nodes-loader/directory-loader.ts:103:31)
    at CustomDirectoryLoader.loadNodeFromFile (/home/<USER>/.local/share/fnm/node-versions/v22.14.0/installation/lib/node_modules/n8n/node_modules/n8n-core/src/nodes-loader/directory-loader.ts:116:25)
    at CustomDirectoryLoader.loadAll (/home/<USER>/.local/share/fnm/node-versions/v22.14.0/installation/lib/node_modules/n8n/node_modules/n8n-core/src/nodes-loader/custom-directory-loader.ts:19:9)
    at LoadNodesAndCredentials.runDirectoryLoader (/home/<USER>/.local/share/fnm/node-versions/v22.14.0/installation/lib/node_modules/n8n/src/load-nodes-and-credentials.ts:302:3)
    at LoadNodesAndCredentials.loadNodesFromCustomDirectories (/home/<USER>/.local/share/fnm/node-versions/v22.14.0/installation/lib/node_modules/n8n/src/load-nodes-and-credentials.ts:213:4)
    at LoadNodesAndCredentials.init (/home/<USER>/.local/share/fnm/node-versions/v22.14.0/installation/lib/node_modules/n8n/src/load-nodes-and-credentials.ts:109:3)
    at Start.init (/home/<USER>/.local/share/fnm/node-versions/v22.14.0/installation/lib/node_modules/n8n/src/commands/base-command.ts:97:3)

Here’s what the directory structure looks like

Here’s my Hyperbrowser.node.ts file

import type {
	IExecuteFunctions,
	INodeExecutionData,
	INodeType,
	INodeTypeDescription,
	IDataObject,
} from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow';
import Hyperbrowser from '@hyperbrowser/sdk';
import { CreateSessionParams, Country, ScrapeOptions, ScrapeFormat } from '@hyperbrowser/sdk/types';

export default class HyperbrowserNode implements INodeType {
	description: INodeTypeDescription = {
		displayName: 'Hyperbrowser',
		name: 'hyperbrowser',
		group: ['transform'],
		version: 1,
		description: 'Interact with websites using Hyperbrowser',
		defaults: {
			name: 'Hyperbrowser',
		},
		// @ts-ignore
		inputs: ['main'],
		// @ts-ignore
		outputs: ['main'],
		credentials: [
			{
				name: 'hyperbrowserApi',
				required: true,
			},
		],
		properties: [
			{
				displayName: 'Operation',
				name: 'operation',
				type: 'options',
				noDataExpression: true,
				options: [
					{
						name: 'Scrape',
						value: 'scrape',
						description: 'Scrape a URL',
						action: 'Scrape a URL',
					},
				],
				default: 'scrape',
			},
			{
				displayName: 'URL',
				name: 'url',
				type: 'string',
				required: true,
				default: '',
				description: 'URL to process',
				placeholder: 'https://example.com',
			},

			{
				displayName: 'Options',
				name: 'options',
				type: 'collection',
				placeholder: 'Add Option',
				default: {},
				options: [
					{
						displayName: 'Only Main Content',
						name: 'onlyMainContent',
						type: 'boolean',
						default: true,
						description: 'Whether to return only the main content of the page',
					},
					{
						displayName: 'Output Format',
						name: 'format',
						type: 'options',
						options: [
							{
								name: 'HTML',
								value: 'html',
							},
							{
								name: 'Markdown',
								value: 'markdown',
							},
						],
						default: 'markdown',
						description: 'Output format to return',
						displayOptions: {
							show: {
								'/operation': ['scrape'],
							},
						},
					},
				],
			},
		],
	};

	async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
		const items = this.getInputData();
		const returnData: INodeExecutionData[] = [];

		const credentials = await this.getCredentials('hyperbrowserApi');
		const client = new Hyperbrowser({
			apiKey: credentials.apiKey as string,
		});

		for (let i = 0; i < items.length; i++) {
			try {
				const operation = this.getNodeParameter('operation', i) as string;
				const url = this.getNodeParameter('url', i) as string;
				const options = this.getNodeParameter('options', i, {}) as {
					format?: ScrapeFormat;
					onlyMainContent?: boolean;
					timeout?: number;
					useProxy?: boolean;
					solveCaptchas?: boolean;
					proxyCountry?: Country;
					maxPages?: number;
				};

				const sessionOptions: CreateSessionParams | undefined = options.useProxy
					? {
							useProxy: true,
							solveCaptchas: options.solveCaptchas || false,
							proxyCountry: options.proxyCountry || 'US',
						}
					: undefined;

				let responseData: IDataObject;

				if (operation === 'scrape') {
					const scrapeOptions: ScrapeOptions = {
						formats: [options.format || 'markdown'],
						onlyMainContent: options.onlyMainContent ?? true,
						timeout: options.timeout || 15000,
					};

					const response = await client.scrape.startAndWait({
						url,
						scrapeOptions,
						sessionOptions,
					});

					responseData = {
						url,
						content: response.data,
						status: response.status,
					};
				} else {
					throw new NodeOperationError(this.getNode(), `Operation "${operation}" is not supported`);
				}

				returnData.push({
					json: responseData,
					pairedItem: { item: i },
				});
			} catch (error) {
				if (this.continueOnFail()) {
					returnData.push({
						json: {
							error: error.message,
							url: this.getNodeParameter('url', i) as string,
						},
						pairedItem: { item: i },
					});
					continue;
				}
				throw error;
			}
		}

		return [returnData];
	}
}

Here’s my HyperbrowserAPI.credential.ts

import {
	IAuthenticateGeneric,
	ICredentialTestRequest,
	ICredentialType,
	INodeProperties,
} from 'n8n-workflow';

export class HyperbrowserApi implements ICredentialType {
	name = 'hyperbrowserApi';
	displayName = 'Hyperbrowser API';
	documentationUrl = 'https://docs.hyperbrowser.ai/';
	properties: INodeProperties[] = [
		{
			displayName: 'API Key',
			name: 'apiKey',
			type: 'string',
			typeOptions: {
				password: true,
			},
			default: '',
		},
	];

	authenticate: IAuthenticateGeneric = {
		type: 'generic',
		properties: {
			headers: {
				'x-api-key': '={{$credentials.apiKey}}',
			},
		},
	};

	test: ICredentialTestRequest = {
		request: {
			baseURL: 'https://app.hyperbrowser.ai/api',
			url: '/scrape',
			method: 'POST',
			body: {
				url: 'https://example.com',
				scrapeOptions: {
					formats: ['markdown'],
				},
			},
		},
	};
} 

Here’s my index.js as well

// This file ensures n8n can find and load your nodes and credentials
const { Hyperbrowser } = require('./dist/nodes/Hyperbrowser/Hyperbrowser.node.js');

module.exports = {
	nodeTypes: {
		hyperbrowser: Hyperbrowser,
	},
	credentialTypes: {
		hyperbrowserApi: require('./dist/credentials/HyperbrowserApi.credentials.js').HyperbrowserApi,
	},
};

So right now, to test locally, I have copied the dist files into ~/.n8n/custom/n8n-nodes-hyperbrowser

I have installed @hyperbrowser/sdk since that is a dependency here in the ~/.n8n directory

Otherwise, most of the code is from the n8n-starter template

Thanks for the help in advance!

Information on your n8n setup

  • n8n version:1.85.4
  • Database (default: SQLite):Default
  • n8n EXECUTIONS_PROCESS setting (default: own, main): Default
  • Running n8n via (Docker, npm, n8n cloud, desktop app): npm
  • Operating system: Linux

Have you added it in the package.json? And then installed it via npm install? (or whatever the new thing was)

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.