From 58408d69f6cee2a20a9dae584473301a68902841 Mon Sep 17 00:00:00 2001 From: namArora3112 Date: Wed, 24 Jun 2026 14:29:34 +0530 Subject: [PATCH 1/8] optimize package compatibility with old arc and newarc with 0.76 --- packages/optimize/RCTAEPOptimize.podspec | 15 ++- packages/optimize/__tests__/OptimizeTests.ts | 2 - packages/optimize/android/build.gradle | 11 ++ .../optimize/NativeAEPOptimizeModule.java | 11 ++ .../optimize/RCTAEPOptimizeModule.java | 6 + .../optimize/RCTAEPOptimizePackage.java | 26 +++- .../optimize/ios/RCTAEPOptimizeCppPrefix.h | 3 + packages/optimize/ios/src/RCTAEPOptimize.h | 32 +++-- packages/optimize/ios/src/RCTAEPOptimize.mm | 120 +++++++++++++----- packages/optimize/package.json | 1 + packages/optimize/specs/NativeAEPOptimize.ts | 5 +- packages/optimize/src/NativeAEPOptimize.ts | 55 +++++++- packages/optimize/src/Optimize.ts | 26 ++-- packages/optimize/src/propositionEvents.ts | 118 +++++++++++++++++ 14 files changed, 355 insertions(+), 76 deletions(-) create mode 100644 packages/optimize/ios/RCTAEPOptimizeCppPrefix.h create mode 100644 packages/optimize/src/propositionEvents.ts diff --git a/packages/optimize/RCTAEPOptimize.podspec b/packages/optimize/RCTAEPOptimize.podspec index b3b947f7..83a96aa6 100644 --- a/packages/optimize/RCTAEPOptimize.podspec +++ b/packages/optimize/RCTAEPOptimize.podspec @@ -2,8 +2,8 @@ require "json" package = JSON.parse(File.read(File.join(__dir__, "package.json"))) # Build-time toggle — mirrors Android's buildConfigField "boolean", "USE_INTEROP_ROOT", "false" -# USE_INTEROP_ROOT=1 pod install → interop layer (old arch, RCTEventEmitter) -# USE_INTEROP_ROOT=0 (default) → Turbo Module (new arch, getTurboModule:) +# USE_INTEROP_ROOT=1 pod install → interop layer (RN 0.76, RCTEventEmitter) +# USE_INTEROP_ROOT=0 (default) → Turbo Module (RN 0.84+, SpecBase + JSI events) use_interop_root = ENV.key?('USE_INTEROP_ROOT') ? ENV['USE_INTEROP_ROOT'].to_i : 0 Pod::Spec.new do |s| @@ -22,14 +22,17 @@ Pod::Spec.new do |s| s.source_files = 'ios/**/*.{h,m,mm}' s.requires_arc = true - s.dependency "React" - s.dependency "React-Codegen" s.dependency "AEPOptimize", ">= 5.0.0", "< 6.0.0" s.pod_target_xcconfig = { "CLANG_ENABLE_MODULES" => "YES", - "OTHER_CPLUSPLUSFLAGS" => "$(inherited) -fcxx-modules", - "HEADER_SEARCH_PATHS" => "$(inherited) \"$(PODS_ROOT)/../build/generated/ios\" \"$(PODS_ROOT)/../build/generated/ios/ReactCodegen\" \"$(PODS_ROOT)/Headers/Public/ReactCodegen\"", + "OTHER_CPLUSPLUSFLAGS" => "$(inherited) -fcxx-modules -include $(PODS_TARGET_SRCROOT)/ios/RCTAEPOptimizeCppPrefix.h", + "HEADER_SEARCH_PATHS" => "$(inherited) \"$(PODS_ROOT)/RCT-Folly\" \"$(PODS_ROOT)/DoubleConversion\" \"$(PODS_ROOT)/fmt/include\" \"$(PODS_CONFIGURATION_BUILD_DIR)/React-jsinspector/jsinspector_modern.framework/Headers\" \"$(PODS_ROOT)/../build/generated/ios\" \"$(PODS_ROOT)/../build/generated/ios/ReactCodegen\" \"$(PODS_ROOT)/Headers/Public/ReactCodegen\"", "GCC_PREPROCESSOR_DEFINITIONS" => "$(inherited) USE_INTEROP_ROOT=#{use_interop_root}" } + + # Codegen TurboModule headers need RCT-Folly + FOLLY_NO_CONFIG even when new arch is off + # (interop path on RN 0.76 still implements getTurboModule: / NativeAEPOptimizeSpec). + new_arch_enabled = ENV['RCT_NEW_ARCH_ENABLED'] == '1' + install_modules_dependencies(s, new_arch_enabled: new_arch_enabled) end diff --git a/packages/optimize/__tests__/OptimizeTests.ts b/packages/optimize/__tests__/OptimizeTests.ts index 3d8f528e..38b3891f 100644 --- a/packages/optimize/__tests__/OptimizeTests.ts +++ b/packages/optimize/__tests__/OptimizeTests.ts @@ -32,13 +32,11 @@ describe('Optimize', () => { it('AEPOptimize onPropositionUpdate is called with correct parameters', async () => { const registerSpy = jest.spyOn(NativeModules.AEPOptimize, 'onPropositionsUpdate'); - const subscribeSpy = jest.spyOn(NativeModules.AEPOptimize, 'onPropositionsUpdated'); let adobeCallback = { call(_: Map): void {} }; await Optimize.onPropositionUpdate(adobeCallback); - expect(subscribeSpy).toHaveBeenCalled(); expect(registerSpy).toHaveBeenCalled(); }); diff --git a/packages/optimize/android/build.gradle b/packages/optimize/android/build.gradle index bac81bfb..ce6a2c4a 100644 --- a/packages/optimize/android/build.gradle +++ b/packages/optimize/android/build.gradle @@ -26,6 +26,7 @@ android { versionCode 1 versionName "1.0" buildConfigField "boolean", "USE_INTEROP_ROOT", "false" + buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled() ? "true" : "false" } buildFeatures { buildConfig true @@ -33,6 +34,16 @@ android { lintOptions { abortOnError false } + + sourceSets { + main { + if (!isNewArchitectureEnabled()) { + java { + exclude '**/NativeAEPOptimizeModule.java' + } + } + } + } } repositories { diff --git a/packages/optimize/android/src/main/java/com/adobe/marketing/mobile/reactnative/optimize/NativeAEPOptimizeModule.java b/packages/optimize/android/src/main/java/com/adobe/marketing/mobile/reactnative/optimize/NativeAEPOptimizeModule.java index bcee31cb..0160c071 100644 --- a/packages/optimize/android/src/main/java/com/adobe/marketing/mobile/reactnative/optimize/NativeAEPOptimizeModule.java +++ b/packages/optimize/android/src/main/java/com/adobe/marketing/mobile/reactnative/optimize/NativeAEPOptimizeModule.java @@ -45,6 +45,7 @@ public class NativeAEPOptimizeModule extends NativeAEPOptimizeSpec { private static final String TAG = "NativeAEPOptimizeModule"; private final Map propositionCache = new ConcurrentHashMap<>(); + private boolean propositionsUpdateListenerRegistered = false; public NativeAEPOptimizeModule(ReactApplicationContext reactContext) { super(reactContext); @@ -123,6 +124,12 @@ public void call(Map decisionScopePropositio @Override public void onPropositionsUpdate() { + // AEP SDK stacks listeners on each call — register once per module instance. + if (propositionsUpdateListenerRegistered) { + Log.d(TAG, "onPropositionsUpdate: AEP listener already registered, skipping duplicate registration."); + return; + } + propositionsUpdateListenerRegistered = true; Optimize.onPropositionsUpdate(new AdobeCallback>() { @Override public void call(Map decisionScopePropositionMap) { @@ -132,6 +139,10 @@ public void call(Map decisionScopePropositio WritableMap payload = Arguments.createMap(); payload.putMap("propositions", RCTAEPOptimizeUtil.createCallbackResponse(decisionScopePropositionMap)); emitOnPropositionsUpdated(payload); + // Android JS subscribes via DeviceEventEmitter (flat map). Do not gate on hasActiveReactInstance — + // SDK callbacks can fire before the instance flag is set on RN 0.85 new arch. + RCTAEPOptimizeUtil.emitOnPropositionsUpdate( + getReactApplicationContext(), decisionScopePropositionMap, false); } }); } diff --git a/packages/optimize/android/src/main/java/com/adobe/marketing/mobile/reactnative/optimize/RCTAEPOptimizeModule.java b/packages/optimize/android/src/main/java/com/adobe/marketing/mobile/reactnative/optimize/RCTAEPOptimizeModule.java index 29f1d867..b230ae5a 100644 --- a/packages/optimize/android/src/main/java/com/adobe/marketing/mobile/reactnative/optimize/RCTAEPOptimizeModule.java +++ b/packages/optimize/android/src/main/java/com/adobe/marketing/mobile/reactnative/optimize/RCTAEPOptimizeModule.java @@ -51,6 +51,7 @@ public class RCTAEPOptimizeModule extends ReactContextBaseJavaModule { private final ReactApplicationContext reactContext; // Cache of private final Map propositionCache = new ConcurrentHashMap<>(); + private boolean propositionsUpdateListenerRegistered = false; public RCTAEPOptimizeModule(ReactApplicationContext reactContext) { super(reactContext); @@ -204,6 +205,11 @@ public void multipleOffersGenerateDisplayInteractionXdm(final ReadableArray offe @ReactMethod public void onPropositionsUpdate() { + if (propositionsUpdateListenerRegistered) { + Log.d(TAG, "onPropositionsUpdate: AEP listener already registered, skipping duplicate registration."); + return; + } + propositionsUpdateListenerRegistered = true; Optimize.onPropositionsUpdate(new AdobeCallback>() { @Override public void call(final Map decisionScopePropositionMap) { diff --git a/packages/optimize/android/src/main/java/com/adobe/marketing/mobile/reactnative/optimize/RCTAEPOptimizePackage.java b/packages/optimize/android/src/main/java/com/adobe/marketing/mobile/reactnative/optimize/RCTAEPOptimizePackage.java index cfe131c5..2f2e5c34 100644 --- a/packages/optimize/android/src/main/java/com/adobe/marketing/mobile/reactnative/optimize/RCTAEPOptimizePackage.java +++ b/packages/optimize/android/src/main/java/com/adobe/marketing/mobile/reactnative/optimize/RCTAEPOptimizePackage.java @@ -14,6 +14,7 @@ import java.util.Map; import com.facebook.react.BaseReactPackage; +import com.facebook.react.ReactPackage; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.module.model.ReactModuleInfo; @@ -29,7 +30,7 @@ * USE_INTEROP_ROOT true -> RCTAEPOptimizeModule (classic bridge); isTurboModule = false. * USE_INTEROP_ROOT false -> NativeAEPOptimizeModule (Turbo); isTurboModule = true. */ -public class RCTAEPOptimizePackage extends BaseReactPackage { +public class RCTAEPOptimizePackage extends BaseReactPackage implements ReactPackage { private static final String MODULE_NAME = "NativeAEPOptimize"; @@ -38,9 +39,23 @@ public NativeModule getModule(String name, ReactApplicationContext reactContext) if (!MODULE_NAME.equals(name)) { return null; } - return BuildConfig.USE_INTEROP_ROOT - ? new RCTAEPOptimizeModule(reactContext) - : new NativeAEPOptimizeModule(reactContext); + if (BuildConfig.USE_INTEROP_ROOT || !BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + return new RCTAEPOptimizeModule(reactContext); + } + return createTurboModule(reactContext); + } + + /** Reflection avoids compile-time dep on NativeAEPOptimizeModule when old arch omits codegen sources. */ + private static NativeModule createTurboModule(ReactApplicationContext reactContext) { + try { + Class cls = Class.forName( + "com.adobe.marketing.mobile.reactnative.optimize.NativeAEPOptimizeModule"); + return (NativeModule) cls.getConstructor(ReactApplicationContext.class).newInstance(reactContext); + } catch (ReflectiveOperationException e) { + throw new RuntimeException( + "NativeAEPOptimizeModule not available — enable New Architecture or set USE_INTEROP_ROOT=true", + e); + } } @Override @@ -49,7 +64,8 @@ public ReactModuleInfoProvider getReactModuleInfoProvider() { @Override public Map getReactModuleInfos() { Map map = new HashMap<>(); - boolean isTurboModule = !BuildConfig.USE_INTEROP_ROOT; + boolean isTurboModule = + BuildConfig.IS_NEW_ARCHITECTURE_ENABLED && !BuildConfig.USE_INTEROP_ROOT; map.put(MODULE_NAME, new ReactModuleInfo( MODULE_NAME, MODULE_NAME, diff --git a/packages/optimize/ios/RCTAEPOptimizeCppPrefix.h b/packages/optimize/ios/RCTAEPOptimizeCppPrefix.h new file mode 100644 index 00000000..409e63c9 --- /dev/null +++ b/packages/optimize/ios/RCTAEPOptimizeCppPrefix.h @@ -0,0 +1,3 @@ +#pragma once +#include +#include diff --git a/packages/optimize/ios/src/RCTAEPOptimize.h b/packages/optimize/ios/src/RCTAEPOptimize.h index da2a9201..9836e9b7 100644 --- a/packages/optimize/ios/src/RCTAEPOptimize.h +++ b/packages/optimize/ios/src/RCTAEPOptimize.h @@ -10,18 +10,26 @@ */ #import -#import -// NativeAEPOptimizeSpecBase (codegen-generated) provides emitOnPropositionsUpdated: -// for JSI-native event emission. Used on BOTH turbo and interop paths because: -// -// 1. getTurboModule: is required on both paths (RCTModuleProviders.mm checks it) -// 2. getTurboModule: → RCTTurboModuleManager creates module with callableJSModules:nil -// 3. callableJSModules:nil → sendEventWithName: silently drops events -// 4. Therefore RCTEventEmitter's sendEventWithName: is dead for any turbo-registered module -// 5. emitOnPropositionsUpdated: bypasses callableJSModules — uses JSI EventEmitterCallback -// -// See: https://reactnative.dev/docs/the-new-architecture/native-modules-custom-events -@interface RCTAEPOptimize : NativeAEPOptimizeSpecBase +#if USE_INTEROP_ROOT + #if RCT_NEW_ARCH_ENABLED + // RN 0.84+ interop (new arch): SpecBase + getTurboModule: — sendEventWithName: is dead + // when getTurboModule: exists (callableJSModules:nil). Use emitOnPropositionsUpdated:. + #import + @interface RCTAEPOptimize : NativeAEPOptimizeSpecBase + #else + // RN 0.76 old arch: pure classic bridge module (RCT_EXPORT_METHOD + RCTEventEmitter). + #import + @interface RCTAEPOptimize : RCTEventEmitter + #endif +#else + // Turbo path (RN 0.84+ default): SpecBase provides emitOnPropositionsUpdated: + // for JSI-native event delivery on both iOS and Android. + // + // sendEventWithName: is dead for any module registered via getTurboModule: + // See: https://reactnative.dev/docs/the-new-architecture/native-modules-custom-events + #import + @interface RCTAEPOptimize : NativeAEPOptimizeSpecBase +#endif @end diff --git a/packages/optimize/ios/src/RCTAEPOptimize.mm b/packages/optimize/ios/src/RCTAEPOptimize.mm index 676875b1..83794981 100644 --- a/packages/optimize/ios/src/RCTAEPOptimize.mm +++ b/packages/optimize/ios/src/RCTAEPOptimize.mm @@ -17,8 +17,17 @@ static NSString *const TAG = @"RCTAEPOptimize"; +// Old-arch iOS (classic bridge): NativeModules only exposes methods registered via +// RCT_EXPORT_METHOD. Protocol/Turbo selectors alone are invisible to JavaScript. +#if USE_INTEROP_ROOT +#define AEP_OPTIMIZE_METHOD(method) RCT_EXPORT_METHOD(method) +#else +#define AEP_OPTIMIZE_METHOD(method) - (void)method +#endif + @implementation RCTAEPOptimize { NSMutableDictionary *propositionCache; + BOOL _propositionsUpdateListenerRegistered; } - (instancetype)init { @@ -35,35 +44,42 @@ - (dispatch_queue_t)methodQueue { return dispatch_get_main_queue(); } -// Module name for TurboModuleRegistry resolution. -+ (NSString *)moduleName { return @"NativeAEPOptimize"; } +// Single moduleName registration for all paths (interop + turbo, old + new arch). +// RCT_EXPORT_MODULE expands to +moduleName — do NOT also define +moduleName manually +// (duplicate declaration when USE_INTEROP_ROOT=0; see aep-turbo-migrate gotcha #29). +RCT_EXPORT_MODULE(NativeAEPOptimize); -// Required on both paths: RCTModuleProviders.mm (codegen-generated) checks -// respondsToSelector:@selector(getTurboModule:) at startup. Without it, -// the module is skipped and TurboModuleRegistry returns null. +// Old-arch interop (RN 0.76): turbo is off — skip getTurboModule: so the module +// registers as a classic bridge NativeModule with RCT_EXPORT_METHOD exports. +// New-arch interop + turbo path: getTurboModule: required for TurboModuleRegistry. +#if !USE_INTEROP_ROOT || RCT_NEW_ARCH_ENABLED - (std::shared_ptr)getTurboModule: (const facebook::react::ObjCTurboModule::InitParams &)params { return std::make_shared(params); } +#endif #pragma mark - NativeAEPOptimizeSpec protocol methods -- (void)extensionVersion:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject { +AEP_OPTIMIZE_METHOD(extensionVersion:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) +{ [AEPLog traceWithLabel:TAG message:@"extensionVersion is called."]; resolve([AEPMobileOptimize extensionVersion]); } -- (void)clearCachedPropositions { +AEP_OPTIMIZE_METHOD(clearCachedPropositions) +{ [AEPLog traceWithLabel:TAG message:@"clearCachedPropositions is called."]; [self clearPropositionsCache]; [AEPMobileOptimize clearCachedPropositions]; } -- (void)getPropositions:(NSArray *)decisionScopeNames +AEP_OPTIMIZE_METHOD(getPropositions:(NSArray *)decisionScopeNames resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject { + reject:(RCTPromiseRejectBlock)reject) +{ [AEPLog traceWithLabel:TAG message:@"getPropositions is called."]; NSArray *decisionScopesArray = [self createDecisionScopesArray:decisionScopeNames]; @@ -90,11 +106,12 @@ - (void)getPropositions:(NSArray *)decisionScopeNames }]; } -- (void)updatePropositions:(NSArray *)decisionScopeNames +AEP_OPTIMIZE_METHOD(updatePropositions:(NSArray *)decisionScopeNames xdm:(NSDictionary *)xdm data:(NSDictionary *)data onSuccess:(RCTResponseSenderBlock)onSuccess - onError:(RCTResponseSenderBlock)onError { + onError:(RCTResponseSenderBlock)onError) +{ [AEPLog traceWithLabel:TAG message:@"updatePropositions is called."]; NSArray *scopes = [self createDecisionScopesArray:decisionScopeNames]; [AEPMobileOptimize updatePropositions:scopes @@ -113,8 +130,16 @@ - (void)updatePropositions:(NSArray *)decisionScopeNames }]; } -- (void)onPropositionsUpdate { +AEP_OPTIMIZE_METHOD(onPropositionsUpdate) +{ [AEPLog traceWithLabel:TAG message:@"onPropositionsUpdate is called."]; + // AEP SDK adds a new MobileCore event listener on every call — register once per module. + if (_propositionsUpdateListenerRegistered) { + [AEPLog traceWithLabel:TAG + message:@"onPropositionsUpdate: AEP listener already registered, skipping duplicate registration."]; + return; + } + _propositionsUpdateListenerRegistered = YES; [AEPMobileOptimize onPropositionsUpdate:^( NSDictionary *decisionScopePropositionDict) { @@ -126,16 +151,29 @@ - (void)onPropositionsUpdate { [propositionDictionary setValue:[self convertPropositionToDict:proposition] forKey:key.name]; } - // Emit via codegen-generated emitOnPropositionsUpdated: (JSI-native). - // Guard: _eventEmitterCallback may be nil if the SDK fires the callback - // before JS has subscribed (e.g. cached propositions from a prior update). - if (_eventEmitterCallback) { - [self emitOnPropositionsUpdated:@{@"propositions": propositionDictionary}]; + NSDictionary *payload = @{@"propositions": propositionDictionary}; + void (^emitPayload)(void) = ^{ +#if USE_INTEROP_ROOT && !RCT_NEW_ARCH_ENABLED + [self sendEventWithName:@"onPropositionsUpdate" body:propositionDictionary]; +#else + if (self->_eventEmitterCallback) { + [self emitOnPropositionsUpdated:payload]; + } else { + [AEPLog traceWithLabel:TAG + message:@"onPropositionsUpdate: skipped emit — no JS listener registered yet"]; + } +#endif + }; + if ([NSThread isMainThread]) { + emitPayload(); + } else { + dispatch_async(dispatch_get_main_queue(), emitPayload); } }]; } -- (void)multipleOffersDisplayed:(NSArray *)offersArray { +AEP_OPTIMIZE_METHOD(multipleOffersDisplayed:(NSArray *)offersArray) +{ [AEPLog debugWithLabel:TAG message:@"multipleOffersDisplayed is called."]; NSMutableArray *nativeOffers = [self getNativeOffersFromOffersArray:offersArray]; if ([nativeOffers count] > 0) { @@ -143,9 +181,10 @@ - (void)multipleOffersDisplayed:(NSArray *)offersArray { } } -- (void)multipleOffersGenerateDisplayInteractionXdm:(NSArray *)offersArray +AEP_OPTIMIZE_METHOD(multipleOffersGenerateDisplayInteractionXdm:(NSArray *)offersArray resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject { + reject:(RCTPromiseRejectBlock)reject) +{ [AEPLog debugWithLabel:TAG message:@"multipleOffersGenerateDisplayInteractionXdm is called."]; NSMutableArray *nativeOffers = [self getNativeOffersFromOffersArray:offersArray]; if ([nativeOffers count] > 0) { @@ -155,8 +194,9 @@ - (void)multipleOffersGenerateDisplayInteractionXdm:(NSArray *)offersArray } } -- (void)offerDisplayed:(NSString *)offerId - propositionMap:(NSDictionary *)dictionary { +AEP_OPTIMIZE_METHOD(offerDisplayed:(NSString *)offerId + propositionMap:(NSDictionary *)dictionary) +{ [AEPLog debugWithLabel:TAG message:@"Offer Displayed"]; AEPOptimizeProposition *proposition = [AEPOptimizeProposition initFromData:dictionary]; NSArray *offers = [proposition offers]; @@ -165,8 +205,9 @@ - (void)offerDisplayed:(NSString *)offerId } } -- (void)offerTapped:(NSString *)offerId - propositionMap:(NSDictionary *)dictionary { +AEP_OPTIMIZE_METHOD(offerTapped:(NSString *)offerId + propositionMap:(NSDictionary *)dictionary) +{ [AEPLog debugWithLabel:TAG message:@"Offer Tapped"]; AEPOptimizeProposition *proposition = [AEPOptimizeProposition initFromData:dictionary]; NSArray *offers = [proposition offers]; @@ -175,10 +216,11 @@ - (void)offerTapped:(NSString *)offerId } } -- (void)generateDisplayInteractionXdm:(NSString *)offerId +AEP_OPTIMIZE_METHOD(generateDisplayInteractionXdm:(NSString *)offerId propositionMap:(NSDictionary *)dictionary resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject { + reject:(RCTPromiseRejectBlock)reject) +{ [AEPLog debugWithLabel:TAG message:@"generateDisplayInteractionXdm"]; AEPOptimizeProposition *proposition = [AEPOptimizeProposition initFromData:dictionary]; NSArray *offers = [proposition offers]; @@ -194,10 +236,11 @@ - (void)generateDisplayInteractionXdm:(NSString *)offerId } } -- (void)generateTapInteractionXdm:(NSString *)offerId +AEP_OPTIMIZE_METHOD(generateTapInteractionXdm:(NSString *)offerId propositionMap:(NSDictionary *)dictionary resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject { + reject:(RCTPromiseRejectBlock)reject) +{ [AEPLog debugWithLabel:TAG message:@"generateTapInteractionXdm"]; AEPOptimizeProposition *proposition = [AEPOptimizeProposition initFromData:dictionary]; NSArray *offers = [proposition offers]; @@ -213,21 +256,32 @@ - (void)generateTapInteractionXdm:(NSString *)offerId } } -- (void)generateReferenceXdm:(NSDictionary *)dictionary +AEP_OPTIMIZE_METHOD(generateReferenceXdm:(NSDictionary *)dictionary resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject { + reject:(RCTPromiseRejectBlock)reject) +{ [AEPLog debugWithLabel:TAG message:@"Proposition generateReferenceXdm"]; AEPOptimizeProposition *proposition = [AEPOptimizeProposition initFromData:dictionary]; resolve([proposition generateReferenceXdm]); } -// addListener/removeListeners kept in spec for backward compatibility. -// No-ops — event emission uses CodegenTypes.EventEmitter (JSI), not bridge. +// Old-arch interop: RCTEventEmitter provides addListener/removeListeners — do not override. +// SpecBase paths (turbo + new-arch interop): empty stubs for NativeEventEmitter compatibility. +#if !USE_INTEROP_ROOT || (USE_INTEROP_ROOT && RCT_NEW_ARCH_ENABLED) - (void)addListener:(NSString *)eventName { } - (void)removeListeners:(double)count { } +#endif + +#if USE_INTEROP_ROOT && !RCT_NEW_ARCH_ENABLED +#pragma mark - RCTEventEmitter (old-arch interop only) + +- (NSArray *)supportedEvents { + return @[ @"onPropositionsUpdate" ]; +} +#endif #pragma mark - Shared helper methods diff --git a/packages/optimize/package.json b/packages/optimize/package.json index aed35c2d..c8fd5045 100644 --- a/packages/optimize/package.json +++ b/packages/optimize/package.json @@ -8,6 +8,7 @@ "types": "./dist/index.d.ts", "scripts": { "cleanup": "rm -rf node_modules", + "prepare": "tsc", "tsc": "tsc" }, "repository": { diff --git a/packages/optimize/specs/NativeAEPOptimize.ts b/packages/optimize/specs/NativeAEPOptimize.ts index 5ee96fd7..4374252a 100644 --- a/packages/optimize/specs/NativeAEPOptimize.ts +++ b/packages/optimize/specs/NativeAEPOptimize.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the License. */ -import type { TurboModule, CodegenTypes } from 'react-native'; +import type { TurboModule } from 'react-native'; +import type { EventEmitter } from 'react-native/Libraries/Types/CodegenTypes'; import { TurboModuleRegistry } from 'react-native'; export type PropositionsPayload = { @@ -36,7 +37,7 @@ export interface Spec extends TurboModule { // TurboModule event support (codegen generates emitOnPropositionsUpdated:) // Different name from the legacy sendEventWithName:@"onPropositionsUpdate" // to avoid conflict between bridge and JSI event channels. - readonly onPropositionsUpdated: CodegenTypes.EventEmitter; + readonly onPropositionsUpdated: EventEmitter; } export default TurboModuleRegistry.getEnforcing('NativeAEPOptimize'); diff --git a/packages/optimize/src/NativeAEPOptimize.ts b/packages/optimize/src/NativeAEPOptimize.ts index 5a4378d7..616296d3 100644 --- a/packages/optimize/src/NativeAEPOptimize.ts +++ b/packages/optimize/src/NativeAEPOptimize.ts @@ -5,7 +5,7 @@ */ import type { TurboModule, EventSubscription } from 'react-native'; -import { TurboModuleRegistry } from 'react-native'; +import { NativeModules, TurboModuleRegistry } from 'react-native'; export type PropositionsPayload = { propositions: Object; @@ -32,10 +32,55 @@ export interface Spec extends TurboModule { generateReferenceXdm(propositionMap: Object): Promise; addListener(eventName: string): void; removeListeners(count: number): void; - // TurboModule event delivery — codegen generates emitOnPropositionsUpdated: (iOS / Android). - // Declared as an explicit function type because @types/react-native (0.66 era) - // predates the CodegenTypes namespace; functionally equivalent to CodegenTypes.EventEmitter. readonly onPropositionsUpdated: (handler: (event: PropositionsPayload) => void) => EventSubscription; } -export default TurboModuleRegistry.getEnforcing('NativeAEPOptimize'); +function hasUpdatePropositions(mod: Spec | null | undefined): mod is Spec { + return mod != null && typeof mod.updatePropositions === 'function'; +} + +function isClassicBridge(): boolean { + return (global as { RN$Bridgeless?: boolean }).RN$Bridgeless !== true; +} + +/** + * RN 0.76 old arch: no __turboModuleProxy — NativeModules is the only working surface. + * RN 0.85+ / new arch: turbo first, then bridge fallback. + */ +function resolveNativeAEPOptimize(): Spec { + const bridge = NativeModules.NativeAEPOptimize as Spec | undefined; + if (isClassicBridge() && hasUpdatePropositions(bridge)) { + return bridge; + } + const turbo = TurboModuleRegistry.get('NativeAEPOptimize'); + if (hasUpdatePropositions(turbo)) { + return turbo; + } + if (hasUpdatePropositions(bridge)) { + return bridge; + } + return TurboModuleRegistry.getEnforcing('NativeAEPOptimize'); +} + +let cachedNative: Spec | undefined; + +function getNativeAEPOptimize(): Spec { + if (!cachedNative || !hasUpdatePropositions(cachedNative)) { + cachedNative = resolveNativeAEPOptimize(); + } + return cachedNative; +} + +const NativeAEPOptimize: Spec = new Proxy({} as Spec, { + get(_target, prop) { + const mod = getNativeAEPOptimize(); + const value = (mod as unknown as Record)[prop]; + // Codegen EventEmitter must not be .bind()-wrapped — breaks JSI subscription on RN 0.85. + if (prop === 'onPropositionsUpdated') { + return value; + } + return typeof value === 'function' ? (value as Function).bind(mod) : value; + }, +}); + +export default NativeAEPOptimize; diff --git a/packages/optimize/src/Optimize.ts b/packages/optimize/src/Optimize.ts index 9d091a67..fd217a73 100644 --- a/packages/optimize/src/Optimize.ts +++ b/packages/optimize/src/Optimize.ts @@ -17,6 +17,7 @@ import Offer from './models/Offer'; import { AdobePropositionCallback } from './models/AdobePropositionCallback'; import AEPOptimizeError from './models/AEPOptimizeError'; import NativeAEPOptimize from './NativeAEPOptimize'; +import { subscribePropositionsUpdated } from './propositionEvents'; interface IOptimize { extensionVersion: () => Promise; @@ -35,6 +36,7 @@ interface IOptimize { } var onPropositionUpdateSubscription: EventSubscription | null = null; +var nativePropositionsUpdateRegistered = false; /** @@ -60,18 +62,20 @@ const Optimize: IOptimize = { onPropositionUpdateSubscription = null; } - // CodegenTypes.EventEmitter: payload is { propositions: { scopeName: proposition, ... } } - // on both iOS and Android (JSI-native delivery, no NativeEventEmitter needed). - onPropositionUpdateSubscription = NativeAEPOptimize.onPropositionsUpdated((payload: { propositions: any }) => { - const map = new Map(); - for (const [key, value] of Object.entries(payload.propositions)) { - map.set(key, new Proposition(value as any)); - } - adobeCallback.call(map); - }); + onPropositionUpdateSubscription = subscribePropositionsUpdated(adobeCallback); + + // AEP SDK stacks MobileCore listeners on each onPropositionsUpdate call — register once. + if (!nativePropositionsUpdateRegistered) { + NativeAEPOptimize.onPropositionsUpdate(); + nativePropositionsUpdateRegistered = true; + } - // Register the listener on the native AEP SDK side - NativeAEPOptimize.onPropositionsUpdate(); + // Bridge listener tracking (legacy interop); safe no-op on turbo. + try { + NativeAEPOptimize.addListener('onPropositionsUpdate'); + } catch { + // Turbo path: optional no-op stub. + } }, /** diff --git a/packages/optimize/src/propositionEvents.ts b/packages/optimize/src/propositionEvents.ts new file mode 100644 index 00000000..f697dc31 --- /dev/null +++ b/packages/optimize/src/propositionEvents.ts @@ -0,0 +1,118 @@ +/* +Copyright 2022 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +import { + DeviceEventEmitter, + EventSubscription, + NativeEventEmitter, + NativeModules, + Platform, + TurboModuleRegistry, +} from 'react-native'; +import Proposition from './models/Proposition'; +import { AdobePropositionCallback } from './models/AdobePropositionCallback'; +import type { PropositionsPayload, Spec } from './NativeAEPOptimize'; +import NativeAEPOptimize from './NativeAEPOptimize'; + +/** Bridge / DeviceEventEmitter name (legacy interop + Android). */ +export const INTEROP_PROPOSITIONS_EVENT = 'onPropositionsUpdate'; + +export function deliverPropositions( + adobeCallback: AdobePropositionCallback, + raw: { propositions?: Record } | Record +) { + try { + const propositionsObj = + raw != null && typeof raw === 'object' && 'propositions' in raw && raw.propositions != null + ? raw.propositions + : raw; + const map = new Map(); + for (const [key, value] of Object.entries(propositionsObj ?? {})) { + map.set(key, new Proposition(value as any)); + } + adobeCallback.call(map); + } catch (error) { + console.error('[AEPOptimize] deliverPropositions failed:', error); + } +} + +type JsiEventEmitter = + | ((handler: (event: PropositionsPayload) => void) => EventSubscription) + | { + addListener?: (handler: (event: PropositionsPayload) => void) => EventSubscription; + }; + +function getJsiEventEmitter(): JsiEventEmitter | undefined { + const fromRegistry = TurboModuleRegistry.get('NativeAEPOptimize')?.onPropositionsUpdated; + const fromModule = (NativeAEPOptimize as unknown as { onPropositionsUpdated?: JsiEventEmitter }) + .onPropositionsUpdated; + return (fromRegistry ?? fromModule) as JsiEventEmitter | undefined; +} + +function trySubscribeTurboJsi( + handler: (payload: Record | { propositions?: Record }) => void +): EventSubscription | null { + const emitter = getJsiEventEmitter(); + if (emitter == null) { + return null; + } + try { + if (typeof emitter === 'function') { + return emitter(handler as (event: PropositionsPayload) => void); + } + if (typeof emitter === 'object' && typeof emitter.addListener === 'function') { + return emitter.addListener(handler as (event: PropositionsPayload) => void); + } + } catch { + // Android codegen EventEmitter is not always JS-callable on older RN builds. + } + return null; +} + +/** + * Subscribe to native proposition-update events across RN 0.76–0.85 paths: + * - iOS/Android turbo + new-arch interop: codegen JSI EventEmitter (onPropositionsUpdated) + * - Android (all): DeviceEventEmitter (onPropositionsUpdate, flat map) + * - iOS old-arch interop: NativeEventEmitter + sendEventWithName + */ +export function subscribePropositionsUpdated( + adobeCallback: AdobePropositionCallback +): EventSubscription { + const handler = (payload: Record | { propositions?: Record }) => + deliverPropositions(adobeCallback, payload); + const subs: EventSubscription[] = []; + + if (Platform.OS === 'android') { + // Android (interop + turbo): codegen EventEmitter is not JS-callable — use DeviceEventEmitter. + subs.push(DeviceEventEmitter.addListener(INTEROP_PROPOSITIONS_EVENT, handler)); + } else { + // iOS turbo + new-arch interop: codegen JSI EventEmitter. + const jsiSub = trySubscribeTurboJsi(handler); + if (jsiSub) { + subs.push(jsiSub); + } else { + // iOS old-arch interop: sendEventWithName → DeviceEventEmitter. + const bridgeModule = (NativeModules as { NativeAEPOptimize?: object }).NativeAEPOptimize; + if (bridgeModule) { + subs.push( + new NativeEventEmitter(bridgeModule as any).addListener(INTEROP_PROPOSITIONS_EVENT, handler) + ); + } + } + } + + return { + remove: () => { + subs.forEach((s) => s.remove()); + }, + } as EventSubscription; +} From 68c85da9ae29dbbfa9cbe61e33cdecd86aae100d Mon Sep 17 00:00:00 2001 From: namArora3112 Date: Wed, 24 Jun 2026 14:32:43 +0530 Subject: [PATCH 2/8] bare 0.76 app added --- apps/BareSampleApp/.bundle/config | 2 + apps/BareSampleApp/.eslintrc.js | 4 + apps/BareSampleApp/.gitignore | 72 + apps/BareSampleApp/.prettierrc.js | 7 + apps/BareSampleApp/.watchmanconfig | 1 + apps/BareSampleApp/App.tsx | 194 + apps/BareSampleApp/Gemfile | 9 + apps/BareSampleApp/Gemfile.lock | 125 + apps/BareSampleApp/README.md | 112 + apps/BareSampleApp/android/app/build.gradle | 119 + apps/BareSampleApp/android/app/debug.keystore | Bin 0 -> 2257 bytes .../android/app/proguard-rules.pro | 10 + .../android/app/src/debug/AndroidManifest.xml | 9 + .../android/app/src/main/AndroidManifest.xml | 26 + .../java/com/baresampleapp/MainActivity.kt | 22 + .../java/com/baresampleapp/MainApplication.kt | 44 + .../res/drawable/rn_edit_text_material.xml | 37 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3056 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 5024 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2096 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2858 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4569 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 7098 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 6464 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 10676 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 9250 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 15523 bytes .../app/src/main/res/values/strings.xml | 3 + .../app/src/main/res/values/styles.xml | 9 + apps/BareSampleApp/android/build.gradle | 33 + apps/BareSampleApp/android/gradle.properties | 39 + .../android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43583 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 + apps/BareSampleApp/android/gradlew | 252 + apps/BareSampleApp/android/gradlew.bat | 94 + apps/BareSampleApp/android/settings.gradle | 6 + apps/BareSampleApp/app.json | 4 + apps/BareSampleApp/babel.config.js | 4 + apps/BareSampleApp/constants/Colors.js | 43 + apps/BareSampleApp/constants/Colors.ts | 42 + .../extensions/AssuranceView.tsx | 68 + .../extensions/CampaignClassicView.tsx | 63 + apps/BareSampleApp/extensions/ConsentView.tsx | 90 + apps/BareSampleApp/extensions/CoreView.tsx | 188 + .../extensions/EdgeBridgeView.tsx | 50 + .../extensions/EdgeIdentityView.tsx | 106 + apps/BareSampleApp/extensions/EdgeView.tsx | 150 + .../BareSampleApp/extensions/IdentityView.tsx | 90 + apps/BareSampleApp/extensions/InboxView.tsx | 494 + .../extensions/MessagingView.tsx | 245 + .../BareSampleApp/extensions/OptimizeView.tsx | 561 + apps/BareSampleApp/extensions/PlacesView.tsx | 108 + apps/BareSampleApp/extensions/ProfileView.tsx | 58 + apps/BareSampleApp/extensions/TargetView.tsx | 217 + apps/BareSampleApp/hooks/useColorScheme.js | 5 + apps/BareSampleApp/hooks/useColorScheme.ts | 1 + apps/BareSampleApp/index.js | 31 + apps/BareSampleApp/ios/.xcode.env | 11 + .../BareSampleApp.xcodeproj/project.pbxproj | 727 ++ .../xcschemes/BareSampleApp.xcscheme | 88 + .../xcschemes/BareSampleApp76.xcscheme | 88 + .../contents.xcworkspacedata | 10 + .../ios/BareSampleApp/AppDelegate.h | 6 + .../ios/BareSampleApp/AppDelegate.mm | 31 + .../AppIcon.appiconset/Contents.json | 53 + .../Images.xcassets/Contents.json | 6 + .../ios/BareSampleApp/Info.plist | 52 + .../ios/BareSampleApp/LaunchScreen.storyboard | 47 + .../ios/BareSampleApp/PrivacyInfo.xcprivacy | 38 + apps/BareSampleApp/ios/BareSampleApp/main.m | 10 + .../BareSampleAppTests/BareSampleApp76Tests.m | 66 + .../ios/BareSampleAppTests/Info.plist | 24 + apps/BareSampleApp/ios/Podfile | 61 + apps/BareSampleApp/ios/Podfile.lock | 2279 ++++ .../BareSampleApp/ios/Podfile.properties.json | 3 + apps/BareSampleApp/jest.config.js | 3 + apps/BareSampleApp/metro.config.js | 49 + .../mocks/contentCards/inbox/mockSettings.js | 131 + .../mocks/contentCards/inbox/mockSettings.ts | 132 + .../mocks/contentCards/templates/demoitems.js | 396 + .../mocks/contentCards/templates/demoitems.ts | 445 + .../mocks/contentCards/templates/imageOnly.js | 209 + .../mocks/contentCards/templates/imageOnly.ts | 222 + .../contentCards/templates/largeImage.js | 273 + .../contentCards/templates/largeImage.ts | 287 + .../contentCards/templates/smallImage.js | 317 + .../contentCards/templates/smallImage.ts | 331 + apps/BareSampleApp/package.json | 107 + apps/BareSampleApp/react-native.config.js | 6 + apps/BareSampleApp/scripts/build-matrix.sh | 505 + apps/BareSampleApp/styles/styles.ts | 48 + apps/BareSampleApp/tsconfig.json | 3 + apps/BareSampleApp/types/props.js | 2 + apps/BareSampleApp/types/props.ts | 3 + apps/BareSampleApp/yarn.lock | 9811 +++++++++++++++++ 95 files changed, 20734 insertions(+) create mode 100644 apps/BareSampleApp/.bundle/config create mode 100644 apps/BareSampleApp/.eslintrc.js create mode 100644 apps/BareSampleApp/.gitignore create mode 100644 apps/BareSampleApp/.prettierrc.js create mode 100644 apps/BareSampleApp/.watchmanconfig create mode 100644 apps/BareSampleApp/App.tsx create mode 100644 apps/BareSampleApp/Gemfile create mode 100644 apps/BareSampleApp/Gemfile.lock create mode 100644 apps/BareSampleApp/README.md create mode 100644 apps/BareSampleApp/android/app/build.gradle create mode 100644 apps/BareSampleApp/android/app/debug.keystore create mode 100644 apps/BareSampleApp/android/app/proguard-rules.pro create mode 100644 apps/BareSampleApp/android/app/src/debug/AndroidManifest.xml create mode 100644 apps/BareSampleApp/android/app/src/main/AndroidManifest.xml create mode 100644 apps/BareSampleApp/android/app/src/main/java/com/baresampleapp/MainActivity.kt create mode 100644 apps/BareSampleApp/android/app/src/main/java/com/baresampleapp/MainApplication.kt create mode 100644 apps/BareSampleApp/android/app/src/main/res/drawable/rn_edit_text_material.xml create mode 100644 apps/BareSampleApp/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 apps/BareSampleApp/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 apps/BareSampleApp/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 apps/BareSampleApp/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 apps/BareSampleApp/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 apps/BareSampleApp/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 apps/BareSampleApp/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 apps/BareSampleApp/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 apps/BareSampleApp/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 apps/BareSampleApp/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 apps/BareSampleApp/android/app/src/main/res/values/strings.xml create mode 100644 apps/BareSampleApp/android/app/src/main/res/values/styles.xml create mode 100644 apps/BareSampleApp/android/build.gradle create mode 100644 apps/BareSampleApp/android/gradle.properties create mode 100644 apps/BareSampleApp/android/gradle/wrapper/gradle-wrapper.jar create mode 100644 apps/BareSampleApp/android/gradle/wrapper/gradle-wrapper.properties create mode 100755 apps/BareSampleApp/android/gradlew create mode 100644 apps/BareSampleApp/android/gradlew.bat create mode 100644 apps/BareSampleApp/android/settings.gradle create mode 100644 apps/BareSampleApp/app.json create mode 100644 apps/BareSampleApp/babel.config.js create mode 100644 apps/BareSampleApp/constants/Colors.js create mode 100644 apps/BareSampleApp/constants/Colors.ts create mode 100644 apps/BareSampleApp/extensions/AssuranceView.tsx create mode 100644 apps/BareSampleApp/extensions/CampaignClassicView.tsx create mode 100644 apps/BareSampleApp/extensions/ConsentView.tsx create mode 100644 apps/BareSampleApp/extensions/CoreView.tsx create mode 100644 apps/BareSampleApp/extensions/EdgeBridgeView.tsx create mode 100644 apps/BareSampleApp/extensions/EdgeIdentityView.tsx create mode 100644 apps/BareSampleApp/extensions/EdgeView.tsx create mode 100644 apps/BareSampleApp/extensions/IdentityView.tsx create mode 100644 apps/BareSampleApp/extensions/InboxView.tsx create mode 100644 apps/BareSampleApp/extensions/MessagingView.tsx create mode 100644 apps/BareSampleApp/extensions/OptimizeView.tsx create mode 100644 apps/BareSampleApp/extensions/PlacesView.tsx create mode 100644 apps/BareSampleApp/extensions/ProfileView.tsx create mode 100644 apps/BareSampleApp/extensions/TargetView.tsx create mode 100644 apps/BareSampleApp/hooks/useColorScheme.js create mode 100644 apps/BareSampleApp/hooks/useColorScheme.ts create mode 100644 apps/BareSampleApp/index.js create mode 100644 apps/BareSampleApp/ios/.xcode.env create mode 100644 apps/BareSampleApp/ios/BareSampleApp.xcodeproj/project.pbxproj create mode 100644 apps/BareSampleApp/ios/BareSampleApp.xcodeproj/xcshareddata/xcschemes/BareSampleApp.xcscheme create mode 100644 apps/BareSampleApp/ios/BareSampleApp.xcodeproj/xcshareddata/xcschemes/BareSampleApp76.xcscheme create mode 100644 apps/BareSampleApp/ios/BareSampleApp.xcworkspace/contents.xcworkspacedata create mode 100644 apps/BareSampleApp/ios/BareSampleApp/AppDelegate.h create mode 100644 apps/BareSampleApp/ios/BareSampleApp/AppDelegate.mm create mode 100644 apps/BareSampleApp/ios/BareSampleApp/Images.xcassets/AppIcon.appiconset/Contents.json create mode 100644 apps/BareSampleApp/ios/BareSampleApp/Images.xcassets/Contents.json create mode 100644 apps/BareSampleApp/ios/BareSampleApp/Info.plist create mode 100644 apps/BareSampleApp/ios/BareSampleApp/LaunchScreen.storyboard create mode 100644 apps/BareSampleApp/ios/BareSampleApp/PrivacyInfo.xcprivacy create mode 100644 apps/BareSampleApp/ios/BareSampleApp/main.m create mode 100644 apps/BareSampleApp/ios/BareSampleAppTests/BareSampleApp76Tests.m create mode 100644 apps/BareSampleApp/ios/BareSampleAppTests/Info.plist create mode 100644 apps/BareSampleApp/ios/Podfile create mode 100644 apps/BareSampleApp/ios/Podfile.lock create mode 100644 apps/BareSampleApp/ios/Podfile.properties.json create mode 100644 apps/BareSampleApp/jest.config.js create mode 100644 apps/BareSampleApp/metro.config.js create mode 100644 apps/BareSampleApp/mocks/contentCards/inbox/mockSettings.js create mode 100644 apps/BareSampleApp/mocks/contentCards/inbox/mockSettings.ts create mode 100644 apps/BareSampleApp/mocks/contentCards/templates/demoitems.js create mode 100644 apps/BareSampleApp/mocks/contentCards/templates/demoitems.ts create mode 100644 apps/BareSampleApp/mocks/contentCards/templates/imageOnly.js create mode 100644 apps/BareSampleApp/mocks/contentCards/templates/imageOnly.ts create mode 100644 apps/BareSampleApp/mocks/contentCards/templates/largeImage.js create mode 100644 apps/BareSampleApp/mocks/contentCards/templates/largeImage.ts create mode 100644 apps/BareSampleApp/mocks/contentCards/templates/smallImage.js create mode 100644 apps/BareSampleApp/mocks/contentCards/templates/smallImage.ts create mode 100644 apps/BareSampleApp/package.json create mode 100644 apps/BareSampleApp/react-native.config.js create mode 100755 apps/BareSampleApp/scripts/build-matrix.sh create mode 100644 apps/BareSampleApp/styles/styles.ts create mode 100644 apps/BareSampleApp/tsconfig.json create mode 100644 apps/BareSampleApp/types/props.js create mode 100644 apps/BareSampleApp/types/props.ts create mode 100644 apps/BareSampleApp/yarn.lock diff --git a/apps/BareSampleApp/.bundle/config b/apps/BareSampleApp/.bundle/config new file mode 100644 index 00000000..848943bb --- /dev/null +++ b/apps/BareSampleApp/.bundle/config @@ -0,0 +1,2 @@ +BUNDLE_PATH: "vendor/bundle" +BUNDLE_FORCE_RUBY_PLATFORM: 1 diff --git a/apps/BareSampleApp/.eslintrc.js b/apps/BareSampleApp/.eslintrc.js new file mode 100644 index 00000000..187894b6 --- /dev/null +++ b/apps/BareSampleApp/.eslintrc.js @@ -0,0 +1,4 @@ +module.exports = { + root: true, + extends: '@react-native', +}; diff --git a/apps/BareSampleApp/.gitignore b/apps/BareSampleApp/.gitignore new file mode 100644 index 00000000..dee6edc9 --- /dev/null +++ b/apps/BareSampleApp/.gitignore @@ -0,0 +1,72 @@ +# OSX +# +.DS_Store + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate +ios/.xcode.env.local + +# Android/IntelliJ +# +build/ +.idea +.gradle +local.properties +*.iml +*.hprof +.cxx/ +*.keystore +!debug.keystore + +# node.js +# +node_modules/ +npm-debug.log +yarn-error.log + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/ + +**/fastlane/report.xml +**/fastlane/Preview.html +**/fastlane/screenshots +**/fastlane/test_output + +# Stale tsc output — Metro resolves .js before .tsx and breaks module resolution +extensions/*.js +styles/styles.js + +# Bundle artifact +*.jsbundle + +# Ruby / CocoaPods +/ios/Pods/ +/vendor/bundle/ + +# Temporary files created by Metro to check the health of the file watcher +.metro-health-check* +.metro-build-matrix.log +.metro-build-matrix.pid + +# testing +/coverage diff --git a/apps/BareSampleApp/.prettierrc.js b/apps/BareSampleApp/.prettierrc.js new file mode 100644 index 00000000..2b540746 --- /dev/null +++ b/apps/BareSampleApp/.prettierrc.js @@ -0,0 +1,7 @@ +module.exports = { + arrowParens: 'avoid', + bracketSameLine: true, + bracketSpacing: false, + singleQuote: true, + trailingComma: 'all', +}; diff --git a/apps/BareSampleApp/.watchmanconfig b/apps/BareSampleApp/.watchmanconfig new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/apps/BareSampleApp/.watchmanconfig @@ -0,0 +1 @@ +{} diff --git a/apps/BareSampleApp/App.tsx b/apps/BareSampleApp/App.tsx new file mode 100644 index 00000000..b0e4ba74 --- /dev/null +++ b/apps/BareSampleApp/App.tsx @@ -0,0 +1,194 @@ +/* +Copyright 2024 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +import * as React from 'react'; +import {Button, View, Text, TextInput, ScrollView, StyleSheet, useColorScheme} from 'react-native'; +import {createDrawerNavigator} from '@react-navigation/drawer'; +import {DarkTheme, DefaultTheme, NavigationContainer} from '@react-navigation/native'; +import OptimizeView from './extensions/OptimizeView'; +import ProfileView from './extensions/ProfileView'; +import MessagingView from './extensions/MessagingView'; +import InboxView from './extensions/InboxView'; +import CoreView from './extensions/CoreView'; +import IdentityView from './extensions/IdentityView'; +import ConsentView from './extensions/ConsentView'; +import EdgeBridgeView from './extensions/EdgeBridgeView'; +import EdgeView from './extensions/EdgeView'; +import AssuranceView from './extensions/AssuranceView'; +import EdgeIdentityView from './extensions/EdgeIdentityView'; +import TargetView from './extensions/TargetView'; +import PlacesView from './extensions/PlacesView'; +import {NavigationProps} from './types/props'; +import CampaignClassicView from './extensions/CampaignClassicView'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { MobileCore, LogLevel } from '@adobe/react-native-aepcore'; +import { useState, useEffect, createContext, useContext } from 'react'; + +declare global { + // RN 0.76+ — true when bridgeless/new-arch runtime is active + // eslint-disable-next-line no-var + var RN$Bridgeless: boolean | undefined; +} + +const STORAGE_KEY = 'aep_app_id'; + +const DEFAULT_APP_ID = '3149c49c3910/0f12baf27522/launch-0d096c129660-development'; + +export const AppContext = createContext({ + appId: DEFAULT_APP_ID, + initSDK: (_id: string) => {}, + rnArchLabel: 'checking…', +}); + +function getRnArchLabel(): string { + return global.RN$Bridgeless === true + ? 'bridgeless / new-arch runtime' + : 'classic bridge (old arch)'; +} + +function HomeScreen({navigation}: NavigationProps) { + const { appId, initSDK, rnArchLabel } = useContext(AppContext); + const [inputAppId, setInputAppId] = useState(appId); + + // Sync input when persisted appId is loaded on startup. + useEffect(() => { setInputAppId(DEFAULT_APP_ID); }, [DEFAULT_APP_ID]); + + return ( + + RN runtime: {rnArchLabel} + App ID + + Active: {appId} +