{
  "version": 3,
  "sources": ["trip_deeplink_core.js"],
  "sourcesContent": ["/*\n * static/js/trip_deeplink_core.js \u2014 the map's `?trip=` / `?trip_share=`\n * arithmetic (SNOW-828).\n *\n * A trip page's \"View on the map\" control is a plain link to `/?trip=<uuid>`\n * (for somebody on the trip) or `/?trip_share=<token>` (for somebody holding\n * the link). This module owns everything about that arrival that is a pure\n * function of the query string and the two URL templates the page carries:\n * which parameter is present, where its geometry is fetched from, and what\n * the address bar should say once it has been read. `map.js` owns the\n * fetching, the source, the layers and the camera.\n *\n * ## Two parameters, not one\n *\n * A uuid and an 11-character token are not confusable, so ONE parameter\n * discriminated by the shape of its value would work. It is two because the\n * rule it encodes \u2014 a link-holder is never handed a uuid, and the two\n * endpoints scope themselves differently \u2014 belongs in the URL space where\n * the rest of the trips app keeps it, not inside a regex here. See\n * `apps/trips/views.py::_map_deep_link`.\n *\n * ## Consumed, never merely read\n *\n * `strip` exists because a parameter left in the address bar is a standing\n * instruction, not a record of how you got here: the map refetches its\n * overlays on several signals, and a `?trip=` still sitting there would fly\n * the camera back to the trip every time. `route_share` is stripped for the\n * same reason (`map.js::consumeRouteShareDeepLink`) \u2014 the difference is that\n * a route share ALSO lives in the session, so stripping it loses nothing,\n * whereas a trip is transient by choice and stripping it is the whole of its\n * lifetime. That is deliberate: a trip has a durable page of its own to go\n * back to, which the arrival banner links to.\n *\n * ## Exports (frozen `self.pwaTripDeepLinkCore`)\n *\n *   PARAMS                        \u2014 the two parameter names, in read order\n *   read(search)                  \u2192 {param, kind, value} or null\n *   endpointFor(link, templates)  \u2192 the geojson URL, or null\n *   strip(search)                 \u2192 the query string with both removed\n *\n * Every function here is pure, so it is unit-tested directly\n * (tests/js/test_trip_deeplink_core.js) with no browser and no map.\n */\n\n// @ts-check\n\n(function () {\n  'use strict';\n\n  /**\n   * The two parameters, in the order they are read.\n   *\n   * `trip` first: a viewer who is both on the trip AND holding the link\n   * should get the participant-scoped endpoint, which answers for a trip\n   * whose share has since been revoked. A URL carrying both is not a shape\n   * anything in this project emits, so the order is a tiebreak rather than\n   * a contract \u2014 but an arbitrary one would be a silent coin-toss.\n   *\n   * `kind` is what `endpointFor` keys its template choice off, so the\n   * parameter name and the identifier space it carries are named together\n   * rather than re-derived.\n   */\n  var PARAMS = Object.freeze([\n    Object.freeze({ param: 'trip', kind: 'uuid' }),\n    Object.freeze({ param: 'trip_share', kind: 'token' }),\n  ]);\n\n  /**\n   * The placeholder each identifier space is spelled with, matching\n   * `apps.public.views._trips_context`.\n   *\n   * @type {Object<string, string>}\n   */\n  var PLACEHOLDER = Object.freeze({ uuid: '__UUID__', token: '__TOKEN__' });\n\n  /**\n   * One trip deep link, as `read()` resolves it from a query string.\n   *\n   * @typedef {Object} TripDeepLink\n   * @property {string} param The query parameter it was read from.\n   * @property {string} kind The identifier space \u2014 `'uuid'` or `'token'`.\n   * @property {string} value The identifier itself, still attacker-controlled.\n   */\n\n  /**\n   * Read whichever trip parameter a query string carries.\n   *\n   * @param {string} search A `location.search`, with or without its `?`.\n   * @returns {?TripDeepLink} The link, or null when there is none.\n   */\n  function read(search) {\n    if (typeof search !== 'string' || !search) return null;\n    var params = new URLSearchParams(search);\n    for (var i = 0; i < PARAMS.length; i += 1) {\n      var value = params.get(PARAMS[i].param);\n      // An empty value is no value: `?trip=` is a malformed link, and\n      // fetching `/trips//route.geojson` for it would 404 noisily rather\n      // than doing nothing quietly.\n      if (value) {\n        return { param: PARAMS[i].param, kind: PARAMS[i].kind, value: value };\n      }\n    }\n    return null;\n  }\n\n  /**\n   * Build the geojson URL for one link from the page's URL templates.\n   *\n   * The value is percent-encoded into the template rather than\n   * concatenated raw. Neither identifier space contains a character that\n   * needs it \u2014 a uuid is hex and dashes, a token is `token_urlsafe` \u2014 but\n   * the value arrives from the address bar, so it is attacker-controlled,\n   * and a path built from it must not be assemblable into something else.\n   *\n   * @param {?TripDeepLink} link A `read()` result.\n   * @param {?Object<string, ?string>} templates The `{uuid, token}` URL\n   *   templates, keyed by the identifier space each parameter carries.\n   * @returns {?string} The URL to fetch, or null when either is missing.\n   */\n  function endpointFor(link, templates) {\n    if (!link || !templates) return null;\n    var template = templates[link.kind];\n    var placeholder = PLACEHOLDER[link.kind];\n    if (!template || !placeholder) return null;\n    if (template.indexOf(placeholder) === -1) return null;\n    return template.replace(placeholder, encodeURIComponent(link.value));\n  }\n\n  /**\n   * Remove BOTH trip parameters from a query string.\n   *\n   * Both, not just the one that was read: a URL carrying `?trip` and\n   * `?trip_share` together would otherwise keep the unread one, which is\n   * the standing instruction this function exists to prevent.\n   *\n   * @param {string} search A `location.search`, with or without its `?`.\n   * @returns {string} The remaining query string, `?`-prefixed, or ''.\n   */\n  function strip(search) {\n    if (typeof search !== 'string' || !search) return '';\n    var params = new URLSearchParams(search);\n    for (var i = 0; i < PARAMS.length; i += 1) params.delete(PARAMS[i].param);\n    var rest = params.toString();\n    return rest ? '?' + rest : '';\n  }\n\n  self.pwaTripDeepLinkCore = Object.freeze({\n    PARAMS: PARAMS,\n    read: read,\n    endpointFor: endpointFor,\n    strip: strip,\n  });\n})();\n"],
  "mappings": "CA8CC,UAAY,CACX,aAeA,IAAIA,EAAS,OAAO,OAAO,CACzB,OAAO,OAAO,CAAE,MAAO,OAAQ,KAAM,MAAO,CAAC,EAC7C,OAAO,OAAO,CAAE,MAAO,aAAc,KAAM,OAAQ,CAAC,CACtD,CAAC,EAQGC,EAAc,OAAO,OAAO,CAAE,KAAM,WAAY,MAAO,WAAY,CAAC,EAiBxE,SAASC,EAAKC,EAAQ,CACpB,GAAI,OAAOA,GAAW,UAAY,CAACA,EAAQ,OAAO,KAElD,QADIC,EAAS,IAAI,gBAAgBD,CAAM,EAC9BE,EAAI,EAAGA,EAAIL,EAAO,OAAQK,GAAK,EAAG,CACzC,IAAIC,EAAQF,EAAO,IAAIJ,EAAOK,CAAC,EAAE,KAAK,EAItC,GAAIC,EACF,MAAO,CAAE,MAAON,EAAOK,CAAC,EAAE,MAAO,KAAML,EAAOK,CAAC,EAAE,KAAM,MAAOC,CAAM,CAExE,CACA,OAAO,IACT,CAgBA,SAASC,EAAYC,EAAMC,EAAW,CACpC,GAAI,CAACD,GAAQ,CAACC,EAAW,OAAO,KAChC,IAAIC,EAAWD,EAAUD,EAAK,IAAI,EAC9BG,EAAcV,EAAYO,EAAK,IAAI,EAEvC,MADI,CAACE,GAAY,CAACC,GACdD,EAAS,QAAQC,CAAW,IAAM,GAAW,KAC1CD,EAAS,QAAQC,EAAa,mBAAmBH,EAAK,KAAK,CAAC,CACrE,CAYA,SAASI,EAAMT,EAAQ,CACrB,GAAI,OAAOA,GAAW,UAAY,CAACA,EAAQ,MAAO,GAElD,QADIC,EAAS,IAAI,gBAAgBD,CAAM,EAC9BE,EAAI,EAAGA,EAAIL,EAAO,OAAQK,GAAK,EAAGD,EAAO,OAAOJ,EAAOK,CAAC,EAAE,KAAK,EACxE,IAAIQ,EAAOT,EAAO,SAAS,EAC3B,OAAOS,EAAO,IAAMA,EAAO,EAC7B,CAEA,KAAK,oBAAsB,OAAO,OAAO,CACvC,OAAQb,EACR,KAAME,EACN,YAAaK,EACb,MAAOK,CACT,CAAC,CACH,GAAG",
  "names": ["PARAMS", "PLACEHOLDER", "read", "search", "params", "i", "value", "endpointFor", "link", "templates", "template", "placeholder", "strip", "rest"]
}
