@gurezo/web-serial-rxjs 0.1.0 → 0.1.1
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/browser/browser-detection.d.ts +104 -0
- package/dist/browser/browser-detection.d.ts.map +1 -0
- package/dist/browser/browser-support.d.ts +57 -0
- package/dist/browser/browser-support.d.ts.map +1 -0
- package/dist/client/index.d.ts +250 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/serial-client.d.ts +98 -0
- package/dist/client/serial-client.d.ts.map +1 -0
- package/dist/errors/serial-error-code.d.ts +132 -0
- package/dist/errors/serial-error-code.d.ts.map +1 -0
- package/dist/errors/serial-error.d.ts +72 -0
- package/dist/errors/serial-error.d.ts.map +1 -0
- package/dist/filters/build-request-options.d.ts +42 -0
- package/dist/filters/build-request-options.d.ts.map +1 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +595 -0
- package/dist/index.mjs +596 -0
- package/dist/index.mjs.map +7 -0
- package/dist/io/observable-to-writable.d.ts +65 -0
- package/dist/io/observable-to-writable.d.ts.map +1 -0
- package/dist/io/readable-to-observable.d.ts +44 -0
- package/dist/io/readable-to-observable.d.ts.map +1 -0
- package/dist/lib/web-serial-rxjs.d.ts +7 -0
- package/dist/lib/web-serial-rxjs.d.ts.map +1 -0
- package/dist/types/options.d.ts +107 -0
- package/dist/types/options.d.ts.map +1 -0
- package/package.json +4 -3
package/dist/index.js
ADDED
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
// packages/web-serial-rxjs/src/client/serial-client.ts
|
|
2
|
+
import { Observable as Observable2, defer, switchMap } from "rxjs";
|
|
3
|
+
|
|
4
|
+
// packages/web-serial-rxjs/src/errors/serial-error-code.ts
|
|
5
|
+
var SerialErrorCode = /* @__PURE__ */ ((SerialErrorCode2) => {
|
|
6
|
+
SerialErrorCode2["BROWSER_NOT_SUPPORTED"] = "BROWSER_NOT_SUPPORTED";
|
|
7
|
+
SerialErrorCode2["PORT_NOT_AVAILABLE"] = "PORT_NOT_AVAILABLE";
|
|
8
|
+
SerialErrorCode2["PORT_OPEN_FAILED"] = "PORT_OPEN_FAILED";
|
|
9
|
+
SerialErrorCode2["PORT_ALREADY_OPEN"] = "PORT_ALREADY_OPEN";
|
|
10
|
+
SerialErrorCode2["PORT_NOT_OPEN"] = "PORT_NOT_OPEN";
|
|
11
|
+
SerialErrorCode2["READ_FAILED"] = "READ_FAILED";
|
|
12
|
+
SerialErrorCode2["WRITE_FAILED"] = "WRITE_FAILED";
|
|
13
|
+
SerialErrorCode2["CONNECTION_LOST"] = "CONNECTION_LOST";
|
|
14
|
+
SerialErrorCode2["INVALID_FILTER_OPTIONS"] = "INVALID_FILTER_OPTIONS";
|
|
15
|
+
SerialErrorCode2["OPERATION_CANCELLED"] = "OPERATION_CANCELLED";
|
|
16
|
+
SerialErrorCode2["UNKNOWN"] = "UNKNOWN";
|
|
17
|
+
return SerialErrorCode2;
|
|
18
|
+
})(SerialErrorCode || {});
|
|
19
|
+
|
|
20
|
+
// packages/web-serial-rxjs/src/errors/serial-error.ts
|
|
21
|
+
var SerialError = class _SerialError extends Error {
|
|
22
|
+
/**
|
|
23
|
+
* Creates a new SerialError instance.
|
|
24
|
+
*
|
|
25
|
+
* @param code - The error code identifying the type of error
|
|
26
|
+
* @param message - A human-readable error message
|
|
27
|
+
* @param originalError - The original error that caused this SerialError, if any
|
|
28
|
+
*/
|
|
29
|
+
constructor(code, message, originalError) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.name = "SerialError";
|
|
32
|
+
this.code = code;
|
|
33
|
+
this.originalError = originalError;
|
|
34
|
+
if (Error.captureStackTrace) {
|
|
35
|
+
Error.captureStackTrace(this, _SerialError);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Check if the error matches a specific error code.
|
|
40
|
+
*
|
|
41
|
+
* This is a convenience method for checking the error code without directly
|
|
42
|
+
* comparing the code property.
|
|
43
|
+
*
|
|
44
|
+
* @param code - The error code to check against
|
|
45
|
+
* @returns `true` if this error's code matches the provided code, `false` otherwise
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* if (error.is(SerialErrorCode.PORT_NOT_OPEN)) {
|
|
50
|
+
* // Handle port not open error
|
|
51
|
+
* }
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
is(code) {
|
|
55
|
+
return this.code === code;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// packages/web-serial-rxjs/src/browser/browser-detection.ts
|
|
60
|
+
var BrowserType = /* @__PURE__ */ ((BrowserType2) => {
|
|
61
|
+
BrowserType2["CHROME"] = "chrome";
|
|
62
|
+
BrowserType2["EDGE"] = "edge";
|
|
63
|
+
BrowserType2["OPERA"] = "opera";
|
|
64
|
+
BrowserType2["UNKNOWN"] = "unknown";
|
|
65
|
+
return BrowserType2;
|
|
66
|
+
})(BrowserType || {});
|
|
67
|
+
function hasWebSerialSupport() {
|
|
68
|
+
return typeof navigator !== "undefined" && "serial" in navigator && navigator.serial !== void 0 && navigator.serial !== null;
|
|
69
|
+
}
|
|
70
|
+
function detectBrowserType() {
|
|
71
|
+
if (typeof navigator === "undefined" || !navigator.userAgent) {
|
|
72
|
+
return "unknown" /* UNKNOWN */;
|
|
73
|
+
}
|
|
74
|
+
const ua = navigator.userAgent.toLowerCase();
|
|
75
|
+
if (ua.includes("edg/")) {
|
|
76
|
+
return "edge" /* EDGE */;
|
|
77
|
+
}
|
|
78
|
+
if (ua.includes("opr/") || ua.includes("opera/")) {
|
|
79
|
+
return "opera" /* OPERA */;
|
|
80
|
+
}
|
|
81
|
+
if (ua.includes("chrome/")) {
|
|
82
|
+
return "chrome" /* CHROME */;
|
|
83
|
+
}
|
|
84
|
+
return "unknown" /* UNKNOWN */;
|
|
85
|
+
}
|
|
86
|
+
function isChromiumBased() {
|
|
87
|
+
const browserType = detectBrowserType();
|
|
88
|
+
return browserType === "chrome" /* CHROME */ || browserType === "edge" /* EDGE */ || browserType === "opera" /* OPERA */;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// packages/web-serial-rxjs/src/browser/browser-support.ts
|
|
92
|
+
function checkBrowserSupport() {
|
|
93
|
+
if (!hasWebSerialSupport()) {
|
|
94
|
+
const browserType = detectBrowserType();
|
|
95
|
+
const browserName = browserType === "unknown" /* UNKNOWN */ ? "your browser" : browserType.toUpperCase();
|
|
96
|
+
throw new SerialError(
|
|
97
|
+
"BROWSER_NOT_SUPPORTED" /* BROWSER_NOT_SUPPORTED */,
|
|
98
|
+
`Web Serial API is not supported in ${browserName}. Please use a Chromium-based browser (Chrome, Edge, or Opera).`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function isBrowserSupported() {
|
|
103
|
+
return hasWebSerialSupport();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// packages/web-serial-rxjs/src/filters/build-request-options.ts
|
|
107
|
+
function buildRequestOptions(options) {
|
|
108
|
+
if (!options || !options.filters || options.filters.length === 0) {
|
|
109
|
+
return void 0;
|
|
110
|
+
}
|
|
111
|
+
for (const filter of options.filters) {
|
|
112
|
+
if (!filter.usbVendorId && !filter.usbProductId) {
|
|
113
|
+
throw new SerialError(
|
|
114
|
+
"INVALID_FILTER_OPTIONS" /* INVALID_FILTER_OPTIONS */,
|
|
115
|
+
"Filter must have at least usbVendorId or usbProductId"
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
if (filter.usbVendorId !== void 0) {
|
|
119
|
+
if (!Number.isInteger(filter.usbVendorId) || filter.usbVendorId < 0 || filter.usbVendorId > 65535) {
|
|
120
|
+
throw new SerialError(
|
|
121
|
+
"INVALID_FILTER_OPTIONS" /* INVALID_FILTER_OPTIONS */,
|
|
122
|
+
`Invalid usbVendorId: ${filter.usbVendorId}. Must be an integer between 0 and 65535.`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (filter.usbProductId !== void 0) {
|
|
127
|
+
if (!Number.isInteger(filter.usbProductId) || filter.usbProductId < 0 || filter.usbProductId > 65535) {
|
|
128
|
+
throw new SerialError(
|
|
129
|
+
"INVALID_FILTER_OPTIONS" /* INVALID_FILTER_OPTIONS */,
|
|
130
|
+
`Invalid usbProductId: ${filter.usbProductId}. Must be an integer between 0 and 65535.`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
filters: options.filters
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// packages/web-serial-rxjs/src/io/observable-to-writable.ts
|
|
141
|
+
function observableToWritable(observable) {
|
|
142
|
+
let writer = null;
|
|
143
|
+
let subscription = null;
|
|
144
|
+
let stream = null;
|
|
145
|
+
stream = new WritableStream({
|
|
146
|
+
async start() {
|
|
147
|
+
if (!stream) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
writer = stream.getWriter();
|
|
151
|
+
subscription = observable.subscribe({
|
|
152
|
+
next: async (chunk) => {
|
|
153
|
+
if (writer) {
|
|
154
|
+
try {
|
|
155
|
+
await writer.write(chunk);
|
|
156
|
+
} catch (error) {
|
|
157
|
+
subscription?.unsubscribe();
|
|
158
|
+
if (writer) {
|
|
159
|
+
writer.releaseLock();
|
|
160
|
+
}
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
error: async (error) => {
|
|
166
|
+
if (writer) {
|
|
167
|
+
try {
|
|
168
|
+
await writer.abort(error);
|
|
169
|
+
} catch {
|
|
170
|
+
} finally {
|
|
171
|
+
writer.releaseLock();
|
|
172
|
+
writer = null;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
complete: async () => {
|
|
177
|
+
if (writer) {
|
|
178
|
+
try {
|
|
179
|
+
await writer.close();
|
|
180
|
+
} catch {
|
|
181
|
+
} finally {
|
|
182
|
+
writer.releaseLock();
|
|
183
|
+
writer = null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
},
|
|
189
|
+
abort(reason) {
|
|
190
|
+
if (subscription) {
|
|
191
|
+
subscription.unsubscribe();
|
|
192
|
+
subscription = null;
|
|
193
|
+
}
|
|
194
|
+
if (writer) {
|
|
195
|
+
writer.abort(reason).catch(() => {
|
|
196
|
+
});
|
|
197
|
+
writer.releaseLock();
|
|
198
|
+
writer = null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
return stream;
|
|
203
|
+
}
|
|
204
|
+
function subscribeToWritable(observable, stream) {
|
|
205
|
+
const writer = stream.getWriter();
|
|
206
|
+
const errorHandler = async (error) => {
|
|
207
|
+
try {
|
|
208
|
+
await writer.abort(error);
|
|
209
|
+
} catch {
|
|
210
|
+
} finally {
|
|
211
|
+
writer.releaseLock();
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
const subscription = observable.subscribe({
|
|
215
|
+
next: async (chunk) => {
|
|
216
|
+
try {
|
|
217
|
+
await writer.write(chunk);
|
|
218
|
+
} catch (error) {
|
|
219
|
+
subscription.unsubscribe();
|
|
220
|
+
writer.releaseLock();
|
|
221
|
+
const serialError = new SerialError(
|
|
222
|
+
"WRITE_FAILED" /* WRITE_FAILED */,
|
|
223
|
+
`Failed to write to stream: ${error instanceof Error ? error.message : String(error)}`,
|
|
224
|
+
error instanceof Error ? error : new Error(String(error))
|
|
225
|
+
);
|
|
226
|
+
await errorHandler(serialError);
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
error: errorHandler,
|
|
230
|
+
complete: async () => {
|
|
231
|
+
try {
|
|
232
|
+
await writer.close();
|
|
233
|
+
} catch {
|
|
234
|
+
} finally {
|
|
235
|
+
writer.releaseLock();
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
return {
|
|
240
|
+
unsubscribe: () => {
|
|
241
|
+
subscription.unsubscribe();
|
|
242
|
+
writer.releaseLock();
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// packages/web-serial-rxjs/src/io/readable-to-observable.ts
|
|
248
|
+
import { Observable } from "rxjs";
|
|
249
|
+
function readableToObservable(stream) {
|
|
250
|
+
return new Observable((subscriber) => {
|
|
251
|
+
const reader = stream.getReader();
|
|
252
|
+
const pump = async () => {
|
|
253
|
+
try {
|
|
254
|
+
while (true) {
|
|
255
|
+
const { done, value } = await reader.read();
|
|
256
|
+
if (done) {
|
|
257
|
+
subscriber.complete();
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
if (value) {
|
|
261
|
+
subscriber.next(value);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
} catch (error) {
|
|
265
|
+
if (error instanceof Error) {
|
|
266
|
+
subscriber.error(
|
|
267
|
+
new SerialError(
|
|
268
|
+
"READ_FAILED" /* READ_FAILED */,
|
|
269
|
+
`Failed to read from stream: ${error.message}`,
|
|
270
|
+
error
|
|
271
|
+
)
|
|
272
|
+
);
|
|
273
|
+
} else {
|
|
274
|
+
subscriber.error(
|
|
275
|
+
new SerialError(
|
|
276
|
+
"READ_FAILED" /* READ_FAILED */,
|
|
277
|
+
"Failed to read from stream: Unknown error",
|
|
278
|
+
error
|
|
279
|
+
)
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
} finally {
|
|
283
|
+
reader.releaseLock();
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
pump().catch((error) => {
|
|
287
|
+
if (!subscriber.closed) {
|
|
288
|
+
subscriber.error(error);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
return () => {
|
|
292
|
+
reader.releaseLock();
|
|
293
|
+
};
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// packages/web-serial-rxjs/src/types/options.ts
|
|
298
|
+
var DEFAULT_SERIAL_CLIENT_OPTIONS = {
|
|
299
|
+
baudRate: 9600,
|
|
300
|
+
dataBits: 8,
|
|
301
|
+
stopBits: 1,
|
|
302
|
+
parity: "none",
|
|
303
|
+
bufferSize: 255,
|
|
304
|
+
flowControl: "none",
|
|
305
|
+
filters: void 0
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// packages/web-serial-rxjs/src/client/serial-client.ts
|
|
309
|
+
var SerialClientImpl = class {
|
|
310
|
+
/**
|
|
311
|
+
* Creates a new SerialClientImpl instance.
|
|
312
|
+
*
|
|
313
|
+
* @param options - Optional configuration options for the serial port connection
|
|
314
|
+
* @throws {@link SerialError} with code {@link SerialErrorCode.BROWSER_NOT_SUPPORTED} if the browser doesn't support Web Serial API
|
|
315
|
+
* @internal
|
|
316
|
+
*/
|
|
317
|
+
constructor(options) {
|
|
318
|
+
/** @internal */
|
|
319
|
+
this.port = null;
|
|
320
|
+
/** @internal */
|
|
321
|
+
this.isOpen = false;
|
|
322
|
+
/** @internal */
|
|
323
|
+
this.readSubscription = null;
|
|
324
|
+
/** @internal */
|
|
325
|
+
this.writeSubscription = null;
|
|
326
|
+
checkBrowserSupport();
|
|
327
|
+
this.options = {
|
|
328
|
+
...DEFAULT_SERIAL_CLIENT_OPTIONS,
|
|
329
|
+
...options,
|
|
330
|
+
filters: options?.filters
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Request a serial port from the user.
|
|
335
|
+
*
|
|
336
|
+
* @returns Observable that emits the selected SerialPort
|
|
337
|
+
* @internal
|
|
338
|
+
*/
|
|
339
|
+
requestPort() {
|
|
340
|
+
return defer(() => {
|
|
341
|
+
checkBrowserSupport();
|
|
342
|
+
return navigator.serial.requestPort(buildRequestOptions(this.options)).catch((error) => {
|
|
343
|
+
if (error instanceof DOMException && error.name === "NotFoundError") {
|
|
344
|
+
throw new SerialError(
|
|
345
|
+
"OPERATION_CANCELLED" /* OPERATION_CANCELLED */,
|
|
346
|
+
"Port selection was cancelled by the user",
|
|
347
|
+
error
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
throw new SerialError(
|
|
351
|
+
"PORT_NOT_AVAILABLE" /* PORT_NOT_AVAILABLE */,
|
|
352
|
+
`Failed to request port: ${error instanceof Error ? error.message : String(error)}`,
|
|
353
|
+
error instanceof Error ? error : new Error(String(error))
|
|
354
|
+
);
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Get available serial ports.
|
|
360
|
+
*
|
|
361
|
+
* @returns Observable that emits an array of available SerialPorts
|
|
362
|
+
* @internal
|
|
363
|
+
*/
|
|
364
|
+
getPorts() {
|
|
365
|
+
return defer(() => {
|
|
366
|
+
checkBrowserSupport();
|
|
367
|
+
return navigator.serial.getPorts().catch((error) => {
|
|
368
|
+
throw new SerialError(
|
|
369
|
+
"PORT_NOT_AVAILABLE" /* PORT_NOT_AVAILABLE */,
|
|
370
|
+
`Failed to get ports: ${error instanceof Error ? error.message : String(error)}`,
|
|
371
|
+
error instanceof Error ? error : new Error(String(error))
|
|
372
|
+
);
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Connect to a serial port.
|
|
378
|
+
*
|
|
379
|
+
* @param port - Optional SerialPort to connect to. If not provided, will request one.
|
|
380
|
+
* @returns Observable that completes when the port is opened
|
|
381
|
+
* @internal
|
|
382
|
+
*/
|
|
383
|
+
connect(port) {
|
|
384
|
+
checkBrowserSupport();
|
|
385
|
+
if (this.isOpen) {
|
|
386
|
+
return new Observable2((subscriber) => {
|
|
387
|
+
subscriber.error(
|
|
388
|
+
new SerialError(
|
|
389
|
+
"PORT_ALREADY_OPEN" /* PORT_ALREADY_OPEN */,
|
|
390
|
+
"Port is already open"
|
|
391
|
+
)
|
|
392
|
+
);
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
const port$ = port ? new Observable2((subscriber) => {
|
|
396
|
+
subscriber.next(port);
|
|
397
|
+
subscriber.complete();
|
|
398
|
+
}) : this.requestPort();
|
|
399
|
+
return port$.pipe(
|
|
400
|
+
switchMap((selectedPort) => {
|
|
401
|
+
return defer(() => {
|
|
402
|
+
this.port = selectedPort;
|
|
403
|
+
return this.port.open({
|
|
404
|
+
baudRate: this.options.baudRate,
|
|
405
|
+
dataBits: this.options.dataBits,
|
|
406
|
+
stopBits: this.options.stopBits,
|
|
407
|
+
parity: this.options.parity,
|
|
408
|
+
bufferSize: this.options.bufferSize,
|
|
409
|
+
flowControl: this.options.flowControl
|
|
410
|
+
}).then(() => {
|
|
411
|
+
this.isOpen = true;
|
|
412
|
+
}).catch((error) => {
|
|
413
|
+
this.port = null;
|
|
414
|
+
this.isOpen = false;
|
|
415
|
+
if (error instanceof SerialError) {
|
|
416
|
+
throw error;
|
|
417
|
+
}
|
|
418
|
+
throw new SerialError(
|
|
419
|
+
"PORT_OPEN_FAILED" /* PORT_OPEN_FAILED */,
|
|
420
|
+
`Failed to open port: ${error instanceof Error ? error.message : String(error)}`,
|
|
421
|
+
error instanceof Error ? error : new Error(String(error))
|
|
422
|
+
);
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
})
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Disconnect from the serial port.
|
|
430
|
+
*
|
|
431
|
+
* @returns Observable that completes when the port is closed
|
|
432
|
+
* @internal
|
|
433
|
+
*/
|
|
434
|
+
disconnect() {
|
|
435
|
+
return defer(() => {
|
|
436
|
+
if (!this.isOpen || !this.port) {
|
|
437
|
+
return Promise.resolve();
|
|
438
|
+
}
|
|
439
|
+
if (this.readSubscription) {
|
|
440
|
+
this.readSubscription.unsubscribe();
|
|
441
|
+
this.readSubscription = null;
|
|
442
|
+
}
|
|
443
|
+
if (this.writeSubscription) {
|
|
444
|
+
this.writeSubscription.unsubscribe();
|
|
445
|
+
this.writeSubscription = null;
|
|
446
|
+
}
|
|
447
|
+
return this.port.close().then(() => {
|
|
448
|
+
this.port = null;
|
|
449
|
+
this.isOpen = false;
|
|
450
|
+
}).catch((error) => {
|
|
451
|
+
this.port = null;
|
|
452
|
+
this.isOpen = false;
|
|
453
|
+
throw new SerialError(
|
|
454
|
+
"CONNECTION_LOST" /* CONNECTION_LOST */,
|
|
455
|
+
`Failed to close port: ${error instanceof Error ? error.message : String(error)}`,
|
|
456
|
+
error instanceof Error ? error : new Error(String(error))
|
|
457
|
+
);
|
|
458
|
+
});
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Get an Observable that emits data read from the serial port.
|
|
463
|
+
*
|
|
464
|
+
* @returns Observable that emits Uint8Array chunks
|
|
465
|
+
* @internal
|
|
466
|
+
*/
|
|
467
|
+
getReadStream() {
|
|
468
|
+
if (!this.isOpen || !this.port || !this.port.readable) {
|
|
469
|
+
throw new SerialError(
|
|
470
|
+
"PORT_NOT_OPEN" /* PORT_NOT_OPEN */,
|
|
471
|
+
"Port is not open or readable stream is not available"
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
return readableToObservable(this.port.readable);
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Write data to the serial port from an Observable.
|
|
478
|
+
*
|
|
479
|
+
* @param data$ - Observable that emits Uint8Array chunks to write
|
|
480
|
+
* @returns Observable that completes when writing is finished
|
|
481
|
+
* @internal
|
|
482
|
+
*/
|
|
483
|
+
writeStream(data$) {
|
|
484
|
+
if (!this.isOpen || !this.port || !this.port.writable) {
|
|
485
|
+
throw new SerialError(
|
|
486
|
+
"PORT_NOT_OPEN" /* PORT_NOT_OPEN */,
|
|
487
|
+
"Port is not open or writable stream is not available"
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
if (this.writeSubscription) {
|
|
491
|
+
this.writeSubscription.unsubscribe();
|
|
492
|
+
}
|
|
493
|
+
this.writeSubscription = subscribeToWritable(data$, this.port.writable);
|
|
494
|
+
return new Observable2((subscriber) => {
|
|
495
|
+
if (!this.writeSubscription) {
|
|
496
|
+
subscriber.error(
|
|
497
|
+
new SerialError(
|
|
498
|
+
"WRITE_FAILED" /* WRITE_FAILED */,
|
|
499
|
+
"Write subscription is not available"
|
|
500
|
+
)
|
|
501
|
+
);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
const originalUnsubscribe = this.writeSubscription.unsubscribe;
|
|
505
|
+
this.writeSubscription = {
|
|
506
|
+
unsubscribe: () => {
|
|
507
|
+
originalUnsubscribe();
|
|
508
|
+
subscriber.complete();
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
data$.subscribe({
|
|
512
|
+
complete: () => {
|
|
513
|
+
if (this.writeSubscription) {
|
|
514
|
+
this.writeSubscription.unsubscribe();
|
|
515
|
+
this.writeSubscription = null;
|
|
516
|
+
}
|
|
517
|
+
subscriber.complete();
|
|
518
|
+
},
|
|
519
|
+
error: (error) => {
|
|
520
|
+
if (this.writeSubscription) {
|
|
521
|
+
this.writeSubscription.unsubscribe();
|
|
522
|
+
this.writeSubscription = null;
|
|
523
|
+
}
|
|
524
|
+
subscriber.error(error);
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Write a single chunk of data to the serial port.
|
|
531
|
+
*
|
|
532
|
+
* @param data - Data to write
|
|
533
|
+
* @returns Observable that completes when the data is written
|
|
534
|
+
* @internal
|
|
535
|
+
*/
|
|
536
|
+
write(data) {
|
|
537
|
+
return defer(() => {
|
|
538
|
+
if (!this.isOpen || !this.port || !this.port.writable) {
|
|
539
|
+
throw new SerialError(
|
|
540
|
+
"PORT_NOT_OPEN" /* PORT_NOT_OPEN */,
|
|
541
|
+
"Port is not open or writable stream is not available"
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
const writer = this.port.writable.getWriter();
|
|
545
|
+
return writer.write(data).then(() => {
|
|
546
|
+
writer.releaseLock();
|
|
547
|
+
}).catch((error) => {
|
|
548
|
+
writer.releaseLock();
|
|
549
|
+
throw new SerialError(
|
|
550
|
+
"WRITE_FAILED" /* WRITE_FAILED */,
|
|
551
|
+
`Failed to write data: ${error instanceof Error ? error.message : String(error)}`,
|
|
552
|
+
error instanceof Error ? error : new Error(String(error))
|
|
553
|
+
);
|
|
554
|
+
});
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Check if the port is currently open.
|
|
559
|
+
*
|
|
560
|
+
* @returns `true` if a port is currently open, `false` otherwise
|
|
561
|
+
* @internal
|
|
562
|
+
*/
|
|
563
|
+
get connected() {
|
|
564
|
+
return this.isOpen;
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Get the current SerialPort instance.
|
|
568
|
+
*
|
|
569
|
+
* @returns The current SerialPort instance, or `null` if no port is open
|
|
570
|
+
* @internal
|
|
571
|
+
*/
|
|
572
|
+
get currentPort() {
|
|
573
|
+
return this.port;
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
// packages/web-serial-rxjs/src/client/index.ts
|
|
578
|
+
function createSerialClient(options) {
|
|
579
|
+
return new SerialClientImpl(options);
|
|
580
|
+
}
|
|
581
|
+
export {
|
|
582
|
+
BrowserType,
|
|
583
|
+
SerialError,
|
|
584
|
+
SerialErrorCode,
|
|
585
|
+
buildRequestOptions,
|
|
586
|
+
checkBrowserSupport,
|
|
587
|
+
createSerialClient,
|
|
588
|
+
detectBrowserType,
|
|
589
|
+
hasWebSerialSupport,
|
|
590
|
+
isBrowserSupported,
|
|
591
|
+
isChromiumBased,
|
|
592
|
+
observableToWritable,
|
|
593
|
+
readableToObservable,
|
|
594
|
+
subscribeToWritable
|
|
595
|
+
};
|