SDK Usage Guide
Automatic Updates
The simplest approach. Updates are checked and installed automatically:
import { initNorrix } from '@norrix/client-sdk';
initNorrix({
updateUrl: 'https://norrix.net',
checkForUpdatesOnLaunch: true,
installUpdatesAutomatically: true,
});With this configuration:
- App launches and checks for updates
- If compatible update available, it’s downloaded
- Update is installed silently
- Update activates on next app restart
With Restart Prompt
Prompt users to restart after an update:
initNorrix({
updateUrl: 'https://norrix.net',
checkForUpdatesOnLaunch: true,
installUpdatesAutomatically: true,
promptToRestartAfterInstall: true, // Shows restart dialog
});Manual Update Control
For full control over the update flow:
import { initNorrix } from '@norrix/client-sdk';
// Initialize without auto-install
const norrix = initNorrix({
updateUrl: 'https://norrix.net',
});
// Check for updates manually
async function checkAndApplyUpdate() {
const update = await norrix.checkForUpdates();
if (!update.updateAvailable) {
console.log('App is up to date');
return;
}
if (update.requiresStoreUpdate) {
// Native changes detected - direct user to app store
showStoreUpdatePrompt(update.compatibilityReason);
return;
}
// Show update prompt to user
const shouldUpdate = await showUpdateDialog({
version: update.version,
releaseNotes: update.releaseNotes,
});
if (!shouldUpdate) {
return; // User declined
}
// Download the update
const success = await norrix.downloadUpdate(update);
if (success) {
// Apply the update
norrix.applyUpdate();
}
}Status Monitoring
Track update progress with callbacks:
import { initNorrix, SyncStatus } from '@norrix/client-sdk';
initNorrix({
updateUrl: 'https://norrix.net',
checkForUpdatesOnLaunch: true,
installUpdatesAutomatically: true,
statusCallback: (status, data) => {
switch (status) {
case SyncStatus.CHECKING_FOR_UPDATE:
showSpinner('Checking for updates...');
break;
case SyncStatus.DOWNLOADING_PACKAGE:
showSpinner('Downloading update...');
break;
case SyncStatus.INSTALLING_UPDATE:
showSpinner('Installing update...');
break;
case SyncStatus.UPDATE_INSTALLED:
hideSpinner();
showToast(`Update ${data?.version} ready!`);
break;
case SyncStatus.UP_TO_DATE:
hideSpinner();
break;
case SyncStatus.ERROR:
hideSpinner();
showError(data?.message);
break;
}
},
downloadProgressCallback: (progress) => {
updateProgressBar(progress);
},
});What Can Be Updated
✅ OTA-Compatible Changes
These changes can be pushed via OTA:
- JavaScript/TypeScript code: Business logic, API calls, utilities
- CSS/SCSS styles: Colors, layouts, animations
- Assets: Images, fonts, icons (in
/srcor/app) - Non-native npm packages: Pure JS/TS packages
❌ Requires Store Build
These changes require a new store binary:
- NativeScript platform versions:
@nativescript/ios,@nativescript/android - Native plugin updates: Plugins with native code
- App_Resources changes: Icons, splash screens, Info.plist
- Custom native code: NativeSource files
Reset OTA Updates
To reset all OTA updates and return to the original store binary:
const norrix = initNorrix({
updateUrl: 'https://norrix.net',
});
// Clear all OTA updates
norrix.resetAllOTAs();This:
- Resets the OTA state file, including the quarantine list
- Removes every installed OTA package from disk
- Clears the mirrored version keys in
ApplicationSettings - Prompts the user to restart, after which the app runs the store binary
Use Cases
- Debugging OTA issues on a single device
- Reverting a problematic update for testing
- Developer testing
To roll back all users to an older codebase, publish a new OTA update with a bumped build number instead. See Rolling Back an OTA Update.
Launch Safety and Rollback
An update is rolled back only when a failure was actually observed. No code is required: importing @norrix/client-sdk at the top of your app entry arms the launch monitor before any app code runs.
What counts as an outcome
| Observation | Effect |
|---|---|
First content displayed (displayed, or a window’s contentLoaded on the 9.1 multi-window lifecycle) | Launch succeeds. The update is trusted from then on |
| Uncaught JavaScript error before first display | Launch fails |
Bootstrap that never reaches UIApplicationMain (iOS), or an uncaught native exception (NSException, Java) | Launch fails |
| Anything else: force quit, watchdog kill, low-memory kill, a background launch, an iOS prewarm | Nothing is recorded. The next launch runs the same update again |
An update is launchable when it has succeeded at least once, or has never failed. An error after the app has displayed content is an application bug, not a delivery failure, and does not roll anything back.
What happens on a failed launch
- The update is quarantined for the current store binary: it is never launched again, and never installed again.
statusCallbackreceivesSyncStatus.ROLLED_BACKwith the failed update id and the error.- The SDK waits briefly for a newer update, then relaunches in-process into the newest launchable bundle — the previous known-good update if there is one, otherwise the store bundle.
- Later checks send the quarantined ids as
excludeIds, so the server does not offer the update again.retryFailedUpdates: trueopts out of that.
All four steps hold even if the failure happens before initNorrix() runs, because the monitor is armed by the import: without an initialized SDK the relaunch still happens, only the telemetry for it and the ROLLED_BACK status callback are skipped. A bundle that throws before the import itself is evaluated is caught natively on iOS, so the next launch picks the last known-good bundle.
Two runtime limitations to know about on @nativescript/ios 9.1.0:
- An error thrown inside a handler that UIKit invokes directly, such as
Application.on('launch', ...)or a view’sloadedhandler, is not reported by the runtime under the defaultuncaughtErrorPolicy. The SDK does not count that launch as successful (no root view exists), but it cannot roll it back either; ship the fix as a new update. NativeScriptRuntime.reloadApplicationis not exposed on that release (a 9.1.x patch adds it), so soft reboots and recovery relaunch through a process restart instead of in place. The outcome is the same; only the restart page is visible.
Quarantine is cleared when the app is updated from the store. To inspect it on a device:
norrix.getQuarantinedUpdateIds(); // ['update-1788917268839']
norrix.isUpdateQuarantined('update-1788917268839'); // trueWhich Version Is Running
getRunningUpdate() reports what the current process is executing, as decided by the native loader at launch:
const running = norrix.getRunningUpdate();
// {
// source: 'update' | 'embedded',
// updateId?: string,
// version: string,
// buildNumber: string,
// confirmed: boolean // this process reached first display
// }
const label = `${running.version} (${running.buildNumber})`;getInstalledUpdate() reports what the next launch will run, or null when that is the store bundle:
const next = norrix.getInstalledUpdate();
if (next && next.id !== norrix.getRunningUpdate().updateId) {
showRestartBanner(`Version ${next.version} is ready`);
}Build version labels from
getRunningUpdate(). TheNorrix_OTA_VersionandNorrix_OTA_BuildNumberkeys inApplicationSettingsdescribe the installed update, not the running one — they show a new build number before it has run, and after it has been rolled back. They are still written for backward compatibility only.
Fingerprint Access
Get the current native binary fingerprint:
const norrix = initNorrix({
updateUrl: 'https://norrix.net',
});
const hash = norrix.getCurrentFingerprintHash();
console.log('Binary fingerprint:', hash);This returns the fingerprint hash embedded in the native binary:
- iOS:
NorrixFingerprintHashinInfo.plist - Android:
norrixFingerprintHashinBuildConfig
The value is baked in at build time and cannot be modified by OTA updates, making it the single source of truth for native compatibility checks.
Force Close App
Utility to close the app (useful for manual restart handling):
norrix.closeAppNow();This calls:
- iOS:
exit(0) - Android:
java.lang.System.exit(0)
Use with caution. Only call after user confirmation.
HMR Compatibility
When Hot Module Replacement (HMR) is enabled during development, the SDK skips OTA checks to avoid conflicts:
statusCallback: (status) => {
if (status === SyncStatus.SKIPPING_BECAUSE_HMR_ENABLED) {
console.log('OTA skipped - HMR is active');
}
};This is automatic. No configuration needed.
Error Handling
Handle update errors gracefully:
initNorrix({
updateUrl: 'https://norrix.net',
checkForUpdatesOnLaunch: true,
installUpdatesAutomatically: true,
statusCallback: (status, data) => {
if (status === SyncStatus.ERROR) {
// Log error for debugging
console.error('OTA Error:', data?.message);
// Optionally notify error tracking
Sentry.captureException(new Error(data?.message));
// App continues normally - OTA is non-blocking
}
},
});OTA errors are non-fatal. The app continues with its current version.