@atlaskit/flag-client

中文文档
1.0.3发布于 2026/11/02Apache-2.0近 30 天下载 2.9万

Browser-safe JavaScript client for reading server-evaluated feature flags from the Feature Flag Platform.

原始使用文档
文档目录14 节
  1. Install and import
  2. Payload shape
  3. Initialize
  4. Users
  5. Replace the payload and user
  6. Read values and details
  7. Safe defaults and code defaults
  8. Object and array values
  9. Exposure telemetry
  10. What an exposure carries
  11. Suppress and log manually
  12. Deduplication, delivery, and persistence
  13. Same-origin and cross-origin delivery
  14. Development

@atlaskit/flag-client

Browser-safe JavaScript client for reading server-evaluated feature flags from a BulkEvaluationPayload.

The client does not fetch, poll, subscribe, or evaluate flag rules locally. A caller, normally the @atlaskit/feature-gate-js-client wrapper, obtains a payload (from the Feature Flag Service /evalFlags response or a server-rendered bootstrap) and hands it to the client through a synchronous provider.

This package is the FFP JavaScript client. It is maintained here, in the Atlassian Frontend monorepo. Its behaviour is specified by the client-js-* capabilities under openspec/specs/ in the feature-flag-platform repository.

Install and import

yarn add @atlaskit/flag-client
import { FlagClient } from '@atlaskit/flag-client/client';
import type { BulkEvaluationPayload, FlagUser } from '@atlaskit/flag-client/types';

Payload shape

BulkEvaluationPayload, which providers return, is the union of the payload wire formats this client supports, discriminated by vs. It currently has one member, BulkEvaluationPayloadV1. A future schema version may have an entirely different shape and will always carry its vs.

A BulkEvaluationPayloadV1 is an object with:

  • vs (optional): the payload schema version. The only supported version is "1", and an absent vs is treated as "1". A payload without vs and an otherwise identical payload with vs: "1" produce identical flag values and details metadata. Any other value, including null or the number 1, fails initialization or replacement with invalid_schema_version before the rest of the payload is validated.
  • flags (required): a BulkEvaluationResult, which maps each flag key to a FlagResult. Its shape matches the flags member of the Feature Flag Service ffp payload. It may be empty.
  • requestContext (required): the identifiers and attributes the payload was evaluated for. It must be well formed, but the client does not use it for reads or details. A payload without it fails with invalid_payload_shape. Its attributes is optional and an absent attributes is retained as {}; a null attributes is rejected with invalid_payload_shape.
  • clientVersion (optional): the version of the flag data, { token, updatedAt? }, matching the Feature Flag Service FfpBootstrapPayload.ClientVersion. When present, token must be a non-empty string and updatedAt a string. The client keeps it with the payload, swaps it on replace, and does not use it for reads or details.

A null requestContext or clientVersion is invalid. Other payload members, such as status or targetApp, are ignored, so an ffp object can be passed as-is. Unknown members inside flag results and their metadata are also ignored.

This input intentionally differs from the server SDK BulkEvaluationPayload described in sdks/README.md: it has no payload-level status.

Each flag result's metadata may also carry holdoutAllocation, segments, targetApps, flagVersion, and errorMessage. When present they must be an object or null, an array of objects, an array of strings, a string, and a string respectively. A holdoutAllocations list from an older producer is ignored.

Initialize

Construct one client per target app and environment, then initialize it. Initialization is synchronous, one-time per client instance, and never throws.

const client = new FlagClient({
    targetApp: 'jira-frontend',
    environmentType: 'prod', // 'dev' | 'local' | 'staging' | 'prod'
    serviceVersion: appVersion, // optional
});
const user: FlagUser = {
    identifiers: { atlassianAccountId: accountId },
    attributes: { plan: 'premium' }, // optional
};

const init = client.initialize(() => payload, user);

if (!init.initialized) {
    // Reads return safe defaults. The SDK logs the failure reason and a troubleshooting tip.
}
  • The constructor options are fixed for the life of the instance. targetApp and environmentType are required and serviceVersion is optional. The client copies them without validating them at runtime, never throws during construction, and does not use them for reads. They identify the client in exposure telemetry.
  • The provider is invoked only during initialize or replace. The client keeps an immutable snapshot of the payload it returns.
  • Failure reasons are already_initialized, provider_threw, provider_returned_no_payload, invalid_schema_version, invalid_payload_shape, and payload_capture_failed. A failed initialization, other than already_initialized, can be retried.

Users

The user is normalized into a frozen copy when it is stored:

  • Identifier values that are not strings are dropped.
  • Attribute values that are not strings, booleans, or finite numbers are dropped.
  • A user, identifiers, or attributes member that is not an object is treated as empty. Omitting attributes is equivalent to {}.

Normalization never fails initialization or replacement, and an empty identifier map is valid. When anything is dropped, the SDK logs one console.warn(...) per call that names the dropped keys but never their values.

The client keeps the normalized user from every initialization attempt other than already_initialized, including one that fails, so exposure telemetry can attribute reads made before initialization succeeds.

Replace the payload and user

Use replace when the user changes. It swaps the payload and user together; the constructor options do not change.

const outcome = client.replace(() => nextPayload, nextUser);

if (!outcome.replaced) {
    // The previous payload and user are still served.
}

replace fails with not_initialized, without invoking the provider, until initialize has succeeded. Its other failure reasons match initialization's provider and payload reasons. Any failed replacement keeps the previous payload and user, and the SDK logs the reason and a tip.

Read values and details

Typed value reads return the initialized flag value when the payload can serve the requested flag and type. Otherwise, they return the caller-provided safe default.

const enabled = client.getBooleanValue('admin-toolbar.enabled', false);
const label = client.getStringValue('button.label', 'Continue');
const maxResults = client.getNumberValue('search.max-results', 25);
const config = client.getObjectValue('ui.config', { density: 'comfortable' });

Details reads return the same selected value plus metadata for the requested flag.

const details = client.getBooleanDetails('admin-toolbar.enabled', false);

console.log(details.value);
console.log(details.metadata.matchReason);
console.log(details.metadata.identifierValue); // from the current user
console.log(details.metadata.holdoutAllocation);

identifierValue is looked up in the current user's identifiers using the result's identifierType, never in the payload's requestContext. holdoutAllocation is the result's allocation, or null when absent, and segments is the result's list, or an empty array when absent. targetApps, flagVersion, and errorMessage are retained internally and are not part of details metadata.

Safe defaults and code defaults

The client returns the safe default when it is uninitialized, the flag is absent, the requested read type does not match the flag result, the runtime value shape is unusable, or the result has useCodeDefault = true.

For useCodeDefault = true, details reads still preserve server metadata from the payload. Other fallback cases use client-generated metadata: not_initialized, flag_not_found, or type_mismatch.

Object and array values

Object and array values selected from initialized payload state come from the client's immutable payload snapshot. Mutation attempts against those returned initialized values do not change later reads or details metadata. Clone a returned object or array before mutating it in application code.

Safe default values are returned as provided by the caller.

Exposure telemetry

In a browser, every typed value or details read records one exposure: an OpenTelemetry log record in the shared exposure schema (sdks/design/otel-exposure-event-schema.md in the feature-flag-platform repository). The client batches exposures and posts them as OTLP/JSON to Flag Telemetry.

Exposure logging is on by default only when a global window exists at construction. In Node and SSR it is off unless enabled: true is passed. DOM test environments such as jsdom and happy-dom define window, so pass enabled: false in tests that must not post exposures. Configure it with the exposureLogging constructor option:

const client = new FlagClient({
    targetApp: 'jira-frontend',
    environmentType: 'prod',
    exposureLogging: {
        enabled: true, // default: whether a global `window` exists
        endpoint: 'https://xp.atlassian.com/ft/v1/logs', // default; or a same-origin path
        batchSize: 100, // default: exposures that trigger an immediate delivery
        flushIntervalMs: 10_000, // default: how long a queued exposure waits
    },
});

An option that fails its type, such as batchSize: 0, endpoint: 'relative/path', or an endpoint URL with a username or password, is ignored: its default applies and the client writes one console.warn naming the option, never its value. Construction never throws. A disabled client does not check endpoint, batchSize, or flushIntervalMs at all, so passing one configuration to both browser and SSR clients is quiet.

What an exposure carries

  • service.name and feature_flag.set.id come from targetApp. deployment.environment_type and deployment.environment.name come from environmentType, and service.version from serviceVersion.
  • The Instrumentation Scope name and atl.feature_flag.sdk.type are @atlaskit/flag-client, and the scope version is this package's version.
  • Identifiers and attributes come from the held payload's requestContext. While the client holds no payload, they come from the current user, which after a failed initialize is the user passed to it. Details identifierValue always comes from the current user.
  • atl.feature_flag.result.source is bootstrap when the read was made while the client held an initialized payload, including for flag_not_found and type_mismatch, and fallback for a read made before a successful initialize, even when logExposure replays it later.
  • Error reasons (not_initialized, flag_not_found, type_mismatch, general_error, and invalid_context) carry no variant or rule, and their error message starts with FFP evaluation failed: <reason>. general_error and invalid_context use the payload's errorMessage instead when it is nonempty. A not_initialized message ends with the cause, such as (no initialize attempt completed) or the most recent failure reason.
  • type_mismatch keeps the payload flag's version and target apps.
  • A holdout allocation becomes the atl.feature_flag.holdout_allocation.* attributes only when its holdoutKey, identifierType, and allocationReason are strings, with holdout_rule_id only for a string holdoutRuleId. Otherwise all four are omitted.

Suppress and log manually

Pass { disableExposureLogging: true } to any read to skip its automatic exposure. Log one later, for example when the user actually sees the feature, with logExposure:

const details = client.getBooleanDetails('new-nav.enabled', false, {
    disableExposureLogging: true,
});
// ...later, when the navigation is rendered:
client.logExposure(details);

For details this client returned, including shallow copies, logExposure records the exposure the read would have produced, marked manual, even after a later initialize or replace. Details the client did not return, such as ones rebuilt from JSON, use the client state current at the call. It never throws and does nothing when exposure logging is disabled.

Deduplication, delivery, and persistence

  • An exposure with the same flag key, variation, rule, match reason, and identifiers as one queued in the last ten minutes is discarded. Manual and automatic exposures share these windows. A successful replace ends them all.
  • One request is in flight at a time, sent with credentials: 'omit'. Network errors, requests not settled within 30 seconds, 429, and 5xx statuses are retried after 1 second and then 2 seconds. Any other non-2xx status drops the batch.
  • Each client has one circuit breaker. After 3 consecutive retryable failures it opens: exposures stay queued, and one batch is sent about every 60 seconds to check whether the endpoint has recovered. The first response that is not retryable closes it, and the queue is flushed. Retryable failures never drop exposures, but from the first retryable failure until a delivery gets any other outcome, the client keeps only the 1,000 most recent exposures in memory.
  • When the page is hidden, undelivered exposures are saved to localStorage under keys starting @atlaskit/flag-client:exposures:, and so is each exposure queued while it stays hidden. The next client constructed on the same origin delivers them to its own endpoint, whichever endpoint the client that saved them was configured with. It sends them as soon as it takes them, rather than after flushIntervalMs, unless its circuit breaker holds them back. Storage errors never throw.
  • Every client on an origin shares a localStorage budget of 131,072 UTF-16 code units (256 KiB), keys included, and saves only its most recent exposures that fit. The others stay in memory. There is no separate limit on how many exposures are saved; the budget alone decides.
  • After a successful delivery, the client records the time under @atlaskit/flag-client:last-delivery, at most once an hour. While deliveries are failing on an origin with no recorded success in the last 7 days, such as one where a content blocker rejects every request, the budget is 4,096 code units (8 KiB) instead.

Same-origin and cross-origin delivery

endpoint also accepts a path that begins with a single /. The client resolves it once at construction against window.location.origin, so a product with a same-origin route to Flag Telemetry avoids CORS entirely, as GASv3 does:

// Delivers to https://<product host>/gateway/api/ft/v1/logs
exposureLogging: { endpoint: '/gateway/api/ft/v1/logs' }

The path above is illustrative; use the route your product actually serves. A path is ignored with a warning when the client is enabled without a window, the origin is opaque, the path starts with // or /\, or it resolves to another origin.

Without a path, exposures are posted cross-origin to https://xp.atlassian.com/ft/v1/logs as application/json, which requires a CORS preflight. Delivery from a product page then depends on that route answering the preflight.

Development

Run from the platform/ directory:

afm test unit packages/measurement/flag-client
afm ts check --project ./packages/measurement/flag-client/tsconfig.app.json
afm lint eslint packages/measurement/flag-client

The shared test vectors in src/__tests__/__fixtures__/ are vendored from the feature-flag-platform repository: bulk-evaluation-fixtures.json from sdks/evaluation-test-vectors, and exposure-test-vectors/ (JSON files only) from sdks/exposure-test-vectors. Keep them in sync when the shared fixtures change.

版本与发布记录

@atlaskit/flag-client 共发布 5 个版本,首次发布于 2026/10/26;过去 12 个月发布了 5 个版本。

版本发布日期距今
1.0.3latest2026/11/025 天前
1.0.22026/11/025 天前
1.0.12026/10/2810 天前
1.0.02026/10/2612 天前
0.0.0-stage2026/10/2612 天前

关于 @atlaskit/flag-client 的常见问题

@atlaskit/flag-client 是什么?

Browser-safe JavaScript client for reading server-evaluated feature flags from the Feature Flag Platform.

如何安装 @atlaskit/flag-client?

在项目目录运行 npm install @atlaskit/flag-client,或使用 pnpm add / yarn add / bun add。

@atlaskit/flag-client 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/atlaskit__flag-client 是否存在。

@atlaskit/flag-client 可以商用吗?

@atlaskit/flag-client 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@atlaskit/flag-client 的下载量有多少?

根据 npm 官方下载统计,@atlaskit/flag-client 在 2026/10/07 – 2026/11/05 这 30 天内被下载 29,145 次,日均约 972 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@atlaskit/flag-client 还在维护吗?

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