You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
292 lines
7.1 KiB
292 lines
7.1 KiB
'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|Object)} handler 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, handler, options = {}) { |
|
for (const listener of this.listeners(type)) { |
|
if ( |
|
!options[kForOnEventAttribute] && |
|
listener[kListener] === handler && |
|
!listener[kForOnEventAttribute] |
|
) { |
|
return; |
|
} |
|
} |
|
|
|
let wrapper; |
|
|
|
if (type === 'message') { |
|
wrapper = function onMessage(data, isBinary) { |
|
const event = new MessageEvent('message', { |
|
data: isBinary ? data : data.toString() |
|
}); |
|
|
|
event[kTarget] = this; |
|
callListener(handler, 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; |
|
callListener(handler, this, event); |
|
}; |
|
} else if (type === 'error') { |
|
wrapper = function onError(error) { |
|
const event = new ErrorEvent('error', { |
|
error, |
|
message: error.message |
|
}); |
|
|
|
event[kTarget] = this; |
|
callListener(handler, this, event); |
|
}; |
|
} else if (type === 'open') { |
|
wrapper = function onOpen() { |
|
const event = new Event('open'); |
|
|
|
event[kTarget] = this; |
|
callListener(handler, this, event); |
|
}; |
|
} else { |
|
return; |
|
} |
|
|
|
wrapper[kForOnEventAttribute] = !!options[kForOnEventAttribute]; |
|
wrapper[kListener] = handler; |
|
|
|
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|Object)} 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 |
|
}; |
|
|
|
/** |
|
* Call an event listener |
|
* |
|
* @param {(Function|Object)} listener The listener to call |
|
* @param {*} thisArg The value to use as `this`` when calling the listener |
|
* @param {Event} event The event to pass to the listener |
|
* @private |
|
*/ |
|
function callListener(listener, thisArg, event) { |
|
if (typeof listener === 'object' && listener.handleEvent) { |
|
listener.handleEvent.call(listener, event); |
|
} else { |
|
listener.call(thisArg, event); |
|
} |
|
}
|
|
|