Skip to Content
Client SDKUsage Guide

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:

  1. App launches and checks for updates
  2. If compatible update available, it’s downloaded
  3. Update is installed silently
  4. 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 /src or /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:

  1. Resets the OTA state file, including the quarantine list
  2. Removes every installed OTA package from disk
  3. Clears the mirrored version keys in ApplicationSettings
  4. 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

ObservationEffect
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 displayLaunch 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 prewarmNothing 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

  1. The update is quarantined for the current store binary: it is never launched again, and never installed again.
  2. statusCallback receives SyncStatus.ROLLED_BACK with the failed update id and the error.
  3. 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.
  4. Later checks send the quarantined ids as excludeIds, so the server does not offer the update again. retryFailedUpdates: true opts 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’s loaded handler, is not reported by the runtime under the default uncaughtErrorPolicy. 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.reloadApplication is 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'); // true

Which 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(). The Norrix_OTA_Version and Norrix_OTA_BuildNumber keys in ApplicationSettings describe 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: NorrixFingerprintHash in Info.plist
  • Android: norrixFingerprintHash in BuildConfig

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.