react-native-mrd-sdk
v2.0.7
Published
Tenant-scoped admin-panel API wrapper SDK for React Native push registration
Readme
react-native-mrd-sdk
react-native-mrd-sdk is a React Native SDK for MRD tenant push registration.
It is a small, typed wrapper around the MRD admin-panel public push APIs.
The host app owns Firebase, permission prompts, token refresh listeners, system notification UI, and app navigation. The SDK owns the MRD API contract around those values: signed requests, typed helper methods, response validation, device metadata, identity helpers, tag helpers, delivery/open reporting helpers, in-app message detail fetching, optional in-app modal display, retries, and React readiness state.
Table Of Contents
- What This SDK Does
- Step-By-Step Client Installation Guide
- API Reference
- Types And Accepted Values
- Signed Request Contract
- Error Handling
- Offline Retry Queue
- Push Payload Helpers
- Troubleshooting
- Verification
What This SDK Does
The SDK is designed around an appId from the MRD admin panel.
It handles:
- signed tenant public push app lookup by
appId - signed identity upsert by
appId - signed push subscription registration by
appId - signed token-state update by URL-encoded token
- signed push delivery/open event reporting for campaign analytics
- signed in-app message detail fetch by message id
- signed in-app lifecycle event reporting for campaign analytics
- signed user readback by
externalId - signed tag merge, replace, and delete by
externalId - request timestamp, nonce, body hash, HMAC signature, request id, and SDK version
- best-effort app/device metadata from
react-native-device-info - current-device token orchestration through a client-provided
pushTokenProvider - current-device permission status resolution for Android, with optional custom provider support
- identity lifecycle helpers such as
login,logout, andgetCurrentIdentity - local tag mutation batching before an identity is known
- optional offline sync retry queue
- React provider, hooks, and readiness boundary
- opt-in React in-app message provider and modal host
- push payload normalization helpers for app-side receive/open handling
- optional React Native Firebase handler helpers for app-provided
messaging()
It does not handle:
- Firebase installation or native Firebase configuration
- Firebase service-account handling or server-side push sending
- Firebase token acquisition unless the app provides
pushTokenProvider - notification permission UX owned by the app
- automatic Firebase listener installation or app navigation
- system notification display
- anonymous token-only registration without
externalId,email, orphone
Step-By-Step Client Installation Guide
Follow these steps in order for a normal React Native Firebase Messaging integration.
Step 1: Confirm What The App Must Own
Before installing the SDK, make sure the client app team understands this split:
| Owner | Responsibility | | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Client app | Firebase installation, native app setup, notification permission UX, FCM token retrieval, token refresh listeners, system notification display, and app navigation. | | MRD SDK | Signed MRD API calls, request headers, HMAC signature, request id, SDK version, normalized responses, identity helpers, tag helpers, device metadata, delivery/open reporting helpers, in-app detail fetch/modal helpers, retry behavior, React readiness state. |
The SDK does not configure Firebase. It can wire an app-provided
@react-native-firebase/messaging instance into MRD delivery/open reporting,
but the app still owns where those handlers are registered and what happens
after a notification tap.
Step 2: Install The SDK
Install the SDK:
yarn add react-native-mrd-sdkStep 3: Install Firebase Messaging In The Host App
If your app uses Firebase Cloud Messaging, install React Native Firebase:
yarn add @react-native-firebase/app @react-native-firebase/messagingThen complete the native Firebase setup for your iOS and Android apps:
- React Native Firebase: https://rnfirebase.io/
- React Native Firebase Messaging: https://rnfirebase.io/messaging/usage
This usually includes Firebase config files such as GoogleService-Info.plist
for iOS and google-services.json for Android, plus the native Gradle/CocoaPods
setup required by your app.
Step 4: Install Device Metadata Support
Install react-native-device-info directly in the host app. The SDK uses it for
best-effort app/device metadata, and a direct app dependency makes native
autolinking more reliable:
yarn add react-native-device-infoLibrary reference: https://github.com/react-native-device-info/react-native-device-info
Step 5: Run Native Install Steps
For iOS, run CocoaPods after adding native dependencies:
cd ios
pod install
cd ..Android 13 and newer require the POST_NOTIFICATIONS runtime permission for
notifications. The SDK can check/request that permission during current-device
registration, but your app should still own the user-facing permission timing
and explanation.
Step 6: Collect Required MRD Admin Values
Get these values from the MRD admin panel or your tenant app configuration:
| Name | Required | Description |
| -------------- | -------- | ---------------------------------------------------- |
| apiBaseUrl | Yes | Tenant API origin. It may include /api or omit it. |
| appId | Yes | Public push app id from the admin panel. |
| sdkAuthToken | Yes | Static SDK signing token from the admin panel. |
Example:
const apiBaseUrl = 'https://foo.localhost:5173';
const appId = 'mrd_htajyzzysucj41h6omw9wvci';
const sdkAuthToken = 'mrd_sdk_REPLACE_WITH_ADMIN_PANEL_TOKEN';Keep sdkAuthToken out of source control. Load it from your app environment,
remote config, or another secure configuration path appropriate for your app.
Step 7: Decide Required And Optional Runtime Values
Registration requires a push token, platform, and at least one user identity.
You can pass token/platform per call, or configure pushTokenProvider once and
use the current-device helpers.
| Name | Required | Description |
| --------------------------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------- |
| token | Yes for explicit-token methods | FCM/APNs-backed device token. |
| platform | Yes for explicit-token methods | One of ios, android, or web. |
| pushTokenProvider | Yes for current-device methods unless token is passed | App-provided function that returns the current device token. |
| externalId, email, or phone | Yes for identity, sync, and registration | At least one identity is required. Prefer stable externalId when available. |
These values are optional and can be passed only when your app has them:
| Name | Description |
| ----------------------- | --------------------------------------------------------------------------------- |
| firstName, lastName | Profile fields sent during identity or registration calls. |
| attributes | Custom user attributes as an object. |
| tags | User tags as an object, or an array for backend-compatible registration payloads. |
| status | Subscription status: subscribed, unsubscribed, pending, or invalid. |
| osPermission | OS permission: granted, denied, unknown, or provisional. |
| backendCode | App/backend diagnostic code for registration or token state updates. |
| reason | Human-readable reason for token state updates. |
| meta | Additional subscription metadata. Merged with SDK-generated device metadata. |
| requestId | Override generated request id when you need your own correlation id. |
| sdkVersion | Override generated SDK version in registration payloads. |
| appId | Override the default client appId for a specific method call. |
Step 8: Create One Reusable SDK Client
Create a single client and reuse it for the app lifetime. The recommended setup
is to configure pushTokenProvider once, then use current-device helpers.
// mrdSdk.ts
import messaging from '@react-native-firebase/messaging';
import { Platform } from 'react-native';
import { createMrdSdkClient } from 'react-native-mrd-sdk';
const apiBaseUrl = 'https://foo.localhost:5173';
const appId = 'mrd_htajyzzysucj41h6omw9wvci';
const sdkAuthToken = 'mrd_sdk_REPLACE_WITH_ADMIN_PANEL_TOKEN';
export const mrd = createMrdSdkClient({
apiBaseUrl,
appId,
sdkAuthToken,
pushTokenProvider: async ({ forceRefresh }) => {
const token = await messaging().getToken();
return {
isNewToken: forceRefresh,
platform: Platform.OS === 'ios' ? 'ios' : 'android',
provider: 'firebase',
token,
};
},
deviceMeta: {
build_channel: 'production',
},
});createMrdSdkClient accepts these options:
| Option | Required | Description |
| ------------------------------ | ----------- | -------------------------------------------------------------------------------------- |
| apiBaseUrl | Usually yes | Base tenant API URL. Required before network calls unless supplied by runtimeConfig. |
| appId | Usually yes | Default app id used by methods when no per-call appId is passed. |
| sdkAuthToken | Usually yes | SDK signing token. Required before public push requests. |
| pushTokenProvider | Optional | Token provider used by current-device helpers. |
| pushPermissionProvider | Optional | Custom permission resolver. Useful for iOS or app-owned permission flows. |
| deviceInfoProvider | Optional | Adds custom device metadata. |
| deviceMeta | Optional | Static metadata merged into every registration/state metadata payload. |
| fetcher | Optional | Custom fetch implementation. |
| headers | Optional | Extra headers added to SDK requests. |
| offlineSyncQueue | Optional | Enables/configures retry queue for sync operations. |
| onIdentityChanged | Optional | Callback fired when local identity changes. |
| onPushSubscriptionSynced | Optional | Callback fired after successful sync. |
| onPushSubscriptionSyncFailed | Optional | Callback fired after failed sync. |
| publicAuth | Optional | Advanced signing overrides for tests or custom signing. |
| requestIdFactory | Optional | Custom request id generator for registration/state operations. |
| runtimeConfig | Optional | Override API paths or default headers. Mostly for tests/versioning. |
| sdkVersion | Optional | Overrides default SDK version metadata. |
If the admin panel rotates the signed SDK auth token while the app is running, update the existing client:
mrd.setSdkAuthToken(nextSdkAuthToken);Step 9: Validate Admin App Metadata
Call initialize() during startup if you want to fail early when the admin app
is disabled, missing credentials, or missing platform support:
await mrd.initialize();This calls:
GET /api/public/push/apps/{appId}Step 10: Identify The Current User
When the app knows the current user, send identity information to the backend:
await mrd.login({
externalId: 'customer-42',
email: '[email protected]',
phone: '+6281234567890',
firstName: 'Jane',
lastName: 'Tester',
attributes: {
country: 'ID',
language: 'id',
},
tags: {
plan: 'pro',
lifecycle: 'trial',
},
});This calls:
PUT /api/public/push/apps/{appId}/users/identifyexternalId is strongly recommended because user readback and tag endpoints are
keyed by externalId.
Step 11: Register And Sync The Current Device
For most apps, call initializeAndSyncCurrentDevice() after login or when the
app has a stable identity:
await mrd.initializeAndSyncCurrentDevice({
externalId: 'customer-42',
email: '[email protected]',
tags: {
plan: 'pro',
locale: 'id',
},
});That one call:
- Fetches push app metadata.
- Checks app enabled, credentials, and platform support.
- Identifies or updates the user.
- Resolves the push token through
pushTokenProvider. - Registers the device subscription.
The API sequence is:
GET /api/public/push/apps/{appId}
PUT /api/public/push/apps/{appId}/users/identify
POST /api/public/push/apps/{appId}/subscriptionsUse identify: false only when the user was just identified and you explicitly
want to skip the identify call:
await mrd.initializeAndSyncCurrentDevice({
externalId: 'customer-42',
identify: false,
});Step 12: Register With An Explicit Token When Needed
If the call site already has the token and platform, use explicit-token sync:
await mrd.initializeAndSync({
token: fcmToken,
platform: 'android',
externalId: 'customer-42',
osPermission: 'granted',
tags: {
plan: 'pro',
},
});Or use the lower-level registration helper:
await mrd.registerPushSubscription({
token: fcmToken,
platform: 'android',
externalId: 'customer-42',
status: 'subscribed',
});Step 13: Handle Firebase Token Refresh
The app owns Firebase token refresh listeners. When Firebase gives you a new token, force a sync with that token:
messaging().onTokenRefresh(async (token) => {
await mrd.initializeAndSyncCurrentDevice({
token,
externalId: 'customer-42',
forceSync: true,
});
});Passing token to a current-device method overrides pushTokenProvider for
that call.
Step 14: Update Permission Or Token State Changes
When the user disables notifications, the token becomes invalid, or your app wants to send a heartbeat-style update, call a token-state method:
await mrd.updateCurrentDevicePushSubscriptionState({
status: 'unsubscribed',
osPermission: 'denied',
backendCode: 'PERMISSION_DENIED',
reason: 'User disabled notifications in settings',
meta: {
source: 'settings_screen',
},
});This calls:
PUT /api/public/push/apps/{appId}/subscriptions/by-token/{urlEncodedToken}The SDK URL-encodes the token and signs the encoded path.
Step 15: Report Delivered And Opened Events
The admin sender should include MRD tracking data in every FCM data payload so
the app-side SDK can connect receive/open events back to the dashboard. FCM data
values are strings, so IDs should be stringified.
Supported FCM data keys:
app_idchannel_idcampaign_idcontact_iddelivery_idmessage_idprovider_message_idautomation_idnode_idtenant_idanalytics_labelmrd_tracking_versionmrd_event_endpointmrd_report_deliverymrd_report_openmrd_tracking
mrd_tracking may contain the same values as JSON. The SDK reads both the
top-level keys and the JSON object, with delivery_id as the preferred
dashboard identity. If delivery_id is missing, the event must include
message_id, provider_message_id, or both campaign_id and contact_id.
For normal campaign sends, include at least app_id, campaign_id,
contact_id, delivery_id, mrd_tracking_version, and mrd_tracking.
Manual reporting is available when your app already owns Firebase handlers:
messaging().onMessage(async (remoteMessage) => {
await mrd.reportPushDeliveryEvent({
event: 'delivered',
platform: Platform.OS === 'ios' ? 'ios' : 'android',
remoteMessage,
});
});
messaging().onNotificationOpenedApp(async (remoteMessage) => {
await mrd.reportPushDeliveryEvent({
event: 'opened',
platform: Platform.OS === 'ios' ? 'ios' : 'android',
remoteMessage,
});
});This calls:
POST /api/public/push/apps/{appId}/eventsFor React Native Firebase, the SDK also provides narrow handler helpers. They
do not install Firebase or own navigation; they only use the messaging()
instance you pass in.
Register background delivery handling as early as possible, commonly in
index.js or index.ts:
import messaging from '@react-native-firebase/messaging';
import { AppRegistry, Platform } from 'react-native';
import { registerMrdFirebaseBackgroundDeliveryHandler } from 'react-native-mrd-sdk';
import { mrd } from './src/mrd';
import { App } from './src/App';
registerMrdFirebaseBackgroundDeliveryHandler({
client: mrd,
messaging: messaging(),
platform: Platform.OS === 'ios' ? 'ios' : 'android',
});
AppRegistry.registerComponent('app', () => App);Register foreground receive, background tap, and cold-start tap handling from your app lifecycle:
import messaging from '@react-native-firebase/messaging';
import { Platform } from 'react-native';
import { subscribeMrdFirebaseNotificationHandlers } from 'react-native-mrd-sdk';
const subscription = subscribeMrdFirebaseNotificationHandlers({
client: mrd,
messaging: messaging(),
platform: Platform.OS === 'ios' ? 'ios' : 'android',
onNotificationOpened: async ({ payload }) => {
// Navigate with payload.launchUrl or resolveMrdPushOpenAction(...).
},
});
await subscription.initialNotificationHandled;
// Later, for example from a React effect cleanup:
subscription.unsubscribe();Handler mapping:
- foreground
onMessagereportsdelivered - background/quit
setBackgroundMessageHandlerreportsdeliveredwhen the JavaScript handler receives the payload - background tap
onNotificationOpenedAppreportsopened - cold-start tap
getInitialNotificationreportsopened
FCM's own reporting dashboard and BigQuery export can provide broader delivery metrics with batching delay. The SDK event endpoint is for immediate MRD-dashboard campaign analytics tied to the IDs in the FCM data payload.
Step 16: Handle In-App Messages
In-app campaigns arrive as lightweight Firebase data payloads with
type: "in_app" or mrd_message_type: "in_app". They intentionally contain
only routing and tracking fields, including message_id and
message_detail_url; the modal template or HTML is fetched by the SDK.
Manual fetch:
const envelope = normalizeMrdInAppMessageEnvelope(remoteMessage);
if (envelope) {
const message = await mrd.getInAppMessage({
envelope,
});
await mrd.reportInAppMessageEvent({
event: 'in_app_received',
envelope,
message,
platform: Platform.OS === 'ios' ? 'ios' : 'android',
});
}This calls:
GET /api/public/push/apps/{appId}/messages/{messageId}
POST /api/public/push/apps/{appId}/eventsFor foreground display, opt into the SDK modal host through the provider:
<MrdSdkProvider
apiBaseUrl={apiBaseUrl}
appId={appId}
inAppMessaging={{
messaging: messaging(),
platform: Platform.OS === 'ios' ? 'ios' : 'android',
htmlRenderer: ({ html }) => {
// Return your WebView renderer when using HTML templates.
return <MyInAppWebView html={html} />;
},
}}
sdkAuthToken={sdkAuthToken}
>
<RootApp />
</MrdSdkProvider>Native custom-build templates render with the SDK modal. HTML templates require
an htmlRenderer callback, usually backed by a host-app react-native-webview
installation. The SDK does not import WebView directly.
Step 17: Manage Tags
Tags can be sent after login or queued before identity exists.
Queue tags before login:
await mrd.addTags({
lifecycle: 'trial',
plan: 'pro',
});
await mrd.login({
externalId: 'customer-42',
});Send tags directly for a known user:
await mrd.addTags(
{
plan: 'enterprise',
},
{
externalId: 'customer-42',
mode: 'merge',
}
);
await mrd.removeTags(['lifecycle'], {
externalId: 'customer-42',
});Tag endpoints:
PUT /api/public/push/apps/{appId}/users/{externalId}/tags
DELETE /api/public/push/apps/{appId}/users/{externalId}/tagsStep 18: Read Back User State
Read the backend user state and tags when you need to verify what is stored:
const user = await mrd.getUser({
externalId: 'customer-42',
});
const tags = await mrd.getTags({
externalId: 'customer-42',
});This calls:
GET /api/public/push/apps/{appId}/users/{externalId}Step 19: Add React Provider Only If Components Need SDK State
The SDK can be used without React context. Add MrdSdkProvider only when
components need readiness state, refresh(), or the shared client.
For the most complete provider setup, create the client yourself and pass it
through client. This keeps pushTokenProvider and other client-level
providers available to methods called from React components.
import messaging from '@react-native-firebase/messaging';
import { Button, Platform } from 'react-native';
import {
MrdSdkProvider,
MrdSdkReadyBoundary,
useMrdSdk,
createMrdSdkClient,
} from 'react-native-mrd-sdk';
const mrd = createMrdSdkClient({
apiBaseUrl: 'https://foo.localhost:5173',
appId: 'mrd_htajyzzysucj41h6omw9wvci',
sdkAuthToken: 'mrd_sdk_REPLACE_WITH_ADMIN_PANEL_TOKEN',
pushTokenProvider: async () => ({
platform: Platform.OS === 'ios' ? 'ios' : 'android',
provider: 'firebase',
token: await messaging().getToken(),
}),
});
function PushRegisterButton() {
const sdk = useMrdSdk();
async function register() {
await sdk.client.initializeAndSyncCurrentDevice({
externalId: 'customer-42',
tags: {
plan: 'pro',
},
});
}
return <Button title="Register push" onPress={register} />;
}
export function App() {
return (
<MrdSdkProvider
apiBaseUrl="https://foo.localhost:5173"
appId="mrd_htajyzzysucj41h6omw9wvci"
sdkAuthToken="mrd_sdk_REPLACE_WITH_ADMIN_PANEL_TOKEN"
client={mrd}
>
<MrdSdkReadyBoundary loadingFallback={null} errorFallback={null}>
<PushRegisterButton />
<RootApp />
</MrdSdkReadyBoundary>
</MrdSdkProvider>
);
}Provider state contains:
| Field | Description |
| ------------ | -------------------------------------------- |
| client | The MrdSdkClient instance. |
| apiBaseUrl | Current base URL. |
| appId | Current app id. |
| status | idle, loading, ready, or error. |
| pushApp | Normalized app metadata when ready. |
| error | Initialization error when status is error. |
| refresh() | Re-runs provider initialization. |
MrdSdkReadyBoundary renders children only when provider status is ready.
MrdSdkBootstrapBoundary is retained as a compatibility alias.
Step 19: Verify The Integration
Use this checklist before marking the client integration done:
- Firebase is installed and configured in the app.
messaging().getToken()returns a token on the target device.apiBaseUrl,appId, andsdkAuthTokenare from the same tenant.mrd.initialize()succeeds.mrd.initializeAndSyncCurrentDevice()succeeds with a valid identity.- Firebase token refresh calls sync the refreshed token.
- Permission changes call
updateCurrentDevicePushSubscriptionState(). - MRD FCM data payloads include
app_id,campaign_id,contact_id,delivery_id, andmrd_tracking. - Foreground/background receive handlers report
deliveredwhen available. - Open handlers report
openedfor background and cold-start taps. - App navigation from notification taps remains implemented in the app.
- Optional payload helpers are used if the app needs MRD launch URL/button parsing.
API Reference
createMrdSdkClient(options)
Creates an MrdSdkClient.
Required for network use:
apiBaseUrlappIdsdkAuthToken
Common optional:
pushTokenProviderpushPermissionProviderdeviceMetadeviceInfoProviderofflineSyncQueue- lifecycle callbacks
client.initialize(appId?)
Fetches and normalizes push app metadata.
Returns Promise<MrdSdkPushApp>.
client.getPushApp(appId?)
Same network operation as initialize. Useful when you want a method name that
describes the read operation directly.
Returns Promise<MrdSdkPushApp>.
client.login(identity)
Identifies or updates a user, stores the identity locally, notifies identity
listeners when it changes, and flushes queued tag mutations when externalId
exists.
Required:
- at least one of
externalId,email, orphone
Optional:
appIdfirstNamelastNameattributestags
Returns Promise<MrdSdkIdentifyUserResult>.
client.identifyUser(identity)
Sends the identify request and normalizes the result. It does not perform the
same local identity lifecycle work as login.
Returns Promise<MrdSdkIdentifyUserResult>.
client.logout()
Clears local SDK identity and notifies identity listeners. It does not call the backend.
client.getCurrentIdentity()
Returns the locally stored identity, or undefined.
client.onIdentityChanged(callback)
Registers an identity listener.
Returns an unsubscribe function.
const unsubscribe = mrd.onIdentityChanged((event) => {
console.log(event.previousIdentity, event.currentIdentity);
});
unsubscribe();client.getCurrentDevicePushToken(options?)
Resolves the current device token.
Optional:
appIdforceRefreshplatformtoken
If token is passed, the SDK returns it directly. Otherwise, a
pushTokenProvider must be configured.
Returns Promise<MrdSdkPushTokenProviderResult>.
client.registerCurrentDevicePushSubscription(options)
Registers the current device token resolved from pushTokenProvider, unless
token is passed directly.
Required:
- at least one of
externalId,email, orphone pushTokenProvider, unlesstokenis passed- resolvable
platform, either passed or returned bypushTokenProvider
Optional:
appIdtokenplatformforceRefreshTokenfirstNamelastNameattributestagsstatusosPermissionbackendCoderequestIdsdkVersionmeta
Returns Promise<MrdSdkCurrentDevicePushSubscriptionRegistrationResult>.
client.registerPushSubscription(options)
Registers an explicit token.
Required:
tokenplatform- at least one of
externalId,email, orphone
Optional:
appIdfirstNamelastNameattributestagsstatusosPermissionbackendCoderequestIdsdkVersionmeta
Returns Promise<MrdSdkPushSubscriptionRegistrationResult>.
client.initializeAndSyncCurrentDevice(options)
High-level current-device orchestration:
- Reads push app metadata.
- Verifies app status, credentials, and platform support.
- Identifies the user unless
identify: false. - Registers the current device token.
Required and optional registration fields are the same as
registerCurrentDevicePushSubscription.
Additional optional:
identifyforceSyncenqueueOfflineRetryonPushSubscriptionSyncedonPushSubscriptionSyncFailed
Returns Promise<MrdSdkInitializeAndSyncCurrentDeviceResult>.
client.initializeAndSync(options)
High-level explicit-token orchestration. Same as
initializeAndSyncCurrentDevice, but requires token and platform.
Returns Promise<MrdSdkInitializeAndSyncPushSubscriptionResult>.
client.initializeAndSyncPushSubscription(options)
Alias for initializeAndSync(options).
client.syncPushSubscription(options)
Runs the same sync flow as initializeAndSync, including app readiness checks,
optional identify, deduping, callbacks, and optional offline retry queueing.
Use this when the name better fits your code path. Most integrations can call
initializeAndSync or initializeAndSyncCurrentDevice directly.
Returns Promise<MrdSdkPushSubscriptionSyncResult>.
client.updateCurrentDevicePushSubscriptionState(options)
Updates status/permission metadata for the current device token.
Required:
pushTokenProvider, unlesstokenis passed- resolvable
platform, either passed or returned bypushTokenProvider
Optional:
appIdtokenplatformforceRefreshTokenstatusosPermissionbackendCodereasonrequestIdmeta
Returns Promise<MrdSdkCurrentDevicePushSubscriptionStateUpdateResult>.
client.updatePushSubscriptionState(options)
Updates status/permission metadata for an explicit token.
Required:
tokenplatform
Optional:
appIdstatusosPermissionbackendCodereasonrequestIdmeta
Returns Promise<MrdSdkPushSubscriptionStateUpdateResult>.
client.reportPushDeliveryEvent(options)
Reports an app-observed push delivery event to the MRD dashboard.
Required:
event:deliveredoropened- a delivery identity from one of:
deliveryIdmessageIdproviderMessageIdcampaignIdwithcontactId
Common optional:
appIdremoteMessagedataplatformoccurredAtrequestIdsdkVersiontoken
When remoteMessage or data contains MRD tracking keys, the SDK reads
app_id, campaign_id, contact_id, delivery_id, message_id,
provider_message_id, mrd_tracking, and the report flags automatically.
Returns Promise<MrdSdkPushDeliveryEventResult>.
client.getInAppMessage(messageIdOrOptions, options?)
Fetches an in-app message detail document using the signed SDK auth headers.
Use the envelope from normalizeMrdInAppMessageEnvelope(remoteMessage) when
handling Firebase payloads so the SDK can reuse the backend-provided
message_detail_url.
Required:
messageId, or anenvelopecontainingmessageId
Optional:
appIdmessageDetailPath
Returns Promise<MrdSdkInAppMessageDetail> with a normalized template whose
renderer is native or html.
client.reportInAppMessageEvent(options)
Reports in-app lifecycle analytics through the same signed public events endpoint.
Required:
event: one ofin_app_received,in_app_displayed,in_app_dismissed,in_app_button_clicked,in_app_action_success,in_app_action_failed, orin_app_render_failed- a delivery identity from one of:
deliveryIdmessageIdcampaignIdwithcontactId
Common optional:
appIdenvelopemessageremoteMessageplatformtemplateRendererbuttonIdactionIdactionTypeerrorReasonoccurredAt
Returns Promise<MrdSdkInAppMessageEventResult>.
client.addTags(tags, options?)
Adds or replaces tags. If no externalId is known, the mutation is queued
locally.
Required:
tagsobject
Optional:
appIdexternalIdmode:mergeorreplace, defaultmerge
Returns Promise<MrdSdkUserTagsResult>.
client.removeTags(tagKeys, options?)
Deletes tags. If no externalId is known, the mutation is queued locally.
Required:
- non-empty
tagKeysarray
Optional:
appIdexternalId
Returns Promise<MrdSdkUserTagsResult>.
client.getTags(options?)
Returns backend tags for an externalId, or local queued tags when no
externalId is known.
Optional:
appIdexternalId
Returns Promise<Record<string, unknown>>.
client.getUser(options?)
Reads backend user state by externalId.
Required:
externalId, either passed directly or available from local identity
Optional:
appId
Returns Promise<MrdSdkPublicUserState>.
client.requestPublicPushJson(options)
Low-level signed request helper for public push endpoints that do not yet have a first-class method.
Required:
methodpathsdkAuthTokenconfigured on the client
Optional:
appIdbodyheaders
The helper signs the request and returns parsed JSON. It validates the HTTP method but does not normalize the response to a high-level SDK type.
client.setSdkAuthToken(newToken)
Rotates the local SDK signing token.
Returns the normalized token string.
client.getSdkAuthToken()
Returns the current SDK auth token, or undefined.
client.getApiBaseUrl()
Returns the resolved API base URL.
client.getAppId()
Returns the default app id configured on the client, or undefined.
client.flushOfflineSyncQueue()
Attempts to flush queued sync retries immediately.
client.getOfflineSyncQueueEntries()
Returns queue entry summaries containing:
idappIdattemptsnextAttemptAt
client.getOfflineSyncQueueSize()
Returns the number of queued sync retry entries.
initializeMrdSdk(options)
Creates or uses a client, calls initialize, and returns a ready React-style
state object.
Required:
apiBaseUrlappIdsdkAuthToken
Optional:
client- most client setup fields used for initialization
autoInitializewhen used through provider/hook setup
useMrdSdk()
Reads the nearest MrdSdkProvider context.
Throws MrdSdkError with code MRD_SDK_CONTEXT_MISSING when used outside the
provider.
useMrdSdkReadyState()
Returns provider state only when status is ready; otherwise returns null.
useMrdSdkInitialization(options)
React hook used by MrdSdkProvider. Most apps should use MrdSdkProvider
instead of calling this hook directly.
MrdSdkProvider
Creates SDK context and initializes app metadata by default.
Required:
apiBaseUrlappIdsdkAuthTokenchildren
Optional:
clientautoInitialize- initialization callbacks and request options
When you need current-device helpers from React components, prefer passing a
prebuilt client with pushTokenProvider.
MrdSdkReadyBoundary
Renders:
idleFallbackwhen state isidleloadingFallbackwhen state isloadingerrorFallbackwhen state iserrorchildrenwhen state isready
MrdSdkBootstrapBoundary
Compatibility alias for MrdSdkReadyBoundary.
Types And Accepted Values
Platforms
type MrdSdkPushPlatform = 'ios' | 'android' | 'web';For normal React Native push registration, use ios or android.
Subscription Status
type MrdSdkPushSubscriptionStatus =
| 'subscribed'
| 'unsubscribed'
| 'pending'
| 'invalid';Default registration status is:
unsubscribedwhenosPermissionisdeniedsubscribedotherwise
OS Permission
type MrdSdkPushOsPermission = 'granted' | 'denied' | 'unknown' | 'provisional';Identity
At least one identity field is required for identity, registration, and sync:
{
externalId?: string;
email?: string;
phone?: string;
}Prefer externalId because tag and user readback endpoints are keyed by
externalId.
Tags
Most SDK tag helpers expect an object:
{
plan: 'pro',
lifecycle: 'trial',
beta: true,
}Registration also accepts a tag array for backend-compatible payloads, but SDK local tag snapshots are object-based.
Device Metadata
The SDK sends best-effort metadata under meta, including values such as:
runtime_platformapp_nameapp_versionbuild_numberbundle_iddevice_branddevice_modeldevice_typesystem_namesystem_versionos_versiontime_zonereact_native_versionapi_levelis_tabletis_emulator
Unavailable fields are omitted. If metadata collection fails, the SDK falls back to minimal metadata and continues.
Signed Request Contract
All public admin-panel calls are signed by the SDK. Client apps never need to build signing headers manually.
The SDK sends:
X-MRD-App-IDX-MRD-TimestampX-MRD-NonceX-MRD-SignatureX-MRD-Signature-Version
Canonical string:
METHOD
/api/public/push/apps/{appId}/...
appId
unixTimestampSeconds
nonce
sha256(rawRequestBody)Important signing details:
- The signed path always starts with
/api/public/.... - If
apiBaseUrlalready includes/api, the request URL avoids duplicating/api. - Token-state updates URL-encode the token in the path and sign the encoded path.
- The SDK retries once for bounded auth-freshness errors such as expired timestamp or replay nonce.
Default public paths:
| Operation | Path |
| --------------------- | ------------------------------------------------------------ |
| Push app lookup | /api/public/push/apps/:appId |
| Identify user | /api/public/push/apps/:appId/users/identify |
| Register subscription | /api/public/push/apps/:appId/subscriptions |
| Update token state | /api/public/push/apps/:appId/subscriptions/by-token/:token |
| Read user | /api/public/push/apps/:appId/users/:externalId |
| Mutate tags | /api/public/push/apps/:appId/users/:externalId/tags |
Error Handling
SDK-originated errors use MrdSdkError:
import { MrdSdkError } from 'react-native-mrd-sdk';
try {
await mrd.initializeAndSyncCurrentDevice({
externalId: 'customer-42',
});
} catch (error) {
if (error instanceof MrdSdkError) {
console.log(error.code, error.message, error.cause);
}
}Common error codes:
| Code | Meaning |
| ------------------------------------------ | ----------------------------------------------------------------------------------------- |
| MRD_SDK_PUBLIC_AUTH_MISSING | sdkAuthToken was not configured before a signed request. |
| MRD_SDK_FETCH_UNAVAILABLE | No global fetch exists and no custom fetcher was supplied. |
| MRD_SDK_INVALID_HTTP_METHOD | Request method is not one of GET, POST, PUT, PATCH, or DELETE. |
| MRD_SDK_INVALID_REQUEST_BODY | Request body could not be serialized to JSON. |
| MRD_SDK_INVALID_RESPONSE_JSON | Response JSON could not be read. |
| MRD_SDK_INVALID_PAYLOAD | Backend payload shape did not match the SDK contract. |
| MRD_SDK_INVALID_PUSH_PLATFORM | Platform is not ios, android, or web. |
| MRD_SDK_INVALID_PUSH_SUBSCRIPTION_STATUS | Status is not one of the supported subscription statuses. |
| MRD_SDK_INVALID_PUSH_OS_PERMISSION | Permission is not one of the supported permission values. |
| MRD_SDK_INVALID_PUSH_TAGS | Tags or tag keys are malformed. |
| MRD_SDK_PUSH_IDENTITY_REQUIRED | No externalId, email, or phone was provided. |
| MRD_SDK_PUSH_TOKEN_PROVIDER_MISSING | Current-device method needs pushTokenProvider or a direct token. |
| MRD_SDK_PUSH_PLATFORM_REQUIRED | Current-device method could not infer platform. |
| MRD_SDK_PUSH_APP_DISABLED | Admin-panel push app is disabled. |
| MRD_SDK_PUSH_APP_CREDENTIALS_MISSING | Admin-panel push app has no Firebase credentials. |
| MRD_SDK_PUSH_PLATFORM_DISABLED | Target platform is disabled for the push app. |
| MRD_SDK_PUBLIC_PUSH_REQUEST_FAILED | Backend request failed. Status/body/backend code are preserved in cause when available. |
| MRD_SDK_CONTEXT_MISSING | useMrdSdk was used outside MrdSdkProvider. |
Offline Retry Queue
The offline retry queue is optional. It applies to sync operations and is meant for transient network/server failures.
const mrd = createMrdSdkClient({
apiBaseUrl,
appId,
sdkAuthToken,
pushTokenProvider,
offlineSyncQueue: {
enabled: true,
storage: AsyncStorage,
baseDelayMs: 2000,
maxDelayMs: 120000,
maxAttempts: 6,
},
});Storage adapter shape:
type MrdSdkKeyValueStorageAdapter = {
getItem: (key: string) => Promise<string | null> | string | null;
setItem: (key: string, value: string) => Promise<void> | void;
removeItem?: (key: string) => Promise<void> | void;
};Useful queue methods:
const size = mrd.getOfflineSyncQueueSize();
const entries = mrd.getOfflineSyncQueueEntries();
await mrd.flushOfflineSyncQueue();Disable queueing for one sync call:
await mrd.initializeAndSyncCurrentDevice({
externalId: 'customer-42',
enqueueOfflineRetry: false,
});Push Payload Helpers
The SDK does not install Firebase, but it provides helpers for normalizing MRD
push payload data and for wiring an app-provided React Native Firebase
messaging() instance into delivery/open reporting.
import {
isMrdInAppMessagePayload,
normalizeMrdInAppMessageEnvelope,
normalizeMrdPushPayloadData,
normalizeMrdPushTrackingData,
resolveMrdPushOpenAction,
shouldReportMrdPushDeliveryEvent,
} from 'react-native-mrd-sdk';
const data = normalizeMrdPushPayloadData(remoteMessage);
const inAppEnvelope = normalizeMrdInAppMessageEnvelope(remoteMessage);
const tracking = normalizeMrdPushTrackingData(remoteMessage);
const shouldReportOpen = shouldReportMrdPushDeliveryEvent(
remoteMessage,
'opened'
);
const action = resolveMrdPushOpenAction(remoteMessage, buttonActionId);normalizeMrdPushPayloadData returns:
appIdcampaignIdcontactIddeliveryIdlaunchUrlbuttonstrackingrawData
normalizeMrdPushTrackingData returns MRD tracking fields used by dashboard
analytics, including appId, campaignId, contactId, deliveryId,
analyticsLabel, eventEndpoint, reportDelivery, and reportOpen.
resolveMrdPushOpenAction returns:
campaignIdactionIdlaunchUrl
shouldReportMrdPushDeliveryEvent returns true only for MRD-marked payloads
with enough identity to report a dashboard event, while respecting
mrd_report_delivery=false and mrd_report_open=false by default.
normalizeMrdInAppMessageEnvelope returns null for normal push payloads. For
in-app payloads it returns the lightweight messageId, messageDetailPath,
tracking fields, and raw FCM data. The helper never expects full template JSON
or HTML inside Firebase data. isMrdInAppMessagePayload is a boolean wrapper.
The Firebase helper exports are:
registerMrdFirebaseBackgroundDeliveryHandlersubscribeMrdFirebaseNotificationHandlers
Both helpers accept a messaging object instead of importing Firebase directly.
When an in-app payload is received, they report in_app_received, optionally
fetch message detail, call onInAppMessageReceived, and skip normal push
delivery/open callbacks.
Troubleshooting
Current-device registration says token provider is missing
Create the client with pushTokenProvider, or pass token directly to the
current-device call.
await mrd.registerCurrentDevicePushSubscription({
token: fcmToken,
platform: 'android',
externalId: 'customer-42',
});Current-device registration cannot infer platform
Pass platform, or return it from pushTokenProvider.
pushTokenProvider: async () => ({
token: await messaging().getToken(),
platform: Platform.OS === 'ios' ? 'ios' : 'android',
});Push app is disabled or platform is disabled
Check the admin-panel push app:
- app is enabled
- credentials exist
- Android/iOS platform is enabled
- the
appIdmatches the app you are configuring
Requests fail after changing apiBaseUrl
apiBaseUrl can include /api or omit it. Do not include public endpoint paths
in apiBaseUrl; the SDK appends the configured public paths.
Good:
apiBaseUrl: 'https://tenant.example.com';
apiBaseUrl: 'https://tenant.example.com/api';Avoid:
apiBaseUrl: 'https://tenant.example.com/api/public/push/apps/...';iOS permission is unknown
The SDK does not own iOS notification permission prompts. Pass osPermission
from your app, or provide pushPermissionProvider.
const mrd = createMrdSdkClient({
apiBaseUrl,
appId,
sdkAuthToken,
pushTokenProvider,
pushPermissionProvider: async () => readIosPushPermissionFromYourApp(),
});Tags are queued instead of sent
addTags and removeTags queue locally until an externalId is available.
Pass externalId to the tag call or call login({ externalId }).
React hook throws context missing
useMrdSdk must be called under MrdSdkProvider.
Verification
For SDK development, run:
yarn typecheck
yarn lint
yarn test --runInBand --watchman=falseFor app integration, verify:
- Firebase is installed and configured in the app.
messaging().getToken()returns a token on a real device or valid simulator setup.apiBaseUrl,appId, andsdkAuthTokencome from the correct tenant.initialize()succeeds.initializeAndSyncCurrentDevice()succeeds after login or with an identity passed.- Token refresh calls sync the new token.
- Permission changes call token-state update.
- MRD FCM payloads include tracking IDs needed for delivery/open reporting.
- Receive handlers report
delivered, and tap handlers reportopened. - Notification tap navigation stays in the app and can use SDK payload helpers.
