@adstage/react-native-sdk 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.
- package/LICENSE +21 -0
- package/README.md +327 -0
- package/adstage-react-native.podspec +24 -0
- package/android/build.gradle +66 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/java/io/nbase/adstage/reactnative/AdStageModule.kt +701 -0
- package/android/src/main/java/io/nbase/adstage/reactnative/AdStagePackage.kt +24 -0
- package/ios/AdStageModule.m +70 -0
- package/ios/AdStageModule.swift +457 -0
- package/lib/commonjs/AdStage.js +213 -0
- package/lib/commonjs/AdStage.js.map +1 -0
- package/lib/commonjs/deeplink/AdStageDeepLink.js +235 -0
- package/lib/commonjs/deeplink/AdStageDeepLink.js.map +1 -0
- package/lib/commonjs/event/AdStageEvent.js +689 -0
- package/lib/commonjs/event/AdStageEvent.js.map +1 -0
- package/lib/commonjs/index.js +34 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/promotion/AdStagePromotion.js +158 -0
- package/lib/commonjs/promotion/AdStagePromotion.js.map +1 -0
- package/lib/commonjs/types.js +2 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/module/AdStage.js +206 -0
- package/lib/module/AdStage.js.map +1 -0
- package/lib/module/deeplink/AdStageDeepLink.js +228 -0
- package/lib/module/deeplink/AdStageDeepLink.js.map +1 -0
- package/lib/module/event/AdStageEvent.js +682 -0
- package/lib/module/event/AdStageEvent.js.map +1 -0
- package/lib/module/index.js +15 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/promotion/AdStagePromotion.js +151 -0
- package/lib/module/promotion/AdStagePromotion.js.map +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/src/AdStage.d.ts +124 -0
- package/lib/typescript/src/AdStage.d.ts.map +1 -0
- package/lib/typescript/src/deeplink/AdStageDeepLink.d.ts +154 -0
- package/lib/typescript/src/deeplink/AdStageDeepLink.d.ts.map +1 -0
- package/lib/typescript/src/event/AdStageEvent.d.ts +426 -0
- package/lib/typescript/src/event/AdStageEvent.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +13 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/promotion/AdStagePromotion.d.ts +98 -0
- package/lib/typescript/src/promotion/AdStagePromotion.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +305 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/package.json +105 -0
- package/src/AdStage.ts +212 -0
- package/src/deeplink/AdStageDeepLink.ts +246 -0
- package/src/event/AdStageEvent.ts +844 -0
- package/src/index.ts +48 -0
- package/src/promotion/AdStagePromotion.ts +162 -0
- package/src/types.ts +392 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
package io.nbase.adstage.reactnative
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.uimanager.ViewManager
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* AdStage React Native 패키지
|
|
10
|
+
*
|
|
11
|
+
* MainApplication.kt의 getPackages()에서 등록합니다.
|
|
12
|
+
*
|
|
13
|
+
* @since 3.0.0
|
|
14
|
+
*/
|
|
15
|
+
class AdStagePackage : ReactPackage {
|
|
16
|
+
|
|
17
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
18
|
+
return listOf(AdStageModule(reactContext))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
22
|
+
return emptyList()
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
//
|
|
2
|
+
// AdStageModule.m
|
|
3
|
+
// adstage-react-native
|
|
4
|
+
//
|
|
5
|
+
// Objective-C 브릿지 헤더
|
|
6
|
+
//
|
|
7
|
+
// Created by NBase on 2025.
|
|
8
|
+
//
|
|
9
|
+
|
|
10
|
+
#import <React/RCTBridgeModule.h>
|
|
11
|
+
#import <React/RCTEventEmitter.h>
|
|
12
|
+
|
|
13
|
+
@interface RCT_EXTERN_MODULE(AdStageModule, RCTEventEmitter)
|
|
14
|
+
|
|
15
|
+
// Initialization
|
|
16
|
+
RCT_EXTERN_METHOD(initialize:(NSString *)apiKey
|
|
17
|
+
serverUrl:(NSString *)serverUrl
|
|
18
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
19
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
20
|
+
|
|
21
|
+
RCT_EXTERN_METHOD(getVersion:(RCTPromiseResolveBlock)resolve
|
|
22
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
23
|
+
|
|
24
|
+
// User Attributes
|
|
25
|
+
RCT_EXTERN_METHOD(setUserAttributes:(NSDictionary *)attributes
|
|
26
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
27
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
28
|
+
|
|
29
|
+
RCT_EXTERN_METHOD(getUserAttributes:(RCTPromiseResolveBlock)resolve
|
|
30
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
31
|
+
|
|
32
|
+
RCT_EXTERN_METHOD(clearUserAttributes:(RCTPromiseResolveBlock)resolve
|
|
33
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
34
|
+
|
|
35
|
+
// DeepLink
|
|
36
|
+
RCT_EXTERN_METHOD(setDeepLinkListener)
|
|
37
|
+
|
|
38
|
+
RCT_EXTERN_METHOD(removeDeepLinkListener)
|
|
39
|
+
|
|
40
|
+
RCT_EXTERN_METHOD(createDeepLink:(NSDictionary *)request
|
|
41
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
42
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
43
|
+
|
|
44
|
+
RCT_EXTERN_METHOD(checkPendingDeepLink)
|
|
45
|
+
|
|
46
|
+
// Promotion
|
|
47
|
+
RCT_EXTERN_METHOD(getPromotionList:(NSDictionary *)params
|
|
48
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
49
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
50
|
+
|
|
51
|
+
RCT_EXTERN_METHOD(handlePromotionClick:(NSDictionary *)promotionMap
|
|
52
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
53
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
54
|
+
|
|
55
|
+
// Event Tracking
|
|
56
|
+
RCT_EXTERN_METHOD(trackEvent:(NSString *)eventName
|
|
57
|
+
params:(NSDictionary *)params
|
|
58
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
59
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
60
|
+
|
|
61
|
+
// Required for RN Event Emitter
|
|
62
|
+
RCT_EXTERN_METHOD(addListener:(NSString *)eventName)
|
|
63
|
+
RCT_EXTERN_METHOD(removeListeners:(double)count)
|
|
64
|
+
|
|
65
|
+
+ (BOOL)requiresMainQueueSetup
|
|
66
|
+
{
|
|
67
|
+
return YES;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@end
|
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
//
|
|
2
|
+
// AdStageModule.swift
|
|
3
|
+
// adstage-react-native
|
|
4
|
+
//
|
|
5
|
+
// AdStage React Native 네이티브 모듈 (iOS)
|
|
6
|
+
//
|
|
7
|
+
// Created by NBase on 2025.
|
|
8
|
+
// Copyright © 2025 NBase. All rights reserved.
|
|
9
|
+
//
|
|
10
|
+
|
|
11
|
+
import Foundation
|
|
12
|
+
import React
|
|
13
|
+
import AdapterAdStage
|
|
14
|
+
|
|
15
|
+
@objc(AdStageModule)
|
|
16
|
+
class AdStageModule: RCTEventEmitter {
|
|
17
|
+
|
|
18
|
+
// MARK: - Constants
|
|
19
|
+
|
|
20
|
+
private static let TAG = "AdStageModule"
|
|
21
|
+
|
|
22
|
+
// MARK: - Properties
|
|
23
|
+
|
|
24
|
+
private var hasListeners = false
|
|
25
|
+
|
|
26
|
+
// MARK: - RCTEventEmitter
|
|
27
|
+
|
|
28
|
+
override static func moduleName() -> String! {
|
|
29
|
+
return "AdStageModule"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
override static func requiresMainQueueSetup() -> Bool {
|
|
33
|
+
return true
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
override func supportedEvents() -> [String]! {
|
|
37
|
+
return ["onDeepLinkReceived"]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
override func startObserving() {
|
|
41
|
+
hasListeners = true
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
override func stopObserving() {
|
|
45
|
+
hasListeners = false
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// MARK: - Initialization
|
|
49
|
+
|
|
50
|
+
@objc
|
|
51
|
+
func initialize(_ apiKey: String,
|
|
52
|
+
serverUrl: String,
|
|
53
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
54
|
+
rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
55
|
+
|
|
56
|
+
NSLog("[\(Self.TAG)] Initializing AdStage SDK...")
|
|
57
|
+
|
|
58
|
+
DispatchQueue.main.async {
|
|
59
|
+
// serverUrl이 빈 문자열이면 apiKey만으로 초기화 (네이티브 SDK 기본값 사용)
|
|
60
|
+
if serverUrl.isEmpty {
|
|
61
|
+
AdStageManager.shared.initialize(apiKey: apiKey)
|
|
62
|
+
} else {
|
|
63
|
+
AdStageManager.shared.initialize(apiKey: apiKey, serverUrl: serverUrl)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
NSLog("[\(Self.TAG)] AdStage SDK initialized successfully")
|
|
67
|
+
resolve(true)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@objc
|
|
72
|
+
func getVersion(_ resolve: @escaping RCTPromiseResolveBlock,
|
|
73
|
+
rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
74
|
+
resolve("3.0.0")
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// MARK: - User Attributes
|
|
78
|
+
|
|
79
|
+
@objc
|
|
80
|
+
func setUserAttributes(_ attributes: NSDictionary,
|
|
81
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
82
|
+
rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
83
|
+
|
|
84
|
+
let userAttrs = UserAttributes(
|
|
85
|
+
gender: attributes["gender"] as? String,
|
|
86
|
+
country: attributes["country"] as? String,
|
|
87
|
+
city: attributes["city"] as? String,
|
|
88
|
+
age: attributes["age"] as? String,
|
|
89
|
+
language: attributes["language"] as? String
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
AdStageManager.shared.setUserAttributes(userAttrs)
|
|
93
|
+
resolve(true)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@objc
|
|
97
|
+
func getUserAttributes(_ resolve: @escaping RCTPromiseResolveBlock,
|
|
98
|
+
rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
99
|
+
if let attrs = AdStageManager.shared.getUserAttributes() {
|
|
100
|
+
let dict: [String: Any?] = [
|
|
101
|
+
"gender": attrs.gender,
|
|
102
|
+
"country": attrs.country,
|
|
103
|
+
"city": attrs.city,
|
|
104
|
+
"age": attrs.age,
|
|
105
|
+
"language": attrs.language
|
|
106
|
+
]
|
|
107
|
+
resolve(dict.compactMapValues { $0 })
|
|
108
|
+
} else {
|
|
109
|
+
resolve(nil)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@objc
|
|
114
|
+
func clearUserAttributes(_ resolve: @escaping RCTPromiseResolveBlock,
|
|
115
|
+
rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
116
|
+
// SDK에서는 nil로 설정하여 초기화
|
|
117
|
+
AdStageManager.shared.setUserAttributes(UserAttributes())
|
|
118
|
+
resolve(true)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// MARK: - DeepLink
|
|
122
|
+
|
|
123
|
+
@objc
|
|
124
|
+
func setDeepLinkListener() {
|
|
125
|
+
NSLog("[\(Self.TAG)] Setting deep link listener")
|
|
126
|
+
|
|
127
|
+
AdStageManager.shared.setDeepLinkListener { [weak self] data in
|
|
128
|
+
guard let self = self, self.hasListeners else { return }
|
|
129
|
+
|
|
130
|
+
NSLog("[\(Self.TAG)] Deep link received: \(data.shortPath)")
|
|
131
|
+
|
|
132
|
+
let params: [String: Any] = [
|
|
133
|
+
"linkId": data.linkId,
|
|
134
|
+
"shortPath": data.shortPath,
|
|
135
|
+
"source": data.source.description,
|
|
136
|
+
"eventType": data.eventType,
|
|
137
|
+
"parameters": data.parameters
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
self.sendEvent(withName: "onDeepLinkReceived", body: params)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
@objc
|
|
145
|
+
func removeDeepLinkListener() {
|
|
146
|
+
// AdStageManager에서 리스너 제거 기능 호출
|
|
147
|
+
NSLog("[\(Self.TAG)] Removing deep link listener")
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
@objc
|
|
151
|
+
func createDeepLink(_ request: NSDictionary,
|
|
152
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
153
|
+
rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
154
|
+
|
|
155
|
+
guard let name = request["name"] as? String else {
|
|
156
|
+
reject("INVALID_PARAMS", "name is required", nil)
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
let builder = AdStageManager.shared.createDeepLinkBuilder()
|
|
161
|
+
builder.setName(name)
|
|
162
|
+
|
|
163
|
+
if let description = request["description"] as? String {
|
|
164
|
+
builder.setDescription(description)
|
|
165
|
+
}
|
|
166
|
+
if let channel = request["channel"] as? String {
|
|
167
|
+
builder.setChannel(channel)
|
|
168
|
+
}
|
|
169
|
+
if let subChannel = request["subChannel"] as? String {
|
|
170
|
+
builder.setSubChannel(subChannel)
|
|
171
|
+
}
|
|
172
|
+
if let campaign = request["campaign"] as? String {
|
|
173
|
+
builder.setCampaign(campaign)
|
|
174
|
+
}
|
|
175
|
+
if let adGroup = request["adGroup"] as? String {
|
|
176
|
+
builder.setAdGroup(adGroup)
|
|
177
|
+
}
|
|
178
|
+
if let creative = request["creative"] as? String {
|
|
179
|
+
builder.setCreative(creative)
|
|
180
|
+
}
|
|
181
|
+
if let keyword = request["keyword"] as? String {
|
|
182
|
+
builder.setKeyword(keyword)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Redirect Config
|
|
186
|
+
if let redirectConfig = request["redirectConfig"] as? [String: Any] {
|
|
187
|
+
if let android = redirectConfig["android"] as? [String: String] {
|
|
188
|
+
builder.setAndroidConfig(
|
|
189
|
+
packageName: android["packageName"] ?? "",
|
|
190
|
+
appScheme: android["appScheme"] ?? "",
|
|
191
|
+
webUrl: android["webUrl"] ?? ""
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
if let ios = redirectConfig["ios"] as? [String: String] {
|
|
195
|
+
builder.setIosConfig(
|
|
196
|
+
appStoreId: ios["appStoreId"] ?? "",
|
|
197
|
+
appScheme: ios["appScheme"] ?? "",
|
|
198
|
+
webUrl: ios["webUrl"] ?? ""
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Parameters
|
|
204
|
+
if let parameters = request["parameters"] as? [String: String] {
|
|
205
|
+
parameters.forEach { key, value in
|
|
206
|
+
builder.addParameter(key: key, value: value)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
builder.create { info, error in
|
|
211
|
+
if let error = error {
|
|
212
|
+
reject("CREATE_DEEPLINK_ERROR", error.localizedDescription, error)
|
|
213
|
+
return
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
guard let info = info else {
|
|
217
|
+
reject("CREATE_DEEPLINK_ERROR", "Failed to create deep link", nil)
|
|
218
|
+
return
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
let dict: [String: Any] = [
|
|
222
|
+
"shortUrl": info.shortUrl ?? "",
|
|
223
|
+
"shortPath": info.shortPath ?? "",
|
|
224
|
+
"linkId": info.linkId ?? ""
|
|
225
|
+
]
|
|
226
|
+
resolve(dict)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
@objc
|
|
231
|
+
func checkPendingDeepLink() {
|
|
232
|
+
// iOS에서는 리스너 설정 시 자동으로 pending 딥링크 전달됨
|
|
233
|
+
NSLog("[\(Self.TAG)] checkPendingDeepLink called")
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// MARK: - Promotion
|
|
237
|
+
|
|
238
|
+
@objc
|
|
239
|
+
func getPromotionList(_ params: NSDictionary,
|
|
240
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
241
|
+
rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
242
|
+
|
|
243
|
+
let listParams = PromotionListParams()
|
|
244
|
+
listParams.bannerType = params["bannerType"] as? String
|
|
245
|
+
listParams.targetAudience = params["targetAudience"] as? String
|
|
246
|
+
listParams.deviceType = params["deviceType"] as? String
|
|
247
|
+
listParams.region = params["region"] as? String
|
|
248
|
+
if let limit = params["limit"] as? Int {
|
|
249
|
+
listParams.limit = limit
|
|
250
|
+
}
|
|
251
|
+
listParams.status = params["status"] as? String
|
|
252
|
+
listParams.partner = params["partner"] as? String
|
|
253
|
+
listParams.primaryInterest = params["primaryInterest"] as? String
|
|
254
|
+
listParams.primaryAgeGroup = params["primaryAgeGroup"] as? String
|
|
255
|
+
listParams.gameGenrePreference = params["gameGenrePreference"] as? String
|
|
256
|
+
listParams.playerSpendingTier = params["playerSpendingTier"] as? String
|
|
257
|
+
listParams.playTimePattern = params["playTimePattern"] as? String
|
|
258
|
+
|
|
259
|
+
AdStageManager.shared.getPromotionList(params: listParams) { promotions, error in
|
|
260
|
+
if let error = error {
|
|
261
|
+
reject("GET_PROMOTION_ERROR", error.localizedDescription, error)
|
|
262
|
+
return
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
guard let promotions = promotions else {
|
|
266
|
+
let dict: [String: Any] = [
|
|
267
|
+
"totalItems": 0,
|
|
268
|
+
"promotions": []
|
|
269
|
+
]
|
|
270
|
+
resolve(dict)
|
|
271
|
+
return
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
let promotionDicts = promotions.map { promo -> [String: Any] in
|
|
275
|
+
return self.promotionToDict(promo)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
let dict: [String: Any] = [
|
|
279
|
+
"totalItems": promotions.count,
|
|
280
|
+
"promotions": promotionDicts
|
|
281
|
+
]
|
|
282
|
+
resolve(dict)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
@objc
|
|
287
|
+
func handlePromotionClick(_ promotionMap: NSDictionary,
|
|
288
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
289
|
+
rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
290
|
+
|
|
291
|
+
guard let promotion = dictToPromotion(promotionMap) else {
|
|
292
|
+
reject("INVALID_PARAMS", "Invalid promotion data", nil)
|
|
293
|
+
return
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// openPromotionStore 사용 - 동기적으로 스토어 열기
|
|
297
|
+
AdStageManager.shared.openPromotionStore(promotion)
|
|
298
|
+
|
|
299
|
+
let dict: [String: Any] = [
|
|
300
|
+
"success": true,
|
|
301
|
+
"storeUrl": promotion.storeUrls?.ios ?? ""
|
|
302
|
+
]
|
|
303
|
+
resolve(dict)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// MARK: - Event Tracking
|
|
307
|
+
|
|
308
|
+
@objc
|
|
309
|
+
func trackEvent(_ eventName: String,
|
|
310
|
+
params: NSDictionary?,
|
|
311
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
312
|
+
rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
313
|
+
|
|
314
|
+
let eventParams = params as? [String: Any] ?? [:]
|
|
315
|
+
|
|
316
|
+
// 이벤트 파라미터 빌더 사용
|
|
317
|
+
var paramsBuilder = TrackEventParams.builder(eventName)
|
|
318
|
+
|
|
319
|
+
// Unity와 동일하게 전체 params Dictionary를 한번에 전달
|
|
320
|
+
// 이렇게 하면 Array, Number, Boolean 등 모든 타입이 보존됨
|
|
321
|
+
let convertedParams = convertReactNativeParams(eventParams)
|
|
322
|
+
paramsBuilder = paramsBuilder.params(convertedParams)
|
|
323
|
+
|
|
324
|
+
let trackParams = paramsBuilder.build()
|
|
325
|
+
|
|
326
|
+
AdStageManager.shared.trackEvent(trackParams) { success, error in
|
|
327
|
+
if success {
|
|
328
|
+
let dict: [String: Any] = [
|
|
329
|
+
"success": true
|
|
330
|
+
]
|
|
331
|
+
resolve(dict)
|
|
332
|
+
} else if let error = error {
|
|
333
|
+
reject("TRACK_EVENT_ERROR", error.localizedDescription, error)
|
|
334
|
+
} else {
|
|
335
|
+
reject("TRACK_EVENT_ERROR", "Unknown error", nil)
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/// React Native에서 전달된 파라미터를 네이티브 타입으로 변환
|
|
341
|
+
/// - 배열 (items) 처리
|
|
342
|
+
/// - 중첩 Dictionary 처리
|
|
343
|
+
/// - Number/Boolean 타입 보존
|
|
344
|
+
private func convertReactNativeParams(_ params: [String: Any]) -> [String: Any] {
|
|
345
|
+
var result: [String: Any] = [:]
|
|
346
|
+
|
|
347
|
+
for (key, value) in params {
|
|
348
|
+
if let arrayValue = value as? [Any] {
|
|
349
|
+
// 배열 (items 등) 처리 - 각 요소도 재귀적으로 변환
|
|
350
|
+
result[key] = arrayValue.map { element -> Any in
|
|
351
|
+
if let dictElement = element as? [String: Any] {
|
|
352
|
+
return convertReactNativeParams(dictElement)
|
|
353
|
+
}
|
|
354
|
+
return element
|
|
355
|
+
}
|
|
356
|
+
} else if let dictValue = value as? [String: Any] {
|
|
357
|
+
// 중첩 Dictionary 처리
|
|
358
|
+
result[key] = convertReactNativeParams(dictValue)
|
|
359
|
+
} else {
|
|
360
|
+
// 기본 타입 (String, Number, Boolean) - 그대로 전달
|
|
361
|
+
result[key] = value
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return result
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// MARK: - Required for RN Event Emitter
|
|
369
|
+
|
|
370
|
+
@objc
|
|
371
|
+
override func addListener(_ eventName: String) {
|
|
372
|
+
super.addListener(eventName)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
@objc
|
|
376
|
+
override func removeListeners(_ count: Double) {
|
|
377
|
+
super.removeListeners(count)
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// MARK: - Helpers
|
|
381
|
+
|
|
382
|
+
private func promotionToDict(_ promo: Promotion) -> [String: Any] {
|
|
383
|
+
var dict: [String: Any] = [
|
|
384
|
+
"id": promo.id,
|
|
385
|
+
"partner": promo.partner,
|
|
386
|
+
"appName": promo.appName,
|
|
387
|
+
"bannerUrl": promo.bannerUrl,
|
|
388
|
+
"bannerType": promo.bannerType,
|
|
389
|
+
"appDescription": promo.appDescription
|
|
390
|
+
]
|
|
391
|
+
|
|
392
|
+
// DeepLink
|
|
393
|
+
dict["deeplink"] = [
|
|
394
|
+
"id": promo.deeplink.id,
|
|
395
|
+
"name": promo.deeplink.name,
|
|
396
|
+
"description": promo.deeplink.linkDescription ?? "",
|
|
397
|
+
"shortPath": promo.deeplink.shortPath,
|
|
398
|
+
"linkType": promo.deeplink.linkType ?? "",
|
|
399
|
+
"channel": promo.deeplink.channel ?? "",
|
|
400
|
+
"subChannel": promo.deeplink.subChannel ?? "",
|
|
401
|
+
"campaign": promo.deeplink.campaign ?? "",
|
|
402
|
+
"status": promo.deeplink.status ?? ""
|
|
403
|
+
]
|
|
404
|
+
|
|
405
|
+
// Store URLs
|
|
406
|
+
if let storeUrls = promo.storeUrls {
|
|
407
|
+
dict["storeUrls"] = [
|
|
408
|
+
"android": storeUrls.android ?? "",
|
|
409
|
+
"ios": storeUrls.ios ?? ""
|
|
410
|
+
]
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return dict
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
private func dictToPromotion(_ dict: NSDictionary) -> Promotion? {
|
|
417
|
+
guard let id = dict["id"] as? String,
|
|
418
|
+
let deeplinkDict = dict["deeplink"] as? [String: Any] else {
|
|
419
|
+
return nil
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
let deeplink = PromotionDeepLink(
|
|
423
|
+
id: deeplinkDict["id"] as? String ?? "",
|
|
424
|
+
name: deeplinkDict["name"] as? String ?? "",
|
|
425
|
+
linkDescription: deeplinkDict["description"] as? String ?? "",
|
|
426
|
+
shortPath: deeplinkDict["shortPath"] as? String ?? "",
|
|
427
|
+
linkType: deeplinkDict["linkType"] as? String ?? "",
|
|
428
|
+
channel: deeplinkDict["channel"] as? String ?? "",
|
|
429
|
+
subChannel: deeplinkDict["subChannel"] as? String ?? "",
|
|
430
|
+
campaign: deeplinkDict["campaign"] as? String ?? "",
|
|
431
|
+
content: deeplinkDict["content"] as? String ?? "",
|
|
432
|
+
keyword: deeplinkDict["keyword"] as? String ?? "",
|
|
433
|
+
redirectConfig: nil,
|
|
434
|
+
status: deeplinkDict["status"] as? String ?? "",
|
|
435
|
+
parameters: nil
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
var storeUrls: StoreUrls? = nil
|
|
439
|
+
if let storeUrlsDict = dict["storeUrls"] as? [String: String] {
|
|
440
|
+
storeUrls = StoreUrls(
|
|
441
|
+
android: storeUrlsDict["android"] ?? "",
|
|
442
|
+
ios: storeUrlsDict["ios"] ?? ""
|
|
443
|
+
)
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return Promotion(
|
|
447
|
+
id: id,
|
|
448
|
+
partner: dict["partner"] as? String ?? "",
|
|
449
|
+
appName: dict["appName"] as? String ?? "",
|
|
450
|
+
bannerUrl: dict["bannerUrl"] as? String ?? "",
|
|
451
|
+
bannerType: dict["bannerType"] as? String ?? "",
|
|
452
|
+
appDescription: dict["appDescription"] as? String ?? "",
|
|
453
|
+
deeplink: deeplink,
|
|
454
|
+
storeUrls: storeUrls
|
|
455
|
+
)
|
|
456
|
+
}
|
|
457
|
+
}
|