Skip to content

Commit 21e6500

Browse files
committedJul 29, 2021
[major] Overhaul event classes
- Remove non-standard `OpenEvent` class. - Make properties read-only. - Update constructor signatures to match the ones defined by the HTML standard.
1 parent bd7febb commit 21e6500

File tree

4 files changed

+437
-84
lines changed

4 files changed

+437
-84
lines changed
 

‎lib/event-target.js

+144-59
Original file line numberDiff line numberDiff line change
@@ -2,112 +2,171 @@
22

33
const { kForOnEventAttribute, kListener } = require('./constants');
44

5+
const kCode = Symbol('kCode');
6+
const kData = Symbol('kData');
7+
const kError = Symbol('kError');
8+
const kMessage = Symbol('kMessage');
9+
const kReason = Symbol('kReason');
10+
const kTarget = Symbol('kTarget');
11+
const kType = Symbol('kType');
12+
const kWasClean = Symbol('kWasClean');
13+
514
/**
615
* Class representing an event.
7-
*
8-
* @private
916
*/
1017
class Event {
1118
/**
1219
* Create a new `Event`.
1320
*
1421
* @param {String} type The name of the event
15-
* @param {Object} target A reference to the target to which the event was
16-
* dispatched
22+
* @throws {TypeError} If the `type` argument is not specified
1723
*/
18-
constructor(type, target) {
19-
this.target = target;
20-
this.type = type;
24+
constructor(type) {
25+
this[kTarget] = null;
26+
this[kType] = type;
2127
}
22-
}
2328

24-
/**
25-
* Class representing a message event.
26-
*
27-
* @extends Event
28-
* @private
29-
*/
30-
class MessageEvent extends Event {
3129
/**
32-
* Create a new `MessageEvent`.
33-
*
34-
* @param {(String|Buffer|ArrayBuffer|Buffer[])} data The received data
35-
* @param {WebSocket} target A reference to the target to which the event was
36-
* dispatched
30+
* @type {*}
3731
*/
38-
constructor(data, target) {
39-
super('message', target);
32+
get target() {
33+
return this[kTarget];
34+
}
4035

41-
this.data = data;
36+
/**
37+
* @type {String}
38+
*/
39+
get type() {
40+
return this[kType];
4241
}
4342
}
4443

44+
Object.defineProperty(Event.prototype, 'target', { enumerable: true });
45+
Object.defineProperty(Event.prototype, 'type', { enumerable: true });
46+
4547
/**
4648
* Class representing a close event.
4749
*
4850
* @extends Event
49-
* @private
5051
*/
5152
class CloseEvent extends Event {
5253
/**
5354
* Create a new `CloseEvent`.
5455
*
55-
* @param {Number} code The status code explaining why the connection is being
56-
* closed
57-
* @param {String} reason A human-readable string explaining why the
58-
* connection is closing
59-
* @param {WebSocket} target A reference to the target to which the event was
60-
* dispatched
56+
* @param {String} type The name of the event
57+
* @param {Object} [options] A dictionary object that allows for setting
58+
* attributes via object members of the same name
59+
* @param {Number} [options.code=0] The status code explaining why the
60+
* connection was closed
61+
* @param {String} [options.reason=''] A human-readable string explaining why
62+
* the connection was closed
63+
* @param {Boolean} [options.wasClean=false] Indicates whether or not the
64+
* connection was cleanly closed
6165
*/
62-
constructor(code, reason, target) {
63-
super('close', target);
66+
constructor(type, options = {}) {
67+
super(type);
6468

65-
this.wasClean = target._closeFrameReceived && target._closeFrameSent;
66-
this.reason = reason;
67-
this.code = code;
69+
this[kCode] = options.code === undefined ? 0 : options.code;
70+
this[kReason] = options.reason === undefined ? '' : options.reason;
71+
this[kWasClean] = options.wasClean === undefined ? false : options.wasClean;
72+
}
73+
74+
/**
75+
* @type {Number}
76+
*/
77+
get code() {
78+
return this[kCode];
79+
}
80+
81+
/**
82+
* @type {String}
83+
*/
84+
get reason() {
85+
return this[kReason];
86+
}
87+
88+
/**
89+
* @type {Boolean}
90+
*/
91+
get wasClean() {
92+
return this[kWasClean];
6893
}
6994
}
7095

96+
Object.defineProperty(CloseEvent.prototype, 'code', { enumerable: true });
97+
Object.defineProperty(CloseEvent.prototype, 'reason', { enumerable: true });
98+
Object.defineProperty(CloseEvent.prototype, 'wasClean', { enumerable: true });
99+
71100
/**
72-
* Class representing an open event.
101+
* Class representing an error event.
73102
*
74103
* @extends Event
75-
* @private
76104
*/
77-
class OpenEvent extends Event {
105+
class ErrorEvent extends Event {
78106
/**
79-
* Create a new `OpenEvent`.
107+
* Create a new `ErrorEvent`.
80108
*
81-
* @param {WebSocket} target A reference to the target to which the event was
82-
* dispatched
109+
* @param {String} type The name of the event
110+
* @param {Object} [options] A dictionary object that allows for setting
111+
* attributes via object members of the same name
112+
* @param {*} [options.error=null] The error that generated this event
113+
* @param {String} [options.message=''] The error message
114+
*/
115+
constructor(type, options = {}) {
116+
super(type);
117+
118+
this[kError] = options.error === undefined ? null : options.error;
119+
this[kMessage] = options.message === undefined ? '' : options.message;
120+
}
121+
122+
/**
123+
* @type {*}
124+
*/
125+
get error() {
126+
return this[kError];
127+
}
128+
129+
/**
130+
* @type {String}
83131
*/
84-
constructor(target) {
85-
super('open', target);
132+
get message() {
133+
return this[kMessage];
86134
}
87135
}
88136

137+
Object.defineProperty(ErrorEvent.prototype, 'error', { enumerable: true });
138+
Object.defineProperty(ErrorEvent.prototype, 'message', { enumerable: true });
139+
89140
/**
90-
* Class representing an error event.
141+
* Class representing a message event.
91142
*
92143
* @extends Event
93-
* @private
94144
*/
95-
class ErrorEvent extends Event {
145+
class MessageEvent extends Event {
96146
/**
97-
* Create a new `ErrorEvent`.
147+
* Create a new `MessageEvent`.
98148
*
99-
* @param {Object} error The error that generated this event
100-
* @param {WebSocket} target A reference to the target to which the event was
101-
* dispatched
149+
* @param {String} type The name of the event
150+
* @param {Object} [options] A dictionary object that allows for setting
151+
* attributes via object members of the same name
152+
* @param {*} [options.data=null] The message content
102153
*/
103-
constructor(error, target) {
104-
super('error', target);
154+
constructor(type, options = {}) {
155+
super(type);
156+
157+
this[kData] = options.data === undefined ? null : options.data;
158+
}
105159

106-
this.message = error.message;
107-
this.error = error;
160+
/**
161+
* @type {*}
162+
*/
163+
get data() {
164+
return this[kData];
108165
}
109166
}
110167

168+
Object.defineProperty(MessageEvent.prototype, 'data', { enumerable: true });
169+
111170
/**
112171
* This provides methods for emulating the `EventTarget` interface. It's not
113172
* meant to be used directly.
@@ -132,20 +191,40 @@ const EventTarget = {
132191

133192
if (type === 'message') {
134193
wrapper = function onMessage(data, isBinary) {
135-
const event = new MessageEvent(isBinary ? data : data.toString(), this);
194+
const event = new MessageEvent('message', {
195+
data: isBinary ? data : data.toString()
196+
});
197+
198+
event[kTarget] = this;
136199
listener.call(this, event);
137200
};
138201
} else if (type === 'close') {
139202
wrapper = function onClose(code, message) {
140-
listener.call(this, new CloseEvent(code, message.toString(), this));
203+
const event = new CloseEvent('close', {
204+
code,
205+
reason: message.toString(),
206+
wasClean: this._closeFrameReceived && this._closeFrameSent
207+
});
208+
209+
event[kTarget] = this;
210+
listener.call(this, event);
141211
};
142212
} else if (type === 'error') {
143213
wrapper = function onError(error) {
144-
listener.call(this, new ErrorEvent(error, this));
214+
const event = new ErrorEvent('error', {
215+
error,
216+
message: error.message
217+
});
218+
219+
event[kTarget] = this;
220+
listener.call(this, event);
145221
};
146222
} else if (type === 'open') {
147223
wrapper = function onOpen() {
148-
listener.call(this, new OpenEvent(this));
224+
const event = new Event('open');
225+
226+
event[kTarget] = this;
227+
listener.call(this, event);
149228
};
150229
} else {
151230
return;
@@ -178,4 +257,10 @@ const EventTarget = {
178257
}
179258
};
180259

181-
module.exports = EventTarget;
260+
module.exports = {
261+
CloseEvent,
262+
ErrorEvent,
263+
Event,
264+
EventTarget,
265+
MessageEvent
266+
};

‎lib/websocket.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ const {
2121
kWebSocket,
2222
NOOP
2323
} = require('./constants');
24-
const { addEventListener, removeEventListener } = require('./event-target');
24+
const {
25+
EventTarget: { addEventListener, removeEventListener }
26+
} = require('./event-target');
2527
const { format, parse } = require('./extension');
2628
const { toBuffer } = require('./buffer-util');
2729

‎test/event-target.test.js

+253
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
'use strict';
2+
3+
const assert = require('assert');
4+
5+
const {
6+
CloseEvent,
7+
ErrorEvent,
8+
Event,
9+
MessageEvent
10+
} = require('../lib/event-target');
11+
12+
describe('Event', () => {
13+
describe('#ctor', () => {
14+
it('takes a `type` argument', () => {
15+
const event = new Event('foo');
16+
17+
assert.strictEqual(event.type, 'foo');
18+
});
19+
});
20+
21+
describe('Properties', () => {
22+
describe('`target`', () => {
23+
it('is enumerable and configurable', () => {
24+
const descriptor = Object.getOwnPropertyDescriptor(
25+
Event.prototype,
26+
'target'
27+
);
28+
29+
assert.strictEqual(descriptor.configurable, true);
30+
assert.strictEqual(descriptor.enumerable, true);
31+
assert.ok(descriptor.get !== undefined);
32+
assert.ok(descriptor.set === undefined);
33+
});
34+
35+
it('defaults to `null`', () => {
36+
const event = new Event('foo');
37+
38+
assert.strictEqual(event.target, null);
39+
});
40+
});
41+
42+
describe('`type`', () => {
43+
it('is enumerable and configurable', () => {
44+
const descriptor = Object.getOwnPropertyDescriptor(
45+
Event.prototype,
46+
'type'
47+
);
48+
49+
assert.strictEqual(descriptor.configurable, true);
50+
assert.strictEqual(descriptor.enumerable, true);
51+
assert.ok(descriptor.get !== undefined);
52+
assert.ok(descriptor.set === undefined);
53+
});
54+
});
55+
});
56+
});
57+
58+
describe('CloseEvent', () => {
59+
it('inherits from `Event`', () => {
60+
assert.ok(CloseEvent.prototype instanceof Event);
61+
});
62+
63+
describe('#ctor', () => {
64+
it('takes a `type` argument', () => {
65+
const event = new CloseEvent('foo');
66+
67+
assert.strictEqual(event.type, 'foo');
68+
});
69+
70+
it('takes an optional `options` argument', () => {
71+
const event = new CloseEvent('close', {
72+
code: 1000,
73+
reason: 'foo',
74+
wasClean: true
75+
});
76+
77+
assert.strictEqual(event.type, 'close');
78+
assert.strictEqual(event.code, 1000);
79+
assert.strictEqual(event.reason, 'foo');
80+
assert.strictEqual(event.wasClean, true);
81+
});
82+
});
83+
84+
describe('Properties', () => {
85+
describe('`code`', () => {
86+
it('is enumerable and configurable', () => {
87+
const descriptor = Object.getOwnPropertyDescriptor(
88+
CloseEvent.prototype,
89+
'code'
90+
);
91+
92+
assert.strictEqual(descriptor.configurable, true);
93+
assert.strictEqual(descriptor.enumerable, true);
94+
assert.ok(descriptor.get !== undefined);
95+
assert.ok(descriptor.set === undefined);
96+
});
97+
98+
it('defaults to 0', () => {
99+
const event = new CloseEvent('close');
100+
101+
assert.strictEqual(event.code, 0);
102+
});
103+
});
104+
105+
describe('`reason`', () => {
106+
it('is enumerable and configurable', () => {
107+
const descriptor = Object.getOwnPropertyDescriptor(
108+
CloseEvent.prototype,
109+
'reason'
110+
);
111+
112+
assert.strictEqual(descriptor.configurable, true);
113+
assert.strictEqual(descriptor.enumerable, true);
114+
assert.ok(descriptor.get !== undefined);
115+
assert.ok(descriptor.set === undefined);
116+
});
117+
118+
it('defaults to an empty string', () => {
119+
const event = new CloseEvent('close');
120+
121+
assert.strictEqual(event.reason, '');
122+
});
123+
});
124+
125+
describe('`wasClean`', () => {
126+
it('is enumerable and configurable', () => {
127+
const descriptor = Object.getOwnPropertyDescriptor(
128+
CloseEvent.prototype,
129+
'wasClean'
130+
);
131+
132+
assert.strictEqual(descriptor.configurable, true);
133+
assert.strictEqual(descriptor.enumerable, true);
134+
assert.ok(descriptor.get !== undefined);
135+
assert.ok(descriptor.set === undefined);
136+
});
137+
138+
it('defaults to false', () => {
139+
const event = new CloseEvent('close');
140+
141+
assert.strictEqual(event.wasClean, false);
142+
});
143+
});
144+
});
145+
});
146+
147+
describe('ErrorEvent', () => {
148+
it('inherits from `Event`', () => {
149+
assert.ok(ErrorEvent.prototype instanceof Event);
150+
});
151+
152+
describe('#ctor', () => {
153+
it('takes a `type` argument', () => {
154+
const event = new ErrorEvent('foo');
155+
156+
assert.strictEqual(event.type, 'foo');
157+
});
158+
159+
it('takes an optional `options` argument', () => {
160+
const error = new Error('Oops');
161+
const event = new ErrorEvent('error', { error, message: error.message });
162+
163+
assert.strictEqual(event.type, 'error');
164+
assert.strictEqual(event.error, error);
165+
assert.strictEqual(event.message, error.message);
166+
});
167+
});
168+
169+
describe('Properties', () => {
170+
describe('`error`', () => {
171+
it('is enumerable and configurable', () => {
172+
const descriptor = Object.getOwnPropertyDescriptor(
173+
ErrorEvent.prototype,
174+
'error'
175+
);
176+
177+
assert.strictEqual(descriptor.configurable, true);
178+
assert.strictEqual(descriptor.enumerable, true);
179+
assert.ok(descriptor.get !== undefined);
180+
assert.ok(descriptor.set === undefined);
181+
});
182+
183+
it('defaults to `null`', () => {
184+
const event = new ErrorEvent('error');
185+
186+
assert.strictEqual(event.error, null);
187+
});
188+
});
189+
190+
describe('`message`', () => {
191+
it('is enumerable and configurable', () => {
192+
const descriptor = Object.getOwnPropertyDescriptor(
193+
ErrorEvent.prototype,
194+
'message'
195+
);
196+
197+
assert.strictEqual(descriptor.configurable, true);
198+
assert.strictEqual(descriptor.enumerable, true);
199+
assert.ok(descriptor.get !== undefined);
200+
assert.ok(descriptor.set === undefined);
201+
});
202+
203+
it('defaults to an empty string', () => {
204+
const event = new ErrorEvent('error');
205+
206+
assert.strictEqual(event.message, '');
207+
});
208+
});
209+
});
210+
});
211+
212+
describe('MessageEvent', () => {
213+
it('inherits from `Event`', () => {
214+
assert.ok(MessageEvent.prototype instanceof Event);
215+
});
216+
217+
describe('#ctor', () => {
218+
it('takes a `type` argument', () => {
219+
const event = new MessageEvent('foo');
220+
221+
assert.strictEqual(event.type, 'foo');
222+
});
223+
224+
it('takes an optional `options` argument', () => {
225+
const event = new MessageEvent('message', { data: 'bar' });
226+
227+
assert.strictEqual(event.type, 'message');
228+
assert.strictEqual(event.data, 'bar');
229+
});
230+
});
231+
232+
describe('Properties', () => {
233+
describe('`data`', () => {
234+
it('is enumerable and configurable', () => {
235+
const descriptor = Object.getOwnPropertyDescriptor(
236+
MessageEvent.prototype,
237+
'data'
238+
);
239+
240+
assert.strictEqual(descriptor.configurable, true);
241+
assert.strictEqual(descriptor.enumerable, true);
242+
assert.ok(descriptor.get !== undefined);
243+
assert.ok(descriptor.set === undefined);
244+
});
245+
246+
it('defaults to `null`', () => {
247+
const event = new MessageEvent('message');
248+
249+
assert.strictEqual(event.data, null);
250+
});
251+
});
252+
});
253+
});

‎test/websocket.test.js

+37-24
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ const fs = require('fs');
1111
const { URL } = require('url');
1212

1313
const WebSocket = require('..');
14+
const {
15+
CloseEvent,
16+
ErrorEvent,
17+
Event,
18+
MessageEvent
19+
} = require('../lib/event-target');
1420
const { EMPTY_BUFFER, GUID, kListener, NOOP } = require('../lib/constants');
1521

1622
class CustomAgent extends http.Agent {
@@ -2315,8 +2321,9 @@ describe('WebSocket', () => {
23152321
ws.close();
23162322
});
23172323

2318-
ws.addEventListener('message', (messageEvent) => {
2319-
assert.strictEqual(messageEvent.data, 'hi');
2324+
ws.addEventListener('message', (event) => {
2325+
assert.ok(event instanceof MessageEvent);
2326+
assert.strictEqual(event.data, 'hi');
23202327
wss.close(done);
23212328
});
23222329
});
@@ -2332,10 +2339,11 @@ describe('WebSocket', () => {
23322339
const wss = new WebSocket.Server({ port: 0 }, () => {
23332340
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
23342341

2335-
ws.addEventListener('close', (closeEvent) => {
2336-
assert.ok(closeEvent.wasClean);
2337-
assert.strictEqual(closeEvent.reason, '');
2338-
assert.strictEqual(closeEvent.code, 1000);
2342+
ws.addEventListener('close', (event) => {
2343+
assert.ok(event instanceof CloseEvent);
2344+
assert.ok(event.wasClean);
2345+
assert.strictEqual(event.reason, '');
2346+
assert.strictEqual(event.code, 1000);
23392347
wss.close(done);
23402348
});
23412349
});
@@ -2347,10 +2355,11 @@ describe('WebSocket', () => {
23472355
const wss = new WebSocket.Server({ port: 0 }, () => {
23482356
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
23492357

2350-
ws.addEventListener('close', (closeEvent) => {
2351-
assert.ok(closeEvent.wasClean);
2352-
assert.strictEqual(closeEvent.reason, 'some daft reason');
2353-
assert.strictEqual(closeEvent.code, 4000);
2358+
ws.addEventListener('close', (event) => {
2359+
assert.ok(event instanceof CloseEvent);
2360+
assert.ok(event.wasClean);
2361+
assert.strictEqual(event.reason, 'some daft reason');
2362+
assert.strictEqual(event.code, 4000);
23542363
wss.close(done);
23552364
});
23562365
});
@@ -2363,25 +2372,29 @@ describe('WebSocket', () => {
23632372
const err = new Error('forced');
23642373
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
23652374

2366-
ws.addEventListener('open', (openEvent) => {
2367-
assert.strictEqual(openEvent.type, 'open');
2368-
assert.strictEqual(openEvent.target, ws);
2375+
ws.addEventListener('open', (event) => {
2376+
assert.ok(event instanceof Event);
2377+
assert.strictEqual(event.type, 'open');
2378+
assert.strictEqual(event.target, ws);
23692379
});
2370-
ws.addEventListener('message', (messageEvent) => {
2371-
assert.strictEqual(messageEvent.type, 'message');
2372-
assert.strictEqual(messageEvent.target, ws);
2380+
ws.addEventListener('message', (event) => {
2381+
assert.ok(event instanceof MessageEvent);
2382+
assert.strictEqual(event.type, 'message');
2383+
assert.strictEqual(event.target, ws);
23732384
ws.close();
23742385
});
2375-
ws.addEventListener('close', (closeEvent) => {
2376-
assert.strictEqual(closeEvent.type, 'close');
2377-
assert.strictEqual(closeEvent.target, ws);
2386+
ws.addEventListener('close', (event) => {
2387+
assert.ok(event instanceof CloseEvent);
2388+
assert.strictEqual(event.type, 'close');
2389+
assert.strictEqual(event.target, ws);
23782390
ws.emit('error', err);
23792391
});
2380-
ws.addEventListener('error', (errorEvent) => {
2381-
assert.strictEqual(errorEvent.message, 'forced');
2382-
assert.strictEqual(errorEvent.type, 'error');
2383-
assert.strictEqual(errorEvent.target, ws);
2384-
assert.strictEqual(errorEvent.error, err);
2392+
ws.addEventListener('error', (event) => {
2393+
assert.ok(event instanceof ErrorEvent);
2394+
assert.strictEqual(event.message, 'forced');
2395+
assert.strictEqual(event.type, 'error');
2396+
assert.strictEqual(event.target, ws);
2397+
assert.strictEqual(event.error, err);
23852398

23862399
wss.close(done);
23872400
});

0 commit comments

Comments
 (0)
Please sign in to comment.