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 afterSwan.login(), andaddSwanIdentifierChangedListenerfires withsource: "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.sdkVersionon every event and identity call carries the published package version instead of a fixed1.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-trackinginstead 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+serviceWorkerUrlas 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;serviceWorkerUrldefaults 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 alongsidestatus: "revoked", which the backend'ssetPushSubscriptionhandler treated as a subscribe (re-setting/subscriptionto 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 sendssubscription: null(the documented unsubscribe sentinel perspec/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 thePOST /v2/device/registerresponse (webPush.vapidPublicKeyfield) and uses it transparently.SwanConfig.vapidPublicKeyremains 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 (defaulttrue, opt-out). When disabled,Swan.subscribeToPush()returnsfalseimmediately 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 (defaultfalse).Swan.isLocationEnabled()now reflects this flag (previously reflected whetherupdateLocation()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.
pushsubscriptionchangeService 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 calledsubscribeToPush().keyValuePairsauto-parse.addNotificationOpenedListenerreceiveskeyValuePairsas 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
Swanobject plus aSwanEventshelper 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'sdatapayload. -
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 aPROFILE_ENRICHrow that routes to/v2/customer/enrich-profile(separate from the/v2/trackEventbatch).CDIDis resolved at flush time fromcurrentCDID(logged in) orgeneratedCDID(anonymous). -
Offline-resilient queue — events persist to
localStorage(keyswan-sdk.v1.queue) on every enqueue / dequeue, so pending events survive a page reload or tab restore. Queue capmaxQueueSize=5000with drop-oldest overflow. Survives corrupt JSON, quota-exceeded, and SSR-style environments without throwing. -
Network resilience — each queue row carries a
retryCountand lifecyclestatus(pending/sending/failed). Failed flushes increment retryCount; atMAX_RETRIES=3the row flips terminal. Retry delays follow exponential backoff (2s, 4s, 8s). -
Stale-sending recovery + 7-day cleanup — on
initialize(), rows stuck insendingfor >5 minutes (presumed crashed mid-flush) revert topending; failed rows older than 7 days are pruned. -
Auto-flush on network restore —
window.onlinetriggers a queue flush, so reconnecting drains pending events without waiting for the next periodic interval. -
Foreground / background lifecycle —
document.visibilitychange → hiddenforce-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/registeroninitialize()with credential caching inlocalStorage, automatic retry onwindow.online, and per-failure classification (network/timeout/http/shape/unknown) surfaced throughaddDeviceRegistrationFailedListener.
Notes
- The SDK is initialized synchronously and returns within 100 ms; device registration runs in the background. Calls to
Swan.track/Swan.identifybefore 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) handlespush(showNotification + delivery ACK),notificationclick(click ACK + deeplink routing), and bridges toaddNotificationOpenedListener/addDeepLinkOpenedListeneron the host page. Host supplies the per-tenant VAPID public key viaSwanConfig.vapidPublicKey. SeeBROWSER_SUPPORT.mdfor the per-browser matrix and the manual smoke checklist. - No runtime dependencies. The SDK ships zero
dependencies/peerDependencies/optionalDependencies— browserfetch+ DOM +localStorageonly.