@pnlight/sdk-react-native 0.4.0 → 0.4.2
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.d.ts +62 -0
- package/RemoteUiView.js +58 -0
- package/app.plugin.js +3 -0
- package/ios/PNLightSDK.m +4 -1
- package/ios/PNLightSDK.swift +2 -2
- package/ios/RemoteUiView.swift +209 -17
- package/ios/RemoteUiViewManager.m +1 -0
- package/package.json +17 -3
- package/plugin/index.js +17 -0
- package/plugin/withIos.js +39 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { StyleProp, ViewStyle } from "react-native";
|
|
2
|
+
|
|
3
|
+
export interface ActionEvent {
|
|
4
|
+
/**
|
|
5
|
+
* Full URL from the action (e.g., "myapp://button-clicked?id=primary")
|
|
6
|
+
* Note: Only custom URL schemes are passed to this callback.
|
|
7
|
+
* Standard http/https URLs are handled automatically by DivKit.
|
|
8
|
+
*/
|
|
9
|
+
url: string;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* URL scheme (e.g., "myapp", "app")
|
|
13
|
+
* Note: Will never be "http" or "https" as those are handled automatically.
|
|
14
|
+
*/
|
|
15
|
+
scheme: string;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* URL path (e.g., "/button-clicked")
|
|
19
|
+
*/
|
|
20
|
+
path: string;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Parsed query parameters as key-value pairs
|
|
24
|
+
*/
|
|
25
|
+
params: { [key: string]: string };
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* DivKit log ID (if available)
|
|
29
|
+
*/
|
|
30
|
+
logId?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface RemoteUiViewProps {
|
|
34
|
+
/**
|
|
35
|
+
* Placement ID passed to getUIConfig(placement)
|
|
36
|
+
*/
|
|
37
|
+
placement: string;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Optional style for the container
|
|
41
|
+
*/
|
|
42
|
+
style?: StyleProp<ViewStyle>;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Optional DivKit card ID (defaults to placement-based ID)
|
|
46
|
+
*/
|
|
47
|
+
cardId?: string;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Callback for handling button clicks with custom URL schemes.
|
|
51
|
+
* Only called for custom schemes (myapp://, app://, etc.).
|
|
52
|
+
* Standard http/https URLs are handled automatically by DivKit.
|
|
53
|
+
* @param event - Action event containing URL, scheme, path, params, and logId
|
|
54
|
+
*/
|
|
55
|
+
onAction?: (event: ActionEvent) => void;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Remote UI view: fetches UI config for the given placement via getUIConfig,
|
|
60
|
+
* shows nothing while loading, then renders the config in a native DivKit view.
|
|
61
|
+
*/
|
|
62
|
+
export function RemoteUiView(props: RemoteUiViewProps): JSX.Element;
|
package/RemoteUiView.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const React = require("react");
|
|
4
|
+
const { requireNativeComponent } = require("react-native");
|
|
5
|
+
|
|
6
|
+
const NativeRemoteUiView = requireNativeComponent("RemoteUiView");
|
|
7
|
+
|
|
8
|
+
async function getUIConfig(placement) {
|
|
9
|
+
return await NativeRemoteUiView.getUIConfig(placement);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Remote UI view: fetches UI config for the given placement via getUIConfig,
|
|
14
|
+
* shows nothing while loading, then renders the config in a native DivKit view.
|
|
15
|
+
*
|
|
16
|
+
* @param {object} props
|
|
17
|
+
* @param {string} props.placement - Placement id passed to getUIConfig(placement)
|
|
18
|
+
* @param {object} props.style - Optional style for the container
|
|
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
|
|
21
|
+
*/
|
|
22
|
+
function RemoteUiView({ placement, style, cardId, onAction }) {
|
|
23
|
+
const [config, setConfig] = React.useState(null);
|
|
24
|
+
const cardIdToUse =
|
|
25
|
+
cardId ?? (placement ? `pnlight_${placement}` : "pnlight_card");
|
|
26
|
+
|
|
27
|
+
React.useEffect(() => {
|
|
28
|
+
if (!placement) return;
|
|
29
|
+
let cancelled = false;
|
|
30
|
+
getUIConfig(placement).then((uiConfig) => {
|
|
31
|
+
if (cancelled) return;
|
|
32
|
+
setConfig(uiConfig?.config ?? null);
|
|
33
|
+
});
|
|
34
|
+
return () => {
|
|
35
|
+
cancelled = true;
|
|
36
|
+
};
|
|
37
|
+
}, [placement]);
|
|
38
|
+
|
|
39
|
+
const handleAction = React.useCallback(
|
|
40
|
+
(event) => {
|
|
41
|
+
if (onAction) {
|
|
42
|
+
onAction(event.nativeEvent);
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
[onAction],
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<NativeRemoteUiView
|
|
50
|
+
style={[{ minHeight: 1, flex: 1 }, style]}
|
|
51
|
+
config={config}
|
|
52
|
+
cardId={cardIdToUse}
|
|
53
|
+
onAction={handleAction}
|
|
54
|
+
/>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = { RemoteUiView };
|
package/app.plugin.js
ADDED
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
|
package/ios/PNLightSDK.swift
CHANGED
|
@@ -26,12 +26,12 @@ class PNLightRNModule: NSObject {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
@objc func getUserId(
|
|
29
|
+
@objc func getUserId(resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
30
30
|
let userId = PNLightSDK.shared.getOrCreateUserId().id
|
|
31
31
|
resolve(userId)
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
@objc func resetUserId(
|
|
34
|
+
@objc func resetUserId(resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
35
35
|
PNLightSDK.shared.resetUserId()
|
|
36
36
|
resolve(nil)
|
|
37
37
|
}
|
package/ios/RemoteUiView.swift
CHANGED
|
@@ -1,45 +1,237 @@
|
|
|
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 nothing until config is set; then renders via DivKit per https://divkit.tech/docs/en/quickstart/ios
|
|
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
|
|
42
|
+
private let divKitComponents: DivKitComponents
|
|
43
|
+
private let urlHandler: RemoteUiUrlHandler
|
|
44
|
+
|
|
11
45
|
private var currentCardId: String = "pnlight_card"
|
|
12
46
|
|
|
47
|
+
private let loadingIndicator: UIActivityIndicatorView
|
|
48
|
+
private let errorLabel: UILabel
|
|
49
|
+
private var secureContainer: UIView?
|
|
50
|
+
|
|
51
|
+
@objc var onAction: (([String: Any]) -> Void)?
|
|
52
|
+
|
|
13
53
|
override init(frame: CGRect) {
|
|
14
|
-
self.
|
|
54
|
+
self.loadingIndicator = UIActivityIndicatorView(style: .large)
|
|
55
|
+
self.errorLabel = UILabel()
|
|
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
|
+
|
|
15
64
|
super.init(frame: frame)
|
|
65
|
+
|
|
66
|
+
// bind back-reference
|
|
67
|
+
self.urlHandler.owner = self
|
|
68
|
+
|
|
69
|
+
backgroundColor = .clear
|
|
70
|
+
|
|
16
71
|
divView.translatesAutoresizingMaskIntoConstraints = false
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
72
|
+
divView.alpha = 0
|
|
73
|
+
divView.backgroundColor = .clear
|
|
74
|
+
|
|
75
|
+
loadingIndicator.translatesAutoresizingMaskIntoConstraints = false
|
|
76
|
+
loadingIndicator.hidesWhenStopped = false
|
|
77
|
+
|
|
78
|
+
errorLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
79
|
+
errorLabel.text = "Failed to load DivKit content"
|
|
80
|
+
errorLabel.textColor = .red
|
|
81
|
+
errorLabel.textAlignment = .center
|
|
82
|
+
errorLabel.numberOfLines = 0
|
|
83
|
+
errorLabel.alpha = 0
|
|
84
|
+
|
|
85
|
+
let isSecure = true
|
|
86
|
+
if isSecure, let secure = Self.makeSecureContainer() {
|
|
87
|
+
secure.translatesAutoresizingMaskIntoConstraints = false
|
|
88
|
+
self.secureContainer = secure
|
|
89
|
+
|
|
90
|
+
secure.addSubview(divView)
|
|
91
|
+
secure.addSubview(loadingIndicator)
|
|
92
|
+
secure.addSubview(errorLabel)
|
|
93
|
+
addSubview(secure)
|
|
94
|
+
|
|
95
|
+
NSLayoutConstraint.activate([
|
|
96
|
+
secure.topAnchor.constraint(equalTo: topAnchor),
|
|
97
|
+
secure.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
98
|
+
secure.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
99
|
+
secure.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
100
|
+
|
|
101
|
+
divView.topAnchor.constraint(equalTo: secure.topAnchor),
|
|
102
|
+
divView.leadingAnchor.constraint(equalTo: secure.leadingAnchor),
|
|
103
|
+
divView.trailingAnchor.constraint(equalTo: secure.trailingAnchor),
|
|
104
|
+
divView.bottomAnchor.constraint(equalTo: secure.bottomAnchor),
|
|
105
|
+
|
|
106
|
+
loadingIndicator.centerXAnchor.constraint(equalTo: secure.centerXAnchor),
|
|
107
|
+
loadingIndicator.centerYAnchor.constraint(equalTo: secure.centerYAnchor),
|
|
108
|
+
|
|
109
|
+
errorLabel.topAnchor.constraint(equalTo: secure.topAnchor),
|
|
110
|
+
errorLabel.leadingAnchor.constraint(equalTo: secure.leadingAnchor),
|
|
111
|
+
errorLabel.trailingAnchor.constraint(equalTo: secure.trailingAnchor),
|
|
112
|
+
errorLabel.bottomAnchor.constraint(equalTo: secure.bottomAnchor),
|
|
113
|
+
])
|
|
114
|
+
} else {
|
|
115
|
+
addSubview(divView)
|
|
116
|
+
addSubview(loadingIndicator)
|
|
117
|
+
addSubview(errorLabel)
|
|
118
|
+
|
|
119
|
+
NSLayoutConstraint.activate([
|
|
120
|
+
divView.topAnchor.constraint(equalTo: topAnchor),
|
|
121
|
+
divView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
122
|
+
divView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
123
|
+
divView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
124
|
+
|
|
125
|
+
loadingIndicator.centerXAnchor.constraint(equalTo: centerXAnchor),
|
|
126
|
+
loadingIndicator.centerYAnchor.constraint(equalTo: centerYAnchor),
|
|
127
|
+
|
|
128
|
+
errorLabel.topAnchor.constraint(equalTo: topAnchor),
|
|
129
|
+
errorLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
130
|
+
errorLabel.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
131
|
+
errorLabel.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
132
|
+
])
|
|
133
|
+
}
|
|
24
134
|
}
|
|
25
135
|
|
|
26
136
|
required init?(coder: NSCoder) {
|
|
27
137
|
fatalError("init(coder:) has not been implemented")
|
|
28
138
|
}
|
|
29
139
|
|
|
30
|
-
|
|
140
|
+
private static func makeSecureContainer() -> UIView? {
|
|
141
|
+
let textField = UITextField()
|
|
142
|
+
textField.isSecureTextEntry = true
|
|
143
|
+
textField.isUserInteractionEnabled = false
|
|
144
|
+
|
|
145
|
+
guard let secureLayer = textField.layer.sublayers?.first,
|
|
146
|
+
let secureView = secureLayer.delegate as? UIView else {
|
|
147
|
+
return nil
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
secureView.subviews.forEach { $0.removeFromSuperview() }
|
|
151
|
+
secureView.isUserInteractionEnabled = true
|
|
152
|
+
secureView.backgroundColor = .clear
|
|
153
|
+
return secureView
|
|
154
|
+
}
|
|
155
|
+
|
|
31
156
|
private func applyConfig(configJson: String?, cardId: String) {
|
|
32
|
-
guard let configJson
|
|
157
|
+
guard let configJson, !configJson.isEmpty,
|
|
33
158
|
let jsonData = configJson.data(using: .utf8) else {
|
|
34
159
|
return
|
|
35
160
|
}
|
|
36
|
-
|
|
161
|
+
|
|
162
|
+
errorLabel.alpha = 0
|
|
163
|
+
errorLabel.text = "Failed to load DivKit content"
|
|
164
|
+
loadingIndicator.startAnimating()
|
|
165
|
+
loadingIndicator.alpha = 1
|
|
166
|
+
divView.alpha = 0
|
|
167
|
+
|
|
168
|
+
let source = DivViewSource(
|
|
169
|
+
kind: .data(jsonData),
|
|
170
|
+
cardId: DivCardID(rawValue: cardId) ?? "divkit"
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
Task { @MainActor in
|
|
174
|
+
do {
|
|
175
|
+
try await self.divView.setSource(source)
|
|
176
|
+
self.showContent()
|
|
177
|
+
} catch {
|
|
178
|
+
self.showError(error: error)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private func showContent() {
|
|
184
|
+
loadingIndicator.stopAnimating()
|
|
185
|
+
UIView.animate(withDuration: 0.3) {
|
|
186
|
+
self.loadingIndicator.alpha = 0
|
|
187
|
+
self.divView.alpha = 1
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private func showError(error: Error) {
|
|
192
|
+
errorLabel.text = "Failed to load DivKit content:\n\(error.localizedDescription)"
|
|
193
|
+
loadingIndicator.stopAnimating()
|
|
194
|
+
UIView.animate(withDuration: 0.3) {
|
|
195
|
+
self.loadingIndicator.alpha = 0
|
|
196
|
+
self.errorLabel.alpha = 1
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
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)
|
|
37
230
|
}
|
|
38
231
|
|
|
39
|
-
// MARK: - React Native
|
|
232
|
+
// MARK: - React Native props
|
|
40
233
|
@objc func setConfig(_ value: NSString?) {
|
|
41
|
-
|
|
42
|
-
applyConfig(configJson: json, cardId: currentCardId)
|
|
234
|
+
applyConfig(configJson: value as String?, cardId: currentCardId)
|
|
43
235
|
}
|
|
44
236
|
|
|
45
237
|
@objc func setCardId(_ value: NSString?) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pnlight/sdk-react-native",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "React Native wrapper for PNLight iOS binary SDK",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -10,16 +10,30 @@
|
|
|
10
10
|
"index.d.ts",
|
|
11
11
|
"ios",
|
|
12
12
|
"PNLight.xcframework",
|
|
13
|
-
"PNLightSDK-ReactNative.podspec"
|
|
13
|
+
"PNLightSDK-ReactNative.podspec",
|
|
14
|
+
"RemoteUiView.js",
|
|
15
|
+
"RemoteUiView.d.ts",
|
|
16
|
+
"app.plugin.js",
|
|
17
|
+
"plugin"
|
|
14
18
|
],
|
|
15
19
|
"keywords": [
|
|
16
20
|
"pnlight",
|
|
17
21
|
"analytics",
|
|
18
22
|
"in-app-purchase",
|
|
19
|
-
"ios"
|
|
23
|
+
"ios",
|
|
24
|
+
"expo"
|
|
20
25
|
],
|
|
21
26
|
"author": "PNLight",
|
|
22
27
|
"license": "Commercial",
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"react-native": "*",
|
|
30
|
+
"expo": "*"
|
|
31
|
+
},
|
|
32
|
+
"peerDependenciesMeta": {
|
|
33
|
+
"expo": {
|
|
34
|
+
"optional": true
|
|
35
|
+
}
|
|
36
|
+
},
|
|
23
37
|
"scripts": {
|
|
24
38
|
"prepare": "node ./scripts/copy-xcframework.js",
|
|
25
39
|
"publish:dry": "npm pack",
|
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;
|