event-target.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. 'use strict';
  2. const { kForOnEventAttribute, kListener } = require('./constants');
  3. const kCode = Symbol('kCode');
  4. const kData = Symbol('kData');
  5. const kError = Symbol('kError');
  6. const kMessage = Symbol('kMessage');
  7. const kReason = Symbol('kReason');
  8. const kTarget = Symbol('kTarget');
  9. const kType = Symbol('kType');
  10. const kWasClean = Symbol('kWasClean');
  11. /**
  12. * Class representing an event.
  13. */
  14. class Event {
  15. /**
  16. * Create a new `Event`.
  17. *
  18. * @param {String} type The name of the event
  19. * @throws {TypeError} If the `type` argument is not specified
  20. */
  21. constructor(type) {
  22. this[kTarget] = null;
  23. this[kType] = type;
  24. }
  25. /**
  26. * @type {*}
  27. */
  28. get target() {
  29. return this[kTarget];
  30. }
  31. /**
  32. * @type {String}
  33. */
  34. get type() {
  35. return this[kType];
  36. }
  37. }
  38. Object.defineProperty(Event.prototype, 'target', { enumerable: true });
  39. Object.defineProperty(Event.prototype, 'type', { enumerable: true });
  40. /**
  41. * Class representing a close event.
  42. *
  43. * @extends Event
  44. */
  45. class CloseEvent extends Event {
  46. /**
  47. * Create a new `CloseEvent`.
  48. *
  49. * @param {String} type The name of the event
  50. * @param {Object} [options] A dictionary object that allows for setting
  51. * attributes via object members of the same name
  52. * @param {Number} [options.code=0] The status code explaining why the
  53. * connection was closed
  54. * @param {String} [options.reason=''] A human-readable string explaining why
  55. * the connection was closed
  56. * @param {Boolean} [options.wasClean=false] Indicates whether or not the
  57. * connection was cleanly closed
  58. */
  59. constructor(type, options = {}) {
  60. super(type);
  61. this[kCode] = options.code === undefined ? 0 : options.code;
  62. this[kReason] = options.reason === undefined ? '' : options.reason;
  63. this[kWasClean] = options.wasClean === undefined ? false : options.wasClean;
  64. }
  65. /**
  66. * @type {Number}
  67. */
  68. get code() {
  69. return this[kCode];
  70. }
  71. /**
  72. * @type {String}
  73. */
  74. get reason() {
  75. return this[kReason];
  76. }
  77. /**
  78. * @type {Boolean}
  79. */
  80. get wasClean() {
  81. return this[kWasClean];
  82. }
  83. }
  84. Object.defineProperty(CloseEvent.prototype, 'code', { enumerable: true });
  85. Object.defineProperty(CloseEvent.prototype, 'reason', { enumerable: true });
  86. Object.defineProperty(CloseEvent.prototype, 'wasClean', { enumerable: true });
  87. /**
  88. * Class representing an error event.
  89. *
  90. * @extends Event
  91. */
  92. class ErrorEvent extends Event {
  93. /**
  94. * Create a new `ErrorEvent`.
  95. *
  96. * @param {String} type The name of the event
  97. * @param {Object} [options] A dictionary object that allows for setting
  98. * attributes via object members of the same name
  99. * @param {*} [options.error=null] The error that generated this event
  100. * @param {String} [options.message=''] The error message
  101. */
  102. constructor(type, options = {}) {
  103. super(type);
  104. this[kError] = options.error === undefined ? null : options.error;
  105. this[kMessage] = options.message === undefined ? '' : options.message;
  106. }
  107. /**
  108. * @type {*}
  109. */
  110. get error() {
  111. return this[kError];
  112. }
  113. /**
  114. * @type {String}
  115. */
  116. get message() {
  117. return this[kMessage];
  118. }
  119. }
  120. Object.defineProperty(ErrorEvent.prototype, 'error', { enumerable: true });
  121. Object.defineProperty(ErrorEvent.prototype, 'message', { enumerable: true });
  122. /**
  123. * Class representing a message event.
  124. *
  125. * @extends Event
  126. */
  127. class MessageEvent extends Event {
  128. /**
  129. * Create a new `MessageEvent`.
  130. *
  131. * @param {String} type The name of the event
  132. * @param {Object} [options] A dictionary object that allows for setting
  133. * attributes via object members of the same name
  134. * @param {*} [options.data=null] The message content
  135. */
  136. constructor(type, options = {}) {
  137. super(type);
  138. this[kData] = options.data === undefined ? null : options.data;
  139. }
  140. /**
  141. * @type {*}
  142. */
  143. get data() {
  144. return this[kData];
  145. }
  146. }
  147. Object.defineProperty(MessageEvent.prototype, 'data', { enumerable: true });
  148. /**
  149. * This provides methods for emulating the `EventTarget` interface. It's not
  150. * meant to be used directly.
  151. *
  152. * @mixin
  153. */
  154. const EventTarget = {
  155. /**
  156. * Register an event listener.
  157. *
  158. * @param {String} type A string representing the event type to listen for
  159. * @param {(Function|Object)} handler The listener to add
  160. * @param {Object} [options] An options object specifies characteristics about
  161. * the event listener
  162. * @param {Boolean} [options.once=false] A `Boolean` indicating that the
  163. * listener should be invoked at most once after being added. If `true`,
  164. * the listener would be automatically removed when invoked.
  165. * @public
  166. */
  167. addEventListener(type, handler, options = {}) {
  168. for (const listener of this.listeners(type)) {
  169. if (
  170. !options[kForOnEventAttribute] &&
  171. listener[kListener] === handler &&
  172. !listener[kForOnEventAttribute]
  173. ) {
  174. return;
  175. }
  176. }
  177. let wrapper;
  178. if (type === 'message') {
  179. wrapper = function onMessage(data, isBinary) {
  180. const event = new MessageEvent('message', {
  181. data: isBinary ? data : data.toString()
  182. });
  183. event[kTarget] = this;
  184. callListener(handler, this, event);
  185. };
  186. } else if (type === 'close') {
  187. wrapper = function onClose(code, message) {
  188. const event = new CloseEvent('close', {
  189. code,
  190. reason: message.toString(),
  191. wasClean: this._closeFrameReceived && this._closeFrameSent
  192. });
  193. event[kTarget] = this;
  194. callListener(handler, this, event);
  195. };
  196. } else if (type === 'error') {
  197. wrapper = function onError(error) {
  198. const event = new ErrorEvent('error', {
  199. error,
  200. message: error.message
  201. });
  202. event[kTarget] = this;
  203. callListener(handler, this, event);
  204. };
  205. } else if (type === 'open') {
  206. wrapper = function onOpen() {
  207. const event = new Event('open');
  208. event[kTarget] = this;
  209. callListener(handler, this, event);
  210. };
  211. } else {
  212. return;
  213. }
  214. wrapper[kForOnEventAttribute] = !!options[kForOnEventAttribute];
  215. wrapper[kListener] = handler;
  216. if (options.once) {
  217. this.once(type, wrapper);
  218. } else {
  219. this.on(type, wrapper);
  220. }
  221. },
  222. /**
  223. * Remove an event listener.
  224. *
  225. * @param {String} type A string representing the event type to remove
  226. * @param {(Function|Object)} handler The listener to remove
  227. * @public
  228. */
  229. removeEventListener(type, handler) {
  230. for (const listener of this.listeners(type)) {
  231. if (listener[kListener] === handler && !listener[kForOnEventAttribute]) {
  232. this.removeListener(type, listener);
  233. break;
  234. }
  235. }
  236. }
  237. };
  238. module.exports = {
  239. CloseEvent,
  240. ErrorEvent,
  241. Event,
  242. EventTarget,
  243. MessageEvent
  244. };
  245. /**
  246. * Call an event listener
  247. *
  248. * @param {(Function|Object)} listener The listener to call
  249. * @param {*} thisArg The value to use as `this`` when calling the listener
  250. * @param {Event} event The event to pass to the listener
  251. * @private
  252. */
  253. function callListener(listener, thisArg, event) {
  254. if (typeof listener === 'object' && listener.handleEvent) {
  255. listener.handleEvent.call(listener, event);
  256. } else {
  257. listener.call(thisArg, event);
  258. }
  259. }