@pnlight/sdk-react-native 0.4.1 → 0.4.3

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/RemoteUiView.js CHANGED
@@ -2,10 +2,13 @@
2
2
 
3
3
  const React = require("react");
4
4
  const { requireNativeComponent } = require("react-native");
5
- const { getUIConfig } = require(".");
6
5
 
7
6
  const NativeRemoteUiView = requireNativeComponent("RemoteUiView");
8
7
 
8
+ async function getUIConfig(placement) {
9
+ return await NativeRemoteUiView.getUIConfig(placement);
10
+ }
11
+
9
12
  /**
10
13
  * Remote UI view: fetches UI config for the given placement via getUIConfig,
11
14
  * shows nothing while loading, then renders the config in a native DivKit view.
@@ -14,10 +17,12 @@ const NativeRemoteUiView = requireNativeComponent("RemoteUiView");
14
17
  * @param {string} props.placement - Placement id passed to getUIConfig(placement)
15
18
  * @param {object} props.style - Optional style for the container
16
19
  * @param {string} [props.cardId] - Optional DivKit card id (defaults to placement-based)
20
+ * @param {function} [props.onAction] - Callback for handling button clicks and custom actions
17
21
  */
18
- function RemoteUiView({ placement, style, cardId }) {
22
+ function RemoteUiView({ placement, style, cardId, onAction }) {
19
23
  const [config, setConfig] = React.useState(null);
20
- const cardIdToUse = cardId ?? (placement ? `pnlight_${placement}` : "pnlight_card");
24
+ const cardIdToUse =
25
+ cardId ?? (placement ? `pnlight_${placement}` : "pnlight_card");
21
26
 
22
27
  React.useEffect(() => {
23
28
  if (!placement) return;
@@ -31,11 +36,21 @@ function RemoteUiView({ placement, style, cardId }) {
31
36
  };
32
37
  }, [placement]);
33
38
 
39
+ const handleAction = React.useCallback(
40
+ (event) => {
41
+ if (onAction) {
42
+ onAction(event.nativeEvent);
43
+ }
44
+ },
45
+ [onAction],
46
+ );
47
+
34
48
  return (
35
49
  <NativeRemoteUiView
36
- style={[{ minHeight: 1 }, style]}
50
+ style={[{ minHeight: 1, flex: 1 }, style]}
37
51
  config={config}
38
52
  cardId={cardIdToUse}
53
+ onAction={handleAction}
39
54
  />
40
55
  );
41
56
  }
package/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export function initialize(apiKey: string, baseDomain?: string): Promise<void>;
2
2
  export function validatePurchase(captcha?: boolean): Promise<boolean>;
3
3
  export function logEvent(
4
4
  eventName: string,
5
- eventArgs?: Record<string, any>
5
+ eventArgs?: Record<string, any>,
6
6
  ): Promise<void>;
7
7
  export function getUserId(): Promise<string>;
8
8
  export function resetUserId(): Promise<void>;
@@ -18,7 +18,7 @@ export type AttributionProvider =
18
18
  export function addAttribution(
19
19
  provider: AttributionProvider,
20
20
  data?: Record<string, any> | null,
21
- identifier?: string | null
21
+ identifier?: string | null,
22
22
  ): Promise<boolean>;
23
23
 
24
24
  export function prefetchUIConfig(placement: string): Promise<void>;
@@ -30,10 +30,21 @@ export interface UIConfig {
30
30
 
31
31
  export function getUIConfig(placement: string): Promise<UIConfig | null>;
32
32
 
33
+ export interface ActionEvent {
34
+ url: string;
35
+ scheme: string;
36
+ path: string;
37
+ params: Record<string, string>;
38
+ logId?: string;
39
+ }
40
+
33
41
  export interface RemoteUiViewProps {
34
42
  placement: string;
35
43
  style?: import("react-native").StyleProp<import("react-native").ViewStyle>;
36
44
  cardId?: string;
45
+ onAction?: (event: ActionEvent) => void;
37
46
  }
38
47
 
39
- export const RemoteUiView: (props: RemoteUiViewProps) => import("react").ReactElement | null;
48
+ export const RemoteUiView: (
49
+ props: RemoteUiViewProps,
50
+ ) => import("react").ReactElement | null;
package/ios/PNLightSDK.m CHANGED
@@ -2,9 +2,12 @@
2
2
  @import PNLight;
3
3
 
4
4
  @interface RCT_EXTERN_MODULE(PNLightRN, NSObject)
5
- RCT_EXTERN_METHOD(initialize:(NSString *)apiKey resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
5
+ RCT_EXTERN_METHOD(initialize:(NSString *)apiKey baseDomain:(NSString *)baseDomain resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
6
6
  RCT_EXTERN_METHOD(validatePurchase:(BOOL)captcha resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
7
7
  RCT_EXTERN_METHOD(logEvent:(NSString *)eventName eventArgs:(NSDictionary *)eventArgs resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
8
+ RCT_EXTERN_METHOD(getUserId:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
9
+ RCT_EXTERN_METHOD(resetUserId:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
10
+ RCT_EXTERN_METHOD(addAttribution:(NSString *)providerString data:(NSDictionary *)data identifier:(NSString *)identifier resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
8
11
  RCT_EXTERN_METHOD(prefetchUIConfig:(NSString *)placement resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
9
12
  RCT_EXTERN_METHOD(getUIConfig:(NSString *)placement resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
10
13
  @end
@@ -1,26 +1,71 @@
1
1
  import UIKit
2
2
  import DivKit
3
3
 
4
- /// Native UIView that renders PNLight UI config (DivKit JSON) inside a DivView.
5
- /// Shows loading until config is set and loaded; supports error state and optional secure container.
6
4
  @objc(RemoteUiView)
7
5
  final class RemoteUiView: UIView {
8
6
 
7
+ // MARK: - URL handler for DivKit
8
+ private final class RemoteUiUrlHandler: DivUrlHandler {
9
+ weak var owner: RemoteUiView?
10
+
11
+ func handle(_ url: URL, info: DivActionInfo, sender: AnyObject?) {
12
+ guard let owner else { return }
13
+
14
+ if owner.isCustomAction(url) {
15
+ owner.handleDivAction(info) // send to RN
16
+ return
17
+ }
18
+
19
+ // Default behavior for normal links
20
+ if url.scheme?.lowercased() == "http" || url.scheme?.lowercased() == "https" {
21
+ DispatchQueue.main.async {
22
+ UIApplication.shared.open(url, options: [:], completionHandler: nil)
23
+ }
24
+ } else {
25
+ // Other non-http(s) schemes: either treat as custom or try open
26
+ DispatchQueue.main.async {
27
+ UIApplication.shared.open(url, options: [:], completionHandler: nil)
28
+ }
29
+ }
30
+ }
31
+
32
+ // Backward compatible overload (DivKit may call this)
33
+ func handle(_ url: URL, sender: AnyObject?) {
34
+ // If DivKit calls the short version, we still try to open it
35
+ DispatchQueue.main.async {
36
+ UIApplication.shared.open(url, options: [:], completionHandler: nil)
37
+ }
38
+ }
39
+ }
40
+
9
41
  private let divView: DivView
10
- private static let divKitComponents = DivKitComponents()
42
+ private let divKitComponents: DivKitComponents
43
+ private let urlHandler: RemoteUiUrlHandler
44
+
11
45
  private var currentCardId: String = "pnlight_card"
12
46
 
13
47
  private let loadingIndicator: UIActivityIndicatorView
14
48
  private let errorLabel: UILabel
15
49
  private var secureContainer: UIView?
16
50
 
51
+ @objc var onAction: (([String: Any]) -> Void)?
52
+
17
53
  override init(frame: CGRect) {
18
- self.divView = DivView(divKitComponents: Self.divKitComponents)
19
54
  self.loadingIndicator = UIActivityIndicatorView(style: .large)
20
55
  self.errorLabel = UILabel()
21
56
 
57
+ // Create url handler first (needed for DivKitComponents init)
58
+ self.urlHandler = RemoteUiUrlHandler()
59
+
60
+ // Pass urlHandler via initializer (actionHandler is a let constant inside DivKitComponents)
61
+ self.divKitComponents = DivKitComponents(urlHandler: urlHandler)
62
+ self.divView = DivView(divKitComponents: divKitComponents)
63
+
22
64
  super.init(frame: frame)
23
65
 
66
+ // bind back-reference
67
+ self.urlHandler.owner = self
68
+
24
69
  backgroundColor = .clear
25
70
 
26
71
  divView.translatesAutoresizingMaskIntoConstraints = false
@@ -52,12 +97,15 @@ final class RemoteUiView: UIView {
52
97
  secure.leadingAnchor.constraint(equalTo: leadingAnchor),
53
98
  secure.trailingAnchor.constraint(equalTo: trailingAnchor),
54
99
  secure.bottomAnchor.constraint(equalTo: bottomAnchor),
100
+
55
101
  divView.topAnchor.constraint(equalTo: secure.topAnchor),
56
102
  divView.leadingAnchor.constraint(equalTo: secure.leadingAnchor),
57
103
  divView.trailingAnchor.constraint(equalTo: secure.trailingAnchor),
58
104
  divView.bottomAnchor.constraint(equalTo: secure.bottomAnchor),
105
+
59
106
  loadingIndicator.centerXAnchor.constraint(equalTo: secure.centerXAnchor),
60
107
  loadingIndicator.centerYAnchor.constraint(equalTo: secure.centerYAnchor),
108
+
61
109
  errorLabel.topAnchor.constraint(equalTo: secure.topAnchor),
62
110
  errorLabel.leadingAnchor.constraint(equalTo: secure.leadingAnchor),
63
111
  errorLabel.trailingAnchor.constraint(equalTo: secure.trailingAnchor),
@@ -73,8 +121,10 @@ final class RemoteUiView: UIView {
73
121
  divView.leadingAnchor.constraint(equalTo: leadingAnchor),
74
122
  divView.trailingAnchor.constraint(equalTo: trailingAnchor),
75
123
  divView.bottomAnchor.constraint(equalTo: bottomAnchor),
124
+
76
125
  loadingIndicator.centerXAnchor.constraint(equalTo: centerXAnchor),
77
126
  loadingIndicator.centerYAnchor.constraint(equalTo: centerYAnchor),
127
+
78
128
  errorLabel.topAnchor.constraint(equalTo: topAnchor),
79
129
  errorLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
80
130
  errorLabel.trailingAnchor.constraint(equalTo: trailingAnchor),
@@ -87,7 +137,6 @@ final class RemoteUiView: UIView {
87
137
  fatalError("init(coder:) has not been implemented")
88
138
  }
89
139
 
90
- /// Creates a secure container that hides content during screenshots and screen recording.
91
140
  private static func makeSecureContainer() -> UIView? {
92
141
  let textField = UITextField()
93
142
  textField.isSecureTextEntry = true
@@ -104,9 +153,8 @@ final class RemoteUiView: UIView {
104
153
  return secureView
105
154
  }
106
155
 
107
- /// Renders the given DivKit JSON string. Pass nil to show blank (e.g. while loading).
108
156
  private func applyConfig(configJson: String?, cardId: String) {
109
- guard let configJson = configJson, !configJson.isEmpty,
157
+ guard let configJson, !configJson.isEmpty,
110
158
  let jsonData = configJson.data(using: .utf8) else {
111
159
  return
112
160
  }
@@ -149,10 +197,41 @@ final class RemoteUiView: UIView {
149
197
  }
150
198
  }
151
199
 
152
- // MARK: - React Native view props
200
+ private func isCustomAction(_ url: URL) -> Bool {
201
+ guard let scheme = url.scheme?.lowercased() else { return false }
202
+ return scheme != "http" && scheme != "https"
203
+ }
204
+
205
+ private func handleDivAction(_ action: DivActionInfo) {
206
+ guard let onAction else { return }
207
+
208
+ var payload: [String: Any] = [:]
209
+
210
+ if let url = action.url {
211
+ payload["url"] = url.absoluteString
212
+
213
+ if let components = URLComponents(url: url, resolvingAgainstBaseURL: false) {
214
+ payload["scheme"] = components.scheme ?? ""
215
+ payload["path"] = components.path
216
+
217
+ if let queryItems = components.queryItems {
218
+ var params: [String: String] = [:]
219
+ for item in queryItems {
220
+ params[item.name] = item.value ?? ""
221
+ }
222
+ payload["params"] = params
223
+ }
224
+ }
225
+ }
226
+
227
+ payload["logId"] = action.logId
228
+
229
+ onAction(payload)
230
+ }
231
+
232
+ // MARK: - React Native props
153
233
  @objc func setConfig(_ value: NSString?) {
154
- let json = value as String?
155
- applyConfig(configJson: json, cardId: currentCardId)
234
+ applyConfig(configJson: value as String?, cardId: currentCardId)
156
235
  }
157
236
 
158
237
  @objc func setCardId(_ value: NSString?) {
@@ -3,4 +3,5 @@
3
3
  @interface RCT_EXTERN_MODULE(RemoteUiViewManager, RCTViewManager)
4
4
  RCT_EXPORT_VIEW_PROPERTY(config, NSString)
5
5
  RCT_EXPORT_VIEW_PROPERTY(cardId, NSString)
6
+ RCT_EXPORT_VIEW_PROPERTY(onAction, RCTDirectEventBlock)
6
7
  @end
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnlight/sdk-react-native",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "React Native wrapper for PNLight iOS binary SDK",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",