@pnlight/sdk-react-native 0.3.8 → 0.4.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.
Files changed (34) hide show
  1. package/PNLight.xcframework/Info.plist +5 -5
  2. package/PNLight.xcframework/ios-arm64/PNLight.framework/Headers/PNLight-Swift.h +2 -0
  3. package/PNLight.xcframework/ios-arm64/PNLight.framework/Modules/PNLight.swiftmodule/Project/arm64-apple-ios.swiftsourceinfo +0 -0
  4. package/PNLight.xcframework/ios-arm64/PNLight.framework/Modules/PNLight.swiftmodule/arm64-apple-ios.abi.json +1019 -47
  5. package/PNLight.xcframework/ios-arm64/PNLight.framework/Modules/PNLight.swiftmodule/arm64-apple-ios.private.swiftinterface +35 -1
  6. package/PNLight.xcframework/ios-arm64/PNLight.framework/Modules/PNLight.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  7. package/PNLight.xcframework/ios-arm64/PNLight.framework/Modules/PNLight.swiftmodule/arm64-apple-ios.swiftinterface +35 -1
  8. package/PNLight.xcframework/ios-arm64/PNLight.framework/PNLight +0 -0
  9. package/PNLight.xcframework/ios-arm64_x86_64-simulator/PNLight.framework/Headers/PNLight-Swift.h +4 -0
  10. package/PNLight.xcframework/ios-arm64_x86_64-simulator/PNLight.framework/Modules/PNLight.swiftmodule/Project/arm64-apple-ios-simulator.swiftsourceinfo +0 -0
  11. package/PNLight.xcframework/ios-arm64_x86_64-simulator/PNLight.framework/Modules/PNLight.swiftmodule/Project/x86_64-apple-ios-simulator.swiftsourceinfo +0 -0
  12. package/PNLight.xcframework/ios-arm64_x86_64-simulator/PNLight.framework/Modules/PNLight.swiftmodule/arm64-apple-ios-simulator.abi.json +1019 -47
  13. package/PNLight.xcframework/ios-arm64_x86_64-simulator/PNLight.framework/Modules/PNLight.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +35 -1
  14. package/PNLight.xcframework/ios-arm64_x86_64-simulator/PNLight.framework/Modules/PNLight.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  15. package/PNLight.xcframework/ios-arm64_x86_64-simulator/PNLight.framework/Modules/PNLight.swiftmodule/arm64-apple-ios-simulator.swiftinterface +35 -1
  16. package/PNLight.xcframework/ios-arm64_x86_64-simulator/PNLight.framework/Modules/PNLight.swiftmodule/x86_64-apple-ios-simulator.abi.json +1019 -47
  17. package/PNLight.xcframework/ios-arm64_x86_64-simulator/PNLight.framework/Modules/PNLight.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +35 -1
  18. package/PNLight.xcframework/ios-arm64_x86_64-simulator/PNLight.framework/Modules/PNLight.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
  19. package/PNLight.xcframework/ios-arm64_x86_64-simulator/PNLight.framework/Modules/PNLight.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +35 -1
  20. package/PNLight.xcframework/ios-arm64_x86_64-simulator/PNLight.framework/PNLight +0 -0
  21. package/PNLightSDK-ReactNative.podspec +5 -2
  22. package/RemoteUiView.js +43 -0
  23. package/app.plugin.js +3 -0
  24. package/index.d.ts +34 -1
  25. package/index.js +18 -2
  26. package/ios/PNLightSDK.m +2 -0
  27. package/ios/PNLightSDK.swift +43 -2
  28. package/ios/RemoteUiView.swift +161 -0
  29. package/ios/RemoteUiViewManager.m +6 -0
  30. package/ios/RemoteUiViewManager.swift +18 -0
  31. package/package.json +39 -26
  32. package/plugin/index.js +17 -0
  33. package/plugin/withIos.js +39 -0
  34. package/README.md +0 -83
@@ -14,6 +14,23 @@ import _Concurrency
14
14
  import _StringProcessing
15
15
  import _SwiftConcurrencyShims
16
16
  import os
17
+ extension PNLight.PNLightSDK {
18
+ public enum AttributionProvider : Swift.String, Swift.Codable {
19
+ case appsFlyer
20
+ case adjust
21
+ case firebase
22
+ case appleAdsAttribution
23
+ case custom
24
+ case facebook
25
+ public init?(rawValue: Swift.String)
26
+ public typealias RawValue = Swift.String
27
+ public var rawValue: Swift.String {
28
+ get
29
+ }
30
+ }
31
+ @discardableResult
32
+ final public func addAttribution(provider: PNLight.PNLightSDK.AttributionProvider, data: [Swift.String : Any]? = nil, identifier: Swift.String? = nil) async -> Swift.Bool
33
+ }
17
34
  extension PNLight.PNLightSDK {
18
35
  final public func logEvent(_ eventName: Swift.String, eventArgs: [Swift.String : Any]? = nil) async
19
36
  }
@@ -26,14 +43,31 @@ extension PNLight.PNLightSDK {
26
43
  extension PNLight.PNLightSDK {
27
44
  final public func validatePurchase(captcha: Swift.Bool = true) async -> Swift.Bool
28
45
  }
46
+ public struct UIConfig {
47
+ public let config: Swift.String?
48
+ public let parameters: [Swift.String : Any]?
49
+ }
50
+ extension PNLight.PNLightSDK {
51
+ final public func getUIConfig(placement: Swift.String) async -> PNLight.UIConfig?
52
+ final public func prefetchUIConfig(placement: Swift.String)
53
+ final public func clearUIConfigCache()
54
+ }
29
55
  extension PNLight.PNLightSDK {
30
56
  final public func getOrCreateUserId() -> (id: Swift.String, isNew: Swift.Bool)
57
+ final public func resetUserId()
58
+ }
59
+ public struct PNLightConfig {
60
+ public var baseDomain: Swift.String?
61
+ public init(baseDomain: Swift.String? = nil)
31
62
  }
32
63
  @objc @_inheritsConvenienceInitializers @_hasMissingDesignatedInitializers final public class PNLightSDK : ObjectiveC.NSObject {
33
64
  public static let shared: PNLight.PNLightSDK
34
- final public func initialize(apiKey: Swift.String) async
65
+ final public func initialize(apiKey: Swift.String, config: PNLight.PNLightConfig? = nil) async
35
66
  @objc deinit
36
67
  }
37
68
  extension PNLight.PNLightSDK : StoreKit.SKPaymentTransactionObserver {
38
69
  @objc final public func paymentQueue(_ queue: StoreKit.SKPaymentQueue, updatedTransactions transactions: [StoreKit.SKPaymentTransaction])
39
70
  }
71
+ extension PNLight.PNLightSDK.AttributionProvider : Swift.Equatable {}
72
+ extension PNLight.PNLightSDK.AttributionProvider : Swift.Hashable {}
73
+ extension PNLight.PNLightSDK.AttributionProvider : Swift.RawRepresentable {}
@@ -14,6 +14,23 @@ import _Concurrency
14
14
  import _StringProcessing
15
15
  import _SwiftConcurrencyShims
16
16
  import os
17
+ extension PNLight.PNLightSDK {
18
+ public enum AttributionProvider : Swift.String, Swift.Codable {
19
+ case appsFlyer
20
+ case adjust
21
+ case firebase
22
+ case appleAdsAttribution
23
+ case custom
24
+ case facebook
25
+ public init?(rawValue: Swift.String)
26
+ public typealias RawValue = Swift.String
27
+ public var rawValue: Swift.String {
28
+ get
29
+ }
30
+ }
31
+ @discardableResult
32
+ final public func addAttribution(provider: PNLight.PNLightSDK.AttributionProvider, data: [Swift.String : Any]? = nil, identifier: Swift.String? = nil) async -> Swift.Bool
33
+ }
17
34
  extension PNLight.PNLightSDK {
18
35
  final public func logEvent(_ eventName: Swift.String, eventArgs: [Swift.String : Any]? = nil) async
19
36
  }
@@ -26,14 +43,31 @@ extension PNLight.PNLightSDK {
26
43
  extension PNLight.PNLightSDK {
27
44
  final public func validatePurchase(captcha: Swift.Bool = true) async -> Swift.Bool
28
45
  }
46
+ public struct UIConfig {
47
+ public let config: Swift.String?
48
+ public let parameters: [Swift.String : Any]?
49
+ }
50
+ extension PNLight.PNLightSDK {
51
+ final public func getUIConfig(placement: Swift.String) async -> PNLight.UIConfig?
52
+ final public func prefetchUIConfig(placement: Swift.String)
53
+ final public func clearUIConfigCache()
54
+ }
29
55
  extension PNLight.PNLightSDK {
30
56
  final public func getOrCreateUserId() -> (id: Swift.String, isNew: Swift.Bool)
57
+ final public func resetUserId()
58
+ }
59
+ public struct PNLightConfig {
60
+ public var baseDomain: Swift.String?
61
+ public init(baseDomain: Swift.String? = nil)
31
62
  }
32
63
  @objc @_inheritsConvenienceInitializers @_hasMissingDesignatedInitializers final public class PNLightSDK : ObjectiveC.NSObject {
33
64
  public static let shared: PNLight.PNLightSDK
34
- final public func initialize(apiKey: Swift.String) async
65
+ final public func initialize(apiKey: Swift.String, config: PNLight.PNLightConfig? = nil) async
35
66
  @objc deinit
36
67
  }
37
68
  extension PNLight.PNLightSDK : StoreKit.SKPaymentTransactionObserver {
38
69
  @objc final public func paymentQueue(_ queue: StoreKit.SKPaymentQueue, updatedTransactions transactions: [StoreKit.SKPaymentTransaction])
39
70
  }
71
+ extension PNLight.PNLightSDK.AttributionProvider : Swift.Equatable {}
72
+ extension PNLight.PNLightSDK.AttributionProvider : Swift.Hashable {}
73
+ extension PNLight.PNLightSDK.AttributionProvider : Swift.RawRepresentable {}
@@ -1,8 +1,10 @@
1
+ # Host app Podfile must include DivKit source for RemoteUiView (DivKit):
2
+ # source 'https://github.com/divkit/divkit-ios.git'
1
3
  Pod::Spec.new do |s|
2
4
  s.name = 'PNLightSDK-ReactNative'
3
- s.version = '0.3.8'
5
+ s.version = '0.3.7'
4
6
  s.summary = 'React Native wrapper for PNLight iOS SDK'
5
- s.description = 'Provides RN bridge to PNLight.xcframework'
7
+ s.description = 'Provides RN bridge to PNLight.xcframework. RemoteUiView uses DivKit (~> 32.32); add DivKit source to Podfile if using it.'
6
8
  s.homepage = 'https://pnlight.app'
7
9
  s.license = { :type => 'MIT', :text => 'See LICENSE' }
8
10
  s.author = { 'PNLight' => 'support@example.com' }
@@ -12,6 +14,7 @@ Pod::Spec.new do |s|
12
14
 
13
15
  s.source_files = 'ios/**/*.{h,m,mm,swift}'
14
16
  s.dependency 'React-Core'
17
+ s.dependency 'DivKit', '~> 32.32'
15
18
  s.vendored_frameworks = 'PNLight.xcframework'
16
19
  s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
17
20
  end
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+
3
+ const React = require("react");
4
+ const { requireNativeComponent } = require("react-native");
5
+ const { getUIConfig } = require(".");
6
+
7
+ const NativeRemoteUiView = requireNativeComponent("RemoteUiView");
8
+
9
+ /**
10
+ * Remote UI view: fetches UI config for the given placement via getUIConfig,
11
+ * shows nothing while loading, then renders the config in a native DivKit view.
12
+ *
13
+ * @param {object} props
14
+ * @param {string} props.placement - Placement id passed to getUIConfig(placement)
15
+ * @param {object} props.style - Optional style for the container
16
+ * @param {string} [props.cardId] - Optional DivKit card id (defaults to placement-based)
17
+ */
18
+ function RemoteUiView({ placement, style, cardId }) {
19
+ const [config, setConfig] = React.useState(null);
20
+ const cardIdToUse = cardId ?? (placement ? `pnlight_${placement}` : "pnlight_card");
21
+
22
+ React.useEffect(() => {
23
+ if (!placement) return;
24
+ let cancelled = false;
25
+ getUIConfig(placement).then((uiConfig) => {
26
+ if (cancelled) return;
27
+ setConfig(uiConfig?.config ?? null);
28
+ });
29
+ return () => {
30
+ cancelled = true;
31
+ };
32
+ }, [placement]);
33
+
34
+ return (
35
+ <NativeRemoteUiView
36
+ style={[{ minHeight: 1 }, style]}
37
+ config={config}
38
+ cardId={cardIdToUse}
39
+ />
40
+ );
41
+ }
42
+
43
+ module.exports = { RemoteUiView };
package/app.plugin.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+
3
+ module.exports = require("./plugin");
package/index.d.ts CHANGED
@@ -1,6 +1,39 @@
1
- export function initialize(apiKey: string): Promise<void>;
1
+ 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
5
  eventArgs?: Record<string, any>
6
6
  ): Promise<void>;
7
+ export function getUserId(): Promise<string>;
8
+ export function resetUserId(): Promise<void>;
9
+
10
+ export type AttributionProvider =
11
+ | "appsFlyer"
12
+ | "adjust"
13
+ | "firebase"
14
+ | "appleAdsAttribution"
15
+ | "custom"
16
+ | "facebook";
17
+
18
+ export function addAttribution(
19
+ provider: AttributionProvider,
20
+ data?: Record<string, any> | null,
21
+ identifier?: string | null
22
+ ): Promise<boolean>;
23
+
24
+ export function prefetchUIConfig(placement: string): Promise<void>;
25
+
26
+ export interface UIConfig {
27
+ config: string | null;
28
+ parameters: Record<string, any> | null;
29
+ }
30
+
31
+ export function getUIConfig(placement: string): Promise<UIConfig | null>;
32
+
33
+ export interface RemoteUiViewProps {
34
+ placement: string;
35
+ style?: import("react-native").StyleProp<import("react-native").ViewStyle>;
36
+ cardId?: string;
37
+ }
38
+
39
+ export const RemoteUiView: (props: RemoteUiViewProps) => import("react").ReactElement | null;
package/index.js CHANGED
@@ -4,8 +4,8 @@ const { NativeModules } = require("react-native");
4
4
  const PNLightNative = NativeModules.PNLightRN || NativeModules.PNLightSDK;
5
5
 
6
6
  module.exports = {
7
- initialize(apiKey) {
8
- return PNLightNative.initialize(apiKey);
7
+ initialize(apiKey, baseDomain = null) {
8
+ return PNLightNative.initialize(apiKey, baseDomain);
9
9
  },
10
10
  async validatePurchase(captcha = true) {
11
11
  return await PNLightNative.validatePurchase(captcha);
@@ -13,4 +13,20 @@ module.exports = {
13
13
  async logEvent(eventName, eventArgs = {}) {
14
14
  return await PNLightNative.logEvent(eventName, eventArgs);
15
15
  },
16
+ async getUserId() {
17
+ return await PNLightNative.getUserId();
18
+ },
19
+ async resetUserId() {
20
+ return await PNLightNative.resetUserId();
21
+ },
22
+ async addAttribution(provider, data = null, identifier = null) {
23
+ return await PNLightNative.addAttribution(provider, data, identifier);
24
+ },
25
+ async prefetchUIConfig(placement) {
26
+ return await PNLightNative.prefetchUIConfig(placement);
27
+ },
28
+ async getUIConfig(placement) {
29
+ return await PNLightNative.getUIConfig(placement);
30
+ },
31
+ RemoteUiView: require("./RemoteUiView").RemoteUiView,
16
32
  };
package/ios/PNLightSDK.m CHANGED
@@ -5,6 +5,8 @@
5
5
  RCT_EXTERN_METHOD(initialize:(NSString *)apiKey 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(prefetchUIConfig:(NSString *)placement resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
9
+ RCT_EXTERN_METHOD(getUIConfig:(NSString *)placement resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
8
10
  @end
9
11
 
10
12
 
@@ -4,9 +4,10 @@ import PNLight
4
4
 
5
5
  @objc(PNLightRN)
6
6
  class PNLightRNModule: NSObject {
7
- @objc func initialize(_ apiKey: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
7
+ @objc func initialize(_ apiKey: String, baseDomain: String? = nil, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
8
8
  Task {
9
- await PNLightSDK.shared.initialize(apiKey: apiKey)
9
+ let config = baseDomain.map { PNLightConfig(baseDomain: $0) }
10
+ await PNLightSDK.shared.initialize(apiKey: apiKey, config: config)
10
11
  resolve(nil)
11
12
  }
12
13
  }
@@ -24,6 +25,46 @@ class PNLightRNModule: NSObject {
24
25
  resolve(nil)
25
26
  }
26
27
  }
28
+
29
+ @objc func getUserId(resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
30
+ let userId = PNLightSDK.shared.getOrCreateUserId().id
31
+ resolve(userId)
32
+ }
33
+
34
+ @objc func resetUserId(resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
35
+ PNLightSDK.shared.resetUserId()
36
+ resolve(nil)
37
+ }
38
+
39
+ @objc func addAttribution(_ providerString: String, data: [String: Any]?, identifier: String?, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
40
+ Task {
41
+ guard let provider = PNLightSDK.AttributionProvider(rawValue: providerString) else {
42
+ reject("INVALID_PROVIDER", "Invalid attribution provider: \(providerString)", nil)
43
+ return
44
+ }
45
+ let result = await PNLightSDK.shared.addAttribution(provider: provider, data: data, identifier: identifier)
46
+ resolve(result)
47
+ }
48
+ }
49
+
50
+ @objc func prefetchUIConfig(_ placement: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
51
+ PNLightSDK.shared.prefetchUIConfig(placement: placement)
52
+ resolve(nil)
53
+ }
54
+
55
+ @objc func getUIConfig(_ placement: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
56
+ Task {
57
+ let uiConfig = await PNLightSDK.shared.getUIConfig(placement: placement)
58
+ if let uiConfig = uiConfig {
59
+ resolve([
60
+ "config": uiConfig.config as Any,
61
+ "parameters": uiConfig.parameters as Any,
62
+ ])
63
+ } else {
64
+ resolve(nil)
65
+ }
66
+ }
67
+ }
27
68
  }
28
69
 
29
70
 
@@ -0,0 +1,161 @@
1
+ import UIKit
2
+ import DivKit
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
+ @objc(RemoteUiView)
7
+ final class RemoteUiView: UIView {
8
+
9
+ private let divView: DivView
10
+ private static let divKitComponents = DivKitComponents()
11
+ private var currentCardId: String = "pnlight_card"
12
+
13
+ private let loadingIndicator: UIActivityIndicatorView
14
+ private let errorLabel: UILabel
15
+ private var secureContainer: UIView?
16
+
17
+ override init(frame: CGRect) {
18
+ self.divView = DivView(divKitComponents: Self.divKitComponents)
19
+ self.loadingIndicator = UIActivityIndicatorView(style: .large)
20
+ self.errorLabel = UILabel()
21
+
22
+ super.init(frame: frame)
23
+
24
+ backgroundColor = .clear
25
+
26
+ divView.translatesAutoresizingMaskIntoConstraints = false
27
+ divView.alpha = 0
28
+ divView.backgroundColor = .clear
29
+
30
+ loadingIndicator.translatesAutoresizingMaskIntoConstraints = false
31
+ loadingIndicator.hidesWhenStopped = false
32
+
33
+ errorLabel.translatesAutoresizingMaskIntoConstraints = false
34
+ errorLabel.text = "Failed to load DivKit content"
35
+ errorLabel.textColor = .red
36
+ errorLabel.textAlignment = .center
37
+ errorLabel.numberOfLines = 0
38
+ errorLabel.alpha = 0
39
+
40
+ let isSecure = true
41
+ if isSecure, let secure = Self.makeSecureContainer() {
42
+ secure.translatesAutoresizingMaskIntoConstraints = false
43
+ self.secureContainer = secure
44
+
45
+ secure.addSubview(divView)
46
+ secure.addSubview(loadingIndicator)
47
+ secure.addSubview(errorLabel)
48
+ addSubview(secure)
49
+
50
+ NSLayoutConstraint.activate([
51
+ secure.topAnchor.constraint(equalTo: topAnchor),
52
+ secure.leadingAnchor.constraint(equalTo: leadingAnchor),
53
+ secure.trailingAnchor.constraint(equalTo: trailingAnchor),
54
+ secure.bottomAnchor.constraint(equalTo: bottomAnchor),
55
+ divView.topAnchor.constraint(equalTo: secure.topAnchor),
56
+ divView.leadingAnchor.constraint(equalTo: secure.leadingAnchor),
57
+ divView.trailingAnchor.constraint(equalTo: secure.trailingAnchor),
58
+ divView.bottomAnchor.constraint(equalTo: secure.bottomAnchor),
59
+ loadingIndicator.centerXAnchor.constraint(equalTo: secure.centerXAnchor),
60
+ loadingIndicator.centerYAnchor.constraint(equalTo: secure.centerYAnchor),
61
+ errorLabel.topAnchor.constraint(equalTo: secure.topAnchor),
62
+ errorLabel.leadingAnchor.constraint(equalTo: secure.leadingAnchor),
63
+ errorLabel.trailingAnchor.constraint(equalTo: secure.trailingAnchor),
64
+ errorLabel.bottomAnchor.constraint(equalTo: secure.bottomAnchor),
65
+ ])
66
+ } else {
67
+ addSubview(divView)
68
+ addSubview(loadingIndicator)
69
+ addSubview(errorLabel)
70
+
71
+ NSLayoutConstraint.activate([
72
+ divView.topAnchor.constraint(equalTo: topAnchor),
73
+ divView.leadingAnchor.constraint(equalTo: leadingAnchor),
74
+ divView.trailingAnchor.constraint(equalTo: trailingAnchor),
75
+ divView.bottomAnchor.constraint(equalTo: bottomAnchor),
76
+ loadingIndicator.centerXAnchor.constraint(equalTo: centerXAnchor),
77
+ loadingIndicator.centerYAnchor.constraint(equalTo: centerYAnchor),
78
+ errorLabel.topAnchor.constraint(equalTo: topAnchor),
79
+ errorLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
80
+ errorLabel.trailingAnchor.constraint(equalTo: trailingAnchor),
81
+ errorLabel.bottomAnchor.constraint(equalTo: bottomAnchor),
82
+ ])
83
+ }
84
+ }
85
+
86
+ required init?(coder: NSCoder) {
87
+ fatalError("init(coder:) has not been implemented")
88
+ }
89
+
90
+ /// Creates a secure container that hides content during screenshots and screen recording.
91
+ private static func makeSecureContainer() -> UIView? {
92
+ let textField = UITextField()
93
+ textField.isSecureTextEntry = true
94
+ textField.isUserInteractionEnabled = false
95
+
96
+ guard let secureLayer = textField.layer.sublayers?.first,
97
+ let secureView = secureLayer.delegate as? UIView else {
98
+ return nil
99
+ }
100
+
101
+ secureView.subviews.forEach { $0.removeFromSuperview() }
102
+ secureView.isUserInteractionEnabled = true
103
+ secureView.backgroundColor = .clear
104
+ return secureView
105
+ }
106
+
107
+ /// Renders the given DivKit JSON string. Pass nil to show blank (e.g. while loading).
108
+ private func applyConfig(configJson: String?, cardId: String) {
109
+ guard let configJson = configJson, !configJson.isEmpty,
110
+ let jsonData = configJson.data(using: .utf8) else {
111
+ return
112
+ }
113
+
114
+ errorLabel.alpha = 0
115
+ errorLabel.text = "Failed to load DivKit content"
116
+ loadingIndicator.startAnimating()
117
+ loadingIndicator.alpha = 1
118
+ divView.alpha = 0
119
+
120
+ let source = DivViewSource(
121
+ kind: .data(jsonData),
122
+ cardId: DivCardID(rawValue: cardId) ?? "divkit"
123
+ )
124
+
125
+ Task { @MainActor in
126
+ do {
127
+ try await self.divView.setSource(source)
128
+ self.showContent()
129
+ } catch {
130
+ self.showError(error: error)
131
+ }
132
+ }
133
+ }
134
+
135
+ private func showContent() {
136
+ loadingIndicator.stopAnimating()
137
+ UIView.animate(withDuration: 0.3) {
138
+ self.loadingIndicator.alpha = 0
139
+ self.divView.alpha = 1
140
+ }
141
+ }
142
+
143
+ private func showError(error: Error) {
144
+ errorLabel.text = "Failed to load DivKit content:\n\(error.localizedDescription)"
145
+ loadingIndicator.stopAnimating()
146
+ UIView.animate(withDuration: 0.3) {
147
+ self.loadingIndicator.alpha = 0
148
+ self.errorLabel.alpha = 1
149
+ }
150
+ }
151
+
152
+ // MARK: - React Native view props
153
+ @objc func setConfig(_ value: NSString?) {
154
+ let json = value as String?
155
+ applyConfig(configJson: json, cardId: currentCardId)
156
+ }
157
+
158
+ @objc func setCardId(_ value: NSString?) {
159
+ currentCardId = (value as String?) ?? "pnlight_card"
160
+ }
161
+ }
@@ -0,0 +1,6 @@
1
+ #import <React/RCTViewManager.h>
2
+
3
+ @interface RCT_EXTERN_MODULE(RemoteUiViewManager, RCTViewManager)
4
+ RCT_EXPORT_VIEW_PROPERTY(config, NSString)
5
+ RCT_EXPORT_VIEW_PROPERTY(cardId, NSString)
6
+ @end
@@ -0,0 +1,18 @@
1
+ import React
2
+ import UIKit
3
+
4
+ @objc(RemoteUiViewManager)
5
+ final class RemoteUiViewManager: RCTViewManager {
6
+
7
+ override func view() -> UIView! {
8
+ return RemoteUiView()
9
+ }
10
+
11
+ override static func moduleName() -> String! {
12
+ return "RemoteUiView"
13
+ }
14
+
15
+ override static func requiresMainQueueSetup() -> Bool {
16
+ return true
17
+ }
18
+ }
package/package.json CHANGED
@@ -1,28 +1,41 @@
1
1
  {
2
- "name": "@pnlight/sdk-react-native",
3
- "version": "0.3.8",
4
- "description": "React Native wrapper for PNLight iOS binary SDK",
5
- "main": "index.js",
6
- "types": "index.d.ts",
7
- "react-native": "index.js",
8
- "files": [
9
- "index.js",
10
- "index.d.ts",
11
- "ios",
12
- "PNLight.xcframework",
13
- "PNLightSDK-ReactNative.podspec"
14
- ],
15
- "keywords": [
16
- "pnlight",
17
- "analytics",
18
- "in-app-purchase",
19
- "ios"
20
- ],
21
- "author": "PNLight",
22
- "license": "Commercial",
23
- "scripts": {
24
- "prepare": "node ./scripts/copy-xcframework.js",
25
- "publish:dry": "npm pack",
26
- "publish:real": "node ./scripts/publish_npm.js"
2
+ "name": "@pnlight/sdk-react-native",
3
+ "version": "0.4.1",
4
+ "description": "React Native wrapper for PNLight iOS binary SDK",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "react-native": "index.js",
8
+ "files": [
9
+ "index.js",
10
+ "index.d.ts",
11
+ "ios",
12
+ "PNLight.xcframework",
13
+ "PNLightSDK-ReactNative.podspec",
14
+ "RemoteUiView.js",
15
+ "app.plugin.js",
16
+ "plugin"
17
+ ],
18
+ "keywords": [
19
+ "pnlight",
20
+ "analytics",
21
+ "in-app-purchase",
22
+ "ios",
23
+ "expo"
24
+ ],
25
+ "author": "PNLight",
26
+ "license": "Commercial",
27
+ "peerDependencies": {
28
+ "react-native": "*",
29
+ "expo": "*"
30
+ },
31
+ "peerDependenciesMeta": {
32
+ "expo": {
33
+ "optional": true
27
34
  }
28
- }
35
+ },
36
+ "scripts": {
37
+ "prepare": "node ./scripts/copy-xcframework.js",
38
+ "publish:dry": "npm pack",
39
+ "publish:real": "node ./scripts/publish_npm.js"
40
+ }
41
+ }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+
3
+ const { withIosPodfileSource } = require("./withIos");
4
+
5
+ /**
6
+ * Expo config plugin for @pnlight/sdk-react-native.
7
+ * Configures the iOS Podfile with the DivKit source required by RemoteUiView.
8
+ *
9
+ * Usage in app.json / app.config.js:
10
+ * "plugins": ["@pnlight/sdk-react-native"]
11
+ */
12
+ function withPNLightSDK(config) {
13
+ config = withIosPodfileSource(config);
14
+ return config;
15
+ }
16
+
17
+ module.exports = withPNLightSDK;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+
3
+ const { withDangerousMod } = require("expo/config-plugins");
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+
7
+ const DIVKIT_SOURCE = "source 'https://github.com/divkit/divkit-ios.git'";
8
+ const DIVKIT_MARKER = "divkit-ios";
9
+
10
+ /**
11
+ * Adds DivKit CocoaPods source to the Podfile (required by PNLightSDK-ReactNative for RemoteUiView).
12
+ * Idempotent: safe to run prebuild multiple times.
13
+ */
14
+ function withIosPodfileSource(config) {
15
+ return withDangerousMod(config, [
16
+ "ios",
17
+ async (config) => {
18
+ const podfilePath = path.join(
19
+ config.modRequest.platformProjectRoot,
20
+ "Podfile"
21
+ );
22
+ let contents = await fs.promises.readFile(podfilePath, "utf8");
23
+
24
+ if (contents.includes(DIVKIT_MARKER)) {
25
+ return config;
26
+ }
27
+
28
+ const lines = contents.split("\n");
29
+ const insertIndex =
30
+ lines.findIndex((line) => /^\s*source\s+['\"]/.test(line)) + 1;
31
+ const idx = insertIndex > 0 ? insertIndex : 1;
32
+ lines.splice(idx, 0, DIVKIT_SOURCE);
33
+ await fs.promises.writeFile(podfilePath, lines.join("\n"));
34
+ return config;
35
+ },
36
+ ]);
37
+ }
38
+
39
+ module.exports = withIosPodfileSource;