@yousolution/node-red-contrib-you-tunnel-websocket 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +45 -0
- package/CHANGELOG.md +109 -0
- package/README.md +83 -4
- package/STABILITY_CONTRACT.md +299 -0
- package/nodes/wsTunnel.html +126 -4
- package/nodes/wsTunnel.js +143 -68
- package/package.json +4 -12
- package/test/ki-001-host-path-passthrough.test.js +187 -0
- package/test/ki-002-tunnel-id-header-default.test.js +60 -0
- package/test/ki-003-debug-log-level.test.js +180 -0
- package/test/ki-004-console-error-usage.test.js +144 -0
- package/test/m-3-concurrent-deployment.test.js +227 -0
- package/test/output-events.test.js +241 -0
- package/test/rwt-001-port-exclusivity.test.js +171 -0
- package/test/rwt-001-port-release.test.js +58 -0
- package/test/rwt-002-shutdown-completion.test.js +226 -0
- package/test/rwt-003-runtime-editor-compatibility.test.js +113 -0
- package/test/rwt-004-persisted-config-compatibility.test.js +140 -0
- package/test/start-failure-path.test.js +212 -0
- package/test/wstunnel-command.test.js +203 -0
- package/test/wstunnel-metrics-input.test.js +234 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# AI Development Rules
|
|
2
|
+
|
|
3
|
+
Before modifying production code:
|
|
4
|
+
|
|
5
|
+
1. Read `STABILITY_CONTRACT.md`.
|
|
6
|
+
2. Identify the RWT-\* invariants affected by the change.
|
|
7
|
+
3. Inspect the relevant implementation and existing tests.
|
|
8
|
+
4. Do not weaken or remove tests to make them pass.
|
|
9
|
+
5. Every bug fix must include a regression test when technically possible.
|
|
10
|
+
6. Do not increase timeouts to hide races.
|
|
11
|
+
7. Do not add retries without identifying the root cause.
|
|
12
|
+
8. Run all pertinent tests after every modification.
|
|
13
|
+
9. Do not use `--forceExit` to hide resource leaks.
|
|
14
|
+
10. Keep changes minimal and scoped to the current task.
|
|
15
|
+
11. Do not refactor unrelated code.
|
|
16
|
+
|
|
17
|
+
## Stability Contract
|
|
18
|
+
|
|
19
|
+
`STABILITY_CONTRACT.md` is normative.
|
|
20
|
+
|
|
21
|
+
Do not weaken, remove, or modify an RWT-\* invariant merely to make an implementation or test pass.
|
|
22
|
+
|
|
23
|
+
If the intended system behavior changes:
|
|
24
|
+
|
|
25
|
+
1. Update `STABILITY_CONTRACT.md`.
|
|
26
|
+
2. Explain why the invariant changed.
|
|
27
|
+
3. Add or update the relevant regression test.
|
|
28
|
+
4. Verify that dependent invariants remain satisfied.
|
|
29
|
+
|
|
30
|
+
Known Issues, Stability Risks, and Open Questions in the Stability Contract are not invariants and must not be treated as requirements without additional evidence.
|
|
31
|
+
|
|
32
|
+
## Workflow
|
|
33
|
+
|
|
34
|
+
For each task:
|
|
35
|
+
|
|
36
|
+
1. Read `STABILITY_CONTRACT.md`.
|
|
37
|
+
2. Inspect the relevant implementation.
|
|
38
|
+
3. Inspect existing tests.
|
|
39
|
+
4. Identify affected invariants, known issues, and stability risks.
|
|
40
|
+
5. Write or reproduce the regression test.
|
|
41
|
+
6. Make the smallest production change necessary.
|
|
42
|
+
7. Run the focused test.
|
|
43
|
+
8. Run the relevant existing suites.
|
|
44
|
+
9. Verify affected RWT-\* invariants.
|
|
45
|
+
10. Report changed files, tests executed, invariant impact, and remaining risks.
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## [1.2.0] - 2026-09-10
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- New `wstunnel command` node for sending commands to connected tunnel clients (fire-and-forget)
|
|
14
|
+
- `wstunnel server` node output: emits `connect` and `disconnect` events when tunnels register or close
|
|
15
|
+
- `wstunnel server` node input: accepts any message to trigger a server metrics snapshot via `getMetrics().snapshot()`
|
|
16
|
+
- Metrics snapshot includes: `active_tunnels`, `active_streams`, `bytes_in_total`, `bytes_out_total`, `event_loop_lag_ms`, per-tunnel `tunnels_detail`
|
|
17
|
+
- `STABILITY_CONTRACT.md` — 4 RWT invariants (RWT-001 through RWT-004) with regression tests
|
|
18
|
+
- `AGENTS.md` — AI development rules and workflow
|
|
19
|
+
- Regression tests for all KI fixes, output events, metrics input, concurrent deployment, and command node
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- KI-001: Inverted host/path default logic — configured values were discarded
|
|
23
|
+
- KI-002: tunnelIdHeaderName HTML default mismatch — editor default did not match runtime
|
|
24
|
+
- KI-003: Leftover debug log statement at info level — changed to debug level
|
|
25
|
+
- KI-004: console.error used in production code — replaced with logger.error
|
|
26
|
+
- KI-005: License inconsistency — package.json declared MIT while source files declared Apache-2.0
|
|
27
|
+
- `data/flows.json`: Fixed `host` and `tunnelIdHeaderName` for Docker compatibility
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
- `wstunnel server` node now accepts input (1 input, was 0)
|
|
31
|
+
- Replaced `setTimeout` with deterministic `setImmediate`/`Promise.resolve` in all test files
|
|
32
|
+
- Updated `@remotelinker/reverse-ws-tunnel` dependency
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## [1.1.2] - 2026-05-04
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
- Add proper cleanup of existing server on port before starting
|
|
40
|
+
- Replace console.log/warn/error with proper logger
|
|
41
|
+
- Improve error handling during server start/stop
|
|
42
|
+
|
|
43
|
+
### Changed
|
|
44
|
+
- Update @remotelinker/reverse-ws-tunnel from ^1.0.10 to ^1.0.11
|
|
45
|
+
- Add log context for each tunnel node
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## [1.1.1] - 2025-??-??
|
|
50
|
+
|
|
51
|
+
### Changed
|
|
52
|
+
- Update @remotelinker/reverse-ws-tunnel from ^1.0.9 to ^1.0.10
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## [1.1.0] - 2025-??-??
|
|
57
|
+
|
|
58
|
+
### Fixed
|
|
59
|
+
- Fix server configuration issues
|
|
60
|
+
|
|
61
|
+
### Changed
|
|
62
|
+
- Disable wstunnel client node temporarily
|
|
63
|
+
- Switch from local/beta package to published reverse-ws-tunnel package
|
|
64
|
+
|
|
65
|
+
### Documentation
|
|
66
|
+
- Update README to match current codebase
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## [1.0.1-dev-3] - 2025-??-??
|
|
71
|
+
|
|
72
|
+
### Added
|
|
73
|
+
- Initial wstunnel client node for reverse WebSocket tunneling
|
|
74
|
+
|
|
75
|
+
### Changed
|
|
76
|
+
- Update version to 1.0.1-dev-3
|
|
77
|
+
- Update dependencies
|
|
78
|
+
- Improve documentation
|
|
79
|
+
- Disable wstunnel client node
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## [1.0.1] - 2025-??-??
|
|
84
|
+
|
|
85
|
+
### Added
|
|
86
|
+
- Add configurable log level dropdown in wstunnel configuration (trace, debug, info, warning, error)
|
|
87
|
+
|
|
88
|
+
### Fixed
|
|
89
|
+
- Remove old connections when redeploying flows
|
|
90
|
+
|
|
91
|
+
### Changed
|
|
92
|
+
- First release with reverse tunnel functionality
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## [1.0.0] - 2025-??-??
|
|
97
|
+
|
|
98
|
+
### Added
|
|
99
|
+
- Initial release with WebSocket tunnel support
|
|
100
|
+
- MSSQL integration
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
[1.2.0]: https://github.com/yousolution-cloud/node-red-contrib-you-tunnel-websocket/compare/v1.1.2...v1.2.0
|
|
105
|
+
[1.1.2]: https://github.com/yousolution-cloud/node-red-contrib-you-tunnel-websocket/compare/v1.1.1...v1.1.2
|
|
106
|
+
[1.1.1]: https://github.com/yousolution-cloud/node-red-contrib-you-tunnel-websocket/compare/v1.1.0...v1.1.1
|
|
107
|
+
[1.1.0]: https://github.com/yousolution-cloud/node-red-contrib-you-tunnel-websocket/compare/v1.0.1...v1.1.0
|
|
108
|
+
[1.0.1]: https://github.com/yousolution-cloud/node-red-contrib-you-tunnel-websocket/compare/v1.0.0...v1.0.1
|
|
109
|
+
[1.0.0]: https://github.com/yousolution-cloud/node-red-contrib-you-tunnel-websocket/tree/v1.0.0
|
package/README.md
CHANGED
|
@@ -39,11 +39,22 @@ Creates a WebSocket server that can tunnel HTTP traffic over WebSocket connectio
|
|
|
39
39
|
|
|
40
40
|
#### Inputs
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
| Property | Type | Description |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `*` | `any` | Send any message to request a metrics snapshot on the output |
|
|
43
45
|
|
|
44
46
|
#### Outputs
|
|
45
47
|
|
|
46
|
-
|
|
48
|
+
Emits messages when tunnels connect, disconnect, or when metrics are requested:
|
|
49
|
+
|
|
50
|
+
| Property | Type | Description |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| `event` | `string` | `"connect"` when a tunnel is registered, `"disconnect"` when it closes, `"metrics"` when a snapshot is returned |
|
|
53
|
+
| `tunnelId` | `string` | The unique tunnel identifier (UUID) — present on connect/disconnect events |
|
|
54
|
+
| `remoteAddress` | `string` | The client IP address — present on connect/disconnect events |
|
|
55
|
+
| `connectedAt` | `number` | `Date.now()` when the tunnel was registered — present on connect events |
|
|
56
|
+
| `serverPort` | `number` | The port the WebSocket server is listening on |
|
|
57
|
+
| `payload` | `object` | On metrics events: the full snapshot object with aggregate and per-tunnel metrics |
|
|
47
58
|
|
|
48
59
|
#### Details
|
|
49
60
|
|
|
@@ -54,6 +65,37 @@ Status indicators:
|
|
|
54
65
|
- 🟢 **listening on port [port]**: Server is active and listening
|
|
55
66
|
- 🟢 **clients [count]**: Number of connected WebSocket clients
|
|
56
67
|
|
|
68
|
+
### wstunnel command
|
|
69
|
+
|
|
70
|
+
Sends commands to connected tunnel clients via WebSocket.
|
|
71
|
+
|
|
72
|
+
#### Properties
|
|
73
|
+
|
|
74
|
+
- **Name**: Optional name for the node
|
|
75
|
+
- **wstunnel**: Reference to a wstunnel configuration node *(required)*
|
|
76
|
+
|
|
77
|
+
#### Inputs
|
|
78
|
+
|
|
79
|
+
| Property | Type | Description |
|
|
80
|
+
|---|---|---|
|
|
81
|
+
| `payload` | `object` | Command payload (may contain `tunnelId`, `command`, `args`) |
|
|
82
|
+
| `tunnelId` | `string` | Target tunnel UUID (alternative to `payload.tunnelId`) |
|
|
83
|
+
| `command` | `string` | Command name to send, e.g. `"kill"` (alternative to `payload.command`) |
|
|
84
|
+
| `args` | `object` | Optional arguments for the command (alternative to `payload.args`) |
|
|
85
|
+
|
|
86
|
+
#### Outputs
|
|
87
|
+
|
|
88
|
+
Two outputs:
|
|
89
|
+
|
|
90
|
+
- **Output 0 (success)**: Command delivered. `msg.payload` is `true`.
|
|
91
|
+
- **Output 1 (failure)**: Tunnel not found or not connected. `msg.payload` is `false`, `msg.error` contains the reason.
|
|
92
|
+
|
|
93
|
+
#### Details
|
|
94
|
+
|
|
95
|
+
The node sends a command to a connected tunnel client. The target tunnel is identified by its `tunnelId`. If the tunnel exists and its WebSocket is open, the command is delivered and output 0 fires. If the tunnel does not exist or is disconnected, output 1 fires with an error message.
|
|
96
|
+
|
|
97
|
+
Missing `tunnelId` or `command` sends the message to output 1 with an error.
|
|
98
|
+
|
|
57
99
|
## Usage
|
|
58
100
|
|
|
59
101
|
### Basic Setup
|
|
@@ -67,6 +109,44 @@ Status indicators:
|
|
|
67
109
|
|
|
68
110
|
3. The server will start listening for WebSocket connections
|
|
69
111
|
|
|
112
|
+
### Monitoring Connections
|
|
113
|
+
|
|
114
|
+
Connect the **wstunnel server** output to a `switch` node to route connect and disconnect events:
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
[wstunnel server] → [switch: event == "connect"] → [notification flow]
|
|
118
|
+
→ [switch: event == "disconnect"] → [cleanup flow]
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Monitoring Metrics
|
|
122
|
+
|
|
123
|
+
Wire an `inject` node to the **wstunnel server** input to periodically request metrics:
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
[inject: every 5s] → [wstunnel server] → [debug: metrics]
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The output `msg.payload` contains:
|
|
130
|
+
|
|
131
|
+
| Field | Description |
|
|
132
|
+
|---|---|
|
|
133
|
+
| `active_tunnels` | Number of currently active tunnels |
|
|
134
|
+
| `active_streams` | Number of active TCP streams |
|
|
135
|
+
| `bytes_in_total / bytes_out_total` | Aggregate byte counters |
|
|
136
|
+
| `event_loop_lag_ms` | `{ p50, p99 }` event loop lag |
|
|
137
|
+
| `tunnels_detail` | Per-tunnel: `remoteAddress`, `streamCount`, `bytesIn`, `bytesOut`, `agentVersion` |
|
|
138
|
+
|
|
139
|
+
### Sending Commands to Clients
|
|
140
|
+
|
|
141
|
+
Wire a trigger to a **wstunnel command** node to send commands to connected clients:
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
[inject: {tunnelId: "uuid", command: "kill"}]
|
|
145
|
+
→ [wstunnel command]
|
|
146
|
+
→ output 0 → [debug: success]
|
|
147
|
+
→ output 1 → [debug: failure]
|
|
148
|
+
```
|
|
149
|
+
|
|
70
150
|
### Connection URLs
|
|
71
151
|
|
|
72
152
|
WebSocket servers are accessible at:
|
|
@@ -92,5 +172,4 @@ Apache-2.0
|
|
|
92
172
|
|
|
93
173
|
Andrea Trentin (<andrea.trentin@yousolution.cloud>)
|
|
94
174
|
|
|
95
|
-
Repository: [yousolution-cloud/node-red-contrib-you-tunnel-websocket](https://github.com/yousolution-cloud/node-red-contrib-you-tunnel-websocket)
|
|
96
|
-
<parameter name="filePath">README.md
|
|
175
|
+
Repository: [yousolution-cloud/node-red-contrib-you-tunnel-websocket](https://github.com/yousolution-cloud/node-red-contrib-you-tunnel-websocket)
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
# Stability Contract
|
|
2
|
+
|
|
3
|
+
This document is normative. It defines the project's **RWT-\*** invariants — properties that must be preserved across all production changes.
|
|
4
|
+
|
|
5
|
+
## What Is an Invariant?
|
|
6
|
+
|
|
7
|
+
An RWT-\* invariant is a property of the system that must remain true across production changes.
|
|
8
|
+
|
|
9
|
+
An invariant describes required behavior or compatibility, not a particular implementation.
|
|
10
|
+
|
|
11
|
+
Implementation details may change as long as the invariant remains satisfied.
|
|
12
|
+
|
|
13
|
+
Known bugs, temporary workarounds, coding conventions, and developer workflow rules are not invariants unless explicitly justified as stability requirements.
|
|
14
|
+
|
|
15
|
+
## How to Use This Contract
|
|
16
|
+
|
|
17
|
+
Before modifying production code:
|
|
18
|
+
|
|
19
|
+
1. Identify which RWT-\* invariants may be affected by the change.
|
|
20
|
+
2. Verify the tests that protect those invariants (if they exist).
|
|
21
|
+
3. Evaluate whether the change preserves all affected invariants.
|
|
22
|
+
4. Add or update regression tests when necessary.
|
|
23
|
+
5. If an invariant must intentionally change, update this file first.
|
|
24
|
+
|
|
25
|
+
This contract does not replace AGENTS.md. It supplements it with stability-specific constraints.
|
|
26
|
+
|
|
27
|
+
## Contract Scope
|
|
28
|
+
|
|
29
|
+
This document defines stability properties only. It must not be used to justify unrelated refactoring, cleanup, or behavior changes.
|
|
30
|
+
|
|
31
|
+
The contract describes what must remain true, not how the implementation must achieve it.
|
|
32
|
+
|
|
33
|
+
## Evidence and Authority
|
|
34
|
+
|
|
35
|
+
The Stability Contract is normative, but each invariant should be supported by repository evidence.
|
|
36
|
+
|
|
37
|
+
Evidence may include:
|
|
38
|
+
|
|
39
|
+
- Production code
|
|
40
|
+
- Automated tests
|
|
41
|
+
- Integration tests
|
|
42
|
+
- Project documentation
|
|
43
|
+
- CHANGELOG entries
|
|
44
|
+
- Commit history
|
|
45
|
+
- External framework contracts relied upon by the project
|
|
46
|
+
|
|
47
|
+
When evidence is insufficient to establish a required behavior, document the behavior as a Known Issue or Open Question rather than promoting it to an invariant.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Invariants
|
|
52
|
+
|
|
53
|
+
### RWT-001 — Exclusive Server Ownership of a Port
|
|
54
|
+
|
|
55
|
+
**Status:** Active
|
|
56
|
+
|
|
57
|
+
**Invariant**
|
|
58
|
+
|
|
59
|
+
A replacement managed WebSocket server must not be started on a port until the previously managed server on that port has completed its shutdown. At no point during a replacement operation may the implementation intentionally start the replacement while the previous server is still active.
|
|
60
|
+
|
|
61
|
+
**Why it matters**
|
|
62
|
+
|
|
63
|
+
If two servers attempt to bind the same port simultaneously, the second will fail with a port-in-use error, or both may enter an undefined state. This prevents orphaned server processes from accumulating across Node-RED redeployments.
|
|
64
|
+
|
|
65
|
+
**Scope**
|
|
66
|
+
|
|
67
|
+
- `nodes/wsTunnel.js` — `WSTunnelServer` constructor
|
|
68
|
+
|
|
69
|
+
**Protected by**
|
|
70
|
+
|
|
71
|
+
Automated tests:
|
|
72
|
+
|
|
73
|
+
- `test/rwt-001-port-exclusivity.test.js` — 2 tests (mock-based ordering verification)
|
|
74
|
+
- `test/rwt-001-port-release.test.js` — 2 tests (deterministic port-binding verification)
|
|
75
|
+
|
|
76
|
+
Historical evidence:
|
|
77
|
+
|
|
78
|
+
- CHANGELOG v1.0.1: "Remove old connections when redeploying flows"
|
|
79
|
+
- CHANGELOG v1.1.2: "Add proper cleanup of existing server on port before starting"
|
|
80
|
+
- Commit `0f2b273`: "fix: remove old connection when deploy"
|
|
81
|
+
|
|
82
|
+
**Potential violations**
|
|
83
|
+
|
|
84
|
+
- Starting a new server before the previous stop completes.
|
|
85
|
+
- Removing the cleanup-before-start sequence.
|
|
86
|
+
- Parallel deployments of multiple `wstunnel server` nodes on the same port without serializing the cleanup.
|
|
87
|
+
|
|
88
|
+
**Verification**
|
|
89
|
+
|
|
90
|
+
Deploy two `wstunnel server` nodes on the same port sequentially. Verify via Node-RED logs that the cleanup message appears before the start message for each deployment.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
### RWT-002 — Node Shutdown Must Signal Completion
|
|
95
|
+
|
|
96
|
+
**Status:** Active
|
|
97
|
+
|
|
98
|
+
**Invariant**
|
|
99
|
+
|
|
100
|
+
When a `WSTunnelServer` node receives a Node-RED close event, its shutdown handler must always signal completion by invoking `done()`, including failure paths.
|
|
101
|
+
|
|
102
|
+
**Why it matters**
|
|
103
|
+
|
|
104
|
+
Node-RED uses the `done()` callback to determine that a node has finished its shutdown sequence. If `done()` is not called, Node-RED will hang indefinitely, preventing redeployment or shutdown of the flow.
|
|
105
|
+
|
|
106
|
+
**Scope**
|
|
107
|
+
|
|
108
|
+
- `nodes/wsTunnel.js` — `WSTunnelServer.on('close')` handler
|
|
109
|
+
|
|
110
|
+
**Protected by**
|
|
111
|
+
|
|
112
|
+
Automated tests:
|
|
113
|
+
|
|
114
|
+
- `test/rwt-002-shutdown-completion.test.js` — 4 tests
|
|
115
|
+
|
|
116
|
+
Historical evidence:
|
|
117
|
+
|
|
118
|
+
- Node-RED framework contract: `done()` must be called in `on('close')`.
|
|
119
|
+
- The `done()` call exists in both success and error branches of the current implementation.
|
|
120
|
+
|
|
121
|
+
**Potential violations**
|
|
122
|
+
|
|
123
|
+
- Adding a new code path in `on('close')` that does not call `done()`.
|
|
124
|
+
- Throwing an exception before `done()` is reached without a catch.
|
|
125
|
+
- Removing the `done()` call in the error branch.
|
|
126
|
+
|
|
127
|
+
**Verification**
|
|
128
|
+
|
|
129
|
+
Deploy a `wstunnel server` node, then remove or redeploy it. Verify that Node-RED does not hang on the deployment.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
### RWT-003 — Node Runtime/Editor Compatibility
|
|
134
|
+
|
|
135
|
+
**Status:** Active
|
|
136
|
+
|
|
137
|
+
**Invariant**
|
|
138
|
+
|
|
139
|
+
Every Node-RED node type exposed by the package must have a corresponding editor definition, and the runtime and editor definitions must refer to the same node type. A node type must remain loadable and configurable through the Node-RED editor after production changes.
|
|
140
|
+
|
|
141
|
+
**Why it matters**
|
|
142
|
+
|
|
143
|
+
Node-RED uses the type name as the binding key between the editor UI (HTML) and the runtime (JS). A mismatch causes the node to be invisible in the editor, to fail to load, or to exhibit undefined behavior.
|
|
144
|
+
|
|
145
|
+
**Scope**
|
|
146
|
+
|
|
147
|
+
- `nodes/wsTunnel.js` — `RED.nodes.registerType()` calls
|
|
148
|
+
- `nodes/wsTunnel.html` — `data-template-name` and `data-help-name` attributes
|
|
149
|
+
|
|
150
|
+
**Protected by**
|
|
151
|
+
|
|
152
|
+
Automated tests:
|
|
153
|
+
|
|
154
|
+
- `test/rwt-003-runtime-editor-compatibility.test.js` — 5 tests
|
|
155
|
+
|
|
156
|
+
Historical evidence:
|
|
157
|
+
|
|
158
|
+
- Node-RED framework contract: type names must be consistent across JS and HTML.
|
|
159
|
+
- The current code has exact matches for the three active node types: `wstunnel`, `wstunnel server`, and `wstunnel command`.
|
|
160
|
+
|
|
161
|
+
**Potential violations**
|
|
162
|
+
|
|
163
|
+
- Renaming a type in JS without updating the HTML template and help attributes.
|
|
164
|
+
- Adding a new `RED.nodes.registerType()` call without corresponding HTML.
|
|
165
|
+
- Changing the type name of an existing node.
|
|
166
|
+
|
|
167
|
+
**Verification**
|
|
168
|
+
|
|
169
|
+
After modifying type registrations, load the Node-RED editor and verify that both node types appear in the palette. Verify that each can be dragged onto the canvas and configured.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
### RWT-004 — Persisted Flow Configuration Compatibility
|
|
174
|
+
|
|
175
|
+
**Status:** Active
|
|
176
|
+
|
|
177
|
+
**Invariant**
|
|
178
|
+
|
|
179
|
+
Existing Node-RED flows using the currently supported `wstunnel` configuration properties must remain loadable after a package upgrade, and their persisted configuration values must retain their existing behavior. Removing or renaming a persisted configuration property requires an explicit migration strategy.
|
|
180
|
+
|
|
181
|
+
**Why it matters**
|
|
182
|
+
|
|
183
|
+
Node-RED stores node configuration in flow JSON files. If configuration properties are removed or renamed, existing flows will lose their configured values, causing silent failures or fallback to unexpected defaults. This breaks deployments that upgrade the module.
|
|
184
|
+
|
|
185
|
+
**Scope**
|
|
186
|
+
|
|
187
|
+
- `nodes/wsTunnel.js` — `WSTunnelNode` constructor (options object)
|
|
188
|
+
- `nodes/wsTunnel.html` — `wstunnel` registration defaults
|
|
189
|
+
|
|
190
|
+
**Protected by**
|
|
191
|
+
|
|
192
|
+
Automated tests:
|
|
193
|
+
|
|
194
|
+
- `test/rwt-004-persisted-config-compatibility.test.js` — 5 tests
|
|
195
|
+
|
|
196
|
+
Historical evidence:
|
|
197
|
+
|
|
198
|
+
- Node-RED framework contract: config node properties are persisted in flow JSON.
|
|
199
|
+
- `data/flows.json` stores `name`, `host`, `port`, `path`, `tunnelIdHeaderName`, and `logLevel` as persisted properties.
|
|
200
|
+
|
|
201
|
+
**Potential violations**
|
|
202
|
+
|
|
203
|
+
- Removing a property from the `options` object without deprecation.
|
|
204
|
+
- Renaming a property without providing a migration path.
|
|
205
|
+
- Changing the type or semantics of an existing property.
|
|
206
|
+
|
|
207
|
+
**Verification**
|
|
208
|
+
|
|
209
|
+
Create a flow JSON file with all current configuration properties set, then deploy the updated module. Verify that all values are preserved and functional in the editor.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Known Stability Risks
|
|
214
|
+
|
|
215
|
+
These are potentially dangerous implementation characteristics that should be considered before modifying affected areas. Known Stability Risks are advisory. They must not be treated as requirements and must not be converted into invariants without additional evidence.
|
|
216
|
+
|
|
217
|
+
### Risk: Global Mutable State in `instances` Object
|
|
218
|
+
|
|
219
|
+
- **Area:** `nodes/wsTunnel.js` line 19
|
|
220
|
+
- **Nature:** The `instances` object is module-level mutable state shared across all node instances. If two `wstunnel server` nodes deploy concurrently on the same port, the stop/start sequence may interleave, causing one node to overwrite the other's state.
|
|
221
|
+
- **Existing test:** None.
|
|
222
|
+
- **What to verify:** Before modifying server lifecycle logic, consider whether concurrency needs to be addressed with per-port locking or serialization.
|
|
223
|
+
|
|
224
|
+
### Risk: Global Logger Context Override
|
|
225
|
+
|
|
226
|
+
- **Area:** `nodes/wsTunnel.js` lines 47–48
|
|
227
|
+
- **Nature:** `setLogContext` and `setLogLevel` affect the global logger. If multiple `wstunnel server` nodes are deployed, the last deployed node's context and level win for all nodes.
|
|
228
|
+
- **Existing test:** None.
|
|
229
|
+
- **What to verify:** Before modifying logger configuration, consider whether per-node logger instances are needed.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Known Issues
|
|
234
|
+
|
|
235
|
+
Known defects or suspicious behavior discovered during contract analysis. Known Issues are non-normative and are documented for visibility only.
|
|
236
|
+
|
|
237
|
+
### KI-001 — Inverted host/path Default Logic *(Resolved)*
|
|
238
|
+
|
|
239
|
+
- **Area:** `nodes/wsTunnel.js` lines 52–53
|
|
240
|
+
- **Nature:** The ternary expressions `node.tunnelConfig.options.host ? '' : undefined` were inverted. When `host` had a value (truthy), it was set to `''` (empty string). When falsy, it was set to `undefined`. The same applied to `path`. This meant configured host/path values were discarded.
|
|
241
|
+
- **Resolution:** Replaced with `host || undefined` and `path || undefined`. Configured values now pass through to `startWebSocketServer` correctly.
|
|
242
|
+
- **Regression test:** `test/ki-001-host-path-passthrough.test.js` (4 tests)
|
|
243
|
+
|
|
244
|
+
### KI-002 — tunnelIdHeaderName Default Mismatch *(Resolved)*
|
|
245
|
+
|
|
246
|
+
- **Area:** `nodes/wsTunnel.js` line 32 vs `nodes/wsTunnel.html` line 77
|
|
247
|
+
- **Nature:** The constructor defaults `tunnelIdHeaderName` to `'x-tunnel-id'`, but the HTML registration defaulted it to `''`. When a user did not configure this field, the runtime value differed from the editor default.
|
|
248
|
+
- **Resolution:** Updated HTML default from `''` to `'x-tunnel-id'` to match constructor, documentation, and library examples.
|
|
249
|
+
- **Regression test:** `test/ki-002-tunnel-id-header-default.test.js` (3 tests)
|
|
250
|
+
|
|
251
|
+
### KI-003 — Leftover Debug Log Statement *(Resolved)*
|
|
252
|
+
|
|
253
|
+
- **Area:** `nodes/wsTunnel.js` line 117
|
|
254
|
+
- **Nature:** `logger.info('[DEBUG] Close called for node ...')` was a debug statement left in production code using info level. It produced unnecessary log noise in every deployment.
|
|
255
|
+
- **Resolution:** Changed to `logger.debug(...)` and removed `[DEBUG]` prefix. Message now only appears when debug logging is enabled.
|
|
256
|
+
- **Regression test:** `test/ki-003-debug-log-level.test.js` (3 tests)
|
|
257
|
+
|
|
258
|
+
### KI-004 — console.error in Production Code *(Resolved)*
|
|
259
|
+
|
|
260
|
+
- **Area:** `nodes/wsTunnel.js` line 127
|
|
261
|
+
- **Nature:** `console.error` was used directly instead of the project's `logger.error`. This bypassed log level filtering and produced inconsistent output format.
|
|
262
|
+
- **Resolution:** Replaced with `logger.error` to maintain logging consistency with the start-failure path.
|
|
263
|
+
- **Regression test:** `test/ki-004-console-error-usage.test.js` (1 test)
|
|
264
|
+
|
|
265
|
+
### KI-005 — License Inconsistency *(Resolved)*
|
|
266
|
+
|
|
267
|
+
- **Area:** `README.md` (Apache-2.0), `package.json` (MIT), `nodes/wsTunnel.js` header (Apache-2.0), `nodes/wsTunnel.html` header (Apache-2.0).
|
|
268
|
+
- **Nature:** The license declarations were inconsistent across files. `package.json` declared MIT while all other files declared Apache-2.0.
|
|
269
|
+
- **Resolution:** Updated `package.json` from `"license": "MIT"` to `"license": "Apache-2.0"` to match the source files and README.
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## Open Questions
|
|
274
|
+
|
|
275
|
+
These are behaviors important to the project for which the repository does not provide sufficient evidence to define a formal invariant. Open Questions are unknown and must not be assumed to be requirements.
|
|
276
|
+
|
|
277
|
+
*(No open questions remain.)*
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Contract Change Policy
|
|
282
|
+
|
|
283
|
+
A modification to an existing RWT-\* invariant requires:
|
|
284
|
+
|
|
285
|
+
1. An explicit update to this file (STABILITY_CONTRACT.md).
|
|
286
|
+
2. A written explanation of the changed semantics and the reason for the change.
|
|
287
|
+
3. An update or addition of regression tests for the affected invariant.
|
|
288
|
+
4. Verification that no dependent invariants are violated.
|
|
289
|
+
|
|
290
|
+
The Stability Contract must never be weakened, removed, or modified solely to make an implementation or test pass. A contract change is appropriate only when the intended system behavior has actually changed.
|
|
291
|
+
|
|
292
|
+
New invariants may only be added when:
|
|
293
|
+
|
|
294
|
+
- There is concrete evidence in the repository (code, tests, documentation, CHANGELOG, or commit history) that the property is intentionally protected.
|
|
295
|
+
- The invariant is formulated as a verifiable property, not a vague guideline.
|
|
296
|
+
- The invariant is not a restatement of a rule already in AGENTS.md.
|
|
297
|
+
- The invariant represents a meaningful stability or compatibility requirement.
|
|
298
|
+
|
|
299
|
+
Implementation details may change without modifying an invariant when the invariant itself remains satisfied.
|