SDKs

Web SDK

Fetch, verify, cache, and serve Ed25519-signed string bundles in the browser or Node.js with a framework-agnostic TypeScript library.

The AirStrings Web SDK fetches remotely managed localized strings, verifies every bundle's Ed25519 signature before exposing a single string, and caches verified bundles for offline use, in the browser or Node.js. Framework-agnostic.

Requirements: Node.js 18+ (uses global fetch) or any modern browser supporting ES2020.

Install

npm install @airstrings/web

Setup

Construct an AirStrings instance. Only organizationId, projectId, environmentId, publicKeys, and locale are required.

import { AirStrings } from '@airstrings/web'

const airstrings = new AirStrings({
  organizationId: 'org_a1b2c3d4e5f6',
  projectId: 'proj_a1b2c3d4e5f6',
  environmentId: 'env_a1b2c3d4e5f6',
  publicKeys: ['BASE64_ED25519_PUBLIC_KEY'],
  locale: 'en',
})
FieldTypeRequiredDescription
organizationIdstringyesYour AirStrings organization ID.
projectIdstringyesProject ID.
environmentIdstringyesEnvironment ID (e.g. production, staging).
publicKeysreadonly string[]yesOne or more base64-encoded Ed25519 public keys. Multiple keys supported for rotation.
localestringyesInitial BCP-47 locale (e.g. "en", "fr-CA").
apiBaseURLstringnoAPI base URL. Defaults to https://api.airstrings.com.
logger(level, msg, ctx) => voidnoOptional logger. Default: no-op.
storeBundleStorenoInject a custom cache backend.
seedreadonly unknown[]noBundled fallback contents supplied at build time (parsed objects or raw JSON strings).
seedDirstring | falsenoNode only. Seed directory override, or false to disable seeding. Default: probe <cwd>/airstrings/bundles/.

Find your IDs and public key in the dashboard under Project Settings > SDK Configuration, or in the setup snippet shown after you publish a bundle.

Usage

Browser

import { AirStrings } from '@airstrings/web'

const airstrings = new AirStrings({
  organizationId: 'org_a1b2c3d4e5f6',
  projectId: 'proj_a1b2c3d4e5f6',
  environmentId: 'env_a1b2c3d4e5f6',
  publicKeys: ['BASE64_ED25519_PUBLIC_KEY'],
  locale: navigator.language,
})

airstrings.on('strings:updated', ({ locale, revision }) => {
  document.getElementById('greeting')!.textContent = airstrings.t('greeting')
})

In the browser, bundles are cached in IndexedDB and the SDK auto-refreshes them when the document becomes visible.

Node.js (server-side)

whenReady() resolves once the first load + bootstrap + refresh cycle completes. Await it before reading strings on the server:

import { AirStrings } from '@airstrings/web'

const airstrings = new AirStrings({
  organizationId: 'org_a1b2c3d4e5f6',
  projectId: 'proj_a1b2c3d4e5f6',
  environmentId: 'env_a1b2c3d4e5f6',
  publicKeys: ['BASE64_ED25519_PUBLIC_KEY'],
  locale: 'en',
})

await airstrings.whenReady()

console.log(airstrings.t('greeting'))

In Node, AirStrings falls back to an in-memory cache (no IndexedDB). The cache lives for the process lifetime (gone on restart).

Locale

Switching locale loads from cache immediately if available, then refreshes:

await airstrings.setLocale('fr-CA')

Methods & properties

  • t(key): string: raw localized string for key, or key as fallback.
  • format(key, args?): string: formats an ICU MessageFormat string.
  • refresh(): Promise<void>: forces a bundle refresh from the CDN. Honors ETag/304.
  • setLocale(bcp47): Promise<void>: switches locale.
  • whenReady(): Promise<void>: resolves once the initial load cycle completes.
  • destroy(): void: removes browser visibility listeners. Call on unmount in long-lived UIs.
  • on(event, handler): () => void: subscribe to events; returns an unsubscribe function.
  • strings, locale, revision, isReady: read-only snapshots of current state.

Events

type AirStringsEvents = {
  'strings:updated': { locale: string; revision: number }
  'strings:error':   { error: AirStringsError }
}

Framework integration

The SDK exposes data and events, not UI bindings.

React, via useSyncExternalStore:

const useStrings = (a: AirStrings) =>
  useSyncExternalStore(
    cb => a.on('strings:updated', cb),
    () => a.strings,
    () => a.strings,
  )

Vue:

const strings = shallowRef(airstrings.strings)
airstrings.on('strings:updated', () => { strings.value = airstrings.strings })

String variants

String variants are A/B experiments: each variant is selected deterministically from a stable assignment id, entirely client-side, with no server round-trip. Set an assignment id once and forward exposures to your own analytics:

// A stable per-user id so each user always sees the same variant.
airstrings.setAssignmentId(currentUser.id)

// Forward every exposure to your own analytics.
airstrings.on('experiment:exposure', ({ key, experimentId, variant, locale, assignmentId }) => {
  analytics.track('experiment_exposure', { key, experimentId, variant, locale, assignmentId })
})

Reads are unchanged: airstrings.t('greeting') returns the assigned variant's value.

Experiment content is Ed25519-verified via a separate experiments_signature and soft-fails to base values if verification fails: variants are never served unverified. Passing null to setAssignmentId() clears the assignment and returns to base values. Available since Web SDK 1.1.0.

See String Variants for how selection, bucketing, and exposure work, and managing experiments from the CLI to create one.

ICU formatting

Every string has a format: "text" (plain) or "icu" (ICU MessageFormat). Use format(key, args) to format ICU strings via intl-messageformat:

airstrings.format('items.count', { count: 3 })

text strings are returned as-is (arguments ignored). Pattern syntax: ICU by example.

Caching & offline

  1. On construction, the SDK loads the cached bundle (if any), then fetches the latest bundle from the CDN.
  2. Every bundle is Ed25519-signed. Verification is mandatory: an invalid signature is rejected and never exposed.
  3. Cached bundles are re-verified on every load (defense in depth). Browser cache uses IndexedDB; Node/SSR uses memory.
  4. Anti-downgrade protection: a newer revision is never replaced by an older one for the same locale.
  5. The SDK refreshes when the document becomes visible, using ETag / If-None-Match to avoid re-downloading unchanged bundles.
  6. Signature failures never throw from the public API. The SDK keeps the previous good bundle and emits strings:error. With no cache and no network, key names are returned as a fallback.

Multiple publicKeys support rotation; see Key rotation.

To ship published, signed bundles inside your build for offline-safe cold starts, see Offline & fallback.

Troubleshooting

SymptomLikely causeFix
npm install can't find the packageWrong package nameThe package is @airstrings/web.
t('key') returns the key nameNo bundle has loaded yet, or no bundle is published for this localeCall await airstrings.whenReady() (or refresh()); confirm organizationId, projectId, and environmentId are correct.
Strings never update after publishingWrong environmentId, or the bundle was published to a different environmentVerify the environment ID matches the one you published from.
strings:error emitted, strings don't changepublicKeys does not contain the key that signed the bundleCopy the environment's public key from Project Settings > SDK Configuration. After rotation, include both old and new keys.

On this page