@purchasely/cordova-plugin-purchasely 5.7.3 → 6.0.0-rc.3
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/README.md +12 -8
- package/__tests__/Purchasely.test.js +307 -92
- package/example/config.xml +1 -11
- package/example/e2e/README.md +44 -0
- package/example/e2e/helpers/driver.js +70 -0
- package/example/e2e/package.json +19 -0
- package/example/e2e/specs/bridge.e2e.js +51 -0
- package/example/e2e/specs/dismiss.e2e.js +37 -0
- package/example/e2e/tools/ci_run_e2e.sh +49 -0
- package/example/e2e/tools/ci_run_e2e_ios.sh +46 -0
- package/example/e2e/wdio.android.conf.js +21 -0
- package/example/e2e/wdio.ios.conf.js +19 -0
- package/example/e2e/wdio.shared.conf.js +28 -0
- package/example/package-lock.json +59 -205
- package/example/package.json +7 -6
- package/example/www/index.html +1 -7
- package/example/www/js/index.js +113 -92
- package/package.json +1 -1
- package/plugin.xml +4 -22
- package/src/android/PurchaselyPlugin.kt +529 -440
- package/src/ios/CDVPurchasely+Events.h +1 -1
- package/src/ios/CDVPurchasely+UserAttributes.h +1 -1
- package/src/ios/CDVPurchasely+UserAttributes.m +2 -0
- package/src/ios/CDVPurchasely.h +21 -16
- package/src/ios/CDVPurchasely.m +497 -261
- package/www/Purchasely.js +208 -40
- package/src/android/PLYProductActivity.kt +0 -168
- package/src/android/PLYSubscriptionsActivity.java +0 -37
- package/src/android/activity_ply_product_activity.xml +0 -6
- package/src/android/activity_ply_subscriptions_activity.xml +0 -6
- package/src/android/theme_purchasely_fullscreen.xml +0 -6
- package/src/android/theme_purchasely_fullscreen_v23.xml +0 -6
- package/src/android/theme_purchasely_fullscreen_v23_light_status_bar.xml +0 -7
package/src/ios/CDVPurchasely.m
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
#import "CDVPurchasely.h"
|
|
9
9
|
#import "Purchasely_Hybrid.h"
|
|
10
10
|
#import "CDVPurchasely+Events.h"
|
|
11
|
+
#import "CDVPurchasely+UserAttributes.h"
|
|
11
12
|
#import "UIColor+PLYHelper.h"
|
|
12
13
|
|
|
13
14
|
@implementation CDVPurchasely
|
|
@@ -16,35 +17,88 @@
|
|
|
16
17
|
self = [super init];
|
|
17
18
|
|
|
18
19
|
self.presentationsLoaded = [NSMutableArray new];
|
|
19
|
-
self.
|
|
20
|
+
self.actionInterceptorCallbackIds = [NSMutableDictionary new];
|
|
21
|
+
self.pendingInterceptCompletions = [NSMutableDictionary new];
|
|
20
22
|
|
|
21
23
|
return self;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
- (void)start:(CDVInvokedUrlCommand*)command {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
// v6: a single options dictionary (see the JS↔native contract), no longer positional args.
|
|
28
|
+
NSDictionary *opts = [command argumentAtIndex:0];
|
|
29
|
+
if (![opts isKindOfClass:[NSDictionary class]]) {
|
|
30
|
+
[self failureFor:command resultString:@"start requires an options object"];
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
NSString *apiKey = opts[@"apiKey"];
|
|
35
|
+
if (![apiKey isKindOfClass:[NSString class]] || apiKey.length == 0) {
|
|
36
|
+
[self failureFor:command resultString:@"apiKey is required"];
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
31
39
|
|
|
32
|
-
[Purchasely
|
|
40
|
+
PurchaselyBuilder *builder = [Purchasely apiKey:apiKey];
|
|
41
|
+
builder = [builder appTechnology:PLYAppTechnologyCordova];
|
|
33
42
|
|
|
34
|
-
[
|
|
43
|
+
NSString *sdkVersion = opts[@"sdkVersion"];
|
|
44
|
+
if ([sdkVersion isKindOfClass:[NSString class]]) {
|
|
45
|
+
builder = [builder sdkBridgeVersion:sdkVersion];
|
|
46
|
+
}
|
|
35
47
|
|
|
36
|
-
[
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
NSString *appUserId = opts[@"appUserId"];
|
|
49
|
+
if ([appUserId isKindOfClass:[NSString class]] && appUserId.length > 0) {
|
|
50
|
+
builder = [builder appUserId:appUserId];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// runningMode is a string ("observer"/"full"), mapped by NAME (iOS enum: observer=2, full=3).
|
|
54
|
+
NSString *runningMode = opts[@"runningMode"];
|
|
55
|
+
enum PLYRunningMode mode = PLYRunningModeObserver;
|
|
56
|
+
if ([runningMode isKindOfClass:[NSString class]] && [runningMode.lowercaseString isEqualToString:@"full"]) {
|
|
57
|
+
mode = PLYRunningModeFull;
|
|
58
|
+
}
|
|
59
|
+
builder = [builder runningMode:mode];
|
|
60
|
+
|
|
61
|
+
NSNumber *logLevel = opts[@"logLevel"];
|
|
62
|
+
if ([logLevel isKindOfClass:[NSNumber class]]) {
|
|
63
|
+
builder = [builder logLevel:(enum PLYLogLevel)logLevel.integerValue];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// StoreKit selection (iOS): storeKit1 bool OR storekitVersion == "storeKit1" forces StoreKit 1.
|
|
67
|
+
BOOL storeKit1 = NO;
|
|
68
|
+
NSNumber *storeKit1Num = opts[@"storeKit1"];
|
|
69
|
+
if ([storeKit1Num isKindOfClass:[NSNumber class]]) {
|
|
70
|
+
storeKit1 = storeKit1Num.boolValue;
|
|
71
|
+
}
|
|
72
|
+
NSString *storekitVersion = opts[@"storekitVersion"];
|
|
73
|
+
if ([storekitVersion isKindOfClass:[NSString class]] && [storekitVersion isEqualToString:@"storeKit1"]) {
|
|
74
|
+
storeKit1 = YES;
|
|
75
|
+
}
|
|
76
|
+
builder = [builder storekitSettings: storeKit1 ? [StorekitSettings storeKit1] : [StorekitSettings storeKit2]];
|
|
77
|
+
|
|
78
|
+
NSNumber *allowDeeplink = opts[@"allowDeeplink"];
|
|
79
|
+
if ([allowDeeplink isKindOfClass:[NSNumber class]]) {
|
|
80
|
+
builder = [builder allowDeeplink:allowDeeplink.boolValue];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
NSNumber *allowCampaigns = opts[@"allowCampaigns"];
|
|
84
|
+
if ([allowCampaigns isKindOfClass:[NSNumber class]]) {
|
|
85
|
+
builder = [builder allowCampaigns:allowCampaigns.boolValue];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Cold-start deeplink URL captured at launch (handled automatically once start completes).
|
|
89
|
+
NSString *deeplink = opts[@"deeplink"];
|
|
90
|
+
if ([deeplink isKindOfClass:[NSString class]] && deeplink.length > 0) {
|
|
91
|
+
NSURL *url = [NSURL URLWithString:deeplink];
|
|
92
|
+
if (url != nil) {
|
|
93
|
+
builder = [builder handleDeeplink:url];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
43
96
|
|
|
97
|
+
[builder startWithInitialized:^(NSError * _Nullable error) {
|
|
44
98
|
if (error != nil) {
|
|
45
99
|
[self failureFor:command resultString: error.localizedDescription];
|
|
46
100
|
} else {
|
|
47
|
-
[self successFor:command resultBool:
|
|
101
|
+
[self successFor:command resultBool:YES];
|
|
48
102
|
}
|
|
49
103
|
}];
|
|
50
104
|
}
|
|
@@ -80,7 +134,8 @@
|
|
|
80
134
|
}
|
|
81
135
|
|
|
82
136
|
NSInteger rawAttribute = [attributeNumber integerValue];
|
|
83
|
-
PLYAttribute
|
|
137
|
+
PLYAttribute attribute = PLYAttributeFirebaseAppInstanceId;
|
|
138
|
+
BOOL attributeFound = YES;
|
|
84
139
|
|
|
85
140
|
switch (rawAttribute) {
|
|
86
141
|
case CordovaPLYAttributeFirebaseAppInstanceId:
|
|
@@ -146,10 +201,13 @@
|
|
|
146
201
|
case CordovaPLYAttributeBatchCustomUserId:
|
|
147
202
|
attribute = PLYAttributeBatchCustomUserId;
|
|
148
203
|
break;
|
|
204
|
+
default:
|
|
205
|
+
attributeFound = NO;
|
|
206
|
+
break;
|
|
149
207
|
}
|
|
150
208
|
|
|
151
209
|
|
|
152
|
-
if (
|
|
210
|
+
if (!attributeFound) {
|
|
153
211
|
return;
|
|
154
212
|
}
|
|
155
213
|
|
|
@@ -161,14 +219,19 @@
|
|
|
161
219
|
[self successFor:command resultString:anonymousId];
|
|
162
220
|
}
|
|
163
221
|
|
|
164
|
-
- (void)
|
|
165
|
-
BOOL
|
|
166
|
-
[Purchasely
|
|
222
|
+
- (void)allowDeeplink:(CDVInvokedUrlCommand*)command {
|
|
223
|
+
BOOL allow = [[command argumentAtIndex:0] boolValue];
|
|
224
|
+
[Purchasely allowDeeplink: allow];
|
|
167
225
|
}
|
|
168
226
|
|
|
169
|
-
- (void)
|
|
170
|
-
|
|
171
|
-
|
|
227
|
+
- (void)allowCampaigns:(CDVInvokedUrlCommand*)command {
|
|
228
|
+
BOOL allow = [[command argumentAtIndex:0] boolValue];
|
|
229
|
+
[Purchasely allowCampaigns: allow];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
- (void)setDefaultPresentationDismissHandler:(CDVInvokedUrlCommand*)command {
|
|
233
|
+
[Purchasely setDefaultPresentationDismissHandler:^(PLYPresentationOutcome * _Nonnull outcome) {
|
|
234
|
+
NSDictionary *resultDict = [self resultDictionaryForOutcome:outcome];
|
|
172
235
|
|
|
173
236
|
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:resultDict];
|
|
174
237
|
[pluginResult setKeepCallbackAsBool:YES];
|
|
@@ -176,118 +239,97 @@
|
|
|
176
239
|
}];
|
|
177
240
|
}
|
|
178
241
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
BOOL isFullscreen = [[command argumentAtIndex:2] boolValue];
|
|
183
|
-
|
|
184
|
-
UIViewController *ctrl = [Purchasely presentationControllerWith:presentationVendorId contentId:contentId loaded:nil completion:^(enum PLYProductViewControllerResult result, PLYPlan * _Nullable plan) {
|
|
185
|
-
NSDictionary *resultDict = [self resultDictionaryForPresentationController:result plan:plan];
|
|
186
|
-
[self successFor:command resultDict:resultDict];
|
|
187
|
-
}];
|
|
188
|
-
|
|
189
|
-
if (ctrl != nil) {
|
|
190
|
-
UINavigationController *navCtrl = [[UINavigationController alloc] initWithRootViewController:ctrl];
|
|
191
|
-
[navCtrl.navigationBar setTranslucent:YES];
|
|
192
|
-
[navCtrl.navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
|
|
193
|
-
[navCtrl.navigationBar setShadowImage: [UIImage new]];
|
|
194
|
-
[navCtrl.navigationBar setTintColor: [UIColor whiteColor]];
|
|
195
|
-
|
|
196
|
-
self.presentedPresentationViewController = navCtrl;
|
|
197
|
-
|
|
198
|
-
if (isFullscreen) {
|
|
199
|
-
navCtrl.modalPresentationStyle = UIModalPresentationFullScreen;
|
|
200
|
-
}
|
|
201
|
-
[Purchasely showController:navCtrl type: PLYUIControllerTypeProductPage from:nil];
|
|
202
|
-
}
|
|
242
|
+
// v6: stop receiving default (campaign/deeplink) presentation dismiss outcomes.
|
|
243
|
+
- (void)removeDefaultPresentationDismissHandler:(CDVInvokedUrlCommand*)command {
|
|
244
|
+
[Purchasely setDefaultPresentationDismissHandler:nil];
|
|
203
245
|
}
|
|
204
246
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
[
|
|
218
|
-
[
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
self.presentedPresentationViewController = navCtrl;
|
|
223
|
-
|
|
224
|
-
if (isFullscreen) {
|
|
225
|
-
navCtrl.modalPresentationStyle = UIModalPresentationFullScreen;
|
|
247
|
+
// v6: wire a builder's presentation lifecycle onto a single Cordova callback.
|
|
248
|
+
// onPresented / onCloseRequested stream keep-alive envelopes ({ event: ... });
|
|
249
|
+
// onDismissed delivers the final outcome (no `event` key) and ends the callback.
|
|
250
|
+
// Must run on the main queue (UIKit). `transition` is the JS transition object.
|
|
251
|
+
- (void)displayWithBuilder:(PLYPresentationBuilder *)builder
|
|
252
|
+
contentId:(NSString *)contentId
|
|
253
|
+
transition:(id)transition
|
|
254
|
+
command:(CDVInvokedUrlCommand *)command {
|
|
255
|
+
if ([contentId isKindOfClass:[NSString class]]) {
|
|
256
|
+
builder = [builder contentId:contentId];
|
|
257
|
+
}
|
|
258
|
+
if ([transition isKindOfClass:[NSDictionary class]]) {
|
|
259
|
+
NSString *bgHex = ((NSDictionary *)transition)[@"backgroundColor"];
|
|
260
|
+
if ([bgHex isKindOfClass:[NSString class]]) {
|
|
261
|
+
UIColor *bg = [UIColor ply_fromHex:bgHex];
|
|
262
|
+
if (bg != nil) { builder = [builder backgroundColor:bg]; }
|
|
226
263
|
}
|
|
227
|
-
[Purchasely showController:navCtrl type: PLYUIControllerTypeProductPage from:nil];
|
|
228
264
|
}
|
|
229
|
-
}
|
|
230
265
|
|
|
231
|
-
|
|
232
|
-
NSString *
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
[
|
|
266
|
+
__weak CDVPurchasely *weakSelf = self;
|
|
267
|
+
NSString *callbackId = command.callbackId;
|
|
268
|
+
builder = [builder onPresented:^(id<PLYPresentation> _Nullable presentation, NSError * _Nullable error) {
|
|
269
|
+
CDVPurchasely *strongSelf = weakSelf; if (strongSelf == nil) return;
|
|
270
|
+
strongSelf.currentPresentation = presentation;
|
|
271
|
+
NSMutableDictionary *env = [NSMutableDictionary new];
|
|
272
|
+
env[@"event"] = @"presented";
|
|
273
|
+
if (presentation != nil) { env[@"presentation"] = [strongSelf resultDictionaryForFetchPresentation:presentation]; }
|
|
274
|
+
if (error != nil) { env[@"error"] = error.localizedDescription; }
|
|
275
|
+
CDVPluginResult *r = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:env];
|
|
276
|
+
[r setKeepCallbackAsBool:YES];
|
|
277
|
+
[strongSelf.commandDelegate sendPluginResult:r callbackId:callbackId];
|
|
278
|
+
}];
|
|
279
|
+
builder = [builder onCloseRequested:^{
|
|
280
|
+
CDVPurchasely *strongSelf = weakSelf; if (strongSelf == nil) return;
|
|
281
|
+
CDVPluginResult *r = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:@{@"event": @"closeRequested"}];
|
|
282
|
+
[r setKeepCallbackAsBool:YES];
|
|
283
|
+
[strongSelf.commandDelegate sendPluginResult:r callbackId:callbackId];
|
|
284
|
+
}];
|
|
285
|
+
builder = [builder onDismissed:^(PLYPresentationOutcome * _Nonnull outcome) {
|
|
286
|
+
CDVPurchasely *strongSelf = weakSelf; if (strongSelf == nil) return;
|
|
287
|
+
strongSelf.currentPresentation = nil;
|
|
288
|
+
[strongSelf successFor:command resultDict:[strongSelf resultDictionaryForOutcome:outcome]];
|
|
240
289
|
}];
|
|
241
290
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
[navCtrl.navigationBar setTranslucent:YES];
|
|
245
|
-
[navCtrl.navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
|
|
246
|
-
[navCtrl.navigationBar setShadowImage: [UIImage new]];
|
|
247
|
-
[navCtrl.navigationBar setTintColor: [UIColor whiteColor]];
|
|
248
|
-
|
|
249
|
-
self.presentedPresentationViewController = navCtrl;
|
|
250
|
-
|
|
251
|
-
if (isFullscreen) {
|
|
252
|
-
navCtrl.modalPresentationStyle = UIModalPresentationFullScreen;
|
|
253
|
-
}
|
|
254
|
-
[Purchasely showController:navCtrl type: PLYUIControllerTypeProductPage from:nil];
|
|
255
|
-
}
|
|
291
|
+
id<PLYPresentationRequest> request = [builder build];
|
|
292
|
+
[request displayWithTransition:[self displayModeFromTransition:transition] completion:nil];
|
|
256
293
|
}
|
|
257
294
|
|
|
258
|
-
- (void)
|
|
259
|
-
NSString *
|
|
260
|
-
NSString *
|
|
261
|
-
|
|
262
|
-
BOOL isFullscreen = [[command argumentAtIndex:3] boolValue];
|
|
295
|
+
- (void)presentPresentationWithIdentifier:(CDVInvokedUrlCommand*)command {
|
|
296
|
+
NSString *presentationVendorId = [command argumentAtIndex:0];
|
|
297
|
+
NSString *contentId = [command argumentAtIndex:1];
|
|
298
|
+
id transition = [command argumentAtIndex:2];
|
|
263
299
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
300
|
+
if (![presentationVendorId isKindOfClass:[NSString class]]) {
|
|
301
|
+
[self failureFor:command resultString:@"presentationId is required"];
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
268
304
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
[navCtrl.navigationBar setShadowImage: [UIImage new]];
|
|
274
|
-
[navCtrl.navigationBar setTintColor: [UIColor whiteColor]];
|
|
305
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
306
|
+
[self displayWithBuilder:[PLYPresentationBuilder forScreenId:presentationVendorId] contentId:contentId transition:transition command:command];
|
|
307
|
+
});
|
|
308
|
+
}
|
|
275
309
|
|
|
276
|
-
|
|
310
|
+
- (void)presentPresentationForPlacement:(CDVInvokedUrlCommand*)command {
|
|
311
|
+
NSString *placementVendorId = [command argumentAtIndex:0];
|
|
312
|
+
NSString *contentId = [command argumentAtIndex:1];
|
|
313
|
+
id transition = [command argumentAtIndex:2];
|
|
277
314
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
[Purchasely showController:navCtrl type: PLYUIControllerTypeProductPage from:nil];
|
|
315
|
+
if (![placementVendorId isKindOfClass:[NSString class]]) {
|
|
316
|
+
[self failureFor:command resultString:@"placementId is required"];
|
|
317
|
+
return;
|
|
282
318
|
}
|
|
319
|
+
|
|
320
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
321
|
+
[self displayWithBuilder:[PLYPresentationBuilder forPlacementId:placementVendorId] contentId:contentId transition:transition command:command];
|
|
322
|
+
});
|
|
283
323
|
}
|
|
284
324
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
325
|
+
// v6: present the default (audience-targeted) presentation — no placement/screen id.
|
|
326
|
+
- (void)presentPresentationForDefault:(CDVInvokedUrlCommand*)command {
|
|
327
|
+
NSString *contentId = [command argumentAtIndex:0];
|
|
328
|
+
id transition = [command argumentAtIndex:1];
|
|
289
329
|
|
|
290
|
-
|
|
330
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
331
|
+
[self displayWithBuilder:[[PLYPresentationBuilder alloc] init] contentId:contentId transition:transition command:command];
|
|
332
|
+
});
|
|
291
333
|
}
|
|
292
334
|
|
|
293
335
|
- (void)purchaseWithPlanVendorId:(CDVInvokedUrlCommand*)command {
|
|
@@ -358,7 +400,9 @@
|
|
|
358
400
|
|
|
359
401
|
- (void)synchronize:(CDVInvokedUrlCommand*)command {
|
|
360
402
|
[Purchasely synchronizeWithSuccess:^{
|
|
403
|
+
[self successFor:command resultBool:YES];
|
|
361
404
|
} failure:^(NSError * _Nonnull error) {
|
|
405
|
+
[self failureFor:command resultString:error.localizedDescription];
|
|
362
406
|
}];
|
|
363
407
|
}
|
|
364
408
|
|
|
@@ -444,12 +488,14 @@
|
|
|
444
488
|
}
|
|
445
489
|
|
|
446
490
|
- (void)removeEventsListener:(CDVInvokedUrlCommand*)command {
|
|
447
|
-
|
|
491
|
+
// v6 `setEventDelegate:` is _Nonnull (no native unregister). The delegate stays
|
|
492
|
+
// registered; clearing eventCommand makes `eventTriggered:` a no-op.
|
|
448
493
|
self.eventCommand = nil;
|
|
449
494
|
}
|
|
450
495
|
|
|
451
496
|
- (void)removeUserAttributeListener:(CDVInvokedUrlCommand*)command {
|
|
452
|
-
|
|
497
|
+
// v6 `setUserAttributeDelegate:` is _Nonnull (no native unregister). Clearing
|
|
498
|
+
// attributeCommand makes the user-attribute callbacks a no-op.
|
|
453
499
|
self.attributeCommand = nil;
|
|
454
500
|
}
|
|
455
501
|
|
|
@@ -458,12 +504,12 @@
|
|
|
458
504
|
self.attributeCommand = command;
|
|
459
505
|
}
|
|
460
506
|
|
|
461
|
-
- (void)
|
|
507
|
+
- (void)handleDeeplink:(CDVInvokedUrlCommand*)command {
|
|
462
508
|
NSString *deeplinkString = [command argumentAtIndex:0];
|
|
463
509
|
NSURL *deeplink = [NSURL URLWithString:deeplinkString];
|
|
464
510
|
|
|
465
511
|
if (deeplink != nil) {
|
|
466
|
-
BOOL result = [Purchasely
|
|
512
|
+
BOOL result = [Purchasely handleDeeplink:deeplink];
|
|
467
513
|
[self successFor:command resultBool:result];
|
|
468
514
|
} else {
|
|
469
515
|
[self successFor:command resultBool:NO];
|
|
@@ -509,33 +555,139 @@
|
|
|
509
555
|
|
|
510
556
|
// Helpers
|
|
511
557
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
558
|
+
// v6: builds a PLYTransition from the JS transition object (see normalizeTransition):
|
|
559
|
+
// { type, dismissible?, height?: { type: 'pixel'|'percentage', value }, backgroundColor? }
|
|
560
|
+
// (a bare mode string is also tolerated). Objective-C only exposes percentage height for
|
|
561
|
+
// drawer/popin (pixel height and popin width are Swift-only in the SDK), so those honor a
|
|
562
|
+
// percentage height + dismissible + background colors; other dimensions are ignored.
|
|
563
|
+
// Returns nil for an unknown/absent value so the backend-defined default is honored.
|
|
564
|
+
- (PLYTransition * _Nullable) displayModeFromTransition:(id) transition {
|
|
565
|
+
NSDictionary *map = [transition isKindOfClass:[NSDictionary class]] ? transition : nil;
|
|
566
|
+
NSString *type = map != nil ? map[@"type"] : ([transition isKindOfClass:[NSString class]] ? transition : nil);
|
|
567
|
+
if (![type isKindOfClass:[NSString class]]) {
|
|
568
|
+
return nil;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
BOOL dismissible = YES;
|
|
572
|
+
if ([map[@"dismissible"] isKindOfClass:[NSNumber class]]) {
|
|
573
|
+
dismissible = [map[@"dismissible"] boolValue];
|
|
574
|
+
}
|
|
515
575
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
576
|
+
// Percentage height only (ObjC-accessible). Pixel height / popin width are not
|
|
577
|
+
// exposed to Objective-C by the SDK; a percentage value is expected in 0.0–1.0.
|
|
578
|
+
NSNumber *heightPercentage = nil;
|
|
579
|
+
NSDictionary *height = map[@"height"];
|
|
580
|
+
if ([height isKindOfClass:[NSDictionary class]] &&
|
|
581
|
+
[height[@"type"] isEqual:@"percentage"] &&
|
|
582
|
+
[height[@"value"] isKindOfClass:[NSNumber class]]) {
|
|
583
|
+
heightPercentage = height[@"value"];
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
PLYColors *backgroundColors = nil;
|
|
587
|
+
NSString *bgHex = map[@"backgroundColor"];
|
|
588
|
+
if ([bgHex isKindOfClass:[NSString class]]) {
|
|
589
|
+
UIColor *c = [UIColor ply_fromHex:bgHex];
|
|
590
|
+
if (c != nil) {
|
|
591
|
+
backgroundColors = [[PLYColors alloc] initWithLightColor:c darkColor:c];
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if ([type isEqualToString:@"fullScreen"]) {
|
|
596
|
+
return [PLYTransition fullScreen];
|
|
597
|
+
} else if ([type isEqualToString:@"modal"]) {
|
|
598
|
+
return [PLYTransition modalWithDismissible:dismissible];
|
|
599
|
+
} else if ([type isEqualToString:@"push"]) {
|
|
600
|
+
return [PLYTransition push];
|
|
601
|
+
} else if ([type isEqualToString:@"inlinePaywall"]) {
|
|
602
|
+
return [PLYTransition inlinePaywall];
|
|
603
|
+
} else if ([type isEqualToString:@"drawer"]) {
|
|
604
|
+
return [[PLYTransition alloc] initWithType:PLYTransitionTypeDrawer heightPercentage:heightPercentage backgroundColors:backgroundColors dismissible:dismissible];
|
|
605
|
+
} else if ([type isEqualToString:@"popin"]) {
|
|
606
|
+
return [[PLYTransition alloc] initWithType:PLYTransitionTypePopin heightPercentage:heightPercentage backgroundColors:backgroundColors dismissible:dismissible];
|
|
607
|
+
}
|
|
608
|
+
return nil;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// v6: the dismiss outcome now arrives as a PLYPresentationOutcome (replaces the
|
|
612
|
+
// (PLYProductViewControllerResult, PLYPlan) pair). Serialized per the JS↔native contract.
|
|
613
|
+
- (NSDictionary<NSString *, NSObject *> *) resultDictionaryForOutcome:(PLYPresentationOutcome * _Nonnull)outcome {
|
|
614
|
+
NSMutableDictionary<NSString *, NSObject *> *dict = [NSMutableDictionary new];
|
|
615
|
+
|
|
616
|
+
// Map the v6 PLYPurchaseResult (cancelled=0, purchased=1, restored=2, none=3) to the
|
|
617
|
+
// JS PurchaseResult enum (PURCHASED=0, CANCELLED=1, RESTORED=2) for back-compat.
|
|
618
|
+
int result;
|
|
619
|
+
switch (outcome.purchaseResult) {
|
|
620
|
+
case PLYPurchaseResultPurchased:
|
|
621
|
+
result = 0;
|
|
622
|
+
break;
|
|
623
|
+
case PLYPurchaseResultCancelled:
|
|
624
|
+
result = 1;
|
|
519
625
|
break;
|
|
520
|
-
case
|
|
521
|
-
|
|
626
|
+
case PLYPurchaseResultRestored:
|
|
627
|
+
result = 2;
|
|
522
628
|
break;
|
|
523
|
-
case
|
|
524
|
-
|
|
629
|
+
case PLYPurchaseResultNone:
|
|
630
|
+
// TODO(v6-verify): PLYPurchaseResultNone (no purchase action) has no JS PurchaseResult
|
|
631
|
+
// equivalent; mapped to CANCELLED(1). closeReason still conveys the precise reason.
|
|
632
|
+
result = 1;
|
|
525
633
|
break;
|
|
526
634
|
}
|
|
635
|
+
[dict setObject:[NSNumber numberWithInt:result] forKey:@"result"];
|
|
636
|
+
|
|
637
|
+
// v6: also expose the string purchaseResult (matches the Flutter outcome contract);
|
|
638
|
+
// omitted for PLYPurchaseResultNone (no purchase action).
|
|
639
|
+
NSString *purchaseResultString = nil;
|
|
640
|
+
switch (outcome.purchaseResult) {
|
|
641
|
+
case PLYPurchaseResultPurchased: purchaseResultString = @"purchased"; break;
|
|
642
|
+
case PLYPurchaseResultCancelled: purchaseResultString = @"cancelled"; break;
|
|
643
|
+
case PLYPurchaseResultRestored: purchaseResultString = @"restored"; break;
|
|
644
|
+
case PLYPurchaseResultNone: break;
|
|
645
|
+
}
|
|
646
|
+
if (purchaseResultString != nil) {
|
|
647
|
+
[dict setObject:purchaseResultString forKey:@"purchaseResult"];
|
|
648
|
+
}
|
|
527
649
|
|
|
528
|
-
|
|
650
|
+
if (outcome.plan != nil) {
|
|
651
|
+
[dict setObject:[outcome.plan asDictionary] forKey:@"plan"];
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// closeReason strings match the JS Purchasely.CloseReason enum, aligned with the
|
|
655
|
+
// native PLYCloseReason wire contract shared with Flutter (button / back_system /
|
|
656
|
+
// programmatic). iOS's interactive (swipe) dismiss maps onto `back_system`; a close
|
|
657
|
+
// with no dismiss reason (PLYCloseReasonNone, e.g. after a purchase) omits the key,
|
|
658
|
+
// mirroring the null closeReason the Flutter bridge reports on iOS.
|
|
659
|
+
NSString *closeReason = nil;
|
|
660
|
+
switch (outcome.closeReason) {
|
|
661
|
+
case PLYCloseReasonButton:
|
|
662
|
+
closeReason = @"button";
|
|
663
|
+
break;
|
|
664
|
+
case PLYCloseReasonInteractiveDismiss:
|
|
665
|
+
closeReason = @"back_system";
|
|
666
|
+
break;
|
|
667
|
+
case PLYCloseReasonProgrammatic:
|
|
668
|
+
closeReason = @"programmatic";
|
|
669
|
+
break;
|
|
670
|
+
case PLYCloseReasonNone:
|
|
671
|
+
break;
|
|
672
|
+
}
|
|
673
|
+
if (closeReason != nil) {
|
|
674
|
+
[dict setObject:closeReason forKey:@"closeReason"];
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
if (outcome.error != nil) {
|
|
678
|
+
[dict setObject:outcome.error.localizedDescription forKey:@"error"];
|
|
679
|
+
}
|
|
529
680
|
|
|
530
|
-
if (
|
|
531
|
-
[
|
|
681
|
+
if (outcome.presentation != nil) {
|
|
682
|
+
[dict setObject:[self resultDictionaryForFetchPresentation:outcome.presentation] forKey:@"presentation"];
|
|
532
683
|
}
|
|
533
|
-
|
|
684
|
+
|
|
685
|
+
return dict;
|
|
534
686
|
}
|
|
535
687
|
|
|
536
688
|
- (NSDictionary<NSString *, NSObject *> *) resultDictionaryForActionInterceptor:(PLYPresentationAction) action
|
|
537
689
|
parameters: (PLYPresentationActionParameters * _Nullable) params
|
|
538
|
-
|
|
690
|
+
info: (PLYInterceptorInfo * _Nullable) info {
|
|
539
691
|
NSMutableDictionary<NSString *, NSObject *> *actionInterceptorResult = [NSMutableDictionary new];
|
|
540
692
|
|
|
541
693
|
NSString* actionString;
|
|
@@ -575,22 +727,27 @@
|
|
|
575
727
|
|
|
576
728
|
[actionInterceptorResult setObject:actionString forKey:@"action"];
|
|
577
729
|
|
|
578
|
-
|
|
730
|
+
// v6: PLYPresentationInfo was replaced by PLYInterceptorInfo. The flat id fields
|
|
731
|
+
// (presentationId/placementId/abTestId/…) are now accessed through info.presentation.
|
|
732
|
+
if (info != nil) {
|
|
579
733
|
NSMutableDictionary<NSString *, NSObject *> *infosResult = [NSMutableDictionary new];
|
|
580
|
-
if (
|
|
581
|
-
[infosResult setObject:
|
|
582
|
-
}
|
|
583
|
-
if (infos.presentationId != nil) {
|
|
584
|
-
[infosResult setObject:infos.presentationId forKey:@"presentationId"];
|
|
585
|
-
}
|
|
586
|
-
if (infos.placementId != nil) {
|
|
587
|
-
[infosResult setObject:infos.placementId forKey:@"placementId"];
|
|
588
|
-
}
|
|
589
|
-
if (infos.abTestId != nil) {
|
|
590
|
-
[infosResult setObject:infos.abTestId forKey:@"abTestId"];
|
|
734
|
+
if (info.contentId != nil) {
|
|
735
|
+
[infosResult setObject:info.contentId forKey:@"contentId"];
|
|
591
736
|
}
|
|
592
|
-
|
|
593
|
-
|
|
737
|
+
id<PLYPresentation> presentation = info.presentation;
|
|
738
|
+
if (presentation != nil) {
|
|
739
|
+
if (presentation.screenId != nil) {
|
|
740
|
+
[infosResult setObject:presentation.screenId forKey:@"presentationId"];
|
|
741
|
+
}
|
|
742
|
+
if (presentation.placementId != nil) {
|
|
743
|
+
[infosResult setObject:presentation.placementId forKey:@"placementId"];
|
|
744
|
+
}
|
|
745
|
+
if (presentation.abTestId != nil) {
|
|
746
|
+
[infosResult setObject:presentation.abTestId forKey:@"abTestId"];
|
|
747
|
+
}
|
|
748
|
+
if (presentation.abTestVariantId != nil) {
|
|
749
|
+
[infosResult setObject:presentation.abTestVariantId forKey:@"abTestVariantId"];
|
|
750
|
+
}
|
|
594
751
|
}
|
|
595
752
|
|
|
596
753
|
[actionInterceptorResult setObject:infosResult forKey:@"info"];
|
|
@@ -609,6 +766,9 @@
|
|
|
609
766
|
if (params.presentation != nil) {
|
|
610
767
|
[paramsResult setObject:params.presentation forKey:@"presentation"];
|
|
611
768
|
}
|
|
769
|
+
if (params.placement != nil) {
|
|
770
|
+
[paramsResult setObject:params.placement forKey:@"placementId"];
|
|
771
|
+
}
|
|
612
772
|
if (params.promoOffer != nil) {
|
|
613
773
|
NSMutableDictionary<NSString *, NSObject *> *promoOffer = [NSMutableDictionary new];
|
|
614
774
|
[promoOffer setObject:params.promoOffer.vendorId forKey:@"vendorId"];
|
|
@@ -635,58 +795,112 @@ static NSString * PLYWebCheckoutProviderToString(PLYWebCheckoutProvider provider
|
|
|
635
795
|
return @"stripe";
|
|
636
796
|
case PLYWebCheckoutProviderOther:
|
|
637
797
|
return @"other";
|
|
798
|
+
case PLYWebCheckoutProviderNone:
|
|
799
|
+
return @"none";
|
|
638
800
|
default:
|
|
639
801
|
return @"unknown";
|
|
640
802
|
}
|
|
641
803
|
}
|
|
642
804
|
|
|
643
|
-
- (
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
805
|
+
// Maps a JS action-kind string (see Purchasely.PresentationAction) to a PLYPresentationAction.
|
|
806
|
+
// Returns NO for an unknown kind.
|
|
807
|
+
static BOOL PLYPresentationActionFromString(NSString *kind, PLYPresentationAction *out) {
|
|
808
|
+
static NSDictionary<NSString *, NSNumber *> *map;
|
|
809
|
+
static dispatch_once_t once;
|
|
810
|
+
dispatch_once(&once, ^{
|
|
811
|
+
map = @{
|
|
812
|
+
@"login": @(PLYPresentationActionLogin),
|
|
813
|
+
@"purchase": @(PLYPresentationActionPurchase),
|
|
814
|
+
@"close": @(PLYPresentationActionClose),
|
|
815
|
+
@"close_all": @(PLYPresentationActionCloseAll),
|
|
816
|
+
@"restore": @(PLYPresentationActionRestore),
|
|
817
|
+
@"navigate": @(PLYPresentationActionNavigate),
|
|
818
|
+
@"promo_code": @(PLYPresentationActionPromoCode),
|
|
819
|
+
@"open_presentation": @(PLYPresentationActionOpenPresentation),
|
|
820
|
+
@"open_placement": @(PLYPresentationActionOpenPlacement),
|
|
821
|
+
@"web_checkout": @(PLYPresentationActionWebCheckout),
|
|
822
|
+
};
|
|
823
|
+
});
|
|
824
|
+
NSNumber *value = [kind isKindOfClass:[NSString class]] ? map[kind] : nil;
|
|
825
|
+
if (value == nil) return NO;
|
|
826
|
+
if (out != NULL) *out = (PLYPresentationAction)value.integerValue;
|
|
827
|
+
return YES;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// v6: register a JS handler for a single action kind. Each intercept emits an
|
|
831
|
+
// event carrying a unique `callbackId`; JS replies via completeActionInterceptor.
|
|
832
|
+
- (void)registerActionInterceptor:(CDVInvokedUrlCommand*)command {
|
|
833
|
+
NSString *kind = [command argumentAtIndex:0];
|
|
834
|
+
PLYPresentationAction action;
|
|
835
|
+
if (!PLYPresentationActionFromString(kind, &action)) {
|
|
836
|
+
NSLog(@"[Purchasely] unknown interceptor kind: %@", kind);
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
self.actionInterceptorCallbackIds[kind] = command.callbackId;
|
|
648
840
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
841
|
+
__weak CDVPurchasely *weakSelf = self;
|
|
842
|
+
[Purchasely interceptAction:action handler:^(PLYInterceptorInfo * _Nonnull info, PLYPresentationActionParameters * _Nullable params, void (^ _Nonnull completion)(enum PLYInterceptResult)) {
|
|
843
|
+
CDVPurchasely *strongSelf = weakSelf;
|
|
844
|
+
if (strongSelf == nil) { completion(PLYInterceptResultNotHandled); return; }
|
|
652
845
|
|
|
846
|
+
NSString *callbackId = strongSelf.actionInterceptorCallbackIds[kind];
|
|
847
|
+
if (callbackId == nil) { completion(PLYInterceptResultNotHandled); return; }
|
|
848
|
+
|
|
849
|
+
// Unique id per intercepted invocation so concurrent intercepts resolve independently.
|
|
850
|
+
NSString *invocationId = [NSString stringWithFormat:@"%@#%lu", kind, (unsigned long)(++strongSelf.interceptorInvocationCounter)];
|
|
851
|
+
// Copy the escaping completion onto the heap before stashing (the dictionary retains but
|
|
852
|
+
// does not copy; the old single-slot property was declared `copy`).
|
|
853
|
+
strongSelf.pendingInterceptCompletions[invocationId] = [completion copy];
|
|
854
|
+
|
|
855
|
+
NSMutableDictionary *event = [[strongSelf resultDictionaryForActionInterceptor:action parameters:params info:info] mutableCopy];
|
|
856
|
+
event[@"callbackId"] = invocationId;
|
|
857
|
+
|
|
858
|
+
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:event];
|
|
859
|
+
[pluginResult setKeepCallbackAsBool:YES];
|
|
860
|
+
[strongSelf.commandDelegate sendPluginResult:pluginResult callbackId:callbackId];
|
|
653
861
|
}];
|
|
654
862
|
}
|
|
655
863
|
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
864
|
+
// v6: stop intercepting a single action kind.
|
|
865
|
+
- (void)unregisterActionInterceptor:(CDVInvokedUrlCommand*)command {
|
|
866
|
+
NSString *kind = [command argumentAtIndex:0];
|
|
867
|
+
PLYPresentationAction action;
|
|
868
|
+
if (!PLYPresentationActionFromString(kind, &action)) return;
|
|
869
|
+
[self.actionInterceptorCallbackIds removeObjectForKey:kind];
|
|
870
|
+
[Purchasely removeActionInterceptor:action];
|
|
661
871
|
}
|
|
662
872
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
873
|
+
// v6: JS reports how an intercepted action was handled, keyed by the invocation
|
|
874
|
+
// id carried on the intercept event. Result is "success"/"failed"/"notHandled".
|
|
875
|
+
- (void)completeActionInterceptor:(CDVInvokedUrlCommand*)command {
|
|
876
|
+
NSString *invocationId = [command argumentAtIndex:0];
|
|
877
|
+
NSString *resultString = [command argumentAtIndex:1];
|
|
878
|
+
if (![invocationId isKindOfClass:[NSString class]]) return;
|
|
879
|
+
|
|
880
|
+
void (^completion)(enum PLYInterceptResult) = self.pendingInterceptCompletions[invocationId];
|
|
881
|
+
if (completion == nil) return;
|
|
882
|
+
[self.pendingInterceptCompletions removeObjectForKey:invocationId];
|
|
883
|
+
|
|
884
|
+
enum PLYInterceptResult result = PLYInterceptResultNotHandled;
|
|
885
|
+
if ([resultString isEqualToString:@"success"]) {
|
|
886
|
+
result = PLYInterceptResultSuccess;
|
|
887
|
+
} else if ([resultString isEqualToString:@"failed"]) {
|
|
888
|
+
result = PLYInterceptResultFailed;
|
|
671
889
|
}
|
|
890
|
+
completion(result);
|
|
672
891
|
}
|
|
673
892
|
|
|
674
|
-
- (void)
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
});
|
|
680
|
-
}
|
|
893
|
+
- (void)closePresentation:(CDVInvokedUrlCommand*)command {
|
|
894
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
895
|
+
[Purchasely closeAllScreens];
|
|
896
|
+
self.currentPresentation = nil;
|
|
897
|
+
});
|
|
681
898
|
}
|
|
682
899
|
|
|
683
|
-
- (void)
|
|
900
|
+
- (void)backPresentation:(CDVInvokedUrlCommand*)command {
|
|
684
901
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
685
|
-
if (self.
|
|
686
|
-
|
|
687
|
-
self.shouldReopenPaywall = NO;
|
|
688
|
-
[Purchasely showController:self.presentedPresentationViewController type:PLYUIControllerTypeProductPage from:nil];
|
|
689
|
-
});
|
|
902
|
+
if (self.currentPresentation != nil) {
|
|
903
|
+
[self.currentPresentation back];
|
|
690
904
|
}
|
|
691
905
|
});
|
|
692
906
|
}
|
|
@@ -887,84 +1101,96 @@ static NSString * PLYWebCheckoutProviderToString(PLYWebCheckoutProvider provider
|
|
|
887
1101
|
NSString *contentId = [command argumentAtIndex:2];
|
|
888
1102
|
|
|
889
1103
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
}
|
|
917
|
-
});
|
|
1104
|
+
PLYPresentationBuilder *builder;
|
|
1105
|
+
if ([placementId isKindOfClass:[NSString class]]) {
|
|
1106
|
+
builder = [PLYPresentationBuilder forPlacementId:placementId];
|
|
1107
|
+
} else if ([presentationId isKindOfClass:[NSString class]]) {
|
|
1108
|
+
builder = [PLYPresentationBuilder forScreenId:presentationId];
|
|
1109
|
+
} else {
|
|
1110
|
+
// Neither id provided → the default (audience-targeted) presentation.
|
|
1111
|
+
builder = [[PLYPresentationBuilder alloc] init];
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
if ([contentId isKindOfClass:[NSString class]]) {
|
|
1115
|
+
builder = [builder contentId:contentId];
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
// v6: build a request and preload it (fetch without display). The purchase/dismiss
|
|
1119
|
+
// outcome now flows through the later presentPresentation: display, not fetch.
|
|
1120
|
+
id<PLYPresentationRequest> request = [builder build];
|
|
1121
|
+
[request preloadWithCompletion:^(id<PLYPresentation> _Nullable presentation, NSError * _Nullable error) {
|
|
1122
|
+
if (error != nil) {
|
|
1123
|
+
[self failureFor:command resultString: error.localizedDescription];
|
|
1124
|
+
} else if (presentation != nil) {
|
|
1125
|
+
[self.presentationsLoaded addObject:presentation];
|
|
1126
|
+
[self successFor:command resultDict:[self resultDictionaryForFetchPresentation:presentation]];
|
|
1127
|
+
}
|
|
1128
|
+
}];
|
|
1129
|
+
});
|
|
918
1130
|
}
|
|
919
1131
|
|
|
920
1132
|
- (void)presentPresentation:(CDVInvokedUrlCommand *)command {
|
|
921
1133
|
NSDictionary<NSString *, id> *presentationDictionary = [command argumentAtIndex:0];
|
|
922
|
-
|
|
1134
|
+
id transition = [command argumentAtIndex:1];
|
|
923
1135
|
NSString *loadingBackgroundColor = [command argumentAtIndex:2];
|
|
924
1136
|
|
|
925
1137
|
if (presentationDictionary == nil) {
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
self.purchaseResolve = command;
|
|
931
|
-
|
|
932
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
933
|
-
|
|
934
|
-
PLYPresentation *presentationLoaded = [self findPresentationLoadedFor:(NSString *)[presentationDictionary objectForKey:@"id"]];
|
|
1138
|
+
[self failureFor:command resultString: @"Presentation cannot be null"];
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
935
1141
|
|
|
936
|
-
|
|
937
|
-
[self failureFor:command resultString: @"Presentation not loaded"];
|
|
938
|
-
return;
|
|
939
|
-
}
|
|
1142
|
+
self.purchaseResolve = command;
|
|
940
1143
|
|
|
941
|
-
|
|
1144
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
1145
|
+
NSString *presentationId = (NSString *)[presentationDictionary objectForKey:@"id"];
|
|
1146
|
+
id<PLYPresentation> presentationLoaded = [self findPresentationLoadedFor:presentationId];
|
|
942
1147
|
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
[presentationLoaded.controller.view setBackgroundColor:backColor];
|
|
948
|
-
}
|
|
949
|
-
}
|
|
1148
|
+
if (presentationLoaded == nil || presentationLoaded.controller == nil) {
|
|
1149
|
+
[self failureFor:command resultString: @"Presentation not loaded"];
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
950
1152
|
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
1153
|
+
NSInteger index = [self findIndexPresentationLoadedFor:presentationId];
|
|
1154
|
+
if (index >= 0) {
|
|
1155
|
+
[self.presentationsLoaded removeObjectAtIndex:index];
|
|
1156
|
+
}
|
|
954
1157
|
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
|
960
|
-
if (presentationLoaded.isFlow) {
|
|
961
|
-
[presentationLoaded displayFrom:nil];
|
|
962
|
-
} else {
|
|
963
|
-
[Purchasely showController:presentationLoaded.controller type: PLYUIControllerTypeProductPage from:nil];
|
|
964
|
-
}
|
|
965
|
-
});
|
|
1158
|
+
if (loadingBackgroundColor != nil) {
|
|
1159
|
+
UIColor *backColor = [UIColor ply_fromHex:loadingBackgroundColor];
|
|
1160
|
+
if (backColor != nil) {
|
|
1161
|
+
[presentationLoaded.controller.view setBackgroundColor:backColor];
|
|
966
1162
|
}
|
|
967
|
-
}
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
// Stream the presentation lifecycle to this command (keep-alive envelopes for
|
|
1166
|
+
// presented/closeRequested, final outcome on dismiss).
|
|
1167
|
+
__weak CDVPurchasely *weakSelf = self;
|
|
1168
|
+
NSString *callbackId = command.callbackId;
|
|
1169
|
+
presentationLoaded.onPresented = ^(id<PLYPresentation> _Nullable presentation, NSError * _Nullable error) {
|
|
1170
|
+
CDVPurchasely *strongSelf = weakSelf; if (strongSelf == nil) return;
|
|
1171
|
+
NSMutableDictionary *env = [NSMutableDictionary new];
|
|
1172
|
+
env[@"event"] = @"presented";
|
|
1173
|
+
if (presentation != nil) { env[@"presentation"] = [strongSelf resultDictionaryForFetchPresentation:presentation]; }
|
|
1174
|
+
if (error != nil) { env[@"error"] = error.localizedDescription; }
|
|
1175
|
+
CDVPluginResult *r = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:env];
|
|
1176
|
+
[r setKeepCallbackAsBool:YES];
|
|
1177
|
+
[strongSelf.commandDelegate sendPluginResult:r callbackId:callbackId];
|
|
1178
|
+
};
|
|
1179
|
+
presentationLoaded.onCloseRequested = ^{
|
|
1180
|
+
CDVPurchasely *strongSelf = weakSelf; if (strongSelf == nil) return;
|
|
1181
|
+
CDVPluginResult *r = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:@{@"event": @"closeRequested"}];
|
|
1182
|
+
[r setKeepCallbackAsBool:YES];
|
|
1183
|
+
[strongSelf.commandDelegate sendPluginResult:r callbackId:callbackId];
|
|
1184
|
+
};
|
|
1185
|
+
presentationLoaded.onDismissed = ^(PLYPresentationOutcome * _Nonnull outcome) {
|
|
1186
|
+
CDVPurchasely *strongSelf = weakSelf; if (strongSelf == nil) return;
|
|
1187
|
+
strongSelf.currentPresentation = nil;
|
|
1188
|
+
[strongSelf successFor:command resultDict:[strongSelf resultDictionaryForOutcome:outcome]];
|
|
1189
|
+
};
|
|
1190
|
+
|
|
1191
|
+
self.currentPresentation = presentationLoaded;
|
|
1192
|
+
[presentationLoaded displayFrom:nil transitionType:[self displayModeFromTransition:transition]];
|
|
1193
|
+
});
|
|
968
1194
|
}
|
|
969
1195
|
|
|
970
1196
|
- (void) revokeDataProcessingConsent:(CDVInvokedUrlCommand *)command {
|
|
@@ -996,9 +1222,9 @@ static NSString * PLYWebCheckoutProviderToString(PLYWebCheckoutProvider provider
|
|
|
996
1222
|
[Purchasely setDebugModeWithEnabled: enabled];
|
|
997
1223
|
}
|
|
998
1224
|
|
|
999
|
-
- (PLYPresentation
|
|
1000
|
-
for (PLYPresentation
|
|
1001
|
-
if ([presentationLoaded.
|
|
1225
|
+
- (id<PLYPresentation>) findPresentationLoadedFor:(NSString * _Nullable) presentationId {
|
|
1226
|
+
for (id<PLYPresentation> presentationLoaded in self.presentationsLoaded) {
|
|
1227
|
+
if ([presentationLoaded.screenId isEqualToString: presentationId]) {
|
|
1002
1228
|
return presentationLoaded;
|
|
1003
1229
|
}
|
|
1004
1230
|
}
|
|
@@ -1007,8 +1233,8 @@ static NSString * PLYWebCheckoutProviderToString(PLYWebCheckoutProvider provider
|
|
|
1007
1233
|
|
|
1008
1234
|
- (NSInteger) findIndexPresentationLoadedFor:(NSString * _Nullable) presentationId {
|
|
1009
1235
|
NSInteger index = 0;
|
|
1010
|
-
for (PLYPresentation
|
|
1011
|
-
if ([presentationLoaded.
|
|
1236
|
+
for (id<PLYPresentation> presentationLoaded in self.presentationsLoaded) {
|
|
1237
|
+
if ([presentationLoaded.screenId isEqualToString: presentationId]) {
|
|
1012
1238
|
return index;
|
|
1013
1239
|
}
|
|
1014
1240
|
index++;
|
|
@@ -1038,14 +1264,13 @@ static NSString * PLYWebCheckoutProviderToString(PLYWebCheckoutProvider provider
|
|
|
1038
1264
|
return dict;
|
|
1039
1265
|
}
|
|
1040
1266
|
|
|
1041
|
-
- (NSDictionary<NSString *, NSObject *> *) resultDictionaryForFetchPresentation:(PLYPresentation
|
|
1267
|
+
- (NSDictionary<NSString *, NSObject *> *) resultDictionaryForFetchPresentation:(id<PLYPresentation> _Nullable) presentation {
|
|
1042
1268
|
NSMutableDictionary<NSString *, NSObject *> *presentationResult = [NSMutableDictionary new];
|
|
1043
1269
|
|
|
1044
|
-
// TODO: fill all parameters.
|
|
1045
1270
|
if (presentation != nil) {
|
|
1046
1271
|
|
|
1047
|
-
if (presentation.
|
|
1048
|
-
[presentationResult setObject:presentation.
|
|
1272
|
+
if (presentation.screenId != nil) {
|
|
1273
|
+
[presentationResult setObject:presentation.screenId forKey:@"id"];
|
|
1049
1274
|
}
|
|
1050
1275
|
|
|
1051
1276
|
if (presentation.placementId != nil) {
|
|
@@ -1064,6 +1289,17 @@ static NSString * PLYWebCheckoutProviderToString(PLYWebCheckoutProvider provider
|
|
|
1064
1289
|
[presentationResult setObject:presentation.abTestVariantId forKey:@"abTestVariantId"];
|
|
1065
1290
|
}
|
|
1066
1291
|
|
|
1292
|
+
// New in v6: campaignId, flowId, height.
|
|
1293
|
+
if (presentation.campaignId != nil) {
|
|
1294
|
+
[presentationResult setObject:presentation.campaignId forKey:@"campaignId"];
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
if (presentation.flowId != nil) {
|
|
1298
|
+
[presentationResult setObject:presentation.flowId forKey:@"flowId"];
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
[presentationResult setObject:[NSNumber numberWithInteger:presentation.height] forKey:@"height"];
|
|
1302
|
+
|
|
1067
1303
|
if (presentation.language != nil) {
|
|
1068
1304
|
[presentationResult setObject:presentation.language forKey:@"language"];
|
|
1069
1305
|
}
|