Swan SDK
Changelog

Web

Release history for the Swan Web SDK.

[web/1.1.3] — 2026-09-22

Distribution: npm — @loyalytics/swan-web-sdk@1.1.3

Fixed

  • Profile updates now always apply to the customer who was active when the update was made. enrichProfile() is queued and sent in the background; previously, if a login or logout happened — or the send was retried — before the queue flushed, the update could be written against the wrong customer. Each queued update now carries the customer it was made for and is always applied to that customer.

[web/1.1.2] — 2026-09-11

Distribution: npm — @loyalytics/swan-web-sdk@1.1.2

Fixed

  • Swan.identify() now switches to the customer's existing profile. When the identifier belongs to a customer already known to Swan (for example a returning shopper signing in on a new browser), the SDK now adopts that customer's identity for all subsequent events, the same as after Swan.login(), and addSwanIdentifierChangedListener fires with source: "identify". Previously the switch happened on the server but events kept being attributed to the anonymous visitor. First-time sign-ins are unaffected.
  • Events now report the installed SDK version. common.sdkVersion on every event and identity call carries the published package version instead of a fixed 1.0.0.

[web/1.1.1] — 2026-06-25

Distribution: npm — @loyalytics/swan-web-sdk@1.1.1

Changed

  • Device registration resolves the device identifier more robustly from the registration response. The SDK now reliably obtains the device identifier when registering, keeping it consistently available for events and attribution.

[web/1.1.0] — 2026-06-22

Distribution: npm — @loyalytics/swan-web-sdk@1.1.0

Changed

  • Silent push notifications no longer display a notification. A push delivered with the silent flag is now received and acknowledged in the background without showing a notification to the user, matching the iOS and Android SDKs. Non-silent pushes are unchanged.

Fixed

  • Logging out now starts a fresh anonymous identity. When a user who first used the app anonymously and later signed in subsequently logs out, the SDK begins a new anonymous identity instead of reusing the one that had been merged into their account on sign-in. This prevents activity after logout from being attributed to the signed-out user.

[web/1.0.3] — 2026-05-26

Distribution: npm — @loyalytics/swan-web-sdk@1.0.3

Changed

  • Service Worker delivery + click ACKs now POST to /web-push-tracking instead of /mobile-push-tracking. Web push events are routed to a dedicated webhook route on the Swan backend, giving per-channel telemetry (web vs mobile push delivery / open rates) without conflating them on shared dashboards. Functionally equivalent for end users — the SW still POSTs the same payload shape; only the URL changes.

Documentation

  • Stale init snippet in the Push Notifications guide (which still showed vapidPublicKey + serviceWorkerUrl as required init args) replaced with the current minimal init shape: Swan.initialize('your-app-id', { production: true }). The SDK reads VAPID from the device-register response; serviceWorkerUrl defaults to /swan-service-worker.js.

[web/1.0.2] — 2026-05-26

Distribution: npm — @loyalytics/swan-web-sdk@1.0.2

Fixed

  • Swan.unsubscribePush() now actually clears the subscription on the Swan backend. Pre-1.0.2 the SDK POSTed the full subscription object alongside status: "revoked", which the backend's setPushSubscription handler treated as a subscribe (re-setting /subscription to its current value). The customer's push state on Swan stayed "subscribed" even though the browser had revoked the subscription locally — meaning a tenant who'd opted out would continue to be a candidate for push sends until manually wiped. The SDK now sends subscription: null (the documented unsubscribe sentinel per spec/wire/push-subscription.yaml), so the backend's unsubscribe branch fires correctly.

[web/1.0.1] — 2026-05-26

Distribution: npm — @loyalytics/swan-web-sdk@1.0.1 Browser floor: Chrome 90+ · Firefox 90+ · Safari 14+ · Edge 90+

Added

  • Backend-served VAPID public key. Host apps no longer need to copy/paste a VAPID public key into Swan.initialize(...). The SDK reads it from the POST /v2/device/register response (webPush.vapidPublicKey field) and uses it transparently. SwanConfig.vapidPublicKey remains as an optional override for test rigs or environments where the backend hasn't yet served the field.
  • Swan.resyncSubscription() — re-POSTs the current browser push subscription to the Swan backend without re-prompting for permission or re-subscribing. Use when the backend's stored copy of the subscription is suspected stale.
  • SwanConfig.pushNotifications.enabled — master gate for the Web Push subsystem (default true, opt-out). When disabled, Swan.subscribeToPush() returns false immediately without registering the Service Worker or prompting. Matches the iOS and Android SDK shape.
  • SwanConfig.location.enabled — opt-in flag for the location-tagging surface (default false). Swan.isLocationEnabled() now reflects this flag (previously reflected whether updateLocation() had been called). Matches the iOS and Android SDK shape.
  • VAPID rotation handling. When the tenant's VAPID public key changes between sessions, the SDK detects the rotation, force-unsubscribes the now-dead existing browser subscription, and re-subscribes with the new key — automatically.
  • pushsubscriptionchange Service Worker handler. When the browser invalidates a subscription on its own (gateway-side rotation, browser internals), the SDK re-subscribes from the SW + re-POSTs to the backend automatically. Previously the SW was a no-op and the user went silent until the host page next called subscribeToPush().
  • keyValuePairs auto-parse. addNotificationOpenedListener receives keyValuePairs as an object regardless of whether the wire payload encoded it as JSON-stringified text or a native object.

Documentation

  • Subscription idempotency contract documented in the Web getting-started guide — including the recommended isPushSubscribed() gate pattern.
  • Per-browser web-push coverage rendered as a proper table.
  • Roadmap-era "Planned API" callouts removed from the per-feature guides; each Web tab now shows working code.

Notes

  • 1.0.0 was published less than a day before 1.0.1; we treat this as a same-cycle correction. Customers on 1.0.0 should upgrade.

[web/1.0.0] — 2026-05-19

Distribution: npm — @loyalytics/swan-web-sdk@1.0.0 Browser floor: Chrome 90+ · Firefox 90+ · Safari 14+ · Edge 90+ Bundle: ~52 kB raw / ~12 kB gzipped (CJS) · zero runtime dependencies

Added

  • First customer release of the Swan Web SDK. Identity, event tracking, and offline-resilient delivery for browser-based host apps. The public surface is a namespace-exported Swan object plus a SwanEvents helper namespace, importable as either named or default.

  • Identity flow — Swan.identify(id, attrs?) (fire-and-forget), Swan.login(id, attrs?) (async, returns { cdid, profileSwitched }), Swan.logout() (clears local state even on network failure), Swan.addSwanIdentifierChangedListener(cb). Pre-CDID-swap queue flush is a hard invariant: events queued before login are POSTed under the anonymous CDID.

  • Event tracking — Swan.track(name, attrs?), Swan.screen(name, attrs?), Swan.flush(), Swan.getQueueSize(). Every event is auto-enriched with platform, deviceId, sessionId, country, currency, businessUnit, currentScreenName. Wire format is byte-stable with the Android, iOS, and React Native SDKs (LZW64-compressed).

  • Super-properties — setCountry, setCurrency, setBusinessUnit, setCurrentScreenName. Stamped on every subsequent event's data payload.

  • Semantic e-commerce helpers (SwanEvents) — 33 typed methods covering discovery, cart, wishlist, checkout, order, account, marketing, and lifecycle. RN-typo wire names preserved verbatim (productAddedToaddTocart, orderExperianceRating) for cross-platform wire stability.

  • Profile enrichment — Swan.enrichProfile(attrs) queues a PROFILE_ENRICH row that routes to /v2/customer/enrich-profile (separate from the /v2/trackEvent batch). CDID is resolved at flush time from currentCDID (logged in) or generatedCDID (anonymous).

  • Offline-resilient queue — events persist to localStorage (key swan-sdk.v1.queue) on every enqueue / dequeue, so pending events survive a page reload or tab restore. Queue cap maxQueueSize=5000 with drop-oldest overflow. Survives corrupt JSON, quota-exceeded, and SSR-style environments without throwing.

  • Network resilience — each queue row carries a retryCount and lifecycle status (pending / sending / failed). Failed flushes increment retryCount; at MAX_RETRIES=3 the row flips terminal. Retry delays follow exponential backoff (2s, 4s, 8s).

  • Stale-sending recovery + 7-day cleanup — on initialize(), rows stuck in sending for >5 minutes (presumed crashed mid-flush) revert to pending; failed rows older than 7 days are pruned.

  • Auto-flush on network restore — window.online triggers a queue flush, so reconnecting drains pending events without waiting for the next periodic interval.

  • Foreground / background lifecycle — document.visibilitychange → hidden force-flushes the queue (web equivalent of mobile background). SwanEvents.appLaunched() is the host-callable foreground hook.

  • Self-telemetry lifecycle events — addInitializedListener, addDeviceRegistrationFailedListener. The SDK emits these to the host app but does NOT upload self-telemetry to the backend.

  • Session ID — UUID v4, attached to every event's data.sessionId, rotates after 20 minutes of inactivity. Surface: Swan.getCurrentSessionId().

  • Device registration — POST /v2/device/register on initialize() with credential caching in localStorage, automatic retry on window.online, and per-failure classification (network / timeout / http / shape / unknown) surfaced through addDeviceRegistrationFailedListener.

Notes

  • The SDK is initialized synchronously and returns within 100 ms; device registration runs in the background. Calls to Swan.track / Swan.identify before registration completes are queued, not dropped.
  • Web Push — Swan.subscribeToPush() / unsubscribePush() / isPushSubscribed() register a Service Worker and subscribe via the browser's Push API. The bundled SW (@loyalytics/swan-web-sdk/sw) handles push (showNotification + delivery ACK), notificationclick (click ACK + deeplink routing), and bridges to addNotificationOpenedListener / addDeepLinkOpenedListener on the host page. Host supplies the per-tenant VAPID public key via SwanConfig.vapidPublicKey. See BROWSER_SUPPORT.md for the per-browser matrix and the manual smoke checklist.
  • No runtime dependencies. The SDK ships zero dependencies / peerDependencies / optionalDependencies — browser fetch + DOM + localStorage only.

On this page