{"version":3,"file":"env.cjs","names":["envKeys","env: Env","whitelist: string[]","version","headers: Record<string, string>","headerKeys","customFetch: typeof fetch"],"sources":["../../src/helpers/env.ts"],"sourcesContent":["// This file exists to help normalize process.env amongst the backend\n// and frontend.  Many frontends (eg. Next, CRA) utilize webpack's DefinePlugin\n// along with prefixes, meaning we have to explicitly use the full `process.env.FOO`\n// string in order to read variables.\n\nimport type { Inngest } from \"../components/Inngest.ts\";\nimport type { Logger } from \"../middleware/logger.ts\";\nimport type { SupportedFrameworkName } from \"../types.ts\";\nimport { version } from \"../version.ts\";\nimport { envKeys, headerKeys } from \"./consts.ts\";\n\n/**\n * @public\n */\nexport type Env = Record<string, EnvValue>;\n\n/**\n * @public\n */\nexport type EnvValue = string | undefined;\n\n/**\n * devServerHost returns the dev server host by searching for the INNGEST_DEVSERVER_URL\n * environment variable (plus project prefixces for eg. react, such as REACT_APP_INNGEST_DEVSERVER_URL).\n *\n * If not found this returns undefined, indicating that the env var has not been set.\n *\n * @example devServerHost()\n */\nexport const devServerHost = (env: Env = getProcessEnv()): EnvValue => {\n  // The prefixed keys we look up for common frameworks (CRA, Next). The\n  // unprefixed `INNGEST_BASE_URL` / `INNGEST_DEV` are read directly by the\n  // Inngest client elsewhere, so we only check the prefixed variants here.\n  const keys = [\n    envKeys.ReactAppInngestBaseUrl,\n    envKeys.NextPublicInngestBaseUrl,\n    envKeys.ReactAppInngestDevMode,\n    envKeys.NextPublicInngestDevMode,\n  ];\n  const values = keys.map((key) => {\n    return env[key];\n  });\n\n  return values.find((v) => {\n    if (!v) {\n      return;\n    }\n\n    try {\n      return Boolean(new URL(v));\n    } catch {\n      // no-op\n    }\n\n    return;\n  });\n};\n\nexport type Mode = \"cloud\" | \"dev\";\n\nexport function checkModeConfiguration({\n  internalLogger,\n  mode,\n  signingKey,\n}: {\n  internalLogger: Logger;\n  mode: Mode;\n  signingKey: string | undefined;\n}): boolean {\n  if (mode === \"cloud\" && !signingKey) {\n    internalLogger.error(\n      `In cloud mode but no signing key found. For local dev, set the INNGEST_DEV=1 env var. For production, set the ${envKeys.InngestSigningKey} env var`,\n    );\n\n    return false;\n  }\n\n  return true;\n}\n\nexport const normalizeUrl = (\n  urlString: string,\n  scheme: string = \"http://\",\n): string => {\n  if (urlString === \"undefined\") {\n    throw new Error(\"URL undefined\");\n  }\n  if (urlString.includes(\"://\")) {\n    return urlString;\n  }\n\n  return `${scheme}${urlString}`;\n};\n\n/**\n * getEnvironmentName returns the suspected branch name for this environment by\n * searching through a set of common environment variables.\n *\n * This could be used to determine if we're on a branch deploy or not, though it\n * should be noted that we don't know if this is the default branch or not.\n */\nexport const getEnvironmentName = (env: Env = getProcessEnv()): EnvValue => {\n  /**\n   * Order is important; more than one of these env vars may be set, so ensure\n   * that we check the most specific, most reliable env vars first.\n   */\n  return (\n    env[envKeys.InngestEnvironment] ||\n    env[envKeys.BranchName] ||\n    env[envKeys.VercelBranch] ||\n    env[envKeys.NetlifyBranch] ||\n    env[envKeys.CloudflarePagesBranch] ||\n    env[envKeys.RenderBranch] ||\n    env[envKeys.RailwayBranch]\n  );\n};\n\nexport const processEnv = (key: envKeys): EnvValue => {\n  if (!Object.values(envKeys).includes(key)) {\n    throw new Error(`Unknown env var: ${key}`);\n  }\n\n  return getProcessEnv()[key];\n};\n\n/**\n * The Deno environment, which is not always available.\n */\ndeclare const Deno: {\n  env: { toObject: () => Env };\n};\n\n/**\n * The Netlify environment, which is not always available.\n */\ndeclare const Netlify: {\n  env: { toObject: () => Env };\n};\n\n/**\n * Get the current process env vars. Only includes env vars that we care about.\n *\n * Returns an empty object if they can't be read.\n */\nexport function getProcessEnv(): Env {\n  const env: Env = {};\n  const whitelist: string[] = Object.values(envKeys);\n\n  for (const [k, v] of Object.entries(allProcessEnv())) {\n    if (!whitelist.includes(k)) {\n      continue;\n    }\n    env[k] = v;\n  }\n\n  return protectEnv(env);\n}\n\n/**\n * Install a `toJSON` that returns `{}` so env var values can't leak via\n * `JSON.stringify`, regardless of where the object is reachable from. The\n * property is enumerable so it survives spreads (e.g. `{...env}`) which carries\n * the protection into the new object. But note that the enumerability also\n * means the static type is technically incorrect.\n *\n * Callers still read values via normal `env[key]` access.\n */\nexport function protectEnv(env: Env): Env {\n  return {\n    ...env,\n\n    // @ts-expect-error - intentional\n    toJSON: () => {\n      return {};\n    },\n  };\n}\n\n/**\n * allProcessEnv returns the current process environment variables, or an empty\n * object if they cannot be read, making sure we support environments other than\n * Node such as Deno, too.\n *\n * Using this ensures we don't dangerously access `process.env` in environments\n * where it may not be defined, such as Deno or the browser.\n */\nconst allProcessEnv = (): Env => {\n  // Node, or Node-like environments\n  try {\n    if (process.env) {\n      return process.env;\n    }\n  } catch (_err) {\n    // noop\n  }\n\n  // Deno\n  try {\n    const env = Deno.env.toObject();\n\n    if (env) {\n      return env;\n    }\n  } catch (_err) {\n    // noop\n  }\n\n  // Netlify\n  try {\n    const env = Netlify.env.toObject();\n\n    if (env) {\n      return env;\n    }\n  } catch (_err) {\n    // noop\n  }\n\n  return {};\n};\n\n/**\n * Generate a standardised set of headers based on input and environment\n * variables.\n *\n *\n */\nexport const inngestHeaders = (opts?: {\n  /**\n   * The environment variables to use instead of `process.env` or any other\n   * default source. Useful for platforms where environment variables are passed\n   * in alongside requests.\n   */\n  env?: Env;\n\n  /**\n   * The framework name to use in the `X-Inngest-Framework` header. This is not\n   * always available, hence being optional.\n   */\n  framework?: string;\n\n  /**\n   * The environment name to use in the `X-Inngest-Env` header. This is likely\n   * to be representative of the target preview environment.\n   */\n  inngestEnv?: string;\n\n  /**\n   * The Inngest client that's making the request. The client itself will\n   * generate a set of headers; specifying it here will ensure that the client's\n   * headers are included in the returned headers.\n   */\n  client?: Inngest;\n\n  /**\n   * The Inngest server we expect to be communicating with, used to ensure that\n   * various parts of a handshake are all happening with the same type of\n   * participant.\n   */\n  expectedServerKind?: string;\n\n  /**\n   * Any additional headers to include in the returned headers.\n   */\n  extras?: Record<string, string>;\n}): Record<string, string> => {\n  const sdkVersion = `inngest-js:v${version}`;\n  const headers: Record<string, string> = {\n    \"Content-Type\": \"application/json\",\n    \"User-Agent\": sdkVersion,\n    [headerKeys.SdkVersion]: sdkVersion,\n    [headerKeys.SdkHandled]: \"true\",\n  };\n\n  if (opts?.framework) {\n    headers[headerKeys.Framework] = opts.framework;\n  }\n\n  if (opts?.expectedServerKind) {\n    headers[headerKeys.InngestExpectedServerKind] = opts.expectedServerKind;\n  }\n\n  const env = {\n    ...getProcessEnv(),\n    ...opts?.env,\n  };\n\n  const inngestEnv = opts?.inngestEnv || getEnvironmentName(env);\n  if (inngestEnv) {\n    headers[headerKeys.Environment] = inngestEnv;\n  }\n\n  const platform = getPlatformName(env);\n  if (platform) {\n    headers[headerKeys.Platform] = platform;\n  }\n\n  return {\n    ...headers,\n    ...opts?.client?.[\"headers\"],\n    ...opts?.extras,\n  };\n};\n\n/**\n * A set of checks that, given an environment, will return `true` if the current\n * environment is running on the platform with the given name.\n */\nconst platformChecks = {\n  /**\n   * Vercel Edge Functions don't have access to environment variables unless\n   * they are explicitly referenced in the top level code, but they do have a\n   * global `EdgeRuntime` variable set that we can use to detect this.\n   */\n  vercel: (env) =>\n    env[envKeys.IsVercel] === \"1\" || typeof EdgeRuntime === \"string\",\n  netlify: (env) => env[envKeys.IsNetlify] === \"true\",\n  \"cloudflare-pages\": (env) => env[envKeys.IsCloudflarePages] === \"1\",\n  render: (env) => env[envKeys.IsRender] === \"true\",\n  railway: (env) => Boolean(env[envKeys.RailwayEnvironment]),\n} satisfies Record<string, (env: Env) => boolean>;\n\ndeclare const EdgeRuntime: string | undefined;\n\n/**\n * A set of checks that, given an environment, will return `true` if the current\n * environment and platform supports streaming responses back to Inngest.\n *\n * Streaming capability is both framework and platform-based. Frameworks are\n * supported in serve handlers, and platforms are checked here.\n *\n * As such, this record declares which platforms we explicitly support for\n * streaming and is used by {@link platformSupportsStreaming}.\n */\nconst streamingChecks: Partial<\n  Record<\n    keyof typeof platformChecks,\n    (framework: SupportedFrameworkName, env: Env) => boolean\n  >\n> = {\n  /**\n   * \"Vercel supports streaming for Serverless Functions, Edge Functions, and\n   * React Server Components in Next.js projects.\"\n   *\n   * In practice, however, there are many reports of streaming not working as\n   * expected on Serverless Functions, so we resort to only allowing streaming\n   * for Edge Functions here.\n   *\n   * See {@link https://vercel.com/docs/frameworks/nextjs#streaming}\n   */\n  vercel: (_framework, _env) => typeof EdgeRuntime === \"string\",\n  \"cloudflare-pages\": () => true,\n};\n\nexport const getPlatformName = (env: Env) => {\n  return (Object.keys(platformChecks) as (keyof typeof platformChecks)[]).find(\n    (key) => {\n      return platformChecks[key](env);\n    },\n  );\n};\n\n/**\n * Returns `true` if we believe the current environment supports streaming\n * responses back to Inngest.\n *\n * We run a check directly related to the platform we believe we're running on,\n * usually based on environment variables.\n */\nexport const platformSupportsStreaming = (\n  framework: SupportedFrameworkName,\n  env: Env = getProcessEnv(),\n): boolean => {\n  return (\n    streamingChecks[getPlatformName(env) as keyof typeof streamingChecks]?.(\n      framework,\n      env,\n    ) ?? false\n  );\n};\n\n/**\n * A unique symbol used to mark a custom fetch implementation. We wrap the\n * implementations to provide some extra control when handling errors.\n */\nconst CUSTOM_FETCH_MARKER = Symbol(\"Custom fetch implementation\");\n\n/**\n * Given a potential fetch function, return the fetch function to use based on\n * this and the environment.\n */\nexport const getFetch = (\n  logger: Logger,\n  givenFetch?: typeof fetch,\n): typeof fetch => {\n  /**\n   * If we've explicitly been given a fetch function, use that.\n   */\n  if (givenFetch) {\n    if (CUSTOM_FETCH_MARKER in givenFetch) {\n      return givenFetch;\n    }\n\n    /**\n     * We wrap the given fetch function to provide some extra control when\n     * handling errors.\n     */\n    const customFetch: typeof fetch = async (...args) => {\n      try {\n        return await givenFetch(...args);\n      } catch (err) {\n        /**\n         * Capture warnings that are not simple fetch failures and highlight\n         * them for the user.\n         *\n         * We also use this opportunity to log the causing error, as code higher\n         * up the stack will likely abstract this.\n         */\n        if (\n          !(err instanceof Error) ||\n          !err.message?.startsWith(\"fetch failed\")\n        ) {\n          logger.error(\n            { err },\n            \"A request failed when using a custom fetch implementation; this may be a misconfiguration. Make sure that your fetch client is correctly bound to the global scope.\",\n          );\n        }\n\n        throw err;\n      }\n    };\n\n    /**\n     * Mark the custom fetch implementation so that we can identify it later, in\n     * addition to adding some runtime properties to it to make it seem as much\n     * like the original fetch as possible.\n     */\n    Object.defineProperties(customFetch, {\n      [CUSTOM_FETCH_MARKER]: {},\n      name: { value: givenFetch.name },\n      length: { value: givenFetch.length },\n    });\n\n    return customFetch;\n  }\n\n  /**\n   * Browser or Node 18+\n   */\n  try {\n    if (typeof globalThis !== \"undefined\" && \"fetch\" in globalThis) {\n      return fetch.bind(globalThis);\n    }\n  } catch (_err) {\n    // no-op\n  }\n\n  /**\n   * Existing polyfilled fetch\n   */\n  if (typeof fetch !== \"undefined\") {\n    return fetch;\n  }\n\n  /**\n   * Environments where fetch cannot be found and must be polyfilled\n   */\n  return require(\"cross-fetch\") as typeof fetch;\n};\n\n/**\n * If `Response` isn't included in this environment, it's probably an earlier\n * Node env that isn't already polyfilling. This function returns either the\n * native `Response` or a polyfilled one.\n */\nexport const getResponse = (): typeof Response => {\n  if (typeof Response !== \"undefined\") {\n    return Response;\n  }\n\n  return require(\"cross-fetch\").Response;\n};\n\n/**\n * Given an unknown value, try to parse it as a `boolean`. Useful for parsing\n * environment variables that could be a selection of different values such as\n * `\"true\"`, `\"1\"`.\n *\n * If the value could not be confidently parsed as a `boolean` or was seen to be\n * `undefined`, this function returns `undefined`.\n */\nexport const parseAsBoolean = (value: unknown): boolean | undefined => {\n  if (typeof value === \"boolean\") {\n    return value;\n  }\n\n  if (typeof value === \"number\") {\n    return Boolean(value);\n  }\n\n  if (typeof value === \"string\") {\n    const trimmed = value.trim().toLowerCase();\n\n    if (trimmed === \"undefined\") {\n      return undefined;\n    }\n\n    if ([\"true\", \"1\"].includes(trimmed)) {\n      return true;\n    }\n\n    if ([\"false\", \"0\"].includes(trimmed)) {\n      return false;\n    }\n  }\n\n  return undefined;\n};\n"],"mappings":"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"}