Push primer (soft-ask)
A soft-ask is an in-app card you show before the operating system’s hard permission prompt. Instead of firing the native push dialog cold — where a single denial is permanent — you first present a branded card explaining the value of notifications. Only when the customer taps “Allow” does the SDK trigger the real iOS / Android system prompt. Customers who tap “Not right now” enter a cooldown window, so you don’t nag them on every launch.
Operators author the primer card as an in-app campaign with the render type push_primer. The native iOS (InAppPushPrimer.swift) and Android (InAppPushPrimer.kt) renderers present the card UI, fire the system prompt when “Allow” is tapped, and keep the cooldown bookkeeping. The JS layer’s job is narrow: listen for the canonical events, decide whether to skip the primer, and (for operator preview / debug flows) read or clear the cooldown.
The push primer requires the native module linked — run pod install for iOS and let Android autolink the library, then rebuild the app. On a Metro-only / web build the bridge is absent and the primer functions return safe defaults (the subscribe call logs a warning and returns a no-op unsubscribe).
Push lifecycle basics
Before the primer, wire up the push lifecycle. The default export aegisPush is a singleton that bridges to the native AegisPush module (an RCTEventEmitter). Configure it once, then start() to subscribe to the OS lifecycle hooks the JS runtime cannot receive directly.
import aegisPush from '@active-reach/react-native-sdk';
// 1. Configure the engagement-POST settings (usually done for you by
// aegis.initialize()).
aegisPush.configure({
apiHost: 'https://api.active-reach.ai',
writeKey: 'YOUR_WRITE_KEY',
organizationId: 'org_abc123',
propertyId: 'prop_xyz789',
});
// 2. Start the native module and subscribe to lifecycle events.
aegisPush.start();
// 3. Tear down on logout / unmount.
aegisPush.stop();start() subscribes to four native lifecycle events emitted by the bridge:
| Native event | What it means | JS handler |
|---|---|---|
aegis.token_refresh | APNs (iOS) / FCM (Android) issued a new device token | registerDeviceToken(token) |
aegis.push_delivered | The OS displayed a notification | trackDelivered(...) |
aegis.push_clicked | The customer tapped the notification | trackClicked(...) |
aegis.push_dismissed | The customer swiped the notification away (Android only) | trackDismissed(...) |
You can also call the lifecycle methods directly if you receive these signals from your own push integration:
import aegisPush from '@active-reach/react-native-sdk';
// Re-register a device token returned by your push library.
await aegisPush.registerDeviceToken(token);
// Report engagement explicitly.
await aegisPush.trackDelivered(campaignId, messageId);
await aegisPush.trackClicked(campaignId, messageId);
await aegisPush.trackDismissed(campaignId, messageId);Subscribing to primer events
The native renderer emits six canonical events through the onPushPrimerEvent channel. Subscribe with onPushPrimerEvent — it returns an unsubscribe function.
import { onPushPrimerEvent } from '@active-reach/react-native-sdk';
const unsubscribe = onPushPrimerEvent((event) => {
switch (event.eventName) {
case 'in_app.displayed':
// The primer card mounted.
break;
case 'in_app.dismissed':
// The card was dismissed without a decision.
break;
case 'push_primer.allow_tapped':
// Customer tapped "Allow" — the system prompt is about to show.
break;
case 'push_primer.later_tapped':
// Customer tapped "Not right now" — cooldown begins.
break;
case 'push_primer.permission_granted':
// OS prompt accepted — you can now send push to this customer.
console.log('Opted in for campaign', event.campaignId);
break;
case 'push_primer.permission_denied':
// OS prompt declined.
break;
}
});
// ... later, on unmount
unsubscribe();Each event is a PushPrimerEvent with this shape:
export interface PushPrimerEvent {
eventName: PushPrimerEventName;
campaignId: string;
payload: Record<string, unknown>;
}The six canonical eventName values are: in_app.displayed, in_app.dismissed, push_primer.allow_tapped, push_primer.later_tapped, push_primer.permission_granted, and push_primer.permission_denied.
Skipping the primer when already authorized
Showing a soft-ask to a customer who has already granted notification permission is pure noise. Check isAlreadyAuthorizedForPush() before presenting the primer and skip it when the answer is true.
import { isAlreadyAuthorizedForPush } from '@active-reach/react-native-sdk';
const authorized = await isAlreadyAuthorizedForPush();
if (authorized) {
// No need for the soft-ask — permission already granted.
return;
}
// Otherwise let the in-app campaign present the primer.Managing the cooldown
When a customer taps “Not right now”, the native side records a cooldown so the primer won’t auto-show again until the window elapses. Read it with isInPushPrimerCooldown() and clear it with clearPushPrimerCooldown() — the latter is intended for operator opt-in resets and debug flows, not routine use.
import {
isInPushPrimerCooldown,
clearPushPrimerCooldown,
} from '@active-reach/react-native-sdk';
// Gate a manual re-prompt on the cooldown state.
const cooling = await isInPushPrimerCooldown();
if (!cooling) {
// Safe to surface the primer again.
}
// Operator preview / debug — force the primer to be eligible again.
await clearPushPrimerCooldown();All four primer functions degrade gracefully when the native module is missing: isAlreadyAuthorizedForPush() and isInPushPrimerCooldown() resolve to false, and clearPushPrimerCooldown() resolves without effect. This keeps shared JS code safe to run on builds that haven’t linked the native push module yet.
What’s next
- Push notification setup — FCM (Android) + APNs (iOS)
- React Native SDK reference — install, tracking, and the full API