vialink-react-native-sdk 2.1.10 → 3.1.1

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.
@@ -4,6 +4,7 @@ import com.facebook.react.bridge.*
4
4
  import com.facebook.react.modules.core.DeviceEventManagerModule
5
5
  import com.vialink.sdk.ViaLinkSDK
6
6
  import com.vialink.sdk.model.DeepLinkData
7
+ import com.vialink.sdk.model.DeferredError
7
8
  import com.vialink.sdk.model.PaymentInitiatedArgs
8
9
  import kotlinx.coroutines.*
9
10
 
@@ -12,11 +13,15 @@ class ViaLinkModule(reactContext: ReactApplicationContext) :
12
13
  ActivityEventListener {
13
14
 
14
15
  companion object {
15
- const val WRAPPER_VERSION = "2.1.0"
16
+ const val WRAPPER_VERSION = "3.1.0"
16
17
  }
17
18
 
18
19
  private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
20
+ /// onDeepLink가 listenerCount==0 시점에 호출된 경우의 캐시.
21
+ /// 3.1.0부터 일반 진입에서도 콜백이 호출되므로, link 진입(map != null) / 일반 진입(map == null)을
22
+ /// 구분하기 위해 별도 플래그(`hasPendingDeepLink`)를 둔다.
19
23
  private var pendingDeepLink: WritableMap? = null
24
+ private var hasPendingDeepLink: Boolean = false
20
25
  private var pendingDeferred: WritableMap? = null
21
26
  private var listenerCount = 0
22
27
 
@@ -39,15 +44,24 @@ class ViaLinkModule(reactContext: ReactApplicationContext) :
39
44
  ViaLinkSDK.setWrapper("react-native/$WRAPPER_VERSION")
40
45
  ViaLinkSDK.init(context, apiKey)
41
46
 
47
+ // 딥링크 콜백 — SDK 3.1.0+: data == null이면 일반 진입을 의미한다.
48
+ // JS 측 emitter는 null payload를 그대로 전달받아 `data: null`로 콜백을 호출한다.
42
49
  ViaLinkSDK.onDeepLink { data ->
43
- val map = data.toWritableMap()
50
+ val map: WritableMap? = data?.toWritableMap()
44
51
  if (listenerCount > 0) sendEvent("onDeepLink", map)
45
- else pendingDeepLink = map
52
+ else {
53
+ pendingDeepLink = map
54
+ hasPendingDeepLink = true
55
+ }
46
56
  }
47
- ViaLinkSDK.onDeferredDeepLink { data ->
48
- val map = data.toWritableMap()
49
- if (listenerCount > 0) sendEvent("onDeferredDeepLink", map)
50
- else pendingDeferred = map
57
+ // 디퍼드 콜백: SDK 3.0+ 시그니처 (data, error) — 항상 1회 호출
58
+ // JS emit 페이로드는 `{data, error}` 객체로 전달한다.
59
+ ViaLinkSDK.onDeferredDeepLink { data, error ->
60
+ val payload = Arguments.createMap()
61
+ data?.let { payload.putMap("data", it.toWritableMap()) }
62
+ error?.let { payload.putMap("error", it.toWritableMap()) }
63
+ if (listenerCount > 0) sendEvent("onDeferredDeepLink", payload)
64
+ else pendingDeferred = payload
51
65
  }
52
66
 
53
67
  currentActivity?.intent?.let { ViaLinkSDK.handleIntent(it) }
@@ -161,10 +175,11 @@ class ViaLinkModule(reactContext: ReactApplicationContext) :
161
175
  @ReactMethod
162
176
  fun addListener(eventName: String) {
163
177
  listenerCount++
164
- // pending 이벤트 flush
165
- if (eventName == "onDeepLink") {
166
- pendingDeepLink?.let { sendEvent("onDeepLink", it) }
178
+ // pending 이벤트 flush — 3.1.0부터 일반 진입(null payload)도 flush해야 한다.
179
+ if (eventName == "onDeepLink" && hasPendingDeepLink) {
180
+ sendEvent("onDeepLink", pendingDeepLink) // null payload도 그대로 전달
167
181
  pendingDeepLink = null
182
+ hasPendingDeepLink = false
168
183
  }
169
184
  if (eventName == "onDeferredDeepLink") {
170
185
  pendingDeferred?.let { sendEvent("onDeferredDeepLink", it) }
@@ -201,3 +216,13 @@ private fun DeepLinkData.toWritableMap(): WritableMap {
201
216
  linkId?.let { map.putInt("linkId", it) }
202
217
  return map
203
218
  }
219
+
220
+ // DeferredError → WritableMap (JS DeferredError 인터페이스와 키가 일치해야 함)
221
+ private fun DeferredError.toWritableMap(): WritableMap {
222
+ val map = Arguments.createMap()
223
+ map.putString("code", code)
224
+ map.putString("message", message)
225
+ httpStatus?.let { map.putInt("httpStatus", it) }
226
+ map.putBoolean("retryable", retryable)
227
+ return map
228
+ }
@@ -4,6 +4,26 @@ export interface DeepLinkData {
4
4
  shortCode?: string;
5
5
  linkId?: number;
6
6
  }
7
+ /**
8
+ * 디퍼드 매칭 실패 정보
9
+ *
10
+ * `onDeferredDeepLink((data, error) => ...)` 콜백의 두 번째 인자로 전달된다.
11
+ * `data == null && error == null`이면 매칭 결과가 "없음"(organic install)이다.
12
+ *
13
+ * - `code`:
14
+ * - `'timeout'`: 5초 데드라인 만료
15
+ * - `'network'`: DNS 실패, 연결 거부 등 (3회 재시도 모두 실패)
16
+ * - `'server_error'`: HTTP 5xx (3회 재시도 모두 실패)
17
+ * - `'invalid_response'`: 응답 JSON 파싱 실패
18
+ * - `'unknown'`: 그 외 모든 예외
19
+ * - `retryable`이 true면 SDK가 다음 앱 실행에서 자동으로 다시 시도한다.
20
+ */
21
+ export interface DeferredError {
22
+ code: 'timeout' | 'network' | 'server_error' | 'invalid_response' | 'unknown';
23
+ message: string;
24
+ httpStatus?: number;
25
+ retryable: boolean;
26
+ }
7
27
  /**
8
28
  * 결제 시도 이벤트 입력 인자.
9
29
  *
@@ -72,15 +92,47 @@ export declare class ViaLinkSDK {
72
92
  */
73
93
  configure(apiKey: string): Promise<void>;
74
94
  /**
75
- * 딥링크 수신 콜백 등록
76
- * App Links / Universal Links로 앱이 열렸을 때 호출됩니다.
95
+ * 딥링크 수신 콜백 등록 (3.1.0+).
96
+ *
97
+ * 네이티브 SDK가 진입 알림(`handleIntent`/`handleUniversalLink`/`notifyAppLaunch`)을 처리할 때 항상 1회 호출됩니다.
98
+ * - App Links / Universal Links 진입이면 매칭된 [DeepLinkData]가 전달됩니다.
99
+ * - 일반 진입(아이콘 탭 등)이면 `null`이 전달됩니다.
100
+ *
101
+ * ```typescript
102
+ * ViaLinkSDK.shared.onDeepLink((data) => {
103
+ * if (!data) {
104
+ * // 일반 진입 — 기본 라우트 유지
105
+ * return;
106
+ * }
107
+ * navigation.navigate(data.path, data.params);
108
+ * });
109
+ * ```
77
110
  */
78
- onDeepLink(callback: (data: DeepLinkData) => void): void;
111
+ onDeepLink(callback: (data: DeepLinkData | null) => void): void;
79
112
  /**
80
113
  * 디퍼드 딥링크 콜백 등록
81
- * 앱 첫 설치 후 실행 시 fingerprint 매칭으로 딥링크 데이터를 전달합니다.
114
+ *
115
+ * 앱 첫 실행 시 매칭 결과가 결정되는 즉시 항상 1회 호출됩니다.
116
+ * 5초 안에 결과가 결정되지 않으면 `error.code === 'timeout'`으로 호출됩니다.
117
+ *
118
+ * ```typescript
119
+ * ViaLinkSDK.shared.onDeferredDeepLink((data, error) => {
120
+ * if (error) {
121
+ * // 매칭 실패 (timeout/network/server_error 등) — 일반 진입
122
+ * return;
123
+ * }
124
+ * if (!data) {
125
+ * // organic install — 일반 진입
126
+ * return;
127
+ * }
128
+ * navigation.navigate(data.path, data.params);
129
+ * });
130
+ * ```
131
+ *
132
+ * 콜백은 멱등성을 보장합니다 (총 1회 호출).
133
+ * `error.retryable`이 true면 다음 앱 실행에서 자동 재시도되며, 그 경우 사용자가 앱을 사용 중일 때 콜백이 도착할 수 있습니다.
82
134
  */
83
- onDeferredDeepLink(callback: (data: DeepLinkData) => void): void;
135
+ onDeferredDeepLink(callback: (data: DeepLinkData | null, error: DeferredError | null) => void): void;
84
136
  /**
85
137
  * 커스텀 이벤트 추적
86
138
  *
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export { ViaLinkSDK } from './ViaLinkSDK';
2
- export type { DeepLinkData, PaymentInitiatedArgs, PaymentInitiatedResult, } from './ViaLinkSDK';
2
+ export type { DeepLinkData, DeferredError, PaymentInitiatedArgs, PaymentInitiatedResult, } from './ViaLinkSDK';
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- 'use strict';var reactNative=require('react-native');var {ViaLinkSDK:n}=reactNative.NativeModules,i=new reactNative.NativeEventEmitter(n),m=/^[A-Za-z0-9_-]{1,100}$/,r=class a{constructor(){this.payment={initiated:async e=>{if(!e||typeof e!="object")throw new Error("args\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.");if(typeof e.orderId!="string"||!m.test(e.orderId))throw new Error("order_id \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 (1~100\uC790, \uC601\uBB38/\uC22B\uC790/\uD558\uC774\uD508/\uC5B8\uB354\uC2A4\uCF54\uC5B4).");if(typeof e.amount!="number"||!Number.isFinite(e.amount)||e.amount<=0)throw new Error("amount\uB294 0\uBCF4\uB2E4 \uD070 \uC22B\uC790\uC5EC\uC57C \uD569\uB2C8\uB2E4.");if(typeof e.currency!="string"||e.currency.trim().length===0)throw new Error("currency\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.");let t=await n.paymentInitiated({orderId:e.orderId,amount:e.amount,currency:e.currency.trim().toUpperCase(),linkId:e.linkId??null,paymentMethod:e.paymentMethod??null,metadata:e.metadata??null});return {success:!!t?.success,paymentEventId:String(t?.paymentEventId??"")}}};}static get shared(){return this._instance||(this._instance=new a),this._instance}async configure(e){await n.configure(e);}onDeepLink(e){this.deepLinkSub?.remove(),this.deepLinkSub=i.addListener("onDeepLink",e);}onDeferredDeepLink(e){this.deferredSub?.remove(),this.deferredSub=i.addListener("onDeferredDeepLink",e);}track(e,t){n.track(e,t??null);}async createLink(e,t,o,s="static",d){return n.createLink(e,t??null,o??null,s,d??null)}destroy(){this.deepLinkSub?.remove(),this.deferredSub?.remove();}};exports.ViaLinkSDK=r;
1
+ 'use strict';var reactNative=require('react-native');var {ViaLinkSDK:r}=reactNative.NativeModules,i=new reactNative.NativeEventEmitter(r),l=/^[A-Za-z0-9_-]{1,100}$/,n=class a{constructor(){this.payment={initiated:async e=>{if(!e||typeof e!="object")throw new Error("args\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.");if(typeof e.orderId!="string"||!l.test(e.orderId))throw new Error("order_id \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 (1~100\uC790, \uC601\uBB38/\uC22B\uC790/\uD558\uC774\uD508/\uC5B8\uB354\uC2A4\uCF54\uC5B4).");if(typeof e.amount!="number"||!Number.isFinite(e.amount)||e.amount<=0)throw new Error("amount\uB294 0\uBCF4\uB2E4 \uD070 \uC22B\uC790\uC5EC\uC57C \uD569\uB2C8\uB2E4.");if(typeof e.currency!="string"||e.currency.trim().length===0)throw new Error("currency\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.");let t=await r.paymentInitiated({orderId:e.orderId,amount:e.amount,currency:e.currency.trim().toUpperCase(),linkId:e.linkId??null,paymentMethod:e.paymentMethod??null,metadata:e.metadata??null});return {success:!!t?.success,paymentEventId:String(t?.paymentEventId??"")}}};}static get shared(){return this._instance||(this._instance=new a),this._instance}async configure(e){await r.configure(e);}onDeepLink(e){this.deepLinkSub?.remove(),this.deepLinkSub=i.addListener("onDeepLink",t=>{e(t??null);});}onDeferredDeepLink(e){this.deferredSub?.remove(),this.deferredSub=i.addListener("onDeferredDeepLink",t=>{e(t?.data??null,t?.error??null);});}track(e,t){r.track(e,t??null);}async createLink(e,t,o,d="static",s){return r.createLink(e,t??null,o??null,d,s??null)}destroy(){this.deepLinkSub?.remove(),this.deferredSub?.remove();}};exports.ViaLinkSDK=n;
@@ -5,9 +5,13 @@ import ViaLinkCore // xcframework module: ViaLinkCore (class: ViaLinkSDK)
5
5
  @objc(ViaLinkSDK)
6
6
  class ViaLinkModule: RCTEventEmitter {
7
7
 
8
- static let wrapperVersion = "2.1.0"
8
+ static let wrapperVersion = "3.1.0"
9
9
 
10
+ /// onDeepLink가 hasListeners == false 시점에 호출된 경우의 캐시.
11
+ /// 3.1.0부터 일반 진입(nil payload)도 콜백이 호출되므로, link 진입(map != nil) /
12
+ /// 일반 진입(map == nil)을 구분하기 위해 별도 플래그(`hasPendingDeepLink`)를 둔다.
10
13
  private var pendingDeepLink: [String: Any?]?
14
+ private var hasPendingDeepLink: Bool = false
11
15
  private var pendingDeferred: [String: Any?]?
12
16
  private var hasListeners = false
13
17
 
@@ -20,9 +24,11 @@ class ViaLinkModule: RCTEventEmitter {
20
24
 
21
25
  override func startObserving() {
22
26
  hasListeners = true
23
- if let pending = pendingDeepLink {
24
- sendEvent(withName: "onDeepLink", body: pending)
27
+ // 3.1.0+: pendingDeepLink가 nil이어도 hasPendingDeepLink가 true면 일반 진입 콜백을 flush한다.
28
+ if hasPendingDeepLink {
29
+ sendEvent(withName: "onDeepLink", body: pendingDeepLink as Any)
25
30
  pendingDeepLink = nil
31
+ hasPendingDeepLink = false
26
32
  }
27
33
  if let pending = pendingDeferred {
28
34
  sendEvent(withName: "onDeferredDeepLink", body: pending)
@@ -37,21 +43,28 @@ class ViaLinkModule: RCTEventEmitter {
37
43
  ViaLinkSDK.shared.setWrapper("react-native/\(Self.wrapperVersion)")
38
44
  ViaLinkSDK.shared.configure(apiKey: apiKey)
39
45
 
46
+ // 딥링크 콜백 — SDK 3.1.0+: data == nil이면 일반 진입을 의미한다.
47
+ // JS 측 emitter는 null payload를 그대로 전달받아 `data: null`로 콜백을 호출한다.
40
48
  ViaLinkSDK.shared.onDeepLink { [weak self] data in
41
- let map = data.toDictionary()
49
+ let map: [String: Any?]? = data?.toDictionary()
42
50
  if self?.hasListeners == true {
43
- self?.sendEvent(withName: "onDeepLink", body: map)
51
+ self?.sendEvent(withName: "onDeepLink", body: map as Any)
44
52
  } else {
45
53
  self?.pendingDeepLink = map
54
+ self?.hasPendingDeepLink = true
46
55
  }
47
56
  }
48
57
 
49
- ViaLinkSDK.shared.onDeferredDeepLink { [weak self] data in
50
- let map = data.toDictionary()
58
+ // 디퍼드 콜백: SDK 3.0+ 시그니처 (data, error) — 항상 1회 호출
59
+ // JS emit 페이로드는 `{data, error}` 객체로 전달한다.
60
+ ViaLinkSDK.shared.onDeferredDeepLink { [weak self] data, error in
61
+ var payload: [String: Any?] = [:]
62
+ if let data = data { payload["data"] = data.toDictionary() }
63
+ if let error = error { payload["error"] = error.toDictionary() }
51
64
  if self?.hasListeners == true {
52
- self?.sendEvent(withName: "onDeferredDeepLink", body: map)
65
+ self?.sendEvent(withName: "onDeferredDeepLink", body: payload)
53
66
  } else {
54
- self?.pendingDeferred = map
67
+ self?.pendingDeferred = payload
55
68
  }
56
69
  }
57
70
 
@@ -151,3 +164,15 @@ extension DeepLinkData {
151
164
  ["path": path, "params": params, "shortCode": shortCode, "linkId": linkId]
152
165
  }
153
166
  }
167
+
168
+ extension DeferredError {
169
+ /// JS DeferredError 인터페이스와 키가 일치해야 함
170
+ func toDictionary() -> [String: Any?] {
171
+ [
172
+ "code": code.rawValue,
173
+ "message": message,
174
+ "httpStatus": httpStatus,
175
+ "retryable": retryable,
176
+ ]
177
+ }
178
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vialink-react-native-sdk",
3
- "version": "2.1.10",
3
+ "version": "3.1.1",
4
4
  "description": "ViaLink 딥링크 SDK for React Native (네이티브 브릿지)",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |s|
2
2
  s.name = "vialink-react-native-sdk"
3
- s.version = "2.1.10"
3
+ s.version = "3.1.1"
4
4
  s.summary = "ViaLink Deep Link SDK for React Native"
5
5
  s.homepage = "https://vialink.app"
6
6
  s.license = "MIT"