event-target.js (6590B)
1 'use strict'; 2 3 const { kForOnEventAttribute, kListener } = require('./constants'); 4 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 14 /** 15 * Class representing an event. 16 */ 17 class 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 44 Object.defineProperty(Event.prototype, 'target', { enumerable: true }); 45 Object.defineProperty(Event.prototype, 'type', { enumerable: true }); 46 47 /** 48 * Class representing a close event. 49 * 50 * @extends Event 51 */ 52 class 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 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 100 /** 101 * Class representing an error event. 102 * 103 * @extends Event 104 */ 105 class 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 137 Object.defineProperty(ErrorEvent.prototype, 'error', { enumerable: true }); 138 Object.defineProperty(ErrorEvent.prototype, 'message', { enumerable: true }); 139 140 /** 141 * Class representing a message event. 142 * 143 * @extends Event 144 */ 145 class 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 168 Object.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 */ 176 const EventTarget = { 177 /** 178 * Register an event listener. 179 * 180 * @param {String} type A string representing the event type to listen for 181 * @param {Function} listener 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, listener, options = {}) { 190 let wrapper; 191 192 if (type === 'message') { 193 wrapper = function onMessage(data, isBinary) { 194 const event = new MessageEvent('message', { 195 data: isBinary ? data : data.toString() 196 }); 197 198 event[kTarget] = this; 199 listener.call(this, event); 200 }; 201 } else if (type === 'close') { 202 wrapper = function onClose(code, message) { 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); 211 }; 212 } else if (type === 'error') { 213 wrapper = function onError(error) { 214 const event = new ErrorEvent('error', { 215 error, 216 message: error.message 217 }); 218 219 event[kTarget] = this; 220 listener.call(this, event); 221 }; 222 } else if (type === 'open') { 223 wrapper = function onOpen() { 224 const event = new Event('open'); 225 226 event[kTarget] = this; 227 listener.call(this, event); 228 }; 229 } else { 230 return; 231 } 232 233 wrapper[kForOnEventAttribute] = !!options[kForOnEventAttribute]; 234 wrapper[kListener] = listener; 235 236 if (options.once) { 237 this.once(type, wrapper); 238 } else { 239 this.on(type, wrapper); 240 } 241 }, 242 243 /** 244 * Remove an event listener. 245 * 246 * @param {String} type A string representing the event type to remove 247 * @param {Function} handler The listener to remove 248 * @public 249 */ 250 removeEventListener(type, handler) { 251 for (const listener of this.listeners(type)) { 252 if (listener[kListener] === handler && !listener[kForOnEventAttribute]) { 253 this.removeListener(type, listener); 254 break; 255 } 256 } 257 } 258 }; 259 260 module.exports = { 261 CloseEvent, 262 ErrorEvent, 263 Event, 264 EventTarget, 265 MessageEvent 266 };