All files / src gatewayStorefront.ts

91.94% Statements 57/62
62.96% Branches 17/27
88.89% Functions 8/9
93.44% Lines 57/61
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 1465x 5x 5x 5x     5x     5x 5x     5x 5x   5x 26x           26x 26x   26x           26x 26x 26x 26x 26x   26x   26x               15x             15x 15x               30x       30x 4x     30x 30x     28x 28x   2x                 28x 23x 23x 23x 23x   5x 4x 2x 2x 2x 2x     1x           4x 2x 2x 2x 2x     2x     2x       25x 21x 21x         21x           5x 5x                  
import {EventEmitter} from "events";
import {EVENTS} from "./enums";
import fetch from "node-fetch";
import {DEFAULT_POLLING_INTERVAL, SATISFY_COMPILE_AMOUNT} from "./config";
import {Logger} from "./logger";
import {IExposeConfig, IGatewayConfiguration, SatisfyUpdateMap} from "./types";
import {container, TYPES} from "./base";
import {HttpClient} from "./client";
import {RouteConfiguration} from "puzzle-warden/dist/request-manager";
import {isDeepStrictEqual} from "util";
import warden from "puzzle-warden";
import Timer = NodeJS.Timer;
 
const logger = container.get(TYPES.Logger) as Logger;
const httpClient = container.get(TYPES.Client) as HttpClient;
 
export class GatewayStorefrontInstance {
  events: EventEmitter = new EventEmitter();
  config: IExposeConfig | undefined;
  assetUrl: string | undefined;
  name: string;
  url: string;
  authToken?: string;
  private satisfyAmount: number = SATISFY_COMPILE_AMOUNT;
  private intervalId: Timer | null | number = null;
 
  private pendingUpdate: SatisfyUpdateMap = {
    hash: null,
    count: 0
  };
 
  constructor(gatewayConfig: IGatewayConfiguration, authToken?: string, gatewayUpdateInterval?: number) {
    this.name = gatewayConfig.name;
    this.url = gatewayConfig.url;
    this.authToken = authToken;
    this.satisfyAmount = gatewayUpdateInterval || SATISFY_COMPILE_AMOUNT;
    httpClient.init('PuzzleJs Storefront');
 
    this.assetUrl = gatewayConfig.assetUrl;
 
    this.fetch();
  }
 
  /**
   * Starts updating gateway by polling with the provided miliseconds
   * @param {number} pollingInterval
   */
  startUpdating(pollingInterval: number = DEFAULT_POLLING_INTERVAL) {
    this.intervalId = setInterval(this.fetch.bind(this), pollingInterval);
  }
 
  /**
   * Stops polling
   */
  stopUpdating() {
    Eif (this.intervalId) {
      clearInterval(this.intervalId as Timer);
    }
  }
 
  /**
   * Fetches gateway condifuration and calls this.bind
   */
  private async fetch() {
    const headers = {
      gateway: this.name,
    };
 
    if (this.authToken) {
      headers["x-authorization"] = this.authToken;
    }
 
    try {
      const res = await fetch(this.url, {
        headers
      });
      const json = await res.json();
      this.update(json);
    } catch (e) {
      logger.error(`Failed to fetch gateway configuration: ${this.name}`, e);
    }
  }
 
  /**
   * Updates gateway configuration and if hash changed emits GATEWAY_UPDATED event
   * @param {IExposeConfig} data
   */
  private update(data: IExposeConfig) {
    if (!this.config) {
      logger.info(`Gateway is ready: ${this.name}`);
      this.connectWarden(data);
      this.config = data;
      this.events.emit(EVENTS.GATEWAY_READY, this);
    } else {
      if (data.hash !== this.config.hash) {
        if (this.satisfyUpdate(data.hash)) {
          logger.info(`Gateway is updated: ${this.name}`);
          this.connectWarden(data);
          this.config = data;
          this.events.emit(EVENTS.GATEWAY_UPDATED, this);
        }
      } else {
        this.resetUpdateStatus(data.hash);
      }
    }
  }
 
  private satisfyUpdate(hash: string): boolean {
    if (this.pendingUpdate.hash === hash) {
      this.pendingUpdate.count++;
      Eif (this.pendingUpdate.count <= this.satisfyAmount) {
        this.resetUpdateStatus(hash);
        return true;
      }
    } else {
      this.resetUpdateStatus(hash);
    }
 
    return false;
  }
 
  private connectWarden(data: IExposeConfig) {
    for (const key in data.fragments) {
      const fragment = data.fragments[key];
      Iif (fragment.warden && fragment.warden.identifier) {
        if (this.shouldUpdateWarden(key, fragment.warden)) {
          warden.register(key, fragment.warden);
        }
      } else {
        warden.unregisterRoute(key);
      }
    }
  }
 
  private resetUpdateStatus(hash: string) {
    this.pendingUpdate.hash = hash;
    this.pendingUpdate.count = 0;
  }
 
  private shouldUpdateWarden(fragmentName: string, newConfiguration: RouteConfiguration) {
    if (!this.config || !this.config.fragments[fragmentName]) return true;
    return !isDeepStrictEqual(this.config.fragments[fragmentName].warden, newConfiguration);
  }
}