@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.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +327 -0
  3. package/adstage-react-native.podspec +24 -0
  4. package/android/build.gradle +66 -0
  5. package/android/src/main/AndroidManifest.xml +4 -0
  6. package/android/src/main/java/io/nbase/adstage/reactnative/AdStageModule.kt +701 -0
  7. package/android/src/main/java/io/nbase/adstage/reactnative/AdStagePackage.kt +24 -0
  8. package/ios/AdStageModule.m +70 -0
  9. package/ios/AdStageModule.swift +457 -0
  10. package/lib/commonjs/AdStage.js +213 -0
  11. package/lib/commonjs/AdStage.js.map +1 -0
  12. package/lib/commonjs/deeplink/AdStageDeepLink.js +235 -0
  13. package/lib/commonjs/deeplink/AdStageDeepLink.js.map +1 -0
  14. package/lib/commonjs/event/AdStageEvent.js +689 -0
  15. package/lib/commonjs/event/AdStageEvent.js.map +1 -0
  16. package/lib/commonjs/index.js +34 -0
  17. package/lib/commonjs/index.js.map +1 -0
  18. package/lib/commonjs/promotion/AdStagePromotion.js +158 -0
  19. package/lib/commonjs/promotion/AdStagePromotion.js.map +1 -0
  20. package/lib/commonjs/types.js +2 -0
  21. package/lib/commonjs/types.js.map +1 -0
  22. package/lib/module/AdStage.js +206 -0
  23. package/lib/module/AdStage.js.map +1 -0
  24. package/lib/module/deeplink/AdStageDeepLink.js +228 -0
  25. package/lib/module/deeplink/AdStageDeepLink.js.map +1 -0
  26. package/lib/module/event/AdStageEvent.js +682 -0
  27. package/lib/module/event/AdStageEvent.js.map +1 -0
  28. package/lib/module/index.js +15 -0
  29. package/lib/module/index.js.map +1 -0
  30. package/lib/module/promotion/AdStagePromotion.js +151 -0
  31. package/lib/module/promotion/AdStagePromotion.js.map +1 -0
  32. package/lib/module/types.js +2 -0
  33. package/lib/module/types.js.map +1 -0
  34. package/lib/typescript/src/AdStage.d.ts +124 -0
  35. package/lib/typescript/src/AdStage.d.ts.map +1 -0
  36. package/lib/typescript/src/deeplink/AdStageDeepLink.d.ts +154 -0
  37. package/lib/typescript/src/deeplink/AdStageDeepLink.d.ts.map +1 -0
  38. package/lib/typescript/src/event/AdStageEvent.d.ts +426 -0
  39. package/lib/typescript/src/event/AdStageEvent.d.ts.map +1 -0
  40. package/lib/typescript/src/index.d.ts +13 -0
  41. package/lib/typescript/src/index.d.ts.map +1 -0
  42. package/lib/typescript/src/promotion/AdStagePromotion.d.ts +98 -0
  43. package/lib/typescript/src/promotion/AdStagePromotion.d.ts.map +1 -0
  44. package/lib/typescript/src/types.d.ts +305 -0
  45. package/lib/typescript/src/types.d.ts.map +1 -0
  46. package/package.json +105 -0
  47. package/src/AdStage.ts +212 -0
  48. package/src/deeplink/AdStageDeepLink.ts +246 -0
  49. package/src/event/AdStageEvent.ts +844 -0
  50. package/src/index.ts +48 -0
  51. package/src/promotion/AdStagePromotion.ts +162 -0
  52. 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
+ }