expo-in-app-update-enterprise
v1.0.1
Published
Enterprise-grade in-app update module for Expo and React Native. Wraps Google Play In-App Updates API with policy engine, audit logging, and banking-grade security features.
Downloads
305
Maintainers
Readme
expo-in-app-update
Enterprise-grade in-app update module for Expo & React Native. Wraps Google Play's In-App Updates API (Android) and iTunes Lookup API (iOS) with a powerful policy engine, audit logging, and banking-grade security features.
✨ Features
| Feature | Description |
|---|---|
| Google Play In-App Updates | Full support for FLEXIBLE and IMMEDIATE update flows on Android |
| iOS Version Checking | Automatic version comparison via iTunes Lookup API |
| Policy Engine | Declarative rules — minimum version, priority threshold, staleness, max dismissals |
| Audit Logger | Structured compliance events for banking / regulated industries |
| UpdateProvider | React Context provider with periodic re-checking |
| UpdateBanner | Animated, themeable slide-in banner for flexible updates |
| ForceUpdateScreen | Full-screen blocking overlay for critical updates |
| Fully Themeable | Every UI component accepts a theme object |
| Accessible | WCAG-compliant — roles, labels, live regions |
| TypeScript | Strict types, comprehensive JSDoc, zero any |
| Testable | Built-in MockConfig for development & testing |
| Expo + Bare RN | Works with Expo managed/bare and plain React Native |
📦 Installation
# Using npm
npm install expo-in-app-update-enterprise
# Using yarn
yarn add expo-in-app-update-enterprise
# Using Expo CLI
npx expo install expo-in-app-update-enterpriseNote: If you're using a bare React Native project, run
npx pod-installafter installation.
🚀 Quick Start
1. Wrap your app with UpdateProvider
import { UpdateProvider } from 'expo-in-app-update-enterprise';
export default function App() {
return (
<UpdateProvider
policy={{
minimumVersion: 10,
maxDismissals: 3,
maxStaleDaysBeforeForce: 14,
}}
autoCheck
checkInterval={3_600_000} // Re-check every hour
>
<MyApp />
</UpdateProvider>
);
}2. Use the hook anywhere in your tree
import { useUpdateContext } from 'expo-in-app-update-enterprise';
function HomeScreen() {
const {
updateInfo,
isChecking,
policyResult,
checkForUpdate,
startUpdate,
} = useUpdateContext();
if (policyResult?.isForced) {
return <ForceUpdateScreen />;
}
return (
<View>
<Text>Current: {updateInfo?.currentVersionName}</Text>
<Button title="Check for Update" onPress={checkForUpdate} />
</View>
);
}3. Add UI components
import {
UpdateProvider,
UpdateBanner,
ForceUpdateScreen,
useUpdateContext,
} from 'expo-in-app-update-enterprise';
function AppShell() {
const { policyResult } = useUpdateContext();
if (policyResult?.isForced) {
return <ForceUpdateScreen />;
}
return (
<>
<MainNavigator />
<UpdateBanner position="bottom" />
</>
);
}
export default function App() {
return (
<UpdateProvider policy={myPolicy} autoCheck>
<AppShell />
</UpdateProvider>
);
}📖 API Reference
useInAppUpdate(options?)
React hook for controlling updates imperatively.
import { useInAppUpdate } from 'expo-in-app-update-enterprise';Options — UseInAppUpdateOptions
| Prop | Type | Default | Description |
|---|---|---|---|
| policy | UpdatePolicy | — | Policy engine configuration |
| autoCheck | boolean | true | Auto-check on mount |
| auditConfig | AuditLoggerConfig | — | Audit logger settings |
| onUpdateAvailable | (info) => void | — | Callback when update is found |
| onUpdateAccepted | (info) => void | — | Callback on acceptance |
| onUpdateDismissed | (info) => void | — | Callback on dismissal |
| onUpdateFailed | (error, info?) => void | — | Callback on failure |
| onUpdateCompleted | () => void | — | Callback on completion |
| onDownloadProgress | (progress) => void | — | Callback for progress events |
Returns — UseInAppUpdateResult
| Property | Type | Description |
|---|---|---|
| updateInfo | UpdateInfo \| null | Available update information |
| downloadProgress | DownloadProgress \| null | Current download progress |
| isChecking | boolean | Whether a check is in progress |
| isDownloading | boolean | Whether a download is in progress |
| isReadyToInstall | boolean | Whether a download completed |
| error | Error \| null | Last error |
| policyResult | PolicyEvaluationResult \| null | Policy engine result |
| checkForUpdate() | () => Promise<UpdateInfo> | Check for updates |
| startUpdate(type?) | (type?) => Promise<UpdateResult> | Start an update flow |
| completeUpdate() | () => Promise<void> | Install a downloaded update |
| dismissUpdate() | () => void | Dismiss the current prompt |
<UpdateProvider>
React Context provider that manages update state for the entire component tree.
import { UpdateProvider } from 'expo-in-app-update-enterprise';Props — UpdateProviderProps
| Prop | Type | Default | Description |
|---|---|---|---|
| policy | UpdatePolicy | — | Policy engine configuration |
| auditConfig | AuditLoggerConfig | — | Audit logger settings |
| autoCheck | boolean | true | Check on mount |
| checkInterval | number | 0 | Re-check interval in ms (0 = off) |
| onAuditEvent | (event) => void | — | Callback for audit events |
| children | ReactNode | — | Child components |
<UpdateBanner>
Animated slide-in banner for flexible update prompts.
import { UpdateBanner } from 'expo-in-app-update-enterprise';Props — UpdateBannerProps
| Prop | Type | Default | Description |
|---|---|---|---|
| theme | UpdateUITheme | — | Theme overrides |
| title | string | Auto | Custom title |
| body | string | Auto | Custom body |
| updateButtonText | string | "Update" | Update button label |
| dismissButtonText | string | "Later" | Dismiss button label |
| restartButtonText | string | "Restart" | Restart button label |
| showProgress | boolean | true | Show progress bar |
| position | 'top' \| 'bottom' | 'bottom' | Banner position |
| icon | ReactNode | — | Custom icon/logo |
| accessibilityLabel | string | Auto | A11y label override |
<ForceUpdateScreen>
Full-screen blocking overlay for mandatory updates.
import { ForceUpdateScreen } from 'expo-in-app-update-enterprise';Props — ForceUpdateScreenProps
| Prop | Type | Default | Description |
|---|---|---|---|
| theme | UpdateUITheme | — | Theme overrides |
| logo | ReactNode | Shield icon | Custom logo |
| title | string | "Update Required" | Title text |
| body | string | Default message | Body text |
| updateButtonText | string | "Update Now" | CTA label |
| secondaryMessage | string | Version info | Secondary message |
| accessibilityLabel | string | Auto | A11y label override |
PolicyEngine
Evaluates declarative rules against update info to decide the update flow.
import { PolicyEngine } from 'expo-in-app-update-enterprise';
const engine = new PolicyEngine({
minimumVersion: 15,
recommendedVersion: 20,
maxStaleDaysBeforeForce: 14,
maxDismissals: 3,
priorityThresholdForForce: 4,
forceUpdateMessage: 'Critical security update required.',
});
const result = engine.evaluate(updateInfo, dismissCount);
if (result.isForced) {
// Show ForceUpdateScreen
} else if (result.shouldUpdate) {
// Show UpdateBanner
}UpdatePolicy Configuration
| Property | Type | Description |
|---|---|---|
| minimumVersion | number | Force IMMEDIATE if below this version code |
| recommendedVersion | number | Suggest FLEXIBLE if below this version code |
| maxStaleDaysBeforeForce | number | Force after N days of availability |
| maxDismissals | number | Force after N user dismissals |
| priorityThresholdForForce | number | Force if priority ≥ threshold (0-5) |
| forceUpdateMessage | string | Custom message for forced updates |
| recommendedUpdateMessage | string | Custom message for flexible updates |
| allowBackgroundUsage | boolean | Allow app use during flex download |
PolicyEvaluationResult
| Property | Type | Description |
|---|---|---|
| shouldUpdate | boolean | Whether an update action is needed |
| resolvedUpdateType | UpdateType \| null | FLEXIBLE or IMMEDIATE |
| triggeredRule | string | Which rule triggered ('minimumVersion', etc.) |
| reason | string | Human-readable explanation |
| isForced | boolean | Whether this is a forced/non-dismissible update |
| message | string? | Message to display |
AuditLogger
Structured compliance logging for update lifecycle events.
import { AuditLogger } from 'expo-in-app-update-enterprise';
const logger = new AuditLogger({
enabled: true,
transport: async (event) => {
await fetch('https://compliance.bank.com/audit', {
method: 'POST',
body: JSON.stringify(event),
});
},
consoleLog: __DEV__,
persistLocally: true,
maxLocalEvents: 1000,
});AuditEventType Enum
| Value | Description |
|---|---|
| UPDATE_CHECK | Check initiated |
| UPDATE_AVAILABLE | Update found |
| UPDATE_NOT_AVAILABLE | No update found |
| UPDATE_STARTED | Update flow started |
| UPDATE_ACCEPTED | User accepted |
| UPDATE_DISMISSED | User dismissed |
| UPDATE_DOWNLOADED | Download completed |
| UPDATE_INSTALLED | Install completed |
| UPDATE_FAILED | Update failed |
| POLICY_EVALUATED | Policy engine ran |
| FORCED_UPDATE_TRIGGERED | Forced update triggered |
| UPDATE_COMPLETED | Restart triggered |
| ERROR | Error occurred |
🏦 Banking App Configuration
For regulated environments that require strict update enforcement and compliance logging:
import {
UpdateProvider,
UpdateBanner,
ForceUpdateScreen,
useUpdateContext,
} from 'expo-in-app-update-enterprise';
const BANKING_POLICY = {
// Force update if below version code 25 (security baseline)
minimumVersion: 25,
// Recommend update if below version code 30
recommendedVersion: 30,
// Force update if available for 7+ days
maxStaleDaysBeforeForce: 7,
// Force after 2 dismissals
maxDismissals: 2,
// Force if priority >= 4 (high/critical)
priorityThresholdForForce: 4,
// Custom compliance messages
forceUpdateMessage:
'This update contains critical security patches required by our ' +
'banking regulations. You must update to continue.',
recommendedUpdateMessage:
'A new version is available with enhanced security features ' +
'and performance improvements.',
};
const AUDIT_CONFIG = {
enabled: true,
transport: async (event) => {
await complianceAPI.logEvent(event);
},
persistLocally: true,
maxLocalEvents: 2000,
globalMetadata: {
appId: 'com.bank.mobile',
region: 'US',
regulatoryFramework: 'PCI-DSS',
},
};
function BankingApp() {
return (
<UpdateProvider
policy={BANKING_POLICY}
auditConfig={AUDIT_CONFIG}
autoCheck
checkInterval={1_800_000} // Check every 30 minutes
onAuditEvent={(event) => {
analytics.track('app_update_event', event);
}}
>
<AppShell />
</UpdateProvider>
);
}
function AppShell() {
const { policyResult } = useUpdateContext();
if (policyResult?.isForced) {
return (
<ForceUpdateScreen
theme={{
primaryColor: '#003366',
backgroundColor: '#001428',
}}
/>
);
}
return (
<>
<MainNavigator />
<UpdateBanner
position="bottom"
theme={{
primaryColor: '#003366',
backgroundColor: 'rgba(0, 20, 40, 0.95)',
}}
/>
</>
);
}🎨 Theming
All UI components accept an UpdateUITheme object:
interface UpdateUITheme {
primaryColor?: string; // Brand color (buttons, progress)
backgroundColor?: string; // Card / screen background
textColor?: string; // Primary text
secondaryTextColor?: string; // Subtitle / secondary text
buttonTextColor?: string; // Button label color
progressBarColor?: string; // Progress bar fill
progressBarTrackColor?: string; // Progress bar track
borderRadius?: number; // Corner radius
fontFamily?: string; // Body font family
headingFontFamily?: string; // Heading font family
}📋 Type Definitions
All types are exported from the package root:
import type {
UpdateType,
UpdateAvailability,
InstallStatus,
UpdateInfo,
DownloadProgress,
UpdateResult,
UpdatePolicy,
PolicyEvaluationResult,
AuditEvent,
AuditEventType,
AuditLoggerConfig,
UpdateUITheme,
UpdateBannerProps,
ForceUpdateScreenProps,
UpdateProviderProps,
UseInAppUpdateOptions,
UseInAppUpdateResult,
MockConfig,
} from 'expo-in-app-update-enterprise';See src/ExpoInAppUpdate.types.ts for full JSDoc documentation on every type.
🧪 Testing
Use MockConfig for development and testing without the Play Store:
import { MockInAppUpdate } from 'expo-in-app-update-enterprise/testing';
const mock = new MockInAppUpdate({
updateAvailable: true,
availableVersionCode: 5,
availableVersionName: '2.0.0',
updatePriority: 3,
stalenessDays: 5,
downloadDurationMs: 3000,
simulateFailure: false,
simulateCancellation: false,
});
// In your test setup:
jest.mock('expo-in-app-update-enterprise/ExpoInAppUpdateModule', () => mock);Testing Policy Engine
import { PolicyEngine } from 'expo-in-app-update-enterprise';
describe('PolicyEngine', () => {
it('forces update when below minimum version', () => {
const engine = new PolicyEngine({ minimumVersion: 10 });
const result = engine.evaluate(
{ ...mockUpdateInfo, currentVersionCode: 5 },
0,
);
expect(result.isForced).toBe(true);
expect(result.resolvedUpdateType).toBe(UpdateType.IMMEDIATE);
expect(result.triggeredRule).toBe('minimumVersion');
});
it('suggests flexible update for recommended version', () => {
const engine = new PolicyEngine({ recommendedVersion: 20 });
const result = engine.evaluate(
{ ...mockUpdateInfo, currentVersionCode: 15 },
0,
);
expect(result.shouldUpdate).toBe(true);
expect(result.resolvedUpdateType).toBe(UpdateType.FLEXIBLE);
expect(result.isForced).toBe(false);
});
});📱 Platform Support
| Feature | Android | iOS | |---|:---:|:---:| | Flexible updates | ✅ Native | ⚠️ Store redirect | | Immediate updates | ✅ Native | ⚠️ Store redirect | | Download progress | ✅ Real-time | ❌ N/A | | Version checking | ✅ Play Store | ✅ iTunes API | | Update priority | ✅ 0-5 scale | ❌ Always 0 | | Staleness days | ✅ Native | ❌ N/A | | In-app install | ✅ Native | ❌ Store only | | Policy engine | ✅ Full | ✅ Full | | Audit logging | ✅ Full | ✅ Full | | BackHandler block | ✅ Native | — N/A |
🤝 Contributing
Contributions are welcome! Please follow these steps:
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'feat: add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Development Setup
# Clone the repo
git clone https://github.com/lewisirimu/expo-in-app-update.git
cd expo-in-app-update-enterprise
# Install dependencies
yarn install
# Build
yarn build
# Run tests
yarn test
# Lint
yarn lintCommit Convention
This project uses Conventional Commits:
feat:— New featurefix:— Bug fixdocs:— Documentation onlyrefactor:— Code refactor (no feature/fix)test:— Tests onlychore:— Build/tooling changes
📄 License
MIT © Munene
See LICENSE for details.
