@virex-tech/paywallo-sdk 1.0.0 → 1.1.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.
@@ -1,245 +0,0 @@
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(PanelStoreKit)
10
- class PanelStoreKit: 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
- }
@@ -1,15 +0,0 @@
1
- #import <UIKit/UIKit.h>
2
- #import <WebKit/WebKit.h>
3
- #import <React/RCTComponent.h>
4
-
5
- @interface PanelWebView : 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
@@ -1,182 +0,0 @@
1
- #import "PanelWebView.h"
2
- #import "PanelWebViewModule.h"
3
- #import <AVFoundation/AVFoundation.h>
4
-
5
- @interface PanelWebView () <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 PanelWebView
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 = 'panelbridge://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.monety.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
- [PanelWebViewModule 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
- [PanelWebViewModule 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:@"panelbridge"]) {
161
- NSString *query = url.query;
162
- if (query) {
163
- NSString *data = [query stringByRemovingPercentEncoding];
164
- if (self.onMessage) {
165
- self.onMessage(@{@"data": data});
166
- }
167
- [PanelWebViewModule 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
@@ -1,41 +0,0 @@
1
- #import <React/RCTViewManager.h>
2
- #import <React/RCTBridge.h>
3
- #import <React/RCTUIManager.h>
4
- #import "PanelWebView.h"
5
-
6
- @interface PanelWebViewManager : RCTViewManager
7
- @end
8
-
9
- @implementation PanelWebViewManager
10
-
11
- RCT_EXPORT_MODULE(PanelWebView)
12
-
13
- - (UIView *)view
14
- {
15
- return [[PanelWebView 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
- PanelWebView *view = (PanelWebView *)viewRegistry[reactTag];
28
- if (![view isKindOfClass:[PanelWebView class]]) {
29
- RCTLogError(@"Invalid view returned from registry, expecting PanelWebView, got: %@", view);
30
- return;
31
- }
32
- [view injectJavaScript:script];
33
- }];
34
- }
35
-
36
- + (BOOL)requiresMainQueueSetup
37
- {
38
- return YES;
39
- }
40
-
41
- @end
@@ -1,8 +0,0 @@
1
- #import <React/RCTEventEmitter.h>
2
-
3
- @interface PanelWebViewModule : RCTEventEmitter
4
-
5
- + (void)emitWebViewMessage:(NSDictionary *)message;
6
- + (void)emitWebViewLoadEnd;
7
-
8
- @end
@@ -1,42 +0,0 @@
1
- #import "PanelWebViewModule.h"
2
-
3
- static PanelWebViewModule *sharedInstance = nil;
4
-
5
- @implementation PanelWebViewModule
6
-
7
- RCT_EXPORT_MODULE(PanelWebViewModule);
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 @[@"PanelWebViewMessage", @"PanelWebViewLoadEnd"];
21
- }
22
-
23
- + (void)emitWebViewMessage:(NSDictionary *)message
24
- {
25
- if (sharedInstance) {
26
- [sharedInstance sendEventWithName:@"PanelWebViewMessage" body:message];
27
- }
28
- }
29
-
30
- + (void)emitWebViewLoadEnd
31
- {
32
- if (sharedInstance) {
33
- [sharedInstance sendEventWithName:@"PanelWebViewLoadEnd" body:@{}];
34
- }
35
- }
36
-
37
- + (BOOL)requiresMainQueueSetup
38
- {
39
- return YES;
40
- }
41
-
42
- @end