react-native-marketap-sdk 0.1.0-beta.11 → 0.1.0-beta.13

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 (43) 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 +278 -69
  4. package/android/src/main/java/com/marketapsdk/ReactNativeBridgeRegistry.kt +110 -0
  5. package/ios/MarketapSdk.m +30 -23
  6. package/ios/MarketapSdk.swift +163 -22
  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 -166
  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/module/MarketapWebBridge.js +204 -15
  19. package/lib/module/MarketapWebBridge.js.map +1 -1
  20. package/lib/module/core/MarketapCore.js +226 -0
  21. package/lib/module/core/MarketapCore.js.map +1 -0
  22. package/lib/module/index.js +20 -165
  23. package/lib/module/index.js.map +1 -1
  24. package/lib/module/internal/marketapCore.js +3 -0
  25. package/lib/module/internal/marketapCore.js.map +1 -0
  26. package/lib/module/internal/marketapPlugin.js +35 -0
  27. package/lib/module/internal/marketapPlugin.js.map +1 -0
  28. package/lib/module/version.js +1 -1
  29. package/lib/typescript/MarketapWebBridge.d.ts +33 -6
  30. package/lib/typescript/MarketapWebBridge.d.ts.map +1 -1
  31. package/lib/typescript/core/MarketapCore.d.ts +54 -0
  32. package/lib/typescript/core/MarketapCore.d.ts.map +1 -0
  33. package/lib/typescript/index.d.ts +2 -41
  34. package/lib/typescript/index.d.ts.map +1 -1
  35. package/lib/typescript/internal/marketapCore.d.ts +3 -0
  36. package/lib/typescript/internal/marketapCore.d.ts.map +1 -0
  37. package/lib/typescript/internal/marketapPlugin.d.ts +10 -0
  38. package/lib/typescript/internal/marketapPlugin.d.ts.map +1 -0
  39. package/lib/typescript/types.d.ts +1 -2
  40. package/lib/typescript/types.d.ts.map +1 -1
  41. package/lib/typescript/version.d.ts +1 -1
  42. package/package.json +1 -1
  43. 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.2') {
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,30 +24,45 @@ 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
37
42
 
38
43
  init {
39
44
  reactApplicationContext.addLifecycleEventListener(this)
45
+ ReactNativeBridgeRegistry.register(this)
46
+ registerExternalInAppCallback()
40
47
  }
41
48
 
42
49
  override fun getName(): String {
43
50
  return NAME
44
51
  }
45
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
+
46
62
  @ReactMethod
47
- fun initialize(projectId: String, promise: Promise) {
63
+ fun initialize(payload: ReadableMap?, promise: Promise) {
48
64
  try {
65
+ val projectId = payload?.getString("projectId") ?: ""
49
66
  if (!isMainProcess()) {
50
67
  promise.resolve(null)
51
68
  return
@@ -75,7 +92,13 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
75
92
  }
76
93
 
77
94
  @ReactMethod
78
- 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
+ }
79
102
  val resolvedLevel = MarketapLogLevel.values().firstOrNull { level ->
80
103
  level.value == logLevel
81
104
  } ?: MarketapLogLevel.NONE
@@ -88,16 +111,29 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
88
111
  }
89
112
 
90
113
  @ReactMethod
91
- fun signup(
92
- userId: String,
93
- userProperties: ReadableMap?,
94
- eventProperties: ReadableMap?,
95
- persistUser: Boolean?,
96
- promise: Promise
97
- ) {
114
+ fun signup(payload: ReadableMap?, promise: Promise) {
98
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
+ }
99
135
  // Call MarketapSDK signup method
100
- Marketap.signup(userId, userProperties.toNonNullableMap(), eventProperties.toNonNullableMap(), persistUser ?: true)
136
+ Marketap.signup(userId, userProperties.toNonNullableMap(), eventProperties.toNonNullableMap(), persistUser)
101
137
  promise.resolve(null)
102
138
  } catch (e: Exception) {
103
139
  promise.reject("SIGNUP_ERROR", e.message, e)
@@ -105,13 +141,21 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
105
141
  }
106
142
 
107
143
  @ReactMethod
108
- fun login(
109
- userId: String,
110
- userProperties: ReadableMap?,
111
- eventProperties: ReadableMap?,
112
- promise: Promise
113
- ) {
144
+ fun login(payload: ReadableMap?, promise: Promise) {
114
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
+ }
115
159
  // Call MarketapSDK login method
116
160
  Marketap.login(userId, userProperties.toNonNullableMap(), eventProperties.toNonNullableMap())
117
161
  promise.resolve(null)
@@ -121,8 +165,14 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
121
165
  }
122
166
 
123
167
  @ReactMethod
124
- fun logout(eventProperties: ReadableMap?, promise: Promise) {
168
+ fun logout(payload: ReadableMap?, promise: Promise) {
125
169
  try {
170
+ val eventProperties =
171
+ if (payload != null && payload.hasKey("eventProperties") && !payload.isNull("eventProperties")) {
172
+ payload.getMap("eventProperties")
173
+ } else {
174
+ null
175
+ }
126
176
  // Call MarketapSDK logout method
127
177
  Marketap.logout(eventProperties.toNonNullableMap())
128
178
  promise.resolve(null)
@@ -132,8 +182,15 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
132
182
  }
133
183
 
134
184
  @ReactMethod
135
- fun track(eventName: String, eventProperties: ReadableMap?, promise: Promise) {
185
+ fun track(payload: ReadableMap?, promise: Promise) {
136
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
+ }
137
194
  // Call MarketapSDK track method
138
195
  Marketap.track(eventName, eventProperties.toNonNullableMap())
139
196
  promise.resolve(null)
@@ -143,8 +200,20 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
143
200
  }
144
201
 
145
202
  @ReactMethod
146
- fun trackPurchase(revenue: Double, eventProperties: ReadableMap?, promise: Promise) {
203
+ fun trackPurchase(payload: ReadableMap?, promise: Promise) {
147
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
+ }
148
217
  // Call MarketapSDK trackPurchase method
149
218
  Marketap.trackPurchase(revenue, eventProperties.toNonNullableMap())
150
219
  promise.resolve(null)
@@ -154,13 +223,21 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
154
223
  }
155
224
 
156
225
  @ReactMethod
157
- fun trackRevenue(
158
- eventName: String,
159
- revenue: Double,
160
- eventProperties: ReadableMap?,
161
- promise: Promise
162
- ) {
226
+ fun trackRevenue(payload: ReadableMap?, promise: Promise) {
163
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
+ }
164
241
  // Call MarketapSDK trackRevenue method
165
242
  Marketap.trackRevenue(eventName, revenue, eventProperties.toNonNullableMap())
166
243
  promise.resolve(null)
@@ -170,8 +247,14 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
170
247
  }
171
248
 
172
249
  @ReactMethod
173
- fun trackPageView(eventProperties: ReadableMap?, promise: Promise) {
250
+ fun trackPageView(payload: ReadableMap?, promise: Promise) {
174
251
  try {
252
+ val eventProperties =
253
+ if (payload != null && payload.hasKey("eventProperties") && !payload.isNull("eventProperties")) {
254
+ payload.getMap("eventProperties")
255
+ } else {
256
+ null
257
+ }
175
258
  // Call MarketapSDK trackPageView method
176
259
  Marketap.trackPageView(eventProperties.toNonNullableMap())
177
260
  promise.resolve(null)
@@ -181,8 +264,15 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
181
264
  }
182
265
 
183
266
  @ReactMethod
184
- fun identify(userId: String, userProperties: ReadableMap?, promise: Promise) {
267
+ fun identify(payload: ReadableMap?, promise: Promise) {
185
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
+ }
186
276
  // Call MarketapSDK identify method
187
277
  Marketap.identify(userId, userProperties.toNonNullableMap())
188
278
  promise.resolve(null)
@@ -205,14 +295,14 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
205
295
  @ReactMethod
206
296
  fun setClickHandler(promise: Promise) {
207
297
  try {
208
- hasListeners = true
298
+ clickHandlerRegistered = true
209
299
  Marketap.setClickHandler { event ->
210
300
  val clickData = convertClickEventToMap(event)
211
301
 
212
- deliverOrStoreClick(clickData)
302
+ ReactNativeBridgeRegistry.deliverClick(clickData)
213
303
  }
214
304
 
215
- flushPendingClick()
305
+ ReactNativeBridgeRegistry.flushIfPending()
216
306
  promise.resolve(null)
217
307
  } catch (e: Exception) {
218
308
  promise.reject("SET_CLICK_HANDLER_ERROR", e.message, e)
@@ -233,35 +323,139 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
233
323
  }
234
324
  }
235
325
 
326
+ // 웹브릿지 인앱 메시지 관련 메서드
236
327
  @ReactMethod
237
- fun setPushToken(token: String, promise: Promise) {
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) {
238
359
  try {
239
- // Android: setPushToken not available - MarketapFirebaseMessagingService handles tokens automatically
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)
240
372
  promise.resolve(null)
241
373
  } catch (e: Exception) {
242
- promise.reject("PUSH_TOKEN_ERROR", e.message, e)
374
+ promise.reject("SET_USER_PROPERTIES_ERROR", e.message, e)
243
375
  }
244
376
  }
245
377
 
246
378
  @ReactMethod
247
- fun getInitialNotification(promise: Promise) {
379
+ fun trackInAppImpression(payload: ReadableMap?, promise: Promise) {
248
380
  try {
249
- val pendingEvent = pendingClickEvent
250
- if (pendingEvent != null) {
251
- pendingClickEvent = null
252
- promise.resolve(pendingEvent)
253
- } else {
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
+ )
254
389
  promise.resolve(null)
390
+ return
255
391
  }
392
+ MarketapPlugin.trackInAppImpression(campaignId, messageId, layoutSubType)
393
+ promise.resolve(null)
256
394
  } catch (e: Exception) {
257
- promise.reject("GET_INITIAL_NOTIFICATION_ERROR", e.message, e)
395
+ promise.reject("TRACK_INAPP_IMPRESSION_ERROR", e.message, e)
396
+ }
397
+ }
398
+
399
+ @ReactMethod
400
+ fun trackInAppClick(payload: ReadableMap?, promise: Promise) {
401
+ try {
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
+ )
412
+ promise.resolve(null)
413
+ return
414
+ }
415
+ MarketapPlugin.trackInAppClick(campaignId, messageId, locationId, url, layoutSubType)
416
+ promise.resolve(null)
417
+ } catch (e: Exception) {
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)
258
452
  }
259
453
  }
260
454
 
261
455
  @ReactMethod
262
456
  fun addListener(eventName: String) {
263
457
  hasListeners = true
264
- flushPendingClick()
458
+ ReactNativeBridgeRegistry.flushIfPending()
265
459
  }
266
460
 
267
461
  @ReactMethod
@@ -270,7 +464,7 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
270
464
  }
271
465
 
272
466
  private fun sendEvent(eventName: String, params: WritableMap?): Boolean {
273
- if (!hasListeners || !isReactReady()) {
467
+ if (!hasListeners || !isReactInstanceReady()) {
274
468
  return false
275
469
  }
276
470
  reactApplicationContext.runOnJSQueueThread {
@@ -281,28 +475,7 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
281
475
  return true
282
476
  }
283
477
 
284
- private fun deliverOrStoreClick(clickData: WritableMap) {
285
- if (hasListeners && isReactReady()) {
286
- if (sendEvent(CLICK_EVENT, clickData)) {
287
- pendingClickEvent = null
288
- } else {
289
- pendingClickEvent = clickData
290
- }
291
- } else {
292
- pendingClickEvent = clickData
293
- }
294
- }
295
-
296
- private fun flushPendingClick() {
297
- val pendingEvent = pendingClickEvent
298
- if (pendingEvent != null && hasListeners && isReactReady()) {
299
- if (sendEvent(CLICK_EVENT, pendingEvent)) {
300
- pendingClickEvent = null
301
- }
302
- }
303
- }
304
-
305
- private fun isReactReady(): Boolean {
478
+ private fun isReactInstanceReady(): Boolean {
306
479
  return reactApplicationContext.hasActiveCatalystInstance() &&
307
480
  reactApplicationContext.hasActiveReactInstance()
308
481
  }
@@ -320,16 +493,52 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
320
493
  return map
321
494
  }
322
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
+
323
512
  override fun onHostResume() {
324
513
  val activity = getCurrentActivity() ?: return
325
514
  CurrentActivityHolder.set(activity)
326
- flushPendingClick()
515
+ ReactNativeBridgeRegistry.flushIfPending()
327
516
  }
328
517
 
329
518
  override fun onHostPause() {
330
519
  }
331
520
 
332
521
  override fun onHostDestroy() {
522
+ isModuleActive = false
523
+ ReactNativeBridgeRegistry.unregister(this)
524
+ reactApplicationContext.removeLifecycleEventListener(this)
525
+ }
526
+
527
+ override fun invalidate() {
528
+ isModuleActive = false
529
+ ReactNativeBridgeRegistry.unregister(this)
333
530
  reactApplicationContext.removeLifecycleEventListener(this)
531
+ super.invalidate()
334
532
  }
335
- }
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
+ }
543
+ }
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
+ }