Overview
getEnvironment() reports whether the code is running in a browser, in a worker, in Node.js, or in an environment the SDK cannot classify. It returns a member of the Environment enum. Both are exported from @bitrix24/b24jssdk and are used internally to gate browser-only behaviour (DOM, localStorage, iframe messaging) away from server contexts.
import { getEnvironment, Environment } from '@bitrix24/b24jssdk'
if (getEnvironment() === Environment.BROWSE) {
// browser-only code (DOM, localStorage, …)
}
Method Signature
enum Environment {
UNKNOWN = 'unknown',
BROWSE = 'browser',
WORKER = 'worker',
NODE = 'node'
}
getEnvironment(): Environment
isBrowserLikeRuntime(): boolean
Key Concepts
- Detection order.
getEnvironment()first checks for a browser by testingwindowandwindow.document; if both are present it returnsEnvironment.BROWSE. Then Node, viaprocess.versions.node. Only then does it ask whether this scope is a worker scope —globalThis instanceof WorkerGlobalScope— returningEnvironment.WORKER; anything left isEnvironment.UNKNOWN. - Node is tested before the worker, and the trade has a cost on both sides. The runtimes that report both — a Deno worker, and a Cloudflare Worker with
nodejs_compat— are servers, where running a webhook is legitimate; calling them browser-like would keep credentials out of headers for no reason and warn on every call that a private secret had leaked. What it costs is the other direction: a browser worker that reports a Node version is read as a server. That is reachable —process@0.11.10leavesprocess.versionsempty, butunenv, the polyfill behind Nitro and Nuxt, answers{ node: '22.14.0' }— and what it costs there, worst first:TelegramHandlerwould send, putting the bot token in a URL from code anyone can read; anAuthorizationheader on arestApi:v3OAuth batch, which the portal's preflight refuses (a path already documented as not working from a browser); a missing client-side warning; and aUser-Agentthe browser drops anyway. Detection is not a substitute for not shipping a secret to the client. - It is an
instanceof, not a "is the name defined" check — the specification puts that constructor in a real worker's prototype chain, so asking whether this scope is one is the precise question. It is narrower than a name check, not airtight: Cloudflare'sworkerddoes haveWorkerGlobalScopein its chain and escapes only because it exposes a second, non-identical constructor as the global binding. Withnodejs_compatand a recent compatibility date it reports a Node version and is classified asNODE; on an older date, or withoutnodejs_compat, it isUNKNOWN. Neither is browser-like, which is the outcome that matters. - A Bun Web Worker reports
NODE, because it reports a Node version and Node is tested first — and Bun defines noWorkerGlobalScopeanywhere either way. Not the right name for it, and harmless: nothing there enforces the browser rules this member exists to respect. - The browser member is
BROWSE, its value is'browser'. Compare against the enum member (Environment.BROWSE), not a bare string, so a rename stays type-safe. BROWSEmeans there is a DOM — a worker is notBROWSE. A worker has nowindow.document, so code guarded onBROWSEmust not run there. But the browser's other rules do apply in one: CORS, forbidden request headers, and a bundle anyone can read.isBrowserLikeRuntime()is the question to ask when that is what matters — it istruefor bothBROWSEandWORKER, and it is what the SDK's own transport uses to decide that it must not send anAuthorizationheader or aUser-Agent.UNKNOWNcovers everything else. Edge/serverless runtimes and other hosts that satisfy none of the three tests fall through toEnvironment.UNKNOWN— treat it as "assume no browser globals". A Cloudflare Worker withoutnodejs_compatis one of them.
Error Handling
getEnvironment() never throws. The window and process checks are typeof guards, so a missing global degrades to the next branch rather than raising a ReferenceError. The worker check is an instanceof, which a global that is not a constructor makes throw a TypeError — it is wrapped in a try / catch that answers false, so an odd runtime cannot turn this into a crash on import.
Examples
Branch between browser, worker and Node.js:
import { getEnvironment, Environment } from '@bitrix24/b24jssdk'
switch (getEnvironment()) {
case Environment.BROWSE:
// Safe to touch the DOM / localStorage here
break
case Environment.WORKER:
// No DOM here — but the browser's rules still apply, so treat this code as
// public. See isBrowserLikeRuntime() below.
break
case Environment.NODE:
// Server-side path (e.g. read env vars, use a webhook client)
break
default:
// Environment.UNKNOWN — an unrecognised host; avoid browser and Node-specific globals
break
}
Tell a missing DOM from a missing browser:
import { getEnvironment, isBrowserLikeRuntime, Environment } from '@bitrix24/b24jssdk'
// "Can I touch the DOM?" — false in a worker.
const hasDom = getEnvironment() === Environment.BROWSE
// "Do the browser's rules apply?" — true in a worker, and the question to ask
// before shipping a secret or setting a forbidden header.
const isPublicCode = isBrowserLikeRuntime()
Guard browser-only work before it runs:
import { getEnvironment, Environment } from '@bitrix24/b24jssdk'
function isBrowser(): boolean {
return getEnvironment() === Environment.BROWSE
}
if (isBrowser()) {
// window / document are available
}
Alternatives and Recommendations
- Use
getEnvironment()for a coarse "where am I running" branch. For finer browser/OS/capability checks (Chrome vs Safari, iOS, touch,localStorageavailability) useBrowserinstead. - Prefer it over ad-hoc
typeof windowchecks. Centralising the detection keeps browser/Node branching consistent and matches the checks the SDK itself makes internally. - Ask
isBrowserLikeRuntime(), not=== BROWSE, for anything but the DOM. A worker is not a server: a webhook secret bundled into one is as public as on the main thread, and a header the browser forbids is dropped there too. - A runtime reported as
WORKERis treated as public code. If yours is a server that merely exposes the Worker API, that would mean a credential kept out of headers, noUser-Agent, and a "this webhook is client-side" warning on every call — which is why the check asks whether the global is a worker scope rather than whether the name exists. If you meet a runtime where it still answers wrongly, please open an issue. - Treat
UNKNOWNconservatively. When you cannot confirm a browser, avoid DOM andlocalStorageaccess — manyBrowsermethods assume those globals exist and can throw outside a browser.