signalk-edge-link 2.9.1 → 3.0.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.
Files changed (145) hide show
  1. package/README.md +310 -306
  2. package/lib/app/config/migrate.js +13 -0
  3. package/lib/app/config/schema.js +58 -0
  4. package/lib/app/config/validation.js +18 -0
  5. package/lib/app/config/watcher.js +191 -0
  6. package/lib/app/connection/build-context.js +328 -0
  7. package/lib/app/connection/context.js +6 -0
  8. package/lib/app/connection/lifecycle-ops.js +206 -0
  9. package/lib/app/connection/process-delta.js +146 -0
  10. package/lib/app/connection/socket-recovery.js +125 -0
  11. package/lib/app/connection/start-client.js +225 -0
  12. package/lib/app/connection/start-server.js +95 -0
  13. package/lib/app/connection-manager/start.js +165 -0
  14. package/lib/app/connection-manager.js +74 -0
  15. package/lib/app/connection.js +46 -0
  16. package/lib/app/lifecycle.js +69 -0
  17. package/lib/bin/edge-link-cli.js +31 -23
  18. package/lib/{compact-delta.js → codec/compact-delta.js} +29 -18
  19. package/lib/{pipeline-utils.js → codec/compression.js} +7 -59
  20. package/lib/{crypto.js → codec/crypto.js} +99 -35
  21. package/lib/codec/delta-sanitizer/filter.js +167 -0
  22. package/lib/codec/delta-sanitizer/internal.js +10 -0
  23. package/lib/codec/delta-sanitizer/quantize.js +102 -0
  24. package/lib/codec/delta-sanitizer/throttle.js +146 -0
  25. package/lib/codec/delta-sanitizer.js +208 -0
  26. package/lib/codec/metadata/cache.js +120 -0
  27. package/lib/{metadata.js → codec/metadata/collect.js} +152 -246
  28. package/lib/codec/metadata-codec.js +24 -0
  29. package/lib/codec/packet/builder.js +257 -0
  30. package/lib/codec/packet/constants.js +115 -0
  31. package/lib/codec/packet/parser.js +208 -0
  32. package/lib/codec/packet-codec.js +25 -0
  33. package/lib/{pathDictionary.js → codec/path-dictionary-data.js} +8 -212
  34. package/lib/codec/path-dictionary.js +221 -0
  35. package/lib/codec/source-codec.js +28 -0
  36. package/lib/{source-snapshot.js → codec/source-snapshot.js} +1 -1
  37. package/lib/{value-dedup.js → codec/value-dedup.js} +17 -4
  38. package/lib/{values-snapshot.js → codec/values-snapshot.js} +57 -58
  39. package/lib/connection-config.js +66 -13
  40. package/lib/domain/delta-batcher.js +102 -0
  41. package/lib/domain/keepalive-manager.js +68 -0
  42. package/lib/domain/metadata-streamer.js +172 -0
  43. package/lib/domain/metrics/prometheus.js +286 -0
  44. package/lib/domain/metrics/registry.js +338 -0
  45. package/lib/domain/monitoring/alert-manager.js +211 -0
  46. package/lib/domain/monitoring/index.js +25 -0
  47. package/lib/{packet-capture.js → domain/monitoring/packet-capture.js} +5 -4
  48. package/lib/domain/monitoring/packet-loss-tracker.js +149 -0
  49. package/lib/domain/monitoring/path-latency-tracker.js +131 -0
  50. package/lib/domain/monitoring/retransmission-tracker.js +106 -0
  51. package/lib/domain/source-registry.js +346 -0
  52. package/lib/domain/source-snapshot-service.js +161 -0
  53. package/lib/domain/subscription-manager.js +284 -0
  54. package/lib/{config-io.js → foundation/config-io.js} +8 -1
  55. package/lib/foundation/config-reload.js +160 -0
  56. package/lib/{constants.js → foundation/constants.js} +23 -2
  57. package/lib/{shared → foundation}/crypto-constants.js +3 -3
  58. package/lib/foundation/logger.js +32 -0
  59. package/lib/foundation/result.js +74 -0
  60. package/lib/foundation/types/index.js +26 -0
  61. package/lib/foundation/types/instance.js +2 -0
  62. package/lib/foundation/types/metrics.js +2 -0
  63. package/lib/foundation/types/monitoring.js +2 -0
  64. package/lib/foundation/types/pipeline.js +2 -0
  65. package/lib/foundation/types/signalk.js +2 -0
  66. package/lib/index.js +26 -280
  67. package/lib/routes/config-validation.js +7 -3
  68. package/lib/routes/config.js +48 -19
  69. package/lib/routes/connections.js +16 -6
  70. package/lib/routes/control.js +31 -18
  71. package/lib/routes/metrics.js +11 -4
  72. package/lib/routes.js +64 -10
  73. package/lib/shared/connection-schema.js +75 -33
  74. package/lib/transport/bonding-health.js +317 -0
  75. package/lib/transport/bonding-types.js +68 -0
  76. package/lib/{bonding.js → transport/bonding.js} +142 -300
  77. package/lib/{congestion.js → transport/congestion.js} +2 -2
  78. package/lib/{metrics-publisher.js → transport/metrics/publisher.js} +45 -48
  79. package/lib/{pipeline-factory.js → transport/pipeline/factory.js} +6 -6
  80. package/lib/transport/pipeline/reliable-client/context.js +57 -0
  81. package/lib/transport/pipeline/reliable-client/control-packets.js +71 -0
  82. package/lib/transport/pipeline/reliable-client/delta-sender.js +266 -0
  83. package/lib/transport/pipeline/reliable-client/lifecycle.js +176 -0
  84. package/lib/transport/pipeline/reliable-client/metadata-sender.js +110 -0
  85. package/lib/transport/pipeline/reliable-client/metrics.js +178 -0
  86. package/lib/transport/pipeline/reliable-client/reliability.js +224 -0
  87. package/lib/transport/pipeline/reliable-client/source-snapshot.js +168 -0
  88. package/lib/transport/pipeline/reliable-client.js +278 -0
  89. package/lib/transport/pipeline/reliable-server/context.js +258 -0
  90. package/lib/transport/pipeline/reliable-server/data-handler.js +349 -0
  91. package/lib/transport/pipeline/reliable-server/metadata.js +319 -0
  92. package/lib/transport/pipeline/reliable-server/metrics-publish.js +121 -0
  93. package/lib/transport/pipeline/reliable-server/receive.js +270 -0
  94. package/lib/transport/pipeline/reliable-server/sessions.js +249 -0
  95. package/lib/transport/pipeline/reliable-server/telemetry.js +174 -0
  96. package/lib/transport/pipeline/reliable-server.js +137 -0
  97. package/lib/transport/pipeline/v1-helpers.js +343 -0
  98. package/lib/transport/pipeline/v1.js +29 -0
  99. package/lib/transport/reliability/connection-epoch.js +47 -0
  100. package/lib/transport/reliability/replay-window.js +100 -0
  101. package/lib/{sequence.js → transport/reliability/sequence.js} +134 -52
  102. package/lib/transport/udp-socket-manager.js +185 -0
  103. package/package.json +234 -167
  104. package/public/277.9cb613d4bea9a89a653e.js +8 -0
  105. package/public/{277.7f5c2d73f7d3387708f5.js.LICENSE.txt → 277.9cb613d4bea9a89a653e.js.LICENSE.txt} +4 -3
  106. package/public/338.8ce093ff77e645cd06ab.js +2 -0
  107. package/public/338.8ce093ff77e645cd06ab.js.LICENSE.txt +19 -0
  108. package/public/342.894d77d0b4c7711244d3.js +2 -0
  109. package/public/342.894d77d0b4c7711244d3.js.LICENSE.txt +9 -0
  110. package/public/391.ccce80e82edafa1a2d38.js +1 -0
  111. package/public/540.7ae7a2fe10d7acb09a6a.js +2 -0
  112. package/public/540.7ae7a2fe10d7acb09a6a.js.LICENSE.txt +9 -0
  113. package/public/662.b6f2b26dd9e371cad442.js +1 -0
  114. package/public/{main.2ae3dd54effad689f0da.css → 662.f43390a01514c6933d99.css} +0 -2
  115. package/public/961.c89ce99701c27f67ce7c.js +2 -0
  116. package/public/961.c89ce99701c27f67ce7c.js.LICENSE.txt +9 -0
  117. package/public/index.html +1 -1
  118. package/public/main.911055a412054a2a84b7.js +1 -0
  119. package/public/remoteEntry.js +1 -2
  120. package/lib/config-watcher.js +0 -334
  121. package/lib/delta-sanitizer.js +0 -530
  122. package/lib/instance.js +0 -1701
  123. package/lib/metrics.js +0 -356
  124. package/lib/monitoring.js +0 -576
  125. package/lib/packet.js +0 -541
  126. package/lib/pipeline-v2-client.js +0 -1241
  127. package/lib/pipeline-v2-server.js +0 -1305
  128. package/lib/pipeline.js +0 -315
  129. package/lib/prometheus.js +0 -252
  130. package/lib/source-replication.js +0 -311
  131. package/public/277.7f5c2d73f7d3387708f5.js +0 -9
  132. package/public/277.7f5c2d73f7d3387708f5.js.map +0 -1
  133. package/public/540.70a0bc6aadb412703390.js +0 -3
  134. package/public/540.70a0bc6aadb412703390.js.LICENSE.txt +0 -14
  135. package/public/540.70a0bc6aadb412703390.js.map +0 -1
  136. package/public/982.0f09129baa471abb323c.js +0 -2
  137. package/public/982.0f09129baa471abb323c.js.map +0 -1
  138. package/public/main.0b6f5e3267731da945f0.js +0 -2
  139. package/public/main.0b6f5e3267731da945f0.js.map +0 -1
  140. package/public/main.2ae3dd54effad689f0da.css.map +0 -1
  141. package/public/remoteEntry.js.map +0 -1
  142. /package/lib/{source-dispatch.js → codec/source-dispatch.js} +0 -0
  143. /package/lib/{CircularBuffer.js → foundation/circular-buffer.js} +0 -0
  144. /package/lib/{types.js → foundation/types/config.js} +0 -0
  145. /package/lib/{retransmit-queue.js → transport/reliability/retransmit-queue.js} +0 -0
package/README.md CHANGED
@@ -1,306 +1,310 @@
1
- # Signal K Edge Link
2
-
3
- [![npm version](https://img.shields.io/npm/v/signalk-edge-link)](https://www.npmjs.com/package/signalk-edge-link)
4
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
5
- [![Node.js](https://img.shields.io/badge/node-%3E%3D16-brightgreen)](https://nodejs.org)
6
-
7
- Signal K Edge Link is a Signal K plugin that transfers vessel deltas between Signal K servers over encrypted UDP.
8
-
9
- It is designed for links where latency, packet loss, and bandwidth usage matter (cellular, satellite, and other unstable WAN paths).
10
-
11
- ![Data Connector Concept](docs/assets/dataconnectorconcept.jpg)
12
-
13
- ## Why use it?
14
-
15
- - **Secure transport** using AES-256-GCM
16
- - **Bandwidth optimization** with Brotli compression (plus optional MessagePack and path dictionary)
17
- - **Two operating modes**:
18
- - **Client**: subscribes to local deltas and sends packets
19
- - **Server**: receives packets, decrypts, and forwards to local Signal K
20
- - **Protocol v2/v3 features** for difficult links:
21
- - ACK/NAK-based reliability
22
- - congestion control
23
- - optional primary/backup bonding
24
- - monitoring and alerting endpoints
25
- - values snapshot replay on subscribe, retry, and socket recovery
26
- - optional server-triggered full-state request on restart (`requestFullStatusOnRestart`)
27
- - Signal K path metadata transport (units, descriptions, zones)
28
- - **Multi-connection support** on one Signal K instance
29
-
30
- ## How data flows
31
-
32
- ```text
33
- Client Signal K
34
- -> subscribe + batch deltas
35
- -> optional path dictionary + MessagePack
36
- -> Brotli compress
37
- -> AES-256-GCM encrypt
38
- -> UDP send
39
-
40
- Server Signal K
41
- <- UDP receive
42
- <- AES-256-GCM decrypt
43
- <- Brotli decompress
44
- <- optional MessagePack + path decode
45
- <- inject into local Signal K
46
- ```
47
-
48
- ## Requirements
49
-
50
- - Two Signal K instances (source and destination)
51
- - UDP reachability from client to server on your chosen port
52
- - Shared encryption key on both ends (32-character ASCII, 64-character hex, or 44-character base64)
53
- - Node.js 16+ (if installing from source: dev dependencies including TypeScript are installed automatically via `npm install`)
54
-
55
- ## Installation
56
-
57
- ### Option A: Signal K Plugin Manager
58
-
59
- Install **Signal K Edge Link** from your Signal K plugin catalog.
60
-
61
- ### Option B: Manual install from source
62
-
63
- ```bash
64
- cd ~/.signalk/node_modules
65
- git clone https://github.com/KEGustafsson/signalk-edge-link.git
66
- cd signalk-edge-link
67
- npm install
68
- npm run build
69
- ```
70
-
71
- Restart Signal K after installation.
72
-
73
- ## Quick start
74
-
75
- ### 1) Configure the destination (Server mode)
76
-
77
- In Signal K Admin UI:
78
-
79
- 1. Open `Server -> Plugin Config -> Signal K Edge Link`
80
- 2. Click **Add Server**
81
- 3. Set:
82
- - `Connection Name` (for example `shore-server`)
83
- - `UDP Port` (default `4446`)
84
- - `Encryption Key` (same shared secret used by client)
85
- - `Protocol Version` (`3` recommended for new deployments; use `2` only for compatibility with an existing v2 peer)
86
- 4. Save
87
-
88
- ### 2) Configure the source (Client mode)
89
-
90
- On the sending Signal K instance:
91
-
92
- 1. Open `Server -> Plugin Config -> Signal K Edge Link`
93
- 2. Click **Add Client**
94
- 3. Set:
95
- - `Connection Name` (for example `vessel-client`)
96
- - `Server Address` (destination host/IP)
97
- - `UDP Port` (must match server)
98
- - `Encryption Key` (must match server)
99
- - `Protocol Version` (`3` recommended for new deployments; use `2` only for compatibility with an existing v2 peer)
100
- 4. Save
101
-
102
- ### 3) Verify traffic
103
-
104
- Open the runtime UI:
105
-
106
- `http://<signalk-host>:3000/plugins/signalk-edge-link/`
107
-
108
- Check that:
109
-
110
- - client `Deltas Sent` increases
111
- - server `Deltas Received` increases
112
- - encryption/decryption errors remain stable at zero
113
-
114
- ## Protocol version guidance
115
-
116
- | Version | Use when | Notes |
117
- | ------- | --------------------------------------------------------- | ----------------------------------------------------------------------------- |
118
- | v1 | stable local links, simplest setup | lower overhead, no ACK/NAK reliability, no metadata transport |
119
- | v2 | packet loss, variable latency, WAN links | adds retransmission, congestion control, bonding, metadata, richer monitoring |
120
- | v3 | same use cases as v2 when both peers can upgrade together | keeps v2 features and authenticates ACK/NAK/HEARTBEAT/HELLO control packets |
121
-
122
- For unstable links, start with **v3** when both peers support it; fall back to **v2** only when you need compatibility with an already deployed v2 peer.
123
-
124
- ## Runtime UI and API
125
-
126
- - Runtime UI: `/plugins/signalk-edge-link/`
127
- - API base path: `/plugins/signalk-edge-link`
128
- - Default API rate limit: **120 requests/minute/IP**
129
-
130
- Most used endpoints:
131
-
132
- - `GET /metrics`
133
- - `GET /network-metrics`
134
- - `GET /monitoring/alerts`
135
- - `GET /connections`
136
- - `GET /instances`
137
- - `GET /instances/:id`
138
- - `GET /connections/:id/metrics`
139
- - `GET /connections/:id/network-metrics`
140
- - `GET /bonding`
141
- - `POST /bonding`
142
-
143
- For full endpoint details, use `docs/api-reference.md`
144
-
145
- ## Configuration model (summary)
146
-
147
- Configuration is an array of independent connections:
148
-
149
- ```json
150
- {
151
- "connections": [
152
- {
153
- "name": "shore-server",
154
- "serverType": "server",
155
- "udpPort": 4446,
156
- "secretKey": "<32-byte key>",
157
- "protocolVersion": 3,
158
- "requestFullStatusOnRestart": false
159
- },
160
- {
161
- "name": "sat-client",
162
- "serverType": "client",
163
- "udpPort": 4447,
164
- "udpAddress": "10.0.0.1",
165
- "secretKey": "<32-byte key>",
166
- "protocolVersion": 3
167
- }
168
- ]
169
- }
170
- ```
171
-
172
- - Each connection runs independently.
173
- - Legacy single-object config is auto-normalized to one connection.
174
- - Client runtime JSON files (`delta_timer.json`, `subscription.json`, `sentence_filter.json`) are stored per connection and can be edited via API.
175
- - `requestFullStatusOnRestart` (server mode, v2/v3, default `false`): when enabled, the server sends a `FULL_STATUS_REQUEST` to each client on first contact after a (re)start; the client immediately replays its complete values snapshot so the server rebuilds state without waiting for incremental deltas. Client-side rate-limited to 10 s to prevent replay floods across rapid restarts.
176
-
177
- For complete setting definitions and ranges, use `docs/configuration-reference.md`.
178
-
179
- Schema and migration helpers:
180
-
181
- - The runtime schema is defined inline as `plugin.schema` in `src/index.ts`
182
- and served to the Signal K admin UI. `docs/configuration-reference.md` is
183
- the authoritative human-readable reference.
184
- - `src/scripts/migrate-config.ts` (convert legacy flat config to `connections[]`)
185
- - `npm run migrate:config -- <input.json> [output.json]`
186
-
187
- ## Security notes
188
-
189
- - Uses AES-256-GCM authenticated encryption.
190
- - Keys must match exactly and can be entered as 32-character ASCII, 64-character hex, or 44-character base64.
191
- - Restrict UDP ingress to trusted source addresses whenever possible.
192
-
193
- Example key generation:
194
-
195
- ```bash
196
- openssl rand -hex 32
197
- ```
198
-
199
- ## Troubleshooting
200
-
201
- Common checks:
202
-
203
- - Verify `udpAddress`, `udpPort`, and `secretKey` match both ends.
204
- - Confirm server UDP port is reachable and not already in use.
205
- - If link quality is poor, switch to `protocolVersion: 3` when both peers can upgrade together, or `2` if you must stay compatible with an existing v2 peer.
206
-
207
- **`testAddress is only supported on v1 clients` after upgrading to v2/v3**
208
-
209
- The fields `testAddress`, `testPort`, and `pingIntervalTime` belong to the v1 ping monitor and are not used by v2/v3 clients (which derive RTT from HEARTBEAT exchanges instead). If these fields are present in a connection with `protocolVersion: 2` or `3` the validator will reject the config.
210
-
211
- Remove them from the affected connection:
212
-
213
- ```json
214
- {
215
- "name": "my-client",
216
- "serverType": "client",
217
- "protocolVersion": 3,
218
- "udpAddress": "...",
219
- "heartbeatInterval": 25000
220
- }
221
- ```
222
-
223
- The plugin strips these fields automatically on startup, but if you see the error when saving via the SignalK admin UI you need to remove them from the stored config JSON manually once.
224
-
225
- For issue-oriented diagnostics, use `docs/troubleshooting.md`.
226
-
227
- ## Developer commands
228
-
229
- ```bash
230
- npm run build
231
- npm run dev
232
- npm test
233
- npm run test:v2
234
- npm run test:integration
235
- npm run lint
236
- npm run lint:fix
237
- npm run cli -- help
238
- npm run cli -- instances list --token=$EDGE_LINK_TOKEN --state=running --limit=10 --page=1 --format=table
239
- npm run cli -- instances show alpha --token=$EDGE_LINK_TOKEN --format=table
240
- npm run cli -- instances create --config ./new-instance.json --token=$EDGE_LINK_TOKEN
241
- npm run cli -- instances update alpha --patch '{"udpAddress":"10.0.0.2"}' --token=$EDGE_LINK_TOKEN
242
- npm run cli -- instances delete alpha --token=$EDGE_LINK_TOKEN
243
- npm run cli -- bonding status --token=$EDGE_LINK_TOKEN --format=table
244
- npm run cli -- bonding update --patch '{"failoverThreshold":300}' --token=$EDGE_LINK_TOKEN
245
- npm run cli -- status --token=$EDGE_LINK_TOKEN --format=table
246
- ```
247
-
248
- ## Management API security
249
-
250
- Set `managementApiToken` in plugin options (or the environment variable `SIGNALK_EDGE_LINK_MANAGEMENT_TOKEN`). Protected routes include:
251
-
252
- - `/instances`, `/bonding`, `/status`, `/plugin-config`
253
- - `/config/*`, `/connections/:id/config/*`
254
- - `/connections/:id/metrics`, `/connections/:id/network-metrics`
255
- - `/connections/:id/bonding`, `/connections/:id/congestion`
256
- - `/connections/:id/monitoring/*`, `/monitoring/alerts`
257
- - `/capture/*`, `/delta-timer`
258
-
259
- Send the token as either:
260
-
261
- - `X-Edge-Link-Token: <token>`
262
- - `Authorization: Bearer <token>`
263
-
264
- CLI commands support `--token=<token>` or the `SIGNALK_EDGE_LINK_MANAGEMENT_TOKEN` environment variable.
265
-
266
- ## Web UI token injection
267
-
268
- Management pages automatically attach auth headers when a token is available. Token sources are checked in this order:
269
-
270
- 1. `window.__EDGE_LINK_AUTH__.token` injected global (preferred for server-side injection)
271
- 2. URL query parameter `?edgeLinkToken=<token>`
272
- 3. `localStorage.setItem("signalkEdgeLinkManagementToken", "<token>")`
273
-
274
- The UI sends both `X-Edge-Link-Token` and `Authorization: Bearer <token>` by default. Override with:
275
-
276
- ```javascript
277
- window.__EDGE_LINK_AUTH__ = {
278
- token: "<token>",
279
- queryParam: "edgeLinkToken",
280
- localStorageKey: "signalkEdgeLinkManagementToken",
281
- headerMode: "both" // "both" | "authorization" | "x-edge-link-token"
282
- };
283
- ```
284
-
285
- ## Documentation map
286
-
287
- - `docs/README.md` (documentation index)
288
- - `docs/architecture-overview.md` (system architecture and lifecycle)
289
- - `docs/configuration-reference.md` (settings and defaults)
290
- - `docs/api-reference.md`
291
- - `docs/protocol-v2.md` (reliable protocol operational overview)
292
- - `docs/protocol-v3-spec.md` (authenticated control-plane details)
293
- - `docs/bonding.md` (bonding concepts and API usage)
294
- - `docs/congestion-control.md` (congestion-control behavior and tuning)
295
- - `docs/metrics.md` (metrics and monitoring reference)
296
- - `docs/management-tools.md` (instance/bonding API + CLI operations)
297
- - `docs/security.md` (security guidance and deployment hardening)
298
- - `docs/performance-tuning.md` (deployment tuning recommendations by hardware profile)
299
- - `samples/` (example JSON configurations for minimal/dev/v2-bonding setups)
300
- - `grafana/dashboards/edge-link.json` (starter Grafana dashboard)
301
- - `src/scripts/migrate-config.ts` (legacy config migration utility)
302
- - `src/bin/edge-link-cli.ts` (CLI wrapper for migration and instance/bonding management)
303
-
304
- ## License
305
-
306
- MIT. See `LICENSE`.
1
+ # Signal K Edge Link
2
+
3
+ [![npm version](https://img.shields.io/npm/v/signalk-edge-link)](https://www.npmjs.com/package/signalk-edge-link)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
5
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D20-brightgreen)](https://nodejs.org)
6
+
7
+ Signal K Edge Link is a Signal K plugin that transfers vessel deltas between Signal K servers over encrypted UDP.
8
+
9
+ It is designed for links where latency, packet loss, and bandwidth usage matter (cellular, satellite, and other unstable WAN paths).
10
+
11
+ ![Data Connector Concept](docs/assets/dataconnectorconcept.jpg)
12
+
13
+ ## Why use it?
14
+
15
+ - **Secure transport** using AES-256-GCM
16
+ - **Bandwidth optimization** with Brotli compression (plus optional MessagePack and path dictionary)
17
+ - **Two operating modes**:
18
+ - **Client**: subscribes to local deltas and sends packets
19
+ - **Server**: receives packets, decrypts, and forwards to local Signal K
20
+ - **Advanced mode** features for difficult links:
21
+ - ACK/NAK-based reliability
22
+ - congestion control
23
+ - optional primary/backup bonding
24
+ - monitoring and alerting endpoints
25
+ - values snapshot replay on subscribe, retry, and socket recovery
26
+ - optional server-triggered full-state request on restart (`requestFullStatusOnRestart`)
27
+ - Signal K path metadata transport (units, descriptions, zones)
28
+ - **Multi-connection support** on one Signal K instance
29
+
30
+ ## How data flows
31
+
32
+ ```text
33
+ Client Signal K
34
+ -> subscribe + batch deltas
35
+ -> optional path dictionary + MessagePack
36
+ -> Brotli compress
37
+ -> AES-256-GCM encrypt
38
+ -> UDP send
39
+
40
+ Server Signal K
41
+ <- UDP receive
42
+ <- AES-256-GCM decrypt
43
+ <- Brotli decompress
44
+ <- optional MessagePack + path decode
45
+ <- inject into local Signal K
46
+ ```
47
+
48
+ ## Requirements
49
+
50
+ - Two Signal K instances (source and destination)
51
+ - UDP reachability from client to server on your chosen port
52
+ - Shared encryption key on both ends (32-character ASCII, 64-character hex, or 44-character base64)
53
+ - Node.js 20.9.0+ (if installing from source: dev dependencies including TypeScript are installed automatically via `npm install`)
54
+
55
+ ## Installation
56
+
57
+ ### Option A: Signal K Plugin Manager
58
+
59
+ Install **Signal K Edge Link** from your Signal K plugin catalog.
60
+
61
+ ### Option B: Manual install from source
62
+
63
+ ```bash
64
+ cd ~/.signalk/node_modules
65
+ git clone https://github.com/KEGustafsson/signalk-edge-link.git
66
+ cd signalk-edge-link
67
+ npm install
68
+ npm run build
69
+ ```
70
+
71
+ Restart Signal K after installation.
72
+
73
+ ## Quick start
74
+
75
+ ### 1) Configure the destination (Server mode)
76
+
77
+ In Signal K Admin UI:
78
+
79
+ 1. Open `Server -> Plugin Config -> Signal K Edge Link`
80
+ 2. Click **Add Server**
81
+ 3. Set:
82
+ - `Connection Name` (for example `shore-server`)
83
+ - `UDP Port` (default `4446`)
84
+ - `Encryption Key` (same shared secret used by client)
85
+ - `Protocol` — select **Advanced** (v3, recommended) for new deployments; select **Basic** (v1) only for stable local links
86
+ 4. Save
87
+
88
+ ### 2) Configure the source (Client mode)
89
+
90
+ On the sending Signal K instance:
91
+
92
+ 1. Open `Server -> Plugin Config -> Signal K Edge Link`
93
+ 2. Click **Add Client**
94
+ 3. Set:
95
+ - `Connection Name` (for example `vessel-client`)
96
+ - `Server Address` (destination host/IP)
97
+ - `UDP Port` (must match server)
98
+ - `Encryption Key` (must match server)
99
+ - `Protocol` — select **Advanced** (v3, recommended) for new deployments; select **Basic** (v1) only for stable local links
100
+ 4. Save
101
+
102
+ ### 3) Verify traffic
103
+
104
+ Open the runtime UI:
105
+
106
+ `http://<signalk-host>:3000/plugins/signalk-edge-link/`
107
+
108
+ Check that:
109
+
110
+ - client `Deltas Sent` increases
111
+ - server `Deltas Received` increases
112
+ - encryption/decryption errors remain stable at zero
113
+
114
+ ## Protocol guidance
115
+
116
+ | Mode | Numeric | Use when | Notes |
117
+ | ------------ | ------- | ---------------------------------------- | ------------------------------------------------------------------------ |
118
+ | **Basic** | v1 | stable local links, simplest setup | lower overhead, no ACK/NAK reliability, no metadata transport |
119
+ | **Advanced** | v3 | packet loss, variable latency, WAN links | retransmission, congestion control, bonding, metadata, HMAC control auth |
120
+
121
+ Use **Advanced** for any new deployment on a WAN or unreliable link. Use **Basic** only for stable local links where simplicity and minimum overhead matter.
122
+
123
+ ## Runtime UI and API
124
+
125
+ - Runtime UI: `/plugins/signalk-edge-link/`
126
+ - API base path: `/plugins/signalk-edge-link`
127
+ - Default API rate limit: **120 requests/minute/IP**
128
+
129
+ For a walkthrough of the configuration panel (progressive-disclosure connection
130
+ cards) and the runtime dashboard panels for server and client, see
131
+ [`docs/web-ui.md`](docs/web-ui.md).
132
+
133
+ Most used endpoints:
134
+
135
+ - `GET /metrics`
136
+ - `GET /network-metrics`
137
+ - `GET /monitoring/alerts`
138
+ - `GET /connections`
139
+ - `GET /instances`
140
+ - `GET /instances/:id`
141
+ - `GET /connections/:id/metrics`
142
+ - `GET /connections/:id/network-metrics`
143
+ - `GET /bonding`
144
+ - `POST /bonding`
145
+
146
+ For full endpoint details, use `docs/api-reference.md`
147
+
148
+ ## Configuration model (summary)
149
+
150
+ Configuration is an array of independent connections:
151
+
152
+ ```json
153
+ {
154
+ "connections": [
155
+ {
156
+ "name": "shore-server",
157
+ "serverType": "server",
158
+ "udpPort": 4446,
159
+ "secretKey": "<32-byte key>",
160
+ "protocolVersion": 3,
161
+ "requestFullStatusOnRestart": false
162
+ },
163
+ {
164
+ "name": "sat-client",
165
+ "serverType": "client",
166
+ "udpPort": 4447,
167
+ "udpAddress": "10.0.0.1",
168
+ "secretKey": "<32-byte key>",
169
+ "protocolVersion": 3
170
+ }
171
+ ]
172
+ }
173
+ ```
174
+
175
+ - Each connection runs independently.
176
+ - Legacy single-object config is auto-normalized to one connection.
177
+ - Client runtime JSON files (`delta_timer.json`, `subscription.json`, `sentence_filter.json`) are stored per connection and can be edited via API.
178
+ - `requestFullStatusOnRestart` (server mode, v3, default `false`): when enabled, the server sends a `FULL_STATUS_REQUEST` to each client on first contact after a (re)start; the client immediately replays its complete values snapshot so the server rebuilds state without waiting for incremental deltas. Client-side rate-limited to 10 s to prevent replay floods across rapid restarts.
179
+
180
+ For complete setting definitions and ranges, use `docs/configuration-reference.md`.
181
+
182
+ Schema and migration helpers:
183
+
184
+ - The runtime schema is defined inline as `plugin.schema` in `src/index.ts`
185
+ and served to the Signal K admin UI. `docs/configuration-reference.md` is
186
+ the authoritative human-readable reference.
187
+ - `src/scripts/migrate-config.ts` (convert legacy flat config to `connections[]`)
188
+ - `npm run migrate:config -- <input.json> [output.json]`
189
+
190
+ ## Security notes
191
+
192
+ - Uses AES-256-GCM authenticated encryption.
193
+ - Keys must match exactly and can be entered as 32-character ASCII, 64-character hex, or 44-character base64 (standard or URL-safe).
194
+ - Restrict UDP ingress to trusted source addresses whenever possible.
195
+ - `authenticatedHeaders` (v3, **default on**, both ends must match) adds an HMAC tag binding each DATA/METADATA packet header to its encrypted payload, preventing on-path header tampering. Set to `false` on both peers only to interoperate with a peer that cannot enable it. See `docs/security.md`.
196
+
197
+ Example key generation:
198
+
199
+ ```bash
200
+ openssl rand -hex 32
201
+ ```
202
+
203
+ ## Troubleshooting
204
+
205
+ Common checks:
206
+
207
+ - Verify `udpAddress`, `udpPort`, and `secretKey` match both ends.
208
+ - Confirm server UDP port is reachable and not already in use.
209
+ - If link quality is poor, switch to **Advanced** (`protocolVersion: 3`) when both peers can upgrade together.
210
+
211
+ **`testAddress is only supported on v1 clients` after upgrading to Advanced mode**
212
+
213
+ The fields `testAddress`, `testPort`, and `pingIntervalTime` belong to the Basic (v1) ping monitor and are not used by Advanced (v3) clients (which derive RTT from HEARTBEAT exchanges instead). If these fields are present in a connection with `protocolVersion: 3` the validator will reject the config.
214
+
215
+ Remove them from the affected connection:
216
+
217
+ ```json
218
+ {
219
+ "name": "my-client",
220
+ "serverType": "client",
221
+ "protocolVersion": 3,
222
+ "udpAddress": "...",
223
+ "heartbeatInterval": 25000
224
+ }
225
+ ```
226
+
227
+ The plugin strips these fields automatically on startup, but if you see the error when saving via the SignalK admin UI you need to remove them from the stored config JSON manually once.
228
+
229
+ For issue-oriented diagnostics, use `docs/troubleshooting.md`.
230
+
231
+ ## Developer commands
232
+
233
+ ```bash
234
+ npm run build
235
+ npm run dev
236
+ npm test
237
+ npm run test:v2
238
+ npm run test:integration
239
+ npm run lint
240
+ npm run lint:fix
241
+ npm run cli -- help
242
+ npm run cli -- instances list --token=$EDGE_LINK_TOKEN --state=running --limit=10 --page=1 --format=table
243
+ npm run cli -- instances show alpha --token=$EDGE_LINK_TOKEN --format=table
244
+ npm run cli -- instances create --config ./new-instance.json --token=$EDGE_LINK_TOKEN
245
+ npm run cli -- instances update alpha --patch '{"udpAddress":"10.0.0.2"}' --token=$EDGE_LINK_TOKEN
246
+ npm run cli -- instances delete alpha --token=$EDGE_LINK_TOKEN
247
+ npm run cli -- bonding status --token=$EDGE_LINK_TOKEN --format=table
248
+ npm run cli -- bonding update --patch '{"failoverThreshold":300}' --token=$EDGE_LINK_TOKEN
249
+ npm run cli -- status --token=$EDGE_LINK_TOKEN --format=table
250
+ ```
251
+
252
+ ## Management API security
253
+
254
+ Set `managementApiToken` in plugin options (or the environment variable `SIGNALK_EDGE_LINK_MANAGEMENT_TOKEN`). Protected routes include:
255
+
256
+ - `/instances`, `/bonding`, `/status`, `/plugin-config`
257
+ - `/config/*`, `/connections/:id/config/*`
258
+ - `/connections/:id/metrics`, `/connections/:id/network-metrics`
259
+ - `/connections/:id/bonding`, `/connections/:id/congestion`
260
+ - `/connections/:id/monitoring/*`, `/monitoring/alerts`
261
+ - `/capture/*`, `/delta-timer`
262
+
263
+ Send the token as either:
264
+
265
+ - `X-Edge-Link-Token: <token>`
266
+ - `Authorization: Bearer <token>`
267
+
268
+ CLI commands support `--token=<token>` or the `SIGNALK_EDGE_LINK_MANAGEMENT_TOKEN` environment variable.
269
+
270
+ ## Web UI token injection
271
+
272
+ Management pages automatically attach auth headers when a token is available. Token sources are checked in this order:
273
+
274
+ 1. `window.__EDGE_LINK_AUTH__.token` injected global (preferred for server-side injection)
275
+ 2. URL query parameter `?edgeLinkToken=<token>` — **disabled by default**; opt in with `includeTokenInQuery: true`. Avoid it: tokens in URLs leak via browser history, server access logs, and `Referer` headers.
276
+ 3. `localStorage.setItem("signalkEdgeLinkManagementToken", "<token>")`
277
+
278
+ The UI sends both `X-Edge-Link-Token` and `Authorization: Bearer <token>` by default. Override with:
279
+
280
+ ```javascript
281
+ window.__EDGE_LINK_AUTH__ = {
282
+ token: "<token>",
283
+ queryParam: "edgeLinkToken",
284
+ localStorageKey: "signalkEdgeLinkManagementToken",
285
+ headerMode: "both" // "both" | "authorization" | "x-edge-link-token"
286
+ };
287
+ ```
288
+
289
+ ## Documentation map
290
+
291
+ - `docs/README.md` (documentation index)
292
+ - `docs/architecture-overview.md` (system architecture and lifecycle)
293
+ - `docs/configuration-reference.md` (settings and defaults)
294
+ - `docs/api-reference.md`
295
+ - `docs/protocol-v3.md` (Basic/Advanced protocol operational overview)
296
+ - `docs/protocol-v3-spec.md` (RFC-style wire specification with authenticated control plane)
297
+ - `docs/bonding.md` (bonding concepts and API usage)
298
+ - `docs/congestion-control.md` (congestion-control behavior and tuning)
299
+ - `docs/metrics.md` (metrics and monitoring reference)
300
+ - `docs/management-tools.md` (instance/bonding API + CLI operations)
301
+ - `docs/security.md` (security guidance and deployment hardening)
302
+ - `docs/performance-tuning.md` (deployment tuning recommendations by hardware profile)
303
+ - `docs/troubleshooting.md` (issue-oriented diagnostics)
304
+ - `samples/` (example JSON configurations for minimal/dev/bonding setups)
305
+ - `src/scripts/migrate-config.ts` (legacy config migration utility)
306
+ - `src/bin/edge-link-cli.ts` (CLI wrapper for migration and instance/bonding management)
307
+
308
+ ## License
309
+
310
+ MIT. See `LICENSE`.
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.migrateConfig = void 0;
4
+ /**
5
+ * Configuration migration re-export (L4 application layer).
6
+ *
7
+ * Provides the config migration entry point from a single canonical location.
8
+ *
9
+ * @module app/config/migrate
10
+ */
11
+ /** Migrate a stored plugin config object to the current schema version. */
12
+ var migrate_config_1 = require("../../scripts/migrate-config");
13
+ Object.defineProperty(exports, "migrateConfig", { enumerable: true, get: function () { return migrate_config_1.migrateConfig; } });