2023-10-03 11:14:36 +08:00
|
|
|
'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);
|
|
|
|
}
|
|
|
|
}
|