@basetime/a2w-webview-ts
v0.2.4
Published
SDK for webview apps running inside the Addtowallet scanner.
Keywords
Readme
a2w-webview-ts
SDK for webview apps running inside the Addtowallet scanner.
See the standard example in examples/standard. See the SPA example in examples/spa.
Installation
npm install @basetime/a2w-webview-tsUsage
import { WebApp } from '@basetime/a2w-webview-ts';
const webApp = new WebApp();
// Check if the app is embedded in the atw scanner webview.
if (!webApp.isEmbedded) {
throw new Error('This app is not embedded in the atw scanner webview.');
}
// Listen for scan events from the scanner.
webApp.on('scan', ({ payload }) => {
console.log(payload);
const isApple = payload.device.model.toLowerCase().includes('iphone');
console.log(`Using ${isApple ? 'iPhone' : 'Android'}`);
// Check the password if one as set in the Addtowallet app.
if (payload.password !== '123434) {
throw new Error('Invalid password.');
}
// Notify the scanner that the webview is ready. (Not currently used.)
webApp.send('ready');
// Wait 5 seconds before navigating to the standby screen.
setTimeout(() => {
webApp.send('navigate', { url: '/' });
}, 5000);
});
// Triggered when the scanner is on the home screen.
webApp.on('standby', ({ payload }) => {
console.log(payload);
console.log('The scanner is in standby mode.');
// Override these settings in the scanner app.
// They remain overridden until the `force` flag is
// used by the backend.
webApp.send('settings', {
pin: '1234',
webviewStandbyUrl: 'https://example.com/standby',
});
});
// Triggered when an error is encountered in the scanner. For example,
webApp.on('error', ({ payload }) => {
console.log(payload.errorCode);
console.log(payload.errorMessage);
});Alternatively, you import the WebApp class directly from the CDN:
<script type="module">
import WebApp from 'https://cdn.addtowallet.io/js/webview/v0.2.3/WebApp.js';
const webApp = new WebApp();
// Listen for scan events from the scanner.
webApp.on('scan', ({ payload }) => {
console.log(payload);
});
// Listen for standby events from the scanner.
webApp.on('standby', ({ payload }) => {
console.log('The scanner is in standby mode.');
});
// Listen for error events from the scanner.
webApp.on('error', ({ payload }) => {
console.log('There has been an error.');
});
</script>React
If your embedded app is built with React, the SDK ships a small set of
hooks under the optional /react subpath. React is declared as an
optional peer dependency, so non-React consumers don't pay any bundle
cost and don't need to install it.
npm install @basetime/a2w-webview-ts reactimport { useEvent, useWebApp } from '@basetime/a2w-webview-ts/react';
export function ScanScreen() {
useEvent('scan', ({ payload }) => {
if (!payload.found) {
return;
}
console.log('Scanned pass:', payload.pass);
});
useEvent('standby', () => {
console.log('Scanner is idle');
});
// `useWebApp` is only needed if you want to call `send`, check
// `isEmbedded`, or otherwise access the instance directly.
const webApp = useWebApp();
if (!webApp.isEmbedded) {
return <p>Open this app inside the atw scanner.</p>;
}
return <p>Waiting for a scan…</p>;
}Using the wildcard listener, we can listen to all events and navigate to the appropriate screen based on the event.
import React, { useState } from 'react';
import { useEvent, useWebApp } from '@basetime/a2w-webview-ts/react';
import { ScanScreen, StandbyScreen, ErrorScreen } from './screens';
export function App() {
const [page, setPage] = useState<'scan' | 'standby' | 'error'>('scan');
useEvent('*', ({ action, payload }) => {
if (action === 'scan') {
setPage('scan');
} else if (action === 'standby') {
setPage('standby');
} else if (action === 'error') {
setPage('error');
}
});
if (page === 'scan') {
return <ScanScreen />;
} else if (page === 'standby') {
return <StandbyScreen />;
} else if (page === 'error') {
return <ErrorScreen />;
}
return <p>Waiting for a scan…</p>;
}Available hooks:
useEvent(event, callback)subscribes to an event for the lifetime of the component. The callback is captured in a ref, so passing an inline arrow function does not cause the listener to re-subscribe on every render.useWebApp()returns a stableWebAppinstance for cases where you need to callsend, inspectisEmbedded, or otherwise interact with the scanner imperatively.
Events
The scanner communicates with your embedded app through a small set of events.
Inbound events (scan, standby, error) are dispatched by the scanner and
consumed via webApp.on(...). Outbound events (navigate, settings) are
sent from your app back to the scanner via webApp.send(...).
All inbound event callbacks receive a message object of the shape
{ action, payload }, where action is the event name and payload is the
event-specific data described below.
Wildcard listener
Pass '*' as the event name to subscribe to every built-in event in a single
call. The callback fires once per event with the actual action (e.g.
'scan'), so it's a convenient way to log, debug, or proxy all scanner
traffic without registering a handler per event:
const off = webApp.on('*', ({ action, payload }) => {
console.log('scanner event:', action, payload);
});
// Later, to unsubscribe:
off();
// or equivalently:
webApp.off('*', callback);The wildcard covers the SDK's built-in AppEvents keys (scan, standby,
error, navigate, ready, settings); custom event names added via a
typed WebApp<E> are not included.
The same wildcard works in the React hook:
import { useEvent } from '@basetime/a2w-webview-ts/react';
useEvent('*', ({ action, payload }) => {
console.log('scanner event:', action, payload);
});scan
Triggered by the scanner each time it processes a pass scan, regardless of
whether the pass was found. Use this event to validate the pass, run any
business logic against the campaign, and optionally drive the scanner to a
new screen using the navigate event.
webApp.on('scan', ({ payload }) => {
if (!payload.found) {
console.warn('Pass not recognized');
return;
}
console.log('Scanned pass:', payload.pass);
});payload (ScanPayload) properties:
scanner(string): The ID of the scanner that produced the event.location(string): The device location as"latitude,longitude".found(boolean): Whether the scanned pass was found in the system.pass(Pass): The full pass object, including its associated campaign.tags(string[]): The tags associated with the scanner.password(string): The password configured in the Addtowallet app, if any. Useful for authenticating the request inside your handler.settings(Record<string, any>): Additional scanner settings.webviewHeight(number): The height of the scanner webview in pixels.webviewWidth(number): The width of the scanner webview in pixels.device(ScannerDeviceInfo): Information about the device:manufacturer(string | null): e.g."Apple","Google","xiaomi".model(string | null): e.g."iPhone XS Max","Pixel 2".osVersion(string | null): e.g."12.3.1","11.0".deviceName(string | null): The user-assigned device name, e.g."Vivian's iPhone XS". May benullif unavailable.
standby
Triggered when the scanner is sitting on its home / standby screen. This is a
good place to push per-device configuration overrides via the settings
event, or to render an idle UI in your embedded webview.
webApp.on('standby', ({ payload }) => {
console.log('Scanner', payload.scanner, 'is idle');
});payload (StandbyPayload) properties:
scanner(string): The ID of the scanner.location(string): The device location as"latitude,longitude".password(string): The password configured in the Addtowallet app, if any.settings(Record<string, any>): Additional scanner settings.webviewHeight(number): The height of the scanner webview in pixels.webviewWidth(number): The width of the scanner webview in pixels.device(ScannerDeviceInfo): Device information (seescanabove).
error
Triggered whenever the scanner encounters a recoverable error, such as a missing pass or campaign. Use this event to surface a user-friendly error state inside your webview.
webApp.on('error', ({ payload }) => {
console.error(`[${payload.errorCode}] ${payload.errorMessage}`);
});payload (ErrorPayload) properties:
scanner(string): The ID of the scanner.location(string): The device location as"latitude,longitude".password(string): The password configured in the Addtowallet app, if any.settings(Record<string, any>): Additional scanner settings.webviewHeight(number): The height of the scanner webview in pixels.webviewWidth(number): The width of the scanner webview in pixels.device(ScannerDeviceInfo): Device information (seescanabove).errorCode(number): A numeric representation of the error, e.g.404.errorMessage(string): A human-readable description of the error, e.g."Campaign not found".
navigate
Sent from your app to the scanner to instruct it to navigate one of its
webviews to a new URL. Commonly used after handling a scan event to return
the scanner to its standby screen or to advance to a custom flow.
webApp.send('navigate', { url: '/' });payload (NavigatePayload) properties:
url(string): The URL (or path) the scanner should navigate to.
ready
Sent from your app to the scanner to notify it that the webview is ready.
webApp.send('ready');settings
Sent from your app to override scanner settings at runtime. Overrides
provided this way persist on the device until the backend pushes a new
settings payload with the force flag. Only the fields you provide are
updated; everything else is left untouched.
webApp.send('settings', {
pin: '1234',
webviewStandbyUrl: 'https://example.com/standby',
});payload is a Partial<Settings> object. Supported fields:
baseUrl(string): The base URL of the API.pin(string): The PIN that unlocks the scanner's settings screen.brandColor(string): The brand color used by the scanner UI.brandLogoUrl(string): The URL of the brand logo.tags(string[]): The tags associated with the scanner.webviewSpaUrl(string): The URL displayed in the SPA webview.webviewScanUrl(string): The URL displayed in the scan webview.webviewStandbyUrl(string): The URL displayed in the standby webview.webviewErrorUrl(string): The URL displayed in the error webview.webviewPassword(string): The password required to access the webview.isKioskMode(boolean): Whether to hide the scan button on the home screen.debugWebviews(boolean): Whether to enable debug mode for webviews.logLevel('debug' | 'info' | 'error'): Minimum log severity.debuglogs everything;infologs info and errors;errorlogs only errors.additionalSettings(Record<string, any>): Arbitrary additional settings.
