All files / src page.ts

84.75% Statements 50/59
61.9% Branches 13/21
88.89% Functions 16/18
84.48% Lines 49/58
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 1614x   4x                 4x   4x   4x 22x   22x 22x     22x   22x 22x 22x 22x   22x 22x                   4x 11x 11x 11x   11x                           11x                       15x 15x 15x               22x 18x   16x 16x 16x 1x   15x                 37x 20x                     4x 26x   29x           17x   29x                 2x 2x 2x 2x               17x 29x 27x                   15x 15x 15x 15x        
import {Template} from "./template";
import {GatewayStorefrontInstance} from "./gatewayStorefront";
import {EVENTS} from "./enums";
import {
  ICookieMap,
  ICookieObject,
  IFragmentEndpointHandler,
  IGatewayMap,
  IPageDependentGateways,
  IResponseHandlers
} from "./types";
import {DEBUG_INFORMATION, DEBUG_QUERY_NAME} from "./config";
import express from "express";
import {nrSegment, nrSegmentAsync} from "./decorators";
 
export class Page {
  ready = false;
  gatewayDependencies: IPageDependentGateways;
  responseHandlers: IResponseHandlers = {};
  private waitingCompilers: { [key: string]: Promise<IFragmentEndpointHandler> } = {};
  private template: Template;
  private rawHtml: string;
  private prgEnabled = false;
 
  constructor(html: string, gatewayMap: IGatewayMap, public name: string) {
    this.rawHtml = html;
    this.template = new Template(html, this.name);
    this.gatewayDependencies = this.template.getDependencies();
 
    this.preparePageDependencies(gatewayMap);
    this.checkPageReady();
  }
 
  /**
   * Request handler, compiles template if not exists
   * @param {{cookies: ICookieObject}} req
   * @param {object} res
   * @returns {Promise<void>}
   */
  @nrSegmentAsync("page.handle", true)
  async handle(req: { cookies: ICookieObject, query: { [name: string]: string } }, res: object) {
    const isDebug = DEBUG_INFORMATION || (req.query && req.query.hasOwnProperty(DEBUG_QUERY_NAME));
    const handlerVersion = this.getHandlerVersion(req.cookies);
    const compileVersion = `${handlerVersion}_${isDebug}`;
 
    Iif (!this.responseHandlers[compileVersion]) {
      const waitedCompileProcess = this.waitingCompilers[compileVersion];
      if (waitedCompileProcess) {
        await waitedCompileProcess;
      } else {
        this.waitingCompilers[compileVersion] = this.template.compile(req.cookies, isDebug);
        this.waitingCompilers[compileVersion]
          .then(handler => {
            this.responseHandlers[compileVersion] = handler;
          });
      }
    }
 
 
    this.responseHandlers[compileVersion](req, res);
  }
 
  post(req: express.Request, res: express.Response, next: express.NextFunction) {
    if (this.prgEnabled) {
      res.redirect(303, req.originalUrl);
    } else {
      next();
    }
  }
 
  async reCompile() {
    const defaultVersion = this.getHandlerVersion({}, true);
    this.responseHandlers[`${defaultVersion}_true`] = await this.template.compile({}, true, true);
    this.responseHandlers[`${defaultVersion}_false`] = await this.template.compile({}, false, true);
  }
 
  /**
   * Prepares dependencies and subscribes to events.
   * @param {IGatewayMap} gatewayMap
   */
  private preparePageDependencies(gatewayMap: IGatewayMap) {
    Object.keys(gatewayMap)
      .filter(gatewayName => this.gatewayDependencies.gateways[gatewayMap[gatewayName].name])
      .forEach(gatewayName => {
        gatewayMap[gatewayName].events.on(EVENTS.GATEWAY_UPDATED, this.gatewayUpdated.bind(this));
        this.gatewayDependencies.gateways[gatewayName].gateway = gatewayMap[gatewayName];
        if (!!gatewayMap[gatewayName].config) {
          this.gatewayDependencies.gateways[gatewayName].ready = true;
        } else {
          gatewayMap[gatewayName].events.once(EVENTS.GATEWAY_READY, this.gatewayReady.bind(this));
        }
      });
  }
 
  /**
   * Checks for page dependencies are ready
   */
  private checkPageReady(): void {
    if (Object.keys(this.gatewayDependencies.gateways).filter(gatewayName => !this.gatewayDependencies.gateways[gatewayName].ready).length === 0) {
      this.ready = true;
    }
  }
 
  /**
   * Based on test cookies returns handler key
   * @returns {string}
   * @param query
   * @param cookies
   */
  @nrSegment("page.getHandlerVersion", true)
  private getHandlerVersion(cookies: ICookieMap, preCompile = false) {
    return Object.values(this.gatewayDependencies.fragments)
      .reduce((key, fragment) => {
        return `${key}_${fragment.instance.name}|${fragment.instance.detectVersion(cookies, preCompile)}`;
      }, '');
  }
 
 
  private updatePrgStatus() {
    this.prgEnabled = Object
      .values(this.gatewayDependencies.fragments)
      .some(fragment => !!(fragment.instance.config && fragment.instance.config.prg && fragment.instance.primary));
  }
 
 
  /**
   * Called on GATEWAY_UPDATED
   * @param {GatewayStorefrontInstance} gateway
   */
  private gatewayUpdated(gateway: GatewayStorefrontInstance) {
    this.updateFragmentsConfig(gateway);
    this.template.load();
    this.updatePrgStatus();
    this.reCompile();
  }
 
  /**
   * Updates fragment config based on gateway configuration
   * @param {GatewayStorefrontInstance} gateway
   */
  private updateFragmentsConfig(gateway: GatewayStorefrontInstance) {
    Object.values(this.gatewayDependencies.fragments).forEach(fragment => {
      if (fragment.gateway === gateway.name && gateway.config) {
        fragment.instance.update(gateway.config.fragments[fragment.instance.name], gateway.url, gateway.name, gateway.assetUrl);
      }
    });
  }
 
  /**
   * Called on GATEWAY_READY
   * @param {GatewayStorefrontInstance} gateway
   */
  private gatewayReady(gateway: GatewayStorefrontInstance) {
    this.gatewayDependencies.gateways[gateway.name].ready = true;
    this.updateFragmentsConfig(gateway);
    this.updatePrgStatus();
    this.checkPageReady();
  }
}