react-native-ios-bridge-implementation

star 17

Implements the iOS bridge layer for a React-Native MoEngage SDK feature. This is Step 2b of the React-Native feature pipeline (parallel to or after react-native-android-bridge-implementation). Produces the Bridge .h/.mm, Handler .h/.m, Constants .h/.m, optional Util .h/.m, and podspec for a new sdk/<featureName> module in the React-Native repo, following the Cards module standard. On completion, asks the user whether to also run react-native-ts-implementation. Do NOT use for Android-only features or JS-only changes.

moengage By moengage schedule Updated 6/3/2026

name: react-native-ios-bridge-implementation description: > Implements the iOS bridge layer for a React-Native MoEngage SDK feature. This is Step 2b of the React-Native feature pipeline (parallel to or after react-native-android-bridge-implementation). Produces the Bridge .h/.mm, Handler .h/.m, Constants .h/.m, optional Util .h/.m, and podspec for a new sdk/ module in the React-Native repo, following the Cards module standard. On completion, asks the user whether to also run react-native-ts-implementation. Do NOT use for Android-only features or JS-only changes. parameters:

  • name: "ticket_id" description: "JIRA ticket ID, e.g. 'MOEN-44072'. Extracted from command text if not supplied." optional: true
  • name: "feature_description" description: "Natural language description of the feature. E.g. 'JWT authentication parity'."
  • name: "contract_branch" description: "Branch in 'mobile-sdk-contracts' with the feature contract."
  • name: "ios_plugin_version" description: "Target MoEngagePlugin pod version. E.g. '3.10.0'."
  • name: "ios_plugin_base_pr_url" description: "URL of the iOS-PluginBase PR from plugin-base-feature-implementation."

Overview

Implements the iOS Objective-C bridge inside the React-Native SDK repo (React-Native) for a MoEngage feature whose plugin-base module already exists.

Prerequisite chain:

  1. plugin-base-feature-implementation — creates/extends the plugin-base module ✅
  2. react-native-ios-bridge-implementation ← you are here (parallel to Android bridge)
  3. react-native-ts-implementation — TypeScript models, spec, Handler, public API

Architecture standard: Follow the Cards module pattern exactly (sdk/cards/ios/).

Example files: Templates are in examples/ adjacent to this SKILL.md. Read each template before generating the corresponding file.


Example Files Index

examples/
  Bridge.h          ← MoEngage<featureNameCamel>Bridge.h
  Bridge.mm         ← MoEngage<featureNameCamel>Bridge.mm
  Constants.h       ← MoEngage<featureNameCamel>ReactConstants.h
  Constants.m       ← MoEngage<featureNameCamel>ReactConstants.m
  Handler.h         ← MoEReactNative<featureNameCamel>Handler.h
  Handler.m         ← MoEReactNative<featureNameCamel>Handler.m
  Util.h            ← MoEngage<featureNameCamel>ReactUtil.h  (only if promise or event methods)
  Util.m            ← MoEngage<featureNameCamel>ReactUtil.m  (only if promise or event methods)

Phase 0 — Clarify Inputs

0.1 Extract ticket ID

Scan the user's full command for MOEN-\d+ticketId. If not found in the command or parameters, ask before proceeding.


Phase 1 — Parse Inputs & Derive All Identifiers

1.1 Extract from contract_branch

Strip everything up to and including the first / or _MOEN-XXXXX_ prefix:

  • feature/experience_contractscontractSuffix = experience_contracts
  • MOEN-44072_jwt_contractcontractSuffix = jwt_contract

1.2 Identifiers table

Identifier Example Rule
ticketId MOEN-44072 MOEN-\d+ from raw command or parameter
contractSuffix jwt_contract branch name after first / or _MOEN-XXXXX_
featureName jwt lowercase slug from feature_description
featureNameCamel Jwt PascalCase of featureName
contractDir authentication subdirectory found in contracts json/ after checkout
rnSdkDir sdk/core see rule below
iosBridgeName MoEReactBridge see rule below
iosHandlerName MoEReactNativeHandler see rule below
iosPluginBridge see rule below see rule below
iosModule see rule below see rule below
iosUtilName MoEngageReactUtils see rule below
branchName feature/MOEN-44072-jwt_contract feature/<ticketId>-<contractSuffix>

1.3 Resolve iosBridgeName, iosHandlerName, and iosUtilName

Look up the existing class names by rnSdkDir. Do not generate these from a formula — use the actual names from the codebase. The Util is a shared module-level class, never a feature-specific one.

rnSdkDir iosBridgeName iosHandlerName iosUtilName iosDelegateName iosDelegatePattern
sdk/core MoEReactBridge MoEReactNativeHandler MoEngageReactUtils MoEngagePluginBridgeDelegate generic string: sendMessageWithEvent:message:
sdk/cards MoEngageCardsBridge MoEReactNativeCardsHandler MoEngageCardsReactUtil MoEngageCardSyncDelegate typed enum: syncCompleteForEventType:withData:
sdk/geofence MoEReactGeofence MoEReactNativeGeofenceHandler ask user ask user ask user
sdk/inbox MoEReactInbox MoEReactNativeInboxHandler ask user ask user ask user
sdk/personalize MoEngagePersonalizeBridge MoEReactNativePersonalizeHandler ask user ask user ask user
unknown ask user ask user ask user ask user ask user

1.5 Resolve rnSdkDir

Scan feature_description for a framework keyword and map to the existing SDK module directory:

Keyword in feature_description rnSdkDir
core, analytics, inapps, or messaging sdk/core
cards sdk/cards
geofence sdk/geofence
inbox sdk/inbox
personalize sdk/personalize
none of the above ask the user which SDK module to add the feature to

Examples:

  • "JWT authentication parity from core"sdk/core
  • "get clicked cards count"sdk/cards
  • "start geofence monitoring"sdk/geofence

1.6 Resolve iosPluginBridge and iosModule

Scan feature_description for a framework keyword:

Keyword in feature_description iosPluginBridge iosModule
analytics, inapps, messaging, or core MoEngagePluginBridge MoEngagePluginBase
anything else (cards, geofence, inbox, …) MoEngagePlugin<featureNameCamel>Bridge MoEngagePlugin<featureNameCamel>

Examples:

  • "jwt support from core"MoEngagePluginBridge / MoEngagePluginBase
  • "set device attribute from analytics"MoEngagePluginBridge / MoEngagePluginBase
  • "get clicked cards count"MoEngagePluginCardsBridge / MoEngagePluginCards
  • "start geofence monitoring"MoEngagePluginGeofenceBridge / MoEngagePluginGeofence

Phase 2 — Read Contracts

cd ../mobile-sdk-contracts
git fetch
git stash
git checkout <contract_branch>
  1. List json/hybridToNative/ to identify contractDir
    • If no matching directory → list available dirs and ask the user
  2. For each .json in json/hybridToNative/<contractDir>/:
    • Filename (without .json) = method name (camelCase)
    • File content = input payload shape
  3. For each .json in json/nativeToHybrid/<contractDir>/:
    • File content = event/response payload shape
  4. Read all .proto files in protos/<contractDir>/ for field names and types

Method classification

Condition Type iOS pattern Files needed
hybridToNative only fire-and-forget RCT_EXPORT_METHOD(name:(NSString*)payload) Bridge + Handler only
both hybridToNative + nativeToHybrid check plugin-base — see rule below depends depends
nativeToHybrid only event delegate method → sendEventWithName:body: Bridge + Handler + Util + Constants

When both hybridToNative and nativeToHybrid exist, do NOT ask the user — instead read the plugin-base header for <iosPluginBridge> and check how the method returns data:

# Find the method declaration in the plugin-base header:
grep -n "<methodName>" ../ios-PluginBase/<module>/<iosPluginBridge>.h
Plugin-base method signature Type iOS pattern Files needed
Has completionHandler:^(NSDictionary *result) block param promise RCT_EXPORT_METHOD(name:payload resolve:resolve reject:reject) Bridge + Handler + Util
No completion block — data returned via delegate event RCT_EXPORT_METHOD(name:payload) + delegate → sendEventWithName:body: Bridge + Handler + Util + Constants

Examples:

  • - (void)getSelfHandledInApps:(NSDictionary*)payload completionHandler:(void(^)(NSDictionary*))handlerpromise
  • - (void)getSelfHandledInApps:(NSDictionary*)payload (no block, data via <iosDelegateName>) → event

If the plugin-base header cannot be found → ask the user.

Build a complete method table before writing any code.


Phase 3 — iOS Bridge Implementation

3.1 Check out the branch

cd React-Native
git fetch
git checkout feature/<ticketId>-<contractSuffix>   # branch created by Android bridge step

If the branch does not exist yet (iOS-first flow):

git checkout -b feature/<ticketId>-<contractSuffix>

3.2 Check if iOS files already exist

ls <rnSdkDir>/ios/ 2>/dev/null
  • Not found → scaffold full iOS layer (Steps 3.3–3.9)
  • Found → read existing files first, then add only the missing methods

3.3 Constants (.h + .m)

→ See examples/Constants.h and examples/Constants.m Generate at: <rnSdkDir>/ios/MoEngage<featureNameCamel>ReactConstants.h/.m

Rules:

  • Always define kPayload = @"payload" (copy from Cards unless already imported via core)
  • One NSString* const per nativeToHybrid event name (omit section if no events)
  • Event name string values must exactly match the TS Constants.ts event names

3.4 Handler (.h + .m)

→ See examples/Handler.h and examples/Handler.m Generate at: <rnSdkDir>/ios/MoEReactNative<featureNameCamel>Handler.h/.m

Rules:

  • Singleton pattern with dispatch_once (copy from Cards exactly)
  • @property (nonatomic, weak) RCTEventEmitter *eventEmitteronly if event methods exist
  • -(void)initialize:(NSString *)payloadonly if event methods exist; sets the sync delegate
  • Fire-and-forget methods: parse payload with [MoEngageReactUtils getJSONRepresentation:payload], call [[<iosPluginBridge> sharedInstance] <methodName>:jsonPayload], no return
  • Promise methods: parse payload, call [[<iosPluginBridge> sharedInstance] <methodName>:jsonPayload completionHandler:^(NSDictionary* result){ [<iosUtilName> handleDataToReact:result rejecter:reject resolver:resolve]; }]
  • Event delegate method — generic pattern (sdk/core, MoEngagePluginBridgeDelegate): implement sendMessageWithEvent:message:. The event string is already the event name — emit directly:
    - (void)sendMessageWithEvent:(NSString *)event message:(NSDictionary<NSString *,id> *)message {
        [self.eventEmitter sendEventWithName:event body:message];
    }
    
  • Event delegate method — typed enum pattern (sdk/cards and other non-core modules, e.g. MoEngageCardSyncDelegate): implement the typed delegate method. Use <iosUtilName> fetchSyncType: to map the enum to an event name string, serialize data to JSON, put in updatedDict[kPayload], emit:
    - (void)syncCompleteForEventType:(MoEngageCardsSyncEventType)eventType withData:(NSDictionary<NSString *,id> *)data {
        NSMutableDictionary *updatedDict = [NSMutableDictionary dictionary];
        NSString *eventName = [MoEngageCardsReactUtil fetchSyncType:eventType];
        if (eventName && data) {
            NSError *err;
            NSData *jsonData = [NSJSONSerialization dataWithJSONObject:data options:0 error:&err];
            if (jsonData) {
                NSString *strPayload = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
                updatedDict[kPayload] = strPayload;
                [self.eventEmitter sendEventWithName:eventName body:updatedDict];
            } else {
                NSLog(@"Error converting dictionary to string %@", err.localizedDescription);
            }
        }
    }
    
    Ask user for the exact delegate protocol name and method signature for unknown modules → add // TODO: verify delegate protocol name.
  • Fire-and-forget / event trigger methods in Handler: parse payload with [MoEngageReactUtils getJSONRepresentation:payload], call [[<iosPluginBridge> sharedInstance] <methodName>:jsonPayload]. Example:
    - (void)getSelfHandledInApps:(NSString *)payload {
        NSDictionary *jsonPayload = [MoEngageReactUtils getJSONRepresentation:payload];
        [[MoEngagePluginBridge sharedInstance] getSelfHandledInApp:jsonPayload];
    }
    
  • Import @import MoEngagePlugin<featureNameCamel>; for the plugin-base bridge class
  • Conform to <iosDelegateName> in the class extension — only if event methods exist

3.5 Util (.h + .m) (only if promise or event methods exist)

→ See examples/Util.h and examples/Util.m

Do not create a feature-specific Util. Use the existing module-level <iosUtilName> file:

  • Path: <rnSdkDir>/ios/<iosUtilName>.h/.m
  • Read the existing file first, then add only the missing methods — never recreate it.

Methods to add if not already present:

  • +fetchSyncType: — maps SDK event type enum → event name constant string
    • Only for typed enum delegate pattern (non-core modules like cards, geofence, inbox)
    • Not needed for sdk/core — core's sendMessageWithEvent:message: already receives the event name string directly
    • One case per nativeToHybrid event type enum value
  • +handleDataToReact:rejecter:resolver: — shared JSON serialization + resolve/reject helper
    • Used by all promise methods in Handler.m
    • On success: resolver(strPayload); on failure: rejecter(error.code, error.localizedDescription, error)

3.6 Bridge (.h + .mm)

→ See examples/Bridge.h and examples/Bridge.mm Generate at: <rnSdkDir>/ios/MoEngage<featureNameCamel>Bridge.h/.mm

Rules:

  • .h: conditional #ifdef RCT_NEW_ARCH_ENABLED — new arch inherits NativeMoEngage<featureNameCamel>Spec, old arch inherits RCTBridgeModule; both extend RCTEventEmitter
  • .mm: include hasListeners ivar
  • startObserving / stopObserving / supportedEventsonly if event methods exist
  • supportedEvents returns array of all nativeToHybrid event name constants
  • RCT_EXPORT_MODULE() — no argument (module name auto-derived from class name)
  • initialize:only if event methods exist; sets eventEmitter = self on the handler
  • Each fire-and-forget: RCT_EXPORT_METHOD(<name>:(NSString *)payload) → call [[<iosHandlerName> sharedInstance] <name>:payload] on handler
  • Each event trigger (hybridToNative side of an event method): RCT_EXPORT_METHOD(<name>:(NSString *)payload) → call [[<iosHandlerName> sharedInstance] <name>:payload] on handler — not initialize:. The handler method calls [[<iosPluginBridge> sharedInstance] <name>:jsonPayload] which triggers the SDK to push the event back via the delegate.
  • Each promise: RCT_EXPORT_METHOD(<name>:(NSString *)payload resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) → delegate to handler
  • New arch block at bottom: getTurboModule: returning NativeMoEngage<featureNameCamel>SpecJSI
  • Add // TODO: verify comment if the NativeMoEngageSpec header name is unknown

3.7 Podspec

First check if a podspec already exists:

ls <rnSdkDir>/*.podspec 2>/dev/null

If podspec already exists (adding to an existing module):

  • Read the existing podspec
  • Find the s.dependency 'MoEngagePlugin<featureNameCamel>', '...' line
  • Update the version to '<ios_plugin_version>'
  • Do not change anything else

If no podspec exists (new module):

  • Copy sdk/cards/ReactNativeMoEngageCards.podspec, rename to ReactNativeMoEngage<featureNameCamel>.podspec
  • s.name"ReactNativeMoEngage<featureNameCamel>"
  • s.summary / s.description → update feature name
  • s.source_files"ios/**/*.{h,m,mm,swift}"
  • s.dependency 'MoEngagePlugin<featureNameCamel>', '<ios_plugin_version>'
  • Keep s.dependency 'React', s.dependency 'ReactNativeMoEngage', and install_modules_dependencies block

3.8 Commit

git add <rnSdkDir>/ios/ <rnSdkDir>/*.podspec
git commit -m "<ticketId>: Add React-Native iOS bridge for <featureName>"

Phase 4 — Create / Update Pull Request

git push -u origin feature/<ticketId>-<contractSuffix>

# Check if PR already exists (from Android bridge step):
gh pr list --head feature/<ticketId>-<contractSuffix> --json number,url

If PR already exists (Android bridge was done first): push a new commit to the same branch and add a comment to the PR explaining iOS was added. Do not create a second PR.

If no PR exists (iOS-first flow):

gh pr create \
  --title "<ticketId>: Add React-Native iOS bridge for <featureName>" \
  --base development \
  --body "$(cat <<'EOF'
## Summary
- Adds iOS Objective-C bridge (`<rnSdkDir>/ios/`) for the <featureName> feature
- Bridge delegates all methods to `MoEReactNative<featureNameCamel>Handler` singleton
- New-arch (TurboModule via RCT_NEW_ARCH_ENABLED) + old-arch (RCTBridgeModule) supported
- iOS plugin version: <ios_plugin_version>

## Related PRs
- ios-plugin-base: <ios_plugin_base_pr_url>

## Contract
Branch: `<contract_branch>` in mobile-sdk-contracts

## Methods
| Method | Type |
|---|---|
<table rows from method table>

🤖 Generated with [Claude Code](https://claude.com/claude-code)
EOF
)"

Phase 5 — Report & Hand-off

Print:

  1. PR URL
  2. Full method table (name, type, iOS delegate)
  3. All // TODO items left for manual verification (delegate protocol name, iOS pod dependency)
  4. List of all files created or modified

Then ask the user:

"iOS bridge for <featureName> is done (PR: ). Would you like to also run the TypeScript layer now (react-native-ts-implementation)? It needs the same contract branch, plugin version, and both PR URLs."


Codebase Reference Files

Read these before generating the corresponding output — copy copyright headers, naming conventions, and structural patterns exactly.

What Codebase path
Bridge .h reference React-Native/sdk/cards/ios/MoEngageCardsBridge.h
Bridge .mm reference React-Native/sdk/cards/ios/MoEngageCardsBridge.mm
Handler .h reference React-Native/sdk/cards/ios/MoEReactNativeCardsHandler.h
Handler .m reference React-Native/sdk/cards/ios/MoEReactNativeCardsHandler.m
Constants .h reference React-Native/sdk/cards/ios/MoEngageCardsReactConstants.h
Constants .m reference React-Native/sdk/cards/ios/MoEngageCardsReactConstants.m
Util .h reference React-Native/sdk/cards/ios/MoEngageCardsReactUtil.h
Util .m reference React-Native/sdk/cards/ios/MoEngageCardsReactUtil.m
Podspec reference React-Native/sdk/cards/ReactNativeMoEngageCards.podspec
Core Bridge (event queue pattern) React-Native/sdk/core/iOS/MoEReactBridge/MoEReactBridge.mm
Core Handler (promise pattern) React-Native/sdk/core/iOS/MoEReactBridge/MoEReactNativeHandler.m
MoEngageReactUtils React-Native/sdk/core/iOS/MoEReactBridge/MoEngageReactUtils.h/.m

Error Handling Rules

  • contract_branch not found in ../mobile-sdk-contracts → stop and tell the user
  • contractDir not found in json/hybridToNative/ → list available dirs and ask
  • rnSdkDir/ios/ already exists → read existing files, add only missing methods
  • Plugin-base delegate protocol name unknown → add // TODO: verify delegate protocol name and continue
  • NativeMoEngage<featureNameCamel>Spec header name unknown → add // TODO: verify TurboModule spec name and continue
  • iOS pod dependency name unknown → add // TODO: verify iOS pod dependency in podspec and continue
  • Push fails → report error and local branch name so the user can push manually
Install via CLI
npx skills add https://github.com/moengage/React-Native --skill react-native-ios-bridge-implementation
Repository Details
star Stars 17
call_split Forks 12
navigation Branch main
article Path SKILL.md
More from Creator