react-native-marketap-sdk 0.1.0-beta.9 → 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 +284 -77
  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 -165
  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 -164
  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 -41
  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,36 +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
- private var isHostResumed = false
36
38
  private var isInitialized = false
37
39
  private var initializedProjectId: String? = null
40
+ private var clickHandlerRegistered = false
41
+ private var isModuleActive = true
38
42
 
39
43
  init {
40
44
  reactApplicationContext.addLifecycleEventListener(this)
45
+ ReactNativeBridgeRegistry.register(this)
46
+ registerExternalInAppCallback()
41
47
  }
42
48
 
43
49
  override fun getName(): String {
44
50
  return NAME
45
51
  }
46
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
+
47
62
  @ReactMethod
48
- fun initialize(projectId: String, promise: Promise) {
63
+ fun initialize(payload: ReadableMap?, promise: Promise) {
49
64
  try {
65
+ val projectId = payload?.getString("projectId") ?: ""
50
66
  if (!isMainProcess()) {
51
67
  promise.resolve(null)
52
68
  return
53
69
  }
54
-
70
+
55
71
  if (isInitialized) {
56
72
  if (initializedProjectId == projectId) {
57
73
  promise.resolve(null)
@@ -62,7 +78,7 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
62
78
  return
63
79
  }
64
80
  }
65
-
81
+
66
82
  // Initialize MarketapSDK with projectId
67
83
  val application = reactApplicationContext.applicationContext as Application
68
84
  CurrentActivityHolder.applyToApplication(application)
@@ -76,29 +92,48 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
76
92
  }
77
93
 
78
94
  @ReactMethod
79
- 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
+ }
80
102
  val resolvedLevel = MarketapLogLevel.values().firstOrNull { level ->
81
103
  level.value == logLevel
82
104
  } ?: MarketapLogLevel.NONE
83
105
  Marketap.setLogLevel(resolvedLevel)
84
106
  }
85
-
107
+
86
108
  private fun isMainProcess(): Boolean {
87
109
  val am = reactApplicationContext.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
88
110
  return am.runningAppProcesses?.find { it.pid == android.os.Process.myPid() }?.processName == reactApplicationContext.packageName
89
111
  }
90
112
 
91
113
  @ReactMethod
92
- fun signup(
93
- userId: String,
94
- userProperties: ReadableMap?,
95
- eventProperties: ReadableMap?,
96
- persistUser: Boolean?,
97
- promise: Promise
98
- ) {
114
+ fun signup(payload: ReadableMap?, promise: Promise) {
99
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
+ }
100
135
  // Call MarketapSDK signup method
101
- Marketap.signup(userId, userProperties.toNonNullableMap(), eventProperties.toNonNullableMap(), persistUser ?: true)
136
+ Marketap.signup(userId, userProperties.toNonNullableMap(), eventProperties.toNonNullableMap(), persistUser)
102
137
  promise.resolve(null)
103
138
  } catch (e: Exception) {
104
139
  promise.reject("SIGNUP_ERROR", e.message, e)
@@ -106,13 +141,21 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
106
141
  }
107
142
 
108
143
  @ReactMethod
109
- fun login(
110
- userId: String,
111
- userProperties: ReadableMap?,
112
- eventProperties: ReadableMap?,
113
- promise: Promise
114
- ) {
144
+ fun login(payload: ReadableMap?, promise: Promise) {
115
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
+ }
116
159
  // Call MarketapSDK login method
117
160
  Marketap.login(userId, userProperties.toNonNullableMap(), eventProperties.toNonNullableMap())
118
161
  promise.resolve(null)
@@ -122,8 +165,14 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
122
165
  }
123
166
 
124
167
  @ReactMethod
125
- fun logout(eventProperties: ReadableMap?, promise: Promise) {
168
+ fun logout(payload: ReadableMap?, promise: Promise) {
126
169
  try {
170
+ val eventProperties =
171
+ if (payload != null && payload.hasKey("eventProperties") && !payload.isNull("eventProperties")) {
172
+ payload.getMap("eventProperties")
173
+ } else {
174
+ null
175
+ }
127
176
  // Call MarketapSDK logout method
128
177
  Marketap.logout(eventProperties.toNonNullableMap())
129
178
  promise.resolve(null)
@@ -133,8 +182,15 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
133
182
  }
134
183
 
135
184
  @ReactMethod
136
- fun track(eventName: String, eventProperties: ReadableMap?, promise: Promise) {
185
+ fun track(payload: ReadableMap?, promise: Promise) {
137
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
+ }
138
194
  // Call MarketapSDK track method
139
195
  Marketap.track(eventName, eventProperties.toNonNullableMap())
140
196
  promise.resolve(null)
@@ -144,8 +200,20 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
144
200
  }
145
201
 
146
202
  @ReactMethod
147
- fun trackPurchase(revenue: Double, eventProperties: ReadableMap?, promise: Promise) {
203
+ fun trackPurchase(payload: ReadableMap?, promise: Promise) {
148
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
+ }
149
217
  // Call MarketapSDK trackPurchase method
150
218
  Marketap.trackPurchase(revenue, eventProperties.toNonNullableMap())
151
219
  promise.resolve(null)
@@ -155,13 +223,21 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
155
223
  }
156
224
 
157
225
  @ReactMethod
158
- fun trackRevenue(
159
- eventName: String,
160
- revenue: Double,
161
- eventProperties: ReadableMap?,
162
- promise: Promise
163
- ) {
226
+ fun trackRevenue(payload: ReadableMap?, promise: Promise) {
164
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
+ }
165
241
  // Call MarketapSDK trackRevenue method
166
242
  Marketap.trackRevenue(eventName, revenue, eventProperties.toNonNullableMap())
167
243
  promise.resolve(null)
@@ -171,8 +247,14 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
171
247
  }
172
248
 
173
249
  @ReactMethod
174
- fun trackPageView(eventProperties: ReadableMap?, promise: Promise) {
250
+ fun trackPageView(payload: ReadableMap?, promise: Promise) {
175
251
  try {
252
+ val eventProperties =
253
+ if (payload != null && payload.hasKey("eventProperties") && !payload.isNull("eventProperties")) {
254
+ payload.getMap("eventProperties")
255
+ } else {
256
+ null
257
+ }
176
258
  // Call MarketapSDK trackPageView method
177
259
  Marketap.trackPageView(eventProperties.toNonNullableMap())
178
260
  promise.resolve(null)
@@ -182,8 +264,15 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
182
264
  }
183
265
 
184
266
  @ReactMethod
185
- fun identify(userId: String, userProperties: ReadableMap?, promise: Promise) {
267
+ fun identify(payload: ReadableMap?, promise: Promise) {
186
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
+ }
187
276
  // Call MarketapSDK identify method
188
277
  Marketap.identify(userId, userProperties.toNonNullableMap())
189
278
  promise.resolve(null)
@@ -206,14 +295,14 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
206
295
  @ReactMethod
207
296
  fun setClickHandler(promise: Promise) {
208
297
  try {
209
- hasListeners = true
298
+ clickHandlerRegistered = true
210
299
  Marketap.setClickHandler { event ->
211
300
  val clickData = convertClickEventToMap(event)
212
-
213
- deliverOrStoreClick(clickData)
301
+
302
+ ReactNativeBridgeRegistry.deliverClick(clickData)
214
303
  }
215
304
 
216
- flushPendingClick()
305
+ ReactNativeBridgeRegistry.flushIfPending()
217
306
  promise.resolve(null)
218
307
  } catch (e: Exception) {
219
308
  promise.reject("SET_CLICK_HANDLER_ERROR", e.message, e)
@@ -234,35 +323,139 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
234
323
  }
235
324
  }
236
325
 
326
+ // 웹브릿지 인앱 메시지 관련 메서드
237
327
  @ReactMethod
238
- 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) {
239
359
  try {
240
- // 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)
241
372
  promise.resolve(null)
242
373
  } catch (e: Exception) {
243
- promise.reject("PUSH_TOKEN_ERROR", e.message, e)
374
+ promise.reject("SET_USER_PROPERTIES_ERROR", e.message, e)
244
375
  }
245
376
  }
246
-
377
+
247
378
  @ReactMethod
248
- fun getInitialNotification(promise: Promise) {
379
+ fun trackInAppImpression(payload: ReadableMap?, promise: Promise) {
249
380
  try {
250
- val pendingEvent = pendingClickEvent
251
- if (pendingEvent != null) {
252
- pendingClickEvent = null
253
- promise.resolve(pendingEvent)
254
- } 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
+ )
255
389
  promise.resolve(null)
390
+ return
256
391
  }
392
+ MarketapPlugin.trackInAppImpression(campaignId, messageId, layoutSubType)
393
+ promise.resolve(null)
257
394
  } catch (e: Exception) {
258
- 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)
259
452
  }
260
453
  }
261
454
 
262
455
  @ReactMethod
263
456
  fun addListener(eventName: String) {
264
457
  hasListeners = true
265
- flushPendingClick()
458
+ ReactNativeBridgeRegistry.flushIfPending()
266
459
  }
267
460
 
268
461
  @ReactMethod
@@ -271,7 +464,7 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
271
464
  }
272
465
 
273
466
  private fun sendEvent(eventName: String, params: WritableMap?): Boolean {
274
- if (!hasListeners || !isReactReady()) {
467
+ if (!hasListeners || !isReactInstanceReady()) {
275
468
  return false
276
469
  }
277
470
  reactApplicationContext.runOnJSQueueThread {
@@ -282,28 +475,7 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
282
475
  return true
283
476
  }
284
477
 
285
- private fun deliverOrStoreClick(clickData: WritableMap) {
286
- if (hasListeners && isHostResumed && isReactReady()) {
287
- if (sendEvent(CLICK_EVENT, clickData)) {
288
- pendingClickEvent = null
289
- } else {
290
- pendingClickEvent = clickData
291
- }
292
- } else {
293
- pendingClickEvent = clickData
294
- }
295
- }
296
-
297
- private fun flushPendingClick() {
298
- val pendingEvent = pendingClickEvent
299
- if (pendingEvent != null && hasListeners && isHostResumed && isReactReady()) {
300
- if (sendEvent(CLICK_EVENT, pendingEvent)) {
301
- pendingClickEvent = null
302
- }
303
- }
304
- }
305
-
306
- private fun isReactReady(): Boolean {
478
+ private fun isReactInstanceReady(): Boolean {
307
479
  return reactApplicationContext.hasActiveCatalystInstance() &&
308
480
  reactApplicationContext.hasActiveReactInstance()
309
481
  }
@@ -321,17 +493,52 @@ class MarketapSdkModule(reactContext: ReactApplicationContext) :
321
493
  return map
322
494
  }
323
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
+
324
512
  override fun onHostResume() {
325
- isHostResumed = true
326
- flushPendingClick()
513
+ val activity = getCurrentActivity() ?: return
514
+ CurrentActivityHolder.set(activity)
515
+ ReactNativeBridgeRegistry.flushIfPending()
327
516
  }
328
517
 
329
518
  override fun onHostPause() {
330
- isHostResumed = false
331
519
  }
332
520
 
333
521
  override fun onHostDestroy() {
334
- isHostResumed = false
522
+ isModuleActive = false
523
+ ReactNativeBridgeRegistry.unregister(this)
524
+ reactApplicationContext.removeLifecycleEventListener(this)
525
+ }
526
+
527
+ override fun invalidate() {
528
+ isModuleActive = false
529
+ ReactNativeBridgeRegistry.unregister(this)
335
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
+ }
336
543
  }
337
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
+ }