signalk-wakelogger 0.2.0-beta.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 (83) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/LICENSE +201 -0
  3. package/README.md +49 -0
  4. package/SECURITY.md +7 -0
  5. package/assets/screenshots/plugin-config.png +0 -0
  6. package/assets/screenshots/plugin-status.png +0 -0
  7. package/assets/wakelogger.svg +6 -0
  8. package/dist/config/defaults.d.ts +18 -0
  9. package/dist/config/defaults.js +30 -0
  10. package/dist/config/defaults.js.map +1 -0
  11. package/dist/config/schema.d.ts +43 -0
  12. package/dist/config/schema.js +47 -0
  13. package/dist/config/schema.js.map +1 -0
  14. package/dist/index.d.ts +3 -0
  15. package/dist/index.js +338 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/outbox/database-outbox.d.ts +25 -0
  18. package/dist/outbox/database-outbox.js +186 -0
  19. package/dist/outbox/database-outbox.js.map +1 -0
  20. package/dist/outbox/database-types.d.ts +19 -0
  21. package/dist/outbox/database-types.js +3 -0
  22. package/dist/outbox/database-types.js.map +1 -0
  23. package/dist/outbox/factory.d.ts +12 -0
  24. package/dist/outbox/factory.js +98 -0
  25. package/dist/outbox/factory.js.map +1 -0
  26. package/dist/outbox/file-outbox.d.ts +27 -0
  27. package/dist/outbox/file-outbox.js +280 -0
  28. package/dist/outbox/file-outbox.js.map +1 -0
  29. package/dist/outbox/interface.d.ts +33 -0
  30. package/dist/outbox/interface.js +3 -0
  31. package/dist/outbox/interface.js.map +1 -0
  32. package/dist/pairing/association-client.d.ts +3 -0
  33. package/dist/pairing/association-client.js +31 -0
  34. package/dist/pairing/association-client.js.map +1 -0
  35. package/dist/pairing/credentials.d.ts +46 -0
  36. package/dist/pairing/credentials.js +168 -0
  37. package/dist/pairing/credentials.js.map +1 -0
  38. package/dist/pairing/pairing-client.d.ts +19 -0
  39. package/dist/pairing/pairing-client.js +102 -0
  40. package/dist/pairing/pairing-client.js.map +1 -0
  41. package/dist/signalk/normaliser.d.ts +11 -0
  42. package/dist/signalk/normaliser.js +119 -0
  43. package/dist/signalk/normaliser.js.map +1 -0
  44. package/dist/signalk/paths.d.ts +2 -0
  45. package/dist/signalk/paths.js +14 -0
  46. package/dist/signalk/paths.js.map +1 -0
  47. package/dist/signalk/subscriber.d.ts +2 -0
  48. package/dist/signalk/subscriber.js +20 -0
  49. package/dist/signalk/subscriber.js.map +1 -0
  50. package/dist/telemetry/profile-store.d.ts +7 -0
  51. package/dist/telemetry/profile-store.js +39 -0
  52. package/dist/telemetry/profile-store.js.map +1 -0
  53. package/dist/telemetry/profile.d.ts +32 -0
  54. package/dist/telemetry/profile.js +94 -0
  55. package/dist/telemetry/profile.js.map +1 -0
  56. package/dist/telemetry/sampler.d.ts +14 -0
  57. package/dist/telemetry/sampler.js +44 -0
  58. package/dist/telemetry/sampler.js.map +1 -0
  59. package/dist/telemetry/types.d.ts +73 -0
  60. package/dist/telemetry/types.js +3 -0
  61. package/dist/telemetry/types.js.map +1 -0
  62. package/dist/transport/adaptive-mode.d.ts +23 -0
  63. package/dist/transport/adaptive-mode.js +63 -0
  64. package/dist/transport/adaptive-mode.js.map +1 -0
  65. package/dist/transport/mqtt-client.d.ts +69 -0
  66. package/dist/transport/mqtt-client.js +365 -0
  67. package/dist/transport/mqtt-client.js.map +1 -0
  68. package/dist/transport/topics.d.ts +10 -0
  69. package/dist/transport/topics.js +13 -0
  70. package/dist/transport/topics.js.map +1 -0
  71. package/dist/trips/state-machine.d.ts +38 -0
  72. package/dist/trips/state-machine.js +118 -0
  73. package/dist/trips/state-machine.js.map +1 -0
  74. package/docs/architecture.md +17 -0
  75. package/docs/development.md +49 -0
  76. package/docs/implementation-plan.md +1943 -0
  77. package/docs/original-plan-gap-audit.md +47 -0
  78. package/docs/pairing.md +17 -0
  79. package/docs/release.md +52 -0
  80. package/docs/telemetry-protocol.md +50 -0
  81. package/docs/trip-detection.md +7 -0
  82. package/docs/validation.md +35 -0
  83. package/package.json +80 -0
@@ -0,0 +1,168 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CredentialStore = void 0;
7
+ exports.validateCredentials = validateCredentials;
8
+ exports.fingerprintPairingCode = fingerprintPairingCode;
9
+ exports.shouldExchangePairingCode = shouldExchangePairingCode;
10
+ const node_crypto_1 = require("node:crypto");
11
+ const node_fs_1 = require("node:fs");
12
+ const node_path_1 = __importDefault(require("node:path"));
13
+ const profile_1 = require("../telemetry/profile");
14
+ class CredentialStore {
15
+ directory;
16
+ credentialsFile;
17
+ identityFile;
18
+ pairingStateFile;
19
+ constructor(directory) {
20
+ this.directory = directory;
21
+ this.credentialsFile = node_path_1.default.join(directory, 'credentials.json');
22
+ this.identityFile = node_path_1.default.join(directory, 'installation-id');
23
+ this.pairingStateFile = node_path_1.default.join(directory, 'pairing-state.json');
24
+ }
25
+ async installationId() {
26
+ await node_fs_1.promises.mkdir(this.directory, { recursive: true, mode: 0o700 });
27
+ try {
28
+ const value = (await node_fs_1.promises.readFile(this.identityFile, 'utf8')).trim();
29
+ if (/^[0-9a-f-]{36}$/i.test(value))
30
+ return value;
31
+ }
32
+ catch (error) {
33
+ if (!isMissing(error))
34
+ throw error;
35
+ }
36
+ const value = (0, node_crypto_1.randomUUID)();
37
+ await atomicWrite(this.identityFile, `${value}\n`);
38
+ return value;
39
+ }
40
+ async load() {
41
+ try {
42
+ const value = JSON.parse(await node_fs_1.promises.readFile(this.credentialsFile, 'utf8'));
43
+ return validateCredentials(value) ? value : undefined;
44
+ }
45
+ catch (error) {
46
+ if (isMissing(error) || error instanceof SyntaxError)
47
+ return undefined;
48
+ throw error;
49
+ }
50
+ }
51
+ async save(credentials) {
52
+ if (!validateCredentials(credentials))
53
+ throw new Error('Pairing service returned invalid device credentials');
54
+ await node_fs_1.promises.mkdir(this.directory, { recursive: true, mode: 0o700 });
55
+ await atomicWrite(this.credentialsFile, `${JSON.stringify(credentials)}\n`);
56
+ const previousState = await this.loadPairingState();
57
+ await this.savePairingState({
58
+ version: 1,
59
+ lastPairingCodeFingerprint: credentials.pairingCodeFingerprint ?? previousState?.lastPairingCodeFingerprint,
60
+ lastDeviceId: credentials.deviceId
61
+ });
62
+ }
63
+ async lastPairingCodeFingerprint() {
64
+ const credentials = await this.load();
65
+ if (credentials?.pairingCodeFingerprint)
66
+ return credentials.pairingCodeFingerprint;
67
+ return (await this.loadPairingState())?.lastPairingCodeFingerprint;
68
+ }
69
+ async forget() {
70
+ const credentials = await this.load();
71
+ if (!credentials)
72
+ return { forgotten: false };
73
+ const previousState = await this.loadPairingState();
74
+ await this.savePairingState({
75
+ version: 1,
76
+ lastPairingCodeFingerprint: credentials.pairingCodeFingerprint ?? previousState?.lastPairingCodeFingerprint,
77
+ lastDeviceId: credentials.deviceId,
78
+ forgottenAt: Date.now()
79
+ });
80
+ try {
81
+ await node_fs_1.promises.unlink(this.credentialsFile);
82
+ }
83
+ catch (error) {
84
+ if (!isMissing(error))
85
+ throw error;
86
+ }
87
+ return { forgotten: true, deviceId: credentials.deviceId };
88
+ }
89
+ async loadPairingState() {
90
+ try {
91
+ const value = JSON.parse(await node_fs_1.promises.readFile(this.pairingStateFile, 'utf8'));
92
+ if (value.version !== 1)
93
+ return undefined;
94
+ if (value.lastPairingCodeFingerprint !== undefined && !/^[0-9a-f]{64}$/.test(value.lastPairingCodeFingerprint))
95
+ return undefined;
96
+ if (value.lastDeviceId !== undefined && !validId(value.lastDeviceId))
97
+ return undefined;
98
+ return value;
99
+ }
100
+ catch (error) {
101
+ if (isMissing(error) || error instanceof SyntaxError)
102
+ return undefined;
103
+ throw error;
104
+ }
105
+ }
106
+ async savePairingState(state) {
107
+ await node_fs_1.promises.mkdir(this.directory, { recursive: true, mode: 0o700 });
108
+ await atomicWrite(this.pairingStateFile, `${JSON.stringify(state)}\n`);
109
+ }
110
+ }
111
+ exports.CredentialStore = CredentialStore;
112
+ function validateCredentials(value) {
113
+ return value?.version === 1 && validId(value.deviceId) && validId(value.clientId) &&
114
+ typeof value.username === 'string' && value.username.length > 0 && value.username.length <= 256 &&
115
+ typeof value.password === 'string' && value.password.length >= 16 && value.password.length <= 4096 &&
116
+ validHostname(value.mqttHost) && Number.isInteger(value.mqttPort) && value.mqttPort >= 1 && value.mqttPort <= 65535 &&
117
+ typeof value.tls === 'boolean' &&
118
+ Number.isFinite(value.pairedAt) &&
119
+ (value.pairingCodeFingerprint === undefined || /^[0-9a-f]{64}$/.test(value.pairingCodeFingerprint)) &&
120
+ validTelemetryProfile(value.telemetryProfile) && validOutboxBinding(value.outboxBinding) && validAssociationStatus(value);
121
+ }
122
+ function fingerprintPairingCode(code) {
123
+ return (0, node_crypto_1.createHash)('sha256').update(code, 'utf8').digest('hex');
124
+ }
125
+ function shouldExchangePairingCode(credentials, code, lastFingerprint) {
126
+ return typeof code === 'string' && code.length > 0 &&
127
+ (credentials?.pairingCodeFingerprint ?? lastFingerprint) !== fingerprintPairingCode(code);
128
+ }
129
+ function validId(value) { return typeof value === 'string' && /^[A-Za-z0-9_-]{1,128}$/.test(value); }
130
+ function validHostname(value) {
131
+ return typeof value === 'string' && value.length <= 253 && /^(?=.{1,253}$)[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?$/.test(value);
132
+ }
133
+ function validTelemetryProfile(value) {
134
+ if (value === undefined)
135
+ return true;
136
+ if (typeof value !== 'object' || value === null)
137
+ return false;
138
+ if ((0, profile_1.parseTelemetryProfile)(value))
139
+ return true;
140
+ const legacy = value;
141
+ return (legacy.sample_period_ms === undefined || (Number.isInteger(legacy.sample_period_ms) && legacy.sample_period_ms >= 250 && legacy.sample_period_ms <= 60_000)) &&
142
+ (legacy.batch_size === undefined || (Number.isInteger(legacy.batch_size) && legacy.batch_size >= 1 && legacy.batch_size <= 60));
143
+ }
144
+ function validOutboxBinding(value) {
145
+ return value === undefined || (value.version === 1 && (value.backend === 'file' || value.backend === 'database') &&
146
+ Number.isFinite(value.initializedAt) && value.initializedAt >= 0);
147
+ }
148
+ function validAssociationStatus(value) {
149
+ if (value.associationStatusUrl === undefined && value.associationToken === undefined)
150
+ return true;
151
+ return typeof value.associationStatusUrl === 'string' && /^https:\/\//.test(value.associationStatusUrl) && value.associationStatusUrl.length <= 2048 &&
152
+ typeof value.associationToken === 'string' && value.associationToken.length >= 32 && value.associationToken.length <= 512;
153
+ }
154
+ function isMissing(error) { return error?.code === 'ENOENT'; }
155
+ async function atomicWrite(target, contents) {
156
+ const temporary = `${target}.tmp`;
157
+ await node_fs_1.promises.writeFile(temporary, contents, { mode: 0o600 });
158
+ const handle = await node_fs_1.promises.open(temporary, 'r+');
159
+ try {
160
+ await handle.sync();
161
+ }
162
+ finally {
163
+ await handle.close();
164
+ }
165
+ await node_fs_1.promises.rename(temporary, target);
166
+ await node_fs_1.promises.chmod(target, 0o600);
167
+ }
168
+ //# sourceMappingURL=credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/pairing/credentials.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { DeviceCredentials } from './credentials';
2
+ export declare class PairingError extends Error {
3
+ readonly retryable: boolean;
4
+ readonly status?: number | undefined;
5
+ constructor(message: string, retryable: boolean, status?: number | undefined);
6
+ }
7
+ interface PairingRequestOptions {
8
+ signal?: AbortSignal;
9
+ }
10
+ export interface PairingRetryOptions extends PairingRequestOptions {
11
+ maxAttempts?: number;
12
+ baseDelayMs?: number;
13
+ maxDelayMs?: number;
14
+ onAttempt?: (attempt: number, maxAttempts: number) => void;
15
+ onRetry?: (nextAttempt: number, maxAttempts: number, delayMs: number, error: PairingError) => void;
16
+ }
17
+ export declare function pairDevice(apiUrl: string, pairingCode: string, installationId: string, options?: PairingRequestOptions): Promise<DeviceCredentials>;
18
+ export declare function pairDeviceWithRetry(apiUrl: string, pairingCode: string, installationId: string, options?: PairingRetryOptions): Promise<DeviceCredentials>;
19
+ export {};
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PairingError = void 0;
4
+ exports.pairDevice = pairDevice;
5
+ exports.pairDeviceWithRetry = pairDeviceWithRetry;
6
+ const credentials_1 = require("./credentials");
7
+ class PairingError extends Error {
8
+ retryable;
9
+ status;
10
+ constructor(message, retryable, status) {
11
+ super(message);
12
+ this.retryable = retryable;
13
+ this.status = status;
14
+ this.name = 'PairingError';
15
+ }
16
+ }
17
+ exports.PairingError = PairingError;
18
+ async function pairDevice(apiUrl, pairingCode, installationId, options = {}) {
19
+ const controller = new AbortController();
20
+ const timeout = setTimeout(() => controller.abort(), 15_000);
21
+ const abort = () => controller.abort();
22
+ options.signal?.addEventListener('abort', abort, { once: true });
23
+ try {
24
+ let response;
25
+ try {
26
+ response = await fetch(apiUrl, {
27
+ method: 'POST',
28
+ headers: { 'content-type': 'application/json', accept: 'application/json' },
29
+ body: JSON.stringify({ code: pairingCode, name: `Signal K ${installationId.slice(0, 8)}` }),
30
+ signal: controller.signal
31
+ });
32
+ }
33
+ catch {
34
+ if (options.signal?.aborted)
35
+ throw new PairingError('Pairing cancelled', false);
36
+ const timedOut = controller.signal.aborted;
37
+ throw new PairingError(timedOut ? 'Pairing request timed out' : 'Pairing service is unavailable', true);
38
+ }
39
+ if (!response.ok) {
40
+ const retryable = response.status === 408 || response.status === 425 || response.status === 429 || response.status >= 500;
41
+ const message = response.status === 400
42
+ ? 'Pairing code is invalid or expired'
43
+ : `Pairing failed with HTTP ${response.status}`;
44
+ throw new PairingError(message, retryable, response.status);
45
+ }
46
+ const body = await response.json();
47
+ const credentials = {
48
+ version: 1,
49
+ deviceId: body.device?.id,
50
+ clientId: body.credentials?.client_id,
51
+ username: body.credentials?.username,
52
+ password: body.credentials?.password,
53
+ mqttHost: body.credentials?.broker_host,
54
+ mqttPort: body.credentials?.broker_port,
55
+ tls: body.credentials?.tls,
56
+ telemetryProfile: body.credentials?.telemetry_profile,
57
+ associationStatusUrl: body.credentials?.association_status_url,
58
+ associationToken: body.credentials?.association_token,
59
+ pairedAt: Date.now()
60
+ };
61
+ if (!(0, credentials_1.validateCredentials)(credentials))
62
+ throw new PairingError('Pairing response failed validation', false);
63
+ return credentials;
64
+ }
65
+ finally {
66
+ clearTimeout(timeout);
67
+ options.signal?.removeEventListener('abort', abort);
68
+ }
69
+ }
70
+ async function pairDeviceWithRetry(apiUrl, pairingCode, installationId, options = {}) {
71
+ const maxAttempts = Math.max(1, Math.min(10, options.maxAttempts ?? 6));
72
+ const baseDelayMs = Math.max(1, options.baseDelayMs ?? 2000);
73
+ const maxDelayMs = Math.max(baseDelayMs, options.maxDelayMs ?? 30_000);
74
+ let lastError = new PairingError('Pairing failed', false);
75
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
76
+ options.onAttempt?.(attempt, maxAttempts);
77
+ try {
78
+ return await pairDevice(apiUrl, pairingCode, installationId, { signal: options.signal });
79
+ }
80
+ catch (error) {
81
+ lastError = error instanceof PairingError ? error : new PairingError('Pairing service is unavailable', true);
82
+ if (!lastError.retryable || attempt >= maxAttempts || options.signal?.aborted)
83
+ throw lastError;
84
+ const delayMs = Math.min(maxDelayMs, baseDelayMs * 2 ** (attempt - 1));
85
+ options.onRetry?.(attempt + 1, maxAttempts, delayMs, lastError);
86
+ await abortableDelay(delayMs, options.signal);
87
+ }
88
+ }
89
+ throw lastError;
90
+ }
91
+ function abortableDelay(delayMs, signal) {
92
+ return new Promise((resolve, reject) => {
93
+ if (signal?.aborted) {
94
+ reject(new PairingError('Pairing cancelled', false));
95
+ return;
96
+ }
97
+ const timer = setTimeout(() => { signal?.removeEventListener('abort', abort); resolve(); }, delayMs);
98
+ const abort = () => { clearTimeout(timer); reject(new PairingError('Pairing cancelled', false)); };
99
+ signal?.addEventListener('abort', abort, { once: true });
100
+ });
101
+ }
102
+ //# sourceMappingURL=pairing-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pairing-client.js","sourceRoot":"","sources":["../../src/pairing/pairing-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { Delta } from '@signalk/server-api';
2
+ import type { TelemetryDraft, TelemetryField } from '../telemetry/types';
3
+ export declare class TelemetryNormaliser {
4
+ private readonly values;
5
+ private dirty;
6
+ private readonly emitted;
7
+ ingest(delta: Delta, receivedAt?: number): void;
8
+ takeSample(now?: number, fields?: Set<TelemetryField>): TelemetryDraft | undefined;
9
+ private get;
10
+ private trackPresence;
11
+ }
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TelemetryNormaliser = void 0;
4
+ const MS_PER_SECOND = 1000;
5
+ const MPS_TO_KNOTS = 1.9438444924406;
6
+ const EARLIEST_SOURCE_TIME = Date.UTC(2000, 0, 1);
7
+ const MAX_FUTURE_MS = 5 * 60 * MS_PER_SECOND;
8
+ class TelemetryNormaliser {
9
+ values = new Map();
10
+ dirty = false;
11
+ emitted = new Set();
12
+ ingest(delta, receivedAt = Date.now()) {
13
+ for (const update of delta.updates) {
14
+ if (!('values' in update))
15
+ continue;
16
+ const sourceAt = parseSourceTime(update.timestamp, receivedAt);
17
+ for (const pathValue of update.values) {
18
+ if (!isSupportedPath(pathValue.path))
19
+ continue;
20
+ if (pathValue.state?.timedOut || !isValidPathValue(pathValue.path, pathValue.value)) {
21
+ if (this.values.delete(pathValue.path))
22
+ this.dirty = true;
23
+ continue;
24
+ }
25
+ this.values.set(pathValue.path, { value: pathValue.value, sourceAt, receivedAt });
26
+ this.dirty = true;
27
+ }
28
+ }
29
+ }
30
+ takeSample(now = Date.now(), fields = new Set(['position', 'sog', 'cog', 'heading', 'depth', 'apparentWind'])) {
31
+ if (!this.dirty)
32
+ return undefined;
33
+ const position = this.get('navigation.position', now);
34
+ if (!position)
35
+ return undefined;
36
+ this.dirty = false;
37
+ const headingTrue = fields.has('heading') ? this.get('navigation.headingTrue', now) : undefined;
38
+ const headingMagnetic = fields.has('heading') ? this.get('navigation.headingMagnetic', now) : undefined;
39
+ const capturedAt = position.sourceAt ?? now;
40
+ const quality = { timestamp: position.sourceAt !== undefined ? 'source' : 'receipt' };
41
+ const values = {
42
+ lat: position.value.latitude,
43
+ lon: position.value.longitude,
44
+ sog_kn: fields.has('sog') ? convertNumber(this.get('navigation.speedOverGround', now), (v) => v * MPS_TO_KNOTS) : undefined,
45
+ cog_deg: fields.has('cog') ? convertNumber(this.get('navigation.courseOverGroundTrue', now), radiansToDegrees) : undefined,
46
+ heading_deg: convertNumber(headingTrue ?? headingMagnetic, radiansToDegrees),
47
+ heading_reference: headingTrue !== undefined ? 'true' : headingMagnetic !== undefined ? 'magnetic' : undefined,
48
+ depth_m: fields.has('depth') ? this.get('environment.depth.belowTransducer', now)?.value : undefined,
49
+ aws_kn: fields.has('apparentWind') ? convertNumber(this.get('environment.wind.speedApparent', now), (v) => v * MPS_TO_KNOTS) : undefined,
50
+ awa_deg: fields.has('apparentWind') ? convertNumber(this.get('environment.wind.angleApparent', now), radiansToDegrees) : undefined
51
+ };
52
+ const cleared = [];
53
+ this.trackPresence('sog', fields.has('sog'), values.sog_kn !== undefined, cleared);
54
+ this.trackPresence('cog', fields.has('cog'), values.cog_deg !== undefined, cleared);
55
+ this.trackPresence('heading', fields.has('heading'), values.heading_deg !== undefined, cleared);
56
+ this.trackPresence('depth', fields.has('depth'), values.depth_m !== undefined, cleared);
57
+ this.trackPresence('apparentWind', fields.has('apparentWind'), values.aws_kn !== undefined || values.awa_deg !== undefined, cleared);
58
+ removeUndefined(values);
59
+ return { capturedAt, receivedAt: now, values, quality, cleared: cleared.length ? cleared : undefined };
60
+ }
61
+ get(path, now) {
62
+ const value = this.values.get(path);
63
+ return value && now - value.receivedAt <= 30_000 ? value : undefined;
64
+ }
65
+ trackPresence(field, due, present, cleared) {
66
+ if (!due)
67
+ return;
68
+ if (present)
69
+ this.emitted.add(field);
70
+ else if (this.emitted.delete(field))
71
+ cleared.push(field);
72
+ }
73
+ }
74
+ exports.TelemetryNormaliser = TelemetryNormaliser;
75
+ function parseSourceTime(value, receivedAt) {
76
+ if (!value)
77
+ return undefined;
78
+ const parsed = Date.parse(value);
79
+ return Number.isFinite(parsed) && parsed >= EARLIEST_SOURCE_TIME && parsed <= receivedAt + MAX_FUTURE_MS ? parsed : undefined;
80
+ }
81
+ function isSupportedPath(path) {
82
+ return [
83
+ 'navigation.position', 'navigation.speedOverGround', 'navigation.courseOverGroundTrue',
84
+ 'navigation.headingTrue', 'navigation.headingMagnetic', 'environment.depth.belowTransducer',
85
+ 'environment.wind.speedApparent', 'environment.wind.angleApparent'
86
+ ].includes(path);
87
+ }
88
+ function isValidPathValue(path, value) {
89
+ if (path === 'navigation.position') {
90
+ const p = value;
91
+ return !!p && finiteBetween(p.latitude, -90, 90) && finiteBetween(p.longitude, -180, 180);
92
+ }
93
+ if (typeof value !== 'number' || !Number.isFinite(value))
94
+ return false;
95
+ switch (path) {
96
+ case 'navigation.speedOverGround': return finiteBetween(value, 0, 80);
97
+ case 'environment.depth.belowTransducer': return finiteBetween(value, 0, 12_000);
98
+ case 'environment.wind.speedApparent': return finiteBetween(value, 0, 120);
99
+ default: return Math.abs(value) <= Math.PI * 100;
100
+ }
101
+ }
102
+ function finiteBetween(value, min, max) {
103
+ return typeof value === 'number' && Number.isFinite(value) && value >= min && value <= max;
104
+ }
105
+ function radiansToDegrees(value) {
106
+ return ((value * 180 / Math.PI) % 360 + 360) % 360;
107
+ }
108
+ function convertNumber(value, convert) {
109
+ if (value === undefined)
110
+ return undefined;
111
+ const number = typeof value === 'number' ? value : value.value;
112
+ return Math.round(convert(number) * 1_000_000) / 1_000_000;
113
+ }
114
+ function removeUndefined(value) {
115
+ for (const key of Object.keys(value))
116
+ if (value[key] === undefined)
117
+ delete value[key];
118
+ }
119
+ //# sourceMappingURL=normaliser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normaliser.js","sourceRoot":"","sources":["../../src/signalk/normaliser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare const SIGNALK_PATHS: readonly ["navigation.position", "navigation.speedOverGround", "navigation.courseOverGroundTrue", "navigation.headingTrue", "navigation.headingMagnetic", "environment.depth.belowTransducer", "environment.wind.speedApparent", "environment.wind.angleApparent"];
2
+ export type SignalKPath = (typeof SIGNALK_PATHS)[number];
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SIGNALK_PATHS = void 0;
4
+ exports.SIGNALK_PATHS = [
5
+ 'navigation.position',
6
+ 'navigation.speedOverGround',
7
+ 'navigation.courseOverGroundTrue',
8
+ 'navigation.headingTrue',
9
+ 'navigation.headingMagnetic',
10
+ 'environment.depth.belowTransducer',
11
+ 'environment.wind.speedApparent',
12
+ 'environment.wind.angleApparent'
13
+ ];
14
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/signalk/paths.ts"],"names":[],"mappings":";;;AAAa,QAAA,aAAa,GAAG;IAC3B,qBAAqB;IACrB,4BAA4B;IAC5B,iCAAiC;IACjC,wBAAwB;IACxB,4BAA4B;IAC5B,mCAAmC;IACnC,gCAAgC;IAChC,gCAAgC;CACxB,CAAA"}
@@ -0,0 +1,2 @@
1
+ import type { Delta, ServerAPI } from '@signalk/server-api';
2
+ export declare function subscribeToTelemetry(app: ServerAPI, callback: (delta: Delta) => void): () => void;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.subscribeToTelemetry = subscribeToTelemetry;
4
+ const paths_1 = require("./paths");
5
+ function subscribeToTelemetry(app, callback) {
6
+ const unsubscribes = [];
7
+ app.subscriptionmanager.subscribe({
8
+ context: 'vessels.self',
9
+ subscribe: paths_1.SIGNALK_PATHS.map((path) => ({ path: path, policy: 'instant', minPeriod: 100 })),
10
+ sourcePolicy: 'preferred'
11
+ }, unsubscribes, (error) => app.error(`Wake Logger subscription error: ${safeError(error)}`), callback);
12
+ return () => {
13
+ for (const unsubscribe of unsubscribes.splice(0))
14
+ unsubscribe();
15
+ };
16
+ }
17
+ function safeError(error) {
18
+ return error instanceof Error ? error.message : String(error);
19
+ }
20
+ //# sourceMappingURL=subscriber.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscriber.js","sourceRoot":"","sources":["../../src/signalk/subscriber.ts"],"names":[],"mappings":";;AAGA,oDAeC;AAjBD,mCAAuC;AAEvC,SAAgB,oBAAoB,CAAC,GAAc,EAAE,QAAgC;IACnF,MAAM,YAAY,GAAiB,EAAE,CAAA;IACrC,GAAG,CAAC,mBAAmB,CAAC,SAAS,CAC/B;QACE,OAAO,EAAE,cAAuB;QAChC,SAAS,EAAE,qBAAa,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAa,EAAE,MAAM,EAAE,SAAkB,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC;QAC7G,YAAY,EAAE,WAAW;KAC1B,EACD,YAAY,EACZ,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,mCAAmC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,EAC3E,QAAQ,CACT,CAAA;IACD,OAAO,GAAG,EAAE;QACV,KAAK,MAAM,WAAW,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;YAAE,WAAW,EAAE,CAAA;IACjE,CAAC,CAAA;AACH,CAAC;AAED,SAAS,SAAS,CAAC,KAAc;IAC/B,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAC/D,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { type TelemetryProfile } from './profile';
2
+ export declare class TelemetryProfileStore {
3
+ private readonly target;
4
+ constructor(directory: string);
5
+ load(): Promise<TelemetryProfile | undefined>;
6
+ save(profile: TelemetryProfile): Promise<void>;
7
+ }
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.TelemetryProfileStore = void 0;
7
+ const node_fs_1 = require("node:fs");
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const profile_1 = require("./profile");
10
+ class TelemetryProfileStore {
11
+ target;
12
+ constructor(directory) { this.target = node_path_1.default.join(directory, 'telemetry-profile.json'); }
13
+ async load() {
14
+ try {
15
+ return (0, profile_1.parseTelemetryProfile)(JSON.parse(await node_fs_1.promises.readFile(this.target, 'utf8')));
16
+ }
17
+ catch (error) {
18
+ if (error.code === 'ENOENT' || error instanceof SyntaxError)
19
+ return undefined;
20
+ throw error;
21
+ }
22
+ }
23
+ async save(profile) {
24
+ await node_fs_1.promises.mkdir(node_path_1.default.dirname(this.target), { recursive: true, mode: 0o700 });
25
+ const temporary = `${this.target}.tmp`;
26
+ await node_fs_1.promises.writeFile(temporary, `${JSON.stringify(profile)}\n`, { mode: 0o600 });
27
+ const handle = await node_fs_1.promises.open(temporary, 'r+');
28
+ try {
29
+ await handle.sync();
30
+ }
31
+ finally {
32
+ await handle.close();
33
+ }
34
+ await node_fs_1.promises.rename(temporary, this.target);
35
+ await node_fs_1.promises.chmod(this.target, 0o600);
36
+ }
37
+ }
38
+ exports.TelemetryProfileStore = TelemetryProfileStore;
39
+ //# sourceMappingURL=profile-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile-store.js","sourceRoot":"","sources":["../../src/telemetry/profile-store.ts"],"names":[],"mappings":";;;;;;AAAA,qCAAwC;AACxC,0DAA4B;AAC5B,uCAAwE;AAExE,MAAa,qBAAqB;IACf,MAAM,CAAQ;IAC/B,YAAY,SAAiB,IAAI,IAAI,CAAC,MAAM,GAAG,mBAAI,CAAC,IAAI,CAAC,SAAS,EAAE,wBAAwB,CAAC,CAAA,CAAC,CAAC;IAE/F,KAAK,CAAC,IAAI;QACR,IAAI,CAAC;YAAC,OAAO,IAAA,+BAAqB,EAAC,IAAI,CAAC,KAAK,CAAC,MAAM,kBAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAA;QAAC,CAAC;QACxF,OAAO,KAAK,EAAE,CAAC;YACb,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,YAAY,WAAW;gBAAE,OAAO,SAAS,CAAA;YACxG,MAAM,KAAK,CAAA;QACb,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAAyB;QAClC,MAAM,kBAAE,CAAC,KAAK,CAAC,mBAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;QAC3E,MAAM,SAAS,GAAG,GAAG,IAAI,CAAC,MAAM,MAAM,CAAA;QACtC,MAAM,kBAAE,CAAC,SAAS,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;QAC9E,MAAM,MAAM,GAAG,MAAM,kBAAE,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;QAC7C,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,IAAI,EAAE,CAAA;QACrB,CAAC;gBAAS,CAAC;YACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QACtB,CAAC;QACD,MAAM,kBAAE,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;QACvC,MAAM,kBAAE,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;IACpC,CAAC;CACF;AAzBD,sDAyBC"}
@@ -0,0 +1,32 @@
1
+ import type { NetworkMode, TelemetryField } from './types';
2
+ export interface PathRateProfile {
3
+ enabled: boolean;
4
+ normalMs: number;
5
+ constrainedMs: number;
6
+ offlineMs: number;
7
+ }
8
+ export interface TelemetryProfile {
9
+ v: 1;
10
+ profileId: string;
11
+ revision: number;
12
+ issuedAt: number;
13
+ paths: Record<TelemetryField, PathRateProfile>;
14
+ transport: {
15
+ batchSize: number;
16
+ maxPayloadBytes: number;
17
+ normalBacklogBytesPerSecond: number;
18
+ constrainedBacklogBytesPerSecond: number;
19
+ };
20
+ }
21
+ export interface ProfileAcknowledgement {
22
+ v: 1;
23
+ profileId: string;
24
+ revision: number;
25
+ status: 'applied' | 'rejected';
26
+ at: number;
27
+ errorCode?: string;
28
+ }
29
+ export declare const DEFAULT_TELEMETRY_PROFILE: TelemetryProfile;
30
+ export declare function parseTelemetryProfile(value: unknown): TelemetryProfile | undefined;
31
+ export declare function legacyProfile(samplePeriodMs?: number, batchSize?: number): TelemetryProfile;
32
+ export declare function periodFor(profile: TelemetryProfile, field: TelemetryField, mode: NetworkMode): number;