All files / platform/core/src/services/_shared pubSubServiceInterface.ts

88.46% Statements 46/52
71.42% Branches 10/14
80% Functions 12/15
90.19% Lines 46/51

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167                                              45288x 45288x 45288x     45288x 39787x   5501x     45288x 19974x                             19974x 84x     19890x 19890x 19890x 166653x 166653x                           45433x                     54706x 54706x   54706x 54706x   54706x 23842x 205790x             2558x 2558x 2558x 2558x 2558x 2558x   2558x 2558x 2558x 2558x 2558x 2558x 2558x 2558x                               145x 145x 145x             178x 178x   178x 84x 168x     178x                                          
import guid from '../../utils/guid';
import debounce from '../../utils/debounce';
 
/**
 * Consumer must implement:
 * this.listeners = {}
 * this.EVENTS = { "EVENT_KEY": "EVENT_VALUE" }
 */
export default {
  subscribe,
  _broadcastEvent,
  _unsubscribe,
  _isValidEvent,
};
 
/**
 * Subscribe to updates.
 *
 * @param {string} eventName The name of the event
 * @param {Function} callback Events callback
 * @return {Object} Observable object with actions
 */
function subscribe(eventName, callback) {
  if (this._isValidEvent(eventName)) {
    const listenerId = guid();
    const subscription = { id: listenerId, callback };
 
    // console.info(`Subscribing to '${eventName}'.`);
    if (Array.isArray(this.listeners[eventName])) {
      this.listeners[eventName].push(subscription);
    } else {
      this.listeners[eventName] = [subscription];
    }
 
    return {
      unsubscribe: () => this._unsubscribe(eventName, listenerId),
    };
  } else E{
    throw new Error(`Event ${eventName} not supported.`);
  }
}
 
/**
 * Unsubscribe to measurement updates.
 *
 * @param {string} eventName The name of the event
 * @param {string} listenerId The listeners id
 * @return void
 */
function _unsubscribe(eventName, listenerId) {
  if (!this.listeners[eventName]) {
    return;
  }
 
  const listeners = this.listeners[eventName];
  if (Array.isArray(listeners)) {
    this.listeners[eventName] = listeners.filter(({ id, callback }) => {
      callback?.clearDebounceTimeout?.();
      return id !== listenerId;
    });
  } else E{
    this.listeners[eventName] = undefined;
  }
}
 
/**
 * Check if a given event is valid.
 *
 * @param {string} eventName The name of the event
 * @return {boolean} Event name validation
 */
function _isValidEvent(eventName) {
  return Object.values(this.EVENTS).includes(eventName);
}
 
/**
 * Broadcasts changes.
 *
 * @param {string} eventName - The event name
 * @param {func} callbackProps - Properties to pass callback
 * @return void
 */
function _broadcastEvent(eventName, callbackProps) {
  const hasListeners = Object.keys(this.listeners).length > 0;
  const hasCallbacks = Array.isArray(this.listeners[eventName]);
 
  const event = new CustomEvent(eventName, { detail: callbackProps });
  document.body.dispatchEvent(event);
 
  if (hasListeners && hasCallbacks) {
    this.listeners[eventName].forEach(listener => {
      listener.callback(callbackProps);
    });
  }
}
 
/** Export a PubSubService class to be used instead of the individual items */
export class PubSubService {
  EVENTS: Record<string, string>;
  subscribe: (eventName: string, callback: (data: unknown) => void) => { unsubscribe: () => void };
  _broadcastEvent: (eventName: string, callbackProps: unknown) => void;
  _unsubscribe: (eventName: string, listenerId: string) => void;
  _isValidEvent: (eventName: string) => boolean;
  listeners: Record<string, Array<{ id: string; callback: (data: unknown) => void }> | undefined>;
  unsubscriptions: Array<() => void>;
  constructor(EVENTS: Record<string, string>) {
    this.EVENTS = EVENTS;
    this.subscribe = subscribe;
    this._broadcastEvent = _broadcastEvent;
    this._unsubscribe = _unsubscribe;
    this._isValidEvent = _isValidEvent;
    this.listeners = {};
    this.unsubscriptions = [];
  }
 
  /**
   * Subscribe to updates with debouncing to limit callback execution frequency
   * @param eventName - The name of the event
   * @param callback - Events callback
   * @param wait - Debounce wait time in milliseconds
   * @param immediate - If true, trigger on the leading edge instead of trailing
   */
  subscribeDebounced(
    eventName: string,
    callback: (data: unknown) => void,
    wait = 300,
    immediate = false
  ) {
    if (this._isValidEvent(eventName)) {
      const debouncedCallback = debounce(callback, wait, immediate);
      return this.subscribe(eventName, debouncedCallback);
    } else E{
      throw new Error(`Event ${eventName} not supported.`);
    }
  }
 
  reset() {
    this.unsubscriptions.forEach(unsub => unsub());
    this.unsubscriptions = [];
 
    Object.keys(this.listeners).forEach(eventName =>
      this.listeners[eventName].forEach(({ callback }) => {
        callback?.clearDebounceTimeout?.();
      })
    );
    this.listeners = {};
  }
 
  /**
   * Creates an event that records whether or not someone
   * has consumed it.  Call eventData.consume() to consume the event.
   * Check eventData.isConsumed to see if it is consumed or not.
   * @param props - to include in the event
   */
  protected createConsumableEvent<T extends Record<string, unknown>>(
    props: T
  ): T & { isConsumed: boolean; consume: () => void } {
    return {
      ...props,
      isConsumed: false,
      consume: function Consume() {
        this.isConsumed = true;
      },
    };
  }
}