{
  "version": 3,
  "sources": ["../../../js/src/cookiebar.ts"],
  "sourcesContent": ["/**\n * Cookiebar functionality, as a TS/JS module.\n *\n * About modules: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules\n *\n * The code is organized here in a way to make the templates work with Django's page\n * cache. This means that anything user-specific (so different django session and even\n * cookie consent cookies) cannot be baked into the templates, as that breaks caches.\n *\n * The cookie bar operates on the following principles:\n *\n * - The developer using the library includes the desired template in their django\n *   templates, using the HTML <template> element. This contains the content for the\n *   cookie bar.\n * - The developer is responsible for loading some Javascript that loads this script.\n * - The main export of this script needs to be called (showCookieBar), with the\n *   appropriate options.\n * - The options include the backend URLs where the retrieve data, which selectors/DOM\n *   nodes to use for various functionality and the hooks to tap into the accept/decline\n *   life-cycle.\n * - When a user accepts or declines (all) cookies, the call to the backend is made via\n *   a fetch request, bypassing any page caches and preventing full-page reloads.\n */\n\n/**\n * A serialized cookie group.\n *\n * See the backend model method `CookieGroup.as_json()`.\n */\nexport interface CookieGroup {\n  varname: string;\n  name: string;\n  description: string;\n  is_required: boolean;\n}\n\nexport interface Options {\n  statusUrl: string;\n  // TODO: also accept element rather than selector?\n  templateSelector: string;\n  /**\n   * DOM selector to the (script) tag holding the JSON-serialized cookie groups.\n   *\n   * This is typically rendered in a template with a template tag, e.g.\n   *\n   * ```django\n   * {% all_cookie_groups 'cookie-consent__cookie-groups' %}\n   * ```\n   *\n   * resulting in the selector: `'#cookie-consent__cookie-groups'`.\n   */\n  cookieGroupsSelector: string;\n  acceptSelector: string;\n  declineSelector: string;\n  /**\n   * Either a string (selector), DOMNode or null.\n   *\n   * If null, the bar is appended to the body. If provided, the node is used or looked\n   * up.\n   */\n  insertBefore: string | HTMLElement | null;\n  /**\n   * Optional callback for when the cookie bar is being shown.\n   *\n   * You can use this to add a CSS class name to the body, for example.\n   */\n  onShow?: () => void;\n  /**\n   * Optional callback called when cookies are accepted.\n   */\n  onAccept?: (acceptedGroups: CookieGroup[], event?: MouseEvent) => void;\n  /**\n   * Optional callback called when cookies are accepted.\n   */\n  onDecline?: (declinedGroups: CookieGroup[], event?: MouseEvent) => void;\n  /**\n   * Name of the header to use for the CSRF token.\n   *\n   * If needed, this can be read/set via `settings.CSRF_HEADER_NAME` in the backend.\n   */\n  csrfHeaderName: string;\n};\n\nexport interface CookieStatus {\n  csrftoken: string;\n  /**\n   * Backend endpoint to POST to to accept the cookie groups.\n   */\n  acceptUrl: string;\n  /**\n   * Backend endpoint to POST to to decline the cookie groups.\n   */\n  declineUrl: string;\n  /**\n   * Array of accepted cookie group varnames.\n   */\n  acceptedCookieGroups: string[];\n  /**\n   * Array of declined cookie group varnames.\n   */\n  declinedCookieGroups: string[];\n  /**\n   * Array of undecided cookie group varnames.\n   */\n  notAcceptedOrDeclinedCookieGroups: string[];\n}\n\nconst DEFAULT_FETCH_HEADERS: Record<string, string> = {\n  'X-Cookie-Consent-Fetch': '1'\n};\n\n/**\n * A simple wrapper around window.fetch that understands the django-cookie-consent\n * backend endpoints.\n *\n * @private - while exported, use at your own risk. This class is not part of the\n * public API covered by SemVer.\n */\nexport class FetchClient {\n  protected statusUrl: string;\n  protected csrfHeaderName: string;\n  protected cookieStatus: CookieStatus | null;\n\n  constructor(statusUrl: string, csrfHeaderName: string) {\n    this.statusUrl = statusUrl;\n    this.csrfHeaderName = csrfHeaderName;\n    this.cookieStatus = null;\n  }\n\n  async getCookieStatus(): Promise<CookieStatus> {\n    if (this.cookieStatus === null) {\n      const response = await window.fetch(\n        this.statusUrl,\n        {\n          method: 'GET',\n          credentials: 'same-origin',\n          headers: DEFAULT_FETCH_HEADERS,\n        }\n      );\n      this.cookieStatus = await response.json();\n    }\n\n    // type checker sanity check\n    if (this.cookieStatus === null) {\n      throw new Error('Unexpectedly received null cookie status');\n    }\n    return this.cookieStatus;\n  };\n\n  async saveCookiesStatusBackend (urlProperty: 'acceptUrl' | 'declineUrl') {\n    const cookieStatus = await this.getCookieStatus();\n    const url = cookieStatus[urlProperty];\n    if (!url) {\n      throw new Error(`Missing url for ${urlProperty} - was the cookie status not loaded properly?`);\n    }\n\n    await window.fetch(url, {\n      method: 'POST',\n      credentials: 'same-origin',\n      headers: {\n        ...DEFAULT_FETCH_HEADERS,\n        [this.csrfHeaderName]: cookieStatus.csrftoken\n      }\n    });\n  }\n}\n\n/**\n * Read the JSON script node contents and parse the content as JSON.\n *\n * The result is the list of available/configured cookie groups.\n * Use the status URL to get the accepted/declined status for an individual user.\n */\nexport const loadCookieGroups = (selector: string): CookieGroup[] => {\n  const node = document.querySelector<HTMLScriptElement>(selector);\n  if (!node) {\n    throw new Error(`No cookie groups (script) tag found, using selector: '${selector}'`);\n  }\n  return JSON.parse(node.innerText);\n};\n\nconst doInsertBefore = (beforeNode: HTMLElement, newNode: Node): void => {\n  const parent = beforeNode.parentNode;\n  if (parent === null) throw new Error('Reference node doesn\\'t have a parent.');\n  parent.insertBefore(newNode, beforeNode);\n}\n\ntype RegisterEventsOptions = Pick<\n  Options,\n  'acceptSelector' | 'onAccept' | 'declineSelector' | 'onDecline'\n> & Pick<\n  CookieStatus,\n  'acceptedCookieGroups' | 'declinedCookieGroups' | 'notAcceptedOrDeclinedCookieGroups'\n> & {\n  client: FetchClient,\n  cookieBarNode: Element;\n  cookieGroups: CookieGroup[];\n}\n\n/**\n * Register the accept/decline event handlers.\n *\n * Note that we can't just set the decline or accept cookie purely client-side, as the\n * cookie possibly has the httpOnly flag set.\n */\nconst registerEvents = ({\n  client,\n  cookieBarNode,\n  cookieGroups,\n  acceptSelector,\n  onAccept,\n  declineSelector,\n  onDecline,\n  acceptedCookieGroups: accepted,\n  declinedCookieGroups: declined,\n  notAcceptedOrDeclinedCookieGroups: undecided,\n}: RegisterEventsOptions): void => {\n\n  const acceptNode = cookieBarNode.querySelector<HTMLElement>(acceptSelector);\n  if (acceptNode) {\n    acceptNode.addEventListener('click', event => {\n      event.preventDefault();\n      const acceptedGroups = filterCookieGroups(cookieGroups, accepted.concat(undecided));\n      onAccept?.(acceptedGroups, event);\n      // trigger async action, but don't wait for completion\n      client.saveCookiesStatusBackend('acceptUrl');\n      cookieBarNode.parentNode!.removeChild(cookieBarNode);\n    });\n  }\n\n  const declineNode = cookieBarNode.querySelector<HTMLElement>(declineSelector);\n  if (declineNode) {\n    declineNode.addEventListener('click', event => {\n      event.preventDefault();\n      const declinedGroups = filterCookieGroups(cookieGroups, declined.concat(undecided));\n      onDecline?.(declinedGroups, event);\n      // trigger async action, but don't wait for completion\n      client.saveCookiesStatusBackend('declineUrl');\n      cookieBarNode.parentNode!.removeChild(cookieBarNode);\n    });\n  }\n};\n\n/**\n * Filter the cookie groups down to a subset of specified varnames.\n */\nconst filterCookieGroups = (cookieGroups: CookieGroup[], varNames: string[]) => {\n  return cookieGroups.filter(group => varNames.includes(group.varname));\n};\n\n// See https://github.com/microsoft/TypeScript/issues/283\nfunction cloneNode<T extends Node>(node: T) {\n  return <T>node.cloneNode(true);\n}\n\nexport const showCookieBar = async (options: Partial<Options> = {}): Promise<void> => {\n  const {\n    templateSelector = '#cookie-consent__cookie-bar',\n    cookieGroupsSelector = '#cookie-consent__cookie-groups',\n    acceptSelector = '.cookie-consent__accept',\n    declineSelector = '.cookie-consent__decline',\n    insertBefore = null,\n    onShow,\n    onAccept,\n    onDecline,\n    statusUrl = '',\n    csrfHeaderName = 'X-CSRFToken', // Django's default, can be overridden with settings.CSRF_HEADER_NAME\n  } = options;\n\n  const cookieGroups = loadCookieGroups(cookieGroupsSelector);\n\n  // no cookie groups -> abort, nothing to do\n  if (!cookieGroups.length) return;\n\n  const templateNode = document.querySelector<HTMLTemplateElement>(templateSelector);\n  if (!templateNode) {\n    throw new Error(`No (template) element found for selector '${templateSelector}'.`)\n  }\n\n  // insert before a given node, if specified, or append to the body as default behaviour\n  const doInsert = insertBefore === null\n    ? (cookieBarNode: Node) => document.querySelector('body')!.appendChild(cookieBarNode)\n    : typeof insertBefore === 'string'\n      ? (cookieBarNode: Node) => {\n        const referenceNode = document.querySelector<HTMLElement>(insertBefore);\n        if (referenceNode === null) throw new Error(`No element found for selector '${insertBefore}'.`)\n        doInsertBefore(referenceNode, cookieBarNode);\n      }\n      : (cookieBarNode: Node) => doInsertBefore(insertBefore, cookieBarNode)\n  ;\n\n  if (!statusUrl) throw new Error('Missing status URL option, did you forget to pass the `statusUrl` option?');\n\n  const client = new FetchClient(statusUrl, csrfHeaderName);\n  const cookieStatus = await client.getCookieStatus();\n\n  // calculate the cookie groups to invoke the callbacks. We deliberately fire those\n  // without awaiting so that our cookie bar is shown/hidden as soon as possible.\n  const {\n    acceptedCookieGroups,\n    declinedCookieGroups,\n    notAcceptedOrDeclinedCookieGroups\n  } = cookieStatus;\n\n  const acceptedGroups = filterCookieGroups(cookieGroups, acceptedCookieGroups);\n  if (acceptedGroups.length) onAccept?.(acceptedGroups);\n  const declinedGroups = filterCookieGroups(cookieGroups, declinedCookieGroups);\n  if (declinedGroups.length) onDecline?.(declinedGroups);\n\n  // there are no (more) cookie groups to accept, don't show the bar\n  if (!notAcceptedOrDeclinedCookieGroups.length) return;\n\n  // grab the contents from the template node and add them to the DOM, optionally\n  // calling the onShow callback\n  const childToClone = templateNode.content.firstElementChild;\n  if (childToClone === null) throw new Error('The cookie bar template element may not be empty.');\n  const cookieBarNode = cloneNode(childToClone);\n  registerEvents({\n    client,\n    cookieBarNode,\n    cookieGroups,\n    acceptSelector,\n    onAccept,\n    declineSelector,\n    onDecline,\n    acceptedCookieGroups,\n    declinedCookieGroups,\n    notAcceptedOrDeclinedCookieGroups,\n  });\n  doInsert(cookieBarNode);\n  onShow?.();\n};\n"],
  "mappings": "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",
  "names": ["cookieBarNode"]
}
