Skip to content

Add openHabTrigger node#5

Open
florian-h05 wants to merge 11 commits intoopenhab:mainfrom
florian-h05:trigger
Open

Add openHabTrigger node#5
florian-h05 wants to merge 11 commits intoopenhab:mainfrom
florian-h05:trigger

Conversation

@florian-h05
Copy link
Copy Markdown
Contributor

@florian-h05 florian-h05 commented Apr 20, 2026

As n8n requires verified nodes to not have any runtime dependencies, I had to implement my own WebSocket client based on Node.js primitives. Node.js introduced a WebSocket client in Node.js 22, but this one doesn't support ignoring invalid TLS certs, sending additional headers etc.

The WebSocket client and its unit tests were generated with Gemini and reviewed and tested myself.

@florian-h05 florian-h05 changed the title Trigger Implement openHABTrigger node Apr 20, 2026
@florian-h05 florian-h05 changed the title Implement openHABTrigger node Add openHABTrigger node Apr 20, 2026
@florian-h05 florian-h05 force-pushed the trigger branch 3 times, most recently from 0a3be2b to 46eca56 Compare April 20, 2026 19:53
@florian-h05 florian-h05 marked this pull request as ready for review April 20, 2026 19:56
@florian-h05 florian-h05 requested a review from a team as a code owner April 20, 2026 19:56
Including tests against a real WebSocket server.

Signed-off-by: Florian Hotze <dev@florianhotze.com>
Signed-off-by: Florian Hotze <dev@florianhotze.com>
Signed-off-by: Florian Hotze <dev@florianhotze.com>
Signed-off-by: Florian Hotze <dev@florianhotze.com>
Signed-off-by: Florian Hotze <dev@florianhotze.com>
Signed-off-by: Florian Hotze <dev@florianhotze.com>
Comment thread README.md Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an openHAB Trigger node to the n8n community package and introduces a small internal WebSocket client (plus Jest-based unit tests) to avoid runtime dependencies while supporting custom headers and optional insecure TLS for local setups.

Changes:

  • Added openHABTrigger node to listen to openHAB event bus via WebSocket and emit incoming events into workflows.
  • Introduced a custom WebSocket client (util/ws.ts) with unit tests and Jest configuration.
  • Updated build/lint/TS configs and packaging metadata to include the new node and util code.

Reviewed changes

Copilot reviewed 10 out of 15 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
util/ws.ts New dependency-free WebSocket client used by the trigger node (handshake, framing, ping/pong).
util/ws.test.ts Jest tests for the WebSocket client (plain + TLS, headers, protocols, ping/pong).
util/openHABApi.ts Shared helper to validate credentials/build base URL/source used by trigger and future code.
nodes/openHABTrigger/openHABTrigger.node.ts New trigger node implementation subscribing to openHAB events and applying filters/heartbeat.
nodes/openHABTrigger/openhab.svg Icon asset for the new trigger node.
package.json Registers the trigger node, adds Jest tooling deps, and updates scripts (including asset copying).
jest.config.js Adds Jest/ts-jest configuration and coverage output directory.
tsconfig.json / tsconfig.eslint.json Includes util/**, adjusts excludes for build vs lint typechecking.
eslint.config.mjs Switches ESLint project file to tsconfig.eslint.json and ignores coverage output.
index.ts Exports the new node from the package entrypoint.
README.md Documents the new trigger node usage and output schema.
.gitignore / .eslintrc.json Ignores Jest coverage output; removes legacy ESLint config in favor of flat config.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread package.json Outdated
Comment thread util/ws.ts Outdated
Comment thread util/ws.ts Outdated
Comment thread util/ws.ts
Comment thread util/ws.ts
Comment thread nodes/openHABTrigger/openHABTrigger.node.ts Outdated
Comment thread README.md Outdated
Comment thread util/ws.ts
Comment on lines +128 to +140
// Inject Sec-WebSocket-Protocol if requested
if (this.options.protocols) {
const protocolsStr = Array.isArray(this.options.protocols)
? this.options.protocols.join(', ')
: this.options.protocols;
request += `Sec-WebSocket-Protocol: ${protocolsStr}\r\n`;
}

if (this.options.headers) {
for (const [headerName, headerValue] of Object.entries(this.options.headers)) {
request += `${headerName}: ${headerValue}\r\n`;
}
}
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The handshake request is built by string-concatenating protocols and headers values directly into the HTTP header block. If any value contains \r/\n, it can inject additional headers or corrupt the request (request smuggling). Reject or sanitize header names/values and subprotocol strings to disallow CR/LF characters before writing the request.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The WebSocketClient is only used by our own, trusted code. I don't think we have to handle this.

Comment thread util/ws.ts
Comment on lines +92 to +94
const encodedAccessToken = accessToken ? Buffer.from(accessToken).toString('base64').replace(/=*$/, '') : null
const subProtocols = encodedAccessToken ? [`org.openhab.ws.accessToken.base64.${encodedAccessToken}`, 'org.openhab.ws.protocol.default'] : [];

Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sec-WebSocket-Protocol values are defined as HTTP tokens (RFC6455), so the subprotocol string must not contain characters like / or =. Buffer.from(accessToken).toString('base64') can produce + and / (and you only strip =), which can make the handshake fail on stricter servers/proxies. Consider using base64url encoding for the token part (replace +-, /_, and strip =) to keep the subprotocol token RFC-compliant.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Signed-off-by: Florian Hotze <dev@florianhotze.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 15 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread util/ws.ts
Comment thread util/ws.ts Outdated
Comment thread nodes/openHABTrigger/openHABTrigger.node.ts
Comment thread util/ws.test.ts Outdated
Comment thread util/openHABApi.ts
Signed-off-by: Florian Hotze <dev@florianhotze.com>
Signed-off-by: Florian Hotze <dev@florianhotze.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 15 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread util/ws.ts Outdated
Comment thread util/ws.test.ts
Comment on lines +158 to +165
it('should emit error on connection failure', (done) => {
const client = new WebSocketClient(`ws://localhost:1`);
client.on('error', (err) => {
expect(err).toBeDefined();
done();
});
client.connect();
});
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test assumes ws://localhost:1 will fail, but port 1 could be open/forwarded in some environments, making the test flaky. Prefer connecting to a guaranteed-unused port (e.g., bind a server to port 0 to obtain an ephemeral port, close it, then connect and assert ECONNREFUSED) or mock net.connect to deterministically trigger an error.

Copilot uses AI. Check for mistakes.
Comment thread nodes/openHABTrigger/openHABTrigger.node.ts Outdated
Comment thread util/openHABApi.ts
}
} else {
const token = credentials.token as string;
if (!token || !token.trim()) {
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Credential validation here treats a whitespace-only token as missing (if (!token || !token.trim())), but the existing openHAB node’s request helper only checks if (!token) (see nodes/openHAB/openHAB.node.ts), so the two nodes will behave differently for the same credential edge-case. Consider aligning the validation logic across nodes (or reusing a single shared helper) to avoid inconsistent user experience.

Suggested change
if (!token || !token.trim()) {
if (!token) {

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will be aligned in a follow-up PR.

@florian-h05 florian-h05 changed the title Add openHABTrigger node Add openHabTrigger node Apr 20, 2026
Signed-off-by: Florian Hotze <dev@florianhotze.com>
@florian-h05 florian-h05 requested a review from kaikreuzer April 20, 2026 22:15
Signed-off-by: Florian Hotze <dev@florianhotze.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants