'use strict'; const { kForOnEventAttribute, kListener } = require('./constants'); const kCode = Symbol('kCode'); const kData = Symbol('kData'); const kError = Symbol('kError'); const kMessage = Symbol('kMessage'); const kReason = Symbol('kReason'); const kTarget = Symbol('kTarget'); const kType = Symbol('kType'); const kWasClean = Symbol('kWasClean'); /** * Class representing an event. */ class Event { /** * Create a new `Event`. * * @param {String} type The name of the event * @throws {TypeError} If the `type` argument is not specified */ constructor(type) { this[kTarget] = null; this[kType] = type; } /** * @type {*} */ get target() { return this[kTarget]; } /** * @type {String} */ get type() { return this[kType]; } } Object.defineProperty(Event.prototype, 'target', { enumerable: true }); Object.defineProperty(Event.prototype, 'type', { enumerable: true }); /** * Class representing a close event. * * @extends Event */ class CloseEvent extends Event { /** * Create a new `CloseEvent`. * * @param {String} type The name of the event * @param {Object} [options] A dictionary object that allows for setting * attributes via object members of the same name * @param {Number} [options.code=0] The status code explaining why the * connection was closed * @param {String} [options.reason=''] A human-readable string explaining why * the connection was closed * @param {Boolean} [options.wasClean=false] Indicates whether or not the * connection was cleanly closed */ constructor(type, options = {}) { super(type); this[kCode] = options.code === undefined ? 0 : options.code; this[kReason] = options.reason === undefined ? '' : options.reason; this[kWasClean] = options.wasClean === undefined ? false : options.wasClean; } /** * @type {Number} */ get code() { return this[kCode]; } /** * @type {String} */ get reason() { return this[kReason]; } /** * @type {Boolean} */ get wasClean() { return this[kWasClean]; } } Object.defineProperty(CloseEvent.prototype, 'code', { enumerable: true }); Object.defineProperty(CloseEvent.prototype, 'reason', { enumerable: true }); Object.defineProperty(CloseEvent.prototype, 'wasClean', { enumerable: true }); /** * Class representing an error event. * * @extends Event */ class ErrorEvent extends Event { /** * Create a new `ErrorEvent`. * * @param {String} type The name of the event * @param {Object} [options] A dictionary object that allows for setting * attributes via object members of the same name * @param {*} [options.error=null] The error that generated this event * @param {String} [options.message=''] The error message */ constructor(type, options = {}) { super(type); this[kError] = options.error === undefined ? null : options.error; this[kMessage] = options.message === undefined ? '' : options.message; } /** * @type {*} */ get error() { return this[kError]; } /** * @type {String} */ get message() { return this[kMessage]; } } Object.defineProperty(ErrorEvent.prototype, 'error', { enumerable: true }); Object.defineProperty(ErrorEvent.prototype, 'message', { enumerable: true }); /** * Class representing a message event. * * @extends Event */ class MessageEvent extends Event { /** * Create a new `MessageEvent`. * * @param {String} type The name of the event * @param {Object} [options] A dictionary object that allows for setting * attributes via object members of the same name * @param {*} [options.data=null] The message content */ constructor(type, options = {}) { super(type); this[kData] = options.data === undefined ? null : options.data; } /** * @type {*} */ get data() { return this[kData]; } } Object.defineProperty(MessageEvent.prototype, 'data', { enumerable: true }); /** * This provides methods for emulating the `EventTarget` interface. It's not * meant to be used directly. * * @mixin */ const EventTarget = { /** * Register an event listener. * * @param {String} type A string representing the event type to listen for * @param {Function} listener The listener to add * @param {Object} [options] An options object specifies characteristics about * the event listener * @param {Boolean} [options.once=false] A `Boolean` indicating that the * listener should be invoked at most once after being added. If `true`, * the listener would be automatically removed when invoked. * @public */ addEventListener(type, listener, options = {}) { let wrapper; if (type === 'message') { wrapper = function onMessage(data, isBinary) { const event = new MessageEvent('message', { data: isBinary ? data : data.toString() }); event[kTarget] = this; listener.call(this, event); }; } else if (type === 'close') { wrapper = function onClose(code, message) { const event = new CloseEvent('close', { code, reason: message.toString(), wasClean: this._closeFrameReceived && this._closeFrameSent }); event[kTarget] = this; listener.call(this, event); }; } else if (type === 'error') { wrapper = function onError(error) { const event = new ErrorEvent('error', { error, message: error.message }); event[kTarget] = this; listener.call(this, event); }; } else if (type === 'open') { wrapper = function onOpen() { const event = new Event('open'); event[kTarget] = this; listener.call(this, event); }; } else { return; } wrapper[kForOnEventAttribute] = !!options[kForOnEventAttribute]; wrapper[kListener] = listener; if (options.once) { this.once(type, wrapper); } else { this.on(type, wrapper); } }, /** * Remove an event listener. * * @param {String} type A string representing the event type to remove * @param {Function} handler The listener to remove * @public */ removeEventListener(type, handler) { for (const listener of this.listeners(type)) { if (listener[kListener] === handler && !listener[kForOnEventAttribute]) { this.removeListener(type, listener); break; } } } }; module.exports = { CloseEvent, ErrorEvent, Event, EventTarget, MessageEvent };