# @nordicid/nurapi-update — API Reference

> Generated: 2026-05-15 05:45:37 UTC  
> Package version: `0.9.9`  
> Source: TypeDoc

Firmware update orchestration for NUR readers and EXA accessories — ZIP package parsing, McuMgr/SMP over NUR ACC_EXT, mode switching, cascading update flow.

## Overview

Firmware update orchestration for Nordic ID NUR readers and EXA accessories — built on top of `@nordicid/nurapi` and works over any registered transport (Web Serial, Web Bluetooth, Node.js serial, TCP).

```bash
npm install @nordicid/nurapi @nordicid/nurapi-update
```

The package handles the full update flow: parsing Nordic ID `UpdateInfo.json` ZIP packages, checking firmware/module compatibility, switching the NUR module between application and bootloader mode, programming NUR module firmware (APP / LOADER) via the bootloader page-write protocol, and programming EXA accessory firmware via MCUboot images delivered over SMP through the NUR `ACC_EXT` MCUMGR subcommand.

```typescript
import { NurApi } from '@nordicid/nurapi';
import '@nordicid/nurapi-node';                                   // pick a transport package
import { parseUpdateZip, runUpdate } from '@nordicid/nurapi-update';

const reader = new NurApi();
await reader.connect('ser:///dev/ttyUSB0');

const pkg = parseUpdateZip(zipBytes);                             // Uint8Array of NIDLatestFW.zip
const result = await runUpdate(reader, pkg, {
  onProgress: (p) => console.log(`${p.itemIndex + 1}/${p.itemCount} ${p.itemName} — ${p.percent}%`),
});

console.log(result.success ? 'OK' : `failed: ${result.errors.length} item(s)`);
```

### What's in the package

| Export | Purpose |
|---|---|
| `parseUpdateZip(bytes)` | Parse a Nordic ID firmware-update ZIP into compatibility-checked items |
| `runUpdate(api, pkg, opts?)` | Run a cascading multi-pass update with progress + abort signal |
| `ensureMode(api, target)` | Toggle the NUR module between mode A (application) and mode B (bootloader) via ENTERBOOT |
| `gatherDeviceInfo(api)` | Read versions, module type, accessory info — input for compatibility checks |
| `checkItemCompatibility(item, deviceInfo)` | Decide whether an item is ready / skipped / blocked |
| `programNurItem(api, item, opts?)` | Program a single NUR module firmware item (APP or LOADER) |
| `programExaItem(api, item, opts?)` | Program an EXA accessory firmware item over MCUboot/SMP |
| `parseMcubootZip(bytes)` | Parse an inner EXA MCUboot package; surfaces SHA256 + image-index per image |
| `McuMgrClient` | Lower-level MCUboot SMP client over `ACC_EXT` if you need fine control |

### Peer dependency

This package has a peer dependency on `@nordicid/nurapi ^0.9.0` — install it alongside the transport package(s) you use (`@nordicid/nurapi-web`, `@nordicid/nurapi-node`, etc.). It does not register URI schemes itself.

### Error handling

Protocol and file-format errors throw `NurApiError` with a `NurError` code — match on `instanceof NurApiError` and `err.code` for routing (e.g. `FILE_INVALID` for malformed packages, `PROGRAM_FAILED` for MCUboot rc errors, `NOT_READY` for failed mode switches). Buffer/bounds violations throw `RangeError`.

## API Reference

### @nordicid/nurapi-update

Firmware update orchestration for Nordic ID NUR readers and EXA accessories — built on [`@nordicid/nurapi`](https://www.npmjs.com/package/@nordicid/nurapi).

#### Install

```bash
npm install @nordicid/nurapi @nordicid/nurapi-update
```

You also need a transport package: [`@nordicid/nurapi-web`](https://www.npmjs.com/package/@nordicid/nurapi-web) (browser) or [`@nordicid/nurapi-node`](https://www.npmjs.com/package/@nordicid/nurapi-node) (Node.js).

#### Quick Start

```typescript
import { NurApi } from '@nordicid/nurapi';
import '@nordicid/nurapi-node';
import { parseUpdateZip, runUpdate } from '@nordicid/nurapi-update';

const reader = new NurApi();
await reader.connect('ser:///dev/ttyUSB0');

const pkg = parseUpdateZip(zipBytes);   // Uint8Array of NIDLatestFW.zip
const result = await runUpdate(reader, pkg, {
  onProgress: (p) => console.log(`${p.itemIndex + 1}/${p.itemCount} ${p.itemName} — ${p.percent}%`),
});

console.log(result.success ? 'OK' : `failed: ${result.errors.length} item(s)`);
```

#### What It Does

- Parses Nordic ID firmware-update ZIP packages (`UpdateInfo.json` + binaries)
- Checks firmware/module compatibility and version matching
- Programs NUR module firmware (APP / LOADER) via bootloader page-write protocol
- Programs EXA accessory firmware via MCUboot/SMP over `ACC_EXT`
- Handles mode switching (application ↔ bootloader) and reconnection
- Cascading multi-pass updates with progress callbacks and `AbortSignal` cancellation

#### API

| Export | Purpose |
|---|---|
| `parseUpdateZip(bytes)` | Parse a firmware-update ZIP into an `UpdatePackage` |
| `runUpdate(api, pkg, opts?)` | Run a full cascading update with progress + abort |
| `gatherDeviceInfo(api)` | Read device versions, module type, accessory info |
| `checkItemCompatibility(item, info)` | Check if an item is ready / up-to-date / blocked |
| `ensureMode(api, target)` | Switch NUR module between mode A and mode B |
| `programNurItem(api, item, opts?)` | Program a single NUR module firmware item |
| `programExaItem(api, item, opts?)` | Program an EXA accessory firmware item |
| `McuMgrClient` | Low-level MCUboot SMP client over `ACC_EXT` |
| `parseMcubootZip(bytes)` | Parse an inner EXA MCUboot ZIP package |

#### Dependencies

| Package | Purpose | Size |
|---|---|---|
| `fflate` | ZIP decompression | ~8 KB |
| `cborg` | CBOR encode/decode for SMP protocol | ~8 KB |

Both are zero-dependency and work in browser + Node.js.

#### Documentation

See the full API reference and guides at [nordicid.github.io/nur_nurapi_typescript](https://nordicid.github.io/nur_nurapi_typescript/).

#### License

See [LICENSE](https://nordicid.github.io/nur_nurapi_typescript/LICENSE).

#### Classes

##### McuMgrClient


McuMgr (Simple Management Protocol) client for EXA accessories.

Provides high-level methods for managing MCUboot images on an EXA
accessory (echo, reset, image upload/erase/state). SMP packets are
tunnelled through the NUR `ACC_EXT` MCUMGR subcommand, so the
accessory must be in DFU mode and reachable through a connected
`NurApi` instance.

Used internally by [programExaItem](#programexaitem); exported for callers that
need fine-grained control over the MCUboot flow.

###### Constructors

###### Constructor

> **new McuMgrClient**(`api`, `log?`): [`McuMgrClient`](#mcumgrclient)


**Parameters**

| Name | Type | Description |
| --- | --- | --- |
| api | `NurApi` |  |
| log? | [`UpdateLogger`](#updatelogger) |  |

**Returns** [`McuMgrClient`](#mcumgrclient)

###### Methods

###### echo()

> **echo**(`msg`): `Promise`\<`string`\>


Send an echo request and return the echoed string.

**Parameters**

| Name | Type | Description |
| --- | --- | --- |
| msg | `string` |  |

**Returns** `Promise`\<`string`\>

###### reset()

> **reset**(`force?`): `Promise`\<`void`\>


Send a reset command to the accessory.

**Parameters**

| Name | Type | Description |
| --- | --- | --- |
| force? | `number` |  |

**Returns** `Promise`\<`void`\>

###### imageUpload()

> **imageUpload**(`image`, `options?`): `Promise`\<`void`\>


Upload a firmware image to the accessory slot.

Sends the image in MTU-sized chunks, tracking offset from device responses.

**Parameters**

| Name | Type | Description |
| --- | --- | --- |
| image | `Uint8Array` |  |
| options? |  |  |
| imageNo? | `number` |  |
| sha? | `Uint8Array`\<`ArrayBufferLike`\> |  |
| onProgress? | (`percent`) => `void` |  |
| signal? | `AbortSignal` | Aborts the upload between chunks. Throws an `AbortError`-named error on the next iteration after the signal becomes aborted. |

**Returns** `Promise`\<`void`\>

###### imageErase()

> **imageErase**(`slot?`, `imageNo?`): `Promise`\<`void`\>


Erase the secondary image slot. Long timeout — flash erase is slow.

**Parameters**

| Name | Type | Description |
| --- | --- | --- |
| slot? | `number` |  |
| imageNo? | `number` |  |

**Returns** `Promise`\<`void`\>

###### imageStateRead()

> **imageStateRead**(): `Promise`\<[`ImageSlot`](#imageslot)[]\>


Read the current image state (slot info).

**Returns** `Promise`\<[`ImageSlot`](#imageslot)[]\>

###### imageStateWrite()

> **imageStateWrite**(`hash`, `confirm`): `Promise`\<`void`\>


Write image state (confirm or test an image).

**Parameters**

| Name | Type | Description |
| --- | --- | --- |
| hash | `Uint8Array` |  |
| confirm | `boolean` |  |

**Returns** `Promise`\<`void`\>

#### Interfaces

##### FirmwareItem


A single firmware entry parsed from the update ZIP package.

###### Properties

###### name

> **name**: `string`


Display name of the firmware (e.g. "NUR2-1W_APP_v7.12-B").

###### filename

> **filename**: `string`


Original filename in the ZIP (e.g. "NUR2-1W_APP_v7.12-B.bin").

###### info

> **info**: `FwInfo`


Parsed FWINFO structure from the item's fwinfo field.

###### data

> **data**: `Uint8Array`


Firmware binary data (or inner ZIP for EXA accessories).

###### minVersion?

> `optional` **minVersion?**: `number`[]


Minimum device version required to apply this update.

###### forceUpdate

> **forceUpdate**: `boolean`


Whether this item should be force-updated regardless of version.

###### forceUpdateVersions

> **forceUpdateVersions**: `string`[]


Specific device versions that trigger forced update.

###### requiredLibVersion?

> `optional` **requiredLibVersion?**: `number`


Minimum NID update library version required (1, 2, or 3).

***

##### UpdatePackage


Result of parsing an update ZIP package.

###### Properties

###### items

> **items**: [`FirmwareItem`](#firmwareitem)[]


All firmware items in the package.

***

##### DeviceInfo


What the connected device reports about itself.

###### Properties

###### module

> **module**: `string`


Module identifier (e.g. "NUR2-1W", "NUR-05WL2").

###### appVersion

> **appVersion**: `number`[]


Application firmware version as numeric array.

###### blVersion

> **blVersion**: `number`[]


Bootloader firmware version as numeric array.

###### secChipVersion?

> `optional` **secChipVersion?**: `number`[]


Secondary chip firmware version, if present.

###### mode

> **mode**: `"A"` \| `"B"`


Current operating mode ("A" = application, "B" = bootloader).

###### hasAccessory

> **hasAccessory**: `boolean`


Whether an accessory (EXA) is connected.

###### accessoryModel?

> `optional` **accessoryModel?**: `string`


Accessory model identifier (e.g. "EXA81", "EXA21").

###### accessoryAppVersion?

> `optional` **accessoryAppVersion?**: `number`[]


Accessory application firmware version.

###### accessoryBlVersion?

> `optional` **accessoryBlVersion?**: `number`[]


Accessory bootloader firmware version.

###### readerInfo?

> `optional` **readerInfo?**: `ReaderInfo`


Full ReaderInfo as returned by GETREADERINFO. Populated by
`gatherDeviceInfo` so consumers can render display fields
(name, serial, altSerial, hwVersion, fccId, swVersion) without
issuing a second GETREADERINFO call — that duplicate call has
been observed to wedge some NUR firmwares after the ACC_EXT
probe sequence.

***

##### ItemCheckResult


Result of checking a firmware item against the current device state.

###### Properties

###### item

> **item**: [`FirmwareItem`](#firmwareitem)


The firmware item that was checked.

###### status

> **status**: [`ItemStatus`](#itemstatus)


Compatibility status.

###### reason?

> `optional` **reason?**: `string`


Human-readable explanation of the status.

###### deviceVersion?

> `optional` **deviceVersion?**: `number`[]


Current device version for this firmware type, if applicable.

***

##### UpdateProgress


Progress information reported during an update operation.

###### Properties

###### phase

> **phase**: `"validating"` \| `"programming"` \| `"verifying"`


Current phase of the update.

###### itemIndex

> **itemIndex**: `number`


Zero-based index of the item being processed.

###### itemCount

> **itemCount**: `number`


Total number of items to process.

###### itemName

> **itemName**: `string`


Display name of the current item.

###### percent

> **percent**: `number`


Progress percentage (0–100) for the current item.

###### status?

> `optional` **status?**: `string`


Human-readable label for the current sub-step (e.g. "Erasing flash",
"Uploading image 1/2", "Waiting for device to restart (12s)").
Updated at each transition so the UI can show what's happening even
during long waits where `percent` does not advance.

***

##### UpdateResult


Result of a complete update operation.

###### Properties

###### success

> **success**: `boolean`


Whether all items were updated successfully.

###### updatedItems

> **updatedItems**: [`FirmwareItem`](#firmwareitem)[]


Items that were successfully updated.

###### errors

> **errors**: `object`[]


Items that failed with their errors.

###### item

> **item**: [`FirmwareItem`](#firmwareitem)

###### error

> **error**: `Error`

***

##### ImageSlot


Slot information returned by McuMgr image state read.

###### Properties

###### image?

> `optional` **image?**: `number`


Image index for multi-image devices (0 = primary, 1 = secondary core).

###### slot

> **slot**: `number`


Slot number (0 = primary, 1 = secondary).

###### version

> **version**: `string`


Image version string.

###### hash

> **hash**: `Uint8Array`


Image SHA256 hash.

###### bootable

> **bootable**: `boolean`


Whether this slot is bootable.

###### pending

> **pending**: `boolean`


Whether this image is pending (will swap on next boot).

###### confirmed

> **confirmed**: `boolean`


Whether this image is confirmed.

###### active

> **active**: `boolean`


Whether this image is active (currently running).

###### permanent

> **permanent**: `boolean`


Whether this image is permanent.

***

##### McubootImage


Parsed MCUboot image with extracted metadata.

###### Properties

###### data

> **data**: `Uint8Array`


Raw image binary data.

###### hash

> **hash**: `Uint8Array`


SHA256 hash extracted from TLV trailer.

###### version

> **version**: `string`


Version string from image header.

###### imageIndex

> **imageIndex**: `number`


Image index for multi-image DFU.

#### Type Aliases

##### ItemStatus

> **ItemStatus** = `"ready"` \| `"up-to-date"` \| `"not-applicable"` \| `"blocked"`


Status of a single firmware item after compatibility check.

***

##### UpdateLogLevel

> **UpdateLogLevel** = `"error"` \| `"warning"` \| `"info"` \| `"verbose"`


Severity levels for update-library diagnostic logging.

***

##### UpdateLogger

> **UpdateLogger** = (`level`, `message`, `context?`) => `void`


Diagnostic logger callback used by the update library to surface internal events.

###### Parameters

###### level

[`UpdateLogLevel`](#updateloglevel)

###### message

`string`

###### context?

`Record`\<`string`, `unknown`\>

**Returns** `void`

***

##### ReconnectRequest

> **ReconnectRequest** = () => `Promise`\<`void`\>


Hook called when the update library has exhausted its auto-reconnect retries
after a device reboot and needs the host application to gesture-trigger a
fresh connection (BLE re-pair, granting a new serial port, etc.).

The library has already detected the disconnect and tried to reconnect on
its own; this hook is the cooperative fallback. The host should:
 1. Surface UI prompting the user to reconnect (e.g., a "Reconnect" button).
 2. Wait for the user's action.
 3. Re-establish the connection (e.g., `reader.connect('ble://request')` from
    a click handler, which pops the platform picker for a fresh handle).
 4. Resolve the returned Promise once `api.connected` is true.

Reject (or throw) if the user cancels — the in-progress update will be
aborted with that error.

**Returns** `Promise`\<`void`\>

#### Functions

##### isExaItem()

> **isExaItem**(`item`): `boolean`


Whether a firmware item targets an EXA accessory (vs the NUR module).

###### Parameters

###### item

[`FirmwareItem`](#firmwareitem)

**Returns** `boolean`

***

##### checkItemCompatibility()

> **checkItemCompatibility**(`item`, `deviceInfo`): [`ItemCheckResult`](#itemcheckresult)


Check whether a firmware item is compatible with the current device state.

Pure function — no device I/O. Determines whether the item should be
applied, is already up-to-date, doesn't apply to this device, or is
blocked by version constraints.

###### Parameters

###### item

[`FirmwareItem`](#firmwareitem)

The firmware item to check

###### deviceInfo

[`DeviceInfo`](#deviceinfo)

Current device state

**Returns** [`ItemCheckResult`](#itemcheckresult) — Check result with status and explanation

***

##### ensureMode()

> **ensureMode**(`api`, `target`): `Promise`\<`void`\>


Switch the NUR module between application mode (`"A"`) and bootloader
mode (`"B"`).

Returns immediately if the device is already in the target mode.
Otherwise issues the mode-switch command, waits briefly for a reboot
notification, then polls `getMode()` until the device reports the
target mode. Works over any transport, including ones that drop the
host link during the switch and reconnect afterwards.

###### Parameters

###### api

`NurApi`

Connected NurApi instance.

###### target

`"A"` \| `"B"`

Target mode: `"A"` (application) or `"B"` (bootloader).

**Returns** `Promise`\<`void`\>

**Throws** `NurApiError` (code `NOT_READY`) if the device does not

  reach the target mode within the verification window.

***

##### gatherDeviceInfo()

> **gatherDeviceInfo**(`api`): `Promise`\<[`DeviceInfo`](#deviceinfo)\>


Gather device information from a connected NUR reader.

Reads NUR-module version/mode/caps and probes the accessory (if any).

###### Parameters

###### api

`NurApi`

Connected NurApi instance

**Returns** `Promise`\<[`DeviceInfo`](#deviceinfo)\> — Complete device information for compatibility checking

***

##### parseMcubootZip()

> **parseMcubootZip**(`zip`): [`McubootImage`](#mcubootimage)[]


Parse an inner MCUboot ZIP package (as used by EXA accessories).

The ZIP contains a `manifest.json` describing image files and their metadata.
Each image binary is parsed to extract its SHA256 hash from the TLV trailer.

###### Parameters

###### zip

`Uint8Array`

Raw inner ZIP bytes (the EXA firmware .zip inside the update package)

**Returns** [`McubootImage`](#mcubootimage)[] — Array of parsed MCUboot images with hash and version info

***

##### parseMcubootImage()

> **parseMcubootImage**(`bin`): `object`


Parse a raw MCUboot image binary to extract its SHA256 hash and version.

Reads the MCUboot header for the version, then walks the TLV trailer
to find the SHA256 hash entry.

###### Parameters

###### bin

`Uint8Array`

Raw MCUboot image binary.

**Returns** `object` — The SHA256 hash from the TLV trailer and the version string

  from the header.

###### hash

> **hash**: `Uint8Array`

###### version

> **version**: `string`

**Throws** `NurApiError` (code `FILE_INVALID`) if the magic is not

  MCUboot's or the SHA256 TLV cannot be found.

**Throws** `RangeError` if the binary is too short for the declared

  header / body / TLV section sizes.

***

##### parseUpdateZip()

> **parseUpdateZip**(`zip`): [`UpdatePackage`](#updatepackage)


Parse an update ZIP package into a structured UpdatePackage.

The ZIP must contain an `UpdateInfo.json` file at the root level
describing all firmware items and their metadata.

###### Parameters

###### zip

`Uint8Array`

Raw bytes of the update ZIP file

**Returns** [`UpdatePackage`](#updatepackage) — Parsed update package with all firmware items and their binary data

**Throws** Error if UpdateInfo.json is missing or referenced firmware files are not found

***

##### programExaItem()

> **programExaItem**(`api`, `item`, `options?`): `Promise`\<`void`\>


Program a single EXA accessory firmware item.

Parses the item's inner MCUboot ZIP, switches the accessory into DFU
mode, uploads each image (net core before app core on dual-core
targets), confirms the swap, and brings the accessory back into
normal application mode. Use this directly for single-accessory
programming; [runUpdate](#runupdate) calls it internally as part of a
package update.

###### Parameters

###### api

`NurApi`

Connected NurApi instance with an EXA accessory attached.

###### item

[`FirmwareItem`](#firmwareitem)

Firmware item — its `data` is the inner MCUboot ZIP.

###### options?

Callbacks and tuning knobs (see fields).

###### onProgress?

(`percent`) => `void`

Per-image progress callback (0-100).

###### onStatus?

(`status`) => `void`

Status message callback for UI display.

###### onReconnectRequired?

[`ReconnectRequest`](#reconnectrequest)

Called when auto-reconnect fails and the host needs to
gesture-trigger a fresh connection (typical for Web Bluetooth
after a net-core swap). Reject to abort.

###### log?

[`UpdateLogger`](#updatelogger)

Diagnostic logger.

###### signal?

`AbortSignal`

Abort signal. Checked at every poll and upload-chunk boundary so
a user cancel takes effect within ~1 s. The thrown error has
`name === "AbortError"`.

###### reconnectTimeoutMs?

`number`

Auto-reconnect window in milliseconds. Default 60 000. Web
Bluetooth hosts may want a shorter value (~15 000) so the
`onReconnectRequired` user-gesture path runs sooner.

**Returns** `Promise`\<`void`\>

**Throws** `NurApiError` on parse failures, programming errors, or if

  the accessory does not reconnect after a reboot.

***

##### programNurItem()

> **programNurItem**(`api`, `item`, `options?`): `Promise`\<`void`\>


Program a single NUR module firmware item (application or bootloader).

Switches the module to bootloader mode, programs the firmware via the
NUR page-write protocol, and switches back to application mode when
done. Use this directly for one-off bin programming; the cascading
update flow in [runUpdate](#runupdate) calls it internally.

###### Parameters

###### api

`NurApi`

Connected NurApi instance.

###### item

[`FirmwareItem`](#firmwareitem)

Firmware item to program (must be type `"APP"` or `"LOADER"`).

###### options?

Callbacks and tuning knobs (see fields).

###### onProgress?

(`percent`) => `void`

Per-page progress callback (0-100).

###### onStatus?

(`status`) => `void`

Status message callback for UI display.

###### onReconnectRequired?

[`ReconnectRequest`](#reconnectrequest)

Reconnect-required hook. Accepted for parity with
[programExaItem](#programexaitem); NUR module reboots reuse the existing
transport handle and don't normally need a user gesture.

###### log?

[`UpdateLogger`](#updatelogger)

Diagnostic logger.

###### signal?

`AbortSignal`

Abort signal. NUR module flashing runs inside `NurFirmwareExt` and
is interruptible only between high-level steps (mode switch,
before/after programming) rather than per page.

###### reconnectTimeoutMs?

`number`

Auto-reconnect window in milliseconds. Default 60 000. Forwarded
to the underlying transport's reconnect logic.

**Returns** `Promise`\<`void`\>

**Throws** `NurApiError` for unsupported firmware types or mode-switch failures.

***

##### runUpdate()

> **runUpdate**(`api`, `pkg`, `options?`): `Promise`\<[`UpdateResult`](#updateresult)\>


Run a complete firmware update against a connected device.

Iterates the items in `pkg`, programs everything that is compatible
with the device's current firmware versions, and repeats in case
programming one item unlocks others (cascading updates). Progress is
reported per item via `options.onProgress`; the operation can be
cancelled via `options.signal`.

###### Parameters

###### api

`NurApi`

Connected NurApi instance.

###### pkg

[`UpdatePackage`](#updatepackage)

Parsed update package from [parseUpdateZip](#parseupdatezip).

###### options?

Callbacks and tuning knobs (see fields).

###### onProgress?

(`progress`) => `void`

Per-item progress callback.

###### signal?

`AbortSignal`

Abort signal — checked between items and within programming loops.

###### log?

[`UpdateLogger`](#updatelogger)

Diagnostic logger for surfacing internal update events.

###### onReconnectRequired?

[`ReconnectRequest`](#reconnectrequest)

Called when auto-reconnect fails and the host needs to gesture-trigger
a fresh connection (e.g. Web Bluetooth re-pair). Reject to abort.

###### reconnectTimeoutMs?

`number`

Auto-reconnect window in milliseconds before falling back to
`onReconnectRequired`. Default 60 000. Web Bluetooth hosts may
want a shorter value (~15 000) so the user picker pops sooner.

**Returns** `Promise`\<[`UpdateResult`](#updateresult)\> — Summary listing the items that were updated and any errors.
