@virex-tech/paywallo-sdk 1.0.0
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/LICENSE +21 -0
- package/MonetySdk.podspec +19 -0
- package/README.md +164 -0
- package/android/build.gradle +54 -0
- package/android/src/main/java/com/monety/sdk/MonetySdkPackage.kt +16 -0
- package/android/src/main/java/com/monety/sdk/PanelStoreKitModule.kt +315 -0
- package/android/src/main/java/com/monety/sdk/PanelWebView.kt +74 -0
- package/android/src/main/java/com/monety/sdk/PanelWebViewManager.kt +52 -0
- package/android/src/main/java/com/paywallo/sdk/PaywalloSdkPackage.kt +16 -0
- package/android/src/main/java/com/paywallo/sdk/PaywalloStoreKitModule.kt +315 -0
- package/android/src/main/java/com/paywallo/sdk/PaywalloWebView.kt +74 -0
- package/android/src/main/java/com/paywallo/sdk/PaywalloWebViewManager.kt +52 -0
- package/dist/index.d.mts +1141 -0
- package/dist/index.d.ts +1141 -0
- package/dist/index.js +4793 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +4709 -0
- package/dist/index.mjs.map +1 -0
- package/ios/PanelStoreKit.m +21 -0
- package/ios/PanelStoreKit.swift +245 -0
- package/ios/PanelWebView.h +15 -0
- package/ios/PanelWebView.m +182 -0
- package/ios/PanelWebViewManager.m +41 -0
- package/ios/PanelWebViewModule.h +8 -0
- package/ios/PanelWebViewModule.m +42 -0
- package/ios/PaywalloStoreKit.m +21 -0
- package/ios/PaywalloStoreKit.swift +245 -0
- package/ios/PaywalloWebView.h +15 -0
- package/ios/PaywalloWebView.m +182 -0
- package/ios/PaywalloWebViewManager.m +41 -0
- package/ios/PaywalloWebViewModule.h +8 -0
- package/ios/PaywalloWebViewModule.m +42 -0
- package/package.json +93 -0
- package/react-native.config.js +14 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import StoreKit
|
|
3
|
+
|
|
4
|
+
// React Native bridge types — avoids need for bridging header
|
|
5
|
+
typealias RCTPromiseResolveBlock = @convention(block) (Any?) -> Void
|
|
6
|
+
typealias RCTPromiseRejectBlock = @convention(block) (String?, String?, Error?) -> Void
|
|
7
|
+
|
|
8
|
+
@available(iOS 15.0, *)
|
|
9
|
+
@objc(PaywalloStoreKit)
|
|
10
|
+
class PaywalloStoreKit: NSObject {
|
|
11
|
+
|
|
12
|
+
private var updateListenerTask: Task<Void, Never>?
|
|
13
|
+
|
|
14
|
+
override init() {
|
|
15
|
+
super.init()
|
|
16
|
+
startTransactionListener()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
deinit {
|
|
20
|
+
updateListenerTask?.cancel()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// MARK: - Transaction Listener
|
|
24
|
+
|
|
25
|
+
private func startTransactionListener() {
|
|
26
|
+
updateListenerTask = Task { [weak self] in
|
|
27
|
+
for await _ in Transaction.updates {
|
|
28
|
+
guard let _ = self else { return }
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// MARK: - Get Products
|
|
34
|
+
|
|
35
|
+
@objc
|
|
36
|
+
func getProducts(
|
|
37
|
+
_ productIds: [String],
|
|
38
|
+
resolve: @escaping RCTPromiseResolveBlock,
|
|
39
|
+
reject: @escaping RCTPromiseRejectBlock
|
|
40
|
+
) {
|
|
41
|
+
Task {
|
|
42
|
+
do {
|
|
43
|
+
let products = try await Product.products(for: Set(productIds))
|
|
44
|
+
let result = products.map { self.productToDict($0) }
|
|
45
|
+
resolve(result)
|
|
46
|
+
} catch {
|
|
47
|
+
reject("PRODUCTS_ERROR", "Failed to load products: \(error.localizedDescription)", error)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// MARK: - Purchase
|
|
53
|
+
|
|
54
|
+
@objc
|
|
55
|
+
func purchase(
|
|
56
|
+
_ productId: String,
|
|
57
|
+
appAccountToken: String?,
|
|
58
|
+
resolve: @escaping RCTPromiseResolveBlock,
|
|
59
|
+
reject: @escaping RCTPromiseRejectBlock
|
|
60
|
+
) {
|
|
61
|
+
Task {
|
|
62
|
+
do {
|
|
63
|
+
let products = try await Product.products(for: [productId])
|
|
64
|
+
guard let product = products.first else {
|
|
65
|
+
reject("PRODUCT_NOT_FOUND", "Product \(productId) not found", nil)
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
var options = Set<Product.PurchaseOption>()
|
|
70
|
+
if let tokenStr = appAccountToken, let uuid = UUID(uuidString: tokenStr) {
|
|
71
|
+
options.insert(.appAccountToken(uuid))
|
|
72
|
+
}
|
|
73
|
+
let result = try await product.purchase(options: options)
|
|
74
|
+
|
|
75
|
+
switch result {
|
|
76
|
+
case .success(let verification):
|
|
77
|
+
switch verification {
|
|
78
|
+
case .verified(let transaction):
|
|
79
|
+
let purchaseResult: [String: Any] = [
|
|
80
|
+
"transactionId": String(transaction.id),
|
|
81
|
+
"productId": transaction.productID,
|
|
82
|
+
"receipt": verification.jwsRepresentation,
|
|
83
|
+
"transactionDate": Int(transaction.purchaseDate.timeIntervalSince1970 * 1000),
|
|
84
|
+
]
|
|
85
|
+
resolve(purchaseResult)
|
|
86
|
+
case .unverified(_, let error):
|
|
87
|
+
reject(
|
|
88
|
+
"VERIFICATION_FAILED",
|
|
89
|
+
"Transaction verification failed: \(error.localizedDescription)", error)
|
|
90
|
+
}
|
|
91
|
+
case .userCancelled:
|
|
92
|
+
resolve(nil)
|
|
93
|
+
case .pending:
|
|
94
|
+
resolve(["pending": true])
|
|
95
|
+
@unknown default:
|
|
96
|
+
reject("UNKNOWN_RESULT", "Unknown purchase result", nil)
|
|
97
|
+
}
|
|
98
|
+
} catch {
|
|
99
|
+
reject("PURCHASE_ERROR", "Purchase failed: \(error.localizedDescription)", error)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// MARK: - Finish Transaction
|
|
105
|
+
|
|
106
|
+
@objc
|
|
107
|
+
func finishTransaction(
|
|
108
|
+
_ transactionIdStr: String,
|
|
109
|
+
resolve: @escaping RCTPromiseResolveBlock,
|
|
110
|
+
reject: @escaping RCTPromiseRejectBlock
|
|
111
|
+
) {
|
|
112
|
+
Task {
|
|
113
|
+
guard let transactionId = UInt64(transactionIdStr) else {
|
|
114
|
+
reject("INVALID_ID", "Invalid transaction ID", nil)
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
for await result in Transaction.currentEntitlements {
|
|
119
|
+
guard case .verified(let transaction) = result else { continue }
|
|
120
|
+
if transaction.id == transactionId {
|
|
121
|
+
await transaction.finish()
|
|
122
|
+
resolve(nil)
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
for await result in Transaction.unfinished {
|
|
128
|
+
guard case .verified(let transaction) = result else { continue }
|
|
129
|
+
if transaction.id == transactionId {
|
|
130
|
+
await transaction.finish()
|
|
131
|
+
resolve(nil)
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Already finished or not found — success either way
|
|
137
|
+
resolve(nil)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// MARK: - Get Active Transactions
|
|
142
|
+
|
|
143
|
+
@objc
|
|
144
|
+
func getActiveTransactions(
|
|
145
|
+
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
146
|
+
reject: @escaping RCTPromiseRejectBlock
|
|
147
|
+
) {
|
|
148
|
+
Task {
|
|
149
|
+
var transactions: [[String: Any]] = []
|
|
150
|
+
|
|
151
|
+
for await result in Transaction.currentEntitlements {
|
|
152
|
+
guard case .verified(let transaction) = result else { continue }
|
|
153
|
+
|
|
154
|
+
let dict: [String: Any] = [
|
|
155
|
+
"transactionId": String(transaction.id),
|
|
156
|
+
"productId": transaction.productID,
|
|
157
|
+
"receipt": result.jwsRepresentation,
|
|
158
|
+
"transactionDate": Int(transaction.purchaseDate.timeIntervalSince1970 * 1000),
|
|
159
|
+
]
|
|
160
|
+
transactions.append(dict)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
resolve(transactions)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// MARK: - Helpers
|
|
168
|
+
|
|
169
|
+
private func productToDict(_ product: Product) -> [String: Any] {
|
|
170
|
+
var dict: [String: Any] = [
|
|
171
|
+
"productId": product.id,
|
|
172
|
+
"title": product.displayName,
|
|
173
|
+
"description": product.description,
|
|
174
|
+
"price": product.price.description,
|
|
175
|
+
"priceValue": NSDecimalNumber(decimal: product.price).doubleValue,
|
|
176
|
+
"localizedPrice": product.displayPrice,
|
|
177
|
+
"type": productTypeString(product.type),
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
// Extract currency from jsonRepresentation
|
|
181
|
+
if let json = try? JSONSerialization.jsonObject(with: product.jsonRepresentation) as? [String: Any] {
|
|
182
|
+
// Try to extract currency from the product JSON
|
|
183
|
+
if let attrs = json["attributes"] as? [String: Any],
|
|
184
|
+
let offers = attrs["offers"] as? [[String: Any]],
|
|
185
|
+
let firstOffer = offers.first,
|
|
186
|
+
let currencyCode = firstOffer["currencyCode"] as? String {
|
|
187
|
+
dict["currency"] = currencyCode
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Fallback currency from locale
|
|
192
|
+
if dict["currency"] == nil {
|
|
193
|
+
if #available(iOS 16, *) {
|
|
194
|
+
dict["currency"] = Locale.current.currency?.identifier ?? "USD"
|
|
195
|
+
} else {
|
|
196
|
+
dict["currency"] = Locale.current.currencyCode ?? "USD"
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if let sub = product.subscription {
|
|
201
|
+
dict["subscriptionPeriod"] = formatPeriod(sub.subscriptionPeriod)
|
|
202
|
+
|
|
203
|
+
if let introOffer = sub.introductoryOffer {
|
|
204
|
+
if introOffer.paymentMode == .freeTrial {
|
|
205
|
+
dict["freeTrialPeriod"] = formatPeriod(introOffer.period)
|
|
206
|
+
} else {
|
|
207
|
+
dict["introductoryPrice"] = introOffer.displayPrice
|
|
208
|
+
dict["introductoryPriceValue"] = NSDecimalNumber(decimal: introOffer.price).doubleValue
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return dict
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private func productTypeString(_ type: Product.ProductType) -> String {
|
|
217
|
+
switch type {
|
|
218
|
+
case .autoRenewable: return "subscription"
|
|
219
|
+
case .consumable: return "consumable"
|
|
220
|
+
case .nonConsumable: return "non_consumable"
|
|
221
|
+
case .nonRenewable: return "non_renewable"
|
|
222
|
+
default: return "unknown"
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private func formatPeriod(_ period: Product.SubscriptionPeriod) -> String {
|
|
227
|
+
switch period.unit {
|
|
228
|
+
case .day:
|
|
229
|
+
return period.value == 7 ? "P1W" : "P\(period.value)D"
|
|
230
|
+
case .week:
|
|
231
|
+
return "P\(period.value)W"
|
|
232
|
+
case .month:
|
|
233
|
+
return "P\(period.value)M"
|
|
234
|
+
case .year:
|
|
235
|
+
return "P\(period.value)Y"
|
|
236
|
+
@unknown default:
|
|
237
|
+
return "P\(period.value)D"
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
@objc
|
|
242
|
+
static func requiresMainQueueSetup() -> Bool {
|
|
243
|
+
return false
|
|
244
|
+
}
|
|
245
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#import <UIKit/UIKit.h>
|
|
2
|
+
#import <WebKit/WebKit.h>
|
|
3
|
+
#import <React/RCTComponent.h>
|
|
4
|
+
|
|
5
|
+
@interface PaywalloWebView : UIView
|
|
6
|
+
|
|
7
|
+
@property (nonatomic, copy) NSString *sourceUrl;
|
|
8
|
+
@property (nonatomic, copy) NSString *sourceHtml;
|
|
9
|
+
@property (nonatomic, copy) NSString *injectedJavaScript;
|
|
10
|
+
@property (nonatomic, copy) RCTDirectEventBlock onMessage;
|
|
11
|
+
@property (nonatomic, copy) RCTDirectEventBlock onLoadEnd;
|
|
12
|
+
|
|
13
|
+
- (void)injectJavaScript:(NSString *)script;
|
|
14
|
+
|
|
15
|
+
@end
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
#import "PaywalloWebView.h"
|
|
2
|
+
#import "PaywalloWebViewModule.h"
|
|
3
|
+
#import <AVFoundation/AVFoundation.h>
|
|
4
|
+
|
|
5
|
+
@interface PaywalloWebView () <WKNavigationDelegate, WKScriptMessageHandler>
|
|
6
|
+
|
|
7
|
+
@property (nonatomic, strong) WKWebView *webView;
|
|
8
|
+
@property (nonatomic, assign) BOOL navigationFinished;
|
|
9
|
+
@property (nonatomic, copy) NSString *pendingInjectedJS;
|
|
10
|
+
|
|
11
|
+
@end
|
|
12
|
+
|
|
13
|
+
@implementation PaywalloWebView
|
|
14
|
+
|
|
15
|
+
- (instancetype)initWithFrame:(CGRect)frame
|
|
16
|
+
{
|
|
17
|
+
self = [super initWithFrame:frame];
|
|
18
|
+
if (self) {
|
|
19
|
+
_navigationFinished = NO;
|
|
20
|
+
[self setupWebView];
|
|
21
|
+
}
|
|
22
|
+
return self;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
- (void)setupWebView
|
|
26
|
+
{
|
|
27
|
+
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
|
|
28
|
+
WKUserContentController *contentController = [[WKUserContentController alloc] init];
|
|
29
|
+
[contentController addScriptMessageHandler:self name:@"ReactNativeWebView"];
|
|
30
|
+
|
|
31
|
+
NSString *injectionScript = @"window.ReactNativeWebView = { postMessage: function(data) { "
|
|
32
|
+
@"try { "
|
|
33
|
+
@" if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.ReactNativeWebView) { "
|
|
34
|
+
@" window.webkit.messageHandlers.ReactNativeWebView.postMessage(data); "
|
|
35
|
+
@" return; "
|
|
36
|
+
@" } "
|
|
37
|
+
@"} catch(e) {} "
|
|
38
|
+
@"var iframe = document.createElement('iframe'); "
|
|
39
|
+
@"iframe.style.display = 'none'; "
|
|
40
|
+
@"iframe.src = 'paywallobridge://message?' + encodeURIComponent(data); "
|
|
41
|
+
@"document.body.appendChild(iframe); "
|
|
42
|
+
@"setTimeout(function() { if (iframe.parentNode) iframe.parentNode.removeChild(iframe); }, 100); "
|
|
43
|
+
@"} };";
|
|
44
|
+
WKUserScript *userScript = [[WKUserScript alloc] initWithSource:injectionScript
|
|
45
|
+
injectionTime:WKUserScriptInjectionTimeAtDocumentStart
|
|
46
|
+
forMainFrameOnly:YES];
|
|
47
|
+
[contentController addUserScript:userScript];
|
|
48
|
+
|
|
49
|
+
config.userContentController = contentController;
|
|
50
|
+
config.allowsInlineMediaPlayback = YES;
|
|
51
|
+
config.mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeNone;
|
|
52
|
+
|
|
53
|
+
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback
|
|
54
|
+
withOptions:AVAudioSessionCategoryOptionMixWithOthers
|
|
55
|
+
error:nil];
|
|
56
|
+
|
|
57
|
+
_webView = [[WKWebView alloc] initWithFrame:self.bounds configuration:config];
|
|
58
|
+
_webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
59
|
+
_webView.navigationDelegate = self;
|
|
60
|
+
_webView.scrollView.bounces = NO;
|
|
61
|
+
_webView.scrollView.scrollEnabled = NO;
|
|
62
|
+
_webView.scrollView.minimumZoomScale = 1.0;
|
|
63
|
+
_webView.scrollView.maximumZoomScale = 1.0;
|
|
64
|
+
_webView.opaque = NO;
|
|
65
|
+
_webView.backgroundColor = [UIColor whiteColor];
|
|
66
|
+
self.backgroundColor = [UIColor whiteColor];
|
|
67
|
+
|
|
68
|
+
[self addSubview:_webView];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
- (void)setSourceUrl:(NSString *)sourceUrl
|
|
72
|
+
{
|
|
73
|
+
_sourceUrl = [sourceUrl copy];
|
|
74
|
+
_navigationFinished = NO;
|
|
75
|
+
if (sourceUrl && sourceUrl.length > 0 && (!_sourceHtml || _sourceHtml.length == 0)) {
|
|
76
|
+
NSURL *url = [NSURL URLWithString:sourceUrl];
|
|
77
|
+
if (url) {
|
|
78
|
+
NSURLRequest *request = [NSURLRequest requestWithURL:url];
|
|
79
|
+
[_webView loadRequest:request];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
- (void)setSourceHtml:(NSString *)sourceHtml
|
|
85
|
+
{
|
|
86
|
+
_sourceHtml = [sourceHtml copy];
|
|
87
|
+
_navigationFinished = NO;
|
|
88
|
+
if (sourceHtml && sourceHtml.length > 0) {
|
|
89
|
+
NSURL *baseURL = [NSURL URLWithString:@"https://app.paywallo.io"];
|
|
90
|
+
[_webView loadHTMLString:sourceHtml baseURL:baseURL];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
- (void)setInjectedJavaScript:(NSString *)injectedJavaScript
|
|
95
|
+
{
|
|
96
|
+
_injectedJavaScript = [injectedJavaScript copy];
|
|
97
|
+
if (!injectedJavaScript || injectedJavaScript.length == 0) return;
|
|
98
|
+
|
|
99
|
+
if (_navigationFinished && _webView) {
|
|
100
|
+
[self executeJavaScript:injectedJavaScript];
|
|
101
|
+
} else {
|
|
102
|
+
self.pendingInjectedJS = injectedJavaScript;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
- (void)executeJavaScript:(NSString *)script
|
|
107
|
+
{
|
|
108
|
+
[_webView evaluateJavaScript:script completionHandler:nil];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
- (void)injectJavaScript:(NSString *)script
|
|
112
|
+
{
|
|
113
|
+
[self executeJavaScript:script];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
#pragma mark - WKScriptMessageHandler
|
|
117
|
+
|
|
118
|
+
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
|
|
119
|
+
{
|
|
120
|
+
if ([message.name isEqualToString:@"ReactNativeWebView"]) {
|
|
121
|
+
NSString *body = message.body;
|
|
122
|
+
if ([body isKindOfClass:[NSString class]]) {
|
|
123
|
+
if (self.onMessage) {
|
|
124
|
+
self.onMessage(@{@"data": body});
|
|
125
|
+
}
|
|
126
|
+
[PaywalloWebViewModule emitWebViewMessage:@{@"data": body}];
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
#pragma mark - WKNavigationDelegate
|
|
132
|
+
|
|
133
|
+
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
|
|
134
|
+
{
|
|
135
|
+
_navigationFinished = YES;
|
|
136
|
+
|
|
137
|
+
if (self.onLoadEnd) {
|
|
138
|
+
self.onLoadEnd(@{});
|
|
139
|
+
}
|
|
140
|
+
[PaywalloWebViewModule emitWebViewLoadEnd];
|
|
141
|
+
|
|
142
|
+
if (self.pendingInjectedJS && self.pendingInjectedJS.length > 0) {
|
|
143
|
+
[self executeJavaScript:self.pendingInjectedJS];
|
|
144
|
+
self.pendingInjectedJS = nil;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error
|
|
149
|
+
{
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error
|
|
153
|
+
{
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
|
|
157
|
+
{
|
|
158
|
+
NSURL *url = navigationAction.request.URL;
|
|
159
|
+
|
|
160
|
+
if ([url.scheme isEqualToString:@"paywallobridge"]) {
|
|
161
|
+
NSString *query = url.query;
|
|
162
|
+
if (query) {
|
|
163
|
+
NSString *data = [query stringByRemovingPercentEncoding];
|
|
164
|
+
if (self.onMessage) {
|
|
165
|
+
self.onMessage(@{@"data": data});
|
|
166
|
+
}
|
|
167
|
+
[PaywalloWebViewModule emitWebViewMessage:@{@"data": data}];
|
|
168
|
+
}
|
|
169
|
+
decisionHandler(WKNavigationActionPolicyCancel);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
decisionHandler(WKNavigationActionPolicyAllow);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
- (void)layoutSubviews
|
|
177
|
+
{
|
|
178
|
+
[super layoutSubviews];
|
|
179
|
+
_webView.frame = self.bounds;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
@end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#import <React/RCTViewManager.h>
|
|
2
|
+
#import <React/RCTBridge.h>
|
|
3
|
+
#import <React/RCTUIManager.h>
|
|
4
|
+
#import "PaywalloWebView.h"
|
|
5
|
+
|
|
6
|
+
@interface PaywalloWebViewManager : RCTViewManager
|
|
7
|
+
@end
|
|
8
|
+
|
|
9
|
+
@implementation PaywalloWebViewManager
|
|
10
|
+
|
|
11
|
+
RCT_EXPORT_MODULE(PaywalloWebView)
|
|
12
|
+
|
|
13
|
+
- (UIView *)view
|
|
14
|
+
{
|
|
15
|
+
return [[PaywalloWebView alloc] init];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
RCT_EXPORT_VIEW_PROPERTY(sourceUrl, NSString)
|
|
19
|
+
RCT_EXPORT_VIEW_PROPERTY(sourceHtml, NSString)
|
|
20
|
+
RCT_EXPORT_VIEW_PROPERTY(injectedJavaScript, NSString)
|
|
21
|
+
RCT_EXPORT_VIEW_PROPERTY(onMessage, RCTDirectEventBlock)
|
|
22
|
+
RCT_EXPORT_VIEW_PROPERTY(onLoadEnd, RCTDirectEventBlock)
|
|
23
|
+
|
|
24
|
+
RCT_EXPORT_METHOD(injectJavaScript:(nonnull NSNumber *)reactTag script:(NSString *)script)
|
|
25
|
+
{
|
|
26
|
+
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
|
27
|
+
PaywalloWebView *view = (PaywalloWebView *)viewRegistry[reactTag];
|
|
28
|
+
if (![view isKindOfClass:[PaywalloWebView class]]) {
|
|
29
|
+
RCTLogError(@"Invalid view returned from registry, expecting PaywalloWebView, got: %@", view);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
[view injectJavaScript:script];
|
|
33
|
+
}];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
+ (BOOL)requiresMainQueueSetup
|
|
37
|
+
{
|
|
38
|
+
return YES;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#import "PaywalloWebViewModule.h"
|
|
2
|
+
|
|
3
|
+
static PaywalloWebViewModule *sharedInstance = nil;
|
|
4
|
+
|
|
5
|
+
@implementation PaywalloWebViewModule
|
|
6
|
+
|
|
7
|
+
RCT_EXPORT_MODULE(PaywalloWebViewModule);
|
|
8
|
+
|
|
9
|
+
- (instancetype)init
|
|
10
|
+
{
|
|
11
|
+
self = [super init];
|
|
12
|
+
if (self) {
|
|
13
|
+
sharedInstance = self;
|
|
14
|
+
}
|
|
15
|
+
return self;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
- (NSArray<NSString *> *)supportedEvents
|
|
19
|
+
{
|
|
20
|
+
return @[@"PaywalloWebViewMessage", @"PaywalloWebViewLoadEnd"];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
+ (void)emitWebViewMessage:(NSDictionary *)message
|
|
24
|
+
{
|
|
25
|
+
if (sharedInstance) {
|
|
26
|
+
[sharedInstance sendEventWithName:@"PaywalloWebViewMessage" body:message];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
+ (void)emitWebViewLoadEnd
|
|
31
|
+
{
|
|
32
|
+
if (sharedInstance) {
|
|
33
|
+
[sharedInstance sendEventWithName:@"PaywalloWebViewLoadEnd" body:@{}];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
+ (BOOL)requiresMainQueueSetup
|
|
38
|
+
{
|
|
39
|
+
return YES;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@end
|
package/package.json
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@virex-tech/paywallo-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "SDK para integração com Monety - Analytics, Feature Flags, Paywalls e Campaigns",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"react-native": "./dist/index.mjs",
|
|
12
|
+
"import": "./dist/index.mjs",
|
|
13
|
+
"require": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"ios",
|
|
19
|
+
"android",
|
|
20
|
+
"MonetySdk.podspec",
|
|
21
|
+
"react-native.config.js"
|
|
22
|
+
],
|
|
23
|
+
"react-native": "./dist/index.mjs",
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsup",
|
|
26
|
+
"dev": "tsup --watch",
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"lint": "eslint .",
|
|
29
|
+
"check": "prettier --check \"src/**/*.{ts,tsx,json}\" && eslint . && tsc --noEmit",
|
|
30
|
+
"fix": "eslint . --fix && prettier --write \"src/**/*.{ts,tsx,json}\"",
|
|
31
|
+
"clean": "rm -rf dist",
|
|
32
|
+
"test": "vitest run",
|
|
33
|
+
"test:watch": "vitest",
|
|
34
|
+
"test:coverage": "vitest run --coverage",
|
|
35
|
+
"prepublishOnly": "npm run build"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"analytics",
|
|
39
|
+
"feature-flags",
|
|
40
|
+
"paywalls",
|
|
41
|
+
"react-native",
|
|
42
|
+
"mobile",
|
|
43
|
+
"subscription",
|
|
44
|
+
"monety"
|
|
45
|
+
],
|
|
46
|
+
"author": "Monety",
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"sideEffects": false,
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=18.0.0"
|
|
54
|
+
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"@react-native-async-storage/async-storage": ">=1.0.0",
|
|
57
|
+
"@react-native-community/netinfo": ">=9.0.0",
|
|
58
|
+
"expo-application": ">=5.0.0",
|
|
59
|
+
"expo-tracking-transparency": ">=0.5.0",
|
|
60
|
+
"react": ">=18.0.0 || >=19.0.0",
|
|
61
|
+
"react-native": ">=0.72.0",
|
|
62
|
+
"react-native-device-info": ">=10.0.0",
|
|
63
|
+
"react-native-fbsdk-next": ">=13.0.0",
|
|
64
|
+
"react-native-keychain": ">=8.0.0"
|
|
65
|
+
},
|
|
66
|
+
"peerDependenciesMeta": {
|
|
67
|
+
"expo-application": {
|
|
68
|
+
"optional": true
|
|
69
|
+
},
|
|
70
|
+
"react-native-keychain": {
|
|
71
|
+
"optional": true
|
|
72
|
+
},
|
|
73
|
+
"react-native-fbsdk-next": {
|
|
74
|
+
"optional": true
|
|
75
|
+
},
|
|
76
|
+
"expo-tracking-transparency": {
|
|
77
|
+
"optional": true
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"devDependencies": {
|
|
81
|
+
"@eslint/js": "^9.0.0",
|
|
82
|
+
"@types/react": "^19.0.0",
|
|
83
|
+
"eslint": "^9.0.0",
|
|
84
|
+
"eslint-config-prettier": "^10.0.0",
|
|
85
|
+
"eslint-plugin-simple-import-sort": "^12.0.0",
|
|
86
|
+
"eslint-plugin-unused-imports": "^4.0.0",
|
|
87
|
+
"prettier": "^3.0.0",
|
|
88
|
+
"tsup": "^8.0.0",
|
|
89
|
+
"typescript": "^5.0.0",
|
|
90
|
+
"typescript-eslint": "^8.0.0",
|
|
91
|
+
"vitest": "^4.1.2"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
dependency: {
|
|
3
|
+
platforms: {
|
|
4
|
+
ios: {
|
|
5
|
+
podspecPath: './MonetySdk.podspec',
|
|
6
|
+
},
|
|
7
|
+
android: {
|
|
8
|
+
sourceDir: './android',
|
|
9
|
+
packageImportPath: 'import com.monety.sdk.MonetySdkPackage;',
|
|
10
|
+
packageInstance: 'new MonetySdkPackage()',
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
};
|