Swan SDK
Changelog

Web

Release history for the Swan Web SDK.

[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 flowSwan.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 trackingSwan.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-propertiessetCountry, 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 enrichmentSwan.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 restorewindow.online triggers a queue flush, so reconnecting drains pending events without waiting for the next periodic interval.

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

  • Self-telemetry lifecycle eventsaddInitializedListener, 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 registrationPOST /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 PushSwan.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