signalk-aiscast 0.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.
package/dist/uplink.js ADDED
@@ -0,0 +1,257 @@
1
+ import { mkdir, readdir, readFile, stat, unlink, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { payloadKey, tagged } from "./nmea.js";
4
+ const ACK_TIMEOUT = 30_000;
5
+ const FLUSH_EVERY = 15_000; // in-memory backlog to disk; a crash loses at most this much
6
+ const FILE_MAX = 500; // sentences per queue file = per replayed frame
7
+ const QUEUE_MAX_BYTES = 100 * 1024 * 1024;
8
+ const SEEN_TTL = 5 * 60_000; // payloads received from aiscast are never published back within this window
9
+ const SEEN_MAX = 20_000;
10
+ // Sends received sentences to aiscast as they arrive; anything not acked, or heard while offline, waits on disk
11
+ // and is replayed oldest-first on the next connection before live sending resumes.
12
+ export class Uplink {
13
+ link;
14
+ log;
15
+ enabled = true; // false when no token: hearing then discards instead of queueing forever
16
+ backlog = [];
17
+ inflight = [];
18
+ pendingLive = [];
19
+ liveScheduled = false;
20
+ draining = false;
21
+ drainAgain = false;
22
+ flushTimer = null;
23
+ ackTimer = null;
24
+ seen = new Map();
25
+ dir;
26
+ onOpen = () => this.drain();
27
+ onClose = () => this.requeueInFlight("socket closed");
28
+ onFrame = (f) => this.ack(f);
29
+ stats = { sent: 0, queued: 0, dropped: 0, inFlight: 0 };
30
+ constructor(link, dataDir, log = () => { }) {
31
+ this.link = link;
32
+ this.log = log;
33
+ this.dir = join(dataDir, "queue");
34
+ }
35
+ async start() {
36
+ await mkdir(this.dir, { recursive: true });
37
+ this.stats.queued = await this.countQueued();
38
+ this.link.on("open", this.onOpen);
39
+ this.link.on("close", this.onClose);
40
+ this.link.on("frame", this.onFrame);
41
+ this.flushTimer = setInterval(() => this.flush(), FLUSH_EVERY);
42
+ this.ackTimer = setInterval(() => this.checkAcks(), 5_000);
43
+ }
44
+ async stop() {
45
+ if (this.flushTimer)
46
+ clearInterval(this.flushTimer);
47
+ if (this.ackTimer)
48
+ clearInterval(this.ackTimer);
49
+ this.flushTimer = this.ackTimer = null;
50
+ this.link.off("open", this.onOpen);
51
+ this.link.off("close", this.onClose);
52
+ this.link.off("frame", this.onFrame);
53
+ this.requeueInFlight("stopping");
54
+ this.backlog.unshift(...this.pendingLive);
55
+ this.stats.queued += this.pendingLive.length;
56
+ this.pendingLive = [];
57
+ await this.flush();
58
+ }
59
+ // Remember a payload that came from aiscast so a local re-broadcast of it is not published back.
60
+ noteReceived(sentence, now = Date.now()) {
61
+ if (this.seen.size >= SEEN_MAX)
62
+ this.pruneSeen(now);
63
+ this.seen.set(payloadKey(sentence), now + SEEN_TTL);
64
+ }
65
+ // A sentence the boat's receiver heard, already filtered to AIS by the caller.
66
+ hear(sentence, now = Date.now(), source) {
67
+ if (!this.enabled)
68
+ return;
69
+ const exp = this.seen.get(payloadKey(sentence));
70
+ if (exp !== undefined) {
71
+ if (exp > now)
72
+ return;
73
+ this.seen.delete(payloadKey(sentence));
74
+ }
75
+ const line = tagged(sentence, now, source);
76
+ if (this.link.open && !this.draining && this.backlog.length === 0) {
77
+ this.pendingLive.push(line);
78
+ if (!this.liveScheduled) {
79
+ this.liveScheduled = true;
80
+ setImmediate(() => this.sendLive());
81
+ }
82
+ }
83
+ else {
84
+ this.backlog.push(line);
85
+ this.stats.queued++;
86
+ if (this.backlog.length >= FILE_MAX)
87
+ this.flush();
88
+ }
89
+ }
90
+ sendLive() {
91
+ this.liveScheduled = false;
92
+ const sentences = this.pendingLive;
93
+ this.pendingLive = [];
94
+ if (sentences.length === 0)
95
+ return;
96
+ if (!this.sendFrame(sentences, false)) {
97
+ this.backlog.push(...sentences);
98
+ this.stats.queued += sentences.length;
99
+ }
100
+ }
101
+ sendFrame(sentences, fromQueue, settle) {
102
+ const frame = { type: "publish", nmea: sentences };
103
+ if (fromQueue)
104
+ frame.replay = true; // aiscast archives stale replayed sentences without emitting them live
105
+ if (!this.link.send(frame))
106
+ return false;
107
+ this.inflight.push({ sentences, at: Date.now(), fromQueue, settle });
108
+ this.stats.inFlight = this.inflight.length;
109
+ return true;
110
+ }
111
+ ack(f) {
112
+ if (f.type !== "ack")
113
+ return;
114
+ const head = this.inflight.shift();
115
+ this.stats.inFlight = this.inflight.length;
116
+ if (!head)
117
+ return;
118
+ this.stats.sent += head.sentences.length;
119
+ head.settle?.(true);
120
+ }
121
+ checkAcks() {
122
+ const head = this.inflight[0];
123
+ if (head && Date.now() - head.at > ACK_TIMEOUT) {
124
+ this.log(`no ack for ${Math.round((Date.now() - head.at) / 1000)} s; reconnecting`);
125
+ this.requeueInFlight("ack timeout");
126
+ this.link.reconnect();
127
+ }
128
+ }
129
+ requeueInFlight(why) {
130
+ if (this.inflight.length === 0)
131
+ return;
132
+ const live = [];
133
+ for (const f of this.inflight) {
134
+ if (f.fromQueue)
135
+ f.settle?.(false);
136
+ else
137
+ live.push(...f.sentences);
138
+ }
139
+ this.inflight = [];
140
+ this.stats.inFlight = 0;
141
+ if (live.length > 0) {
142
+ this.log(`${why}: ${live.length} unacked sentences back to the queue`);
143
+ this.backlog.unshift(...live);
144
+ this.stats.queued += live.length;
145
+ }
146
+ }
147
+ // Backlog → one file. Oldest files go when the cap is exceeded. Never throws: a full or read-only disk
148
+ // must not take the Signal K process down, so the backlog is kept in memory and retried next time.
149
+ async flush() {
150
+ if (this.backlog.length === 0)
151
+ return;
152
+ const sentences = this.backlog;
153
+ this.backlog = [];
154
+ try {
155
+ await writeFile(join(this.dir, `${Date.now()}.json`), JSON.stringify(sentences));
156
+ }
157
+ catch (err) {
158
+ this.log(`queue write failed: ${err.message}`);
159
+ this.backlog.unshift(...sentences);
160
+ return;
161
+ }
162
+ await this.enforceCap().catch((err) => this.log(`queue cap: ${err.message}`));
163
+ }
164
+ async files() {
165
+ return (await readdir(this.dir)).filter((f) => f.endsWith(".json")).sort();
166
+ }
167
+ async readQueueFile(name) {
168
+ try {
169
+ const parsed = JSON.parse(await readFile(join(this.dir, name), "utf8"));
170
+ return Array.isArray(parsed) ? parsed : null;
171
+ }
172
+ catch {
173
+ return null;
174
+ }
175
+ }
176
+ async countQueued() {
177
+ let n = 0;
178
+ for (const f of await this.files()) {
179
+ const sentences = await this.readQueueFile(f);
180
+ if (sentences)
181
+ n += sentences.length;
182
+ else
183
+ await unlink(join(this.dir, f)).catch(() => { });
184
+ }
185
+ return n;
186
+ }
187
+ async enforceCap() {
188
+ const files = await this.files();
189
+ const sizes = await Promise.all(files.map((f) => stat(join(this.dir, f)).then((s) => s.size, () => 0)));
190
+ let total = sizes.reduce((a, b) => a + b, 0);
191
+ for (let i = 0; i < files.length && total > QUEUE_MAX_BYTES; i++) {
192
+ const n = (await this.readQueueFile(files[i]))?.length ?? 0;
193
+ await unlink(join(this.dir, files[i])).catch(() => { });
194
+ total -= sizes[i];
195
+ this.stats.dropped += n;
196
+ this.stats.queued -= n;
197
+ }
198
+ }
199
+ // Replay queued files oldest-first, one frame in flight, then let live sending resume. A drain that is cut
200
+ // short (socket closed, ack timeout) leaves the unacked remainder on disk for the next open.
201
+ drain() {
202
+ if (this.draining) {
203
+ this.drainAgain = true;
204
+ return;
205
+ }
206
+ this.draining = true;
207
+ this.drainAgain = false;
208
+ this.drainLoop()
209
+ .catch((err) => this.log(`drain failed: ${err.message}`))
210
+ .finally(() => {
211
+ this.draining = false;
212
+ if (this.drainAgain && this.link.open)
213
+ this.drain();
214
+ });
215
+ }
216
+ async drainLoop() {
217
+ for (;;) {
218
+ await this.flush();
219
+ const [next] = await this.files();
220
+ if (!next)
221
+ return;
222
+ const path = join(this.dir, next);
223
+ const sentences = await this.readQueueFile(next);
224
+ if (!sentences) {
225
+ await unlink(path).catch(() => { });
226
+ continue;
227
+ }
228
+ for (let i = 0; i < sentences.length; i += FILE_MAX) {
229
+ const chunk = sentences.slice(i, i + FILE_MAX);
230
+ const acked = await new Promise((resolve) => {
231
+ if (!this.sendFrame(chunk, true, resolve))
232
+ resolve(false);
233
+ });
234
+ if (!acked) {
235
+ if (i > 0)
236
+ await writeFile(path, JSON.stringify(sentences.slice(i))).catch(() => { });
237
+ return;
238
+ }
239
+ this.stats.queued -= chunk.length;
240
+ }
241
+ await unlink(path);
242
+ }
243
+ }
244
+ pruneSeen(now) {
245
+ for (const [k, exp] of this.seen)
246
+ if (exp <= now)
247
+ this.seen.delete(k);
248
+ // Still full of live entries: drop the oldest (Map keeps insertion order) rather than scan on every event.
249
+ let excess = this.seen.size - SEEN_MAX / 2;
250
+ for (const k of this.seen.keys()) {
251
+ if (excess-- <= 0)
252
+ break;
253
+ this.seen.delete(k);
254
+ }
255
+ }
256
+ }
257
+ //# sourceMappingURL=uplink.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uplink.js","sourceRoot":"","sources":["../src/uplink.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "signalk-aiscast",
3
+ "version": "0.0.0",
4
+ "description": "Share the AIS your receiver hears with aiscast, and pull nearby traffic from it when you have no receiver.",
5
+ "license": "MIT",
6
+ "author": "Brandon Keepers <brandon@opensoul.org>",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/openwatersio/aiscast.git",
10
+ "directory": "signalk-plugin"
11
+ },
12
+ "homepage": "https://github.com/openwatersio/aiscast/tree/main/signalk-plugin#readme",
13
+ "type": "module",
14
+ "main": "./dist/index.js",
15
+ "exports": "./dist/index.js",
16
+ "files": [
17
+ "dist",
18
+ "README.md"
19
+ ],
20
+ "keywords": [
21
+ "signalk-node-server-plugin",
22
+ "signalk-category-ais",
23
+ "ais",
24
+ "aiscast"
25
+ ],
26
+ "signalk": {
27
+ "displayName": "AIScast"
28
+ },
29
+ "engines": {
30
+ "node": ">=20"
31
+ },
32
+ "scripts": {
33
+ "build": "tsc -p tsconfig.json",
34
+ "prepack": "npm run build",
35
+ "test": "vitest run",
36
+ "typecheck": "tsc -p tsconfig.json --noEmit"
37
+ },
38
+ "dependencies": {
39
+ "@canboat/ts-pgns": "^1.11.18",
40
+ "@signalk/nmea0183-signalk": "^3.20.1",
41
+ "ggencoder": "^1.0.10",
42
+ "ws": "^8.18.0"
43
+ },
44
+ "devDependencies": {
45
+ "@signalk/server-api": "^2.31.1",
46
+ "@types/node": "^24.0.0",
47
+ "@types/ws": "^8.18.0",
48
+ "typescript": "^5.9.0",
49
+ "vitest": "^4.0.0"
50
+ }
51
+ }