react-native-marketap-sdk 0.1.0-beta.8 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/android/build.gradle +1 -1
  2. package/android/src/main/AndroidManifest.xml +1 -10
  3. package/android/src/main/java/com/marketapsdk/MarketapSdkModule.kt +299 -62
  4. package/android/src/main/java/com/marketapsdk/ReactNativeBridgeRegistry.kt +110 -0
  5. package/ios/MarketapSdk.m +30 -23
  6. package/ios/MarketapSdk.swift +232 -162
  7. package/lib/commonjs/MarketapWebBridge.js +205 -15
  8. package/lib/commonjs/MarketapWebBridge.js.map +1 -1
  9. package/lib/commonjs/core/MarketapCore.js +233 -0
  10. package/lib/commonjs/core/MarketapCore.js.map +1 -0
  11. package/lib/commonjs/index.js +20 -150
  12. package/lib/commonjs/index.js.map +1 -1
  13. package/lib/commonjs/internal/marketapCore.js +9 -0
  14. package/lib/commonjs/internal/marketapCore.js.map +1 -0
  15. package/lib/commonjs/internal/marketapPlugin.js +40 -0
  16. package/lib/commonjs/internal/marketapPlugin.js.map +1 -0
  17. package/lib/commonjs/version.js +1 -1
  18. package/lib/commonjs/version.js.map +1 -1
  19. package/lib/module/MarketapWebBridge.js +204 -15
  20. package/lib/module/MarketapWebBridge.js.map +1 -1
  21. package/lib/module/core/MarketapCore.js +226 -0
  22. package/lib/module/core/MarketapCore.js.map +1 -0
  23. package/lib/module/index.js +20 -149
  24. package/lib/module/index.js.map +1 -1
  25. package/lib/module/internal/marketapCore.js +3 -0
  26. package/lib/module/internal/marketapCore.js.map +1 -0
  27. package/lib/module/internal/marketapPlugin.js +35 -0
  28. package/lib/module/internal/marketapPlugin.js.map +1 -0
  29. package/lib/module/version.js +1 -1
  30. package/lib/module/version.js.map +1 -1
  31. package/lib/typescript/MarketapWebBridge.d.ts +33 -6
  32. package/lib/typescript/MarketapWebBridge.d.ts.map +1 -1
  33. package/lib/typescript/core/MarketapCore.d.ts +54 -0
  34. package/lib/typescript/core/MarketapCore.d.ts.map +1 -0
  35. package/lib/typescript/index.d.ts +2 -40
  36. package/lib/typescript/index.d.ts.map +1 -1
  37. package/lib/typescript/internal/marketapCore.d.ts +3 -0
  38. package/lib/typescript/internal/marketapCore.d.ts.map +1 -0
  39. package/lib/typescript/internal/marketapPlugin.d.ts +10 -0
  40. package/lib/typescript/internal/marketapPlugin.d.ts.map +1 -0
  41. package/lib/typescript/types.d.ts +1 -2
  42. package/lib/typescript/types.d.ts.map +1 -1
  43. package/lib/typescript/version.d.ts +1 -1
  44. package/lib/typescript/version.d.ts.map +1 -1
  45. package/package.json +1 -1
  46. package/react-native-marketap-sdk.podspec +1 -1
@@ -99,7 +99,7 @@ dependencies {
99
99
  implementation "com.facebook.react:react-native:+"
100
100
 
101
101
  // MarketapSDK dependency with forced version constraints
102
- implementation('com.github.marketap-dev:marketap-android-sdk:1.1.7') {
102
+ implementation('com.github.marketap-dev:marketap-android-sdk:1.3.3') {
103
103
  exclude group: 'androidx.appcompat', module: 'appcompat'
104
104
  exclude group: 'androidx.core', module: 'core-ktx'
105
105
  }
@@ -1,12 +1,3 @@
1
1
  <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
2
  package="com.marketapsdk">
3
- <application>
4
- <service
5
- android:name="com.marketap.sdk.client.push.MarketapFirebaseMessagingService"
6
- android:exported="false">
7
- <intent-filter>
8
- <action android:name="com.google.firebase.MESSAGING_EVENT" />
9
- </intent-filter>
10
- </service>
11
- </application>
12
- </manifest>
3
+ </manifest>
@@ -13,6 +13,8 @@ import com.facebook.react.bridge.Arguments
13
13
  import com.facebook.react.modules.core.DeviceEventManagerModule
14
14
  import com.facebook.react.bridge.LifecycleEventListener
15
15
  import com.marketap.sdk.Marketap
16
+ import com.marketap.sdk.MarketapPlugin
17
+ import com.marketap.sdk.MarketapWebBridge
16
18
  import com.marketap.sdk.model.external.MarketapCampaignType
17
19
  import com.marketap.sdk.model.external.MarketapLogLevel
18
20
  import com.marketap.sdk.client.CurrentActivityHolder
@@ -22,31 +24,50 @@ private fun ReadableMap?.toNonNullableMap(): Map<String, Any>? {
22
24
  }
23
25
 
24
26
  class MarketapSdkModule(reactContext: ReactApplicationContext) :
25
- ReactContextBaseJavaModule(reactContext), LifecycleEventListener {
27
+ ReactContextBaseJavaModule(reactContext), LifecycleEventListener, ReactNativeBridgeTarget {
26
28
 
27
29
  companion object {
28
30
  const val NAME = "MarketapSdk"
29
31
  private const val CLICK_EVENT = "MarketapClickEvent"
30
-
31
- private var pendingClickEvent: WritableMap? = null
32
+ private const val IN_APP_MESSAGE_EVENT = "MarketapInAppMessageEvent"
33
+
34
+ private var isExternalInAppCallbackRegistered = false
32
35
  }
33
36
 
34
37
  private var hasListeners = false
35
38
  private var isInitialized = false
36
39
  private var initializedProjectId: String? = null
40
+ private var clickHandlerRegistered = false
41
+ private var isModuleActive = true
42
+
43
+ init {
44
+ reactApplicationContext.addLifecycleEventListener(this)
45
+ ReactNativeBridgeRegistry.register(this)
46
+ registerExternalInAppCallback()
47
+ }
37
48
 
38
49
  override fun getName(): String {
39
50
  return NAME
40
51
  }
41
52
 
53
+ override val isAttachedToReact: Boolean
54
+ get() = isModuleActive
55
+
56
+ override val isClickHandlerRegistered: Boolean
57
+ get() = clickHandlerRegistered
58
+
59
+ override val isReactReady: Boolean
60
+ get() = isModuleActive && hasListeners && isReactInstanceReady()
61
+
42
62
  @ReactMethod
43
- fun initialize(projectId: String, promise: Promise) {
63
+ fun initialize(payload: ReadableMap?, promise: Promise) {
44
64
  try {
65
+ val projectId = payload?.getString("projectId") ?: ""
45
66
  if (!isMainProcess()) {
46
67
  promise.resolve(null)
47
68
  return
48
69
  }
49
-
70
+
50
71
  if (isInitialized) {
51
72
  if (initializedProjectId == projectId) {
52
73
  promise.resolve(null)
@@ -57,7 +78,7 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
57
78
  return
58
79
  }
59
80
  }
60
-
81
+
61
82
  // Initialize MarketapSDK with projectId
62
83
  val application = reactApplicationContext.applicationContext as Application
63
84
  CurrentActivityHolder.applyToApplication(application)
@@ -71,29 +92,48 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
71
92
  }
72
93
 
73
94
  @ReactMethod
74
- fun setLogLevel(logLevel: Int) {
95
+ fun setLogLevel(payload: ReadableMap?) {
96
+ val logLevel =
97
+ if (payload != null && payload.hasKey("logLevel") && !payload.isNull("logLevel")) {
98
+ payload.getInt("logLevel")
99
+ } else {
100
+ 0
101
+ }
75
102
  val resolvedLevel = MarketapLogLevel.values().firstOrNull { level ->
76
103
  level.value == logLevel
77
104
  } ?: MarketapLogLevel.NONE
78
105
  Marketap.setLogLevel(resolvedLevel)
79
106
  }
80
-
107
+
81
108
  private fun isMainProcess(): Boolean {
82
109
  val am = reactApplicationContext.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
83
110
  return am.runningAppProcesses?.find { it.pid == android.os.Process.myPid() }?.processName == reactApplicationContext.packageName
84
111
  }
85
112
 
86
113
  @ReactMethod
87
- fun signup(
88
- userId: String,
89
- userProperties: ReadableMap?,
90
- eventProperties: ReadableMap?,
91
- persistUser: Boolean?,
92
- promise: Promise
93
- ) {
114
+ fun signup(payload: ReadableMap?, promise: Promise) {
94
115
  try {
116
+ val userId = payload?.getString("userId") ?: ""
117
+ val userProperties =
118
+ if (payload != null && payload.hasKey("userProperties") && !payload.isNull("userProperties")) {
119
+ payload.getMap("userProperties")
120
+ } else {
121
+ null
122
+ }
123
+ val eventProperties =
124
+ if (payload != null && payload.hasKey("eventProperties") && !payload.isNull("eventProperties")) {
125
+ payload.getMap("eventProperties")
126
+ } else {
127
+ null
128
+ }
129
+ val persistUser =
130
+ if (payload != null && payload.hasKey("persistUser") && !payload.isNull("persistUser")) {
131
+ payload.getBoolean("persistUser")
132
+ } else {
133
+ true
134
+ }
95
135
  // Call MarketapSDK signup method
96
- Marketap.signup(userId, userProperties.toNonNullableMap(), eventProperties.toNonNullableMap(), persistUser ?: true)
136
+ Marketap.signup(userId, userProperties.toNonNullableMap(), eventProperties.toNonNullableMap(), persistUser)
97
137
  promise.resolve(null)
98
138
  } catch (e: Exception) {
99
139
  promise.reject("SIGNUP_ERROR", e.message, e)
@@ -101,13 +141,21 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
101
141
  }
102
142
 
103
143
  @ReactMethod
104
- fun login(
105
- userId: String,
106
- userProperties: ReadableMap?,
107
- eventProperties: ReadableMap?,
108
- promise: Promise
109
- ) {
144
+ fun login(payload: ReadableMap?, promise: Promise) {
110
145
  try {
146
+ val userId = payload?.getString("userId") ?: ""
147
+ val userProperties =
148
+ if (payload != null && payload.hasKey("userProperties") && !payload.isNull("userProperties")) {
149
+ payload.getMap("userProperties")
150
+ } else {
151
+ null
152
+ }
153
+ val eventProperties =
154
+ if (payload != null && payload.hasKey("eventProperties") && !payload.isNull("eventProperties")) {
155
+ payload.getMap("eventProperties")
156
+ } else {
157
+ null
158
+ }
111
159
  // Call MarketapSDK login method
112
160
  Marketap.login(userId, userProperties.toNonNullableMap(), eventProperties.toNonNullableMap())
113
161
  promise.resolve(null)
@@ -117,8 +165,14 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
117
165
  }
118
166
 
119
167
  @ReactMethod
120
- fun logout(eventProperties: ReadableMap?, promise: Promise) {
168
+ fun logout(payload: ReadableMap?, promise: Promise) {
121
169
  try {
170
+ val eventProperties =
171
+ if (payload != null && payload.hasKey("eventProperties") && !payload.isNull("eventProperties")) {
172
+ payload.getMap("eventProperties")
173
+ } else {
174
+ null
175
+ }
122
176
  // Call MarketapSDK logout method
123
177
  Marketap.logout(eventProperties.toNonNullableMap())
124
178
  promise.resolve(null)
@@ -128,8 +182,15 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
128
182
  }
129
183
 
130
184
  @ReactMethod
131
- fun track(eventName: String, eventProperties: ReadableMap?, promise: Promise) {
185
+ fun track(payload: ReadableMap?, promise: Promise) {
132
186
  try {
187
+ val eventName = payload?.getString("eventName") ?: ""
188
+ val eventProperties =
189
+ if (payload != null && payload.hasKey("eventProperties") && !payload.isNull("eventProperties")) {
190
+ payload.getMap("eventProperties")
191
+ } else {
192
+ null
193
+ }
133
194
  // Call MarketapSDK track method
134
195
  Marketap.track(eventName, eventProperties.toNonNullableMap())
135
196
  promise.resolve(null)
@@ -139,8 +200,20 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
139
200
  }
140
201
 
141
202
  @ReactMethod
142
- fun trackPurchase(revenue: Double, eventProperties: ReadableMap?, promise: Promise) {
203
+ fun trackPurchase(payload: ReadableMap?, promise: Promise) {
143
204
  try {
205
+ val revenue =
206
+ if (payload != null && payload.hasKey("revenue") && !payload.isNull("revenue")) {
207
+ payload.getDouble("revenue")
208
+ } else {
209
+ 0.0
210
+ }
211
+ val eventProperties =
212
+ if (payload != null && payload.hasKey("eventProperties") && !payload.isNull("eventProperties")) {
213
+ payload.getMap("eventProperties")
214
+ } else {
215
+ null
216
+ }
144
217
  // Call MarketapSDK trackPurchase method
145
218
  Marketap.trackPurchase(revenue, eventProperties.toNonNullableMap())
146
219
  promise.resolve(null)
@@ -150,13 +223,21 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
150
223
  }
151
224
 
152
225
  @ReactMethod
153
- fun trackRevenue(
154
- eventName: String,
155
- revenue: Double,
156
- eventProperties: ReadableMap?,
157
- promise: Promise
158
- ) {
226
+ fun trackRevenue(payload: ReadableMap?, promise: Promise) {
159
227
  try {
228
+ val eventName = payload?.getString("eventName") ?: ""
229
+ val revenue =
230
+ if (payload != null && payload.hasKey("revenue") && !payload.isNull("revenue")) {
231
+ payload.getDouble("revenue")
232
+ } else {
233
+ 0.0
234
+ }
235
+ val eventProperties =
236
+ if (payload != null && payload.hasKey("eventProperties") && !payload.isNull("eventProperties")) {
237
+ payload.getMap("eventProperties")
238
+ } else {
239
+ null
240
+ }
160
241
  // Call MarketapSDK trackRevenue method
161
242
  Marketap.trackRevenue(eventName, revenue, eventProperties.toNonNullableMap())
162
243
  promise.resolve(null)
@@ -166,8 +247,14 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
166
247
  }
167
248
 
168
249
  @ReactMethod
169
- fun trackPageView(eventProperties: ReadableMap?, promise: Promise) {
250
+ fun trackPageView(payload: ReadableMap?, promise: Promise) {
170
251
  try {
252
+ val eventProperties =
253
+ if (payload != null && payload.hasKey("eventProperties") && !payload.isNull("eventProperties")) {
254
+ payload.getMap("eventProperties")
255
+ } else {
256
+ null
257
+ }
171
258
  // Call MarketapSDK trackPageView method
172
259
  Marketap.trackPageView(eventProperties.toNonNullableMap())
173
260
  promise.resolve(null)
@@ -177,8 +264,15 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
177
264
  }
178
265
 
179
266
  @ReactMethod
180
- fun identify(userId: String, userProperties: ReadableMap?, promise: Promise) {
267
+ fun identify(payload: ReadableMap?, promise: Promise) {
181
268
  try {
269
+ val userId = payload?.getString("userId") ?: ""
270
+ val userProperties =
271
+ if (payload != null && payload.hasKey("userProperties") && !payload.isNull("userProperties")) {
272
+ payload.getMap("userProperties")
273
+ } else {
274
+ null
275
+ }
182
276
  // Call MarketapSDK identify method
183
277
  Marketap.identify(userId, userProperties.toNonNullableMap())
184
278
  promise.resolve(null)
@@ -201,22 +295,14 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
201
295
  @ReactMethod
202
296
  fun setClickHandler(promise: Promise) {
203
297
  try {
298
+ clickHandlerRegistered = true
204
299
  Marketap.setClickHandler { event ->
205
300
  val clickData = convertClickEventToMap(event)
206
-
207
- if (hasListeners) {
208
- sendEvent(CLICK_EVENT, clickData)
209
- pendingClickEvent = null
210
- } else {
211
- pendingClickEvent = clickData
212
- }
213
- }
214
-
215
- pendingClickEvent?.let { pendingEvent ->
216
- sendEvent(CLICK_EVENT, pendingEvent)
217
- pendingClickEvent = null
301
+
302
+ ReactNativeBridgeRegistry.deliverClick(clickData)
218
303
  }
219
-
304
+
305
+ ReactNativeBridgeRegistry.flushIfPending()
220
306
  promise.resolve(null)
221
307
  } catch (e: Exception) {
222
308
  promise.reject("SET_CLICK_HANDLER_ERROR", e.message, e)
@@ -237,34 +323,139 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
237
323
  }
238
324
  }
239
325
 
326
+ // 웹브릿지 인앱 메시지 관련 메서드
327
+ @ReactMethod
328
+ fun trackFromWebBridge(payload: ReadableMap?, promise: Promise) {
329
+ try {
330
+ val eventName = payload?.getString("eventName") ?: ""
331
+ val eventProperties =
332
+ if (payload != null && payload.hasKey("eventProperties") && !payload.isNull("eventProperties")) {
333
+ payload.getMap("eventProperties")
334
+ } else {
335
+ null
336
+ }
337
+ val handleInAppInReactNative =
338
+ if (payload != null && payload.hasKey("handleInAppInReactNative") && !payload.isNull("handleInAppInReactNative")) {
339
+ payload.getBoolean("handleInAppInReactNative")
340
+ } else {
341
+ false
342
+ }
343
+ if (handleInAppInReactNative) {
344
+ ReactNativeBridgeRegistry.setWebBridgeSourceTarget(this)
345
+ MarketapWebBridge.setExternalWebBridgeActive(true)
346
+ } else {
347
+ ReactNativeBridgeRegistry.clearWebBridgeSourceTarget()
348
+ MarketapWebBridge.setExternalWebBridgeActive(false)
349
+ }
350
+ MarketapPlugin.trackEvent(eventName, eventProperties.toNonNullableMap())
351
+ promise.resolve(null)
352
+ } catch (e: Exception) {
353
+ promise.reject("TRACK_FROM_WEB_BRIDGE_ERROR", e.message, e)
354
+ }
355
+ }
356
+
357
+ @ReactMethod
358
+ fun setUserProperties(payload: ReadableMap?, promise: Promise) {
359
+ try {
360
+ val userProperties =
361
+ if (payload != null && payload.hasKey("userProperties") && !payload.isNull("userProperties")) {
362
+ payload.getMap("userProperties")
363
+ } else {
364
+ null
365
+ }
366
+ val props = userProperties.toNonNullableMap()
367
+ if (props == null) {
368
+ promise.reject("ARG_ERROR", "Missing userProperties", null)
369
+ return
370
+ }
371
+ MarketapPlugin.setUserProperties(props)
372
+ promise.resolve(null)
373
+ } catch (e: Exception) {
374
+ promise.reject("SET_USER_PROPERTIES_ERROR", e.message, e)
375
+ }
376
+ }
377
+
240
378
  @ReactMethod
241
- fun setPushToken(token: String, promise: Promise) {
379
+ fun trackInAppImpression(payload: ReadableMap?, promise: Promise) {
242
380
  try {
243
- // Android: setPushToken not available - MarketapFirebaseMessagingService handles tokens automatically
381
+ val campaignId = payload?.getString("campaignId")
382
+ val messageId = payload?.getString("messageId")
383
+ val layoutSubType = payload?.getString("layoutSubType")
384
+ if (campaignId == null || messageId == null) {
385
+ android.util.Log.w(
386
+ NAME,
387
+ "trackInAppImpression: missing required params (campaignId=$campaignId, messageId=$messageId)"
388
+ )
389
+ promise.resolve(null)
390
+ return
391
+ }
392
+ MarketapPlugin.trackInAppImpression(campaignId, messageId, layoutSubType)
244
393
  promise.resolve(null)
245
394
  } catch (e: Exception) {
246
- promise.reject("PUSH_TOKEN_ERROR", e.message, e)
395
+ promise.reject("TRACK_INAPP_IMPRESSION_ERROR", e.message, e)
247
396
  }
248
397
  }
249
-
398
+
250
399
  @ReactMethod
251
- fun getInitialNotification(promise: Promise) {
400
+ fun trackInAppClick(payload: ReadableMap?, promise: Promise) {
252
401
  try {
253
- val pendingEvent = pendingClickEvent
254
- if (pendingEvent != null) {
255
- pendingClickEvent = null
256
- promise.resolve(pendingEvent)
257
- } else {
402
+ val campaignId = payload?.getString("campaignId")
403
+ val messageId = payload?.getString("messageId")
404
+ val locationId = payload?.getString("locationId")
405
+ val url = payload?.getString("url")
406
+ val layoutSubType = payload?.getString("layoutSubType")
407
+ if (campaignId == null || messageId == null || locationId == null) {
408
+ android.util.Log.w(
409
+ NAME,
410
+ "trackInAppClick: missing required params (campaignId=$campaignId, messageId=$messageId, locationId=$locationId)"
411
+ )
258
412
  promise.resolve(null)
413
+ return
259
414
  }
415
+ MarketapPlugin.trackInAppClick(campaignId, messageId, locationId, url, layoutSubType)
416
+ promise.resolve(null)
260
417
  } catch (e: Exception) {
261
- promise.reject("GET_INITIAL_NOTIFICATION_ERROR", e.message, e)
418
+ promise.reject("TRACK_INAPP_CLICK_ERROR", e.message, e)
419
+ }
420
+ }
421
+
422
+ @ReactMethod
423
+ fun hideCampaign(payload: ReadableMap?, promise: Promise) {
424
+ try {
425
+ val campaignId = payload?.getString("campaignId")
426
+ val hideType = payload?.getString("hideType")
427
+ if (campaignId == null) {
428
+ android.util.Log.w(NAME, "hideCampaign: missing campaignId")
429
+ promise.resolve(null)
430
+ return
431
+ }
432
+ MarketapPlugin.hideInAppMessage(campaignId, hideType)
433
+ promise.resolve(null)
434
+ } catch (e: Exception) {
435
+ promise.reject("HIDE_CAMPAIGN_ERROR", e.message, e)
436
+ }
437
+ }
438
+
439
+ @ReactMethod
440
+ fun setDeviceOptIn(payload: ReadableMap?, promise: Promise) {
441
+ try {
442
+ val optIn: Boolean? =
443
+ if (payload != null && payload.hasKey("optIn") && !payload.isNull("optIn")) {
444
+ payload.getBoolean("optIn")
445
+ } else {
446
+ null
447
+ }
448
+ Marketap.setDeviceOptIn(optIn)
449
+ promise.resolve(null)
450
+ } catch (e: Exception) {
451
+ promise.reject("SET_DEVICE_OPT_IN_ERROR", e.message, e)
262
452
  }
263
453
  }
264
454
 
265
455
  @ReactMethod
266
456
  fun addListener(eventName: String) {
267
457
  hasListeners = true
458
+ ReactNativeBridgeRegistry.flushIfPending()
268
459
  }
269
460
 
270
461
  @ReactMethod
@@ -272,12 +463,21 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
272
463
  hasListeners = false
273
464
  }
274
465
 
275
- private fun sendEvent(eventName: String, params: WritableMap?) {
276
- if (hasListeners) {
466
+ private fun sendEvent(eventName: String, params: WritableMap?): Boolean {
467
+ if (!hasListeners || !isReactInstanceReady()) {
468
+ return false
469
+ }
470
+ reactApplicationContext.runOnJSQueueThread {
277
471
  reactApplicationContext
278
472
  .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
279
473
  .emit(eventName, params)
280
474
  }
475
+ return true
476
+ }
477
+
478
+ private fun isReactInstanceReady(): Boolean {
479
+ return reactApplicationContext.hasActiveCatalystInstance() &&
480
+ reactApplicationContext.hasActiveReactInstance()
281
481
  }
282
482
 
283
483
  private fun convertClickEventToMap(event: com.marketap.sdk.model.external.MarketapClickEvent): WritableMap {
@@ -293,15 +493,52 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
293
493
  return map
294
494
  }
295
495
 
496
+ override fun sendInAppMessage(
497
+ campaign: Map<String, Any?>,
498
+ messageId: String,
499
+ hasCustomClickHandler: Boolean
500
+ ) {
501
+ val args = Arguments.createMap()
502
+ args.putMap("campaign", Arguments.makeNativeMap(campaign))
503
+ args.putString("messageId", messageId)
504
+ args.putBoolean("hasCustomClickHandler", hasCustomClickHandler)
505
+ sendEvent(IN_APP_MESSAGE_EVENT, args)
506
+ }
507
+
508
+ override fun sendClick(clickData: WritableMap) {
509
+ sendEvent(CLICK_EVENT, clickData)
510
+ }
511
+
296
512
  override fun onHostResume() {
297
- // Handle app resume
513
+ val activity = getCurrentActivity() ?: return
514
+ CurrentActivityHolder.set(activity)
515
+ ReactNativeBridgeRegistry.flushIfPending()
298
516
  }
299
517
 
300
518
  override fun onHostPause() {
301
- // Handle app pause
302
519
  }
303
520
 
304
521
  override fun onHostDestroy() {
305
- // Handle app destroy
522
+ isModuleActive = false
523
+ ReactNativeBridgeRegistry.unregister(this)
524
+ reactApplicationContext.removeLifecycleEventListener(this)
525
+ }
526
+
527
+ override fun invalidate() {
528
+ isModuleActive = false
529
+ ReactNativeBridgeRegistry.unregister(this)
530
+ reactApplicationContext.removeLifecycleEventListener(this)
531
+ super.invalidate()
532
+ }
533
+
534
+ private fun registerExternalInAppCallback() {
535
+ if (isExternalInAppCallbackRegistered) {
536
+ return
537
+ }
538
+ isExternalInAppCallbackRegistered = true
539
+
540
+ MarketapWebBridge.setExternalInAppMessageCallback { campaign, messageId, hasCustomClickHandler ->
541
+ ReactNativeBridgeRegistry.sendInAppMessage(campaign, messageId, hasCustomClickHandler)
542
+ }
306
543
  }
307
544
  }
@@ -0,0 +1,110 @@
1
+ package com.marketapsdk
2
+
3
+ import com.facebook.react.bridge.WritableMap
4
+
5
+ internal interface ReactNativeBridgeTarget {
6
+ val isAttachedToReact: Boolean
7
+ val isClickHandlerRegistered: Boolean
8
+ val isReactReady: Boolean
9
+
10
+ fun sendInAppMessage(
11
+ campaign: Map<String, Any?>,
12
+ messageId: String,
13
+ hasCustomClickHandler: Boolean
14
+ )
15
+
16
+ fun sendClick(clickData: WritableMap)
17
+ }
18
+
19
+ internal object ReactNativeBridgeRegistry {
20
+ private val activeTargets: MutableList<ReactNativeBridgeTarget> = mutableListOf()
21
+
22
+ private val clickHandlerTargets: List<ReactNativeBridgeTarget>
23
+ get() = activeTargets.filter { it.isClickHandlerRegistered && it.isAttachedToReact }
24
+ private val attachedTargets: List<ReactNativeBridgeTarget>
25
+ get() = activeTargets.filter { it.isAttachedToReact }
26
+
27
+ private var lastPendingClick: WritableMap? = null
28
+
29
+ // trackFromWebBridge를 호출한 타겟 (인앱 메시지 전달 대상)
30
+ private var webBridgeSourceTarget: ReactNativeBridgeTarget? = null
31
+
32
+ fun register(target: ReactNativeBridgeTarget) {
33
+ log("Register target: $target")
34
+ activeTargets.add(target)
35
+ }
36
+
37
+ fun unregister(target: ReactNativeBridgeTarget) {
38
+ log("Unregister target: $target")
39
+ activeTargets.remove(target)
40
+ if (webBridgeSourceTarget == target) {
41
+ webBridgeSourceTarget = null
42
+ }
43
+ }
44
+
45
+ /**
46
+ * trackFromWebBridge 호출 시 소스 타겟 설정
47
+ */
48
+ fun setWebBridgeSourceTarget(target: ReactNativeBridgeTarget) {
49
+ webBridgeSourceTarget = target
50
+ }
51
+
52
+ /**
53
+ * 소스 타겟 초기화
54
+ */
55
+ fun clearWebBridgeSourceTarget() {
56
+ webBridgeSourceTarget = null
57
+ }
58
+
59
+ /**
60
+ * 네이티브 SDK에서 RN으로 인앱 메시지 전달
61
+ * trackFromWebBridge를 호출한 타겟에만 전달
62
+ */
63
+ fun sendInAppMessage(
64
+ campaign: Map<String, Any?>,
65
+ messageId: String,
66
+ hasCustomClickHandler: Boolean
67
+ ) {
68
+ val target = webBridgeSourceTarget
69
+ webBridgeSourceTarget = null
70
+
71
+ if (target != null && target.isAttachedToReact && target.isReactReady) {
72
+ target.sendInAppMessage(campaign, messageId, hasCustomClickHandler)
73
+ }
74
+ }
75
+
76
+ fun flushIfPending() {
77
+ log("flush called, $lastPendingClick")
78
+ lastPendingClick?.let { clickData ->
79
+ log("Flush pending click on new instance: $clickData")
80
+ deliverClick(clickData)
81
+ }
82
+ }
83
+
84
+ fun deliverClick(clickData: WritableMap) {
85
+ log("Deliver click: $clickData")
86
+ val readyTargets = clickHandlerTargets.filter { it.isReactReady }
87
+ if (readyTargets.isNotEmpty()) {
88
+ log("There are ${readyTargets.size} click handler targets available; delivery click: $clickData")
89
+ readyTargets.forEach { target ->
90
+ log("Deliver click to $target: $clickData")
91
+ target.sendClick(clickData)
92
+ }
93
+ lastPendingClick = null
94
+ } else {
95
+ log("Targets not ready; store click: $clickData")
96
+ lastPendingClick = clickData
97
+ }
98
+ }
99
+
100
+ fun popPendingClick(): WritableMap? {
101
+ val pending = lastPendingClick
102
+ lastPendingClick = null
103
+ return pending
104
+ }
105
+
106
+ private fun log(message: String) {
107
+ // TODO: route to Marketap logger when available in RN layer
108
+ android.util.Log.d("MarketapBridgeRegistry", message)
109
+ }
110
+ }