The official JavaScript SDK for building interactive presentations and custom features on the Mobile Locker platform.
This SDK gives IVA developers programmatic access to the Mobile Locker platform from within a presentation — including user data, CRM records, analytics, device capabilities, storage, and more.
The SDK works in two environments:
| Environment | Description |
|---|---|
| iOS/iPadOS | Running inside the Mobile Locker iOS app (isIOS() === true) |
| Electron | Running inside the Mobile Locker Windows app (isElectron() === true) |
| CDN | Loaded as part of a CDN-hosted presentation (isCDN() === true) |
When running outside of either environment (e.g. local development), most SDK calls are silently no-ops or return sensible local fallbacks. No errors are thrown — so you can develop locally without a live Mobile Locker context.
npm install @mobilelocker/javascript-sdk
# or
yarn add @mobilelocker/javascript-sdk
The SDK initializes automatically — there is no init() call required. It reads authentication and configuration from the ?jwt= query parameter that Mobile Locker injects into every presentation URL at runtime.
import mobilelocker from '@mobilelocker/javascript-sdk'
// The SDK is ready to use immediately after import
const user = await mobilelocker.user.get()
console.log(`Hello, ${user.name}`)
Use these helpers to branch behavior based on where your code is running:
import mobilelocker from '@mobilelocker/javascript-sdk'
mobilelocker.isMobileLocker() // true in any app or CDN context
mobilelocker.isApp() // true in the iOS app or Electron (Windows) app
mobilelocker.isIOS() // true specifically in the iOS or iPadOS app
mobilelocker.isElectron() // true in the Electron (Windows) app
mobilelocker.isCDN() // true when served from a CDN presentation URL
The SDK is organized into domains. All methods are async unless noted.
analyticsTrack custom events within a presentation.
mobilelocker.analytics.logEvent(category, action, uri, data)
mobilelocker.analytics.trackPageView(uri)
congressesAccess lead retrieval events and attendees (badge/card scanning).
const events = await mobilelocker.congresses.list()
const attendees = await mobilelocker.congresses.getAttendees(eventID)
const businessCards = await mobilelocker.congresses.getBusinessCards(eventID)
await mobilelocker.congresses.submitLead(eventID, attendeeID, data)
contactsRead the current user's contacts.
const contacts = await mobilelocker.contacts.getAll()
const chunk = await mobilelocker.contacts.getChunked(minID, limit)
crmInteract with the connected CRM (Salesforce, etc.).
const accounts = await mobilelocker.crm.getAccounts()
await mobilelocker.crm.refresh('incremental') // or 'full'
const results = await mobilelocker.crm.query('SELECT Id, Name FROM Account')
dataSubmit form/data capture events.
// Synchronous
mobilelocker.data.submitForm('lead-form', {firstName: 'Jane', email: 'jane@example.com'})
databaseQuery SQLite databases bundled with the current presentation.
const databases = await mobilelocker.database.list()
// → ['products.sqlite', 'search/fts.db']
const result = await mobilelocker.database.query(
'products.sqlite',
'SELECT * FROM products WHERE category = ?',
['widgets'],
)
// result.rows — array of row objects
// result.rows_affected — integer (always 0 for SELECT)
// result.last_insert_row_id — null for SELECT
Named parameters are also supported:
const result = await mobilelocker.database.query(
'products.sqlite',
'SELECT * FROM products WHERE category = :category AND approved = :approved',
{ category: 'oncology', approved: 1 },
)
Inspect a table's shape (useful during development):
const description = await mobilelocker.database.describe('products.sqlite', 'products')
// description.name — 'products'
// description.sql — 'CREATE TABLE products (id INTEGER PRIMARY KEY, ...)'
// description.columns — array of column info objects:
// { cid, name, type, not_null, default_value, primary_key }
deviceRead device and app metadata (iOS app only).
const info = await mobilelocker.device.getInfo()
// info.app.version, info.os.name, info.hardware.model, info.orientation, etc.
httpMake cross-origin HTTP requests. In the iOS app, requests are proxied through the native layer to avoid CORS restrictions.
const response = await mobilelocker.http.get('https://api.example.com/data')
const response = await mobilelocker.http.post('https://api.example.com/submit', {body: payload})
// response.status, response.data, response.headers
logStructured logging with levels, filtering, and SDK log access.
// Enable/disable debug mode (synchronous)
mobilelocker.log.setMode(true)
mobilelocker.log.isEnabled() // → boolean
// Write log entries from your presentation code (synchronous)
mobilelocker.log.debug('Fetching products', { category: 'oncology' })
mobilelocker.log.info('Query returned 42 rows')
mobilelocker.log.warn('Result set large — consider paginating')
mobilelocker.log.error('Query failed', { error: err.message })
// Read SDK log entries (async)
const logs = await mobilelocker.log.getSdkLogs({ level: 'error', domain: 'crm' })
const results = await mobilelocker.log.searchSdkLogs('timeout', { domain: 'database' })
// Session mode (synchronous)
mobilelocker.log.liveMode() // activate live session recording
mobilelocker.log.practiceMode() // deactivate (practice mode)
// Manage log entries (async)
await mobilelocker.log.deleteSdkLog(id)
await mobilelocker.log.clearSdkLogs()
networkCheck connectivity status.
const status = await mobilelocker.network.getStatus()
// status.connected — boolean
// status.type — 'wifi' | 'cellular' | 'wired' | 'none'
presentationAccess the current presentation and trigger lifecycle actions.
const presentation = await mobilelocker.presentation.get()
await mobilelocker.presentation.download(presentationID) // returns DownloadStatus
mobilelocker.presentation.reload()
scannerTrigger the device camera scanner (iOS app only).
const result = await mobilelocker.scanner.scanBusinessCard(eventID)
const result = await mobilelocker.scanner.scanBadge(eventID)
// result.status — 'success' | 'cancelled' | 'failed'
// result.attendee or result.businessCard on success
searchSearch across presentations, customers, contacts, attendees, and business cards.
const results = await mobilelocker.search.query('Acme', {
types: ['customers', 'presentations'],
limit: 10,
})
// results.customers.results, results.presentations.results, etc.
sessionRead events recorded during the current session.
const events = await mobilelocker.session.getDeviceEvents()
shareShare a presentation or send email.
// Synchronous
mobilelocker.share.presentation(
[{email: 'jane@example.com', name: 'Jane'}],
mobilelocker.notificationLevels.NOTIFY_FIRST,
)
mobilelocker.share.email(
{name: 'Jane', email: 'jane@example.com'},
'Thanks for stopping by',
'It was great to meet you.',
)
storagePersist arbitrary data tied to the current presentation/user context.
await mobilelocker.storage.set('scan-results', {leads: [...]})
const entry = await mobilelocker.storage.get('scan-results')
const entries = await mobilelocker.storage.getAll({name: 'scan-results'})
await mobilelocker.storage.delete('scan-results')
uiControl the presentation UI (iOS app only where noted).
mobilelocker.ui.openPDF('/files/brochure.pdf', 'Product Brochure')
const result = await mobilelocker.ui.openVideo('/files/demo.mp4', {
autoplay: true,
showControls: true,
})
// result.status — 'completed' | 'dismissed' | 'failed'
mobilelocker.ui.showToolbar() // iOS app only
mobilelocker.ui.hideToolbar() // iOS app only
userGet the currently authenticated user.
const user = await mobilelocker.user.get()
// user.id, user.name, user.email, user.current_team_id
Used with share.presentation():
mobilelocker.notificationLevels.NOTIFY_NONE // 0 — no notifications
mobilelocker.notificationLevels.NOTIFY_FIRST // 1 — notify on first open only
mobilelocker.notificationLevels.NOTIFY_EVERY // 2 — notify on every open
mobilelocker.notificationLevels.NOTIFY_WEEKLY // 3 — weekly digest
mobilelocker.notificationLevels.NOTIFY_MONTHLY // 4 — monthly digest
All SDK methods throw typed errors. Import the error classes to handle them specifically:
import mobilelocker, {
MobileLockerError,
MobileLockerCRMError,
MobileLockerDatabaseError,
MobileLockerHTTPError,
MobileLockerHttpResponseError,
GeneralErrorCode,
CRMErrorCode,
DatabaseErrorCode,
} from '@mobilelocker/javascript-sdk'
try {
const accounts = await mobilelocker.crm.getAccounts()
} catch (err) {
if (err instanceof MobileLockerCRMError) {
if (err.code === CRMErrorCode.AuthExpired) {
// prompt re-authentication
}
}
}
| Class | Code constants |
|---|---|
MobileLockerError |
GeneralErrorCode.NotConnected, ServerError, RequestTimeout |
MobileLockerCRMError |
CRMErrorCode.NotConnected, AuthExpired, SOQLInvalid, ServerError |
MobileLockerDatabaseError |
DatabaseErrorCode.NotReady, InvalidPath, WriteNotPermitted, QueryFailed |
MobileLockerHTTPError |
HTTPErrorCode.NotConnected, ServerError |
MobileLockerHttpResponseError |
.status, .statusText, .headers, .data (non-2xx HTTP responses) |
The SDK ships with full TypeScript definitions. All domain types are exported:
import type {
User,
Presentation,
Customer,
Attendee,
BusinessCard,
UserContact,
StorageEntry,
SearchResults,
DeviceInfo,
NetworkStatus,
ScanResult,
HTTPResponse,
VideoResult,
} from '@mobilelocker/javascript-sdk'
src/
domains/ — one file per SDK domain (analytics, crm, database, …)
types/ — entity types mirroring GRDB model toJSON() output
errors.ts — error classes and error code constants
index.ts — composes the mobilelocker object and re-exports everything
src/types/ — entity shapes that directly mirror a GRDB model's toJSON() output: Presentation, Customer, Attendee, User, etc. These are the data objects returned by the iOS app. A type belongs here if it represents a row from the database and could appear across multiple domains.
src/domains/<domain>.ts — parameter types, filter types, response envelopes, and status unions that are specific to a single domain's API surface: SearchOptions, SearchResults, StorageFilter, ScanStatus, DownloadStatus, etc. Keep these co-located with the functions that use them.
src/types/database.ts is the one exception — DatabaseQueryResult, DatabaseColumnInfo, and DatabaseTableDescription are domain-specific but live in types/ because there are three related types that would clutter the database domain file.
All JSON keys use snake_case — consistent with the Laravel/Spatie backend that seeds the iOS database and with the GRDB model toJSON() methods that produce the wire format.
When a GRDB model in mobilelocker-ios gains or loses a field in its toJSON(), update the corresponding type in src/types/ in the same change.
ISC © Mobile Locker