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