Web SDK - Current (24.3.0)
Introduction
The Atomic Web SDK allows you to integrate an Atomic stream container into your web app or site, presenting cards from a stream to your customers.
Releases
The current stable release is 24.3.0.
This documentation only applies to the latest stable Web SDK version or prior. For beta versions of the Web SDK, see the Web SDK beta guide.
Browser support
The Atomic Web SDK supports the latest version of Chrome, Firefox, Edge (Chromium-based), Safari on macOS, Safari on iOS and Chrome on Android.
Boilerplate app
You can use our React boilerplate app to help you get started with the Atomic SDK for Web. You can download it from its GitHub repository. Alternatively, you can follow this guide.
Installation
The current version of the Atomic Web SDK is 24.3.0
, and is hosted on our CDN at https://downloads.atomic.io/web-sdk/release/24.3.0/sdk.js
.
As of version 24.2.1
the Web SDK also offers a bundle variant which does not include the font used for icons in action cards. Instead this font is fetched via CDN as required, allowing the size of your initial bundle loaded by the browser to be smaller. Both variations function identically in all other respects.
This variant is hosted on our CDN at https://downloads.atomic.io/web-sdk/release/24.3.0/sdk-cdn-icons.js
.
To integrate it, add the script for your chosen variant as a source to your web page:
- Standard
- Font icons excluded
<html>
...
<body>
<script src="https://downloads.atomic.io/web-sdk/release/24.3.0/sdk.js"></script>
</body>
</html>
<html>
...
<body>
<script src="https://downloads.atomic.io/web-sdk/release/24.3.0/sdk-cdn-icons.js"></script>
</body>
</html>
The SDK can be installed via npm, with the bundled font variant being @atomic.io/action-cards-web-sdk and the variant excluding icon fonts being @atomic.io/action-cards-web-sdk-cdn-icons.
When Atomic releases a new version of the Web SDK, you will need to manually update your scripts with a URL that references the download location of the latest version.
Content Security Policy
If your website enforces a content security policy (CSP) and is using any the following directives, you will need to add the resources corresponding to that directive to your CSP in order to use the Atomic Web SDK.
Directive | Required Resources |
---|---|
frame-src | blob: |
connect-src | https://*.client-api.atomic.io wss://*.client-api.atomic.io |
style-src | 'self' 'unsafe-inline' |
font-src | 'self' data: https://fonts.gstatic.com |
script-src | 'self' https://downloads.atomic.io |
image-src | https://edge.atomic.io |
media-src | https://edge.atomic.io |
Setup
Before displaying a stream container or single card, you must locate your API base URL, environment ID and API key.
API host, API keys and environment id
You must specify your SDK API base URL when configuring the Atomic SDK. Locate this URL in the Atomic Workbench:
- In the Workbench, click on the cog icon in the bottom left and select 'Settings'. On the screen that appears, look for the SDK section. Find the API host details here, and create API keys as required.
- Alternatively, open the command palette and type API host or API keys as required.
The SDK API base URL is different to the API base URL endpoint, which is also available under Configuration. The SDK API base URL ends with client-api.atomic.io.
You can find the environment ID at the top right of the Configuration screen.
Authenticating requests with a JWT
To authenticate requests from the SDK to the Atomic Platform, you must supply an asynchronous callback which will return an authentication token (JSON Web Token or JWT) when requested. This is set by calling the setSessionDelegate
method, giving it a function which resolves to a promise that supplies an authentication token.
AtomicSDK.setSessionDelegate(() => {
return Promise.resolve('<authToken>')
})
Your callback will be invoked when the SDK requires a JWT. The token returned by your callback will be cached by the SDK and used for subsequent authenticated requests until expiry. After expiry the callback will be invoked once again to request a fresh token. The callback must return the token within 5 seconds, otherwise error handling will be triggered within the SDK. More information on the JWT structure is available in the Authentication section.
JWT Expiry interval
The Atomic SDK allows you to configure the time interval to determine whether the JSON Web Token (JWT) has expired. If the interval between the current time and the token's exp
field is smaller than that interval, the token is considered to be expired. To set the token expiry interval call the setTokenExpiryInterval
method, passing it a number greater than 0, representing the time in seconds for the expiry interval. If this method is not called, the default value is 60 seconds.
// sets the token expiry interval to 180 seconds
AtomicSDK.setTokenExpiryInterval(180)
JWT Retry interval
An optional second parameter is available to set the retryInterval
, this is the time in milliseconds that the SDK should wait before attempting a repeated request for a JWT from the session delegate in the event of a token request failing.
const retryInterval = 5000
AtomicSDK.setSessionDelegate(() => {
return Promise.resolve('<authToken>')
}, retryInterval)
Login convenience method
To be ready to display a stream container you need to have initialised the SDK & set the Session Delegate. You can use the convenience method login
to accomplish this in one call. This method has the following parameters:
apiHost
: a string value representing your API hostapiKey
: a string value representing your API keyenvironmentId
: a string value representing your environment idsessionDelegate
: a function that resolves to a promise which supplies an authentication token to the SDKretryInterval
: (optional) a number which defines the JWT Retry interval
This method should be called in the following manner:
AtomicSDK.login(
'<apiHost>',
'<apiKey>',
'<environmentId>',
'<sessionDelegate>',
'<retryInterval>'
)
WebSockets and HTTP API protocols
You can specify the protocol the SDK uses to communicate with the Atomic Platform when fetching cards. This can be done by calling the setApiProtocol
method before calling initialise
, so that the SDK knows which protocol to use when establishing a connection to the platform:
AtomicSDK.setApiProtocol('<communicationProtocol>')
The valid options for communicationProtocol
are: "http"
or "webSockets"
. If this method is not called the SDK will default to WebSockets for communication with the Atomic Platform.
Logging out the current user
The SDK provides a method AtomicSDK.logout
for clearing user-related data when a previous user logs out, or when the active user changes, so that the cache (including the JWT) is clear when a new user logs in to your app. The method also sends any pending analytics events back to the Atomic Platform.
The method accepts an optional parameter deregisterNotifications
, a boolean that indicates whether the device should be deregistered for notifications upon logging out. This parameter only affects a Cordova integration at present as Web push notifications are currently not supported.
- After logging out, you must log in to the SDK (by calling either
AtomicSDK.login
orAtomicSDK.initialise
&AtomicSDK.setSessionDelegate
) to proceed with another user. Otherwise, the Atomic SDK will raise exceptions. - This method also purges all cached card data stored by the SDK and disables all SDK activities.
- This method also invalidates existing stream containers. However, they are not removed from the page in order to allow you to choose how this will be handled. After calling
AtomicSDK.logout
the stream containers will display their empty feed state but users cannot take any action with them. Once you are ready you can call thestop
method on your stream container instance(s) to stop the stream container and remove it from the page.
The code snippet below illustrates how to log out from the Atomic SDK & log in a new user:
// The stream container instance being displayed to the user
const instance = AtomicSDK.launch({...})
...
// Logout from the SDK, in this case passing the option to deregister notifications
await AtomicSDK.logout(true)
// Handle any logout tasks in your own app now, the stream container will display an "empty feed" UI
...
// Remove the stream container when ready
instance.stop()
...
// Log in again with a new user when ready
AtomicSDK.login('<apiHost>', '<apiKey>', '<environmentId>', '<sessionDelegate>', '<retryInterval>')
// Create a new stream container instance when ready
const instance = AtomicSDK.launch({...})
Displaying a stream container
This section applies to all types of container. Containers can be created using the launch
method (launcher view) , the embed
method (standalone vertical and horizontal containers), or the singleCard
method (single card view).
Specifics and code examples for each type of container are explained in more detail in their dedicated sections below.
Before displaying a stream container, you must initialize the SDK by calling the initialise
method:
AtomicSDK.initialise('<apiHost>', '<apiKey>', '<environmentId>')
Stream container ID
First, you’ll need to locate your stream container ID.
Navigate to the Workbench, select Configuration > SDK > Stream containers. Alternatively, open the command palette and type Stream containers. Find the ID next to the stream container you are integrating.
Configuration options
Some configuration options are common for all types of container, other options are only available to a specific type of container. We mention the container type for each configuration option that is not available across all container types.
Style and presentation
A selection of UI elements can be customized within stream containers (this customization does not apply to single card views, with the exception of toastConfig
). These are configured using the enabledUiElements
property on a stream container configuration object:
cardListToast
- defaults totrue
. Set tofalse
to turn off toast messages on the card list.customToastContainer
- defaults tofalse
. Set totrue
to use an alternative toast message presentation for standalone or launcher stream containers which allows you to reposition the toast message.toastConfig
- an optional configuration object to customize the toast messages displayed by the SDK.timeout
: optionally supply a number that sets how long the toast message should be displayed for in milliseconds.toastMessages
: an optional object where you can set custom strings for the following properties:submittedCard
,dismissedCard
,snoozedCard
,feedbackReceived
&fileUploadFailed
. These will be displayed as the toast message for the respective card event.
cardListHeader
- defaults totrue
. Set tofalse
to disable the display of the title at the top of the card list.customContainerHeader
- can optionally supply a custom header to be displayed above the card feed when displaying alaunch
orembed
type stream container.scrollHeader
: an optional boolean to control whether the custom header scrolls with the card feed, defaults totrue
.headerElement
: a string representing valid HTML to be rendered as the custom header. Styles to be applied to the header should be supplied inline on the HTML elements.
launcherButton
- an optional configuration object for the button that toggles alaunch
type stream container. Accepts the following properties:disabled
: defaults tofalse
. Set totrue
to prevent the launcher button from displaying on the page.backgroundColor
: a string value for a valid CSS color that will be used as the background color for the button.openIconSrc
: the source for the image tag displayed in the launcher button when it is in the closed state.closeIconSrc
: the source for the image tag displayed in the launcher button when it is in the open state.
The code snippet below shows how to initialize a launcher container type with a value for all of the enabledUiElements
properties.
AtomicSDK.launch({
...
enabledUiElements: {
cardListToast: true,
customToastContainer: true,
toastConfig: {
timeout: 5000,
toastMessages: {
submittedCard: 'Custom submitted message',
dismissedCard: 'Custom dismissed message',
snoozedCard: 'Custom snoozed message',
feedbackReceived: 'Custom feedback message',
fileUploadFailed: 'Custom file upload failed message'
}
},
cardListHeader: true,
customContainerHeader: {
scrollHeader: true,
headerElement: `
<div style="padding: 10px;background-color: cyan;border-radius: 5px;">
<h1 style="color: grey;">Custom Header</h1>
</div>
`
}
launcherButton: {
disabled: false,
backgroundColor: '#00ffff',
openIconSrc: 'https://example.com/icon-open.svg',
closeIconSrc: 'https://example.com/icon-close.svg'
}
}
});
Functionality
onRuntimeVariablesRequested
: an optional property that can be used on the configuration object to allow your app to resolve runtime variables. If this callback is not implemented, runtime variables will fall back to their default values, as defined in the Atomic Workbench.runtimeVariableResolutionTimeout
defaults to 5 seconds (5000 ms) if not provided.
Read more about runtime variables in the Runtime variables section.
CardListRefreshInterval
The Atomic Web SDK uses WebSockets to keep the card list up-to-date.
If a WebSocket connection cannot be established, or is not permitted, the SDK will revert to polling for new cards. If the SDK has been configured to use HTTP for its communication protocol via the setApiProtocol
method, polling will always be used to update the card list.
When the SDK uses to polling, it will check for new cards every 15 seconds by default. You can customize how frequently this happens by specifying a value (in milliseconds) for the cardListRefreshInterval
configuration option:
AtomicSDK.launch({
...
cardListRefreshInterval: 3000
});
Custom strings
You can customize the following strings used by the Web SDK, using the customStrings
configuration object:
- The title displayed at the top of the card list (
cardListTitle
); - The message displayed when the user has never received any cards before (
awaitingFirstCard
); - The message displayed when the user has seen at least one card in the container before, but has no cards to complete (
allCardsCompleted
); - The title to display for the card snooze functionality in the card overflow menu, and at the top of the card snooze screen (
cardSnoozeTitle
). - The text displayed next to the option to indicate that a card was useful (
votingUsefulTitle
); - The text displayed next to the option to indicate that a card was not useful (
votingNotUsefulTitle
); - The title displayed at the top of the screen presented when a user indicates that a card was not useful (
votingFeedbackTitle
). - The error message shown when the user does not have an internet connection (
noInternetConnectionMessage
). - The error message shown when the theme or card list cannot be loaded due to an API error (
dataLoadFailedMessage
). - The title of the button allowing the user to retry the failed request for the card list or theme (
tryAgainTitle
). - The text displayed on the card overlay while a file upload is in progress (
processingStateMessage
). - The title of the button on the card overlay allowing the user to cancel file uploads that are currently in progress (
processingStateCancelButtonTitle
).
If you don't provide these custom strings, the SDK defaults will be used:
cardListTitle
: "Cards"awaitingFirstCard
: "Cards will appear here when there's something to action."allCardsCompleted
: "All cards completed"cardSnoozeTitle
: "Remind me"votingUsefulTitle
: "This is useful"votingNotUsefulTitle
: "This isn't useful"votingFeedbackTitle
: "Send feedback"noInternetConnectionMessage
: "No internet connection"dataLoadFailedMessage
: "Couldn't load data"tryAgainTitle
: "Try again"processingStateMessage
: "Sending, please wait..."processingStateCancelButtonTitle
: "Cancel process"
The code snippet below shows how to customize some of these strings.
AtomicSDK.launch({
...
customStrings: {
cardListTitle: 'Things to do',
awaitingFirstCard: 'Cards will appear here soon',
allCardsCompleted: 'All cards completed',
cardSnoozeTitle: 'Snooze',
votingUsefulTitle: 'Positive feedback',
votingNotUsefulTitle: 'Negative feedback',
votingFeedbackTitle: 'Tell us more',
noInternetConnectionMessage: 'No internet connection available',
dataLoadFailedMessage: 'Failed to load cards',
tryAgainTitle: 'Please try again',
processingStateMessage: 'File uploads in progress',
processingStateCancelButtonTitle: 'Cancel file upload'
}
});
Displaying a custom header
The Web SDK supports displaying a custom header above your card feed for stream containers created using the launch
or embed
methods. It has no effect for singleCard
stream containers.
The custom header is supplied as an HTML string to the customContainerHeader
property of the customized UI elements. Styles should be applied inline to the HTML elements. Do not attempt to reference classes or other styling from your host application stylesheets because these will not be applied.
Resizing standalone embeds to fit content
You can optionally choose to have standalone embeds resize to fit all of their content, so that they do not scroll. This allows you to embed a stream container inside of another scrolling container of your choice. This feature is enabled by setting the 3rd parameter of the AtomicSDK.embed
method to true
(by default, this value is false
):
AtomicSDK.embed(document.querySelector('#embed'), {
...
onSizeChanged: (width, height) => {
console.log('Standalone embed changed size to', width, height)
}
}, true);
When enabled, the height of the iframe will be automatically updated to reflect its content when it changes. The onSizeChanged
callback will also be triggered when the height changes, allowing you to adjust your UI as necessary.
Card minimum height
You can enforce a minimum height for the cards displayed in your stream container, if you'd prefer them to be large enough to display the card overflow menu without scrolling:
AtomicSDK.launch({
...
features: {
...
cardMinimumHeight: 250 // Replace 250 with your desired minimum height
}
});
The minimum height is specified in pixels.
Single card container toast messages
Toast messages will now be displayed for the single card stream container. The toast messages are enabled by default, as they are for the other stream container variants. The single card toast message can be configured (or disabled) in the same way as they can for the other stream container variants, see the style and presentation section of the Web SDK for details on how to do this.
The default position of single card toast notifications is in the centre at the bottom of the viewport and with a maximum width of 500px. The SDK exposes a class (toast-container
) on the iframe containing the toast messages which can be used to apply your own styling should you wish to do so. See the CSS code snippet below for an example of how to reposition single card toasts to the bottom right of the viewport and with a smaller width, if your embed element id was host-embed-element
.
#host-embed-element iframe.toast-container {
max-width: 300px;
right: 0;
left: initial;
transform: initial;
}
Reposition launcher or standalone (vertical & horizontal) stream container toast messages
(introduced in 23.4.2)
You have the ability customize the positioning of toast messages displayed by the standalone or launcher stream container variants if you wish. To do so set the customToastContainer
option to true style and presentation
Once you have done so the toast position will default to the centre at the bottom of the viewport and with a maximum width of 500px, the same as for single card toasts. In the same manner as for single card toasts you can use the toast-container
class to reposition the iframe containing the toast messages.
Displaying a launcher
The Web SDK supports an additional implementation option - the launcher. This is implemented as a stream container that automatically resizes itself to accommodate its content, without growing beyond the bounds of the browser window. A trigger button is provided which allows you to open and close the stream container. This trigger button is positioned in the bottom right of your page by default. It can be re-positioned by using the .atomic-sdk-launcher-wrapper
.atomic-sdk-launcher-wrapper
.atomic-sdk-launcher
In addition, it is possible to control the size and position of the launcher container itself via the iframe.atomic-sdk-frame.launcher
The launcher is not supported in any of the other SDKs.
Embed with a launcher button
The code sample below shows how to use the AtomicSDK.launch(config)
method, to create an instance of a stream container that is toggled by clicking the provided launcher button on screen.
<html>
...
<body>
<!-- Installation -->
<script src="https://downloads.atomic.io/web-sdk/release/24.3.0/sdk.js"></script>
<script>
AtomicSDK.initialise('<apiHost>', '<apiKey>', '<environmentId>')
AtomicSDK.setSessionDelegate(() => {
return Promise.resolve('<authToken>')
})
AtomicSDK.launch({
streamContainerId: '1234',
onCardCountChanged: count => {
console.log('Card count is now', count)
},
customStrings: {
cardListTitle: 'Things to do'
}
})
</script>
</body>
</html>
Responding to the launcher opening or closing
When creating a stream container in the launcher mode you can supply an optional callback that will be invoked whenever the stream container is opened or closed. To use this callback set it on the onLauncherToggled
property of the configuration object supplied to the AtomicSDK.launch(config)
method. The function you set there will be called with one argument; a boolean representing whether the launcher has just been opened (true
) or closed (false
). The code sample below shows how to set this callback.
AtomicSDK.launch({
...
onLauncherToggled: isOpen => {
if (isOpen) {
console.log('the launcher is has been opened')
} else {
console.log('the launcher has been closed')
}
}
})
Opening and closing the launcher externally
If you choose to embed an Atomic stream container in the launcher mode (using AtomicSDK.launch
), you can open or close the stream container from another trigger, such as a button or link, instead of using the launcher button in the bottom right of the screen. If required you can disable the built-in launcher button using the enabledUiElements
property of the customized UI elements.
Read the manually controlling a launcher stream container section for more details.
Displaying a single card
Use AtomicSDK.singleCard(element, config)
to create an instance of a stream container that displays a single card, without any surrounding UI. The card is embedded inside of the specified element
. Any subviews open inside a separate frame.
When displaying a single card (using the AtomicSDK.singleCard
method), the top most card in the given stream container is shown. This is the card with the highest priority that was sent most recently. The iframe that renders the single card automatically adjusts to the height of the card - this is set directly on the iframe's style
property. When the card is actioned, dismissed or snoozed, and there are no other cards in the stream container, the card is removed, and the single card view collapses to a height of 0.
When a new card arrives, the single card view will resize to fit that card.
You can respond to changes in the height of the single card view using:
- CSS classes on the single card view. If the single card view is displaying a card, it has a class of
has-card
. The frame itself always has a class ofsingle-card
. - Setting the
onSizeChanged
callback, on the configuration object that is passed to the stream container when callingAtomicSDK.singleCard
. This callback is triggered when the size of the single card view changes, and you can use this to perform additional actions such as animating the card in or out, or removing it from the page.
AtomicSDK.singleCard(document.querySelector('#embed'), {
onSizeChanged: (width, height) => {
console.log(`The single card view now has a height of ${height}.`)
}
})
When displaying a single card view, all card subviews, full image/video views, and the snooze selection screen all display inside a modal iframe alongside the card. You can position this modal wherever you like on your page. It can be targeted using the class modal-subview
, and when the modal iframe is displaying a subview, it has a class of has-subview
.
The iframe generated by the singleCard
method can be styled just like any other DOM element with CSS.
<html>
...
<body>
<!--Installation-->
<script src="https://downloads.atomic.io/web-sdk/release/24.3.0/sdk.js"></script>
<script>
AtomicSDK.initialise('<apiHost>', '<apiKey>', '<environmentId>')
AtomicSDK.setSessionDelegate(() => {
return Promise.resolve('<authToken>')
})
AtomicSDK.singleCard(document.querySelector('#embed'), {
streamContainerId: '1234',
onCardCountChanged: count => {
console.log('Card count is now', count)
},
customStrings: {
cardListTitle: 'Things to do'
}
})
</script>
</body>
</html>
Displaying a vertical stream container
This code sample shows how to use the AtomicSDK.embed(element, config, autosize)
method to create an instance of a stream container, embedded as an iframe inside of the specified element
.
The iframe generated by the embed
method can be styled just like any other DOM element with CSS.
<html>
...
<body>
<!--Installation-->
<script src="https://downloads.atomic.io/web-sdk/release/24.3.0/sdk.js"></script>
<script>
AtomicSDK.initialise('<apiHost>', '<apiKey>', '<environmentId>')
AtomicSDK.setSessionDelegate(() => {
return Promise.resolve('<authToken>')
})
AtomicSDK.embed(document.querySelector('#embed'), {
streamContainerId: '1234',
onCardCountChanged: count => {
console.log('Card count is now', count)
},
customStrings: {
cardListTitle: 'Things to do'
}
})
</script>
</body>
</html>
Displaying a horizontal stream container
The Web SDK also supports displaying stream containers created with embed
as a horizontally orientated stream of cards. In this horizontal view the cards are rendered from left to right.
When creating a stream container with the embed
method, pass a configuration object which contains a HorizontalContainerConfig
object within the features
property:
AtomicSDK.embed({
streamContainerId: "1234",
...
features: {
horizontalContainerConfig: {
enabled: true,
cardWidth: 400,
emptyStyle: "standard",
headerAlignment: "center",
scrollMode: "snap",
lastCardAlignment: "left"
}
}
})
The iframe generated by the embed
methods can be styled just like any other DOM element with CSS.
If a stream container is specified to be horizontal (by setting enabled
to true
on the HorizontalContainerConfig
object), you must also supply a cardWidth
property. The SDK will throw an exception for your stream container without this.
Configuration object
This object allows you to configure the horizontal stream container via the following properties:
enabled
: A boolean flag that instructs the SDK to display this stream container in horizontal layout.cardWidth
: The width of each card in the stream container. All cards in the container will have this same width and it must be assigned explicitly.emptyStyle
: The style of the empty state (when there are no cards) of the container. Possible values are:standard
: Default value. The stream container displays a no-card user interface.shrink
: The stream container shrinks out of view.
headerAlignment
: The alignment of the card list title in the horizontal stream container header. Possible values are:center
: Default value. The title is aligned in the middle of the header.left
: The title is aligned to the left of the header.
scrollMode
: The scrolling behavior of the stream container. Possible values are:snap
: Default value. The stream container snaps between cards when scrolling.free
: The container scrolls freely.
lastCardAlignment
: The alignment of the card when there is only one present in the stream container. Possible values are:left
: Default value. The last card is aligned to the left of the container.center
: The last card is aligned in the middle of the container.
API-driven card containers
(introduced in 23.4.0)
The SDK provides the ability to create an "API-driven" card container that can be used for observing a stream container without rendering any UI. This is done using the SDK method AtomicSDK.observableStreamContainer
which accepts a configuration object that has a subset of the properties accepted by the other stream container variants:
streamContainerId
: a string representing the id of the stream container you want to observe.cardFeedHandler
: a callback function that will be invoked with acards
parameter when the card feed is updated, this parameter is an array of cards.cardListRefreshInterval
: an optional number value representing the interval in milliseconds between HTTP polls of the card feed when the SDK is operating with the HTTP communication protocol, defaults to 15 seconds if not provided.onRuntimeVariablesRequested
: an optional function that the SDK will use to resolve runtime variables, read more about these in the runtime variables section.runtimeVariableResolutionTimeout
: an optional number value representing the time in milliseconds the SDK will wait for your app to resolve runtime variables, defaults to 5 seconds if not provided.onCardCountChanged
: an optional callback function that will be invoked when the card count changes in the container, read more in the onCardCountChanged callback section.features
: an optional object that accepts one propertyruntimeVariableAnalytics
which is a boolean value indicating whether runtime variable analytics are enabled for this container.
When you initialize one of these stream containers you are returned an instance of AACHeadlessStreamContainer
. Calling the start
method on this instance will start observing changes in the card feed, using either WebSockets or HTTP polling depending on how you have configured the SDK network protocol using AtomicSDK.setApiProtocol
. Updates to the card feed will be passed to the callback function provided as the cardFeedHandler
.
To stop observing card feed updates call the stop
method on the instance. Observation will also stop when you call the AtomicSDK.logout
method.
The following code snippet illustrates how to initialise one of these API-driven containers:
const observableContainer = AtomicSDK.observableStreamContainer({
streamContainerId: '1234',
cardFeedHandler: cards => {
console.log('handling card update', cards)
}
})
// start observing card feed changes
observableContainer.start()
// stop observing card feed changes
observableContainer.stop()
Examples
Accessing card metadata
Card metadata encompasses data that, while not part of the card's content, are still critical pieces of information. Key metadata elements include:
- Card instance ID: This is the unique identifier assigned to a card upon its publication.
- Card priority: Defined in the Workbench, this determines the card's position within the feed. The priority will be an integer between 1 & 10, a priority of 1 indicates the highest priority, placing the card at the top of the feed.
- Action flags: Also defined in the Workbench, these flags dictate the visibility of options such as dismissing, snoozing, and voting menus for the card.
The following code snippet illustrates how to access these card metadata values for a card instance:
const onCardsChanged = (cards) => {
cards.forEach(card => {
console.log('Card instance id is: ', card.instance.id)
console.log('Card priority is: ', card.metadata.priority)
console.log('Card actions are: ', card.actions)
})
}
const observableContainer = AtomicSDK.observeStreamContainer({
...
cardFeedHandler: onCardsChanged
})
Traversing card elements
A card consists of Elements which are defined in the Workbench in the TOP CARD
section of a card template. The elements intended for the top level of your card are accessible via the defaultView
property on a card, this is a CardLayout
which contains a list of the elements in its nodes
property.
note: If you are using TypeScript in your application the card types are distributed with the SDK and should assist with discovery of the available properties on a CardInstance
.
The code snippet below illustrates how you would traverse the cards received and access the layout nodes for each card, viewing content from some common card elements:
const onCardsChanged = (cards) => {
cards.forEach(card => {
card.defaultView.nodes.forEach(node => {
// some common card elements
node.type === 'cardDescription' && console.log('Card category is: ', node.attributes.text)
node.type === 'headline' && console.log('Card headline is: ', node.attributes.text)
node.type === 'text' && console.log('Card text block is: ', node.attributes.text)
node.type === 'list' && console.log('First card list item is: ', node.children[0].attributes.text)
})
})
}
const observableContainer = AtomicSDK.observeStreamContainer({
...
cardFeedHandler: onCardsChanged
})
Accessing subviews
A card can have additional layouts known as "subviews", these are defined in the Workbench in the SUBVIEWS
section of a card template. Each subview has a unique ID used to identify it.
The code snippet below illustrates how you can access a particular subview layout for a card:
const onCardsChanged = (cards) => {
const targetSubviewId = 'my-subview-id'
const firstCard = cards[0]
const subviewToDisplay = firstCard.subviews[targetSubviewId]
console.log('Subview title is: ', subviewToDisplay.title)
console.log('Subview layout nodes are: ', subviewToDisplay.nodes)
}
const observableContainer = AtomicSDK.observeStreamContainer({
...
cardFeedHandler: onCardsChanged
})
API-driven card actions
(introduced in 23.4.0)
The SDK provides the ability to execute card actions through pure SDK API. The currently supported actions are: dismiss, submit, and snooze. To execute these card actions, call the AtomicSDK.onCardAction
method with the target stream container and card instance ID then call the appropriate card action method from the object that is returned:
Dismissing a card
Call the dismiss
method with onActionSuccess
and onActionFailed
parameters, these are functions to be invoked on success or failure of the dismiss action. The code snippet below illustrates how you would do this:
const successHandler = () => {
console.log('successfully dismissed card')
}
const failureHandler = error => {
console.error('failed to dismiss card', error)
}
AtomicSDK.onCardAction('stream-container-id', 'card-id').dismiss(
successHandler,
failureHandler
)
Snoozing a card
Call the snooze
method with onActionSuccess
and onActionFailed
parameters, these are functions to be invoked on success or failure of the dismiss action. Also a third number parameter representing the time in seconds that the card should be snoozed for, this must be a positive number. The code snippet below illustrates how you would do this:
const successHandler = () => {
console.log('successfully snoozed card')
}
const failureHandler = error => {
console.error('failed to snooze card', error)
}
const snoozeIntervalSeconds = 60
AtomicSDK.onCardAction('stream-container-id', 'card-id').snooze(
successHandler,
failureHandler,
snoozeIntervalSeconds
)
Submitting a card
Atomic cards now include button names when they are submitted. The name will be added to analytics to enable referencing the triggering button in an Action Flow. Resulting from this change as of version 24.2.0
the submitButtonName
parameter must be supplied when performing an API-driven card submission.
Call the submit
method with onActionSuccess
, onActionFailed
parameters, these are functions to be invoked on success or failure of the dismiss action. Also supply a submitButtonName
parameter which is the button "name" attribute obtained from the card data. Optionally a fourth parameter may be supplied for the response object that you wish to submit with the card. This object can only contain values with are strings, numbers or booleans.
The following code snippet illustrates how to obtain a button name from the top-level of the first card:
const onCardsChanged = (cards) => {
let submitButton
const actionButtons = cards[0]?.defaultView.nodes.find(
node => node.type === 'form'
)
if (actionButtons.children.length > 0) {
submitButton = actionButtons.children.find(
node => node.type === 'submitButton'
)
}
const submitButtonName = submitButton?.attributes?.name
}
const observableContainer = AtomicSDK.observeStreamContainer({
...
cardFeedHandler: onCardsChanged
})
The next code snippet shows how to perform a card submit action:
const successHandler = () => {console.log('successfully submitted card')}
const failureHandler = (error) => {console.error('failed to submit card', error)}
// The name attribute of your submit button as obtained from the above code snippet
const submitButtonName = 'submit-button-123'
const cardResponse = {
email: 'user@example.com'
subscribed: true,
count: 5
}
// submit with a payload
AtomicSDK.onCardAction('stream-container-id', 'card-id').submit(successHandler, failureHandler, submitButtonName, cardResponse)
Manually controlling the open state of a stream container
Manually controlling a launcher stream container
If you have a launcher type stream container, you can open or close this container via another trigger, instead of the launcher button supplied by the SDK.
To do this, call the setOpen
method on the stream container instance to open or close the stream container:
let instance = AtomicSDK.launch({
...
});
instance.setOpen(true);
Manually controlling other stream container variants
If you have a single card, horizontal or vertical stream container that is selectively displayed to the user (such as one that is hidden inside of a notification drawer) you need to inform the SDK when this stream container is "open" and viewable by the user. This is important so that analytics events such as stream-displayed
& card-displayed
are correctly dispatched. Failing to do so will result in an incorrect count of seen and unread cards, as described in the retrieving the count of cards section of this guide.
Use the controlledContainerOpenState
feature flag in the configuration object when initialising your stream container. This ensures that your container will be initialised in the "closed" state. After initialisation of the container, it is then the responsibility of the host app to call the setOpen
method on the stream container instance when the stream container is being displayed to or hidden from the user:
const instance = AtomicSDK.embed({
streamContainerId: "1234",
...
features: {
controlledContainerOpenState: true
}
});
// when the host app has displayed the container to the user call
instance.setOpen(true);
// when the host app is hiding the container from the user call
instance.setOpen(false);
Note: The controlledContainerOpenState
feature flag is not required for the launcher stream container and has no effect on it.
Closing a stream container
To stop a stream container, and remove it from your web page, call the stop
method on the previously created instance:
let instance = AtomicSDK.embed(document.querySelector('#embed'), {
...
})
instance.stop()
Customizing the first time loading experience
When a stream container with a given ID is launched for the first time on a user's device, the SDK loads the theme and caches it in the browser for future use. On subsequent launches of the same stream container, the cached theme is used and the theme is updated in the background, for the next launch. Note that this first time loading screen is not presented in single card view - if a single card view fails to load, it collapses to a height of 0.
The SDK supports some basic properties to style the first time load screen, which displays a loading spinner in the center of the container. If the theme fails to load for the first time, an error message is displayed with a 'Try again' button. One of two default error messages are possible - 'Couldn't load data' or 'No internet connection'. See the custom strings section of this guide if you want to change the default wording.
First time loading screen colors are customized using the following SDK configuration properties:
backgroundColor
: the background of the first time loading screen. Defaults to#FFFFFF
.textColor
: the color to use for the error message shown when the theme fails to load. Defaults torgba(0,0,0,0.5)
.loadingSpinnerColor
: the color to use for the loading spinner on the first time loading screen. Defaults to#000000
.buttonTextColor
: the color to use for the 'Try again' button, shown when the theme fails to load. Defaults to#000000
.
AtomicSDK.launch({
...
firstLoadStyles: {
backgroundColor: '#FFFFFF',
textColor: '#000000',
loadingSpinnerColor: '#000000',
buttonTextColor: '#000000'
}
});
Dark mode
Stream containers in the Atomic Web SDK support dark mode. You can configure an optional dark theme for your stream container in the Atomic Workbench.
The interface style (interfaceStyle
) property determines which theme is rendered:
automatic
: If the user's device is currently set to light mode, the stream container will use the light (default) theme. If the user's device is currently set to dark mode, the stream container will use the dark theme (or fallback to the light theme if this has not been configured). If the stream container does not have a dark theme configured, the light theme will always be used.light
: The stream container will always render in light mode, regardless of the device setting.dark
: The stream container will always render in dark mode, regardless of the device setting.
To change the interface style, set the corresponding value for the interfaceStyle
property on the configuration
object when creating the stream container.
Filtering cards
Stream containers (vertical or horizontal) and single card containers can have one or more filters applied. These filters determine which cards are displayed.
When you have created a stream container, the stream container instance that is returned has a streamFilters
property. This can be used to set filters to be applied to that stream container. Each stream filter consists of both a filter value and an operator. To set a stream filter you need to call the desired filter value function from the streamFilters
object, and chain off from that the desired filter operator. After setting the stream filters call the apply
function:
const instance = AtomicSDK.launch({
...
})
// this will set a card priority stream filter with the greaterThan operator
instance.streamFilters.addCardPriorityFilter().greaterThan(3)
instance.streamFilters.apply()
Multiple stream filters can be applied, the snippet below shows how you would set filters to only show cards with a card priority greater than 5 & created after 15th February 2020, for a launcher type container. The apply
function needs to be called just once and must be called after you have set all the filters you wish to apply:
const instance = AtomicSDK.launch({
...
})
instance.streamFilters.addCardPriorityFilter().greaterThan(5)
instance.streamFilters.addCardCreatedFilter().greaterThan('2020-02-15T00:00:00.000Z')
instance.streamFilters.apply()
Filter values
The filter value is used to filter cards in a stream container. The table below summarises the different card attributes that can be filtered on, as well as the permitted data type for that filter and the filter operators that can be applied to it.
Card attribute | Description | Filter function | Value type | Permitted operators |
---|---|---|---|---|
Priority | Card priority defined in Workbench, Card -> Delivery | streamFilters.addCardPriorityFilter() | number between 1 & 10 inclusive | equals notEqualTo greaterThan greaterThanOrEqualTo lessThan lessThanOrEqualTo in notIn between |
Card template ID | The template ID of a card, see below for how to get it | streamFilters.addCardTemplateIdFilter() | string | equals notEqualTo in notIn |
Card template name | The template name of a card | streamFilters.addCardTemplateNameFilter() | string | equals notEqualTo in notIn |
Card template created date | The date time when a card template is created | streamFilters.addCardCreatedFilter() | ISO date string | equals notEqualTo greaterThan greaterThanOrEqualTo lessThan lessThanOrEqualTo |
Custom variable | The variables defined for a card in Workbench, Card -> Variables | streamFilters.addVariableFilter() | multiple | equals notEqualTo greaterThan greaterThanOrEqualTo lessThan lessThanOrEqualTo in notIn between |
Payload variable | The value for variables as defined in API event payload | streamFilters.addPayloadVariableFilter() | multiple | equals notEqualTo greaterThan greaterThanOrEqualTo lessThan lessThanOrEqualTo in notIn between |
Payload metadata | The value for metadata as defined in API event payload | streamFilters.addPayloadMetadataFilter() | multiple | equals notEqualTo greaterThan greaterThanOrEqualTo lessThan lessThanOrEqualTo in notIn between |
Note: It's important to specify the right value type when referencing custom variables for filter value. There are five types of variables in the Workbench, currently four are supported: String, Number, Date and Boolean.
On the card editing page, click on the ID part of the overflow menu at the upper-right corner to copy it to the clipboard.
Filter operators
The filter operator is the operational logic applied to a filter. The table below summarises the available operators as well as the value types each will accept. The available value types are further narrowed depending on which filter value you are filtering on.
Operator | Description | Supported value types |
---|---|---|
equals | Equal to the filter value | number string iso date string boolean null |
notEqualTo | Not equal to the filter value | number string iso date string boolean null |
greaterThan | Greater than the filter value | iso date string number |
greaterThanOrEqualTo | Greater than or equal to the filter value | iso date string number |
lessThan | Less than the filter value | iso date string number |
lessThanOrEqualTo | Less than or equal to the filter value | iso date string number |
in | In the set of filter values supplied | number[] string[] |
notIn | Not in the set of filter values supplied | number[] string[] |
between | In the range bound by the two values supplied. Will only accept an array containing two numbers e.g. [2, 8] will match values between 2 and 8 inclusive. | number[] |
Each operator supports different value types. For example, the operator lessThan
only supports an iso date string or a number. Passing any other value types will raise an exception.
Removing filters
You have the ability to clear filters on a stream container if required. To clear the filters you should call the clearFilters
function on the on the streamFilters
property for the relevant stream container instance. For example:
const instance = AtomicSDK.launch({
...
})
// applying filters to your stream container instance
instance.streamFilters.addCardPriorityFilter().greaterThan(5)
instance.streamFilters.addCardCreatedFilter().greaterThan('2020-02-15T00:00:00.000Z')
instance.streamFilters.apply()
// at some later stage in order to clear the filters you should trigger
instance.streamFilters.clearFilters()
Image linking
(introduced in 24.1.0)
Image elements can be used to trigger a navigation action. In the Atomic Workbench you can configure the behavior of the link to trigger a subview navigation, open a URL or to send a payload to your app. For information on how to handle the image link payload see custom action payloads on image links
Custom action payloads
Supporting custom action payloads on link & submit buttons
In the Atomic Workbench, you can create a link button or submit button with a custom action payload. When such a button is tapped, the appropriate callback, onLinkButtonPressed
or onSubmitButtonPressed
is triggered, allowing you to perform an action in your web app based on that payload.
The callback is passed an object, containing the payload that was defined in the Workbench for that button (under the actionPayload
property), as well as the stream container ID and card instance ID that triggered the button.
AtomicSDK.launch({
...
streamContainerId: "1234",
onLinkButtonPressed: (data) => {
if(data.streamContainerId === "1234" &&
data.cardInstanceId === "abcd-1234" &&
data.actionPayload.screen === 'home') {
navigateToHomeScreen()
}
},
onSubmitButtonPressed: (data) => {
if(data.streamContainerId === "1234" &&
data.cardInstanceId === "abcd-1234" &&
data.actionPayload.screen === 'home') {
navigateToHomeScreen()
}
}
});
Supporting custom action payloads on image links
Similar to action payloads on buttons you can also create an image link with a custom action payload. When such an image is tapped, the onLinkButtonPressed
callback is triggered and is passed an object with the same properties as for a button with a custom action payload.
Customizing toast messages for card events
You can customize any of the toast messages used when dismissing, completing, snoozing and placing feedback on a card. This is configurable for each stream container. You simply supply a string for each custom message. If you do not supply a string, the defaults will be used. See the custom strings section of this guide if you want to change the default wording.
Read the Style and presentation section to understand how to do this using the toastConfig
configuration object. That section also has a code example.
Card snoozing
The Atomic SDKs provide the ability to snooze a card from a stream container or single card view. Snooze functionality is exposed through the card’s action buttons, overflow menu and the quick actions menu (exposed by swiping a card to the left, on iOS and Android).
Selecting snooze option from either location brings up the snooze date and time selection screen. The user selects a date and time in the future until which the card will be snoozed. Snoozing a card will result in the card disappearing from the user’s card list or single card view, and reappearing again at the selected date and time. A user can snooze a card more than once.
See the custom strings section of this guide if you want to change the default wording.
Preventing snoozing beyond card expiry date
Selecting a date & time combination beyond a card's expiry date is prevented when snoozing a card using the SDK's built-in interface.
There are three ways to snooze a card in the Atomic SDK:
- Overflow Menu: Select the overflow menu item (default "Remind me"), then choose a date & time in the built-in selector.
- Snooze Button: Click a snooze button on the card and select a date & time via the built-in selector.
- Pre-set Snooze Button: Click a snooze button with a pre-set snooze period, which snoozes the card immediately without showing the selector.
If an expiry date is set on the card, you cannot select dates beyond this expiry in scenarios 1 and 2. Scenario 3 remains unaffected, as the snooze period is explicitly pre-configured in the Workbench.
Card voting
The Atomic SDKs support card voting, which allows you to gauge user sentiment towards the cards you send. When integrating the SDKs, you can choose to enable options for customers to indicate whether a card was useful to the user or not, accessible when they tap on the overflow button in the top right of a card.
If the user indicates that the card was useful, a corresponding analytics event is sent for that card (card-voted-up
).
If they indicate that the card was not useful, they are presented with a secondary screen where they can choose to provide further feedback. The available reasons for why a card wasn’t useful are:
- It’s not relevant;
- I see this too often;
- Something else.
If they select "Something else", a free-form input is presented, where the user can provide additional feedback. The free form input is limited to 280 characters. After tapping "Submit", an analytics event containing this feedback is sent (card-voted-down
).
See the custom strings section of this guide if you want to change the default wording.
Card voting is disabled by default. You can enable positive card voting ("This is useful"), negative card voting ("This isn’t useful"), or both:
AtomicSDK.launch({
...
features: {
cardVoting: {
canVoteUseful: true, // Whether the user can vote that a card is useful.
canVoteNotUseful: true // Whether the user can vote that a card is not useful.
}
}
})
Responding to card events
The SDK allows you to perform custom actions in response to events occurring on a card, such as when a user:
- submits (or fails to submit) a card;
- dismisses (or fails to dismiss) a card;
- snoozes (or fails to snooze) a card;
- indicates a card is useful (when card voting is enabled);
- indicates a card is not useful (when card voting is enabled).
To be notified when these happen, assign an onCardEvent
callback when creating your stream container:
AtomicSDK.launch({
...
// Callback notified when card events occur.
onCardEvent: (event) => {
console.log(`Card event occurred: ${event.type}`)
}
...
});
The identifier for the event is available in the type
property, and will be one of the following:
submitted
submit-failed
dismissed
dismiss-failed
snoozed
snooze-failed
voted-useful
voted-not-useful
Sending custom events
A custom event can be used in the Workbench to create segments for card targeting. For more details of custom events, see Custom Events.
You can send custom events directly to the Atomic Platform for the logged in user, via the sendCustomEvent
method, passing it an object with an eventName
and optionally properties
where you can add additional data for your event.
The event will be created for the user defined by the authentication token returned in the session delegate. As such, you cannot specify target user IDs using this method.
AtomicSDK.sendCustomEvent({
eventName: 'myCustomEvent',
properties: {
action: 'updated-profile'
}
}).catch(error => {
// An error is thrown if something prevents the custom event from being sent to the Platform
})
SDK Event Observer
The SDK provides the ability to observe actions within the SDK via the SDK event observer, this observer is a callback that you set via the observeSDKEvents
SDK method. The callback will be invoked with an event object that conforms to a particular event type.
The code snippet below shows how to set the event observer callback:
AtomicSDK.observeSDKEvents(event => {
console.log('sdk event observed: ', event)
})
The actions that will trigger this callback include card & stream actions, the table below contains the full list of actions that the event observer may be called with and their corresponding event type.
Event name | Event type | Description |
---|---|---|
card-completed | SDKCardCompletedEvent | The user has completed a card |
card-dismissed | SDKCardDismissedEvent | The user has dismissed a card |
card-snoozed | SDKCardSnoozedEvent | The user has snoozed a card |
card-feed-updated | SDKFeedEvent | A stream container has had its card feed updated |
card-displayed | SDKCardDisplayedEvent | A card has been displayed to the user |
card-voted-up | SDKCardVotedUpEvent | The user provided positive feedback on a card |
card-voted-down | SDKCardVotedDownEvent | The user provided negative feedback on a card |
runtime-vars-updated | SDKRuntimeVarsUpdatedEvent | One or more runtime variables have been resolved |
stream-displayed | SDKStreamDisplayedEvent | A stream container has been displayed to the user |
user-redirected | SDKRedirectedEvent | The user is redirected by a URL or a custom payload |
snooze-options-displayed | SDKSnoozeOptionsDisplayedEvent | The snooze date/time selector has been displayed |
snooze-options-canceled | SDKSnoozeOptionsCanceledEvent | The user has exited the snooze date/time selector |
card-subview-displayed | SDKSubviewDisplayedEvent | A card subview has been opened |
card-subview-exited | SDKSubviewExitedEvent | A card subview has been exited |
video-played | SDKVideoPlayedEvent | The user has started playback of a video |
video-completed | SDKVideoCompletedEvent | The user has watched a video to completion |
sdk-initialized | SDKInitEvent | The SDK has been initialized and supplied with a valid auth token |
request-failed | SDKRequestFailedEvent | A request to the Atomic client API has failed or a WebSocket failure has caused a fallback to HTTP polling |
notification-received | SDKNotificationReceivedEvent | A push notification has been received |
user-file-uploads-started | SDKUserFileUploadsStartedEvent | A user has started the process of uploading files for a card |
user-file-uploads-completed | SDKUserFileUploadsCompletedEvent | A user has successfully completed the process of uploading files for a card |
user-file-uploads-failed | SDKUserFileUploadsFailedEvent | A user has completed the process of uploading files, with one or more uploads failing |
Accessing properties for a particular event
If using TypeScript the event types are distributed with the SDK and you will get event property suggestions via your IDE. If your project is written in JavaScript refer to this resource for examples showing the shape of event objects.
The SDK events which may be emitted are available on the AtomicSDK.SDKEvents
property, you can use this to check which event you are receiving:
AtomicSDK.observeSDKEvents(event => {
if (event.eventName === AtomicSDK.SDKEvents.cardFeedUpdated) {
// properties for that event will be suggested via your IDE
console.log('card count', event.properties.cardCount)
}
})
There are some additional properties on the event objects not specified in the types or the provided examples. These properties are provided for debugging purposes and are subject to change so should not be relied upon in production code.
Stopping event observation
To stop observing events call the observeSDKEvents
SDK method passing in a null
value for the callback:
AtomicSDK.observeSDKEvents(null)
Metadata pass-through
Any metadata sent from an API request can now be accessed from within the SDK using the SDK Event Observer, once enabled in the Workbench
Currently the metadata is only returned in the card-displayed event.
An example body in an API request to an Action Flow trigger endpoint might look like:
{
"flows": [
{
"payload": {
"metadata": {
"account": "123445",
"fruit": "apple"
}
},
"target": {
"type": "user",
"targetUserIds": "user-123"
}
}
]
}
To retrieve the metadata just capture the card-displayed event in an SDK Event Observer, such as:
AtomicSDK.observeSDKEvents(event => {
if (event.eventName === "card-displayed") {
console.log(JSON.stringify(event));
}
});
The json returned in the event data will take the following shape:
{
"id": "043195e2-8b1d-40b2-b4f2-12a70bd1467e",
"endUserId": "user-123",
"timestamp": "2024-08-27T23:13:18.587Z",
"properties": {
"account": "123445",
"fruit": "apple"
},
"eventContext": {
"userLocalTimestamp": "2024-08-28T11:13:18.587+12:00"
},
"analyticsEvent": "card-displayed",
"hostContext": {},
"sdkContext": {
"containerId": "my-container"
},
"streamContext": {
"streamLength": 0,
"streamLengthVisible": 1,
"cardPositionInStream": 1
},
"cardContext": {
"cardInstanceId": "286c3fe4-a0ad-5879-a585-9736de8e2cdb",
"cardInstanceStatus": "active",
"cardViewState": "topview",
"cardPresentation": "individual"
},
"eventName": "card-displayed"
}
The metadata is included in the properties object:
"properties": {
"account": "123445",
"fruit": "apple"
},
Event observer use cases
Trigger a user metrics request on SDK event
The SDK event observer can be used to determine when it is appropriate to update the user metrics for a logged in user. To assist with this the SDK provides you with a set, AtomicSDK.userMetricsTriggerEvents
, containing the event names that indicate the user metrics could have changed and should be refreshed.
The code snippet below illustrates how to use this set, in combination with the event observer, to keep card count values in your application up to date:
AtomicSDK.observeSDKEvents(event => {
if (AtomicSDK.userMetricsTriggerEvents.has(event.eventName)) {
// observed SDK event is one that indicates user metrics should be refreshed
AtomicSDK.requestUserMetrics().then(metrics => {
// use the returned user metrics response to update card counts in your application
console.log('user metrics updated', metrics)
})
}
})
Push notifications
Web push notifications are currently not supported in the Web SDK.
It is possible to set up notifications similarly to our native iOS and Android SDKs by integrating the Web SDK (version 1.5.0 and above) inside a native mobile application (e.g. Cordova or Capacitor/Ionic).
Cordova / Capacitor
The Web SDK (version 1.5.0 and above) can support push notifications when the SDK is embedded within a web view on a native iOS or Android app. For example, if the SDK is embedded in a Cordova or Capacitor app.
In order to integrate support for push notifications, you will need to use either native code, or a plugin to expose the native callbacks and device information for push notifications.
If using Cordova, one possible plugin is cordova-plugin-push, if using the Capacitor/Ionic framework @capacitor/push-notifications may be used.
First, follow the instructions in the plugin's documentation to integrate it inside your app.
You will need to create a push certificate or token for iOS apps. On Android you need to set up Firebase Cloud Messaging which will generate a server key. These will be required to configure push notifications in the workbench.
Then, in your app you will need to provide the Atomic Web SDK with information about the app and device it is running on.
Finally, the app must register the device for notifications and let Atomic know which stream containers to receive push notifications for.
For a complete example of integrating push notifications in a Cordova application, see our Cordova boilerplate app on GitHub
1. Set the device details for the SDK
You must pass information about the native device to the Atomic SDK before registering for notifications. Using the setNativeDeviceInfo
method which expects a single argument, a DeviceInfo
object with the following keys:
platform
- either'iOS'
or'Android'
.deviceId
- the unique device identifier.appId
- either the application identifier of your device on Android, or the bundle identifier on iOS, it should also exactly match theappId
set when configuring the app for notifications in the workbench.
If you are using Cordova, you may use the cordova-plugin-device to get the device information.
The following code snippets shows how to call this method, using the cordova-plugin-device
plugin to get the device platform and uuid.
AtomicSDK.setNativeDeviceInfo({
platform: device.platform,
deviceId: device.uuid,
appId: 'com.example.yourApp'
})
2. Register the user against specific stream containers for push notifications
You need to signal to the Atomic Platform which stream containers are eligible to receive push notifications in your app for the current device.
AtomicSDK.registerStreamContainersForNotifications([containerId])
You will need to do this each time the logged-in user changes.
This method takes an optional parameter - notificationsEnabled
. This parameter updates the user's notificationsEnabled
preference in the Atomic Platform. You can also inspect and update this preference using the Atomic API documentation for user preferences or using the updateUser method.
If you pass false
for this parameter, the user's notificationsEnabled
preference will be set to false
, which means that they will not receive notifications on any eligible devices, even if their device is registered in this step, and the device push token is passed to Atomic in the next step. If you pass true
, the user's notificationEnabled
preference will be set to true
, which is the default, and allows the user to receive notifications on any eligible device. This allows you to explicitly enable or disable notifications for the current user, via UI in your own app - such as a notification settings screen.
If you call registerStreamContainersForNotifications
, without supplying the notificationsEnabled
parameter, the user's notificationsEnabled
preference in the Atomic Platform is not affected.
AtomicSDK.registerStreamContainersForNotifications([containerId], true)
3. Send the push token to the Atomic Platform
Send the device's push token to the Atomic Platform when it changes.
When using the cordova-plugin-push plugin the device token can be accessed as data.registrationId
on the registration
callback.
Pass this token as a string to the registerDeviceForNotifications
method, for example:
AtomicSDK.registerDeviceForNotifications(deviceToken)
You can call the registerDeviceForNotifications
method any time you want to update the push token stored for the user in the Atomic Platform. We recommend calling it each time the app is launched to ensure the token is always up-to-date.
You will also need to update this token every time the logged-in user changes in your app, so the Atomic Platform knows who to send notifications to.
4. Deregister the device from notifications
To deregister the device for Atomic notifications for your app, such as when a user completely logs out of your app, call deregisterDeviceForNotifications
on AtomicSDK
.
Calling AtomicSDK.logout()
will automatically invoke this method.
If the deregistration fails, the promise will be rejected, returning a description of the failure that occurred.
try {
await AtomicSDK.deregisterDeviceForNotifications()
} catch (e) {
// `e` contains the reason for the failure to deregister the device for notifications.
}
5. (Optional) Parse the Atomic data from the push notification
Every push notification from the Atomic platform includes an object with the key atomic
. This object includes:
- information about the card that generated the push notification
- the stream container ID of that card
- (optional) extra detail in a custom object that is included in the notification.
You can check if a push notification originated from the platform and parse the data into a structured object using the AtomicSDK.notificationFromPushPayload
method.
The method will either return a structured AACPushNotification
object if the notification came from the Atomic platform, or null if it came from another source.
The AACPushNotification object has three keys:
cardInstanceId: string
The instance ID of the card that the push notification relates to. Can be used to apply a filter on a stream container.containerId: string
The ID of the stream container that generated the notification.detail: object
A custom object defined asnotificationDetail
and sent as part of the flow that generated the card.
Here is an example of using this method with the cordova-plugin-push plugin:
push.on('notification', data => {
const notificationData = AtomicSDK.notificationFromPushPayload(
data.additionalData
)
console.log(`Notification data from atomic is: ${notificationData}`)
})
6. (Optional) Track when push notifications are received
Due to the limitations of the mobile platforms, tracking when a push notification is received is not straightforward if the app is not open when the notification is received.
On iOS, to track when push notifications are delivered to your user's device you must use a native Notification Service Extension. This requires you to write native code and to use our iOS native SDK - further information on tracking notification receipt is available in the iOS SDK guide.
On Android tracking if the notification is received in the background is not supported by the Atomic SDK, but delivery can be tracked using Firebase.
If you do not wish to build a native notification service extension, or you wish to keep analytics comparable between iOS and Android, you can choose instead to track notification delivery when a user taps on the notification. On both iOS and Android this will automatically open your app.
If you are using the cordova-plugin-push plugin, a user tapping on the notification will trigger the notification
callback.
At this point you can call the trackPushNotificationReceived
method, which will return true if the notification is from the Atomic platform, and false otherwise.
This will send the 'notification-received' analytics event to the platform.
Notifications received while the app is in the foreground will also trigger this callback, but by default they will not display a banner. You may wish to filter out these notifications from tracking, by checking if the additionalData.foreground
attribute is set to true and not calling the trackPushNotification
received method in these cases. This will ensure that the notification-received
analytics events correspond directly to a user tapping on a push notification.
Here is an example of using this method with the cordova-plugin-push plugin:
push.on('notification', data => {
console.log('onNotification', data)
const tracked = AtomicSDK.trackPushNotificationReceived(data.additionalData)
console.log(`Push notification was tracked: ${tracked}`)
})
Retrieving the count of active and unseen cards
All cards are unseen the moment they are sent. A card becomes "seen" when it has been shown on the customer's screen (even if only briefly or partly). A quick scroll-through might not make the card "seen", this depends on the scrolling speed. The user metrics only count "active" cards, which means that snoozed and embargoed cards will not be included in the count.
The Atomic Web SDK exposes an object known as user metrics. These metrics include:
- The number of cards available to the user across all stream containers;
- The number of cards that haven't been seen across all stream containers (these are sometimes referred to as unread cards);
- The number of cards available to the user in a specific stream container (equivalent to the card count functionality in the previous section);
- The number of cards not yet seen by the user (unread cards) in a specific stream container.
These metrics allow you to display badges in your UI that indicate how many cards are available to the user but not yet viewed, or the total number of cards in a given stream container.
AtomicSDK.requestUserMetrics()
.then(metrics => {
console.log('User metrics returned:', {
totalCards: metrics.totalCards(),
unseenCards: metrics.unseenCards(),
totalCardsInContainer: metrics.totalCardsForStreamContainer(
'<streamContainerId>'
),
unseenCardsInContainer: metrics.unseenCardsForStreamContainer(
'<streamContainerId>'
)
})
})
.catch(err => {
console.error(err)
})
If you have a single card, horizontal or vertical stream container that is selectively displayed to the user (such as one that is hidden inside of a notification drawer) you need to inform the SDK when this stream container is "open" and viewable by the user to make sure the card count is updated correctly. See the manually controlling the open state of a stream container section for detailed instructions on how to do this.
onCardCountChanged callback
When creating a stream container, you can supply a callback that will be triggered when the card count changes in that container. This will occur when a card is dismissed, completed or the number of cards in the card list changes. The callback will be called with two parameters:
visibleCount
: total number of cards visible in the container (read and unread, excluding cards that are not visible because of a filter)totalCount
: total number of cards available in the stream (including cards that are not visible because of a filter)
The callback is specified as a configuration option to the launch
and embed
methods:
AtomicSDK.launch({
...
onCardCountChanged: (visibleCount, totalCount) => {
console.log('Card count is now', {
visibleCards: visibleCount,
totalCards: totalCount
});
}
});
Runtime variables
Runtime variables are resolved in the SDK at runtime, rather than from an event payload when the card is assembled. Runtime variables are defined in the Atomic Workbench.
The SDK will ask the host app to resolve runtime variables when a list of cards is loaded (and at least one card has a runtime variable), or when new cards arrive (and at least one card has a runtime variable).
Runtime variables are resolved by your app via the onRuntimeVariablesRequested
property, specified on the configuration object passed to the stream container. If this callback is not implemented, runtime variables will fall back to their default values, as defined in the Atomic Workbench.
This callback, when triggered by the SDK, provides you with:
- An array of objects representing the cards in the list with runtime variables. Each card object contains:
- The event name that triggered the card’s creation;
- The lifecycle identifier associated with the card;
- A
Map
representing the runtime variables on the particular card.
- A callback, that must be called by the host app, with the resolved cards, once all variables are resolved.
If a variable is not resolved, that variable will use its default value, as defined in the Atomic Workbench.
If you do not call the provided callback before the runtimeVariableResolutionTimeout
elapses (defined on the configuration object passed to the stream container), the default values for all runtime variables will be used. Calling the provided callback more than once has no effect.
runtimeVariableResolutionTimeout
defaults to 5 seconds (5000 ms) if not provided.
AtomicSDK.launch({
...
// Maximum amount of time that runtime variable resolution can take (in milliseconds)
runtimeVariableResolutionTimeout: 8000,
// Callback that resolves runtime variables
onRuntimeVariablesRequested: (cards, callback) => {
const date = new Date()
cards.forEach(function(card) {
card.runtimeVariables.set('dateShort', date.toDateString())
card.runtimeVariables.set('dateLong', date.toString())
card.runtimeVariables.set('name', 'User Name')
})
callback(cards)
}
...
});
Runtime variables can currently only be resolved to string values.
You can also manually update runtime variables at any time by calling updateVariables
on the stream container instance:
const instance = AtomicSDK.launch({...});
instance.updateVariables();
Accessibility
Accessibility attributes, such as appropriate titles and ARIA attributes, are available.
Alternate text
Alternate text is supported on all image and video components. This alternate text is supplied by you when authoring a card, and is used to describe the image or video itself. Alternate text forms part of a description that is passed to the system screen reader (when enabled).
The following description is used for each image or video format:
Format | Text supplied to screen reader |
---|---|
Banner | Alternate text |
Inline | Alternate text |
Text | Label is used (also defined in the Workbench), as this component does not render an image thumbnail |
Thumbnail | Alternate text, label and description |
Focus rings
The Atomic Web SDK allows you to customize the focus ring used to highlight components when a user tabs through a stream container or single card view. The focus ring is rendered as a solid outline around the active component, and only appears when the user tabs between components using their keyboard.
To customize the focus ring:
- Open the Atomic Workbench.
- Navigate to the Theme Editor (Configuration > SDK > Container themes).
- Select a theme and then choose Accessibility > Focus ring.
The color of this focus border is customized using the colors.border.focus
property in your theme.
There are two options you can customize for the focus ring color:
Feed
: refers to the screen displaying the card list or single card;Subview
: all other screens in the SDK.
SDK Analytics
If you use runtime variables on a card, you can optionally choose to send the resolved values of any runtime variables back to the Atomic Platform as an analytics event. This per-card analytics event - runtime-vars-updated
- contains the values of runtime variables rendered in the card and seen by the end user. Therefore, you should not enable this feature if your runtime variables contain sensitive data that you do not wish to store on the Atomic Platform.
To enable this feature, set the features.runtimeVariableAnalytics
flag on your configuration object to true
:
AtomicSDK.launch({
...
features: {
runtimeVariableAnalytics: true
}
...
})
Updating user data
The SDK allows you to update profile & preference data for the logged in user via the updateUser
method.
This method accepts one parameter, a user update object, representing the profile & preference details that you wish to update for the logged in user. This is the user as identified by the auth token provided by the authentication callback.
The user update object has two optional properties, profile
& preferences
, each of which has a number of optional properties that may be updated for the user.
Profile fields
The following optional profile fields can be supplied to update the data for the user. Any fields which have not been supplied will remain unmodified after the user update. Further custom fields can be supplied, however these must first be defined in the workbench as described in the Custom fields section of the documentation.
external_id
: An optional string that can be used as an identifier for the user.name
: An optional string.phone
: An optional string.city
: An optional string.country
: An optional string.region
: An optional string.
Preference fields
The following optional preference fields can be supplied to update the data for the user. Again, any fields which have not been supplied will remain unmodified after the user update.
-
notificationsEnabled
: An optional boolean to determine whether notifications are enabled. -
notifications
: An optional object that defines the notification time preferences of the user for different days of the week, as well as a default value that will take precedence if a day does not have notification preferences defined for it. Only the following keys:mon
,tue
,wed
,thu
,fri
,sat
,sun
,default
, will be accepted for this object. If any other value is provided the SDK will display a warning and the notification object will be skipped when updating the user data.Each day accepts an array of notification time periods, these are periods during which notifications are allowed. If an empty array is provided notifications will be disabled for that day. The following example would enable notifications between 8am - 5:30pm & 7pm - 10pm:
[{
from: {hours: 8, minutes: 0},
to: {hours: 17: minutes: 30}
}, {
from: {hours: 19, minutes: 0},
to: {hours: 22, minutes: 0}
}]hours
a 24h value that must be between 0 & 23 inclusive,minutes
a value between 0 & 59 inclusive.
An example of a user update submitted using the updateUser
method:
const userUpdateObject = {
profile: {
// profile fields to be updated:
external_id: '123',
name: 'User Name',
email: 'email@example.com',
phone: '(+64)210001234',
city: 'Wellington',
country: 'New Zealand',
region: 'Wellington',
// any further custom fields that have already been defined in the Workbench:
myCustomField: 'My custom value'
},
preferences: {
// preference fields to be updated
notificationsEnabled: true,
notifications: {
default: [{from: {hours: 8, minutes: 0}, to: {hours: 17, minutes: 45}}],
sat: [],
sun: []
}
}
}
AtomicSDK.updateUser(userUpdateObject)
Custom icons
(introduced in 24.2.0)
This is currently a beta feature in the Atomic Workbench.
See the card element reference for supported SVG features in custom icons.
The SDK now supports the use of custom icons in card elements. When you are editing a card in the card template editor of the Workbench you will notice that for card elements that support it the properties panel will show an "Include icon" option. From this location you can select an icon to use, either from the Media Library or Font Awesome.
Choosing to use an icon from the Media Library you have the ability to provide an SVG format icon and an optional fallback icon to be used in case the SVG fails to load. The "Select icon" dropdown will present any SVG format assets in your media library which can be used as a custom icon. To add an icon for use you can press the "Open Media Library" button at the bottom of the dropdown.
Custom icon colors
The Workbench theme editor now provides the ability to set a color & opacity value for icons in each of the places where an icon may be used. The SDK will apply the following rules when determining what color the provided icon should be displayed in:
- All icons will be displayed with the colors as dictated in the SVG file.
- Black is used if no colors are specified in the SVG file.
- Where a
currentColor
value is used in the SVG file, the following hierarchy is applied:
- Use the icon theme color if this has been supplied.
- Use the color of the text associated with the icon.
- Use a default black color.
Custom icon sizing
The custom icon will be rendered in a square icon container with a width & height in pixels equal to the font size of the associated text. Your supplied SVG icon will be rendered centered inside this icon container, at its true size until it is constrained by the size of container, at which point it will scale down to fit.
Fallback Rules
If the provided SVG image fails to load due to a broken URL or network issues the following fallback rules apply:
- The fallback FontAwesome icon is used if it is set in Atomic Workbench for this custom icon.
- Otherwise, a default placeholder image is displayed.
Multiple display heights
(introduced in 24.2.0)
In the Atomic Workbench you can now specify the display height for banner and inline media components. The height of inline image and video elements, along with banner image and video elements, may be specified as one of four different display heights. These are;
- "Tall" (200px) the image or video is 200 pixels high, spanning the whole width and cropped as necessary. This is the default value and matches existing image and video components.
- "Medium" (120px) the same as tall, but only 120 pixels high.
- "Short" (50px) the same as tall but 50 pixels high. Not supported for inline or banner videos.
- "Original" the image will maintain its original aspect ratio, adjusting the height dynamically based on the width of the card to avoid any cropping.
When using these new layouts be mindful that customers using older versions of the SDK will default to the "Tall" layout.
File Upload
This is currently a preview feature in the Workbench
Card & feed overlay during file upload
The file upload process will commence when a user attempts to submit a card that has files attached. While the file upload is in progress the card (or card subview when submitting from a subview) will be replaced with an overlay to prevent user interaction while this takes place. This overlay displays a loading indicator, a message indicating what is taking place, and a button that may be used to cancel the file upload.
If the card is within a launcher, vertical or horizontal stream container variant, any other cards within the feed will be covered by a scrim while the upload is in progress, to prevent user interaction with other cards in the feed and visually indicate that the upload has not finished.
Single card & Horizontal stream container overlay
The modal iframe, used to present subviews in the single card & horizontal stream containers, has an 'uploading-files' class added to the iframe element while there is a file upload in progress. This can be used to trigger any UI you may wish to display in your application while the file upload is in progress.
The following code snippet is an example of how you could detect this:
// An observer to observe changes in the modal subview iframe class list
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.target.classList.contains('uploading-files')) {
// Uploading has commenced, modify UI as required
} else {
// Uploading has finished, modify UI as required
}
})
})
// Observe changes in the relevant modal subview, in this case one that
// has been embedded inside of a host element with the ID "embed-container".
observer.observe(document.querySelector('#embed-container>.modal-subview'), {
attributeFilter: ['class']
})
Custom strings
The card overlay message & cancel button label text can be customized using the custom strings feature of the SDK. If you wish to change the text used you would supply a value for the processingStateMessage
and/or the processingStateCancelButtonTitle
for the stream container(s) that you wanted to customize this for.
Toast message
If a file upload fails to successfully complete the user will be displayed a toast message indicating this and providing the option to retry. By default the text of this message will be "Couldn't upload file(s)", if you wish to customize this you may do so via the toast config configuration object for the stream container, setting a value for the fileUploadFailed
toast message.
SDK events
There are new events that are emitted during the file upload process. When a user attempts to submit a card that has files attached, the file upload process will commence and these events can be used to track its progression:
user-file-uploads-started
, emitted once the file upload process has started.user-file-uploads-completed
, emitted once the file upload process has completed, with all files having been successfully uploaded.user-file-uploads-failed
, emitted once the file upload process has completed or been cancelled, with one or more files having failed to upload.
These events can be observed using the SDK event observer. The details for the relevant files are found in the properties
of the event object, keyed by the form field they were submitted, for example:
{
eventName: "user-file-uploads-completed",
...
properties: {
upload_fieldname: {
filename: "filename_in_S3_bucket.png",
targetBucketId: "S3_bucket_id"
}
}
}
Currently, no event is generated to indicate when a user cancels an upload. We will add a corresponding SDK event in upcoming versions.
Utility methods
Debug logging
Enable debug logging to view verbose logs of activity within the SDK, output to the browser console. Debug logging is disabled by default, and should not be enabled in release builds.
AtomicSDK.enableDebugMode(level)
The level parameter is a number that indicates the verbosity of the logs exposed:
- Level 0: Default, no logs exposed
- Level 1: Operations and transactions are exposed
- Level 2: Operations, transactions and its details are exposed, plus level 1.
- Level 3: Internal developer logs, plus level 2
Error handling
The SDK allows you to set a callback via the AtomicSDK.onError
property. This passes javascript errors that may occur in the SDK.
AtomicSDK.onError = function (error) {
// handle the reported error as required
console.error('An error occurred in the Atomic SDK: ', error)
}
The callback you set will receive errors that occur within the stream containers you have created. If you do not set this callback, errors will instead be logged to the browser console.
Errors resulting from communication between the SDK & the Atomic platform, such as authentication & network errors, will be logged via the debug logger at the level of verbosity you have chosen. These logs can be observed in the browser console and will be a string consisting of the error name and error description.
Setting the version of the client app
The SDK provides a method AtomicSDK.setClientAppVersion
for setting the current version of your app. This app version is attached to generated analytics events to make it easy to track issues between app versions and to get insight into version usage.
Version strings have a maximum length of 128 characters, if you supply a longer string it will be trimmed to that length.
The following example sets the client app version to Version 2.3.1
.
AtomicSDK.setClientAppVersion('Version 2.3.1')
Third-party dependencies
The Atomic Web SDK (as at the current release 24.3.0
) uses the following third-party dependencies:
- Font Awesome Free
5.13.1
- snarkdown
1.2.1
- date-fns
1.30.1
- core-js
2.6.11
- focus-visible
5.2.0
- preact
10.4.1
- redux-zero
5.1.4
- picostyle
2.2.0
- external-svg-loader
1.7.1