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,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_TELEMETRY_PROFILE = void 0;
4
+ exports.parseTelemetryProfile = parseTelemetryProfile;
5
+ exports.legacyProfile = legacyProfile;
6
+ exports.periodFor = periodFor;
7
+ const navRate = { enabled: true, normalMs: 1000, constrainedMs: 1000, offlineMs: 1000 };
8
+ const secondaryRate = { enabled: true, normalMs: 1000, constrainedMs: 5000, offlineMs: 10_000 };
9
+ exports.DEFAULT_TELEMETRY_PROFILE = {
10
+ v: 1,
11
+ profileId: 'standard',
12
+ revision: 1,
13
+ issuedAt: 0,
14
+ paths: {
15
+ position: { ...navRate }, sog: { ...navRate }, cog: { ...navRate }, heading: { ...navRate },
16
+ depth: { ...secondaryRate }, apparentWind: { ...secondaryRate }
17
+ },
18
+ transport: {
19
+ batchSize: 60,
20
+ maxPayloadBytes: 64 * 1024,
21
+ normalBacklogBytesPerSecond: 64 * 1024,
22
+ constrainedBacklogBytesPerSecond: 8 * 1024
23
+ }
24
+ };
25
+ function parseTelemetryProfile(value) {
26
+ if (!value || typeof value !== 'object')
27
+ return undefined;
28
+ const input = value;
29
+ if (input.v !== 1 || !validId(input.profileId) || !positiveInteger(input.revision) || !nonNegativeNumber(input.issuedAt))
30
+ return undefined;
31
+ if (!input.paths || typeof input.paths !== 'object' || !input.transport || typeof input.transport !== 'object')
32
+ return undefined;
33
+ const paths = input.paths;
34
+ const parsedPaths = {};
35
+ for (const field of fields()) {
36
+ const parsed = parsePathRate(paths[field], field === 'position');
37
+ if (!parsed)
38
+ return undefined;
39
+ parsedPaths[field] = parsed;
40
+ }
41
+ if (Object.keys(paths).some((key) => !fields().includes(key)))
42
+ return undefined;
43
+ const transport = input.transport;
44
+ if (!integerBetween(transport.batchSize, 1, 120) || !integerBetween(transport.maxPayloadBytes, 8192, 65_536) ||
45
+ !integerBetween(transport.normalBacklogBytesPerSecond, 1024, 262_144) ||
46
+ !integerBetween(transport.constrainedBacklogBytesPerSecond, 512, 65_536) ||
47
+ transport.constrainedBacklogBytesPerSecond > transport.normalBacklogBytesPerSecond)
48
+ return undefined;
49
+ return {
50
+ v: 1, profileId: input.profileId, revision: input.revision, issuedAt: input.issuedAt,
51
+ paths: parsedPaths,
52
+ transport: {
53
+ batchSize: transport.batchSize,
54
+ maxPayloadBytes: transport.maxPayloadBytes,
55
+ normalBacklogBytesPerSecond: transport.normalBacklogBytesPerSecond,
56
+ constrainedBacklogBytesPerSecond: transport.constrainedBacklogBytesPerSecond
57
+ }
58
+ };
59
+ }
60
+ function legacyProfile(samplePeriodMs, batchSize) {
61
+ const period = integerBetween(samplePeriodMs, 250, 60_000) ? samplePeriodMs : 1000;
62
+ return {
63
+ ...structuredClone(exports.DEFAULT_TELEMETRY_PROFILE),
64
+ paths: Object.fromEntries(fields().map((field) => [field, {
65
+ ...exports.DEFAULT_TELEMETRY_PROFILE.paths[field],
66
+ normalMs: period,
67
+ constrainedMs: field === 'depth' || field === 'apparentWind' ? Math.max(period, 5000) : period,
68
+ offlineMs: field === 'depth' || field === 'apparentWind' ? Math.max(period, 10_000) : period
69
+ }])),
70
+ transport: { ...exports.DEFAULT_TELEMETRY_PROFILE.transport, batchSize: integerBetween(batchSize, 1, 60) ? batchSize : 60 }
71
+ };
72
+ }
73
+ function periodFor(profile, field, mode) {
74
+ const rate = profile.paths[field];
75
+ return mode === 'NORMAL' ? rate.normalMs : mode === 'CONSTRAINED' ? rate.constrainedMs : rate.offlineMs;
76
+ }
77
+ function parsePathRate(value, required) {
78
+ if (!value || typeof value !== 'object')
79
+ return undefined;
80
+ const input = value;
81
+ if (typeof input.enabled !== 'boolean' || (required && !input.enabled))
82
+ return undefined;
83
+ if (!integerBetween(input.normalMs, 250, 600_000) || !integerBetween(input.constrainedMs, 250, 600_000) || !integerBetween(input.offlineMs, 250, 600_000))
84
+ return undefined;
85
+ if (!(input.normalMs <= input.constrainedMs && input.constrainedMs <= input.offlineMs))
86
+ return undefined;
87
+ return { enabled: input.enabled, normalMs: input.normalMs, constrainedMs: input.constrainedMs, offlineMs: input.offlineMs };
88
+ }
89
+ function fields() { return ['position', 'sog', 'cog', 'heading', 'depth', 'apparentWind']; }
90
+ function validId(value) { return typeof value === 'string' && /^[A-Za-z0-9_-]{1,80}$/.test(value); }
91
+ function positiveInteger(value) { return Number.isSafeInteger(value) && Number(value) > 0; }
92
+ function nonNegativeNumber(value) { return typeof value === 'number' && Number.isFinite(value) && value >= 0; }
93
+ function integerBetween(value, min, max) { return Number.isInteger(value) && Number(value) >= min && Number(value) <= max; }
94
+ //# sourceMappingURL=profile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile.js","sourceRoot":"","sources":["../../src/telemetry/profile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import { type TelemetryProfile } from './profile';
2
+ import type { NetworkMode, TelemetryField } from './types';
3
+ export declare class PathSampler {
4
+ private profile;
5
+ private mode;
6
+ private lastIncluded;
7
+ constructor(profile: TelemetryProfile, mode?: NetworkMode);
8
+ updateProfile(profile: TelemetryProfile): void;
9
+ updateMode(mode: NetworkMode): void;
10
+ currentMode(): NetworkMode;
11
+ currentProfile(): TelemetryProfile;
12
+ samplePeriodMs(): number;
13
+ dueFields(now: number): Set<TelemetryField>;
14
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PathSampler = void 0;
4
+ const profile_1 = require("./profile");
5
+ class PathSampler {
6
+ profile;
7
+ mode;
8
+ lastIncluded = new Map();
9
+ constructor(profile, mode = 'OFFLINE') {
10
+ this.profile = profile;
11
+ this.mode = mode;
12
+ }
13
+ updateProfile(profile) {
14
+ this.profile = profile;
15
+ this.lastIncluded.clear();
16
+ }
17
+ updateMode(mode) {
18
+ if (this.mode !== mode)
19
+ this.lastIncluded.clear();
20
+ this.mode = mode;
21
+ }
22
+ currentMode() { return this.mode; }
23
+ currentProfile() { return this.profile; }
24
+ samplePeriodMs() {
25
+ return (0, profile_1.periodFor)(this.profile, 'position', this.mode);
26
+ }
27
+ dueFields(now) {
28
+ const due = new Set(['position']);
29
+ this.lastIncluded.set('position', now);
30
+ for (const field of ['sog', 'cog', 'heading', 'depth', 'apparentWind']) {
31
+ const config = this.profile.paths[field];
32
+ if (!config.enabled)
33
+ continue;
34
+ const last = this.lastIncluded.get(field) ?? Number.NEGATIVE_INFINITY;
35
+ if (now - last >= (0, profile_1.periodFor)(this.profile, field, this.mode)) {
36
+ due.add(field);
37
+ this.lastIncluded.set(field, now);
38
+ }
39
+ }
40
+ return due;
41
+ }
42
+ }
43
+ exports.PathSampler = PathSampler;
44
+ //# sourceMappingURL=sampler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sampler.js","sourceRoot":"","sources":["../../src/telemetry/sampler.ts"],"names":[],"mappings":";;;AAAA,uCAA4D;AAG5D,MAAa,WAAW;IAGF;IAAmC;IAF/C,YAAY,GAAG,IAAI,GAAG,EAA0B,CAAA;IAExD,YAAoB,OAAyB,EAAU,OAAoB,SAAS;QAAhE,YAAO,GAAP,OAAO,CAAkB;QAAU,SAAI,GAAJ,IAAI,CAAyB;IAAG,CAAC;IAExF,aAAa,CAAC,OAAyB;QACrC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QACtB,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAA;IAC3B,CAAC;IAED,UAAU,CAAC,IAAiB;QAC1B,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI;YAAE,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAA;QACjD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;IAClB,CAAC;IAED,WAAW,KAAkB,OAAO,IAAI,CAAC,IAAI,CAAA,CAAC,CAAC;IAC/C,cAAc,KAAuB,OAAO,IAAI,CAAC,OAAO,CAAA,CAAC,CAAC;IAE1D,cAAc;QACZ,OAAO,IAAA,mBAAS,EAAC,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;IACvD,CAAC;IAED,SAAS,CAAC,GAAW;QACnB,MAAM,GAAG,GAAG,IAAI,GAAG,CAAiB,CAAC,UAAU,CAAC,CAAC,CAAA;QACjD,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,CAAA;QACtC,KAAK,MAAM,KAAK,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,cAAc,CAAU,EAAE,CAAC;YAChF,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;YACxC,IAAI,CAAC,MAAM,CAAC,OAAO;gBAAE,SAAQ;YAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,iBAAiB,CAAA;YACrE,IAAI,GAAG,GAAG,IAAI,IAAI,IAAA,mBAAS,EAAC,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC5D,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;gBACd,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;YACnC,CAAC;QACH,CAAC;QACD,OAAO,GAAG,CAAA;IACZ,CAAC;CACF;AApCD,kCAoCC"}
@@ -0,0 +1,73 @@
1
+ export type HeadingReference = 'true' | 'magnetic';
2
+ export type NetworkMode = 'NORMAL' | 'CONSTRAINED' | 'OFFLINE';
3
+ export type TelemetryField = 'position' | 'sog' | 'cog' | 'heading' | 'depth' | 'apparentWind';
4
+ export interface TelemetryQuality {
5
+ timestamp: 'source' | 'receipt';
6
+ }
7
+ export interface TelemetryValues {
8
+ lat: number;
9
+ lon: number;
10
+ sog_kn?: number;
11
+ cog_deg?: number;
12
+ heading_deg?: number;
13
+ heading_reference?: HeadingReference;
14
+ depth_m?: number;
15
+ aws_kn?: number;
16
+ awa_deg?: number;
17
+ }
18
+ export interface TripEvidence {
19
+ event: 'trip_start_candidate' | 'trip_started' | 'trip_stop_candidate' | 'trip_stopped';
20
+ effectiveAt: number;
21
+ trackingSessionId?: string;
22
+ reason: {
23
+ sogKn?: number;
24
+ distanceMovedMetres?: number;
25
+ movementDurationSeconds?: number;
26
+ };
27
+ }
28
+ export interface TelemetryDraft {
29
+ capturedAt: number;
30
+ receivedAt: number;
31
+ trackingSessionId?: string;
32
+ values: TelemetryValues;
33
+ quality: TelemetryQuality;
34
+ cleared?: Exclude<TelemetryField, 'position'>[];
35
+ evidence?: TripEvidence;
36
+ }
37
+ export interface TelemetrySample extends TelemetryDraft {
38
+ v: 1;
39
+ deviceId: string;
40
+ sequence: number;
41
+ }
42
+ export interface TelemetryBatch {
43
+ v: 1;
44
+ deviceId: string;
45
+ droppedThrough?: number;
46
+ samples: TelemetrySample[];
47
+ }
48
+ export interface ApplicationAck {
49
+ v: 1;
50
+ deviceId: string;
51
+ ackSequence: number;
52
+ acknowledgedAt?: number;
53
+ }
54
+ export interface PluginStatusMetrics {
55
+ pluginVersion: string;
56
+ connectionState: string;
57
+ queueMessageCount: number;
58
+ queueDiskBytes: number;
59
+ queueOldestCapturedAt?: number;
60
+ queueDroppedCount: number;
61
+ acknowledgedSequence: number;
62
+ currentSequence: number;
63
+ trackingState?: string;
64
+ storageBackend?: 'file' | 'database';
65
+ networkMode?: NetworkMode;
66
+ modeReason?: string;
67
+ profileId?: string;
68
+ profileRevision?: number;
69
+ lastAcknowledgedAt?: number;
70
+ acknowledgementLatencyMs?: number;
71
+ reconnectCount?: number;
72
+ publishedBytes?: number;
73
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/telemetry/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,23 @@
1
+ import type { NetworkMode } from '../telemetry/types';
2
+ export declare class AdaptiveModeMonitor {
3
+ private readonly now;
4
+ private mode;
5
+ private reason;
6
+ private reconnects;
7
+ private slowAcknowledgements;
8
+ private healthySince?;
9
+ constructor(now?: () => number);
10
+ connected(): void;
11
+ disconnected(reason?: string): void;
12
+ reconnect(): void;
13
+ acknowledgement(latencyMs: number): void;
14
+ failure(reason?: string): void;
15
+ tick(): void;
16
+ current(): {
17
+ mode: NetworkMode;
18
+ reason: string;
19
+ reconnectCount: number;
20
+ };
21
+ private constrain;
22
+ private set;
23
+ }
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AdaptiveModeMonitor = void 0;
4
+ class AdaptiveModeMonitor {
5
+ now;
6
+ mode = 'OFFLINE';
7
+ reason = 'mqtt_disconnected';
8
+ reconnects = [];
9
+ slowAcknowledgements = 0;
10
+ healthySince;
11
+ constructor(now = Date.now) {
12
+ this.now = now;
13
+ }
14
+ connected() {
15
+ this.healthySince = this.now();
16
+ if (this.mode === 'OFFLINE')
17
+ this.set('NORMAL', 'mqtt_connected');
18
+ }
19
+ disconnected(reason = 'mqtt_disconnected') {
20
+ this.healthySince = undefined;
21
+ this.set('OFFLINE', reason);
22
+ }
23
+ reconnect() {
24
+ const now = this.now();
25
+ this.reconnects = this.reconnects.filter((at) => now - at <= 5 * 60_000);
26
+ this.reconnects.push(now);
27
+ if (this.reconnects.length >= 2)
28
+ this.constrain('frequent_reconnects');
29
+ }
30
+ acknowledgement(latencyMs) {
31
+ if (latencyMs > 5000)
32
+ this.slowAcknowledgements += 1;
33
+ else
34
+ this.slowAcknowledgements = 0;
35
+ if (this.slowAcknowledgements >= 3)
36
+ this.constrain('slow_acknowledgements');
37
+ if (this.mode === 'CONSTRAINED' && latencyMs < 3000) {
38
+ this.healthySince ??= this.now();
39
+ }
40
+ else if (latencyMs >= 3000)
41
+ this.healthySince = undefined;
42
+ }
43
+ failure(reason = 'publish_failure') { this.constrain(reason); }
44
+ tick() {
45
+ if (this.mode === 'CONSTRAINED' && this.healthySince !== undefined && this.now() - this.healthySince >= 120_000) {
46
+ this.slowAcknowledgements = 0;
47
+ this.reconnects = [];
48
+ this.set('NORMAL', 'healthy_transport');
49
+ }
50
+ }
51
+ current() {
52
+ return { mode: this.mode, reason: this.reason, reconnectCount: this.reconnects.length };
53
+ }
54
+ constrain(reason) {
55
+ if (this.mode === 'OFFLINE')
56
+ return;
57
+ this.healthySince = undefined;
58
+ this.set('CONSTRAINED', reason);
59
+ }
60
+ set(mode, reason) { this.mode = mode; this.reason = reason; }
61
+ }
62
+ exports.AdaptiveModeMonitor = AdaptiveModeMonitor;
63
+ //# sourceMappingURL=adaptive-mode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adaptive-mode.js","sourceRoot":"","sources":["../../src/transport/adaptive-mode.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ import type { DeviceCredentials } from '../pairing/credentials';
2
+ import { type TelemetryProfile } from '../telemetry/profile';
3
+ import type { NetworkMode, PluginStatusMetrics, TelemetrySample } from '../telemetry/types';
4
+ import type { OutboxStore } from '../outbox/interface';
5
+ export type ConnectionState = 'offline' | 'connecting' | 'online' | 'degraded' | 'authentication_failed';
6
+ interface TransportOptions {
7
+ profile: TelemetryProfile;
8
+ onState: (state: ConnectionState, detail?: string) => void;
9
+ onMode?: (mode: NetworkMode, reason: string) => void;
10
+ onProfile?: (profile: TelemetryProfile) => Promise<void>;
11
+ debug?: (message: string) => void;
12
+ random?: () => number;
13
+ now?: () => number;
14
+ }
15
+ export declare class WakeLoggerTransport {
16
+ private readonly credentials;
17
+ private readonly outbox;
18
+ private readonly options;
19
+ private client?;
20
+ private stopped;
21
+ private reconnectAttempt;
22
+ private reconnectTimer?;
23
+ private pumpTimer?;
24
+ private healthTimer?;
25
+ private pumpRunning;
26
+ private current?;
27
+ private statePublishInFlight;
28
+ private statePublishPending;
29
+ private authenticationFailed;
30
+ private statusMetrics?;
31
+ private profile;
32
+ private readonly monitor;
33
+ private readonly topics;
34
+ private inFlight;
35
+ private nextPublishAfter;
36
+ private lastLivePublishedAt;
37
+ private lastLivePublishedSequence;
38
+ private lastAcknowledgedAt?;
39
+ private acknowledgementLatencyMs?;
40
+ private publishedBytes;
41
+ private lastMode?;
42
+ private backlogMessageCount;
43
+ private backlogTokens;
44
+ private backlogTokenAt?;
45
+ constructor(credentials: DeviceCredentials, outbox: OutboxStore, options: TransportOptions);
46
+ start(): void;
47
+ stop(): Promise<void>;
48
+ updateCurrent(sample: TelemetrySample): void;
49
+ updateProfile(profile: TelemetryProfile): void;
50
+ updateStatus(metrics: PluginStatusMetrics): void;
51
+ transportMetrics(): Pick<PluginStatusMetrics, 'networkMode' | 'modeReason' | 'lastAcknowledgedAt' | 'acknowledgementLatencyMs' | 'reconnectCount' | 'publishedBytes'>;
52
+ private connect;
53
+ private onConnect;
54
+ private drainCurrentState;
55
+ private publishLiveSample;
56
+ private pump;
57
+ private onMessage;
58
+ private handleAcknowledgement;
59
+ private handleProfile;
60
+ private checkAcknowledgementTimeout;
61
+ private scheduleReconnect;
62
+ private syncMode;
63
+ private effectiveStatusMetrics;
64
+ private transportFailure;
65
+ private publish;
66
+ private now;
67
+ private refillBacklogBudget;
68
+ }
69
+ export {};