{"version":3,"file":"environment.cjs","sources":["../../../src/tools/environment.ts"],"sourcesContent":["/**\n * Define the environment\n */\n\nexport enum Environment {\n  UNKNOWN = 'unknown',\n  BROWSE = 'browser',\n  /**\n   * A Web Worker, Shared Worker or Service Worker.\n   *\n   * Reported separately from {@link Environment.BROWSE} because the two differ\n   * in the one way most callers of this function care about: a worker has no\n   * DOM. It is not a server either — the browser's rules apply to it, CORS\n   * included, and code shipped to it is as public as code on the main thread.\n   * {@link isBrowserLikeRuntime} is the question to ask when that is what\n   * matters rather than the DOM.\n   */\n  WORKER = 'worker',\n  NODE = 'node'\n}\n\n/**\n * Is the global object of *this* scope a worker scope?\n *\n * Separate from the enum so the `instanceof` — and the reason it is an\n * `instanceof` rather than a `typeof` — stays readable. Never throws, and that\n * covers the read as well as the comparison: a global that is not a constructor\n * makes `instanceof` throw a `TypeError`, and a lazily-defined one is an\n * accessor that can throw on read. Either would otherwise become a crash on\n * import, in a function every transport calls.\n */\nfunction isWorkerGlobalScope(): boolean {\n  try {\n    // The read is inside the `try`, not before it: a lazily-defined global is an\n    // accessor, and an accessor can throw. That is the likeliest hostile shape of\n    // the three this guard covers, and it was the one left outside.\n    const scope = (globalThis as { WorkerGlobalScope?: unknown }).WorkerGlobalScope\n\n    if ('undefined' === typeof scope) {\n      return false\n    }\n\n    return globalThis instanceof (scope as new () => unknown)\n  } catch {\n    return false\n  }\n}\n\nexport function getEnvironment(): Environment {\n  // Check for the presence of a window (browser)\n  if (typeof window !== 'undefined' && typeof window.document !== 'undefined') {\n    return Environment.BROWSE\n  }\n\n  // Node before the worker test. This is a trade with a real cost on both sides,\n  // so both are written down.\n  //\n  // What the order buys: the runtimes that report **both** — a Deno worker, and\n  // a Cloudflare Worker with `nodejs_compat` — are servers. Running a webhook\n  // there is entirely legitimate, and calling them browser-like would keep\n  // credentials out of headers for no reason and, worse, warn on every call that\n  // a private secret had leaked. A false security alarm on a correct deployment\n  // is not a cheap failure.\n  //\n  // What it costs: a **browser** worker that reports a Node version is read as a\n  // server. That is not hypothetical — `process@0.11.10` sets\n  // `process.versions = {}`, but `unenv`, the polyfill behind Nitro and Nuxt,\n  // answers `{ node: '22.14.0' }` from a getter and installs itself as\n  // `globalThis.process`. A bundle that pulls it into a worker therefore lands\n  // in this branch. What that costs there, worst first: `TelegramHandler` would\n  // send, putting the bot token in a URL from code anyone can read — the only\n  // item on this list where a secret leaves the machine. Then an `Authorization`\n  // header on a `restApi:v3` OAuth batch, which the portal's preflight refuses —\n  // a path already documented as not working from a browser. Then a missing\n  // client-side warning, and a `User-Agent` the browser drops anyway.\n  //\n  // The handler is the one that matters, and detection cannot be its only\n  // defence: its own page says not to register it in code that ships to a\n  // browser or a worker, precisely because a runtime can lie about which it is.\n  //\n  // Measured both, rather than reasoned: `process@0.11.10/browser.js:160` and\n  // `unenv/dist/runtime/node/internal/process/process.mjs:67`.\n  //\n  // Check for the presence of process (Node.js)\n  if (typeof process !== 'undefined' && process.versions && process.versions.node) {\n    return Environment.NODE\n  }\n\n  // `globalThis instanceof WorkerGlobalScope`, not merely \"the constructor is\n  // defined\": the question is whether *this* scope is a worker scope, and the\n  // HTML specification puts that constructor in a real worker's prototype chain.\n  //\n  // It is a narrower test than the name check it replaces, but not a complete\n  // one, and the honest version of why is measured rather than assumed.\n  // Cloudflare's workerd **does** have `WorkerGlobalScope` in the chain — it\n  // escapes only because it exposes a second, non-identical constructor as the\n  // global binding, which is a quirk of that runtime rather than a rule. With\n  // `nodejs_compat` it reports a Node version and the branch above answers\n  // first, which is the outcome that matters; without it, it lands in `UNKNOWN`\n  // today and would land in `WORKER` if workerd ever unified the binding. That\n  // would cost a server a dropped `User-Agent` and a false \"your secret is\n  // public\" warning — worth knowing about rather than discovering.\n  //\n  // Measured: Node 22 and Bun define the global in neither the main thread nor\n  // their own workers — so a genuine Bun Web Worker reports `NODE`. That is not\n  // the right name for it; it is harmless, because nothing there enforces what\n  // this member exists to respect.\n  if (isWorkerGlobalScope()) {\n    return Environment.WORKER\n  }\n\n  return Environment.UNKNOWN\n}\n\n/**\n * Does the browser's rulebook apply here — CORS, forbidden request headers, a\n * bundle anyone can read?\n *\n * `getEnvironment() === Environment.BROWSE` is a different question, and answers\n * this one wrongly: it asks whether there is a DOM, which a worker does not\n * have. Everything else a browser enforces still applies in one.\n *\n * Being wrong in the two directions costs very different things.\n *\n * A false **no** in a browser context asks for a header the portal's preflight\n * does not allow, and the request never leaves at all: an opaque network error\n * after the retry budget burns. A false **yes** in a runtime that enforces none\n * of this now costs four things, not one — `idempotencyKey` refused outright\n * (`JSSDK_HTTP_IDEMPOTENCY_KEY_BROWSER`), a credential kept in the body rather\n * than a header (which the portal answers visibly), a dropped `User-Agent`, and\n * a \"this webhook is client-side\" warning on a runtime where the secret is in\n * fact private. The third is a false security alarm, which is why the worker\n * test asks whether this scope *is* a worker rather than whether the name is\n * defined: a runtime that merely exposes the constructor is not one.\n */\nexport function isBrowserLikeRuntime(): boolean {\n  const environment = getEnvironment()\n\n  return Environment.BROWSE === environment || Environment.WORKER === environment\n}\n"],"names":["Environment"],"mappings":";;;;;;;;;;;;AAIO,IAAK,WAAA,qBAAAA,YAAAA,KAAL;AACL,EAAAA,aAAA,SAAA,CAAA,GAAU,SAAA;AACV,EAAAA,aAAA,QAAA,CAAA,GAAS,SAAA;AAWT,EAAAA,aAAA,QAAA,CAAA,GAAS,QAAA;AACT,EAAAA,aAAA,MAAA,CAAA,GAAO,MAAA;AAdG,EAAA,OAAAA,YAAAA;AAAA,CAAA,EAAA,WAAA,IAAA,EAAA;AA2BZ,SAAS,mBAAA,GAA+B;AACtC,EAAA,IAAI;AAIF,IAAA,MAAM,QAAS,UAAA,CAA+C,iBAAA;AAE9D,IAAA,IAAI,WAAA,KAAgB,OAAO,KAAA,EAAO;AAChC,MAAA,OAAO,KAAA;AAAA,IACT;AAEA,IAAA,OAAO,UAAA,YAAuB,KAAA;AAAA,EAChC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,KAAA;AAAA,EACT;AACF;AAfS,MAAA,CAAA,mBAAA,EAAA,qBAAA,CAAA;AAiBF,SAAS,cAAA,GAA8B;AAE5C,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAe,OAAO,MAAA,CAAO,aAAa,WAAA,EAAa;AAC3E,IAAA,OAAO,SAAA;AAAA,EACT;AAgCA,EAAA,IAAI,OAAO,OAAA,KAAY,WAAA,IAAe,QAAQ,QAAA,IAAY,OAAA,CAAQ,SAAS,IAAA,EAAM;AAC/E,IAAA,OAAO,MAAA;AAAA,EACT;AAqBA,EAAA,IAAI,qBAAoB,EAAG;AACzB,IAAA,OAAO,QAAA;AAAA,EACT;AAEA,EAAA,OAAO,SAAA;AACT;AAhEgB,MAAA,CAAA,cAAA,EAAA,gBAAA,CAAA;AAuFT,SAAS,oBAAA,GAAgC;AAC9C,EAAA,MAAM,cAAc,cAAA,EAAe;AAEnC,EAAA,OAAO,SAAA,kBAAuB,eAAe,QAAA,kBAAuB,WAAA;AACtE;AAJgB,MAAA,CAAA,oBAAA,EAAA,sBAAA,CAAA;;;;;;"}