@elu-labs/analytics-browser

中文文档
1.412.4发布于 2026/11/01MITTypeScriptESM + CJS零依赖

ELU Analytics browser SDK.

原始使用文档
文档目录10 节
  1. Script installation
  2. NPM installation
  3. Self-hosted instance
  4. Initialization options
  5. Web Vitals and optional performance samples
  6. Identify users and organizations
  7. Consent and privacy
  8. Feature flags
  9. Other common operations
  10. License

ELU Analytics for the web

Capture product events, connect activity to users and organizations, evaluate feature flags, and record sessions according to your ELU privacy settings.

Use one installation method per page: the ELU script or the NPM client. Both use your ELU site key. Keep account credentials and server secrets out of browser code.

Script installation

Copy the Web snippet from your ELU integration settings into your site's <head>. Keep the supplied URL and site key:

<script async src="https://elu.dev/v1/YOUR_SITE_KEY.js"></script>

The script initializes ELU for that site. Do not call init(). After the loader has run, use window.elu:

window.elu?.capture('checkout_started', { source: 'pricing_page' })
window.elu?.identify('user_123', { plan: 'pro' })

The script loads asynchronously. Optional chaining prevents an error if it has not loaded, but does not queue that call. Connect important early calls to the script's load event, or use the NPM client below. Once the loader has created window.elu, state-changing calls can wait for SDK startup; getters may still return undefined.

NPM installation

Install the package and use the site key from your ELU project's Web settings.

npm install @elu-labs/analytics-browser@latest

The page itself must use HTTPS, including during local development. Serve your local app with a certificate trusted by your browser, or use a trusted HTTPS preview. Initialization stays paused on HTTP localhost; HTTPS collection endpoints alone are not sufficient.

Initialize once in browser code after your consent manager permits analytics, and reuse the returned client:

import { createEluClient } from '@elu-labs/analytics-browser'

const elu = createEluClient('YOUR_SITE_KEY')

elu.capture('checkout_started', { source: 'pricing_page' })

For an ELU collection instance on your own domain, set its HTTPS URL:

const elu = createEluClient('YOUR_SITE_KEY', {
    api_host: 'https://analytics.example.com',
})

The instance must serve site configuration and the events, flags and replay endpoints that configuration authorizes. Its recorder assets use the same base URL unless you supply assets_host. Cloud defaults need no host options. Both options accept an absolute HTTPS URL with an optional path prefix, such as https://analytics.example.com/elu. Endpoints are placed under that prefix; credentials, queries, fragments and irregular paths are refused. The selected URLs cannot change after the first successful client initialization on a page. A served configuration cannot redirect requests to another origin or outside the selected path prefix.

Self-hosted instance

With a custom api_host, the client makes no request to any ELU Cloud origin:

RequestELU CloudCustom api_host
Site configuration /sdk/v2/<key>/confighttps://elu.devapi_host
Events, flags and replayhttps://ingest.elu.devapi_host
Recorder /sdk/recorder/<sha256>.jshttps://assets.elu.devassets_host or api_host
Script storage modulehttps://elu.devassets_host or api_host

If the recorder host does not deliver the recorder, for example because a network filter or a Content Security Policy blocks only that host, the client fetches the same file once from the events host, at /static/elu-owned/sdk/recorder/<sha256>.js under https://ingest.elu.dev or your api_host. Its bytes must match the pinned digest.

The client uses ELU-owned storage for identity and consent. Existing ELU state is retained; a visitor without it receives a fresh identity under the site's opt_out_capturing_by_default and respect_dnt settings. Earlier SDK storage and rollback synchronization are no longer supported.

A page can load the instance's script directly:

<script src="https://analytics.example.com/static/elu.js"></script>
<script>_eluSdk.init('YOUR_PROJECT_TOKEN', { api_host: 'https://analytics.example.com' })</script>

init starts the same client as the snippet and publishes window.elu when the page has none. It requires an explicit api_host and an HTTPS page. The installation guide describes page options, shared identity and consent.

When canvas capture is on (the page's session_recording.captureCanvas ?? your instance's setting) and the instance does not hide images and media, the recorder also sends canvas pixels as replay frames. It starts a Web Worker from a blob: URL, so a Content Security Policy must allow worker-src blob: (or script-src blob:) as well as connect-src for your instance. Without it, canvas is left out and the rest of the replay continues. A WebGL canvas created before recording starts needs preserveDrawingBuffer: createEluClient supplies it for contexts created after it is called (unless the page turns canvas off), as the instance's install snippet does, so create the client before your map or chart.

Canvas frames are pictures, so text masking, input masking and mask selectors do not apply to them: text drawn into a canvas is recorded as pixels even when all text is masked. A block rule, or hiding images and media, keeps a canvas out of replay.

When replay network capture is on (the page's capture_performance and session_recording.recordHeaders/recordBody ?? your instance's setting), the recorder adds the page's resource and navigation timings and, as selected, the method, status, headers and bodies of fetch and XMLHttpRequest requests. Credential headers are always recorded as redacted; large, binary and sensitive-looking bodies are replaced by a note; requests to your instance are never recorded. Replay masking does not apply to these rows, and capture_network_requests does not control them.

When console recording is on (the page's enable_recording_console_log ?? your instance's setting), the recorder also records the page's console.* calls and uncaught errors in the replay, as the earlier script did: each entry keeps its level, a stack trace and the serialized arguments, at most ten strings of 2,000 characters. The console is patched only while a replay session is recording and restored when it stops. Masking settings do not apply to console entries: what a page logs is recorded whatever the text rule, as it was for the earlier script. A DOM node logged to the console is recorded as its selector path, never its markup, and a logged input, composition or keyboard event carries no typed text, so blocked or masked text and input values do not leave the page that way. The page's enable_recording_console_log decides: true records console even where the instance does not ask for it, false never does; omitted, the instance's setting decides. The same precedence, page option ?? project grant, applies to every automatic collector, and a page-enabled one runs with the same masking and bounds as a granted one. A replay starts with a full snapshot of the page and takes a fresh one at the first change after every session_recording.full_snapshot_interval_millis of activity, five minutes unless the page says otherwise. Each checkpoint begins a new replay chunk, so seeking lands on one and a player that cannot apply an incremental change is whole again at the next. A chunk never carries more than 5,000 events, whatever its size in bytes.

The package exports createEluClient and the TypeScript type EluClient. ESM and CommonJS root imports are supported. Importing the package does not initialize analytics. Call the factory only in a browser, never while rendering on the server. In a component framework, retain one client for the page rather than creating one on every render or mount. The NPM client does not create window.elu; keep the returned object.

Initialization options

OptionDefaultBehavior
api_hostELU CloudHTTPS URL of your instance, optionally with a path prefix (https://example.com/elu), for its events, flags and replay. Site authority must advertise endpoints under it.
assets_hostInstance origin, or ELU Cloud assets for the Cloud ingest originOptional separate HTTPS URL, optionally with a path prefix, for recorder assets.
capture_pageview'history_change'Initial pageview plus History API and hash-route changes. true captures the initial pageview only; false disables automatic pageviews.
capture_pageleave'if_capture_pageview'$pageleave on pagehide, with the left page's $prev_pageview_* duration and scroll values, delivered by the next page the visitor loads on the site. 'if_capture_pageview' sends it whenever capture_pageview is on; true always; false never.
autocapturetrueEnables automatic interaction capture, subject to site policy and consent. An object also enables it, narrowed by the 1.409.5 url_allowlist, url_ignorelist, dom_event_allowlist, element_allowlist, css_selector_allowlist and css_selector_ignorelist lists; element_attribute_ignorelist names attributes that are never sent as element content.
capture_exceptionsThe project's setting in ELUUnhandled errors and promise rejections, with stack frames (script URL without query, function, line, column). true turns them on for this page, false off; omitted, the project's captureExceptions decides, whose suppression rules apply whenever it grants them. Each exception type is rate limited as in the 1.409.5 bundle (10 tokens refilled by one every 10 seconds; a burst sends at most 9). Manual captureException() remains available and is not limited.
capture_copied_textYour instance's captureCopiedTextSet true for $copy_autocapture when a visitor copies or cuts, whether or not your instance grants copied text: the selected text (at most 255 characters, card and social-security numbers dropped) and $copy_type. Set false to never send it, whatever your instance grants; unset, your instance's captureCopiedText decides. Either way the selection is judged under your instance's masking and the page's own replay masking (session_recording's mask and block selectors and classes, and the elu-mask class): nothing is sent under mask_all_text, for a visitor who opted out, when the element copied from, an ancestor or any part of the selection reaches a no-capture marker, an element your instance masks or blocks, an image element (img, picture, video) while your instance blocks images, rrweb's default rr-block or rr-mask class, or a password, hidden or sensitively named field, from any form field (even where the replay records its value), or from a closed shadow tree, nested in an open one or not, in every browser. Only text the page shows can account for a selection: one carrying a form field's default text, a closed tree's text or hidden text sends nothing. A selection too large to judge within 20 ms, or over 50,000 nodes, sends nothing rather than delaying the copy.
capture_network_requeststrueSet false to stop reporting fetch/XHR requests. Each report carries method, status, timing and a scrubbed path only — never query strings, headers or bodies. Up to 200 per page.
capture_performanceWeb Vitals and replay network timing: the project's settings in ELU; samples off unless enabled in ELUtrue turns on Web Vitals, replay network timing and the memory and long-task samples for this page; false turns all of them off; omitted, the project decides. An object sets web_vitals and network_timing (each decides for the page), web_vitals_allowed_metrics, memory and long_tasks individually; see below.
capture_heatmapsThe project's heatmap setting in ELUtrue or { flush_interval_milliseconds } (default 5,000) sends click, rage-click, dead-click and mouse-move positions as $$heatmap on this page; false sends none; omitted, the project's captureHeatmaps decides. Coordinates only.
enable_recording_console_logThe project's setting in ELUConsole recording in replay. true records the page's console calls on this page, false never does; omitted, the project's captureConsole decides.
disable_session_recordingfalsetrue keeps replay off on this page even when your instance grants it. Events are unaffected. There is no method to turn it back on.
session_recording{ full_snapshot_interval_millis: 300000 }full_snapshot_interval_millis: milliseconds of activity between full snapshots of the page in replay, kept between 10 s and 30 min. recordHeaders and recordBody decide replay network headers and bodies for the page (true on, false off; omitted, the project's captureNetwork); captureCanvas: { recordCanvas, canvasFps, canvasQuality } decides canvas pixels the same way, bounded (1 to 12 frames per second, a quality above 0 up to 1), within the instance's masking. Page masking on top of the instance's masking, only ever stricter: maskAllInputs: true, maskInputOptions, maskTextSelector, maskTextClass, blockSelector, blockClass, ignoreClass, ignoreSelector. A RegExp class is read in the exact-class shape /^(a|b)$/ only. Other members are ignored.
mask_all_textYour instance's captureElementContenttrue masks every text node in replay and keeps element text out of interaction events, whatever your instance grants. false sends element text on interaction events for this page whether or not your instance grants it, within your instance's masking (none where it masks all text); it never unmasks replay. Omitted, your instance decides.
mask_all_element_attributesYour instance's captureElementContenttrue keeps element attributes out of interaction events, whatever your instance grants. false sends them for this page whether or not your instance grants them, less the autocapture element_attribute_ignorelist. Omitted, your instance decides.
loaded(client)NoneRuns after local startup and before the initial automatic pageview. It does not mean configuration, flags or delivery have completed. An async callback is not awaited.
before_sendNoneA function, or an array of functions called in order, run on every event before it is queued: capture, captureException and the automatic events alike. It receives { event, properties, $set?, $set_once?, timestamp }, with the registered super properties merged under the event's own, and returns it, edited in place or replaced, or null to drop the event; a super property it removes stays removed. Identity changes pass through it as the events the earlier script sent for them ($identify with $set/$set_once, $set with properties.$set/$set_once, $create_alias, $groupidentify with properties.$group_set); null drops the change. A hook that throws drops the event with one console error. Replay data does not pass through it.
property_denylist[]Property names removed from every event's properties, registered super properties included, before sanitize_properties and before_send. The earlier script's property_blacklist is still read and merged in front of it.
sanitize_propertiesNoneDeprecated; use before_send. (properties, eventName) returns the properties to send; runs after the denylist and before before_send.

The script loader chooses its startup options; changing NPM options does not configure a script installation. Replay and privacy settings come from ELU; a page can only add to them. Elements with the elu-mask class have their text masked, elu-no-capture are recorded as placeholders, and inputs with elu-ignore-input have their changes left out, whatever the instance's rules say. A session_recording member the recorder cannot honour (a RegExp class outside the exact-class shape /^(a|b)$/, a selector that does not parse or is left open by an unclosed comment, string, bracket or parenthesis or a trailing backslash) keeps replay off on that page rather than recording what it asked to hide, and the runtime names the option once in the console; maskTextFn and maskInputFn are not run, and the content they would have rewritten is masked as below. There is no set_config() or per-request endpoint override on this client: the earlier script's names are accepted on the returned object for pages written against it, set_config() is ignored with a console warning, and startSessionRecording() / stopSessionRecording() switch the page's recorder within what the instance allows. See earlier script methods.

Web Vitals and optional performance samples

When Web Vitals are on (the page's capture_performance ?? your ELU project's captureWebVitals), each page reports its Core Web Vitals (LCP, FCP, CLS and INP, or the metrics the project lists) as one or more $web_vitals events carrying the values, their ratings and the page location only. Metrics that settle early are sent together after a short delay; CLS and INP are also sent while the page is visible, each time they change, because capture stops once a page is hidden. A page that first paints while hidden reports no paint timings. The page's option decides in both directions: capture_performance: true or { web_vitals: true } turns them on even where the project does not grant them (every metric, narrowed by web_vitals_allowed_metrics), { web_vitals: false } or capture_performance: false turns them off; omitted, the project decides, and web_vitals_allowed_metrics narrows its metrics.

Performance sampling is off when neither your ELU site configuration nor local initialization options enable it. For the NPM client, you can opt in explicitly:

const elu = createEluClient('YOUR_SITE_KEY', {
    capture_performance: {
        memory: true,
        long_tasks: true,
        sample_interval_ms: 30_000,
    },
})

The TypeScript options type is PerformanceCaptureConfig. Each local setting overrides its remote counterpart; true enables both metrics and false disables both, along with Web Vitals. A capture_performance value that is neither a boolean nor an object turns the samples off. The interval defaults to 30 seconds, with a five-second minimum. If a renewed site configuration omits performance settings, previous remote opt-ins end; explicit local overrides still require valid capture authority.

Samples use the normal $performance_sample event queue. Memory measurements are available only when the browser exposes heap statistics; long-task measurements require PerformanceObserver support. Unsupported measurements are omitted. Collection pauses while the page is hidden and discards the old measurement window when identity, consent or capture configuration changes. Opt-out, blocked capture and expired configuration prevent sampling.

A final sample on hide or navigation is best-effort. Browser lifecycle and consent checks still apply; closing a tab or process can prevent enqueue or delivery. This feature does not guarantee an unload sample or process-death recovery.

Identify users and organizations

Use your application's stable internal ID, not an email address, name or phone number. Identify after sign-in and before related custom events:

elu.identify('user_123', { plan: 'pro' }, { signup_source: 'website' })
elu.group('organization', 'org_456', { plan: 'pro' })
elu.capture('project_created', { template: 'blank' })

// On sign-out:
elu.reset()

The third argument to identify sets person properties only if absent. Use setPersonProperties for later changes. group associates subsequent activity with that group type; resetGroups clears group associations.

reset() begins a new anonymous identity and clears super properties, groups and flag context. It keeps the device ID ($device_id) unless called as reset(true), and preserves the visitor's opt-out choice. Identity reset is not consent to resume capture; use your consent manager for that decision.

If consent is required before any SDK activity, wait to load the script or call createEluClient until consent is granted. Initialization can open browser storage and request configuration; opting out afterward cannot undo those initial actions.

For an initialized client:

elu.opt_out_capturing()

// Only when the visitor explicitly opts back in:
elu.opt_in_capturing({ captureEventName: false })

Calling opt_in_capturing() without options also requests an $opt_in event. Opt-out suppresses capture, flag evaluation and replay. It does not delete data already delivered to ELU.

Keep passwords, payment details, tokens and sensitive personal data out of event properties, IDs and exception messages. Replay masking does not sanitize arbitrary custom event properties.

Session replay is activated separately for each project. Contact ELU support if it is unavailable; installing the package does not enable recording. Replay also requires consent, a site configuration that permits it, and an eligible browser and session. Review the site's input, text, image and blocked-element settings in ELU; do not assume all visible text and images are masked by default.

Masking in replay always means length-preserving *. A masked text node keeps its whitespace and has every other character replaced by *, and a masked input value becomes one * per character, so Team settings is recorded as **** ******** and a seven-character password as *******. Masked content is never blanked or dropped: the replay keeps the page's layout, and nothing of the original leaves the page except its length. HTML comments are always masked the same way. Blocking is the separate rule that removes content: a blocked element is recorded as an empty placeholder of the same size, and blocked images keep no src.

Feature flags

Flags load asynchronously. undefined means no usable value is available; handle it separately from an explicit false:

const unsubscribe = elu.onFeatureFlags((_keys, _variants, context) => {
    if (context?.errorsLoading) return
    const enabled = elu.isFeatureEnabled('new_checkout', { defaultValue: false })
    // Render the new checkout only when enabled === true.
})

// When the component is disposed:
unsubscribe()

Use getFeatureFlag for a boolean or string variant, getFeatureFlagPayload for its JSON payload, or getFeatureFlagResult for { key, enabled, variant, payload }. Flag reads can report exposure; pass { send_event: false } to a flag/feature read when you do not want that exposure event. Use reloadFeatureFlags() to request new values. Feature flags are not a server-side authorization boundary.

Once an evaluation has loaded, every event carries it: $feature/<key> with each evaluated flag's value and $active_feature_flags with the keys whose value is truthy. These are derived from the loaded evaluation, never stored as super properties, so reset() and an identity change clear them until the next evaluation lands; a super-property change (register, register_once, unregister) keeps the loaded evaluation, served until its own expiry or the next evaluation without a new request, so a read or an event in the same turn still sees it; an event's own property of the same name wins, and the stamps fit inside the capture property budget. $feature_flag_called also names the evaluation it reports: $feature_flag_request_id, $feature_flag_evaluated_at (milliseconds since the Unix epoch) and $used_bootstrap_value, true while the value came from the cached evaluation rather than a response received on this page. A value is reported once per flag key and value for the anonymous visitor, across page loads (the ledger is kept in localStorage per site key, as the 1.409.5 bundle kept $flag_call_reported): identify() keeps it and reset() starts a new one. Flags are evaluated again on every configuration renewal (at most five minutes apart).

Other common operations

elu.register({ app_area: 'dashboard' }) // Properties on later events
elu.register_once({ first_campaign: 'docs' })
elu.unregister('app_area')
elu.setPersonProperties({ plan: 'business' })
elu.captureException(new Error('Upload failed'), { operation: 'upload' })

capture() returns no delivery receipt or Promise. Confirm integration with a non-sensitive test event in ELU; a script download or method return alone does not prove ingestion.

License

ELU contributions are available under the MIT License, including commercial use, modification, and redistribution.

Third-party portions retain their original Apache-2.0, MIT, or ISC terms and attribution in THIRD_PARTY_NOTICES.txt. ELU's MIT terms do not replace those upstream terms. Include LICENSE, NOTICE-ELU, and THIRD_PARTY_NOTICES.txt when redistributing the SDK. Published npm versions retain the terms included in their archives.

版本与发布记录

@elu-labs/analytics-browser 共发布 18 个版本,首次发布于 2026/09/19;过去 12 个月发布了 18 个版本。

版本发布日期距今
1.412.4latest2026/11/012 天前
1.412.32026/10/313 天前
1.412.22026/10/313 天前
1.412.12026/10/313 天前
1.412.02026/10/268 天前
1.411.82026/10/1123 天前

其他发布通道:next → 1.411.1。

关于 @elu-labs/analytics-browser 的常见问题

@elu-labs/analytics-browser 是什么?

ELU Analytics浏览器SDK。

如何安装 @elu-labs/analytics-browser?

在项目目录运行 npm install @elu-labs/analytics-browser,或使用 pnpm add / yarn add / bun add。

@elu-labs/analytics-browser 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@elu-labs/analytics-browser 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@elu-labs/analytics-browser 可以商用吗?

@elu-labs/analytics-browser 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@elu-labs/analytics-browser 还在维护吗?

最新版本 1.412.4 发布于 2026/11/01(2 天前);至今共发布 18 个版本,其中过去 12 个月发布了 18 个;首次发布于 2026/09/19。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。