Migrating from SDK v1 to v2
This document covers any code changes you'll need to make when migrating for the
v1
Airship Web SDK to v2
, as well as covering new features you may wish to
implement while making updates.
The changes are minimal, and for most integrations the upgrade will not involve more than updating some existing method calls to their new names or locations. In some cases calls have been simplified to reduce the amount of asynchronous waiting you must do before using an interface.
Removed Support
Some features were removed from the SDK; if you still rely on these features,
then you will wish to stay on the v1
SDK:
- Support for HTTP setups; this is also referred to as "secure bridge". The
v2
SDK requires HTTPS on all pages.- The registration page plugin has been removed, as it was only needed for these setups
- Multi-domain setups are not supported; this is where a single registration was shared across multiple domains or subdomains.
- APNs Safari is no longer supported, as Apple started supporting VAPID push in Safari 16 on macOS 13 and above, which was released October 2022.
Known Issues
- AMP support is untested
New Features
The following new features were added:
Built for ES6/ES2015
The Airship SDK has dropped ES5 support, resulting in a significant reduction in the SDK's initial load and parse times.
Registering Browsers without Push Notification Opt-in
It's now possible to register a browser without requesting notification opt-in,
so you can track events, set tags and attributes, and display In-App Experiences
to your web audience. A new method sdk.create()
has been added as a sibling to
the existing sdk.register()
, and will create a channel for the browser but not
prompt for browser notification access. At a later date you may still call
sdk.register()
to request notification access.
const {channelId} = await sdk.create()
console.log('registered a channel', channelId)
In-App Experiences
In-App Experiences are now supported on web devices, so you can display scenes, surveys, and stories to your users. This feature is enabled by default in the SDK and does not require any further integration for support. However you may wish to track additional events to support triggering of these experiences.
// a custom event
const event = new sdk.CustomEvent('ate_food', 3.5, {meal: 'pancakes'})
await event.track()
// screen view events
await sdk.analytics.trackScreen('home')
Changes Required
The following changes are required when migrating to the v2
SDK; this includes
methods which have changed or moved, as well as methods that have been
deprecated and removed.
Legacy Initialization Support Removed
Very early implementers of the Airship SDK had a different initialization snippet from later versions. Support for this early snippet has been dropped. The deprecated snippet will appear similar to the following:
!function(n,t,c,e,u){function r(n){try{f=n(u)}catch(n){return h=n,void i(p,n)}i(s,f)}function i(n,t){for(var c=0;c<n.length;c++)d(n[c],t);
}function o(n,t){return n&&(f?d(n,f):s.push(n)),t&&(h?d(t,h):p.push(t)),l}function a(n){return o(!1,n)}function d(t,c){
n.setTimeout(function(){t(c)},0)}var f,h,s=[],p=[],l={then:o,catch:a,_setup:r};n[e]=l;var v=t.createElement("script");
v.src=c,v.async=!0,v.id="_uasdk",v.rel=e,t.head.appendChild(v)}(window,document,'https://aswpsdkus.com/notify/v1/ua-sdk.min.js',
'UA', options)
Most projects will have the newer snippet, however if you have the earlier version you must update to the latest one.
The easiest way to determine which version you have:
- If the string
_async_setup
appears in your snippet, you have the updated initialization and do not need to take any action. - If the string
_setup
appears (not prefixed by_async
) you have an old version of the initialization and must update.
If you need to update your snippet, the best method is to log into your Airship dashboard and re-download the SDK bundle from your application.
Channel Tags, Attributes, and ID
The previous Channel tags and attributes interfaces have been deprecated, and
new interfaces which mirror those available on the Contact interface. Any calls
to sdk.channel.tags
or sdk.channel.attributes
must be updated to these new
interfaces:
// tag editor
await sdk.channel
.editTags()
.add('device', 'cool-tag')
.remove('device', 'uncool-tag')
.apply()
// attribute editor
await sdk.channel
.editAttributes()
.set('first_name', 'Guy')
.remove('birthday')
.apply()
All operations are queued in the editor instance until apply()
is called. If
not called, any changes are discarded.
The channel ID is no longer synchronously available on the channel. See the migration guide later in this document for details.
Removal of "has tags" methods
The tags.has()
methods were removed, as this method does not provide an
accurate view of the actual channel or contact's tags. If your application
requires tracking any segmentation data, we recommend storing that data within
your application.
Chainable Tag, Attribute, and Subscription List Editors
As you saw in the section above, the tag and attributes interfaces are now chainable. This is also true of subscription lists:
await sdk.contact.subscriptions
.edit()
.subscribe('news')
.unsubscribe('product_updates')
.apply()
Locale
The locale override interface has been removed, and replaced with a new
interface. Any calls to sdk.localeOverride
must be updated:
await sdk.locale.set({language: 'fr', country: 'FR'})
Simplified Asynchronous Interfaces
The following interfaces previously required awaiting the promise they returned before they could be used, but are now synchronously available, simplifying their use:
sdk.channel.subscriptions
sdk.contact
sdk.contact.editTags
sdk.contact.editAttributes
sdk.contact.subscriptions
Any previous code where you were await
-ing those methods or calling .then
on
them should be updated.
// listing channel subscriptions
const lists = sdk.channel.subscriptions.list()
// updating contact tags
await sdk.contact
.editTags()
.set('crm', 'pro-user')
.remove('interests', 'spelling')
.apply()
Updates to SDK Support Methods
SDK support methods have been updated to better represent the available features in the browser, and to help you decide what actions you can take. See the "Support Methods" section of the upgrade guide below for details on the changes made.
Feature Flags and Preference Center Interfaces
The Feature Flags and Preference Center interfaces have moved; where they were
previously at the top-level of the SDK, they are now under a components
property:
// feature flags
const {eligible} = await sdk.components.featureFlags.get('new_feature')
// preference centers
const definition = await sdk.components.preferenceCenters.get('web_preferences')
Enabling and Disabling Data Collection
Previously, re-enabling data collection via sdk.setDataCollectionEnabled(true)
would attempt to opt the channel back in to push notifications if the browser's
notification permission was granted. However in some cases this wouldn't
actually opt the browser in, but would cause the sdk.channel.optedIn()
method
to temporarily resolve to true
, even though the browser was not actually opted
in.
This method no longer attempts to opt the channel back in, as we do not know if
the channel should be opted in. Instead, if you wish to opt the channel back
in after re-enabling data collection, call the register()
method after
checking permission is granted:
await sdk.setDataCollectionEnabled(true)
const permission = await sdk.getNotificationPermission()
if (permission === 'granted') {
await sdk.register()
}
Minor Changes
sdk.disableAnalytics
is no longer available, use the asynchronous methodssdk.analytics.isEnabled
andsdk.analytics.setEnabled
sdk.isGestureRequired
has been removed as all browsers now recommend only performing a notification opt-in as the result of a gesturesdk.permission
has been removed, use the asynchronous methodsdk.getNotificationPermission()
to check notification permissions
Upgrade Guide
Important: the steps in this upgrade guide must be performed in a single release; there is no "partial" upgrade supported.
Update your SDK initialization
Note: Before starting, read the above "Legacy Initialization Support Removed" section of this document, as it contains important information that will affect a small number of early adopters of the Airship Web SDK. This section assumes you've already updated to the latest snippet.
Your initialization snippet (typically in your site's HTML template, and in the push worker) will contain a reference to the location of the Airship SDK's source URL; it will look something like:
https://aswpsdkus.com/notify/v1/ua-sdk.min.js
To update to the new SDK version, you should visit the Settings page in your Airship Dashboard for the Web platform. There you can upgrade from v1 to v2, and download a new bundle which contains updated initialization snippets for v2.
Important: do not forget to update the push worker, which will be included in the bundle you download from the Airship Dashboard!
Update to the New API
There were a small set of changes to the SDK's public interface which must be updated in your code; we'll cover each of the items individually, giving an indication of what you should be searching for and examples of how you'd update the calls.
Channel ID
If you were relying on the value channel.id
, you must update how you retrieve
that value as it is no longer synchronously available. Calls to the old
interface will look like:
channel.id
This is now a method channel.id()
which will resolve to either the Channel ID,
or null
if one is not available. In cases where you must be notified of the
ID when available, you can use the following methods:
// the `channel` event is emit once per SDK initialization when the channel is
// loaded or registered
sdk.channel.addEventListener('channel', ({detail}) => {
console.log('channel id is %s', detail.id)
})
// channel is available on registration
const {channelId} = await channel.register()
// or on create
const {channelId} = await channel.create()
It's generally recommended you store the result of registering or creating a channel should you need the ID for integration purposes.
Channel Tags
If you're making any calls to the channel.tags
interface, you must update
these to the new TagEditor. Calls to the old interface will look like:
channel.tags.add('likes-food')
channel.tags.remove('dislikes-food')
channel.tags.add('sauerkraut', 'foods')
These calls will be updated to use the TagEditor available on the Channel. They will look something like:
await sdk.channel
.editTags()
.add('device', 'likes-food')
.remove('device', 'dislikes-food')
.add('foods', 'sauerkraut')
.apply()
This interface has the benefit of all tags being applied at once when the
apply
method is called.
Note: Previously, the device
tag group was implicit when omitting the
second parameter to the tag add/remove/set methods. It is now explicitly
required. The tag group is always the first parameter to the TagEditor
methods, where previously it was the optional second parameter.
Warning: The tags has
method has been removed, and there is no replacement
method. If you require tracking of segmentation data within your application, we
suggest moving that data into your application's storage.
Channel Attributes
If you're making any calls to the channel.attributes
interface, you must
update these to the new AttributeEditor. Calls to the old interface
would look like:
channel.attributes.set('first_name', 'Guy')
channel.attributes.remove('industry')
These calls will be updated to use the AttributeEditor available on the UaSDK.Channel. They will look something like:
await sdk.channel
.editAttributes()
.set('first_name', 'Guy')
.remove('industry')
.apply()
This interface has the benefit of all attribute mutations being applied at once
when the apply
method is called.
Channel Subscription Lists
The subscription lists editor for the channel no longer needs to wait for a promise to resolve, and supports chaining. If you were using the editor, it would have looked something like:
// using await
const editor = await sdk.channel.subscriptions.edit()
editor.subscribe('cool-list')
editor.unsubscribe('uncool-list')
await editor.apply()
// using .then
sdk.channel.subscriptions
.edit()
.then((editor) => {
editor.subscribe('cool-list')
editor.unsubscribe('uncool-list')
return editor.apply()
})
.then(() => console.log('changes applied'))
The edit()
method no longer returns a promise, but instead a ready-to-use
SubscriptionListEditor. Any calls may be updated to use the editor
immediately:
await sdk.channel.subscriptions
.edit()
.subscribe('cool-list')
.unsubscribe('uncool-list')
.apply()
Named User Interface
The named user interface available at channel.namedUser
has been removed in
favor of the UaSDK.Contact interface. If you were using the old
interface, you would have calls that look like:
channel.namedUser.set('cool-user')
channel.namedUser.remove()
These will be updated to use the UaSDK.Contact interface, which has matching calls for the above:
// replaces the `set` method on named user
await sdk.contact.identify('cool-user')
// replaces the `remove` method on named user
await sdk.contact.remove()
Similar to the above for the channel, there were interfaces for editing named user tags and attributes; we won't re-hash all of the changes, but they will be calls similar to:
channel.namedUser.tags.add('sauerkraut', 'foods')
channel.namedUser.attributes.set('first_name', 'Guy')
These will be updated to use the TagEditor and AttributeEditor interfaces available on the UaSDK.Contact respectively:
// tags
await sdk.contact.editTags().add('foods', 'sauerkraut').apply()
// attributes
await sdk.contact.editAttributes().set('first_name', 'Guy').apply()
Removal of Named User ID retrieval methods
With the removal of channel.namedUser
the channel.namedUser.id
property was
removed and has no equivalent replacement, as it did not necessarily provide an
accurate view of the current Named User ID if it had been changed outside of the
SDK. If your application requires tracking this ID, we recommend storing that
data within your application.
Contact Interface
The UaSDK.Contact interface was previously only asynchronously available, as were its editor methods:
contact.editTags()
contact.editAttributes()
contact.subscriptions.edit()
If you were using any of these, you may have been awaiting them or calling
.then
on the promise they return. This is no longer required, nor is the
sdk.contact
required to be awaited.
Previously you may have had code like:
// with await
const contact = await sdk.contact
const editor = await contact.editTags()
editor.add('foods', 'sauerkraut')
await editor.apply()
// with .then
sdk.contact
.then((contact) => contact.editTags())
.then((editor) => {
editor.add('foods', 'sauerkraut')
return editor.apply()
})
.then(() => console.log('applied tags'))
…this has been greatly simplified in the latest SDK:
// with await
await sdk.contact.editTags().add('foods', 'sauerkraut').apply()
// with .then
sdk.contact
.editTags()
.add('foods', 'sauerkraut')
.apply()
.then(() => console.log('applied tags'))
Custom Event
Calls to the CustomEvent track()
method previously would resolve to an
object containing a boolean ok
property, and would not give a reason why the
call had failed. We now raise an UaSDK.Errors.ReportingDisabledError (or
subclass thereof) if an error occurs due to reporting being disabled.
You should try/catch calls to the track()
method:
const event = new sdk.CustomEvent('ate_food', 3.5, {meal: 'pancakes'})
try {
await event.track()
} catch (e) {
if (e instanceof sdk.errors.ReportingDisabledError) {
console.warn('reporting is disabled')
return
}
throw e
}
Locale Override
The sdk.localeOverride
interface has been removed in favor of a simplified
UaSDK.LocaleManager interface. Calls to the old interface would look
like:
localeOverride.setCountry('FR')
localeOverride.setLanguage('fr')
localeOverride.set({language: 'fr', country: 'FR'})
localeOverride.clear()
Any usages of this previous localeOverride
interface must be updated to use
sdk.locale
, which has a similar interface:
// setting just the country
await sdk.locale.set({country: 'FR'})
// setting just the language
await sdk.locale.set({language: 'fr'})
// setting both simultaneously
await sdk.locale.set({language: 'fr', country: 'FR'})
// clearing any set override
await sdk.locale.clear()
The localeOverride
interface also had static properties which allowed
accessing the currently set overrides:
localeOverride.language
localeOverride.country
These properties have been removed, and are replaced with an asynchronous get
method which can retrieve the current resolved locale for the browser: unlike
the older methods that only returned the override, this method returns the
full locale for the current browser as an object with country
and language
properties. Note that in some browsers, country
may be null
:
const {country, language} = await sdk.locale.get()
Preference Centers
Optional SDK components have moved from the top-level SDK object to a
components
sub-object; this means any calls to retrieve preference centers via
PreferenceCenterManager must be updated. Previously, calls would've
looked like:
preferenceCenters.get()
preferenceCenters.list()
These will be updated to use the components
object:
const definitions = await sdk.components.preferenceCenters.list()
const definition = await sdk.components.preferenceCenters.get('web_preferences')
Feature Flags
Optional SDK components have moved from the top-level SDK object to a
components
sub-object; this means any calls to evaluate feature flag
membership via FeatureFlagManager must be updated. Previously, calls
would've looked like:
featureFlags.get('new_feature')
featureFlags.trackInteraction(newFeatureFlag)
These calls must be updated to use the components
object:
const flag = await sdk.components.featureFlags.get('new_feature')
// later, when a user has interacted with the feature
await sdk.components.featureFlags.trackInteraction(flag)
Disable Analytics Flag
Previously, there was a property on the SDK which acted as a getter and setter for disabling analytics. These calls would look like:
sdk.disableAnalytics
This property has been removed in favor of separate asynchronous getter and setter methods, and usages must be updated. Note: This method is the inverse of the method it replaces to better match other SDK APIs!
const enabled = await sdk.analytics.isEnabled()
// disable analytics
await sdk.analytics.setEnabled(false)
// enable analytics
await sdk.analytics.setEnabled(true)
Data Collection Flag
Previously, there was a property on the SDK which acted as a getter and setter for disabling data collection. These calls would look like:
sdk.dataCollectionEnabled
This property has been removed in favor of separate asynchronous getter and setter methods, and usages must be updated:
const enabled = await sdk.isDataCollectionEnabled()
// disable data collection
await sdk.setDataCollectionEnabled(false)
// enable data collection
await sdk.setDataCollectionEnabled(true)
Additionally, if you use these methods the previous SDK had a behavior which
would partially opt in the channel if data collection was toggled back on. This
behavior has been removed, and if you wish to opt a channel back in you should
use the sdk.register()
method to opt the channel back in to push after
checking the permission:
await sdk.setDataCollectionEnabled(true)
const permission = await sdk.getNotificationPermission()
if (permission === 'granted') {
await sdk.register()
}
Notification Permission Property
Previously, there was a property on the SDK which would determine the current browser permission for notifications. Calls to this property would look like:
sdk.permission
This property has been removed in favor of an asynchronous method which will be more accurate in a wider variety of cases.
const permission = await sdk.getNotificationPermission()
if (permission === 'denied') {
// permission is denied at the browser level
}
Note: this new method returns a slightly different return value; previously
the value default
would be returned if the permission was in its default state
(neither granted nor denied). Now the value prompt
is returned for consistency
with the PermissionStatus interface.
Can Register Method
Previously, there was a property on the SDK which would determine if the browser was currently able to register. Calls to this property would look like:
sdk.canRegister
This has been updated to an asynchronous method which will return an accurate value in a wider variety of cases.
if (await sdk.canRegister()) {
// ask user if they'd like to opt in to notifications
}
Gesture Required
Previously, there was an SDK property which could be checked to determine if push notification registration was required to be called as the result of a user gesture. Calls would look like:
sdk.isGestureRequired
This property has been removed as all vendors and best practices now state that notification opt-in must be done as a reaction to a gesture.
Support Methods
The following support properties and methods have had their meanings updated to better match the SDKs new features:
sdk.isSupported
istrue
if the SDK can load and track events; web push support is not consideredsdk.isWebPushSupported
istrue
if the browser is supported and supports web push; it does not consider if the current context is supported (such as requiring it be added to the home screen on iOS)sdk.isAllowedPushRegistrationContext
istrue
if the browser is currently in an allowed registration context; note this does not otherwise check for browser features, so should only be checked after ansdk.isWebPushSupported
checksdk.canRegister()
is a comprehensive check against all checks required for web push registration, and ensures:- the browser supports web push
- the page is a secure context
- the browser is in an allowable context (such as being saved to the home screen on iOS)
- push permission has not already been denied
In general, you will not need to change how you were using these methods; however if your app is expected to support Safari on iOS, you may wish to ensure you are checking for that support in the following way:
if (sdk.isWebPushSupported && !sdk.isAllowedPushRegistrationContext) {
// prompt user to add the website to their home screen
} else if (await sdk.canRegister()) {
// is in an allowed context; prompt the user to opt into push notifications
}
Migrate APNs Safari Registrations
If your website or application was previously configured for Safari using an APNs certificate, you may wish to migrate your existing Safari audience to VAPID push.
You can either perform this manually by calling sdk.register()
in browsers you
decide to migrate, or by setting the migrateAPNsSafari
configuration value in
your initialization snippet to true
. When this option is set, we will attempt
to automatically migrate any browsers with existing APNs registrations to VAPID
when they visit your website.
Test and Deploy
Once you've updated API calls, you are ready to test in your staging environment, and should everything work as expected deploy to production.
Your end users will not notice the change from the old to new SDK, and will be
seamlessly transitioned from the v1
to the v2
SDK upon visit. It will not
affect their ability to receive notifications if they've not yet visited.
Full List of Changes
- The SDK now supports creating a channel for browsers without notification
opt-in, using
sdk.create()
. You may still request notification permission at a later date withsdk.register()
- Legacy channel interfaces have been removed for tags, attributes, and named
user:
channel.tags
has been replaced by the tag editor atchannel.editTags()
channel.attributes
has been replaced by the attribute editor atchannel.editAttributes()
channel.namedUser
has been replaced by thecontact
interface
- The channel id is no longer synchronously available at
channel.id
; this is now an asynchronous methodchannel.id()
to retrieve it when available. - The legacy synchronous loader has been removed; only the
_async_setup
initialization snippet is supported - Many interfaces that required awaiting a promise to resolve are now available
synchronously for easier use:
- The SDK's contact interface at
sdk.contact
- The contact's tags, subscription lists, and attribute editors available at:
sdk.contact.editTags()
sdk.contact.editAttributes()
sdk.contact.subscriptions.edit()
- The channel subscription list editor, available at
sdk.channel.subscriptions.edit()
- The SDK's contact interface at
- Calls to the
track
method of theCustomEvent
class will now throwsdk.errors.ReportingDisabledError
(or a subclass thereof) if event reporting is disabled due to analytics or SDK data collection being disabled. sdk.dataCollectionEnabled
is no longer available, use the asynchronous methods:sdk.isDataCollectionEnabled
to get the current settingsdk.setDataCollectionEnabled
to update the setting
sdk.localeOverride
moves tosdk.locale
, and has an updated interface- All calls change to
sdk.locale.set
sdk.localeOverride.setCountry('DE')
becomessdk.locale.set({country: 'DE'})
and similarly forlanguage
- Current locale no longer available as synchronous properties on the locale,
instead use
await sdk.locale.get()
which returns the resolved locale, not just overrides
- All calls change to
sdk.disableAnalytics
is no longer available, use the asynchronous methods:sdk.analytics.isEnabled
to get the current settingsdk.analytics.setEnabled
to update the setting
sdk.isGestureRequired
has been removed as all browsers now recommend only performing a notification opt-in as the result of a gesture- Enabling data collection via
sdk.setDataCollectionEnabled
will no longer implicitly opt channels back in if the browser notification permission is granted, as we cannot know for certain if a recorded opt-out was due to data collection being disabled or some other mechanism (such as an explicit call tosdk.channel.optOut()
). If you wish to opt the user back in, you may check thesdk.permission
method and determine ifsdk.register()
should be called again sdk.permission
has been removed, use the asynchronous methodsdk.getNotificationPermission()
to check notification permissionssdk.canRegister
has been changed to an asynchronous method rather than a static property- SDK support methods have been updated to better represent the available
features in the browser:
sdk.isSupported
istrue
if the SDK can load and track events; web push support is not consideredsdk.isWebPushSupported
istrue
if the browser is supported and supports web push; it does not consider if the current context is supported (such as requiring it be added to the home screen on iOS)sdk.isAllowedPushRegistrationContext
istrue
if the browser is currently in an allowed registration context; note this does not otherwise check for browser features, so should only be checked after ansdk.isWebPushSupported
checksdk.canRegister
is a comprehensive check against all checks required for web push registration, and ensures:- The browser supports web push
- The page is a secure context
- The browser is in an allowable context (such as being saved to the home screen on iOS)
- Push permission has not already been denied
- Optional components have moved to
sdk.components
:sdk.preferenceCenters
moves tosdk.components.preferenceCenters
sdk.featureFlags
moves tosdk.components.featureFlags