@technoculture/data-bridge 0.1.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/CMakeLists.txt +57 -0
- package/README.md +77 -0
- package/dist/index.d.mts +148 -0
- package/dist/index.d.ts +148 -0
- package/dist/index.js +314 -0
- package/dist/index.mjs +285 -0
- package/lib/index.ts +171 -0
- package/lib/resilient.ts +276 -0
- package/package.json +75 -0
- package/prebuilds/darwin-arm64/.ninja_deps +0 -0
- package/prebuilds/darwin-arm64/.ninja_log +6 -0
- package/prebuilds/darwin-arm64/CMakeCache.txt +398 -0
- package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CMakeCCompiler.cmake +84 -0
- package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CMakeCXXCompiler.cmake +104 -0
- package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CMakeDetermineCompilerABI_C.bin +0 -0
- package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CMakeDetermineCompilerABI_CXX.bin +0 -0
- package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CMakeSystem.cmake +15 -0
- package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CompilerIdC/CMakeCCompilerId.c +905 -0
- package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CompilerIdC/a.out +0 -0
- package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CompilerIdC/apple-sdk.c +1 -0
- package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CompilerIdCXX/CMakeCXXCompilerId.cpp +920 -0
- package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CompilerIdCXX/a.out +0 -0
- package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CompilerIdCXX/apple-sdk.cpp +1 -0
- package/prebuilds/darwin-arm64/CMakeFiles/CMakeConfigureLog.yaml +531 -0
- package/prebuilds/darwin-arm64/CMakeFiles/InstallScripts.json +7 -0
- package/prebuilds/darwin-arm64/CMakeFiles/TargetDirectories.txt +3 -0
- package/prebuilds/darwin-arm64/CMakeFiles/cmake.check_cache +1 -0
- package/prebuilds/darwin-arm64/CMakeFiles/data_bridge_node.dir/Users/satyamtiwary/Documents/Python-Things/data-bridge/src/protocol/crc16.cpp.o +0 -0
- package/prebuilds/darwin-arm64/CMakeFiles/data_bridge_node.dir/Users/satyamtiwary/Documents/Python-Things/data-bridge/src/transport/platform/linux/linux_serial.cpp.o +0 -0
- package/prebuilds/darwin-arm64/CMakeFiles/data_bridge_node.dir/src/addon.cpp.o +0 -0
- package/prebuilds/darwin-arm64/CMakeFiles/data_bridge_node.dir/src/serial_wrapper.cpp.o +0 -0
- package/prebuilds/darwin-arm64/CMakeFiles/rules.ninja +64 -0
- package/prebuilds/darwin-arm64/Release/data_bridge_node.node +0 -0
- package/prebuilds/darwin-arm64/build.ninja +192 -0
- package/prebuilds/darwin-arm64/cmake_install.cmake +61 -0
- package/src/addon.cpp +219 -0
- package/src/serial_wrapper.cpp +8 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// lib/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
DataBridge: () => DataBridge,
|
|
34
|
+
ResilientDataBridge: () => ResilientDataBridge,
|
|
35
|
+
default: () => index_default
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(index_exports);
|
|
38
|
+
var import_events2 = require("events");
|
|
39
|
+
var import_path = __toESM(require("path"));
|
|
40
|
+
|
|
41
|
+
// lib/resilient.ts
|
|
42
|
+
var import_events = require("events");
|
|
43
|
+
var ResilientDataBridge = class _ResilientDataBridge extends import_events.EventEmitter {
|
|
44
|
+
bridge = null;
|
|
45
|
+
port;
|
|
46
|
+
options;
|
|
47
|
+
messageQueue = [];
|
|
48
|
+
isConnected = false;
|
|
49
|
+
isReconnecting = false;
|
|
50
|
+
shouldReconnect = true;
|
|
51
|
+
reconnectAttempt = 0;
|
|
52
|
+
reconnectTimeout = null;
|
|
53
|
+
constructor(port, options) {
|
|
54
|
+
super();
|
|
55
|
+
this.port = port;
|
|
56
|
+
this.options = {
|
|
57
|
+
baudRate: options.baudRate ?? 115200,
|
|
58
|
+
reconnect: options.reconnect ?? true,
|
|
59
|
+
reconnectDelay: options.reconnectDelay ?? 1e3,
|
|
60
|
+
maxReconnectDelay: options.maxReconnectDelay ?? 3e4,
|
|
61
|
+
maxQueueSize: options.maxQueueSize ?? 1e3
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Open a resilient serial connection.
|
|
66
|
+
*
|
|
67
|
+
* @param port - Serial port path
|
|
68
|
+
* @param options - Configuration including reconnection settings
|
|
69
|
+
*/
|
|
70
|
+
static async open(port, options = {}) {
|
|
71
|
+
const instance = new _ResilientDataBridge(port, options);
|
|
72
|
+
await instance.connect();
|
|
73
|
+
return instance;
|
|
74
|
+
}
|
|
75
|
+
async connect() {
|
|
76
|
+
try {
|
|
77
|
+
this.bridge = await DataBridge.open(this.port, {
|
|
78
|
+
baudRate: this.options.baudRate
|
|
79
|
+
});
|
|
80
|
+
this.isConnected = true;
|
|
81
|
+
this.reconnectAttempt = 0;
|
|
82
|
+
this.bridge.on("data", (data) => {
|
|
83
|
+
this.emit("data", data);
|
|
84
|
+
});
|
|
85
|
+
this.bridge.on("error", (err) => {
|
|
86
|
+
this.emit("error", err);
|
|
87
|
+
this.handleDisconnect();
|
|
88
|
+
});
|
|
89
|
+
if (this.isReconnecting) {
|
|
90
|
+
this.isReconnecting = false;
|
|
91
|
+
this.emit("reconnected");
|
|
92
|
+
await this.flushQueue();
|
|
93
|
+
}
|
|
94
|
+
} catch (err) {
|
|
95
|
+
this.isConnected = false;
|
|
96
|
+
if (this.shouldReconnect && this.options.reconnect) {
|
|
97
|
+
this.scheduleReconnect();
|
|
98
|
+
} else {
|
|
99
|
+
throw err;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
handleDisconnect() {
|
|
104
|
+
if (!this.isConnected) return;
|
|
105
|
+
this.isConnected = false;
|
|
106
|
+
this.bridge = null;
|
|
107
|
+
this.emit("disconnect");
|
|
108
|
+
if (this.shouldReconnect && this.options.reconnect) {
|
|
109
|
+
this.scheduleReconnect();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
scheduleReconnect() {
|
|
113
|
+
if (this.reconnectTimeout) return;
|
|
114
|
+
this.isReconnecting = true;
|
|
115
|
+
this.reconnectAttempt++;
|
|
116
|
+
const baseDelay = this.options.reconnectDelay;
|
|
117
|
+
const maxDelay = this.options.maxReconnectDelay;
|
|
118
|
+
const delay = Math.min(
|
|
119
|
+
baseDelay * Math.pow(2, this.reconnectAttempt - 1) + Math.random() * 1e3,
|
|
120
|
+
maxDelay
|
|
121
|
+
);
|
|
122
|
+
this.emit("reconnecting", this.reconnectAttempt, delay);
|
|
123
|
+
this.reconnectTimeout = setTimeout(async () => {
|
|
124
|
+
this.reconnectTimeout = null;
|
|
125
|
+
try {
|
|
126
|
+
await this.connect();
|
|
127
|
+
} catch {
|
|
128
|
+
}
|
|
129
|
+
}, delay);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Send data with guaranteed delivery.
|
|
133
|
+
*
|
|
134
|
+
* If disconnected, the message is queued and will be sent
|
|
135
|
+
* automatically when the connection is restored.
|
|
136
|
+
*
|
|
137
|
+
* @param data - Data to send
|
|
138
|
+
* @returns Promise that resolves when data is acknowledged
|
|
139
|
+
*/
|
|
140
|
+
async send(data) {
|
|
141
|
+
const buffer = typeof data === "string" ? Buffer.from(data) : data;
|
|
142
|
+
if (this.isConnected && this.bridge) {
|
|
143
|
+
try {
|
|
144
|
+
await this.bridge.send(buffer);
|
|
145
|
+
} catch (err) {
|
|
146
|
+
return this.queueMessage(buffer);
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
return this.queueMessage(buffer);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
queueMessage(data) {
|
|
153
|
+
return new Promise((resolve, reject) => {
|
|
154
|
+
while (this.messageQueue.length >= this.options.maxQueueSize) {
|
|
155
|
+
const dropped = this.messageQueue.shift();
|
|
156
|
+
dropped?.reject(new Error("Message dropped: queue overflow"));
|
|
157
|
+
}
|
|
158
|
+
this.messageQueue.push({
|
|
159
|
+
data,
|
|
160
|
+
resolve,
|
|
161
|
+
reject,
|
|
162
|
+
timestamp: Date.now()
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
async flushQueue() {
|
|
167
|
+
while (this.messageQueue.length > 0 && this.isConnected && this.bridge) {
|
|
168
|
+
const msg = this.messageQueue.shift();
|
|
169
|
+
try {
|
|
170
|
+
await this.bridge.send(msg.data);
|
|
171
|
+
msg.resolve();
|
|
172
|
+
} catch (err) {
|
|
173
|
+
this.messageQueue.unshift(msg);
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Close the connection permanently.
|
|
180
|
+
* Rejects all pending queued messages.
|
|
181
|
+
*/
|
|
182
|
+
async close() {
|
|
183
|
+
this.shouldReconnect = false;
|
|
184
|
+
if (this.reconnectTimeout) {
|
|
185
|
+
clearTimeout(this.reconnectTimeout);
|
|
186
|
+
this.reconnectTimeout = null;
|
|
187
|
+
}
|
|
188
|
+
for (const msg of this.messageQueue) {
|
|
189
|
+
msg.reject(new Error("Connection closed"));
|
|
190
|
+
}
|
|
191
|
+
this.messageQueue = [];
|
|
192
|
+
if (this.bridge) {
|
|
193
|
+
await this.bridge.close();
|
|
194
|
+
this.bridge = null;
|
|
195
|
+
}
|
|
196
|
+
this.isConnected = false;
|
|
197
|
+
this.emit("close");
|
|
198
|
+
}
|
|
199
|
+
/** Current connection state */
|
|
200
|
+
get connected() {
|
|
201
|
+
return this.isConnected;
|
|
202
|
+
}
|
|
203
|
+
/** Number of messages waiting in queue */
|
|
204
|
+
get queueLength() {
|
|
205
|
+
return this.messageQueue.length;
|
|
206
|
+
}
|
|
207
|
+
// Type-safe event emitter
|
|
208
|
+
on(event, listener) {
|
|
209
|
+
return super.on(event, listener);
|
|
210
|
+
}
|
|
211
|
+
once(event, listener) {
|
|
212
|
+
return super.once(event, listener);
|
|
213
|
+
}
|
|
214
|
+
emit(event, ...args) {
|
|
215
|
+
return super.emit(event, ...args);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// lib/index.ts
|
|
220
|
+
var addon;
|
|
221
|
+
function loadAddon() {
|
|
222
|
+
const possiblePaths = [
|
|
223
|
+
// Prebuilt binaries
|
|
224
|
+
`../prebuilds/${process.platform}-${process.arch}/data_bridge_node.node`,
|
|
225
|
+
// Local build
|
|
226
|
+
"../build/Release/data_bridge_node.node"
|
|
227
|
+
];
|
|
228
|
+
for (const p of possiblePaths) {
|
|
229
|
+
try {
|
|
230
|
+
return require(import_path.default.join(__dirname, p));
|
|
231
|
+
} catch {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
throw new Error("Failed to load native addon. Run `npm run build` first.");
|
|
236
|
+
}
|
|
237
|
+
addon = loadAddon();
|
|
238
|
+
var DataBridge = class _DataBridge extends import_events2.EventEmitter {
|
|
239
|
+
native;
|
|
240
|
+
_isOpen = false;
|
|
241
|
+
constructor() {
|
|
242
|
+
super();
|
|
243
|
+
this.native = new addon.DataBridge();
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Open a serial port with guaranteed reliable communication.
|
|
247
|
+
*
|
|
248
|
+
* @param port - Serial port path (e.g., '/dev/ttyUSB0' or 'COM3')
|
|
249
|
+
* @param options - Configuration options
|
|
250
|
+
* @returns Promise resolving to a DataBridge instance
|
|
251
|
+
*/
|
|
252
|
+
static async open(port, options = {}) {
|
|
253
|
+
const instance = new _DataBridge();
|
|
254
|
+
const baud = options.baudRate ?? 115200;
|
|
255
|
+
const onData = (data) => {
|
|
256
|
+
instance.emit("data", data);
|
|
257
|
+
};
|
|
258
|
+
try {
|
|
259
|
+
await instance.native.open(port, baud, onData);
|
|
260
|
+
instance._isOpen = true;
|
|
261
|
+
return instance;
|
|
262
|
+
} catch (err) {
|
|
263
|
+
throw new Error(`Failed to open ${port}: ${err instanceof Error ? err.message : err}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Send data with guaranteed delivery.
|
|
268
|
+
*
|
|
269
|
+
* The data will be fragmented if necessary, checksummed, and
|
|
270
|
+
* retransmitted until acknowledged by the receiver.
|
|
271
|
+
*
|
|
272
|
+
* @param data - Data to send (Buffer or string)
|
|
273
|
+
* @returns Promise resolving when data is acknowledged
|
|
274
|
+
*/
|
|
275
|
+
async send(data) {
|
|
276
|
+
if (!this._isOpen) {
|
|
277
|
+
throw new Error("Port not open");
|
|
278
|
+
}
|
|
279
|
+
const buffer = typeof data === "string" ? Buffer.from(data) : data;
|
|
280
|
+
await this.native.send(buffer);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Close the serial port.
|
|
284
|
+
*/
|
|
285
|
+
async close() {
|
|
286
|
+
if (this._isOpen) {
|
|
287
|
+
this.native.close();
|
|
288
|
+
this._isOpen = false;
|
|
289
|
+
this.emit("close");
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Check if the port is currently open.
|
|
294
|
+
*/
|
|
295
|
+
get isOpen() {
|
|
296
|
+
return this._isOpen && this.native.isOpen();
|
|
297
|
+
}
|
|
298
|
+
// Type-safe event emitter methods
|
|
299
|
+
on(event, listener) {
|
|
300
|
+
return super.on(event, listener);
|
|
301
|
+
}
|
|
302
|
+
once(event, listener) {
|
|
303
|
+
return super.once(event, listener);
|
|
304
|
+
}
|
|
305
|
+
emit(event, ...args) {
|
|
306
|
+
return super.emit(event, ...args);
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
var index_default = DataBridge;
|
|
310
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
311
|
+
0 && (module.exports = {
|
|
312
|
+
DataBridge,
|
|
313
|
+
ResilientDataBridge
|
|
314
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// lib/index.ts
|
|
9
|
+
import { EventEmitter as EventEmitter2 } from "events";
|
|
10
|
+
import path from "path";
|
|
11
|
+
|
|
12
|
+
// lib/resilient.ts
|
|
13
|
+
import { EventEmitter } from "events";
|
|
14
|
+
var ResilientDataBridge = class _ResilientDataBridge extends EventEmitter {
|
|
15
|
+
bridge = null;
|
|
16
|
+
port;
|
|
17
|
+
options;
|
|
18
|
+
messageQueue = [];
|
|
19
|
+
isConnected = false;
|
|
20
|
+
isReconnecting = false;
|
|
21
|
+
shouldReconnect = true;
|
|
22
|
+
reconnectAttempt = 0;
|
|
23
|
+
reconnectTimeout = null;
|
|
24
|
+
constructor(port, options) {
|
|
25
|
+
super();
|
|
26
|
+
this.port = port;
|
|
27
|
+
this.options = {
|
|
28
|
+
baudRate: options.baudRate ?? 115200,
|
|
29
|
+
reconnect: options.reconnect ?? true,
|
|
30
|
+
reconnectDelay: options.reconnectDelay ?? 1e3,
|
|
31
|
+
maxReconnectDelay: options.maxReconnectDelay ?? 3e4,
|
|
32
|
+
maxQueueSize: options.maxQueueSize ?? 1e3
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Open a resilient serial connection.
|
|
37
|
+
*
|
|
38
|
+
* @param port - Serial port path
|
|
39
|
+
* @param options - Configuration including reconnection settings
|
|
40
|
+
*/
|
|
41
|
+
static async open(port, options = {}) {
|
|
42
|
+
const instance = new _ResilientDataBridge(port, options);
|
|
43
|
+
await instance.connect();
|
|
44
|
+
return instance;
|
|
45
|
+
}
|
|
46
|
+
async connect() {
|
|
47
|
+
try {
|
|
48
|
+
this.bridge = await DataBridge.open(this.port, {
|
|
49
|
+
baudRate: this.options.baudRate
|
|
50
|
+
});
|
|
51
|
+
this.isConnected = true;
|
|
52
|
+
this.reconnectAttempt = 0;
|
|
53
|
+
this.bridge.on("data", (data) => {
|
|
54
|
+
this.emit("data", data);
|
|
55
|
+
});
|
|
56
|
+
this.bridge.on("error", (err) => {
|
|
57
|
+
this.emit("error", err);
|
|
58
|
+
this.handleDisconnect();
|
|
59
|
+
});
|
|
60
|
+
if (this.isReconnecting) {
|
|
61
|
+
this.isReconnecting = false;
|
|
62
|
+
this.emit("reconnected");
|
|
63
|
+
await this.flushQueue();
|
|
64
|
+
}
|
|
65
|
+
} catch (err) {
|
|
66
|
+
this.isConnected = false;
|
|
67
|
+
if (this.shouldReconnect && this.options.reconnect) {
|
|
68
|
+
this.scheduleReconnect();
|
|
69
|
+
} else {
|
|
70
|
+
throw err;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
handleDisconnect() {
|
|
75
|
+
if (!this.isConnected) return;
|
|
76
|
+
this.isConnected = false;
|
|
77
|
+
this.bridge = null;
|
|
78
|
+
this.emit("disconnect");
|
|
79
|
+
if (this.shouldReconnect && this.options.reconnect) {
|
|
80
|
+
this.scheduleReconnect();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
scheduleReconnect() {
|
|
84
|
+
if (this.reconnectTimeout) return;
|
|
85
|
+
this.isReconnecting = true;
|
|
86
|
+
this.reconnectAttempt++;
|
|
87
|
+
const baseDelay = this.options.reconnectDelay;
|
|
88
|
+
const maxDelay = this.options.maxReconnectDelay;
|
|
89
|
+
const delay = Math.min(
|
|
90
|
+
baseDelay * Math.pow(2, this.reconnectAttempt - 1) + Math.random() * 1e3,
|
|
91
|
+
maxDelay
|
|
92
|
+
);
|
|
93
|
+
this.emit("reconnecting", this.reconnectAttempt, delay);
|
|
94
|
+
this.reconnectTimeout = setTimeout(async () => {
|
|
95
|
+
this.reconnectTimeout = null;
|
|
96
|
+
try {
|
|
97
|
+
await this.connect();
|
|
98
|
+
} catch {
|
|
99
|
+
}
|
|
100
|
+
}, delay);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Send data with guaranteed delivery.
|
|
104
|
+
*
|
|
105
|
+
* If disconnected, the message is queued and will be sent
|
|
106
|
+
* automatically when the connection is restored.
|
|
107
|
+
*
|
|
108
|
+
* @param data - Data to send
|
|
109
|
+
* @returns Promise that resolves when data is acknowledged
|
|
110
|
+
*/
|
|
111
|
+
async send(data) {
|
|
112
|
+
const buffer = typeof data === "string" ? Buffer.from(data) : data;
|
|
113
|
+
if (this.isConnected && this.bridge) {
|
|
114
|
+
try {
|
|
115
|
+
await this.bridge.send(buffer);
|
|
116
|
+
} catch (err) {
|
|
117
|
+
return this.queueMessage(buffer);
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
return this.queueMessage(buffer);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
queueMessage(data) {
|
|
124
|
+
return new Promise((resolve, reject) => {
|
|
125
|
+
while (this.messageQueue.length >= this.options.maxQueueSize) {
|
|
126
|
+
const dropped = this.messageQueue.shift();
|
|
127
|
+
dropped?.reject(new Error("Message dropped: queue overflow"));
|
|
128
|
+
}
|
|
129
|
+
this.messageQueue.push({
|
|
130
|
+
data,
|
|
131
|
+
resolve,
|
|
132
|
+
reject,
|
|
133
|
+
timestamp: Date.now()
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
async flushQueue() {
|
|
138
|
+
while (this.messageQueue.length > 0 && this.isConnected && this.bridge) {
|
|
139
|
+
const msg = this.messageQueue.shift();
|
|
140
|
+
try {
|
|
141
|
+
await this.bridge.send(msg.data);
|
|
142
|
+
msg.resolve();
|
|
143
|
+
} catch (err) {
|
|
144
|
+
this.messageQueue.unshift(msg);
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Close the connection permanently.
|
|
151
|
+
* Rejects all pending queued messages.
|
|
152
|
+
*/
|
|
153
|
+
async close() {
|
|
154
|
+
this.shouldReconnect = false;
|
|
155
|
+
if (this.reconnectTimeout) {
|
|
156
|
+
clearTimeout(this.reconnectTimeout);
|
|
157
|
+
this.reconnectTimeout = null;
|
|
158
|
+
}
|
|
159
|
+
for (const msg of this.messageQueue) {
|
|
160
|
+
msg.reject(new Error("Connection closed"));
|
|
161
|
+
}
|
|
162
|
+
this.messageQueue = [];
|
|
163
|
+
if (this.bridge) {
|
|
164
|
+
await this.bridge.close();
|
|
165
|
+
this.bridge = null;
|
|
166
|
+
}
|
|
167
|
+
this.isConnected = false;
|
|
168
|
+
this.emit("close");
|
|
169
|
+
}
|
|
170
|
+
/** Current connection state */
|
|
171
|
+
get connected() {
|
|
172
|
+
return this.isConnected;
|
|
173
|
+
}
|
|
174
|
+
/** Number of messages waiting in queue */
|
|
175
|
+
get queueLength() {
|
|
176
|
+
return this.messageQueue.length;
|
|
177
|
+
}
|
|
178
|
+
// Type-safe event emitter
|
|
179
|
+
on(event, listener) {
|
|
180
|
+
return super.on(event, listener);
|
|
181
|
+
}
|
|
182
|
+
once(event, listener) {
|
|
183
|
+
return super.once(event, listener);
|
|
184
|
+
}
|
|
185
|
+
emit(event, ...args) {
|
|
186
|
+
return super.emit(event, ...args);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// lib/index.ts
|
|
191
|
+
var addon;
|
|
192
|
+
function loadAddon() {
|
|
193
|
+
const possiblePaths = [
|
|
194
|
+
// Prebuilt binaries
|
|
195
|
+
`../prebuilds/${process.platform}-${process.arch}/data_bridge_node.node`,
|
|
196
|
+
// Local build
|
|
197
|
+
"../build/Release/data_bridge_node.node"
|
|
198
|
+
];
|
|
199
|
+
for (const p of possiblePaths) {
|
|
200
|
+
try {
|
|
201
|
+
return __require(path.join(__dirname, p));
|
|
202
|
+
} catch {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
throw new Error("Failed to load native addon. Run `npm run build` first.");
|
|
207
|
+
}
|
|
208
|
+
addon = loadAddon();
|
|
209
|
+
var DataBridge = class _DataBridge extends EventEmitter2 {
|
|
210
|
+
native;
|
|
211
|
+
_isOpen = false;
|
|
212
|
+
constructor() {
|
|
213
|
+
super();
|
|
214
|
+
this.native = new addon.DataBridge();
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Open a serial port with guaranteed reliable communication.
|
|
218
|
+
*
|
|
219
|
+
* @param port - Serial port path (e.g., '/dev/ttyUSB0' or 'COM3')
|
|
220
|
+
* @param options - Configuration options
|
|
221
|
+
* @returns Promise resolving to a DataBridge instance
|
|
222
|
+
*/
|
|
223
|
+
static async open(port, options = {}) {
|
|
224
|
+
const instance = new _DataBridge();
|
|
225
|
+
const baud = options.baudRate ?? 115200;
|
|
226
|
+
const onData = (data) => {
|
|
227
|
+
instance.emit("data", data);
|
|
228
|
+
};
|
|
229
|
+
try {
|
|
230
|
+
await instance.native.open(port, baud, onData);
|
|
231
|
+
instance._isOpen = true;
|
|
232
|
+
return instance;
|
|
233
|
+
} catch (err) {
|
|
234
|
+
throw new Error(`Failed to open ${port}: ${err instanceof Error ? err.message : err}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Send data with guaranteed delivery.
|
|
239
|
+
*
|
|
240
|
+
* The data will be fragmented if necessary, checksummed, and
|
|
241
|
+
* retransmitted until acknowledged by the receiver.
|
|
242
|
+
*
|
|
243
|
+
* @param data - Data to send (Buffer or string)
|
|
244
|
+
* @returns Promise resolving when data is acknowledged
|
|
245
|
+
*/
|
|
246
|
+
async send(data) {
|
|
247
|
+
if (!this._isOpen) {
|
|
248
|
+
throw new Error("Port not open");
|
|
249
|
+
}
|
|
250
|
+
const buffer = typeof data === "string" ? Buffer.from(data) : data;
|
|
251
|
+
await this.native.send(buffer);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Close the serial port.
|
|
255
|
+
*/
|
|
256
|
+
async close() {
|
|
257
|
+
if (this._isOpen) {
|
|
258
|
+
this.native.close();
|
|
259
|
+
this._isOpen = false;
|
|
260
|
+
this.emit("close");
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Check if the port is currently open.
|
|
265
|
+
*/
|
|
266
|
+
get isOpen() {
|
|
267
|
+
return this._isOpen && this.native.isOpen();
|
|
268
|
+
}
|
|
269
|
+
// Type-safe event emitter methods
|
|
270
|
+
on(event, listener) {
|
|
271
|
+
return super.on(event, listener);
|
|
272
|
+
}
|
|
273
|
+
once(event, listener) {
|
|
274
|
+
return super.once(event, listener);
|
|
275
|
+
}
|
|
276
|
+
emit(event, ...args) {
|
|
277
|
+
return super.emit(event, ...args);
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
var index_default = DataBridge;
|
|
281
|
+
export {
|
|
282
|
+
DataBridge,
|
|
283
|
+
ResilientDataBridge,
|
|
284
|
+
index_default as default
|
|
285
|
+
};
|