node-red-contrib-ntrip 0.2.10 → 0.2.11

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +5 -3
  3. package/examples/watchdog.json +1 -1
  4. package/ntrip/lib/ntrip-client.js +70 -72
  5. package/ntrip/nodes/nmea-decoder-node.js +36 -38
  6. package/ntrip/nodes/nmea-encoder-node.js +97 -97
  7. package/ntrip/nodes/ntrip-client-node.js +153 -148
  8. package/ntrip/nodes/rtcm-decoder-node.js +50 -47
  9. package/ntrip/nodes/rtcm-encoder-node.js +33 -32
  10. package/package.json +16 -6
  11. package/.github/dependabot.yml +0 -23
  12. package/.github/workflows/node.js.yml +0 -32
  13. package/.github/workflows/release.yml +0 -65
  14. package/.prettierignore +0 -20
  15. package/.prettierrc.json +0 -9
  16. package/CLAUDE.md +0 -73
  17. package/doc/architecture/README.md +0 -19
  18. package/doc/architecture/adr/0001-single-registration-file.md +0 -64
  19. package/doc/architecture/adr/0002-ntrip-uploader-extension.md +0 -71
  20. package/doc/architecture/adr/0003-two-output-decoder-design.md +0 -73
  21. package/doc/architecture/adr/0004-stateful-handshake-interception.md +0 -74
  22. package/doc/architecture/adr/0005-rtcm-partial-frame-buffer.md +0 -87
  23. package/doc/architecture/adr/0006-nmea-multi-sentence-split.md +0 -66
  24. package/doc/architecture/adr/0007-mocha-test-helper-stack.md +0 -67
  25. package/doc/architecture/adr/0008-coordinate-gating-sentinel.md +0 -76
  26. package/doc/architecture/adr/README.md +0 -18
  27. package/doc/architecture/architecture-decisions.md +0 -60
  28. package/doc/architecture/behavioural-design.md +0 -226
  29. package/doc/architecture/errors-and-weaknesses.md +0 -71
  30. package/doc/architecture/future-improvements.md +0 -130
  31. package/doc/architecture/overview.md +0 -77
  32. package/doc/architecture/refactoring-recommendations.md +0 -114
  33. package/doc/architecture/statistics.md +0 -118
  34. package/doc/architecture/structural-design.md +0 -141
  35. package/eslint.config.js +0 -36
  36. package/test/nmea-decoder.spec.js +0 -146
  37. package/test/nmea-encoder.spec.js +0 -104
  38. package/test/rtcm-decoder.spec.js +0 -116
  39. package/test/rtcm-encoder.spec.js +0 -144
@@ -1,67 +0,0 @@
1
- # ADR-0007: Mocha + node-red-node-test-helper for the test suite
2
-
3
- ## Status
4
-
5
- Accepted in 0.2.6.
6
-
7
- ## Context
8
-
9
- Up to 0.2.5 the package had no automated tests — three patch releases in two
10
- weeks shipped regressions that an even minimal suite would have caught.
11
- Adding tests was overdue. Choices:
12
-
13
- - **Mocha** vs **Jest** as the runner. Mocha is the dominant choice for
14
- published `node-red-contrib-*` packages; Jest's auto-mocking and snapshotting
15
- add nothing this package needs.
16
- - **`node-red-node-test-helper`** vs **direct invocation of node factories
17
- with a hand-rolled `RED` stub**. The helper spins up a real Node-RED runtime
18
- in-process, loads a flow, and exposes wired nodes. A hand-rolled stub would
19
- let tests run faster but would not exercise the real lifecycle hooks
20
- (`registerType`, `createNode`, `node.receive`, `node.send`) — which is
21
- exactly where the recent regressions lived.
22
- - **`chai` v4 vs v5.** v5 is ESM-only. The package is CommonJS and there is
23
- no reason to move it; pin v4.
24
-
25
- ## Decision
26
-
27
- - Mocha for the test runner.
28
- - `node-red-node-test-helper` for the in-process Node-RED runtime.
29
- - `chai` v4 for the assertion DSL.
30
- - `node-red` itself as a dev dep (required by the helper from v0.3 onwards).
31
-
32
- Tests live under `test/` with one `*.spec.js` per node. Each previously-fixed
33
- regression has a dedicated test tagged `(regression: …)` so they can be run
34
- in isolation with `mocha -g regression`.
35
-
36
- CI runs `npm test` across the existing matrix (Node 18 / 20 / 22).
37
-
38
- ## Consequences
39
-
40
- **Positive**
41
-
42
- - Real Node-RED runtime — tests verify the actual `registerType` + `createNode`
43
- + `node.send` path, not a stub.
44
- - Each spec file is small (~100 LOC) and self-contained.
45
- - Regression tests are explicit — visible in test names, locked in for the
46
- long term.
47
- - CI gate prevents accidental regressions on every push.
48
-
49
- **Negative**
50
-
51
- - `node-red` is a ~400-package dev dependency tree, with some known
52
- vulnerabilities (all dev-only, not reachable in the published artefact).
53
- - First test run is slower (~80 ms helper startup) than a unit test on plain
54
- functions would be.
55
- - Spec authors have to learn the `helper.load(...)` + `helper.getNode(...)`
56
- +`on('input', …)` pattern — slight learning curve.
57
-
58
- **Trade-offs**
59
-
60
- - Chose **realism** over **speed and small dep tree**. For four nodes with
61
- tight Node-RED integration, integrating the real runtime is worth the
62
- weight.
63
-
64
- ## Related
65
-
66
- - Code: [test/](../../../test/) and [.github/workflows/node.js.yml](../../../.github/workflows/node.js.yml)
67
- - Statistics: [statistics.md](../statistics.md) "Test suite" section
@@ -1,76 +0,0 @@
1
- # ADR-0008: `(0, 0, 0)` as the "no location" sentinel for GGA emission
2
-
3
- ## Status
4
-
5
- Accepted in 0.2.5.
6
-
7
- ## Context
8
-
9
- Many NTRIP casters require a position fix from the client before they will
10
- stream corrections — they need to know which network-RTK base station to
11
- deliver corrections from. The standard way to convey the fix is a periodic
12
- NMEA `GGA` sentence from the client to the caster. The upstream `ntrip-client`
13
- package's `xyz` option turns on this behaviour: if `xyz` is set, a `GGA` is
14
- synthesised from those coordinates and sent every `interval` ms.
15
-
16
- In the editor form the user supplies three numeric fields (X / Y / Z) plus an
17
- interval. There needs to be a way to say "I have no location, do not send
18
- GGA" — for casters that *don't* require GGA, sending a synthetic one is
19
- unnecessary noise.
20
-
21
- Two options:
22
-
23
- 1. **A dedicated checkbox / radio** (`Send periodic GGA: yes/no`).
24
- 2. **A sentinel value** — treat `(0, 0, 0)` as "no location".
25
-
26
- Pre-0.2.5 the code used `x && y && z` as the gate, which had the wrong
27
- semantics — *any* zero component (e.g. someone on the equator with X≈6378km,
28
- Y=0, Z=0) disabled the GGA.
29
-
30
- ## Decision
31
-
32
- Use `(0, 0, 0)` as the sentinel. The check is:
33
-
34
- ```javascript
35
- if (Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z)
36
- && !(x === 0 && y === 0 && z === 0)) {
37
- options.xyz = [x, y, z];
38
- }
39
- ```
40
-
41
- - All three must be finite — `NaN` (the result of `parseFloat('')` on an
42
- empty form) does not enable GGA.
43
- - The all-zero triple is the only excluded value.
44
- - Coordinates with one or two zero components are fine — they fall through to
45
- the caster.
46
-
47
- ## Consequences
48
-
49
- **Positive**
50
-
51
- - One-field-fewer in the editor form.
52
- - Equator / axis-aligned positions work correctly.
53
- - Matches the convention used by other NTRIP tooling (rtklib, str2str): if
54
- you don't want GGA, you set the coordinates to all zeros.
55
-
56
- **Negative**
57
-
58
- - Discoverability — a user looking at the form has no in-UI hint that
59
- `(0, 0, 0)` is special. The README and the editor help text document this,
60
- but a fresh user might not read either.
61
- - The Earth's center of mass at `(0, 0, 0)` ECEF is technically a *real*
62
- position. Not a meaningful place for a GNSS receiver to be, but the sentinel
63
- collides with one mathematically valid input.
64
-
65
- **Trade-offs**
66
-
67
- - Picked **minimum form surface** over **explicit toggle**. If user feedback
68
- reports confusion, swap to an explicit checkbox in a future minor release
69
- and keep the sentinel for backward compatibility.
70
-
71
- ## Related
72
-
73
- - Code: [ntrip/nodes/ntrip-client-node.js:43-46](../../../ntrip/nodes/ntrip-client-node.js#L43-L46)
74
- - Editor help text: [ntrip/99-ntrip.html](../../../ntrip/99-ntrip.html) "Optional ECEF coordinate triple" block
75
- - README: "Notes on usage — NTRIP Client: coordinates"
76
- - CHANGELOG: 0.2.5 "Coordinate guard changed from `x && y && z` to `not all zero`"
@@ -1,18 +0,0 @@
1
- # ADR Index
2
-
3
- This folder contains Architecture Decision Records. One file per ADR, numbered
4
- in the order they were accepted.
5
-
6
- | # | Title | Status | Date accepted |
7
- |---|-------|--------|---------------|
8
- | [0001](0001-single-registration-file.md) | Single registration file for all nodes | Accepted | original design |
9
- | [0002](0002-ntrip-uploader-extension.md) | NTRIP uploader as full-replacement extension of the upstream client | Accepted | 0.2.0 |
10
- | [0003](0003-two-output-decoder-design.md) | Two-output `[ok, error]` convention for decoders / encoder | Accepted | original design (reaffirmed in 0.2.6) |
11
- | [0004](0004-stateful-handshake-interception.md) | Stateful handshake interception in the NtripClient | Accepted | 0.2.5 |
12
- | [0005](0005-rtcm-partial-frame-buffer.md) | Carry-over buffer for partial RTCM frames | Accepted | 0.2.5 |
13
- | [0006](0006-nmea-multi-sentence-split.md) | Multi-sentence splitting in the NMEA decoder | Accepted | 0.2.5 |
14
- | [0007](0007-mocha-test-helper-stack.md) | Mocha + node-red-node-test-helper for the test suite | Accepted | 0.2.6 |
15
- | [0008](0008-coordinate-gating-sentinel.md) | `(0, 0, 0)` as the "no location" sentinel for GGA emission | Accepted | 0.2.5 |
16
-
17
- See [../architecture-decisions.md](../architecture-decisions.md) for the
18
- template and the criteria for when to add a new ADR.
@@ -1,60 +0,0 @@
1
- # Architecture Decisions
2
-
3
- This chapter is the index of Architecture Decision Records (ADRs). Each
4
- individual ADR is stored as its own markdown file under [adr/](adr/) and
5
- follows a compact template (Status / Context / Decision / Consequences /
6
- Related).
7
-
8
- ## Index
9
-
10
- | # | Title | Status |
11
- |---|-------|--------|
12
- | [0001](adr/0001-single-registration-file.md) | Single registration file for all nodes | Accepted |
13
- | [0002](adr/0002-ntrip-uploader-extension.md) | NTRIP uploader as full-replacement extension of the upstream client | Accepted |
14
- | [0003](adr/0003-two-output-decoder-design.md) | Two-output `[ok, error]` convention for decoders / encoder | Accepted |
15
- | [0004](adr/0004-stateful-handshake-interception.md) | Stateful handshake interception in the NtripClient | Accepted |
16
- | [0005](adr/0005-rtcm-partial-frame-buffer.md) | Carry-over buffer for partial RTCM frames | Accepted |
17
- | [0006](adr/0006-nmea-multi-sentence-split.md) | Multi-sentence splitting in the NMEA decoder | Accepted |
18
- | [0007](adr/0007-mocha-test-helper-stack.md) | Mocha + node-red-node-test-helper for the test suite | Accepted |
19
- | [0008](adr/0008-coordinate-gating-sentinel.md) | `(0, 0, 0)` as the "no location" sentinel for GGA emission | Accepted |
20
-
21
- ## When to add a new ADR
22
-
23
- Add one when a decision is non-obvious and would surprise a contributor reading
24
- the code six months from now. Typical signals:
25
-
26
- - The code does something differently from how upstream / community examples
27
- do it.
28
- - The choice was load-bearing — reverting it would break a documented contract.
29
- - There is a clear trade-off (e.g. simpler code vs. larger memory footprint)
30
- that future maintainers should understand before changing direction.
31
-
32
- ## When *not* to add an ADR
33
-
34
- Skip the formality for routine bug fixes, refactors that don't change external
35
- behaviour, or decisions that are already obvious from idiomatic style. The
36
- goal is a small, stable archive — not a write-up of every commit.
37
-
38
- ## Template
39
-
40
- ```markdown
41
- # ADR-NNNN: Short imperative title
42
-
43
- ## Status
44
- Proposed | Accepted | Superseded by ADR-MMMM | Deprecated
45
-
46
- ## Context
47
- What problem are we solving? What constraints / forces are at play?
48
-
49
- ## Decision
50
- What we are going to do, in one or two sentences.
51
-
52
- ## Consequences
53
- - Positive: …
54
- - Negative: …
55
- - Trade-offs: …
56
-
57
- ## Related
58
- - Code: [path/to/file.js:LL-LL](../../path/to/file.js#LLL-LLL)
59
- - ADRs: [[ADR-XXXX](XXXX-title.md)]
60
- ```
@@ -1,226 +0,0 @@
1
- # Behavioural Design
2
-
3
- ## Node-RED node lifecycle
4
-
5
- Every node in this package follows Node-RED's standard lifecycle:
6
-
7
- ```
8
- deploy / restart
9
-
10
-
11
- constructor(config) ← create network resources, reset counters
12
-
13
-
14
- on('input', msg) ──── many ────► (transform / forward / write to socket)
15
-
16
-
17
- on('close', done) ← release resources, clear status, done()
18
- ```
19
-
20
- The constructor receives a `config` object built from the editor form. The
21
- input handler is called for every message flowing into the node. The close
22
- handler runs on flow redeploy or Node-RED shutdown.
23
-
24
- ## NtripClient — handshake state machine
25
-
26
- The `NtripClient` node intercepts the caster's first reply line and uses it to
27
- set its own status. Prior to v0.2.5 this check ran on **every** inbound TCP
28
- packet, which both inflated the message counter and risked dropping RTCM bytes
29
- that shared a TCP segment with the handshake reply. The current design is a
30
- two-state machine:
31
-
32
- ```
33
- start
34
-
35
-
36
- ┌──────────────┐ data: "ICY 200 OK\r\n\r\n..." ┌─────────────┐
37
- │ disconnected │ ───────────────────────────────► │ connected │
38
- │ │ data: "ICY 406 ..." │ │
39
- │ │ ──────► error, stay disconnected │ │
40
- │ │ data: "SOURCETABLE 200 OK..." │ │
41
- │ │ ──────► error, stay disconnected │ │
42
- │ │ data: anything else │ │
43
- │ │ ──────► forward, transition to ─►│ │
44
- └──────────────┘ └──────┬──────┘
45
- ▲ │
46
- │ socket close / error │
47
- └──────────────────────────────────────────────────┘
48
- ```
49
-
50
- Key behaviours:
51
-
52
- - **Transitional bytes are not lost.** When the handshake reply and the first
53
- RTCM frame share a TCP segment (common), the node splits at the `\r\n\r\n`
54
- header terminator and forwards the trailing RTCM bytes on the output.
55
- - **Reset on close.** The `connected` flag is set back to `false` when the
56
- socket closes, so the next reconnect runs the handshake recognition again.
57
- - **Counters track Rx and Tx separately.** `messagesReceived` is only bumped
58
- for data forwarded after handshake (not control replies). `messagesSent` is
59
- bumped per input message written to the caster.
60
-
61
- See [ADR-0004](adr/0004-stateful-handshake-interception.md).
62
-
63
- ## NtripClient — input handling
64
-
65
- ```
66
- msg.payload arrives
67
-
68
-
69
- payload nullish? ─yes─► drop (no-op)
70
- │ no
71
-
72
- Array.isArray(payload)? ─yes─► client.setXYZ(payload) # update GGA seed
73
- │ no
74
-
75
- client.write(payload)
76
-
77
-
78
- node.passthrough? ─yes─► node.send(msg) # echo on output
79
- │ no
80
-
81
- updateStatus()
82
- ```
83
-
84
- An `Array` payload is interpreted as a coordinate triple `[x, y, z]` and routed
85
- to the underlying client's `setXYZ` method — used by Sapos and other VRS
86
- casters that gate the stream on a `GGA` from the client. A non-array payload
87
- is written verbatim to the caster.
88
-
89
- ## NtripClient — outbound write error handling
90
-
91
- `net.Socket.write` is asynchronous; runtime failures (ECONNRESET, EPIPE) come
92
- through the socket's `error` event rather than as synchronous throws. The node
93
- therefore has two error paths:
94
-
95
- - **Synchronous** — the try/catch around `client.write` catches misuse
96
- (`client` undefined, wrong-shape payload), reports via `node.error` and
97
- flashes the status badge red.
98
- - **Asynchronous** — the socket `error` listener calls `node.error` with the
99
- same wording. `AggregateError` instances (e.g. from happy-eyeballs DNS
100
- resolution failures) are expanded into one log line per inner error.
101
-
102
- ## RtcmDecoder — frame reassembly across input events
103
-
104
- RTCM 3 frames are variable-length and routinely cross TCP packet boundaries.
105
- The decoder keeps a `pendingBuffer` on the node instance and reassembles:
106
-
107
- ```
108
- on('input', msg):
109
- chunk = msg.payload (Buffer or coerced)
110
- buffer = concat(pendingBuffer, chunk)
111
-
112
- loop while buffer not empty:
113
- try: [message, length] = RtcmTransport.decode(buffer)
114
- on throw:
115
- if buffer.length > 64KB:
116
- emit error on output 2
117
- buffer = empty
118
- break # keep remainder for next input event
119
- if length not positive or > buffer.length:
120
- break # defensive zero-length guard
121
- emit decoded message on output 1
122
- buffer = buffer.slice(length)
123
-
124
- pendingBuffer = buffer
125
- updateStatus()
126
- ```
127
-
128
- - **Partial-frame tolerance.** A throw is interpreted as "frame may be incomplete";
129
- bytes stay in `pendingBuffer` to be re-tried with the next chunk.
130
- - **Garbage cap.** If `pendingBuffer` grows past 64 KB without ever decoding a
131
- frame, the data is flushed to the error output and the buffer is cleared —
132
- prevents runaway memory if the upstream feeds non-RTCM bytes indefinitely.
133
- - **Forward-progress guarantee.** A non-positive or oversized `length` from the
134
- decoder breaks the loop rather than spinning.
135
-
136
- See [ADR-0005](adr/0005-rtcm-partial-frame-buffer.md).
137
-
138
- ## NmeaDecoder — multi-sentence splitting
139
-
140
- A single input chunk may carry several `\r\n`-delimited sentences. The decoder
141
- splits and decodes each independently:
142
-
143
- ```
144
- on('input', msg):
145
- rawInput = msg.payload.nmeaMessage ?? msg.payload (preserved untouched)
146
- rawString = Buffer? rawInput.toString('utf8') : rawInput
147
-
148
- for each sentence in rawString.split(/\r?\n/):
149
- sentence = sentence.trim()
150
- skip if empty
151
- try: NmeaTransport.decode(sentence) → emit on output 1
152
- on throw: emit on output 2 with
153
- input: rawInput (Buffer if Buffer was supplied)
154
- inputString: rawString (utf8 form)
155
- ```
156
-
157
- - **`input` preservation.** The original payload (`Buffer` if a `Buffer` was
158
- supplied) is carried through to the error output unchanged — restored in
159
- v0.2.6 after a regression in v0.2.5 forced everything through `toString`
160
- before storing.
161
-
162
- See [ADR-0006](adr/0006-nmea-multi-sentence-split.md).
163
-
164
- ## NmeaEncoder — instance passthrough vs construction
165
-
166
- The encoder accepts two input shapes:
167
-
168
- - A pre-constructed `NmeaMessage*` instance — passed straight to
169
- `NmeaTransport.encode` without inspection.
170
- - A plain field object plus a `messageType` discriminator — fed through a
171
- `switch (messageType)` that maps to the right `NmeaMessage*.construct(input)`
172
- factory.
173
-
174
- ```
175
- on('input', msg):
176
- require payload, payload.nmeaMessage, payload.messageType (else error output)
177
-
178
- try:
179
- messageType = String(payload.messageType).toUpperCase()
180
- input = payload.nmeaMessage
181
-
182
- if input instanceof NmeaMessage:
183
- nmeaMessage = input
184
- else:
185
- nmeaMessage = NmeaMessage<Type>.construct(input)
186
- unknown messageType → throw (caught and routed to error)
187
-
188
- message = NmeaTransport.encode(nmeaMessage)
189
- emit on output 1
190
-
191
- on throw: emit on output 2 (no node.error)
192
- ```
193
-
194
- ## Two-output error routing
195
-
196
- Every decoder/encoder uses the `[ok, error]` two-output convention rather than
197
- calling `node.error()` for per-message failures. The reason is that high-rate
198
- streams routinely emit mismatched binary (the README explicitly suggests
199
- wiring an NMEA decoder onto an RTK2Go stream as a demonstration) — calling
200
- `node.error` for each failure floods the Node-RED `[error]` log. The error
201
- output is the channel; wire it to a debug or downstream handler.
202
-
203
- See [ADR-0003](adr/0003-two-output-decoder-design.md).
204
-
205
- ## Status badge convention
206
-
207
- All four nodes follow the same rule for the badge under the node in the
208
- Node-RED editor:
209
-
210
- | State | Fill | Shape | Text |
211
- |-------|------|-------|------|
212
- | starting | yellow | ring | `connecting...` (NtripClient only) |
213
- | running | green | ring | counters, e.g. `Rx 123 Tx 4` / `NMEA: 12 Invalid: 0` |
214
- | connected (NtripClient) | green | ring | `NTRIP server connected.` |
215
- | handshake error | red | ring | `NTRIP server rejected connection.` or `No mountpoint <name>` |
216
- | write/decode error | red | ring | error message |
217
- | closed | (cleared) | — | — |
218
-
219
- ## Close behaviour
220
-
221
- | Node | On `close` |
222
- |------|------------|
223
- | NtripClient | `client.removeAllListeners()`, then `client.close()`, clear status. |
224
- | RtcmDecoder | Discard `pendingBuffer`, clear status. |
225
- | NmeaDecoder | Clear status. |
226
- | NmeaEncoder | Clear status. |
@@ -1,71 +0,0 @@
1
- # Errors and Weaknesses
2
-
3
- This chapter inventories known limitations. Items are grouped by severity and
4
- labelled by status (Open / Fixed-in-vX.Y.Z / Accepted as design constraint).
5
-
6
- ## Critical
7
-
8
- | ID | Description | Status |
9
- |----|-------------|--------|
10
- | C-1 | `NmeaEncoder` instance passthrough was broken (`input.prototype instanceof NmeaMessage` always false) | Fixed in 0.2.4 |
11
- | C-2 | `NmeaEncoder` catch block referenced an out-of-scope `input`, crashing on every encoder failure | Fixed in 0.2.4 |
12
- | C-3 | `NtripClient` crashed at construction when `mode` was neither `download` nor `upload` (`client.on('data',…)` on `undefined`) | Fixed in 0.2.5 |
13
- | C-4 | `RtcmDecoder` dropped any partial frame at TCP packet boundaries (no carry-over) | Fixed in 0.2.5 |
14
- | C-5 | `NmeaDecoder` only decoded the first sentence of a multi-sentence chunk despite documentation claiming otherwise | Fixed in 0.2.5 |
15
- | C-6 | `NmeaDecoder` and `RtcmDecoder` called `node.error()` for every decode failure — flooded the Node-RED log when fed mismatched binary (the README's own demo wiring) | Fixed in 0.2.6 |
16
- | C-7 | `NmeaDecoder` error output stored the `toString`-converted form on `input`, losing the original `Buffer` | Fixed in 0.2.6 |
17
-
18
- ## High
19
-
20
- | ID | Description | Status |
21
- |----|-------------|--------|
22
- | H-1 | Always-on handshake check on every data packet — risked dropping RTCM bytes that shared a TCP segment with the `ICY 200 OK` reply | Fixed in 0.2.5 |
23
- | H-2 | Coordinate guard `x && y && z` silently dropped axis-aligned positions (e.g. equator) | Fixed in 0.2.5 |
24
- | H-3 | `NmeaEncoder` unknown `messageType` silently emitted `nmeaMessage: undefined` on the success output | Fixed in 0.2.4 |
25
- | H-4 | `client.write` rejection errors were not visible (async — not caught by the surrounding `try`) | **Open.** Async write failures now surface via the socket `error` listener, but no per-input error feedback. |
26
- | H-5 | RTCM/NMEA decoder loop had no zero-length-progress guard — theoretical infinite loop | Fixed in 0.2.5 |
27
-
28
- ## Medium
29
-
30
- | ID | Description | Status |
31
- |----|-------------|--------|
32
- | M-1 | Password input rendered as `type="text"` in the editor form | Fixed in 0.2.5 |
33
- | M-2 | CR/LF injection possible via `mountpoint`/`username`/`password` (interpolated into the handshake string) | Fixed in 0.2.5 — rejected at construct time |
34
- | M-3 | `AggregateError` referenced unconditionally; ReferenceError on Node ≤ 14 | Fixed in 0.2.5 — `typeof` guard, and `engines.node` bumped to ≥ 18 |
35
- | M-4 | `host` accepted as `""`/`null`; `interval` forwarded as a raw string | Fixed in 0.2.5 |
36
- | M-5 | Counter `messagesReceived` mixed inbound, outbound, and handshake replies | Fixed in 0.2.5 — split into `Rx`/`Tx` |
37
- | M-6 | No connection-state event on the `NtripClient` output — downstream flows can only react to the status badge text | **Open.** See [Future Improvements](future-improvements.md). |
38
- | M-7 | Listener leaks on `client.close()` — accumulated across reconnects | Fixed in 0.2.5 — `removeAllListeners` on close |
39
-
40
- ## Low
41
-
42
- | ID | Description | Status |
43
- |----|-------------|--------|
44
- | L-1 | `engines.node: ">=7.6.0"` was inconsistent with code that uses `AggregateError` and the CI matrix (18/20/22) | Fixed in 0.2.5 — bumped to `>=18.0.0` |
45
- | L-2 | Editor help text was stubs (`<p>.</p>`) for three of four nodes | Fixed in 0.2.6 — full `<dl class="message-properties">` blocks |
46
- | L-3 | Decoders/encoder kept the original input attached to every output message (memory pressure for high-rate streams) | **Open.** Cost is one Buffer reference per emission. |
47
- | L-4 | `'socket timeouted'` typo in the upstream socket error message | Fixed in 0.2.5 |
48
- | L-5 | NMEA case `'Object'` was unreachable because `messageType.toUpperCase()` ran first | Fixed in 0.2.4 |
49
-
50
- ## Open design-level constraints
51
-
52
- These are *accepted* limitations — documented rather than fixed, because the
53
- fix would be a significant architectural change.
54
-
55
- - **NTRIP over plaintext TCP only.** No NTRIPS / TLS support. NTRIP V2 over TLS
56
- is a real protocol but the upstream `ntrip-client` package does not implement
57
- it, and this contrib does not bring its own transport layer. See
58
- [Future Improvements](future-improvements.md).
59
- - **`_connect()` full replacement** (not delegation) in
60
- [ntrip/lib/ntrip-client.js](../../ntrip/lib/ntrip-client.js). Improvements to
61
- the upstream client's connection logic (keep-alive, IPv6 handling, TLS
62
- upgrade) won't propagate into the uploader path. See
63
- [ADR-0002](adr/0002-ntrip-uploader-extension.md).
64
- - **`credentials` block must be declared in two places** — both the JS
65
- `registerType` and the HTML `RED.nodes.registerType`. Drift is detectable
66
- only at runtime when persistence silently fails.
67
- - **No per-node integration tests for `NtripClient`** — the unit test stack
68
- covers the three pure transformer nodes only. The client requires a fake TCP
69
- server for meaningful testing; see [Future Improvements](future-improvements.md).
70
- - **Editor HTML help text duplicates much of the runtime documentation** — any
71
- schema change to a `payload` shape needs two updates (code + help text).
@@ -1,130 +0,0 @@
1
- # Future Improvements
2
-
3
- These are *new capability* items rather than refactors. They would change
4
- behaviour visible to the user, and most warrant a minor version bump
5
- (0.3.0+) or a major one if they break the public message shape.
6
-
7
- ## Protocol / network
8
-
9
- ### NTRIPS (NTRIP over TLS)
10
-
11
- NTRIP V2 may run over TLS (typically port 2102, sometimes 443). The package
12
- currently relies on plaintext TCP via `net.createConnection`. Adding TLS would
13
- need:
14
-
15
- - A toggle in the editor form (`useTls: boolean`).
16
- - A separate `tls.connect` path in [ntrip/lib/ntrip-client.js](../../ntrip/lib/ntrip-client.js).
17
- - Optional CA bundle / SNI / certificate-pinning settings.
18
- - Documentation that NTRIPS handshake is identical to NTRIP V2 once the TLS
19
- tunnel is established.
20
-
21
- Breaks no existing flow — pure additive.
22
-
23
- ### Connection-state event output
24
-
25
- Today, downstream Node-RED flows cannot react to `NtripClient` reconnects,
26
- rejections, or handshake completion except by scraping the status badge text.
27
- Three implementation options, in order of disruption:
28
-
29
- 1. Emit a tagged message on the existing output:
30
- `{ payload: null, state: 'connected' | 'rejected' | 'reconnecting' }`.
31
- Slightly viral — every downstream node must filter `payload != null`.
32
- 2. Add a second output port for state events. Breaks layout of existing flows.
33
- 3. Surface state via Node-RED's runtime event bus (`RED.events.emit`).
34
- Non-breaking but requires opt-in from consumers.
35
-
36
- ### NTRIP source-table fetch / mountpoint picker
37
-
38
- The branch `prepared getMountpoints: postponed…` (commit `38a243b`) indicates a
39
- prior attempt at fetching a caster's source table to populate a dropdown in
40
- the editor. The hold-up was that credentials are stored server-side and the
41
- editor JS can't access them. A workable approach: add a small admin endpoint
42
- (`RED.httpAdmin.get('/ntrip/sourcetable')`) that performs the fetch on the
43
- server using the stored credentials.
44
-
45
- ## Test / quality
46
-
47
- ### NtripClient integration tests
48
-
49
- Use `net.createServer` to stand up a fake caster that the test harness fully
50
- controls. Worth-testing scenarios:
51
-
52
- - Each `authmode` writes the correct handshake string.
53
- - `ICY 200 OK\r\n\r\n<rtcm>` reply: trailing bytes are forwarded.
54
- - `ICY 406`: error logged, status red, no data forwarded.
55
- - Mid-stream socket close: `connected` resets so the next handshake reply
56
- re-enters the state machine.
57
- - `msg.payload = [x, y, z]` routes to `setXYZ` (verify via the upstream
58
- client's internal GGA emission).
59
- - CR/LF in credentials: rejected at construct, no socket opened.
60
-
61
- ### Coverage reporting
62
-
63
- Add `c8` (or `nyc`) as a dev dep and a `coverage` npm script. Currently
64
- Statistics shows "not measured"; a single CI badge changes that. Suggest a
65
- 70 % statement coverage floor as a soft target — high enough to be useful, low
66
- enough not to require gymnastics on the rare error paths.
67
-
68
- ### Linting
69
-
70
- Adopt `eslint` with `eslint-config-node-red-contribs` or the maintainer's own
71
- preference. Would have caught most of the regressions from 0.2.4 / 0.2.5
72
- (unused vars, `let` vs `const`, missing returns).
73
-
74
- ### TypeScript types (`.d.ts`)
75
-
76
- The four nodes have a public message-shape contract (the `payload` schemas
77
- documented in the help text). Publishing JSDoc-derived `.d.ts` files would let
78
- TypeScript users in Node-RED's `function` nodes get autocomplete for
79
- `msg.payload.messageType`, `msg.payload.message.staX`, etc. Lightweight to
80
- add via `// @ts-check` and JSDoc on the existing JS source.
81
-
82
- ## Editor UX
83
-
84
- ### Per-node info-sidebar fixtures
85
-
86
- Add small example payloads to the help text so users can copy them straight
87
- into an `inject` node:
88
-
89
- ```javascript
90
- // Example NmeaEncoder input
91
- msg.payload = { messageType: 'GGA', nmeaMessage: { /* fields */ } };
92
- ```
93
-
94
- The HTML stubs added in 0.2.6 are documentation but no example flows are
95
- linked from the editor — only from the README. A `<a href="…/examples/…">`
96
- link inside each help block would close that gap.
97
-
98
- ### Hide credentials when not needed
99
-
100
- When `mode === 'download'` and the caster doesn't require auth, the
101
- Username/Password fields are still shown. A small `oneditprepare` hide rule
102
- keyed on a "Requires authentication" checkbox would declutter the dialog.
103
-
104
- ## Codec coverage
105
-
106
- ### More NMEA sentence types
107
-
108
- The current encoder supports 17 sentence types. The `@gnss/nmea` library has
109
- more (e.g. `VBW`, `VDR`, `WCV`, …). Adding a case is one import + one
110
- `switch` arm; the test suite already covers the through-path, so each new
111
- type is ~5 LOC.
112
-
113
- ### RTCM message-type filter
114
-
115
- A `messageTypeFilter: number[]` config (e.g. `[1005, 1077, 1087]`) on the
116
- decoder would let users drop frame types they don't care about *before* they
117
- reach downstream nodes. Reduces flow noise and downstream work.
118
-
119
- ## Distribution / packaging
120
-
121
- ### Publish `node-red` entries
122
-
123
- [package.json](../../package.json) `keywords` includes `"mmea"` (typo — should
124
- be `"nmea"`). Catalogue search misses are real downloads lost.
125
-
126
- ### CI: publish on tag
127
-
128
- The `.github/workflows/npm-publish.yml` (out of scope here) already exists.
129
- Worth pairing with semantic-release or a manual tag-driven publish so the
130
- release cycle is auditable from GitHub.