Skip to content

Commit 14525a6

Browse files
committed
Move transports to own folder/files for easier dynamic loading. Update tests.
1 parent 133c740 commit 14525a6

File tree

8 files changed

+196
-163
lines changed

8 files changed

+196
-163
lines changed

src/RosLib.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ export { default as Topic } from "./core/Topic.js";
1212
export { default as Param } from "./core/Param.js";
1313
export { default as Service } from "./core/Service.js";
1414
export { default as Action } from "./core/Action.js";
15+
16+
// Core Transport exports
1517
export {
18+
AbstractTransport,
1619
type ITransport,
1720
type ITransportFactory,
1821
type TransportEvent,
19-
AbstractTransport,
20-
WebSocketTransportFactory,
21-
} from "./core/Transport.js";
22+
} from "./core/transport/Transport.js";
23+
export { WebSocketTransportFactory } from "./core/transport/WebSocketTransportFactory.js";
2224

2325
// ActionLib exports
2426
export { default as ActionClient } from "./actionlib/ActionClient.js";

src/core/Ros.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ import type {
3030
ITransport,
3131
ITransportFactory,
3232
TransportEvent,
33-
} from "./Transport.js";
34-
import { WebSocketTransportFactory } from "./Transport.js";
33+
} from "./transport/Transport.js";
34+
import { WebSocketTransportFactory } from "./transport/WebSocketTransportFactory.ts";
3535

3636
/**
3737
* Manages connection to the rosbridge server and all interactions with ROS.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import type { RosbridgeMessage } from "../../types/protocol.js";
2+
import { AbstractTransport } from "./Transport.js";
3+
4+
/**
5+
* Uses the native `WebSocket` class to send and receive messages.
6+
*
7+
* @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
8+
*/
9+
export class NativeWebSocketTransport extends AbstractTransport {
10+
private socket: WebSocket;
11+
12+
constructor(socket: WebSocket) {
13+
super();
14+
this.socket = socket;
15+
this.registerEventListeners();
16+
}
17+
18+
public send(message: RosbridgeMessage): void {
19+
this.socket.send(JSON.stringify(message));
20+
}
21+
22+
public close(): void {
23+
this.socket.close();
24+
}
25+
26+
public isConnecting(): boolean {
27+
return this.socket.readyState === WebSocket.CONNECTING;
28+
}
29+
30+
public isOpen(): boolean {
31+
return this.socket.readyState === WebSocket.OPEN;
32+
}
33+
34+
public isClosing(): boolean {
35+
return this.socket.readyState === WebSocket.CLOSING;
36+
}
37+
38+
public isClosed(): boolean {
39+
return this.socket.readyState === WebSocket.CLOSED;
40+
}
41+
42+
private registerEventListeners(): void {
43+
this.socket.onopen = (event: Event) => {
44+
this.emit("open", event);
45+
};
46+
47+
this.socket.onclose = (event: CloseEvent) => {
48+
this.emit("close", event);
49+
};
50+
51+
this.socket.onerror = (event: Event) => {
52+
this.emit("error", event);
53+
};
54+
55+
this.socket.onmessage = (event: MessageEvent) => {
56+
this.handleRawMessage(event.data);
57+
};
58+
}
59+
}
Lines changed: 18 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,16 @@ import type {
33
RosbridgePngMessage,
44
RosbridgeMessage,
55
RosbridgeFragmentMessage,
6-
} from "../types/protocol.js";
6+
} from "../../types/protocol.js";
77
import {
88
isRosbridgeFragmentMessage,
99
isRosbridgeMessage,
1010
isRosbridgePngMessage,
11-
} from "../types/protocol.js";
12-
import * as ws from "ws";
11+
} from "../../types/protocol.js";
1312
import { deserialize } from "bson";
1413
import CBOR from "cbor-js";
15-
import typedArrayTagger from "../util/cborTypedArrayTags.js";
16-
import decompressPng from "../util/decompressPng.js";
14+
import typedArrayTagger from "../../util/cborTypedArrayTags.js";
15+
import decompressPng from "../../util/decompressPng.js";
1716

1817
/**
1918
* Because transport implementations may have different event types
@@ -22,6 +21,12 @@ import decompressPng from "../util/decompressPng.js";
2221
*/
2322
export type TransportEvent = unknown;
2423

24+
/**
25+
* Abstraction responsible for sending and receiving messages
26+
* between the client and the rosbridge server.
27+
*
28+
* Inspired by the WebSocket API, which is the default reference implementation.
29+
*/
2530
export interface ITransport {
2631
on(
2732
event: "open" | "close" | "error",
@@ -40,8 +45,16 @@ export interface ITransport {
4045
isClosed(): boolean;
4146
}
4247

48+
/**
49+
* Invoked when the roslib needs a new transport, such as
50+
* when (re)connecting to the rosbridge server.
51+
*/
4352
export type ITransportFactory = (url: string) => Promise<ITransport>;
4453

54+
/**
55+
* Abstract base class for all transport implementations.
56+
* Provides a default implementation for decoding raw rosbridge messages.
57+
*/
4558
export abstract class AbstractTransport
4659
extends EventEmitter<{
4760
open: [TransportEvent];
@@ -227,132 +240,3 @@ export abstract class AbstractTransport
227240
}
228241
}
229242
}
230-
231-
export class NativeWebSocketTransport extends AbstractTransport {
232-
private socket: WebSocket;
233-
234-
constructor(socket: WebSocket) {
235-
super();
236-
this.socket = socket;
237-
this.registerEventListeners();
238-
}
239-
240-
public send(message: RosbridgeMessage): void {
241-
this.socket.send(JSON.stringify(message));
242-
}
243-
244-
public close(): void {
245-
this.socket.close();
246-
}
247-
248-
public isConnecting(): boolean {
249-
return this.socket.readyState === WebSocket.CONNECTING;
250-
}
251-
252-
public isOpen(): boolean {
253-
return this.socket.readyState === WebSocket.OPEN;
254-
}
255-
256-
public isClosing(): boolean {
257-
return this.socket.readyState === WebSocket.CLOSING;
258-
}
259-
260-
public isClosed(): boolean {
261-
return this.socket.readyState === WebSocket.CLOSED;
262-
}
263-
264-
private registerEventListeners(): void {
265-
this.socket.onopen = (event: Event) => {
266-
this.emit("open", event);
267-
};
268-
269-
this.socket.onclose = (event: CloseEvent) => {
270-
this.emit("close", event);
271-
};
272-
273-
this.socket.onerror = (event: Event) => {
274-
this.emit("error", event);
275-
};
276-
277-
this.socket.onmessage = (event: MessageEvent) => {
278-
this.handleRawMessage(event.data);
279-
};
280-
}
281-
}
282-
283-
export class WsWebSocketTransport extends AbstractTransport {
284-
private socket: ws.WebSocket;
285-
286-
constructor(socket: ws.WebSocket) {
287-
super();
288-
this.socket = socket;
289-
this.registerEventListeners();
290-
}
291-
292-
public send(message: RosbridgeMessage): void {
293-
this.socket.send(JSON.stringify(message));
294-
}
295-
296-
public close(): void {
297-
this.socket.close();
298-
}
299-
300-
public isConnecting(): boolean {
301-
return this.socket.readyState === ws.WebSocket.CONNECTING;
302-
}
303-
304-
public isOpen(): boolean {
305-
return this.socket.readyState === ws.WebSocket.OPEN;
306-
}
307-
308-
public isClosing(): boolean {
309-
return this.socket.readyState === ws.WebSocket.CLOSING;
310-
}
311-
312-
public isClosed(): boolean {
313-
return this.socket.readyState === ws.WebSocket.CLOSED;
314-
}
315-
316-
private registerEventListeners(): void {
317-
this.socket.onopen = (event: ws.Event) => {
318-
this.emit("open", event);
319-
};
320-
321-
this.socket.onclose = (event: ws.CloseEvent) => {
322-
this.emit("close", event);
323-
};
324-
325-
this.socket.onerror = (event: ws.ErrorEvent) => {
326-
this.emit("error", event);
327-
};
328-
329-
this.socket.onmessage = (event: ws.MessageEvent) => {
330-
this.handleRawMessage(event.data);
331-
};
332-
}
333-
}
334-
335-
/**
336-
* A transport factory that uses WebSockets to send and receive messages.
337-
* Will use the native `WebSocket` class if available, otherwise falls back
338-
* to the `ws` package.
339-
*
340-
* @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
341-
* @see https://github.com/websockets/ws
342-
*/
343-
export const WebSocketTransportFactory: ITransportFactory = async (
344-
url: string,
345-
): Promise<ITransport> => {
346-
// Browsers, Deno, Bun, and Node 22+ support WebSockets natively
347-
if (typeof WebSocket === "function") {
348-
const socket = new WebSocket(url);
349-
socket.binaryType = "arraybuffer";
350-
return new NativeWebSocketTransport(socket);
351-
}
352-
353-
// If in Node.js, import ws to replace WebSocket API
354-
const ws = await import("ws");
355-
const socket = new ws.WebSocket(url);
356-
socket.binaryType = "arraybuffer";
357-
return new WsWebSocketTransport(socket);
358-
};
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import type { ITransport, ITransportFactory } from "./Transport.js";
2+
import { NativeWebSocketTransport } from "./NativeWebSocketTransport.js";
3+
4+
/**
5+
* A transport factory that uses WebSockets to send and receive messages.
6+
* Will use the native `WebSocket` class if available, otherwise falls back
7+
* to the `ws` package.
8+
*
9+
* @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
10+
* @see https://github.com/websockets/ws
11+
*/
12+
export const WebSocketTransportFactory: ITransportFactory = async (
13+
url: string,
14+
): Promise<ITransport> => {
15+
// Browsers, Deno, Bun, and Node 22+ support WebSockets natively
16+
if (typeof WebSocket === "function") {
17+
const socket = new WebSocket(url);
18+
socket.binaryType = "arraybuffer";
19+
return new NativeWebSocketTransport(socket);
20+
}
21+
22+
// If in Node.js, import ws to replace WebSocket API
23+
// Dynamically import the dependencies as they may not
24+
// be available in a browser environment.
25+
const ws = await import("ws");
26+
const { WsWebSocketTransport } = await import("./WsWebSocketTransport.js");
27+
const socket = new ws.WebSocket(url);
28+
socket.binaryType = "arraybuffer";
29+
return new WsWebSocketTransport(socket);
30+
};
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import * as ws from "ws";
2+
import type { RosbridgeMessage } from "../../types/protocol.js";
3+
import { AbstractTransport } from "./Transport.js";
4+
5+
/**
6+
* Uses the `ws` package to send and receive messages.
7+
*
8+
* @see https://github.com/websockets/ws
9+
*/
10+
export class WsWebSocketTransport extends AbstractTransport {
11+
private socket: ws.WebSocket;
12+
13+
constructor(socket: ws.WebSocket) {
14+
super();
15+
this.socket = socket;
16+
this.registerEventListeners();
17+
}
18+
19+
public send(message: RosbridgeMessage): void {
20+
this.socket.send(JSON.stringify(message));
21+
}
22+
23+
public close(): void {
24+
this.socket.close();
25+
}
26+
27+
public isConnecting(): boolean {
28+
return this.socket.readyState === ws.WebSocket.CONNECTING;
29+
}
30+
31+
public isOpen(): boolean {
32+
return this.socket.readyState === ws.WebSocket.OPEN;
33+
}
34+
35+
public isClosing(): boolean {
36+
return this.socket.readyState === ws.WebSocket.CLOSING;
37+
}
38+
39+
public isClosed(): boolean {
40+
return this.socket.readyState === ws.WebSocket.CLOSED;
41+
}
42+
43+
private registerEventListeners(): void {
44+
this.socket.onopen = (event: ws.Event) => {
45+
this.emit("open", event);
46+
};
47+
48+
this.socket.onclose = (event: ws.CloseEvent) => {
49+
this.emit("close", event);
50+
};
51+
52+
this.socket.onerror = (event: ws.ErrorEvent) => {
53+
this.emit("error", event);
54+
};
55+
56+
this.socket.onmessage = (event: ws.MessageEvent) => {
57+
this.handleRawMessage(event.data);
58+
};
59+
}
60+
}

test/ros.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import type {
55
ITransport,
66
ITransportFactory,
77
TransportEvent,
8-
} from "../src/core/Transport.js";
9-
import { WebSocketTransportFactory } from "../src/core/Transport.js";
8+
} from "../src/core/transport/Transport.js";
9+
import { WebSocketTransportFactory } from "../src/core/transport/WebSocketTransportFactory.js";
1010
import Ros from "../src/core/Ros.js";
1111
import type { RosbridgeMessage } from "../src/types/protocol.js";
1212

0 commit comments

Comments
 (0)