Pear Mobile OTA
Pear Mobile OTA (the pear-mobile module) for embedding Pear's over-the-air updates and Bare worklets into React Native and Expo apps.
Pear Mobile OTA
Pear Mobile OTA is the mobile counterpart to Pear OTA, provided by the pear-mobile module. It embeds the same over-the-air update and storage model into React Native and Expo apps, running the peer-to-peer core in a Bare worklet via react-native-bare-kit instead of a desktop pear-runtime process.
pear-mobile is MVP and experimental—its API may still change.
Install it into a React Native or Expo app:
npm install pear-mobileTwo entry points, one package
Unlike desktop's pear-runtime, which is required the same way on both sides of a worker boundary, pear-mobile resolves to a different export depending on which side requires it—governed by its package.json exports conditions:
- From the React Native/Expo JS thread—the
react-nativecondition—pear-mobileis a static launcher for the worklet:PearRuntime.run(filename, bundle, argv). - From inside the Bare worklet (
workers/main.js)—the default condition—pear-mobileis the same shape as desktop'sPearRuntimeclass:new PearRuntime(opts), with.storage,.updater,.ready(), and.close().
This is also why hello-pear-worker works unmodified across desktop and mobile templates: it always does require('pear-runtime'), and its own conditional exports/imports redirect that specifier to pear-mobile on iOS, Android, and simulator hosts. See Where the app logic goes for that in context.
Starting the worklet (React Native side)
import PearRuntime from 'pear-mobile'
import bundle from './worker.bundle.js'
const IPC = PearRuntime.run('/worker.bundle', bundle, [
updatesEnabled.toString(),
version,
upgrade,
productName
])PearRuntime.run is static—there is no instance on this side. It starts a react-native-bare-kit Worklet with the given entry filename and bundle, passes argv through as the worklet's positional Bare.argv, and returns the worklet's IPC duplex. Unlike desktop's PearRuntime.run(specifier, args), mobile takes the bundle's content (bundle) rather than just a path—there is no filesystem for the worklet to load from on a device, so the JS source has to be handed over directly. Bundle the worker first with bare-pack (see Bundle a Bare app); a stale bundle fails silently rather than erroring.
Instantiating (inside the worklet)
const PearRuntime = require('pear-mobile') // or require('pear-runtime') via hello-pear-worker's redirect
const pear = new PearRuntime({ dir, version, upgrade, name, app })
pear.on('error', console.error)
pear.on('minver-required', ({ minver, version }) => {
// prompt the user to update from the App Store / Play Store
})Options
upgrade<String>- Required. Pear upgrade link, typically from thepackage.jsonupgradefield.name<String>- Required. The application's product name.version<String>- Current app version; used to decide whether an update should be stored.dir<String>- Directory to store runtime data. Defaults to the platform's persistent app-data directory (resolved viabare-storage'spersistent()—iOS: the app's Application Support directory; Android: the app'sfilesdirectory).storage<String>- Override the peer-to-peer application storage path. Defaults to<dir>/app-storage.app<String>- Path the updater stages the OTA payload into. Defaults to<dir>/pear-runtime/ota.store<Corestore>&swarm<Hyperswarm>- Supply an existing Corestore and Hyperswarm for update replication. Both must be passed together; when omitted,pear-mobilecreates and swarms its own.updates<Boolean>- Set tofalseto opt out of updates.bootstrap<Array>- Override the HyperDHT bootstrap nodes used for update replication.skipUpdate<Function>- Optional async hook, checked before the built-inminvergate. Returntrueto skip an available update for a reason of your own.
Updates
Update events mirror desktop Pear OTA—pear.updater emits updating, then updated once the new version is staged:
pear.updater.on('updating', () => {
// update view to indicate updating in progress
})
pear.updater.on('updated', () => {
// update view to indicate update ready; call pear.updater.applyUpdate() when the user confirms
})The minver gate
Mobile adds one event desktop doesn't have. Before taking an update, pear-mobile reads /pear.json from the update drive and compares its updates.minver against the running native version:
- native version is
>=minver— the update proceeds as normal. - native version is
<minver— nothing is downloaded, and the instance emitsminver-requiredwith{ minver, version }instead.
This exists because a mobile OTA replaces the JavaScript bundle only—native code changes still require an App Store / Play Store release, so an OTA payload can require a newer native build than the one it's about to update. Set pear.json updates.minver whenever a release changes the native/OTA contract; see Version management in the hello-pear-react-native README for the full sequencing rules.
Lifecycle
Instantiation is eager, same as desktop—pear-mobile starts opening its store and swarm as soon as new PearRuntime(opts) returns:
await pear.ready()goodbye(() => pear.close()) // e.g. via graceful-goodbyeSee also
- Pear OTA—the desktop counterpart this module mirrors.
hello-pear-worker—the shared worker that requirespear-runtimeand lets each template'spackage.jsonredirect it topear-mobileon mobile.- Start from the hello-pear-react-native template—this module in a working template.
- Bare Kit—the
WorkletandIPCAPIpear-mobileruns on. - Handle app suspension—keeping the worklet in step with the OS lifecycle.