Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions ios/RNMParticle.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
B39BCD8F2E2A06D700FC90B8 /* RoktEventManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B39BCD8C2E2A06D700FC90B8 /* RoktEventManager.m */; };
B39BCD8F2E2A06D700FC90B8 /* RoktEventManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = B39BCD8C2E2A06D700FC90B8 /* RoktEventManager.mm */; };
B39BCD962E30562400FC90B8 /* RoktLayoutManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B39BCD932E30562400FC90B8 /* RoktLayoutManager.m */; };
B39BCD972E30562400FC90B8 /* RNMPRokt.mm in Sources */ = {isa = PBXBuildFile; fileRef = B39BCD922E30562400FC90B8 /* RNMPRokt.mm */; };
B39BCD982E30562400FC90B8 /* RNMParticle.mm in Sources */ = {isa = PBXBuildFile; fileRef = B39BCD912E30562400FC90B8 /* RNMParticle.mm */; };
Expand All @@ -31,7 +31,7 @@
/* Begin PBXFileReference section */
B39BCD892E2A06D700FC90B8 /* RNMPRokt.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNMPRokt.h; sourceTree = "<group>"; };
B39BCD8B2E2A06D700FC90B8 /* RoktEventManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RoktEventManager.h; sourceTree = "<group>"; };
B39BCD8C2E2A06D700FC90B8 /* RoktEventManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RoktEventManager.m; sourceTree = "<group>"; };
B39BCD8C2E2A06D700FC90B8 /* RoktEventManager.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RoktEventManager.mm; sourceTree = "<group>"; };
B39BCD912E30562400FC90B8 /* RNMParticle.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RNMParticle.mm; sourceTree = "<group>"; };
B39BCD922E30562400FC90B8 /* RNMPRokt.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RNMPRokt.mm; sourceTree = "<group>"; };
B39BCD932E30562400FC90B8 /* RoktLayoutManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RoktLayoutManager.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -78,7 +78,7 @@
B39BCD952E30562400FC90B8 /* RoktNativeLayoutComponentView.mm */,
B39BCD892E2A06D700FC90B8 /* RNMPRokt.h */,
B39BCD8B2E2A06D700FC90B8 /* RoktEventManager.h */,
B39BCD8C2E2A06D700FC90B8 /* RoktEventManager.m */,
B39BCD8C2E2A06D700FC90B8 /* RoktEventManager.mm */,
DBDF24DA1E007EB1000F3D73 /* RNMParticle.h */,
);
path = RNMParticle;
Expand Down Expand Up @@ -146,7 +146,7 @@
B39BCD972E30562400FC90B8 /* RNMPRokt.mm in Sources */,
B39BCD982E30562400FC90B8 /* RNMParticle.mm in Sources */,
B39BCD992E30562400FC90B8 /* RoktNativeLayoutComponentView.mm in Sources */,
B39BCD8F2E2A06D700FC90B8 /* RoktEventManager.m in Sources */,
B39BCD8F2E2A06D700FC90B8 /* RoktEventManager.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
14 changes: 14 additions & 0 deletions ios/RNMParticle/RoktEventManager.h
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>

#ifdef RCT_NEW_ARCH_ENABLED
#import <RNMParticle/RNMParticle.h>
#endif // RCT_NEW_ARCH_ENABLED

@class RoktEvent;

NS_ASSUME_NONNULL_BEGIN

// Conforms to the codegen'd spec under the New Architecture so the emitter is
// registered as a TurboModule. Without it this class is a plain RCTBridgeModule,
// which bridgeless only instantiates when the host app enables TurboModule interop
// -- otherwise NativeModules.RoktEventManager is undefined and every Rokt event is
// dropped before reaching JS.
#ifdef RCT_NEW_ARCH_ENABLED
@interface RoktEventManager : RCTEventEmitter <RCTBridgeModule, NativeRoktEventManagerSpec>
#else
@interface RoktEventManager : RCTEventEmitter <RCTBridgeModule>
#endif // RCT_NEW_ARCH_ENABLED

+ (instancetype _Nonnull)allocWithZone:(NSZone * _Nullable)zone;
- (void)onWidgetHeightChanges:(CGFloat)widgetHeight placement:(NSString * _Nonnull)selectedPlacement;
- (void)onFirstPositiveResponse;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
#import "RoktEventManager.h"
@import RoktContracts;
// Not `@import RoktContracts` -- this file is Objective-C++ and the pod builds without
// -fcxx-modules, so the module import fails. Matches how RNMPRokt.mm imports the same types.
#if __has_include(<RoktContracts/RoktContracts-Swift.h>)
#import <RoktContracts/RoktContracts-Swift.h>
#elif __has_include(<RoktContracts/RoktContracts.h>)
#import <RoktContracts/RoktContracts.h>
#endif
#import <os/log.h>

static os_log_t _rokt_events_os_log(void) {
Expand Down Expand Up @@ -233,4 +239,10 @@ - (void)onRoktEvents:(RoktEvent * _Nonnull)event viewName:(NSString * _Nullable)
}
}

#ifdef RCT_NEW_ARCH_ENABLED
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
return std::make_shared<facebook::react::NativeRoktEventManagerSpecJSI>(params);
}
#endif // RCT_NEW_ARCH_ENABLED

@end
1 change: 1 addition & 0 deletions js/__tests__/attribute-normalization.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ jest.mock(
Platform: { OS: 'ios' },
TurboModuleRegistry: {
getEnforcing: jest.fn(() => mockNativeModule),
get: jest.fn(() => null),
},
}),
{ virtual: true }
Expand Down
57 changes: 57 additions & 0 deletions js/__tests__/rokt-event-manager.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* The Rokt event emitter is an `RCTEventEmitter`, which conforms only to `RCTBridgeModule`.
* Bridgeless does not instantiate those unless the host app enabled TurboModule interop, so
* resolving it purely through `NativeModules` left it undefined and dropped every Rokt event
* before it reached JS. It is now registered via codegen and must be resolved through the
* TurboModule registry first, with a `NativeModules` fallback for the old architecture.
*/

function loadRoktEventManager(
turboModule: unknown,
nativeModules: Record<string, unknown>
) {
jest.resetModules();
jest.doMock(
'react-native',
() => ({
NativeModules: nativeModules,
TurboModuleRegistry: { get: jest.fn(() => turboModule) },
}),
{ virtual: true }
);
// eslint-disable-next-line @typescript-eslint/no-var-requires
return require('../rokt/rokt-event-manager').RoktEventManager;
}

afterEach(() => {
jest.resetModules();
});

describe('RoktEventManager resolution', () => {
it('resolves through the TurboModule registry when NativeModules has no entry', () => {
const turboModule = { addListener: jest.fn(), removeListeners: jest.fn() };

expect(loadRoktEventManager(turboModule, {})).toBe(turboModule);
});

it('prefers the TurboModule registry over NativeModules', () => {
const turboModule = { addListener: jest.fn(), removeListeners: jest.fn() };
const legacyModule = { addListener: jest.fn() };

expect(
loadRoktEventManager(turboModule, { RoktEventManager: legacyModule })
).toBe(turboModule);
});

it('falls back to NativeModules on the old architecture', () => {
const legacyModule = { addListener: jest.fn() };

expect(loadRoktEventManager(null, { RoktEventManager: legacyModule })).toBe(
legacyModule
);
});

it('is null when neither is available, so NativeEventEmitter is not handed undefined', () => {
expect(loadRoktEventManager(null, {})).toBeNull();
});
});
5 changes: 4 additions & 1 deletion js/__tests__/rokt-layout-view-style.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ jest.mock(
NativeModules: { RoktEventManager: {} },
NativeEventEmitter: jest.fn(() => ({ addListener: mockAddListener })),
requireNativeComponent: jest.fn(() => 'RoktLegacyLayout'),
TurboModuleRegistry: { getEnforcing: jest.fn(() => ({})) },
TurboModuleRegistry: {
getEnforcing: jest.fn(() => ({})),
get: jest.fn(() => null),
},
}),
{ virtual: true }
);
Expand Down
20 changes: 20 additions & 0 deletions js/codegenSpecs/rokt/NativeRoktEventManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

/**
* Event channel for Rokt placement events.
*
* The native side is an `RCTEventEmitter`, which only conforms to `RCTBridgeModule`.
* Under bridgeless that is not enough to be instantiated unless the host app has
* opted into TurboModule interop, so this spec exists to register the emitter as a
* TurboModule and keep `RoktEvents` reaching JS in every architecture.
*
* iOS only. Android delivers the same events over `RCTDeviceEventEmitter`, so this
* module is absent there and `TurboModuleRegistry.get` returns null by design.
*/
export interface Spec extends TurboModule {
addListener(eventName: string): void;
removeListeners(count: number): void;
}

export default TurboModuleRegistry.get<Spec>('RoktEventManager');
20 changes: 20 additions & 0 deletions js/rokt/rokt-event-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { NativeModules, TurboModuleRegistry } from 'react-native';

const ROKT_EVENT_MANAGER_MODULE_NAME = 'RoktEventManager';

/**
* The native emitter that carries `RoktEvents` and `LayoutHeightChanges` to JS.
*
* Resolved through the TurboModule registry first: the native class is an
* `RCTEventEmitter`, and under bridgeless those are only instantiated when the host app
* has enabled TurboModule interop. Registering it via codegen makes it resolvable either
* way; without this the module is undefined and every Rokt event is dropped silently.
*
* Falls back to `NativeModules` for the old architecture, and is null on Android, where
* the same events arrive over `RCTDeviceEventEmitter` and `NativeEventEmitter` accepts a
* null module.
*/
export const RoktEventManager =
TurboModuleRegistry.get(ROKT_EVENT_MANAGER_MODULE_NAME) ??
NativeModules[ROKT_EVENT_MANAGER_MODULE_NAME] ??

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to test this won Android and see it need any other changes.

null;
20 changes: 15 additions & 5 deletions js/rokt/rokt-layout-view.ios.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@ import {
requireNativeComponent,
StyleSheet,
NativeEventEmitter,
NativeModules,
ViewProps,
NativeModule,
} from 'react-native';
import React, { Component } from 'react';
import { isFabricEnabled } from '../utils/architecture';
import RoktLayoutNativeComponent from '../codegenSpecs/rokt/RoktLayoutNativeComponent';

const RoktEventManager = NativeModules.RoktEventManager as NativeModule;
import { RoktEventManager } from './rokt-event-manager';

export interface HeightChangedEvent extends Event {
height: string;
Expand Down Expand Up @@ -43,13 +41,25 @@ const LayoutNativeComponent = (
: requireNativeComponent<RoktNativeLayoutProps>('RoktLegacyLayout')
) as any;

const eventManagerEmitter = new NativeEventEmitter(RoktEventManager);
// Built on first use rather than at module scope: constructing a NativeEventEmitter with a
// missing native module throws on iOS, which would take down the bundle at import time
// instead of degrading to a placement that never resizes.
let eventManagerEmitter: NativeEventEmitter | undefined;

function getEventManagerEmitter(): NativeEventEmitter {
if (!eventManagerEmitter) {
eventManagerEmitter = new NativeEventEmitter(
RoktEventManager as NativeModule
);
}
return eventManagerEmitter;
}
Comment on lines +44 to +56

export class RoktLayoutView extends Component<
RoktLayoutViewProps,
RoktLayoutViewState
> {
subscription = eventManagerEmitter.addListener(
subscription = getEventManagerEmitter().addListener(
'LayoutHeightChanges',
(widgetChanges: WidgetChangeEvent) => {
if (widgetChanges.selectedPlacement == this.state.placeholderName) {
Expand Down
3 changes: 1 addition & 2 deletions js/rokt/rokt.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { NativeModules, Platform, TurboModuleRegistry } from 'react-native';
import { getNativeModule, isNewArchitecture } from '../utils/architecture';
import type { Spec as NativeMPRoktInterface } from '../codegenSpecs/rokt/NativeMPRokt';
import { RoktEventManager } from './rokt-event-manager';

const ROKT_MODULE_NAME = 'RNMPRokt';
const MPRokt =
Expand Down Expand Up @@ -124,8 +125,6 @@ class RoktConfig implements IRoktConfig {
this.cacheConfig = cacheConfig;
}
}
const { RoktEventManager } = NativeModules;

export { RoktEventManager };

export type ColorMode = 'light' | 'dark' | 'system';
Loading