Skip to content

Commit 3b7d5ea

Browse files
committed
events: Add helpers for event listeners
* Simplify code * Support EventTarget and EventEmitter
1 parent 3df2b08 commit 3b7d5ea

File tree

6 files changed

+103
-92
lines changed

6 files changed

+103
-92
lines changed

packages/connection/index.js

+19-47
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { EventEmitter, promise } from "@xmpp/events";
1+
import { EventEmitter, promise, listeners } from "@xmpp/events";
22
import jid from "@xmpp/jid";
33
import xml from "@xmpp/xml";
44
import StreamError from "./lib/StreamError.js";
@@ -8,13 +8,14 @@ const NS_STREAM = "urn:ietf:params:xml:ns:xmpp-streams";
88
const NS_JABBER_STREAM = "http://etherx.jabber.org/streams";
99

1010
class Connection extends EventEmitter {
11+
#socketListeners = null;
12+
#parserListeners = null;
13+
1114
constructor(options = {}) {
1215
super();
1316
this.jid = null;
1417
this.timeout = 2000;
1518
this.options = options;
16-
this.socketListeners = Object.create(null);
17-
this.parserListeners = Object.create(null);
1819
this.status = "offline";
1920
this.socket = null;
2021
this.parser = null;
@@ -40,7 +41,7 @@ class Connection extends EventEmitter {
4041
this.parser.write(str);
4142
}
4243

43-
_onParserError(error) {
44+
#onParserError(error) {
4445
// https://xmpp.org/rfcs/rfc6120.html#streams-error-conditions-bad-format
4546
// "This error can be used instead of the more specific XML-related errors,
4647
// such as <bad-namespace-prefix/>, <invalid-xml/>, <not-well-formed/>, <restricted-xml/>,
@@ -62,32 +63,16 @@ class Connection extends EventEmitter {
6263

6364
_attachSocket(socket) {
6465
this.socket = socket;
65-
const listeners = this.socketListeners;
66-
67-
listeners.data = this._onData.bind(this);
68-
69-
listeners.close = this.#onSocketClosed.bind(this);
70-
71-
listeners.connect = () => {
72-
this._status("connect");
73-
};
74-
75-
listeners.error = (error) => {
76-
this.emit("error", error);
77-
};
78-
79-
this.socket.on("close", listeners.close);
80-
this.socket.on("data", listeners.data);
81-
this.socket.on("error", listeners.error);
82-
this.socket.on("connect", listeners.connect);
66+
this.#socketListeners ??= listeners(socket, {
67+
data: this._onData.bind(this),
68+
close: this.#onSocketClosed.bind(this),
69+
connect: () => this._status("connect"),
70+
error: (error) => this.emit("error", error),
71+
}).subscribe();
8372
}
8473

8574
_detachSocket() {
86-
const { socketListeners, socket } = this;
87-
for (const k of Object.getOwnPropertyNames(socketListeners)) {
88-
socket.removeListener(k, socketListeners[k]);
89-
delete socketListeners[k];
90-
}
75+
this.#socketListeners?.unsubscribe();
9176
this.socket = null;
9277
}
9378

@@ -143,29 +128,16 @@ class Connection extends EventEmitter {
143128

144129
_attachParser(parser) {
145130
this.parser = parser;
146-
const listeners = this.parserListeners;
147-
148-
listeners.element = this._onElement.bind(this);
149-
listeners.error = this._onParserError.bind(this);
150-
151-
listeners.end = this.#onStreamClosed.bind(this);
152-
153-
listeners.start = (element) => {
154-
this._status("open", element);
155-
};
156-
157-
this.parser.on("error", listeners.error);
158-
this.parser.on("element", listeners.element);
159-
this.parser.on("end", listeners.end);
160-
this.parser.on("start", listeners.start);
131+
this.#parserListeners ??= listeners(parser, {
132+
element: this._onElement.bind(this),
133+
error: this.#onParserError.bind(this),
134+
end: this.#onStreamClosed.bind(this),
135+
start: (element) => this._status("open", element),
136+
}).subscribe();
161137
}
162138

163139
_detachParser() {
164-
const listeners = this.parserListeners;
165-
for (const k of Object.getOwnPropertyNames(listeners)) {
166-
this.parser.removeListener(k, listeners[k]);
167-
delete listeners[k];
168-
}
140+
this.#parserListeners?.unsubscribe();
169141
this.parser = null;
170142
this.root = null;
171143
}

packages/events/index.js

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import TimeoutError from "./lib/TimeoutError.js";
66
import promise from "./lib/promise.js";
77
import Deferred from "./lib/Deferred.js";
88
import procedure from "./lib/procedure.js";
9+
import listeners from "./lib/listeners.js";
10+
import onoff from "./lib/onoff.js";
911

1012
export {
1113
EventEmitter,
@@ -15,4 +17,6 @@ export {
1517
promise,
1618
Deferred,
1719
procedure,
20+
listeners,
21+
onoff,
1822
};

packages/events/lib/listeners.js

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import onoff from "./onoff.js";
2+
3+
export default function listeners(target, events) {
4+
let subscribed = false;
5+
6+
const { on, off } = onoff(target);
7+
8+
return {
9+
subscribed,
10+
subscribe() {
11+
if (subscribed) throw new Event("Listeners already subscribed");
12+
for (const [event, handler] of Object.entries(events)) {
13+
on(event, handler);
14+
}
15+
subscribed = true;
16+
return this;
17+
},
18+
unsubscribe() {
19+
for (const [event, handler] of Object.entries(events)) {
20+
off(event, handler);
21+
}
22+
return this;
23+
},
24+
};
25+
}

packages/events/lib/onoff.js

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const map = new WeakMap();
2+
3+
export default function onoff(target) {
4+
let m = map.get(target);
5+
6+
if (!m) {
7+
const on = (target.addListener ?? target.addEventListener).bind(target);
8+
const off = (target.removeListener ?? target.removeEventListener).bind(
9+
target,
10+
);
11+
const once = (
12+
target.once ??
13+
((event, handler) =>
14+
target.addEventListener(event, handler, { once: true }))
15+
).bind(target);
16+
m = { on, off, once };
17+
map.set(target, m);
18+
}
19+
20+
return m;
21+
}

packages/events/lib/promise.js

+9-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
1+
import onoff from "./onoff.js";
2+
13
import TimeoutError from "./TimeoutError.js";
24

3-
export default function promise(EE, event, rejectEvent = "error", timeout) {
5+
export default function promise(target, event, rejectEvent = "error", timeout) {
46
return new Promise((resolve, reject) => {
57
let timeoutId;
68

9+
const { off, once } = onoff(target);
10+
711
const cleanup = () => {
812
clearTimeout(timeoutId);
9-
EE.removeListener(event, onEvent);
10-
EE.removeListener(rejectEvent, onError);
13+
off(event, onEvent);
14+
off(rejectEvent, onError);
1115
};
1216

1317
function onError(reason) {
@@ -20,9 +24,9 @@ export default function promise(EE, event, rejectEvent = "error", timeout) {
2024
cleanup();
2125
}
2226

23-
EE.once(event, onEvent);
27+
once(event, onEvent);
2428
if (rejectEvent) {
25-
EE.once(rejectEvent, onError);
29+
once(rejectEvent, onError);
2630
}
2731

2832
if (timeout) {

packages/websocket/lib/Socket.js

+25-40
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import WS from "ws";
2-
import { EventEmitter } from "@xmpp/events";
2+
import { EventEmitter, listeners } from "@xmpp/events";
33
import { parseURI } from "@xmpp/connection/lib/util.js";
44

55
// eslint-disable-next-line n/no-unsupported-features/node-builtins
@@ -8,10 +8,7 @@ const WebSocket = globalThis.WebSocket || WS;
88
const CODE = "ECONNERROR";
99

1010
export default class Socket extends EventEmitter {
11-
constructor() {
12-
super();
13-
this.listeners = Object.create(null);
14-
}
11+
#listeners = null;
1512

1613
isSecure() {
1714
if (!this.url) return false;
@@ -28,46 +25,34 @@ export default class Socket extends EventEmitter {
2825

2926
_attachSocket(socket) {
3027
this.socket = socket;
31-
const { listeners } = this;
32-
listeners.open = () => {
33-
this.emit("connect");
34-
};
35-
36-
listeners.message = ({ data }) => this.emit("data", data);
37-
listeners.error = (event) => {
38-
const { url } = this;
39-
// WS
40-
let { error } = event;
41-
// DOM
42-
if (!error) {
43-
error = new Error(`WebSocket ${CODE} ${url}`);
44-
error.errno = CODE;
45-
error.code = CODE;
46-
}
28+
this.#listeners ??= listeners(socket, {
29+
open: () => this.emit("connect"),
30+
message: ({ data }) => this.emit("data", data),
31+
error: (event) => {
32+
const { url } = this;
33+
// WS
34+
let { error } = event;
35+
// DOM
36+
if (!error) {
37+
error = new Error(`WebSocket ${CODE} ${url}`);
38+
error.errno = CODE;
39+
error.code = CODE;
40+
}
4741

48-
error.event = event;
49-
error.url = url;
50-
this.emit("error", error);
51-
};
52-
53-
listeners.close = (event) => {
54-
this._detachSocket();
55-
this.emit("close", !event.wasClean, event);
56-
};
57-
58-
this.socket.addEventListener("open", listeners.open);
59-
this.socket.addEventListener("message", listeners.message);
60-
this.socket.addEventListener("error", listeners.error);
61-
this.socket.addEventListener("close", listeners.close);
42+
error.event = event;
43+
error.url = url;
44+
this.emit("error", error);
45+
},
46+
close: (event) => {
47+
this._detachSocket();
48+
this.emit("close", !event.wasClean, event);
49+
},
50+
}).subscribe();
6251
}
6352

6453
_detachSocket() {
6554
delete this.url;
66-
const { socket, listeners } = this;
67-
for (const k of Object.getOwnPropertyNames(listeners)) {
68-
socket.removeEventListener(k, listeners[k]);
69-
delete listeners[k];
70-
}
55+
this.#listeners?.unsubscribe;
7156
delete this.socket;
7257
}
7358

0 commit comments

Comments
 (0)