npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-native-mrd-sdk

v2.0.4

Published

Tenant-scoped admin-panel API wrapper SDK for React Native push registration

Downloads

768

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, and notification handling. The SDK owns the MRD API contract around those values: signed requests, typed helper methods, response validation, device metadata, identity helpers, tag helpers, retries, and React readiness state.

Table Of Contents

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 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, and getCurrentIdentity
  • local tag mutation batching before an identity is known
  • optional offline sync retry queue
  • React provider, hooks, and readiness boundary
  • push payload normalization helpers for app-side open handling

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
  • notification received/opened listeners
  • anonymous token-only registration without externalId, email, or phone

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, foreground/background/opened notification handling. | | MRD SDK | Signed MRD API calls, request headers, HMAC signature, request id, SDK version, normalized responses, identity helpers, tag helpers, device metadata, retry behavior, React readiness state. |

The SDK does not configure Firebase and does not install notification listeners. It receives values from the app and sends them to the MRD tenant public push APIs safely.

Step 2: Install The SDK

Install the SDK:

yarn add react-native-mrd-sdk

Step 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/messaging

Then 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-info

Library 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/identify

externalId 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:

  1. Fetches push app metadata.
  2. Checks app enabled, credentials, and platform support.
  3. Identifies or updates the user.
  4. Resolves the push token through pushTokenProvider.
  5. 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}/subscriptions

Use 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: 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}/tags

Step 16: 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 17: 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 18: 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, and sdkAuthToken are 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().
  • Notification open handling is 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:

  • apiBaseUrl
  • appId
  • sdkAuthToken

Common optional:

  • pushTokenProvider
  • pushPermissionProvider
  • deviceMeta
  • deviceInfoProvider
  • offlineSyncQueue
  • 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, or phone

Optional:

  • appId
  • firstName
  • lastName
  • attributes
  • tags

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:

  • appId
  • forceRefresh
  • platform
  • token

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, or phone
  • pushTokenProvider, unless token is passed
  • resolvable platform, either passed or returned by pushTokenProvider

Optional:

  • appId
  • token
  • platform
  • forceRefreshToken
  • firstName
  • lastName
  • attributes
  • tags
  • status
  • osPermission
  • backendCode
  • requestId
  • sdkVersion
  • meta

Returns Promise<MrdSdkCurrentDevicePushSubscriptionRegistrationResult>.

client.registerPushSubscription(options)

Registers an explicit token.

Required:

  • token
  • platform
  • at least one of externalId, email, or phone

Optional:

  • appId
  • firstName
  • lastName
  • attributes
  • tags
  • status
  • osPermission
  • backendCode
  • requestId
  • sdkVersion
  • meta

Returns Promise<MrdSdkPushSubscriptionRegistrationResult>.

client.initializeAndSyncCurrentDevice(options)

High-level current-device orchestration:

  1. Reads push app metadata.
  2. Verifies app status, credentials, and platform support.
  3. Identifies the user unless identify: false.
  4. Registers the current device token.

Required and optional registration fields are the same as registerCurrentDevicePushSubscription.

Additional optional:

  • identify
  • forceSync
  • enqueueOfflineRetry
  • onPushSubscriptionSynced
  • onPushSubscriptionSyncFailed

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, unless token is passed
  • resolvable platform, either passed or returned by pushTokenProvider

Optional:

  • appId
  • token
  • platform
  • forceRefreshToken
  • status
  • osPermission
  • backendCode
  • reason
  • requestId
  • meta

Returns Promise<MrdSdkCurrentDevicePushSubscriptionStateUpdateResult>.

client.updatePushSubscriptionState(options)

Updates status/permission metadata for an explicit token.

Required:

  • token
  • platform

Optional:

  • appId
  • status
  • osPermission
  • backendCode
  • reason
  • requestId
  • meta

Returns Promise<MrdSdkPushSubscriptionStateUpdateResult>.

client.addTags(tags, options?)

Adds or replaces tags. If no externalId is known, the mutation is queued locally.

Required:

  • tags object

Optional:

  • appId
  • externalId
  • mode: merge or replace, default merge

Returns Promise<MrdSdkUserTagsResult>.

client.removeTags(tagKeys, options?)

Deletes tags. If no externalId is known, the mutation is queued locally.

Required:

  • non-empty tagKeys array

Optional:

  • appId
  • externalId

Returns Promise<MrdSdkUserTagsResult>.

client.getTags(options?)

Returns backend tags for an externalId, or local queued tags when no externalId is known.

Optional:

  • appId
  • externalId

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:

  • method
  • path
  • sdkAuthToken configured on the client

Optional:

  • appId
  • body
  • headers

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:

  • id
  • appId
  • attempts
  • nextAttemptAt

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:

  • apiBaseUrl
  • appId
  • sdkAuthToken

Optional:

  • client
  • most client setup fields used for initialization
  • autoInitialize when 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:

  • apiBaseUrl
  • appId
  • sdkAuthToken
  • children

Optional:

  • client
  • autoInitialize
  • initialization callbacks and request options

When you need current-device helpers from React components, prefer passing a prebuilt client with pushTokenProvider.

MrdSdkReadyBoundary

Renders:

  • idleFallback when state is idle
  • loadingFallback when state is loading
  • errorFallback when state is error
  • children when state is ready

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:

  • unsubscribed when osPermission is denied
  • subscribed otherwise

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_platform
  • app_name
  • app_version
  • build_number
  • bundle_id
  • device_brand
  • device_model
  • device_type
  • system_name
  • system_version
  • os_version
  • time_zone
  • react_native_version
  • api_level
  • is_tablet
  • is_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-ID
  • X-MRD-Timestamp
  • X-MRD-Nonce
  • X-MRD-Signature
  • X-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 apiBaseUrl already 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 notification listeners, but it provides helpers for normalizing MRD push payload data.

import {
  normalizeMrdPushPayloadData,
  resolveMrdPushOpenAction,
} from 'react-native-mrd-sdk';

const data = normalizeMrdPushPayloadData(remoteMessage);

const action = resolveMrdPushOpenAction(remoteMessage, buttonActionId);

normalizeMrdPushPayloadData returns:

  • campaignId
  • launchUrl
  • buttons
  • rawData

resolveMrdPushOpenAction returns:

  • campaignId
  • actionId
  • launchUrl

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 appId matches 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=false

For 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, and sdkAuthToken come 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.
  • Notification open handling uses the app's listener and optional SDK payload helpers.