@nonito/react-native-sdk
v0.1.0-beta.1
Published
Nonito In-App Messaging SDK for React Native
Maintainers
Readme
@nonito/react-native-sdk
In-app messaging SDK for React Native. Fetches message definitions from the Nonito backend, evaluates trigger rules on-device as users navigate, and renders modals, banners, fullscreen takeovers, and bottom sheets natively.
Install
npm install @nonito/react-native-sdk @react-native-async-storage/async-storage@react-native-async-storage/async-storage is a required peer dependency — the SDK uses it to persist identity and frequency-cap state. On iOS, run cd ios && pod install.
Peer requirements: React >= 18, React Native >= 0.72.
Quick start
Wrap your app in NonitoProvider. It configures the SDK, subscribes to display events, and renders matched messages above your UI.
import { NonitoProvider } from '@nonito/react-native-sdk';
export default function App() {
return (
<NonitoProvider appKey="nk_live_abc123">
<RootNavigator />
</NonitoProvider>
);
}Then track screens and events. Each call re-evaluates trigger rules against the cached messages.
import { useNonito, useTrackScreen } from '@nonito/react-native-sdk';
function CheckoutScreen() {
useTrackScreen('checkout'); // fires on mount
const { identify, trackEvent } = useNonito();
return (
<Button
title="Buy"
onPress={() => trackEvent('purchase', { total: 249 })}
/>
);
}Identify customers after login so attribute-based triggers can match:
const { identify, reset } = useNonito();
await identify('customer_123', { tier: 'gold', total_orders: 5 });
await reset(); // on logoutConfiguration
<NonitoProvider
appKey="nk_live_abc123"
apiBaseUrl="https://api.nonito.xyz"
config={{
logLevel: 'debug',
inAppMessagesEnabled: true,
fetchIntervalMs: 300_000,
flushIntervalMs: 30_000,
}}
>| Option | Default | Purpose |
|---|---|---|
| appKey | — | Required. From the Nonito dashboard. |
| apiBaseUrl | https://api.nonito.xyz | Nonito API base URL. The analytics server owns the /sdk/* endpoints. |
| logLevel | warn | none | error | warn | info | debug |
| inAppMessagesEnabled | true | Master switch for in-app messaging. |
| pushEnabled | true | Reserved — push is not yet implemented. |
| fetchIntervalMs | 300000 | Message definition poll interval. |
| flushIntervalMs | 30000 | Analytics event flush interval. |
How triggering works
On configure, the SDK registers the device, downloads eligible message definitions, and caches them locally. It re-polls on fetchIntervalMs and refetches whenever identity changes.
Every trackScreen() / trackEvent() call runs the trigger engine over the cached set. Rules are OR'd groups of AND'd conditions:
[
{
"id": "g1",
"conditions": [
{ "type": "screen", "operator": "equals", "value": "checkout" },
{ "type": "attribute", "attribute": "total_orders", "operator": "gte", "value": 3 }
]
}
]Condition types: event, screen, attribute, session.
Operators: equals, not_equals, contains, gte, lte, in.
Messages that pass frequency capping are sorted by priority descending, and the first match wins — only one message displays at a time.
equals and not_equals fall back to string comparison, so 5 matches "5". Attribute paths are dot-notated (profile.tier).
Frequency capping
Each message carries a frequency block, enforced on-device and persisted across launches:
| Field | Meaning |
|---|---|
| max_impressions_per_device | Lifetime cap. 0 disables. |
| max_per_session | Per-session cap. Session counters reset on launch. |
| cooldown_hours | Minimum hours between impressions. 0 disables. |
Handling CTAs
NonitoProvider reports CTA clicks and dismisses automatically. To act on them — deep links, navigation — register a handler:
const { sdk } = useNonito();
sdk?.setActionHandler((action) => {
if (action.actionType === 'deep_link' && action.actionValue) {
Linking.openURL(action.actionValue);
}
});Suppress a message conditionally by returning false:
sdk?.setWillDisplayHandler((message) => !isInCriticalFlow);Imperative API
If you'd rather not use the provider:
import { Nonito } from '@nonito/react-native-sdk';
await Nonito.configure({ appKey: 'nk_live_abc123' });
Nonito.shared.trackScreen('checkout');
Nonito.shared.trackEvent('add_to_cart', { sku: 'ABC' });
await Nonito.shared.identify('customer_123', { tier: 'gold' });
await Nonito.shared.shutdown();Nonito.configure() is idempotent — calling it twice warns and returns the existing instance. Nonito.shared throws if accessed before configure.
Testing without a backend
const { sdk } = useNonito();
sdk?.inApp.setTestMessages([myMessageFixture]);Backend endpoints
The SDK expects three endpoints on apiBaseUrl:
| Endpoint | Purpose |
|---|---|
| POST /sdk/register | Register device token + customer identity |
| GET /sdk/in-app-messages | Fetch eligible messages and trigger rules |
| POST /sdk/events | Batch analytics events (impressions, clicks, dismisses) |
Development
npm install
npm test # 173 tests
npm run typecheck
npm run buildTrigger-engine behavior is pinned by shared JSON fixtures in ../fixtures/, run against both this SDK and the Flutter SDK to keep the two implementations in lockstep. See eval/ at the repo root.
License
MIT
