From c07c78339b3263392452ea16be673d840a07ebf2 Mon Sep 17 00:00:00 2001 From: Luke Horvat Date: Thu, 11 Aug 2022 14:06:33 +0200 Subject: [PATCH] Use a different approach to prevent consumers from emitting meta events (#98) --- index.js | 53 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/index.js b/index.js index e8eaabb..1612c7d 100644 --- a/index.js +++ b/index.js @@ -10,19 +10,13 @@ const resolvedPromise = Promise.resolve(); const listenerAdded = Symbol('listenerAdded'); const listenerRemoved = Symbol('listenerRemoved'); -// Define a symbol that allows internal code to emit meta events, but prevents userland from doing so. -const metaEventsAllowed = Symbol('metaEventsAllowed'); - +let canEmitMetaEvents = false; let isGlobalDebugEnabled = false; -function assertEventName(eventName, allowMetaEvents) { +function assertEventName(eventName) { if (typeof eventName !== 'string' && typeof eventName !== 'symbol' && typeof eventName !== 'number') { throw new TypeError('`eventName` must be a string, symbol, or number'); } - - if (isMetaEvent(eventName) && allowMetaEvents !== metaEventsAllowed) { - throw new TypeError('`eventName` cannot be meta event `listenerAdded` or `listenerRemoved`'); - } } function assertListener(listener) { @@ -157,6 +151,17 @@ function defaultMethodNamesOrAssert(methodNames) { const isMetaEvent = eventName => eventName === listenerAdded || eventName === listenerRemoved; +function emitMetaEvent(emitter, eventName, eventData) { + if (isMetaEvent(eventName)) { + try { + canEmitMetaEvents = true; + emitter.emit(eventName, eventData); + } finally { + canEmitMetaEvents = false; + } + } +} + class Emittery { static mixin(emitteryPropertyName, methodNames) { methodNames = defaultMethodNamesOrAssert(methodNames); @@ -253,13 +258,13 @@ class Emittery { eventNames = Array.isArray(eventNames) ? eventNames : [eventNames]; for (const eventName of eventNames) { - assertEventName(eventName, metaEventsAllowed); + assertEventName(eventName); getListeners(this, eventName).add(listener); this.logIfDebugEnabled('subscribe', eventName, undefined); if (!isMetaEvent(eventName)) { - this.emit(listenerAdded, {eventName, listener}, metaEventsAllowed); + emitMetaEvent(this, listenerAdded, {eventName, listener}); } } @@ -271,13 +276,13 @@ class Emittery { eventNames = Array.isArray(eventNames) ? eventNames : [eventNames]; for (const eventName of eventNames) { - assertEventName(eventName, metaEventsAllowed); + assertEventName(eventName); getListeners(this, eventName).delete(listener); this.logIfDebugEnabled('unsubscribe', eventName, undefined); if (!isMetaEvent(eventName)) { - this.emit(listenerRemoved, {eventName, listener}, metaEventsAllowed); + emitMetaEvent(this, listenerRemoved, {eventName, listener}); } } } @@ -299,14 +304,18 @@ class Emittery { events(eventNames) { eventNames = Array.isArray(eventNames) ? eventNames : [eventNames]; for (const eventName of eventNames) { - assertEventName(eventName, metaEventsAllowed); + assertEventName(eventName); } return iterator(this, eventNames); } - async emit(eventName, eventData, allowMetaEvents) { - assertEventName(eventName, allowMetaEvents); + async emit(eventName, eventData) { + assertEventName(eventName); + + if (isMetaEvent(eventName) && !canEmitMetaEvents) { + throw new TypeError('`eventName` cannot be meta event `listenerAdded` or `listenerRemoved`'); + } this.logIfDebugEnabled('emit', eventName, eventData); @@ -332,8 +341,12 @@ class Emittery { ]); } - async emitSerial(eventName, eventData, allowMetaEvents) { - assertEventName(eventName, allowMetaEvents); + async emitSerial(eventName, eventData) { + assertEventName(eventName); + + if (isMetaEvent(eventName) && !canEmitMetaEvents) { + throw new TypeError('`eventName` cannot be meta event `listenerAdded` or `listenerRemoved`'); + } this.logIfDebugEnabled('emitSerial', eventName, eventData); @@ -364,7 +377,7 @@ class Emittery { this.logIfDebugEnabled('subscribeAny', undefined, undefined); anyMap.get(this).add(listener); - this.emit(listenerAdded, {listener}, metaEventsAllowed); + emitMetaEvent(this, listenerAdded, {listener}); return this.offAny.bind(this, listener); } @@ -377,7 +390,7 @@ class Emittery { this.logIfDebugEnabled('unsubscribeAny', undefined, undefined); - this.emit(listenerRemoved, {listener}, metaEventsAllowed); + emitMetaEvent(this, listenerRemoved, {listener}); anyMap.get(this).delete(listener); } @@ -427,7 +440,7 @@ class Emittery { } if (typeof eventName !== 'undefined') { - assertEventName(eventName, metaEventsAllowed); + assertEventName(eventName); } count += anyMap.get(this).size;