UNPKG

7.32 kBJavaScriptView Raw
1'use strict';
2
3const { kForOnEventAttribute, kListener } = require('./constants');
4
5const kCode = Symbol('kCode');
6const kData = Symbol('kData');
7const kError = Symbol('kError');
8const kMessage = Symbol('kMessage');
9const kReason = Symbol('kReason');
10const kTarget = Symbol('kTarget');
11const kType = Symbol('kType');
12const kWasClean = Symbol('kWasClean');
13
14/**
15 * Class representing an event.
16 */
17class Event {
18 /**
19 * Create a new `Event`.
20 *
21 * @param {String} type The name of the event
22 * @throws {TypeError} If the `type` argument is not specified
23 */
24 constructor(type) {
25 this[kTarget] = null;
26 this[kType] = type;
27 }
28
29 /**
30 * @type {*}
31 */
32 get target() {
33 return this[kTarget];
34 }
35
36 /**
37 * @type {String}
38 */
39 get type() {
40 return this[kType];
41 }
42}
43
44Object.defineProperty(Event.prototype, 'target', { enumerable: true });
45Object.defineProperty(Event.prototype, 'type', { enumerable: true });
46
47/**
48 * Class representing a close event.
49 *
50 * @extends Event
51 */
52class CloseEvent extends Event {
53 /**
54 * Create a new `CloseEvent`.
55 *
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
65 */
66 constructor(type, options = {}) {
67 super(type);
68
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];
93 }
94}
95
96Object.defineProperty(CloseEvent.prototype, 'code', { enumerable: true });
97Object.defineProperty(CloseEvent.prototype, 'reason', { enumerable: true });
98Object.defineProperty(CloseEvent.prototype, 'wasClean', { enumerable: true });
99
100/**
101 * Class representing an error event.
102 *
103 * @extends Event
104 */
105class ErrorEvent extends Event {
106 /**
107 * Create a new `ErrorEvent`.
108 *
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}
131 */
132 get message() {
133 return this[kMessage];
134 }
135}
136
137Object.defineProperty(ErrorEvent.prototype, 'error', { enumerable: true });
138Object.defineProperty(ErrorEvent.prototype, 'message', { enumerable: true });
139
140/**
141 * Class representing a message event.
142 *
143 * @extends Event
144 */
145class MessageEvent extends Event {
146 /**
147 * Create a new `MessageEvent`.
148 *
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
153 */
154 constructor(type, options = {}) {
155 super(type);
156
157 this[kData] = options.data === undefined ? null : options.data;
158 }
159
160 /**
161 * @type {*}
162 */
163 get data() {
164 return this[kData];
165 }
166}
167
168Object.defineProperty(MessageEvent.prototype, 'data', { enumerable: true });
169
170/**
171 * This provides methods for emulating the `EventTarget` interface. It's not
172 * meant to be used directly.
173 *
174 * @mixin
175 */
176const EventTarget = {
177 /**
178 * Register an event listener.
179 *
180 * @param {String} type A string representing the event type to listen for
181 * @param {(Function|Object)} handler The listener to add
182 * @param {Object} [options] An options object specifies characteristics about
183 * the event listener
184 * @param {Boolean} [options.once=false] A `Boolean` indicating that the
185 * listener should be invoked at most once after being added. If `true`,
186 * the listener would be automatically removed when invoked.
187 * @public
188 */
189 addEventListener(type, handler, options = {}) {
190 for (const listener of this.listeners(type)) {
191 if (
192 !options[kForOnEventAttribute] &&
193 listener[kListener] === handler &&
194 !listener[kForOnEventAttribute]
195 ) {
196 return;
197 }
198 }
199
200 let wrapper;
201
202 if (type === 'message') {
203 wrapper = function onMessage(data, isBinary) {
204 const event = new MessageEvent('message', {
205 data: isBinary ? data : data.toString()
206 });
207
208 event[kTarget] = this;
209 callListener(handler, this, event);
210 };
211 } else if (type === 'close') {
212 wrapper = function onClose(code, message) {
213 const event = new CloseEvent('close', {
214 code,
215 reason: message.toString(),
216 wasClean: this._closeFrameReceived && this._closeFrameSent
217 });
218
219 event[kTarget] = this;
220 callListener(handler, this, event);
221 };
222 } else if (type === 'error') {
223 wrapper = function onError(error) {
224 const event = new ErrorEvent('error', {
225 error,
226 message: error.message
227 });
228
229 event[kTarget] = this;
230 callListener(handler, this, event);
231 };
232 } else if (type === 'open') {
233 wrapper = function onOpen() {
234 const event = new Event('open');
235
236 event[kTarget] = this;
237 callListener(handler, this, event);
238 };
239 } else {
240 return;
241 }
242
243 wrapper[kForOnEventAttribute] = !!options[kForOnEventAttribute];
244 wrapper[kListener] = handler;
245
246 if (options.once) {
247 this.once(type, wrapper);
248 } else {
249 this.on(type, wrapper);
250 }
251 },
252
253 /**
254 * Remove an event listener.
255 *
256 * @param {String} type A string representing the event type to remove
257 * @param {(Function|Object)} handler The listener to remove
258 * @public
259 */
260 removeEventListener(type, handler) {
261 for (const listener of this.listeners(type)) {
262 if (listener[kListener] === handler && !listener[kForOnEventAttribute]) {
263 this.removeListener(type, listener);
264 break;
265 }
266 }
267 }
268};
269
270module.exports = {
271 CloseEvent,
272 ErrorEvent,
273 Event,
274 EventTarget,
275 MessageEvent
276};
277
278/**
279 * Call an event listener
280 *
281 * @param {(Function|Object)} listener The listener to call
282 * @param {*} thisArg The value to use as `this`` when calling the listener
283 * @param {Event} event The event to pass to the listener
284 * @private
285 */
286function callListener(listener, thisArg, event) {
287 if (typeof listener === 'object' && listener.handleEvent) {
288 listener.handleEvent.call(listener, event);
289 } else {
290 listener.call(thisArg, event);
291 }
292}