@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.
- package/PNLight.xcframework/Info.plist +5 -5
- package/PNLight.xcframework/ios-arm64/PNLight.framework/Headers/PNLight-Swift.h +2 -0
- package/PNLight.xcframework/ios-arm64/PNLight.framework/Modules/PNLight.swiftmodule/Project/arm64-apple-ios.swiftsourceinfo +0 -0
- package/PNLight.xcframework/ios-arm64/PNLight.framework/Modules/PNLight.swiftmodule/arm64-apple-ios.abi.json +1019 -47
- package/PNLight.xcframework/ios-arm64/PNLight.framework/Modules/PNLight.swiftmodule/arm64-apple-ios.private.swiftinterface +35 -1
- package/PNLight.xcframework/ios-arm64/PNLight.framework/Modules/PNLight.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
- package/PNLight.xcframework/ios-arm64/PNLight.framework/Modules/PNLight.swiftmodule/arm64-apple-ios.swiftinterface +35 -1
- package/PNLight.xcframework/ios-arm64/PNLight.framework/PNLight +0 -0
- package/PNLight.xcframework/ios-arm64_x86_64-simulator/PNLight.framework/Headers/PNLight-Swift.h +4 -0
- package/PNLight.xcframework/ios-arm64_x86_64-simulator/PNLight.framework/Modules/PNLight.swiftmodule/Project/arm64-apple-ios-simulator.swiftsourceinfo +0 -0
- package/PNLight.xcframework/ios-arm64_x86_64-simulator/PNLight.framework/Modules/PNLight.swiftmodule/Project/x86_64-apple-ios-simulator.swiftsourceinfo +0 -0
- package/PNLight.xcframework/ios-arm64_x86_64-simulator/PNLight.framework/Modules/PNLight.swiftmodule/arm64-apple-ios-simulator.abi.json +1019 -47
- package/PNLight.xcframework/ios-arm64_x86_64-simulator/PNLight.framework/Modules/PNLight.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +35 -1
- package/PNLight.xcframework/ios-arm64_x86_64-simulator/PNLight.framework/Modules/PNLight.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
- package/PNLight.xcframework/ios-arm64_x86_64-simulator/PNLight.framework/Modules/PNLight.swiftmodule/arm64-apple-ios-simulator.swiftinterface +35 -1
- package/PNLight.xcframework/ios-arm64_x86_64-simulator/PNLight.framework/Modules/PNLight.swiftmodule/x86_64-apple-ios-simulator.abi.json +1019 -47
- package/PNLight.xcframework/ios-arm64_x86_64-simulator/PNLight.framework/Modules/PNLight.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +35 -1
- package/PNLight.xcframework/ios-arm64_x86_64-simulator/PNLight.framework/Modules/PNLight.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
- package/PNLight.xcframework/ios-arm64_x86_64-simulator/PNLight.framework/Modules/PNLight.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +35 -1
- package/PNLight.xcframework/ios-arm64_x86_64-simulator/PNLight.framework/PNLight +0 -0
- package/PNLightSDK-ReactNative.podspec +5 -2
- package/RemoteUiView.js +43 -0
- package/app.plugin.js +3 -0
- package/index.d.ts +34 -1
- package/index.js +18 -2
- package/ios/PNLightSDK.m +2 -0
- package/ios/PNLightSDK.swift +43 -2
- package/ios/RemoteUiView.swift +161 -0
- package/ios/RemoteUiViewManager.m +6 -0
- package/ios/RemoteUiViewManager.swift +18 -0
- package/package.json +39 -26
- package/plugin/index.js +17 -0
- package/plugin/withIos.js +39 -0
- 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 {}
|
|
Binary file
|
|
@@ -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 {}
|
|
Binary file
|
|
@@ -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.
|
|
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
|
package/RemoteUiView.js
ADDED
|
@@ -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
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
|
|
package/ios/PNLightSDK.swift
CHANGED
|
@@ -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
|
-
|
|
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,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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
+
}
|
package/plugin/index.js
ADDED
|
@@ -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;
|