text: /**
   * This module adds PubCommonId to the User ID module
   * The {@link module:modules/userId} module is required
   * @module modules/pubCommonIdSystem
   * @requires module:modules/userId
   */
  
  import * as utils from '../src/utils.js';
  import {submodule} from '../src/hook.js';
  import {getStorageManager} from '../src/storageManager.js';
  import {ajax} from '../src/ajax.js';
  
  const PUB_COMMON_ID = 'PublisherCommonId';
  const MODULE_NAME = 'pubCommonId';
  
  const COOKIE = 'cookie';
  const LOCAL_STORAGE = 'html5';
  const SHAREDID_OPT_OUT_VALUE = '00000000000000000000000000';
  const SHAREDID_URL = 'https://id.sharedid.org/id';
  const SHAREDID_SUFFIX = '_sharedid';
  const EXPIRED_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:01 GMT';
  const SHAREDID_DEFAULT_STATE = false;
  const GVLID = 887;
  
  const storage = getStorageManager(GVLID, 'pubCommonId');
  
  /**
   * Store sharedid in either cookie or local storage
   * @param {Object} config Need config.storage object to derive key, expiry time, and storage type.
   * @param {string} value Shareid value to store
   */
  
  function storeData(config, value) {
    try {
      if (value) {
        const key = config.storage.name + SHAREDID_SUFFIX;
        const expiresStr = (new Date(Date.now() + (storage.expires * (60 * 60 * 24 * 1000)))).toUTCString();
  
        if (config.storage.type === COOKIE) {
          if (storage.cookiesAreEnabled()) {
            storage.setCookie(key, value, expiresStr, 'LAX', pubCommonIdSubmodule.domainOverride());
          }
        } else if (config.storage.type === LOCAL_STORAGE) {
          if (storage.hasLocalStorage()) {
            storage.setDataInLocalStorage(`${key}_exp`, expiresStr);
            storage.setDataInLocalStorage(key, value);
          }
        }
      }
    } catch (error) {
      utils.logError(error);
    }
  }
  
  /**
   * Read sharedid from cookie or local storage
   * @param config Need config.storage to derive key and storage type
   * @return {string}
   */
  function readData(config) {
    try {
      const key = config.storage.name + SHAREDID_SUFFIX;
      if (config.storage.type === COOKIE) {
        if (storage.cookiesAreEnabled()) {
          return storage.getCookie(key);
        }
      } else if (config.storage.type === LOCAL_STORAGE) {
        if (storage.hasLocalStorage()) {
          const expValue = storage.getDataFromLocalStorage(`${key}_exp`);
          if (!expValue) {
            return storage.getDataFromLocalStorage(key);
          } else if ((new Date(expValue)).getTime() - Date.now() > 0) {
            return storage.getDataFromLocalStorage(key)
          }
        }
      }
    } catch (error) {
      utils.logError(error);
    }
  }
  
  /**
   * Delete sharedid from cookie or local storage
   * @param config Need config.storage to derive key and storage type
   */
  function delData(config) {
    try {
      const key = config.storage.name + SHAREDID_SUFFIX;
      if (config.storage.type === COOKIE) {
        if (storage.cookiesAreEnabled()) {
          storage.setCookie(key, '', EXPIRED_COOKIE_DATE);
        }
      } else if (config.storage.type === LOCAL_STORAGE) {
        storage.removeDataFromLocalStorage(`${key}_exp`);
        storage.removeDataFromLocalStorage(key);
      }
    } catch (error) {
      utils.logError(error);
    }
  }
  
  /**
   * setup success and error handler for sharedid callback thru ajax
   * @param {string} pubcid Current pubcommon id
   * @param {function} callback userId module callback.
   * @param {Object} config Need config.storage to derive sharedid storage params
   * @return {{success: success, error: error}}
   */
  
  function handleResponse(pubcid, callback, config) {
    return {
      success: function (responseBody) {
        if (responseBody) {
          try {
            let responseObj = JSON.parse(responseBody);
            utils.logInfo('PubCommonId: Generated SharedId: ' + responseObj.sharedId);
            if (responseObj.sharedId) {
              if (responseObj.sharedId !== SHAREDID_OPT_OUT_VALUE) {
                // Store sharedId locally
                storeData(config, responseObj.sharedId);
              } else {
                // Delete local copy if the user has opted out
                delData(config);
              }
            }
            // Pass pubcid even though there is no change in order to trigger decode
            callback(pubcid);
          } catch (error) {
            utils.logError(error);
          }
        }
      },
      error: function (statusText, responseBody) {
        utils.logInfo('PubCommonId: failed to get sharedid');
      }
    }
  }
  
  /**
   * Builds and returns the shared Id URL with attached consent data if applicable
   * @param {Object} consentData
   * @return {string}
   */
  function sharedIdUrl(consentData) {
    if (!consentData || typeof consentData.gdprApplies !== 'boolean' || !consentData.gdprApplies) return SHAREDID_URL;
  
    return `${SHAREDID_URL}?gdpr=1&gdpr_consent=${consentData.consentString}`
  }
  
  /**
   * Wraps pixelCallback in order to call sharedid sync
   * @param {string} pubcid Pubcommon id value
   * @param {function|undefined} pixelCallback fires a pixel to first party server
   * @param {Object} config Need config.storage to derive sharedid storage params.
   * @return {function(...[*]=)}
   */
  
  function getIdCallback(pubcid, pixelCallback, config, consentData) {
    return function (callback) {
      if (typeof pixelCallback === 'function') {
        pixelCallback();
      }
      ajax(sharedIdUrl(consentData), handleResponse(pubcid, callback, config), undefined, {method: 'GET', withCredentials: true});
    }
  }
  
  /** @type {Submodule} */
  export const pubCommonIdSubmodule = {
    /**
     * used to link submodule with config
     * @type {string}
     */
    name: MODULE_NAME,
    /**
     * Vendor id of prebid
     * @type {Number}
     */
    gvlid: GVLID,
    /**
     * Return a callback function that calls the pixelUrl with id as a query parameter
     * @param pixelUrl
     * @param id
     * @returns {function}
     */
    makeCallback: function (pixelUrl, id = '') {
      if (!pixelUrl) {
        return;
      }
  
      // Use pubcid as a cache buster
      const urlInfo = utils.parseUrl(pixelUrl);
      urlInfo.search.id = encodeURIComponent('pubcid:' + id);
      const targetUrl = utils.buildUrl(urlInfo);
  
      return function () {
        utils.triggerPixel(targetUrl);
      };
    },
    /**
     * decode the stored id value for passing to bid requests
     * @function
     * @param {string} value
     * @param {SubmoduleConfig} config
     * @returns {{pubcid:string}}
     */
    decode(value, config) {
      const idObj = {'pubcid': value};
      const {params: {enableSharedId = SHAREDID_DEFAULT_STATE} = {}} = config;
  
      if (enableSharedId) {
        const sharedId = readData(config);
        if (sharedId) idObj['sharedid'] = {id: sharedId};
      }
  
      return idObj;
    },
    /**
     * performs action to obtain id
     * @function
     * @param {SubmoduleConfig} [config] Config object with params and storage properties
     * @param {Object} consentData
     * @param {string} storedId Existing pubcommon id
     * @returns {IdResponse}
     */
    getId: function (config = {}, consentData, storedId) {
      const {params: {create = true, pixelUrl, enableSharedId = SHAREDID_DEFAULT_STATE} = {}} = config;
      let newId = storedId;
      if (!newId) {
        try {
          if (typeof window[PUB_COMMON_ID] === 'object') {
            // If the page includes its own pubcid module, then save a copy of id.
            newId = window[PUB_COMMON_ID].getId();
          }
        } catch (e) {
        }
  
        if (!newId) newId = (create && utils.hasDeviceAccess()) ? utils.generateUUID() : undefined;
      }
  
      const pixelCallback = this.makeCallback(pixelUrl, newId);
      const combinedCallback = enableSharedId ? getIdCallback(newId, pixelCallback, config, consentData) : pixelCallback;
  
      return {id: newId, callback: combinedCallback};
    },
    /**
     * performs action to extend an id.  There are generally two ways to extend the expiration time
     * of stored id: using pixelUrl or return the id and let main user id module write it again with
     * the new expiration time.
     *
     * PixelUrl, if defined, should point back to a first party domain endpoint.  On the server
     * side, there is either a plugin, or customized logic to read and write back the pubcid cookie.
     * The extendId function itself should return only the callback, and not the id itself to avoid
     * having the script-side overwriting server-side.  This applies to both pubcid and sharedid.
     *
     * On the other hand, if there is no pixelUrl, then the extendId should return storedId so that
     * its expiration time is updated.  Sharedid, however, will have to be updated by this submodule
     * separately.
     *
     * @function
     * @param {SubmoduleParams} [config]
     * @param {ConsentData|undefined} consentData
     * @param {Object} storedId existing id
     * @returns {IdResponse|undefined}
     */
    extendId: function(config = {}, consentData, storedId) {
      const {params: {extend = false, pixelUrl, enableSharedId = SHAREDID_DEFAULT_STATE} = {}} = config;
  
      if (extend) {
        try {
          if (typeof window[PUB_COMMON_ID] === 'object') {
            if (enableSharedId) {
              // If the page includes its own pubcid module, then there is nothing to do
              // except to update sharedid's expiration time
              storeData(config, readData(config));
            }
            return;
          }
        } catch (e) {
        }
  
        if (pixelUrl) {
          const callback = this.makeCallback(pixelUrl, storedId);
          return {callback: callback};
        } else {
          if (enableSharedId) {
            // Update with the same value to extend expiration time
            storeData(config, readData(config));
          }
          return {id: storedId};
        }
      }
    },
  
    /**
     * @param {string} domain
     * @param {HTMLDocument} document
     * @return {(string|undefined)}
     */
    domainOverride: function () {
      const domainElements = document.domain.split('.');
      const cookieName = `_gd${Date.now()}`;
      for (let i = 0, topDomain, testCookie; i < domainElements.length; i++) {
        const nextDomain = domainElements.slice(i).join('.');
  
        // write test cookie
        storage.setCookie(cookieName, '1', undefined, undefined, nextDomain);
  
        // read test cookie to verify domain was valid
        testCookie = storage.getCookie(cookieName);
  
        // delete test cookie
        storage.setCookie(cookieName, '', 'Thu, 01 Jan 1970 00:00:01 GMT', undefined, nextDomain);
  
        if (testCookie === '1') {
          // cookie was written successfully using test domain so the topDomain is updated
          topDomain = nextDomain;
        } else {
          // cookie failed to write using test domain so exit by returning the topDomain
          return topDomain;
        }
      }
    }
  };
  
  submodule('userId', pubCommonIdSubmodule);
  