UNPKG

@openziti/ziti-browzer-sw

Version:

Service Worker used as part of the OpenZiti browZer stack

1,571 lines 86.9 kB
define(['exports', './ziti-browzer-sw-workbox-core-56411b90', './ziti-browzer-sw-workbox-strategies-80d7d1b3', './ziti-browzer-sw-misc-7bbd97fe', './ziti-browzer-sw--ziti-browzer-core--427625e5', './ziti-browzer-sw--lodash-es--5f72a803'], (function (exports, zitiBrowzerSwWorkboxCore, zitiBrowzerSwWorkboxStrategies, zitiBrowzerSwMisc, zitiBrowzerSw_ZitiBrowzerCore_, zitiBrowzerSw_LodashEs_) { 'use strict';

  var name = "@openziti/ziti-browzer-sw-workbox-strategies";
  var version = "0.89.0";
  var type = "module";
  var description = "Custom Workbox Strategies used as part of the OpenZiti browZer stack";
  var main = "src/index.js";
  var scripts = {
  	build: "run-s clean bundle-sw",
  	"bundle-sw": "rollup -c",
  	clean: "rimraf dist",
  	test: "node_modules/.bin/mocha --require @babel/register",
  	preinstall: "node -e \"if(process.env.npm_execpath.indexOf('yarn') === -1) throw new Error('You must use Yarn to install, not NPM')\""
  };
  var company = "NetFoundry";
  var keywords = [
  	"ziti",
  	"zero trust",
  	"zero-trust",
  	"browZer",
  	"browser",
  	"js",
  	"javascript",
  	"workbox-strategy",
  	"netfoundry",
  	"workbox"
  ];
  var license = "Apache-2.0";
  var devDependencies = {
  	"@babel/core": "^7.17.5",
  	"@babel/polyfill": "^7.2.5",
  	"@babel/preset-env": "^7.16.11",
  	"@babel/register": "^7.17.0",
  	"@rollup/plugin-babel": "^5.2.0",
  	"@rollup/plugin-commonjs": "^22.0.0",
  	"@rollup/plugin-json": "^6.1.0",
  	"@rollup/plugin-multi-entry": "^4.0.0",
  	"@rollup/plugin-node-resolve": "^13.3.0",
  	"@rollup/plugin-replace": "^4.0.0",
  	"@rollup/plugin-typescript": "^12.1.2",
  	"@surma/rollup-plugin-off-main-thread": "^2.2.3",
  	"@types/jwt-decode": "^3.1.0",
  	"@types/lodash-es": "^4.17.6",
  	"@types/node": "^22.13.5",
  	"@types/uuid": "^8.3.4",
  	"babel-plugin-dynamic-import-node": "^2.3.3",
  	"babel-plugin-transform-async-to-promises": "^0.8.15",
  	"common-tags": "^1.8.0",
  	cpy: "^9.0.0",
  	del: "^6.0.0",
  	execa: "^4.0.3",
  	globby: "^14.1.0",
  	gulp: "^4.0.2",
  	mocha: "^9.2.1",
  	"npm-run-all": "^4.1.5",
  	rimraf: "^3.0.2",
  	rollup: "^2.70.2",
  	"rollup-plugin-commonjs": "^10.1.0",
  	"rollup-plugin-node-resolve": "^5.2.0",
  	"rollup-plugin-terser": "^7.0.2",
  	"rollup-plugin-typescript2": "^0.31.2",
  	tslib: "^2.8.1",
  	typescript: "^5.2.2"
  };
  var workbox = {
  	browserNamespace: "workbox.zitiStrategies",
  	outputFilename: "ziti-browzer-sw-workbox-strategies",
  	packageType: "sw"
  };
  var repository = {
  	type: "git",
  	url: "https://github.com/openziti/ziti-browzer-sw-workbox-strategies.git"
  };
  var author = {
  	name: "NetFoundry",
  	url: "http://netfoundry.io",
  	email: "openziti@netfoundry.io"
  };
  var bugs = {
  	url: "https://github.com/openziti/ziti-browzer-sw-workbox-strategies/issues"
  };
  var homepage = "https://github.com/openziti/ziti-browzer-sw-workbox-strategies";
  var babel = {
  	plugins: [
  		"dynamic-import-node"
  	],
  	presets: [
  		"@babel/preset-env"
  	]
  };
  var dependencies = {
  	"@openziti/ziti-browzer-core": "^0.68.0",
  	"async-mutex": "^0.5.0",
  	cheerio: "^1.0.0-rc.12",
  	"jwt-decode": "^3.1.2",
  	"lodash-es": "^4.17.21",
  	uuid: "^11.1.0",
  	"workbox-core": "^6.6.0",
  	"workbox-expiration": "^6.6.0",
  	"workbox-strategies": "^6.6.0"
  };
  var packageManager = "yarn@4.0.2+sha512.4e502bea682e7d8004561f916f1da2dfbe6f718024f6aa50bf8cd86f38ea3a94a7f1bf854a9ca666dd8eafcfb8d44baaa91bf5c7876e79a7aeac952c332f0e88";
  var pjson = {
  	name: name,
  	version: version,
  	type: type,
  	description: description,
  	main: main,
  	scripts: scripts,
  	company: company,
  	keywords: keywords,
  	license: license,
  	devDependencies: devDependencies,
  	workbox: workbox,
  	repository: repository,
  	author: author,
  	bugs: bugs,
  	homepage: homepage,
  	babel: babel,
  	dependencies: dependencies,
  	packageManager: packageManager
  };

  var regexZBR = new RegExp(/ziti-browzer-runtime-\w{8}\.js/, 'g');
  var regexZBRnaked = new RegExp(/ziti-browzer-runtime\.js/, 'gi');
  var regexZBRLogo = new RegExp(/ziti-browzer-logo/, 'g');
  var regexZBRcss = new RegExp(/ziti-browzer-css-\w{8}\.css/, 'g');
  var regexZBRCORS = new RegExp(/ziti-cors-proxy/, 'g');
  var regexEdgeClt = new RegExp(/\/edge\/client\/v1/, 'g');
  var regexZBWASM = new RegExp(/libcrypto.*.wasm/, 'g');
  var regexPolipop = new RegExp(/polipop/, 'g');
  var regexCannySetup = new RegExp(/canny-setup/, 'g');
  var regexOAUTHTOKEN = new RegExp(/\/oauth\/token/, 'g');
  var regexFavicon = new RegExp(/\/favicon\.ico/, 'g');
  var regexJSDelivr = new RegExp(/jsdelivr.net/, 'g');
  var regexZentaoRefreshRandom = new RegExp(/\/index\.php\?m\=user\&f\=refreshRandom/, 'g');
  var regexZentaoLogin = new RegExp(/\/index\.php\?m\=user\&f\=login/, 'g');
  var regexZentaoLoginReferer = new RegExp(/\/index\.php\?m\=user\&f\=login\&referer\=/, 'g');
  var regexAtImport = new RegExp(/\@import/, 'gi');
  var regexSlash = new RegExp(/^\/$/, 'g');
  var regexTextHtml = new RegExp(/text\/html/, 'i');
  var regexTextXml = new RegExp(/text\/xml/, 'i');
  var regexMstsXml = new RegExp(/application\/x\-msts\-radc\+xml/, 'i');
  var regexAppJSON = new RegExp(/application\/json/, 'i');
  var regexVideo = new RegExp(/video/, 'i');
  var regexMpeg = new RegExp(/mpeg/, 'i');
  var regexControllerAPI;
  /**
   * An implementation of a Ziti network request strategy.
   *
   * If the network request fails, and there is no cache match, this will throw
   * a `WorkboxError` exception.
   *
   */
  class ZitiFirstStrategy extends zitiBrowzerSwWorkboxStrategies.CacheFirst /* NetworkFirst */ {
      /**
       * @param {Object} [options]
       * @param {string} [options._zitiBrowzerServiceWorkerGlobalScope] config dsts
       * @param {string} [options._logLevel] Which level to log at
       * @param {string} [options._controllerApi] Location of Ziti Controller
       * @param {number} [options.zitiNetworkTimeoutSeconds] If set, any network requests
       * that fail to respond within the timeout will fallback to the cache.
       *
       */
      constructor(options = {}) {
          super(options);
          this._zitiBrowzerServiceWorkerGlobalScope = options.zitiBrowzerServiceWorkerGlobalScope || 0;
          this._zitiNetworkTimeoutSeconds = options.zitiNetworkTimeoutSeconds || 0;
          this._logLevel = options.logLevel || 'Silent';
          this._controllerApi = options.controllerApi || '<controllerApi-not-configured>';
          this._initialized = false;
          this._targetServiceHost = '';
          var controllerAPIURL = new URL(this._controllerApi);
          regexControllerAPI = new RegExp(controllerAPIURL.host, 'g');
          this._initializationMutex = new zitiBrowzerSwMisc.Mutex();
          this._uuid = options.uuid;
          this._rootPaths = [];
          this._core = new zitiBrowzerSw_ZitiBrowzerCore_.ZitiBrowzerCore({});
          this.logger = this._core.createZitiLogger({
              logLevel: this._logLevel,
              suffix: 'ZBSW',
              useSWPostMessage: options.eruda,
              zitiBrowzerServiceWorkerGlobalScope: this._zitiBrowzerServiceWorkerGlobalScope,
          });
          this.logger.trace(`ZitiFirstStrategy ctor completed`);
      }
      parseCSP(policy) {
          const result = {};
          policy.split(";").forEach((directive) => {
              const [directiveKey, ...directiveValue] = directive.trim().split(/\s+/g);
              if (directiveKey &&
                  !Object.prototype.hasOwnProperty.call(result, directiveKey)) {
                  result[directiveKey] = directiveValue;
              }
          });
          return result;
      }
      ;
      buildCSP({ directives }) {
          const namesSeen = new Set();
          const result = [];
          Object.keys(directives).forEach((originalName) => {
              const name = originalName.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
              if (namesSeen.has(name)) {
                  throw new Error(`${originalName} is specified more than once`);
              }
              namesSeen.add(name);
              let value = directives[originalName];
              if (Array.isArray(value)) {
                  value = value.join(" ");
              }
              else if (value === true) {
                  value = "";
              }
              if (value) {
                  result.push(`${name} ${value}`);
              }
              else if (value !== false) {
                  result.push(name);
              }
          });
          return result.join("; ");
      }
      ;
      generateNewCSP(val) {
          let origCSP = this.parseCSP(val);
          this.logger.trace(`generateNewCSP() origCSP: `, origCSP);
          let idpURL = new URL(this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.idp.host);
          let idpHost = idpURL.host;
          let controllerURL = new URL(this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.controller.api);
          let controllerHost = controllerURL.host;
          if (origCSP['default-src']) {
              origCSP['default-src'].push(`https://*.netfoundry.io:*`);
              origCSP['default-src'].push(`https://*.cloudziti.io`);
              origCSP['default-src'].push(`wss://*.netfoundry.io:*`);
              origCSP['default-src'].push("data:");
              origCSP['default-src'].push("https://opencollective.com");
          }
          if (origCSP['script-src']) {
              origCSP['script-src'].push(`${this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.idp.host}`.replace('https://', ''));
              origCSP['script-src'].push(`canny.io`);
              if (!origCSP['script-src'].includes("'unsafe-eval'")) {
                  origCSP['script-src'].push("'unsafe-eval'");
              }
          }
          if (origCSP['connect-src']) {
              origCSP['connect-src'].push(`${idpHost}`);
              origCSP['connect-src'].push(`${this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.self.host}`);
              origCSP['connect-src'].push(`${controllerHost}`);
              origCSP['connect-src'].push(`https://*.netfoundry.io:*`);
              origCSP['connect-src'].push(`https://*.cloudziti.io`);
              origCSP['connect-src'].push(`wss://*.netfoundry.io:*`);
              origCSP['connect-src'].push(`wss://localhost:*`);
              if (!origCSP['connect-src'].includes("data:")) {
                  origCSP['connect-src'].push("data:");
              }
          }
          if (origCSP['img-src']) {
              origCSP['img-src'].push(`data:`);
              origCSP['img-src'].push(`${this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.self.host}`);
              origCSP['img-src'].push(`*`);
          }
          if (origCSP['font-src']) {
              if (!origCSP['font-src'].includes("data:")) {
                  origCSP['font-src'].push("data:");
              }
          }
          let directives = {};
          if (!zitiBrowzerSw_LodashEs_.isUndefined(origCSP['child-src'])) {
              directives.childSrc = origCSP['child-src'];
          }
          if (!zitiBrowzerSw_LodashEs_.isUndefined(origCSP['connect-src'])) {
              directives.connectSrc = origCSP['connect-src'];
          }
          if (!zitiBrowzerSw_LodashEs_.isUndefined(origCSP['default-src'])) {
              directives.defaultSrc = origCSP['default-src'];
          }
          if (!zitiBrowzerSw_LodashEs_.isUndefined(origCSP['font-src'])) {
              directives.fontSrc = origCSP['font-src'];
          }
          if (!zitiBrowzerSw_LodashEs_.isUndefined(origCSP['frame-ancestors'])) {
              directives.frameAncestors = origCSP['frame-ancestors'];
          }
          if (!zitiBrowzerSw_LodashEs_.isUndefined(origCSP['frame-src'])) {
              directives.frameSrc = origCSP['frame-src'];
          }
          if (!zitiBrowzerSw_LodashEs_.isUndefined(origCSP['img-src'])) {
              directives.imgSrc = origCSP['img-src'];
          }
          if (!zitiBrowzerSw_LodashEs_.isUndefined(origCSP['media-src'])) {
              directives.mediaSrc = origCSP['media-src'];
          }
          if (!zitiBrowzerSw_LodashEs_.isUndefined(origCSP['object-src'])) {
              directives.objectSrc = origCSP['object-src'];
          }
          if (!zitiBrowzerSw_LodashEs_.isUndefined(origCSP['script-src'])) {
              directives.scriptSrc = origCSP['script-src'];
          }
          if (!zitiBrowzerSw_LodashEs_.isUndefined(origCSP['style-src'])) {
              directives.styleSrc = origCSP['style-src'];
          }
          if (!zitiBrowzerSw_LodashEs_.isUndefined(origCSP['worker-src'])) {
              directives.workerSrc = origCSP['worker-src'];
          }
          let newCSP = this.buildCSP({ directives });
          let newCSParray = this.parseCSP(newCSP);
          this.logger.trace(`generateNewCSP() newCSP: `, newCSParray);
          return newCSP;
      }
      /**
       * Remain in lazy-sleepy loop until z-b-runtime sends us the _zitiConfig
       *
       */
      async await_zitiConfig(requestUrl, _handler) {
          let self = this;
          let ctr = 0;
          let waitTime = 100;
          return new Promise((resolve, _reject) => {
              (async function waitFor_zitiConfig() {
                  if (zitiBrowzerSw_LodashEs_.isUndefined(self._zitiBrowzerServiceWorkerGlobalScope._zitiConfig) || zitiBrowzerSw_LodashEs_.isUndefined(self._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.access_token)) {
                      ctr++;
                      self.logger.trace(`await_zitiConfig: ...waiting [${ctr}] for [${requestUrl}]`);
                      if (ctr == 5) { // kick the ZBR, and ask for the config
                          self.logger.trace('await_zitiConfig: sending ZITI_CONFIG_NEEDED msg to ZBR');
                          self._zitiBrowzerServiceWorkerGlobalScope._sendMessageToClients({ type: 'ZITI_CONFIG_NEEDED' });
                          setTimeout(waitFor_zitiConfig, waitTime);
                      }
                      else if (ctr == 10) { // only do the unregister once
                          self.logger.trace(`await_zitiConfig: initiating unregister`);
                          // Let's try and 'reboot' the ZBR/SW pair
                          await self._zitiBrowzerServiceWorkerGlobalScope._unregister();
                          return resolve(-1);
                      }
                      else {
                          setTimeout(waitFor_zitiConfig, waitTime);
                      }
                  }
                  else {
                      self.logger.trace(`await_zitiConfig: config acquired for [${requestUrl}]`);
                      return resolve(0);
                  }
              })();
          });
      }
      /**
       * Remain in lazy-sleepy loop until z-b-runtime notifies us that it has completed initialization
       *
       */
      async await_zbrInitialized(request) {
          let self = this;
          let ctr = 0;
          return new Promise((resolve, reject) => {
              (function waitFor_zbrInitialized() {
                  if (self._zitiBrowzerServiceWorkerGlobalScope._zbrReloadPending) { // this gets reset when ZBR sends the SW the 
                      self.logger.trace(`await_zbrInitialized: ...waiting for [${request.url}]`);
                      ctr++;
                      if (ctr > 40) {
                          return reject();
                      }
                      setTimeout(waitFor_zbrInitialized, 250);
                  }
                  else {
                      self.logger.trace(`await_zbrInitialized: ...acquired for [${request.url}]`);
                      self.logger.trace(`await_zbrInitialized: ...setting logLevel to [${self._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.sw.logLevel}]`);
                      self.logger.logLevel = self._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.sw.logLevel;
                      return resolve();
                  }
              })();
          });
      }
      accessTokenRefreshedEventHandler(accessTokenRefreshedEvent) {
          this.logger.trace(`accessTokenRefreshedEventHandler() ${accessTokenRefreshedEvent}`);
          this._zitiBrowzerServiceWorkerGlobalScope._currentAPISession = this._zitiBrowzerServiceWorkerGlobalScope._zitiContext.getCurrentAPISession();
          this._zitiBrowzerServiceWorkerGlobalScope._accessTokenRefreshed(); // This will cause ZBR to ask us for the new data
      }
      idpAuthHealthEventHandler(idpAuthHealthEvent) {
          this.logger.trace(`idpAuthHealthEventHandler() ${idpAuthHealthEvent}`);
          if (idpAuthHealthEvent.expired) {
              this.logger.trace(`idpAuthHealthEventHandler: authToken has expired and will be torn down`);
              setTimeout(function (_zitiBrowzerServiceWorkerGlobalScope) {
                  _zitiBrowzerServiceWorkerGlobalScope._accessTokenExpired(); // This will cause a logout with the IdP
              }, 10, this._zitiBrowzerServiceWorkerGlobalScope);
              setTimeout(function (_zitiBrowzerServiceWorkerGlobalScope) {
                  _zitiBrowzerServiceWorkerGlobalScope._unregister(); // Let's try and 'reboot' the ZBR/SW pair
              }, 500, this._zitiBrowzerServiceWorkerGlobalScope);
          }
      }
      async noConfigForServiceEventHandler(noConfigForServiceEvent) {
          this.logger.trace(`noConfigForServiceEventHandler() `, noConfigForServiceEvent);
          await this._zitiBrowzerServiceWorkerGlobalScope._noConfigForService(noConfigForServiceEvent);
      }
      async noConfigProtocolForServiceEventHandler(noConfigProtocolForServiceEvent) {
          this.logger.trace(`noConfigProtocolForServiceEventHandler() `, noConfigProtocolForServiceEvent);
          await this._zitiBrowzerServiceWorkerGlobalScope._noConfigProtocolForService(noConfigProtocolForServiceEvent);
      }
      async WSSEnabledEdgeRouterConnectionErrorEventHandler(wssERConnectionErrorEvent) {
          this.logger.trace(`WSSEnabledEdgeRouterConnectionErrorEventHandler() `, wssERConnectionErrorEvent);
          await this._zitiBrowzerServiceWorkerGlobalScope._wssERConnectionError(wssERConnectionErrorEvent);
      }
      async ControllerConnectionErrorEventHandler(controllerConnectionErrorEvent) {
          this.logger.trace(`ControllerConnectionErrorEventHandler() `, controllerConnectionErrorEvent);
          await this._zitiBrowzerServiceWorkerGlobalScope._controllerConnectionError(controllerConnectionErrorEvent);
      }
      async sessionCreationErrorEventHandler(sessionCreationErrorEvent) {
          this.logger.trace(`sessionCreationErrorEventHandler() `, sessionCreationErrorEvent);
          await this._zitiBrowzerServiceWorkerGlobalScope._sessionCreationError(sessionCreationErrorEvent);
          this._zitiBrowzerServiceWorkerGlobalScope._unregisterNoReload();
      }
      async noServiceEventHandler(noServiceEvent) {
          this.logger.trace(`noServiceEventHandler() `, noServiceEvent);
          await this._zitiBrowzerServiceWorkerGlobalScope._noService(noServiceEvent);
          this._zitiBrowzerServiceWorkerGlobalScope._unregisterNoReload();
      }
      async invalidAuthEventHandler(invalidAuthEvent) {
          this.logger.trace(`invalidAuthEventHandler() `, invalidAuthEvent);
          await this._zitiBrowzerServiceWorkerGlobalScope._invalidAuth(invalidAuthEvent);
      }
      async noWSSRoutersEventHandler(noWSSRoutersEvent) {
          this.logger.trace(`noWSSRoutersEventHandler() `, noWSSRoutersEvent);
          await this._zitiBrowzerServiceWorkerGlobalScope._noWSSRouters(noWSSRoutersEvent);
      }
      async channelConnectFailEventHandler(channelConnectFailEvent) {
          this.logger.trace(`channelConnectFailEventHandler() `, channelConnectFailEvent);
          await this._zitiBrowzerServiceWorkerGlobalScope._channelConnectFail(channelConnectFailEvent);
      }
      async xgressEventHandler(xgressEvent) {
          this._zitiBrowzerServiceWorkerGlobalScope._xgressEvent(xgressEvent);
      }
      async nestedTLSHandshakeTimeoutEventHandler(nestedTLSHandshakeTimeoutEvent) {
          this._zitiBrowzerServiceWorkerGlobalScope._nestedTLSHandshakeTimeout(nestedTLSHandshakeTimeoutEvent);
      }
      /**
       * Do all work necessary to initialize the ZitiFirstStrategy instance.
       *
       */
      async _initialize() {
          // Run the init sequence within a critical-section
          await this._initializationMutex.runExclusive(async () => {
              return new Promise(async (resolve, _) => {
                  if (!this._initialized) {
                      this.logger.trace(`_initialize: entered`);
                      if (zitiBrowzerSw_LodashEs_.isUndefined(this._zitiContext)) {
                          this._zitiContext = this._core.createZitiContext({
                              logger: this.logger,
                              controllerApi: this._controllerApi,
                              sdkType: pjson.name,
                              sdkVersion: pjson.version,
                              sdkBranch: this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.sdkBranch,
                              sdkRevision: this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.sdkRevision,
                              token_type: this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.token_type,
                              id_token: this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.id_token,
                              access_token: this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.access_token,
                              bootstrapperTargetService: this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.target.service,
                              bootstrapperHost: this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.self.host,
                          });
                          this.logger.trace(`_initialize: ZitiContext created`);
                          this._zitiBrowzerServiceWorkerGlobalScope._zitiContext = this._zitiContext;
                          // Make SW scope available to idpAuthHealthEventHandler
                          this._zitiContext._zitiBrowzerServiceWorkerGlobalScope = this._zitiBrowzerServiceWorkerGlobalScope;
                          this._zitiContext.setKeyTypeEC();
                          await this._zitiContext.initialize({
                              loadWASM: true, // unlike the ZBR, here in the ZBSW, we always instantiate the internal WebAssembly
                              doAuthenticate: true, // unlike the ZBR, here in the ZBSW, we always auth with Controller
                              jspi: this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.jspi,
                              target: this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.target,
                              bootstrapperHost: this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.self.host
                          });
                          await this._zitiContext.listControllerVersion();
                          this._zitiContext.on(zitiBrowzerSw_ZitiBrowzerCore_.ZITI_CONSTANTS.ZITI_EVENT_IDP_AUTH_HEALTH, this.idpAuthHealthEventHandler);
                          this._zitiContext.on(zitiBrowzerSw_ZitiBrowzerCore_.ZITI_CONSTANTS.ZITI_EVENT_NO_CONFIG_FOR_SERVICE, this.noConfigForServiceEventHandler);
                          this._zitiContext.on(zitiBrowzerSw_ZitiBrowzerCore_.ZITI_CONSTANTS.ZITI_EVENT_NO_SERVICE, this.noServiceEventHandler);
                          this._zitiContext.on(zitiBrowzerSw_ZitiBrowzerCore_.ZITI_CONSTANTS.ZITI_EVENT_SESSION_CREATION_ERROR, this.sessionCreationErrorEventHandler);
                          this._zitiContext.on(zitiBrowzerSw_ZitiBrowzerCore_.ZITI_CONSTANTS.ZITI_EVENT_INVALID_AUTH, this.invalidAuthEventHandler);
                          this._zitiContext.on(zitiBrowzerSw_ZitiBrowzerCore_.ZITI_CONSTANTS.ZITI_EVENT_CHANNEL_CONNECT_FAIL, this.channelConnectFailEventHandler);
                          this._zitiContext.on(zitiBrowzerSw_ZitiBrowzerCore_.ZITI_CONSTANTS.ZITI_EVENT_NO_WSS_ROUTERS, this.noWSSRoutersEventHandler);
                          this._zitiContext.on(zitiBrowzerSw_ZitiBrowzerCore_.ZITI_CONSTANTS.ZITI_EVENT_XGRESS, this.xgressEventHandler);
                          this._zitiContext.on(zitiBrowzerSw_ZitiBrowzerCore_.ZITI_CONSTANTS.ZITI_EVENT_NESTED_TLS_HANDSHAKE_TIMEOUT, this.nestedTLSHandshakeTimeoutEventHandler);
                          this._zitiContext.on(zitiBrowzerSw_ZitiBrowzerCore_.ZITI_CONSTANTS.ZITI_EVENT_NO_CONFIG_PROTOCOL_FOR_SERVICE, this.noConfigProtocolForServiceEventHandler);
                          this._zitiContext.on(zitiBrowzerSw_ZitiBrowzerCore_.ZITI_CONSTANTS.ZITI_EVENT_WSS_ROUTER_CONNECTION_ERROR, this.WSSEnabledEdgeRouterConnectionErrorEventHandler);
                          this._zitiContext.on(zitiBrowzerSw_ZitiBrowzerCore_.ZITI_CONSTANTS.ZITI_EVENT_CONTROLLER_CONNECTION_ERROR, this.ControllerConnectionErrorEventHandler);
                          this._zitiContext.on(zitiBrowzerSw_ZitiBrowzerCore_.ZITI_CONSTANTS.ZITI_EVENT_ACCESS_TOKEN_REFRESHED, this.accessTokenRefreshedEventHandler);
                          this.logger.trace(`_initialize: ZitiContext '${this._uuid}' initialized`);
                      }
                      else {
                          this.logger.trace(`_initialize: initiating unregister`);
                          await this._zitiBrowzerServiceWorkerGlobalScope._unregister(); // Let's try and 'reboot' the ZBR/SW pair
                          this.logger.trace(`_initialize: terminated`);
                      }
                      setTimeout(async function (self, resolve) {
                          let result = await self._zitiContext.enroll(); // this acquires an ephemeral Cert
                          if (!result) {
                              self.logger.trace(`_initialize: ephemeral Cert acquisition failed`);
                              // If we couldn't acquire a cert, it most likely means that the JWT from the IdP needs a refresh
                              self.logger.trace(`_initialize: initiating unregister`);
                              await self._zitiBrowzerServiceWorkerGlobalScope._unregister(); // Let's try and 'reboot' the ZBR/SW pair
                              self.logger.trace(`_initialize: terminated`);
                          }
                          else {
                              self.logger.trace(`_initialize: ephemeral Cert acquisition succeeded`);
                              self._rootPaths.push(self._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.target.path);
                              self._initialized = true;
                              self._zitiBrowzerServiceWorkerGlobalScope._currentAPISession = self._zitiContext.getCurrentAPISession();
                              self.logger.trace(`_initialize: ZitiContext '${self._uuid}' initialize complete`);
                          }
                          return resolve(null);
                      }, 500, this, resolve);
                  }
                  else {
                      return resolve(null);
                  }
              });
          })
              .catch((err) => {
              this.logger.error(err);
              return new Promise(async (_, reject) => {
                  reject(err);
              });
          });
      }
      _sendServiceUnavailable(_zitiBrowzerServiceWorkerGlobalScope, newUrl) {
          _zitiBrowzerServiceWorkerGlobalScope._sendMessageToClients({
              type: 'SERVICE_UNAVAILABLE_TO_IDENTITY',
              payload: {
                  message: `Ziti Service ${newUrl.hostname} is unavailable to your identity; Notify your administrator.`
              }
          });
      }
      /**
       * Determine if this request should be routed over Ziti, or over raw internet.
       *
       * @private
       * @param {Request} request The request from the fetch event.
       * @return {ZitiShouldRouteResult} If request should go over Ziti we return a (possibly adjusted) URL
    
       */
      async _shouldRouteOverZiti(request) {
          let result = {};
          this.logger.trace(`_shouldRouteOverZiti starting`);
          let url = new URL(request.url);
          result.url = url.toString();
          let targetHost = url.hostname;
          let targetPort = url.port;
          if (zitiBrowzerSw_LodashEs_.isEqual(targetHost, this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.self.host) && zitiBrowzerSw_LodashEs_.isEqual(targetPort, '')) {
              targetPort = '443';
          }
          let targetPath = url.pathname;
          this.logger.trace(`_shouldRouteOverZiti targetHost:port path is: ${targetHost}:${targetPort} ${targetPath}`);
          try {
              // We want to intercept fetch requests that target the Ziti BrowZer Bootstrapper... that is...
              // ...we want to intercept any request from the web app that targets the server from 
              // which the app was loaded.
              let targetserviceHost = await this._zitiContext.getConfigHostByServiceName(this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.target.service);
              let connectAppData = await this._zitiContext.getConnectAppDataByServiceName(this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.target.service, this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.target.scheme);
              var targetServiceRegex = new RegExp(targetserviceHost, 'g');
              var browzerLoadBalancerRegex = new RegExp(this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.loadbalancer.host, 'g');
              if ((zitiBrowzerSw_LodashEs_.isEqual(targetHost, this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.self.host) && (zitiBrowzerSw_LodashEs_.isEqual(targetPort, this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.self.port))) // yes, the request is targeting the Ziti BrowZer Bootstrapper
                  ||
                      (zitiBrowzerSw_LodashEs_.isEqual(targetHost, this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.self.host) && (zitiBrowzerSw_LodashEs_.isEqual(targetPort, this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.loadbalancer.port))) // yes, the request is targeting the Ziti BrowZer LB
                  ||
                      (this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.loadbalancer.host && request.url.match(browzerLoadBalancerRegex))) { // yes, the request is targeting the Ziti BrowZer LoadBalancer
                  var newUrl = new URL(request.url);
                  if (zitiBrowzerSw_LodashEs_.isEqual(targetPath, '/')) {
                      result.routeOverZiti = true;
                      result.serviceName = this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.target.service;
                      result.serviceScheme = this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.target.scheme;
                      result.serviceConnectAppData = connectAppData;
                  }
                  else if ((request.url.match(regexZBR)) || (request.url.match(regexZBRnaked)) || (request.url.match(regexZBWASM)) || (request.url.match(regexZBRLogo)) || (request.url.match(regexZBRcss)) || (request.url.match(regexZBRCORS))) { // the request seeks z-b-r/wasm/logo/css/cors-proxy
                      this.logger.trace(`_shouldRouteOverZiti: z-b-r/css/wasm/logo, bypassing intercept of [${request.url}]`);
                      result.routeOverZiti = false;
                  }
                  else {
                      // Don't muck with URL only because top-level domain of target matches the top-level domain of the load-balancer. 
                      // Only do that if the entire hostname matches, or else sub-domains represented by different Services will be routed 
                      // to the wrong place.
                      if (zitiBrowzerSw_LodashEs_.isEqual(newUrl.hostname, this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.self.host)) {
                          newUrl.hostname = this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.target.service;
                          newUrl.port = this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.target.port;
                          if (zitiBrowzerSw_LodashEs_.isEqual(newUrl.port, this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.self.port) ||
                              zitiBrowzerSw_LodashEs_.isEqual(newUrl.port, this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.loadbalancer.port)) {
                              newUrl.port = '';
                          }
                      }
                      var pathnameArray = newUrl.pathname.split('/');
                      var targetpathnameArray = this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.target.path.split('/');
                      if (!zitiBrowzerSw_LodashEs_.isEqual(pathnameArray[1], targetpathnameArray[1])) {
                          newUrl.pathname = this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.target.path + newUrl.pathname;
                          newUrl.pathname = newUrl.pathname.replace('//', '/');
                      }
                      this.logger.trace('_shouldRouteOverZiti: transformed URL: ', newUrl.toString());
                      result.serviceName = await this._zitiContext.shouldRouteOverZiti(newUrl);
                      this.logger.trace(`_shouldRouteOverZiti result.serviceName[${result.serviceName}]`);
                      if (zitiBrowzerSw_LodashEs_.isUndefined(result.serviceName) || zitiBrowzerSw_LodashEs_.isEqual(result.serviceName, '')) { // If we have no config associated with the hostname:port, do not intercept
                          this.logger.warn(`_shouldRouteOverZiti: no associated Ziti config, bypassing intercept of [${request.url}]`);
                          setTimeout(this._sendServiceUnavailable, 250, this._zitiBrowzerServiceWorkerGlobalScope, newUrl);
                      }
                      else {
                          result.routeOverZiti = true;
                          result.url = newUrl.toString();
                          result.serviceScheme = this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.target.scheme;
                          result.serviceConnectAppData = connectAppData;
                      }
                  }
              }
              // If no routing determination has been made yet
              if (zitiBrowzerSw_LodashEs_.isUndefined(result.routeOverZiti)) {
                  result.serviceName = await this._zitiContext.shouldRouteOverZiti(url);
                  this.logger.trace(`_shouldRouteOverZiti result.serviceName[${result.serviceName}]`);
                  if (zitiBrowzerSw_LodashEs_.isUndefined(result.serviceName) || zitiBrowzerSw_LodashEs_.isEqual(result.serviceName, '')) { // If we have no config associated with the hostname:port, do not intercept
                      this.logger.warn(`_shouldRouteOverZiti: no associated Ziti config, bypassing intercept of [${request.url}]`);
                      result.routeOverZiti = false;
                  }
                  else {
                      result.routeOverZiti = true;
                      result.url = url.toString();
                      result.serviceScheme = url.protocol;
                      let connectAppData = await this._zitiContext.getConnectAppDataByServiceName(result.serviceName, url.protocol);
                      result.serviceConnectAppData = connectAppData;
                  }
              }
          }
          catch (e) {
              this.logger.error(e);
          }
          this.logger.trace(`_shouldRouteOverZiti result[${result.routeOverZiti}]`);
          return result;
      }
      /**
       * Determine if this request can used previously cached response, or be routed over Ziti/internet.
       *
       * @private
       * @param {Request} request The request from the fetch event.
       * @return {boolean} If request can used previously cached response
    
       */
      _shouldUseCache(request) {
          if (request.method !== 'GET') { // Only cache GET responses
              this.logger.trace(`_shouldUseCache: handling ${request.method} method; NOT using cache`);
              return false;
          }
          if (request.url.match(regexEdgeClt)) { // Never cache responses from Ziti Controller
              this.logger.trace(`_shouldUseCache: handling request to Ziti Controller; NOT using cache`);
              return false;
          }
          if (request.url.match(regexControllerAPI)) { // Never cache responses from Ziti Controller
              this.logger.trace(`_shouldUseCache: handling request to Ziti Controller; NOT using cache`);
              return false;
          }
          // We will allow the SW to cache teh ZBR/WASM files ...for the moment
          //
          // if ( (request.url.match( regexZBR )) || ((request.url.match( regexZBWASM ))) ) { // Do not cache the ZBR/WASM
          //   this.logger.trace(`_shouldUseCache: handling request for ZBR|WASM; NOT using cache`);
          //   return false;
          // }
          if (request.url.match(regexSlash)) { // Never cache responses for root path
              this.logger.trace(`_shouldUseCache: handling request for '/'; NOT using cache`);
              return false;
          }
          let url = new URL(request.url);
          if (url.pathname === '/') { // Do not cache the web app's root path
              this.logger.trace(`_shouldUseCache: handling request for ROOT path; NOT using cache`);
              return false;
          }
          let isRootPath = this._rootPaths.find((element) => element === `${url.pathname}`);
          if (isRootPath) { // Do not cache the web app's root path
              this.logger.trace(`_shouldUseCache: handling request for ROOT path; NOT using cache`);
              return false;
          }
          if (url.search !== '') { // Do not cache requests with search parms
              this.logger.trace(`_shouldUseCache: handling request with search parms; NOT using cache`);
              return false;
          }
          // if (request.url.match( regexLibRdpHtml )) {
          //   this.logger.trace(`_shouldUseCache: handling request with librdphtml; NOT using cache`);
          //   return false;
          // }
          // Cache everything else
          this.logger.trace(`_shouldUseCache: we WILL cache response for ${request.url}`);
          return true;
      }
      _isRootPATH(request) {
          if (request.url.match(regexSlash)) {
              return true;
          }
          let url = new URL(request.url);
          if (url.pathname === '/') {
              return true;
          }
          let isRootPath = this._rootPaths.find((element) => element === `${url.pathname}`);
          if (isRootPath) {
              return true;
          }
          return false;
      }
      /**
       * @private
       * @param {Request|string} request A request to run this strategy for.
       * @param {workbox-strategies.StrategyHandler} handler The event that triggered the request.
       * @return {Promise<Response>}
       */
      async _handle(request, handler) {
          let tryZiti;
          this.logger.trace(`_handle entered for: [${request.url}]`);
          const requestURL = new URL(request.url);
          // If hitting the Controller, or seeking z-b-runtime|WASM, then
          // we never go over Ziti, and we let the browser route the request 
          // to the Controller or browZer Bootstrapper.  
          if ((request.url.match(regexControllerAPI)) || //    "     "      "
              (request.url.match(regexZBR)) || // seeking Ziti BrowZer Runtime
              (request.url.match(regexZBRnaked)) || // seeking Ziti BrowZer Runtime
              (request.url.match(regexZBRLogo)) || // seeking Ziti BrowZer Logo
              (request.url.match(regexZBRCORS)) || // seeking Ziti BrowZer CORS proxy
              (request.url.match(regexZBRcss)) || // seeking Ziti BrowZer CSS
              (request.url.match(regexPolipop)) || // seeking Ziti Polipop
              (request.url.match(regexCannySetup)) || // seeking Canny setup
              (request.url.match(regexOAUTHTOKEN)) || // seeking IdP token 
              (request.url.match(regexFavicon)) || // seeking favicon
              (request.url.match(regexZBWASM)) || // seeking Ziti BrowZer WASM
              (request.url.match(regexJSDelivr)) // seeking CDN content
          ) {
              tryZiti = false;
          }
          else {
              tryZiti = true;
          }
          // if (request.url.match( regexLibRdpHtml )) {
          //   await this._zitiBrowzerServiceWorkerGlobalScope._sendMessageToClients( { type: 'REAPPLY_WEBSOCKET_INTERCEPT'} );
          // }
          if (this._isRootPATH(request)) {
              const url = new URL(request.url);
              const urlSearchParams = new URLSearchParams(url.search);
              const codeParm = urlSearchParams.get('code');
              const stateParm = urlSearchParams.get('state');
              if (codeParm && stateParm) { // possible IdP-related URL
                  if (this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig && requestURL.hostname === this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.self.host) { // ..but if hitting the protected web app itself
                      tryZiti = true; // ..then let it go over Ziti
                  }
                  else {
                      tryZiti = false; // ..otherwise, route over raw internet since it's IdP-related
                  }
              }
          }
          if (tryZiti && this._zitiBrowzerServiceWorkerGlobalScope._zbrReloadPending) {
              if (request.url.match(regexZBWASM)) ;
              else if (request.url.match(regexControllerAPI)) ;
              else {
                  await this.await_zbrInitialized(request).catch(async (_err) => {
                      this.logger.debug(`ZBR init not responding`);
                      await this._zitiBrowzerServiceWorkerGlobalScope._unregister();
                      throw new zitiBrowzerSwWorkboxCore.WorkboxError('no-response', { url: request.url });
                  });
              }
          }
          if (tryZiti && (!this._isRootPATH(request)) && (!request.url.match(regexZBR))) { // if NOT in the process of bootstrapping from HTTP Agent
              if (zitiBrowzerSw_LodashEs_.isUndefined(this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig)) { // ...and we don't yet have the zitiConfig from ZBR
                  if (!request.url.match(regexEdgeClt)) { // ...and NOT hitting the controller
                      let result = await this.await_zitiConfig(request.url, handler); // ...then wait for ZBR to send zitiConfig to us
                      if (result < 0) {
                          let redirectResponse = new Response('', {
                              status: 302,
                              statusText: 'Found',
                              headers: {
                                  Location: '/'
                              }
                          });
                          return redirectResponse;
                      }
                  }
              }
          }
          let self = this;
          let skipInject = false;
          let useCache = this._shouldUseCache(request);
          if (useCache) {
              let cachResponse = await handler.cacheMatch(request);
              if (cachResponse) {
                  return cachResponse;
              }
          }
          const promises = [];
          let timeoutId;
          let response;
          let shouldRoute = { routeOverZiti: false };
          if (tryZiti) {
              if (this._isRootPATH(request)) { // seeking root path
                  if (zitiBrowzerSw_LodashEs_.isUndefined(this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig)) { // ...but we don't yet have the zitiConfig from ZBR
                      tryZiti = false; // ...then we're bootstrapping, so load ZBR from HTTP Agent
                  }
              }
          }
          if (tryZiti) {
              if (zitiBrowzerSw_LodashEs_.isUndefined(this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig)) {
                  let result = await this.await_zitiConfig(request.url, handler);
                  if (result < 0) {
                      let redirectResponse = new Response('', {
                          status: 302,
                          statusText: 'Found',
                          headers: {
                              Location: '/'
                          }
                      });
                      return redirectResponse;
                  }
              }
              // If going over Ziti, we must first complete the work to ensure WASM is instantiated, we have a cert, etc
              await this.await_zitiConfig('null', handler); // wait for ZBR to send zitiConfig to us
              await this._initialize();
              this._targetServiceHost = await this._zitiContext.getConfigHostByServiceName(this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.target.service);
              // Now determine if we're going over Ziti or not
              shouldRoute = await this._shouldRouteOverZiti(request);
          }
          if (this._zitiNetworkTimeoutSeconds) {
              const { id, promise } = this._getZitiTimeoutPromise({ request, handler });
              timeoutId = id;
              promises.push(promise);
          }
          let networkPromise;
          let zitiNetworkPromise;
          if (!shouldRoute.routeOverZiti) {
              this.logger.trace(`_handle: ------- routing over raw internet ----------`);
              networkPromise = this._getNetworkPromise({
                  timeoutId,
                  request,
                  handler,
                  useCache,
              });
              promises.push(networkPromise);
          }
          else {
              this.logger.trace(`_handle: ------- routing over Ziti ----------`);
              zitiNetworkPromise = this._getZitiNetworkPromise({
                  timeoutId,
                  shouldRoute,
                  request,
                  handler,
                  useCache,
              });
              promises.push(zitiNetworkPromise);
          }
          response = await handler.waitUntil((async () => {
              let netPromise = networkPromise || zitiNetworkPromise;
              // Promise.race() will resolve as soon as the first promise resolves.
              return ((await handler.waitUntil(Promise.race(promises))) ||
                  // If Promise.race() resolved with null, it might be due to a network
                  // timeout + a cache miss. If that were to happen, we'd rather wait until
                  // the netPromise (which is either over Ziti or raw internet) resolves 
                  // instead of returning null.
                  //
                  // Note that it's fine to await an already-resolved promise, so we don't
                  // have to check to see if it's still "in flight".
                  (await netPromise));
          })()).catch((err) => {
              this.logger.error(err);
              return new Promise(async (_, reject) => {
                  reject(err);
              });
          });
          if (!response) {
              this.logger.error(`no-response when trying to reach URL [${request.url}]`);
              await this._zitiBrowzerServiceWorkerGlobalScope._requestFailedWithNoResponse({
                  url: request.url
              });
              let errResponse = new Response('', {
                  status: 500,
                  statusText: 'ServerError',
              });
              return errResponse;
          }
          const location = response.headers.get('Location');
          const contentType = response.headers.get('Content-Type');
          if (request.url.match(regexZentaoRefreshRandom)) {
              skipInject = true;
          }
          if (request.url.match(regexZentaoLogin) && !request.url.match(regexZentaoLoginReferer)) {
              skipInject = true;
          }
          if (location && response.status >= 300 && response.status < 400) {
              if (!this._rootPaths.find((element) => element === `${location}`)) {
                  this._rootPaths.push(location);
              }
              skipInject = true;
          }
          if (response.status === 403) {
              skipInject = true;
          }
          if (!contentType || !contentType.match(regexTextHtml)) {
              skipInject = true;
          }
          if (contentType && contentType.match(regexTextHtml)) {
              /**
               * Jenkins thing
               */
              useCache = false;
          }
          if (contentType && contentType.match(regexTextXml)) ;
          if (contentType && contentType.match(regexMstsXml)) ;
          if (contentType && contentType.match(regexAppJSON)) {
              useCache = false;
          }
          if ((contentType && contentType.match(regexVideo)) || (contentType && contentType.match(regexMpeg))) {
              /**
               * streaming media server thing
               */
              useCache = false;
          }
          function streamingAttrReplace($, elementType, attrType) {
              $(elementType).each(async (_, e) => {
                  let attr = $(e).attr(attrType);
                  if (attr) {
                      try {
                          let url = new URL(attr);
                          if (zitiBrowzerSw_LodashEs_.isEqual(url.host, self._targetServiceHost)) {
                              attr = attr.replace('http:', 'https:');
                              attr = attr.replace(self._targetServiceHost, self._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.self.host);
                              $(e).attr(attrType, attr);
                          }
                      }
                      catch (e) {
                          let newUrl = new URL(`${requestURL.origin}`);
                          if (!attr.startsWith('/')) {
                              newUrl.pathname = `${requestURL.pathname}/${attr}`;
                          }
                          else {
                              newUrl.pathname = `${requestURL.pathname}${attr}`;
                          }
                          newUrl.pathname = newUrl.pathname.replace('//', '/');
                          attr = newUrl.toString();
                          self.logger.trace(`streamingAttrReplace: transformed attr[${attr}] `);
                          $(e).attr(attrType, attr);
                      }
                  }
              });
          }
          function streamingImportReplace($, elementType) {
              $(elementType).each(async (_, e) => {
                  let text = $(e).text();
                  if (text.match(regexAtImport)) {
                      let importUrl = text.slice(text.indexOf('@') + 7);
                      importUrl = importUrl.replaceAll('"', '');
                      importUrl = importUrl.replace(';', '');
                      let url = new URL(importUrl);
                      if (zitiBrowzerSw_LodashEs_.isEqual(url.host, self._targetServiceHost)) {
                          text = text.replace('http:', 'https:');
                          text = text.replace(self._targetServiceHost, self._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.self.host);
                          $(e).text(text);
                      }
                  }
              });
          }
          if (shouldRoute.routeOverZiti && !skipInject) {
              var ignore = false;
              if (!ignore) {
                  if (response.body) {
                      let fromBootstrapper = false;
                      let fullDocument = true;
                      function _obtainBootStrapperURL() {
                          let url;
                          if (self._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.loadbalancer.host) {
                              url = `https://${self._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.self.host}:${self._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.loadbalancer.port}`;
                          }
                          else {
                              url = `${self._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.self.scheme}://${self._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.self.host}:${self._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.self.port}`;
                          }
                          return url;
                      }
                      function streamingHEADReplace() {
                          let buffer = '';
                          return new TransformStream({
                              transform(chunk, _controller) {
                                  try {
                                      if (fullDocument && !fromBootstrapper) {
                                          let zbrLocation = self._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.runtime.src;
                                          // Parse the HTML
                                          const $ = zitiBrowzerSwMisc.load(chunk, {
                                              decodeEntities: false // ensure we don't evaporate Angular-specific attributes like ng-app, ng-model, etc.
                                          });
                                          // Ensure any links in the HTML that point to the target service are using the correct scheme
                                          streamingAttrReplace($, 'link', 'href');
                                          streamingAttrReplace($, 'a', 'href');
                                          streamingAttrReplace($, 'area', 'href');
                                          streamingAttrReplace($, 'base', 'href');
                                          streamingAttrReplace($, 'img', 'src');
                                          streamingAttrReplace($, 'audio', 'src');
                                          streamingAttrReplace($, 'embed', 'src');
                                          streamingAttrReplace($, 'iframe', 'src');
                                          streamingAttrReplace($, 'input', 'src');
                                          streamingAttrReplace($, 'script', 'src');
                                          streamingAttrReplace($, 'source', 'src');
                                          streamingAttrReplace($, 'track', 'src');
                                          streamingAttrReplace($, 'video', 'src');
                                          // Ensure any @import's in the HTML that point to the target service are using the correct scheme
                                          streamingImportReplace($, 'style');
                                          self.logger.trace(`streamingHEADReplace: HTML before modifications is: ${$.html()}`);
                                          let zbrElement = $('<script></script> ').attr('id', 'from-ziti-browzer-sw').attr('type', 'text/javascript').attr('src', `${_obtainBootStrapperURL()}/${zbrLocation}`); //.attr('defer', `defer`);
                                          let cannyElement = $('<script></script> ')
                                              .attr('id', 'ziti-browzer-canny-setup')
                                              .attr('type', 'text/javascript')
                                              .attr('src', `${_obtainBootStrapperURL()}/canny-setup.js`);
                                          let ppElement = $('<script></script> ')
                                              .attr('id', 'ziti-browzer-pp')
                                              .attr('type', 'text/javascript')
                                              .attr('src', `${_obtainBootStrapperURL()}/polipop.min.js`);
                                          let ppCss1Element = $('<link> ')
                                              .attr('id', 'ziti-browzer-ppcss')
                                              .attr('rel', 'stylesheet')
                                              .attr('href', `${_obtainBootStrapperURL()}/polipop.core.min.css`);
                                          let ppCss2Element = $('<link> ')
                                              .attr('rel', 'stylesheet')
                                              .attr('href', `${_obtainBootStrapperURL()}/polipop.compact.min.css`);
                                          // let otElement = $('<meta></meta> ')
                                          //     .attr('id', 'ziti-browzer-origin-trial')
                                          //     .attr('http-equiv', 'origin-trial')
                                          //     .attr('content', `${self._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.runtime.originTrialToken}`);
                                          let kcElement = $('<meta name="author" content="OpenZiti BrowZer" />');
                                          // Locate the CSP
                                          let cspElement = $('meta[http-equiv="content-security-policy"]');
                                          // If we found a CSP
                                          if (cspElement.length > 0) {
                                              // self.logger.trace('streamingHEADReplace: CSP found in html with content: ', cspElement.attr('content'));
                                              // then augment it to enable WASM load/xeq
                                              let cspContent = cspElement.attr('content');
                                              let newCspContent = self.generateNewCSP(cspContent);
                                              cspElement.attr('content', newCspContent);
                                              // self.logger.trace('streamingHEADReplace: CSP is now enhanced with content: ', cspElement.attr('content'));
                                              // Inject the PP immediately after the CSP
                                              cspElement.after(ppCss1Element);
                                              cspElement.after(ppCss2Element);
                                              cspElement.after(ppElement);
                                              // cspElement.after(otElement);
                                              // let otEl = $('meta[id="ziti-browzer-origin-trial"]');
                                              // Inject the ZBR immediately after the origin trial meta
                                              cspElement.after(zbrElement);
                                              cspElement.after(cannyElement);
                                              buffer += $.html();
                                          }
                                          // If we did NOT find a CSP
                                          else {
                                              // Locate the HEAD
                                              let headElement = $('head');
                                              headElement.prepend(cannyElement);
                                              // Inject the Ziti browZer Runtime at the front of <head> element so we are prepared to intercept as soon as possible over on the browser
                                              headElement.prepend(zbrElement);
                                              headElement.prepend(ppElement);
                                              headElement.prepend(ppCss2Element);
                                              headElement.prepend(ppCss1Element);
                                              // headElement.prepend(otElement);
                                              buffer += $.html();
                                          }
                                      }
                                  }
                                  catch (e) {
                                      self.logger.error(e);
                                  }
                              },
                              flush(controller) {
                                  if (buffer) {
                                      buffer = buffer.replace('document.domain', 'document.zitidomain');
                                      self.logger.trace(`streamingHEADReplace: HTML after modifications is: ${buffer}`);
                                      controller.enqueue(buffer);
                                  }
                              }
                          });
                      }
                      const bodyStream = response.body
                          .pipeThrough(new TextDecoderStream())
                          .pipeThrough(streamingHEADReplace())
                          .pipeThrough(new TextEncoderStream());
                      const newHeaders = new Headers(response.headers);
                      const newResponse = new Response(bodyStream, {
                          status: response.status,
                          statusText: response.statusText,
                          headers: newHeaders
                      });
                      response = newResponse;
                  }
              }
          }
          else {
              if (shouldRoute.routeOverZiti && (contentType && contentType.match(regexTextXml))) {
                  if (response.body) {
                      function streamingXMLReplace() {
                          let buffer = '';
                          return new TransformStream({
                              transform(chunk, _controller) {
                                  try {
                                      // Parse the XML
                                      const $ = zitiBrowzerSwMisc.load(chunk, {
                                          xmlMode: true,
                                      });
                                      self.logger.trace('streamingXMLReplace: XML before modifications is: ', $.html());
                                      streamingAttrReplace($, 'RDWAPage', 'baseurl');
                                      buffer += $.html();
                                  }
                                  catch (e) {
                                      self.logger.error(e);
                                  }
                              },
                              flush(controller) {
                                  if (buffer) {
                                      buffer = buffer.replace(self._targetServiceHost, self._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.self.host);
                                      self.logger.trace('streamingXMLReplace: XML after modifications is: ', buffer);
                                      controller.enqueue(buffer);
                                  }
                              }
                          });
                      }
                      const bodyStream = response.body
                          .pipeThrough(new TextDecoderStream())
                          .pipeThrough(streamingXMLReplace())
                          .pipeThrough(new TextEncoderStream());
                      const newHeaders = new Headers(response.headers);
                      const newResponse = new Response(bodyStream, {
                          status: response.status,
                          statusText: response.statusText,
                          headers: newHeaders
                      });
                      response = newResponse;
                  }
              }
          }
          if (useCache) {
              await handler.waitUntil(handler.cachePut(request, response.clone()));
          }
          return response;
      }
      /**
       * @param {Object} options
       * @param {Request} options.request
       * @param {Event} options.event
       * @return {Promise<Response>}
       *
       * @private
       */
      _getZitiTimeoutPromise({ request, handler, }) {
          let timeoutId;
          let self = this;
          const timeoutPromise = new Promise((resolve) => {
              const onNetworkTimeout = async () => {
                  self.logger.debug(`Timing out the network response at ${self._zitiNetworkTimeoutSeconds} seconds`);
                  resolve(await handler.cacheMatch(request));
              };
              timeoutId = setTimeout(onNetworkTimeout, this._zitiNetworkTimeoutSeconds * 1000);
          });
          return {
              promise: timeoutPromise,
              id: timeoutId,
          };
      }
      /**
       *
       * @param zitiRequest
       * @returns body|undefined
       */
      async getRequestBody(zitiRequest) {
          var requestBlob = await zitiRequest.blob();
          if (requestBlob.size > 0) {
              return (requestBlob);
          }
          return (undefined);
      }
      /**
       *
       * @param headersObject
       */
      dumpHeaders(headersObject) {
          for (var pair of headersObject.entries()) {
              this.logger.trace('dumpHeaders: ', pair[0], pair[1]);
          }
      }
      /**
       *
       * @param headersObject
       */
      generateCSP(headersObject) {
          for (var pair of headersObject.entries()) {
              this.logger.trace('dumpHeaders: ', pair[0], pair[1]);
          }
      }
      /**
       * @param {Object} options
       * @param {number|undefined} options.timeoutId
       * @param {string} options.zitiURL
       * @param {Request} options.request
       * @param {Event} options.event
       * @return {Promise<Response>}
       *
       * @private
       */
      async _getZitiNetworkPromise({ timeoutId, shouldRoute, request, handler, useCache, }) {
          let error = null;
          let response;
          try {
              this.logger.debug(`doing Ziti fetch for: ${request.url}`);
              /**
               * Instantiate a fresh HTTP Request object that we will push through the ziti-browzer-core which will:
               *
               * 1) contain re-routed host
               * 2) have any headers we need to pile on
               * 3) prepare to stream out any body data associated with the intercepted request
               */
              const zitiRequest = new Request(request, {});
              var newHeaders = new Headers();
              zitiRequest.headers.forEach(function (header, key) {
                  if (!zitiBrowzerSw_LodashEs_.isEqual(key, 'origin')) {
                      newHeaders.append(key, header);
                  }
              });
              if (!zitiBrowzerSw_LodashEs_.isEqual(request.referrer, '')) {
                  newHeaders.append('referer', request.referrer);
              }
              try {
                  newHeaders.append('Remote-User', await this._zitiContext.getAccessTokenEmail());
              }
              catch (e) { }
              if (zitiBrowzerSw_LodashEs_.isEqual(request.mode, 'navigate')) {
                  this.logger.trace(`Navigation event detected: [${request.url}]`);
                  newHeaders.append('Sec-Fetch-Mode', 'navigate');
              }
              // Propagate any Cookie values we have accumulated
              let cookieHeaderValue = '';
              for (const cookie in this._zitiBrowzerServiceWorkerGlobalScope._cookieObject) {
                  if (cookie !== '' && !zitiBrowzerSw_LodashEs_.isEqual(cookie, '__ziti-browzer-config')) { // don't send the ZBR config cookie
                      if (this._zitiBrowzerServiceWorkerGlobalScope._cookieObject.hasOwnProperty(cookie)) {
                          cookieHeaderValue += cookie + '=' + this._zitiBrowzerServiceWorkerGlobalScope._cookieObject[cookie];
                          // this.logger.debug(`cookieHeaderValue: [${cookieHeaderValue}]`);
                          newHeaders.append('Cookie', cookieHeaderValue);
                          cookieHeaderValue = '';
                      }
                  }
              }
              var blob = await this.getRequestBody(zitiRequest);
              let referrerUrlPathname = '/';
              if (request.referrer) {
                  let referrerUrl = new URL(request.referrer);
                  referrerUrlPathname = referrerUrl.pathname;
              }
              var zitiResponse = await this._zitiContext.httpFetch(shouldRoute.url, {
                  serviceName: shouldRoute.serviceName,
                  serviceScheme: shouldRoute.serviceScheme,
                  servicePath: this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.target.path,
                  serviceConnectAppData: shouldRoute.serviceConnectAppData,
                  bootstrapperHost: this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.self.host,
                  method: zitiRequest.method,
                  headers: newHeaders,
                  mode: zitiRequest.mode,
                  cache: zitiRequest.cache,
                  credentials: zitiRequest.credentials,
                  redirect: zitiRequest.redirect,
                  referrerPolicy: zitiRequest.referrerPolicy,
                  body: blob
              });
              this.logger.debug(`Got zitiResponse from [${zitiResponse.url}]`);
              /**
               * Now that ziti-browzer-core has returned us a ZitiResponse, instantiate a fresh native Response object that we
               * will return to the Browser. This requires us to:
               *
               * 1) propagate the HTTP headers, status, etc
               * 2) pipe the HTTP response body
               */
              if (zitiBrowzerSw_LodashEs_.isUndefined(zitiResponse.headers.raw)) {
                  return zitiResponse;
              }
              var zitiHeaders = zitiResponse.headers.raw();
              const contentType = zitiHeaders['content-type'];
              let isTextHtml = false;
              let isTextXml = false;
              if (contentType && contentType[0] && contentType[0].match(regexTextHtml)) {
                  isTextHtml = true;
              }
              if (contentType && contentType[0] && contentType[0].match(regexTextXml)) {
                  isTextXml = true;
              }
              var headers = new Headers();
              const keys = Object.keys(zitiHeaders);
              for (let i = 0; i < keys.length; i++) {
                  let key = keys[i];
                  let val = zitiHeaders[key][0];
                  this.logger.trace(`ZitiFirstStrategy: zitiResponse.headers: [${key}] [${val}]`);
                  if (key.toLowerCase() === 'set-cookie') {
                      if (Array.isArray(val)) {
                          for (var ndx = 0; ndx < val.length; ndx++) {
                              this.logger.trace('ZitiFirstStrategy: sending SET_COOKIE cmd');
                              this._zitiBrowzerServiceWorkerGlobalScope._sendMessageToClients({
                                  type: 'SET_COOKIE',
                                  payload: val[ndx]
                              });
                              let parts = val[ndx].split('=');
                              function extractCookieVal(str) {
                                  const match = str.match(/=(.*?);/);
                                  return match ? match[1] : null;
                              }
                              const result = extractCookieVal(val[ndx]);
                              this._zitiBrowzerServiceWorkerGlobalScope._cookieObject[parts[0]] = result;
                          }
                      }
                      else {
                          headers.append('x-ziti-browzer-set-cookie', val);
                          this.logger.trace('ZitiFirstStrategy: sending SET_COOKIE cmd');
                          let resp = await this._zitiBrowzerServiceWorkerGlobalScope._sendMessageToClients({
                              type: 'SET_COOKIE',
                              payload: val
                          });
                          this.logger.trace('ZitiFirstStrategy: SET_COOKIE response: ', resp);
                          let parts = val.split('=');
                          this._zitiBrowzerServiceWorkerGlobalScope._cookieObject[parts[0]] = parts[1];
                      }
                  }
                  else if (key.toLowerCase() === 'location') {
                      this.logger.trace(`location header transform needed for: ${val}`);
                      function updateSignInWithGoogleRedirectUri(url, newValue, logger) {
                          let paramName = 'redirect_uri';
                          try {
                              let urlObj = new URL(url);
                              let searchParams = urlObj.searchParams;
                              // If url is doing a "Sign in with Google"
                              if (zitiBrowzerSw_LodashEs_.isEqual(urlObj.host, 'accounts.google.com') && zitiBrowzerSw_LodashEs_.isEqual(urlObj.pathname, '/o/oauth2/auth') && searchParams.has(paramName)) {
                                  // Transform the redirect_uri host to be the browZer bootstrapper
                                  let paramValue = searchParams.get(paramName);
                                  if (paramValue) {
                                      let redirect_uriObj = new URL(paramValue);
                                      redirect_uriObj.host = newValue;
                                      redirect_uriObj.port = '443';
                                      redirect_uriObj.protocol = 'https';
                                      searchParams.set(paramName, redirect_uriObj.toString());
                                  }
                              }
                              logger.trace(`updateSignInWithGoogleRedirectUri(): location header transformed to: ${urlObj.toString()}`);
                              return urlObj.toString();
                          }
                          catch (e) {
                              return url;
                          }
                      }
                      // NOT YET
                      // val = updateSignInWithGoogleRedirectUri(val, this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.self.host, this.logger);
                      let pathname;
                      let skipTransform = false;
                      if (val.startsWith('/')) {
                          pathname = val;
                      }
                      else {
                          function isValidUrl(string) {
                              try {
                                  let url = new URL(string);
                                  return url;
                              }
                              catch (err) {
                                  return undefined;
                              }
                          }
                          let locationUrl = isValidUrl(val);
                          if (zitiBrowzerSw_LodashEs_.isUndefined(locationUrl)) { // i.e. it's a relative path (no slashes)
                              pathname = `${referrerUrlPathname}${val}`;
                          }
                          else {
                              if (zitiBrowzerSw_LodashEs_.isEqual(locationUrl.hostname, this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.self.host)) {
                                  pathname = locationUrl.pathname + locationUrl.search;
                              }
                              else {
                                  let serviceConnectAppData = await this._zitiBrowzerServiceWorkerGlobalScope._zitiContext.getConnectAppDataByServiceName(this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.target.service, 'https');
                                  if (!zitiBrowzerSw_LodashEs_.isUndefined(serviceConnectAppData)) {
                                      if (zitiBrowzerSw_LodashEs_.isEqual(locationUrl.hostname, serviceConnectAppData.dst_hostname)) {
                                          pathname = locationUrl.pathname + locationUrl.search;
                                      }
                                      else if (zitiBrowzerSw_LodashEs_.isEqual(locationUrl.hostname, serviceConnectAppData.dst_ip)) {
                                          pathname = locationUrl.pathname + locationUrl.search;
                                      }
                                  }
                                  else {
                                      skipTransform = true;
                                  }
                              }
                          }
                      }
                      if (!zitiBrowzerSw_LodashEs_.isUndefined(pathname) && !skipTransform) {
                          let serviceScheme = shouldRoute.serviceScheme;
                          if (serviceScheme && !serviceScheme.endsWith(':')) {
                              serviceScheme = serviceScheme + ":";
                          }
                          let serviceName = shouldRoute.serviceName;
                          if (zitiBrowzerSw_LodashEs_.isEqual(serviceName, this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.target.service)) {
                              serviceName = this._zitiBrowzerServiceWorkerGlobalScope._zitiConfig.browzer.bootstrapper.self.host;
                          }
                          let newLocationUrl = new URL(`${serviceScheme}//${serviceName}${pathname}`);
                          val = newLocationUrl.toString();
                          this.logger.trace(`location header transformed to: ${val}`);
                      }
                      else {
                          this.logger.trace(`location header transform bypassed for: ${val}`);
                      }
                  }
                  else if (key.toLowerCase() === 'content-security-policy') {
                      val = this.generateNewCSP(val);
                  }
                  headers.append(key, val);
              }
              headers.append('x-ziti-browzer-sw-workbox-strategies-version', pjson.version);
              if ((!zitiBrowzerSw_LodashEs_.isEqual(zitiResponse.status, 204)) && (zitiResponse.status < 300 || zitiResponse.status > 399)) {
                  if (isTextHtml) {
                      var responseBlob = await zitiResponse.blob();
                      var responseBlobStream = responseBlob.stream();
                      const responseStream = new ReadableStream({
                          start(controller) {
                              function push() {
                                  var chunk = responseBlobStream.read();
                                  if (chunk) {
                                      controller.enqueue(chunk);
                                      push();
                                  }
                                  else {
                                      controller.close();
                                      return;
                                  }
                              }
                              ;
                              push();
                          }
                      });
                      response = new Response(responseStream, { "status": zitiResponse.status, "headers": headers });
                  }
                  else if (isTextXml) {
                      var responseBlob = await zitiResponse.blob();
                      var responseBlobStream = responseBlob.stream();
                      const responseStream = new ReadableStream({
                          start(controller) {
                              function push() {
                                  var chunk = responseBlobStream.read();
                                  if (chunk) {
                                      controller.enqueue(chunk);
                                      push();
                                  }
                                  else {
                                      controller.close();
                                      return;
                                  }
                              }
                              ;
                              push();
                          }
                      });
                      response = new Response(responseStream, { "status": zitiResponse.status, "headers": headers });
                  }
                  else {
                      const responseStream = new ReadableStream({
                          start(controller) {
                              function push(chunk) {
                                  try {
                                      if (chunk) {
                                          controller.enqueue(chunk);
                                      }
                                      else {
                                          // if (!isClosed) {
                                          // isClosed = true;
                                          controller.close();
                                          // }
                                          return;
                                      }
                                  }
                                  catch (e) { }
                              }
                              ;
                              zitiResponse.body.on('data', (chunk) => {
                                  push(chunk);
                              });
                              zitiResponse.body.on('end', () => {
                                  push(null);
                              });
                          }
                      });
                      response = new Response(responseStream, { "status": zitiResponse.status, "headers": headers });
                  }
              }
              else {
                  response = new Response(null, { "status": zitiResponse.status, "headers": headers });
              }
              this.logger.trace(`ZitiFirstStrategy: formed native response for [${zitiResponse.url}]`);
          }
          catch (fetchError) {
              this.logger.error(`Got error: `, fetchError);
              if (fetchError instanceof Error) {
                  error = fetchError;
              }
          }
          if (timeoutId) {
              clearTimeout(timeoutId);
          }
          if (response) {
              this.logger.debug(`Got response from Ziti network.`);
          }
          else {
              if (useCache) {
                  this.logger.warn(`Unable to get a response from Ziti network. Will respond ` +
                      `with a cached response.`);
              }
          }
          if ((error || !response) && useCache) {
              response = await handler.cacheMatch(request);
              if (response) {
                  this.logger.debug(`Found a cached response`);
              }
          }
          return response;
      }
      /**
       * @param {Object} options
       * @param {number|undefined} options.timeoutId
       * @param {Request} options.request
       * @param {Event} options.event
       * @return {Promise<Response>}
       *
       * @private
       */
      async _getNetworkPromise({ timeoutId, request, handler, useCache, }) {
          let error;
          let response;
          try {
              // if (useCache) {
              //   this.logger.debug(`doing raw internet fetchAndCachePut for: `, request.url);
              //   response = await handler.fetchAndCachePut(request);
              // } else {
              this.logger.debug(`doing raw internet fetch for [${request.url}]`);
              response = await handler.fetch(request);
              // }
              this.logger.debug(`Got raw internet response from [${request.url}]`);
          }
          catch (fetchError) {
              this.logger.error(`Got error: [${fetchError}]`);
              if (fetchError instanceof Error) {
                  error = fetchError;
              }
          }
          if (timeoutId) {
              clearTimeout(timeoutId);
          }
          if (response) {
              this.logger.debug(`Got response from network.`);
              /**
               * If we get a failed respose that is also an 'opaqueredirect', it is most likely
               * because the attempt to fetch something served by the HTTP Agent is being canceled,
               * then redirected to the IdP (re)authentication URL.
               *
               * HACK ALERT:
               * For reaons currently unknown, if we do NOT unregister the SW, before letting
               * the OIDC middleware continue, it will fail...
               * So, we force an unregister of the SW here to keep the (re)authentication flow working.
               */
              if (!response.ok && (response.type === 'opaqueredirect')) {
                  this.logger.debug(`Got 'opaqueredirect' response from network; doing SW unregister now`);
                  await this._zitiBrowzerServiceWorkerGlobalScope._unregister();
              }
          }
          else {
              if (useCache) {
                  this.logger.warn(`Unable to get a response from the network. Will respond ` +
                      `with a cached response.`);
              }
          }
          if ((error || !response) && useCache) {
              response = await handler.cacheMatch(request);
              if (response) {
                  this.logger.debug(`Found a cached response in the '${this.cacheName}'` + ` cache.`);
              }
          }
          return response;
      }
  }

  exports.ZitiFirstStrategy = ZitiFirstStrategy;

}));