Skip to content

Commit

Permalink
[major] Overhaul event classes
Browse files Browse the repository at this point in the history
- Remove non-standard `OpenEvent` class.
- Make properties read-only.
- Update constructor signatures to match the ones defined by the HTML
  standard.
  • Loading branch information
lpinca committed Jul 29, 2021
1 parent bd7febb commit 21e6500
Show file tree
Hide file tree
Showing 4 changed files with 437 additions and 84 deletions.
203 changes: 144 additions & 59 deletions lib/event-target.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,112 +2,171 @@

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.
*
* @private
*/
class Event {
/**
* Create a new `Event`.
*
* @param {String} type The name of the event
* @param {Object} target A reference to the target to which the event was
* dispatched
* @throws {TypeError} If the `type` argument is not specified
*/
constructor(type, target) {
this.target = target;
this.type = type;
constructor(type) {
this[kTarget] = null;
this[kType] = type;
}
}

/**
* Class representing a message event.
*
* @extends Event
* @private
*/
class MessageEvent extends Event {
/**
* Create a new `MessageEvent`.
*
* @param {(String|Buffer|ArrayBuffer|Buffer[])} data The received data
* @param {WebSocket} target A reference to the target to which the event was
* dispatched
* @type {*}
*/
constructor(data, target) {
super('message', target);
get target() {
return this[kTarget];
}

this.data = data;
/**
* @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
* @private
*/
class CloseEvent extends Event {
/**
* Create a new `CloseEvent`.
*
* @param {Number} code The status code explaining why the connection is being
* closed
* @param {String} reason A human-readable string explaining why the
* connection is closing
* @param {WebSocket} target A reference to the target to which the event was
* dispatched
* @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(code, reason, target) {
super('close', target);
constructor(type, options = {}) {
super(type);

this.wasClean = target._closeFrameReceived && target._closeFrameSent;
this.reason = reason;
this.code = code;
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 open event.
* Class representing an error event.
*
* @extends Event
* @private
*/
class OpenEvent extends Event {
class ErrorEvent extends Event {
/**
* Create a new `OpenEvent`.
* Create a new `ErrorEvent`.
*
* @param {WebSocket} target A reference to the target to which the event was
* dispatched
* @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}
*/
constructor(target) {
super('open', target);
get message() {
return this[kMessage];
}
}

Object.defineProperty(ErrorEvent.prototype, 'error', { enumerable: true });
Object.defineProperty(ErrorEvent.prototype, 'message', { enumerable: true });

/**
* Class representing an error event.
* Class representing a message event.
*
* @extends Event
* @private
*/
class ErrorEvent extends Event {
class MessageEvent extends Event {
/**
* Create a new `ErrorEvent`.
* Create a new `MessageEvent`.
*
* @param {Object} error The error that generated this event
* @param {WebSocket} target A reference to the target to which the event was
* dispatched
* @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(error, target) {
super('error', target);
constructor(type, options = {}) {
super(type);

this[kData] = options.data === undefined ? null : options.data;
}

this.message = error.message;
this.error = error;
/**
* @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.
Expand All @@ -132,20 +191,40 @@ const EventTarget = {

if (type === 'message') {
wrapper = function onMessage(data, isBinary) {
const event = new MessageEvent(isBinary ? data : data.toString(), this);
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) {
listener.call(this, new CloseEvent(code, message.toString(), this));
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) {
listener.call(this, new ErrorEvent(error, this));
const event = new ErrorEvent('error', {
error,
message: error.message
});

event[kTarget] = this;
listener.call(this, event);
};
} else if (type === 'open') {
wrapper = function onOpen() {
listener.call(this, new OpenEvent(this));
const event = new Event('open');

event[kTarget] = this;
listener.call(this, event);
};
} else {
return;
Expand Down Expand Up @@ -178,4 +257,10 @@ const EventTarget = {
}
};

module.exports = EventTarget;
module.exports = {
CloseEvent,
ErrorEvent,
Event,
EventTarget,
MessageEvent
};
4 changes: 3 additions & 1 deletion lib/websocket.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ const {
kWebSocket,
NOOP
} = require('./constants');
const { addEventListener, removeEventListener } = require('./event-target');
const {
EventTarget: { addEventListener, removeEventListener }
} = require('./event-target');
const { format, parse } = require('./extension');
const { toBuffer } = require('./buffer-util');

Expand Down
Loading

0 comments on commit 21e6500

Please sign in to comment.