import { Emitter, type DefaultEventMap } from 'rettime' import { Disposable } from './disposable' import { createLogger, type Logger } from './utils/logger' export enum InterceptorReadyState { INACTIVE = 'INACTIVE', ACTIVE = 'ACTIVE', DISPOSED = 'DISPOSED', } declare global { var __MSW_INTERCEPTORS_REGISTRY: Map> | undefined } const interceptorsRegistry = (globalThis.__MSW_INTERCEPTORS_REGISTRY ??= new Map>()) export abstract class Interceptor< Events extends DefaultEventMap, > extends Disposable { declare ['constructor']: typeof Interceptor protected emitter: Emitter protected readonly logger: Logger public readyState: InterceptorReadyState static readonly symbol: symbol #owners: Set static singleton>( InterceptorClass: (new () => T) & { symbol: symbol } ): T { const symbol = InterceptorClass.symbol const existing = interceptorsRegistry.get(symbol) if (existing instanceof InterceptorClass) { return existing } const newInstance = new InterceptorClass() interceptorsRegistry.set(symbol, newInstance) return newInstance } constructor() { super() this.#owners = new Set() this.readyState = InterceptorReadyState.INACTIVE this.emitter = new Emitter() this.logger = createLogger(this.#getLoggerNamespace()) } protected abstract predicate(): boolean protected abstract setup(): void public apply(owner: object = this): void { if (this.#owners.has(owner)) { return } if ( this.readyState !== InterceptorReadyState.ACTIVE && !this.predicate() ) { return } this.#owners.add(owner) if (this.readyState === InterceptorReadyState.ACTIVE) { return } try { this.setup() this.readyState = InterceptorReadyState.ACTIVE this.logger.info('apply') } catch (error) { this.dispose(owner) throw error } } public dispose(owner: object = this): void { if (!this.#owners.delete(owner)) { return } if (this.#owners.size > 0) { return } super.dispose() this.emitter.removeAllListeners() this.readyState = InterceptorReadyState.DISPOSED this.logger.info('disable') } public on: Emitter['on'] = (type, listener, options) => { return this.emitter.on(type, listener, options) } public once: Emitter['once'] = (type, listener, options) => { return this.emitter.once(type, listener, options) } public listeners: Emitter['listeners'] = (type) => { return this.emitter.listeners(type) } public listenerCount: Emitter['listenerCount'] = (type) => { return this.emitter.listenerCount(type) } public removeListener: Emitter['removeListener'] = ( type, listener ) => { return this.emitter.removeListener(type, listener) } public removeAllListeners: Emitter['removeAllListeners'] = (type) => { this.logger.info('removeAllListeners %o', { eventType: type ?? '*' }) return this.emitter.removeAllListeners(type) } #getLoggerNamespace(): string { const symbolDescription = this.constructor.symbol?.description if (symbolDescription) { return symbolDescription.replace(/-interceptor$/, '') } return this.constructor.name.replace(/Interceptor$/, '') } }