diff --git a/example/src/App.tsx b/example/src/App.tsx index 9ab4197f..d806855c 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -48,6 +48,27 @@ export default function App() { return [{ fieldKey: 'email', message: 'Invalid email' }]; }} + onRegister={ async (_) => { + await new Promise((resolve) => setTimeout(resolve, 1000)); + + return [{ fieldKey: 'email', message: 'Invalid register!!!!!' }]; + }} + onSubscribeClick={ (e, prevent) => { + console.log('onSubscribeClick', e.nativeEvent); + prevent(); + }} + onLoginClick={ (e, prevent) => { + console.log('onLoginClick', e.nativeEvent); + prevent(); + }} + onDiscoveryLinkClick={ (e, prevent) => { + console.log('onDiscoveryLinkClick', e.nativeEvent); + prevent(); + }} + onDataPolicyClick={ (e, prevent) => { + console.log('onDataPolicyClick', e.nativeEvent); + prevent(); + }} /> diff --git a/ios/NativePaywallModule.mm b/ios/NativePaywallModule.mm index bb17b437..2e21c9cc 100644 --- a/ios/NativePaywallModule.mm +++ b/ios/NativePaywallModule.mm @@ -9,12 +9,20 @@ @implementation NativePaywallModule - (void)emit:(NSString *)event data:(NSString *)data { - NSString *notifName = @"registerNotification"; + NSString *extractedEventName = @""; + NSRange eventPrefixRange = [event rangeOfString:@"event."]; + NSRange resolveSuffixRange = [event rangeOfString:@":resolve" options:NSBackwardsSearch]; - if ([event containsString:@"onFormSubmit"]) { - notifName = @"formSubmitNotification"; + if (eventPrefixRange.location != NSNotFound && resolveSuffixRange.location != NSNotFound && + eventPrefixRange.location + eventPrefixRange.length < resolveSuffixRange.location) { + + NSRange nameRange = NSMakeRange(eventPrefixRange.location + eventPrefixRange.length, + resolveSuffixRange.location - (eventPrefixRange.location + eventPrefixRange.length)); + extractedEventName = [event substringWithRange:nameRange]; } + NSString *notifName = [[NSString alloc] initWithFormat:@"%@Notification", extractedEventName]; + [NSNotificationCenter.defaultCenter postNotificationName:notifName object:self userInfo:@{@"object": data}]; diff --git a/ios/PaywallView.mm b/ios/PaywallView.mm index 78065c11..ada9fea6 100644 --- a/ios/PaywallView.mm +++ b/ios/PaywallView.mm @@ -30,6 +30,8 @@ @implementation PaywallView { NSMutableDictionary *_formSubmitObservers; NSMutableDictionary *_registerObservers; + NSMutableDictionary *_preventActionObservers; + Access * access; BOOL parented; @@ -95,6 +97,7 @@ - (instancetype)initWithFrame:(CGRect)frame _formSubmitObservers = [[NSMutableDictionary alloc] init]; _registerObservers = [[NSMutableDictionary alloc] init]; + _preventActionObservers = [[NSMutableDictionary alloc] init]; } return self; } @@ -207,42 +210,54 @@ - (void)initEvents self.eventEmitter.onPaywallSeen(rnEvent); }]; - [access onSubscribeTappedWithOnce:false :^(ClickEvent * _Nullable event, void (^ _Nonnull callback)(void)) { + [access onSubscribeTappedWithOnce:false :^(ClickEvent * _Nullable event, void (^ _Nonnull prevent)()) { + NSNumber *messageId = [self setupObserverForNotif:@"onSubscribeClickNotification" method:prevent]; + PaywallViewEventEmitter::OnSubscribeClick rnEvent = PaywallViewEventEmitter::OnSubscribeClick { [event.widget UTF8String], [event.actionName UTF8String], - [event.button UTF8String], [event.url UTF8String], + [event.button UTF8String], + [messageId doubleValue], }; self.eventEmitter.onSubscribeClick(rnEvent); }]; - - [access onLoginTappedWithOnce:false :^(ClickEvent * _Nullable event, void (^ _Nonnull callback)(void)) { + + [access onLoginTappedWithOnce:false :^(ClickEvent * _Nullable event, void (^ _Nonnull prevent)()) { + NSNumber *messageId = [self setupObserverForNotif:@"onLoginClickNotification" method:prevent]; + PaywallViewEventEmitter::OnLoginClick rnEvent = PaywallViewEventEmitter::OnLoginClick { [event.widget UTF8String], [event.actionName UTF8String], - [event.button UTF8String], [event.url UTF8String], + [event.button UTF8String], + [messageId doubleValue], }; self.eventEmitter.onLoginClick(rnEvent); }]; - [access onDiscoveryLinkTappedWithOnce:false :^(ClickEvent * _Nullable event, void (^ _Nonnull callback)(void)) { + [access onDiscoveryLinkTappedWithOnce:false :^(ClickEvent * _Nullable event, void (^ _Nonnull prevent)()) { + NSNumber *messageId = [self setupObserverForNotif:@"onDiscoveryLinkClickNotification" method:prevent]; + PaywallViewEventEmitter::OnDiscoveryLinkClick rnEvent = PaywallViewEventEmitter::OnDiscoveryLinkClick { [event.widget UTF8String], [event.actionName UTF8String], [event.button UTF8String], [event.url UTF8String], + [messageId doubleValue], }; self.eventEmitter.onDiscoveryLinkClick(rnEvent); }]; - [access onDataPolicyTappedWithOnce:false :^(ClickEvent * _Nullable event, void (^ _Nonnull callback)(void)) { + [access onDataPolicyTappedWithOnce:false :^(ClickEvent * _Nullable event, void (^ _Nonnull prevent)()) { + NSNumber *messageId = [self setupObserverForNotif:@"onDataPolicyClickNotification" method:prevent]; + PaywallViewEventEmitter::OnDataPolicyClick rnEvent = PaywallViewEventEmitter::OnDataPolicyClick { [event.widget UTF8String], [event.actionName UTF8String], [event.button UTF8String], [event.url UTF8String], + [messageId doubleValue], }; self.eventEmitter.onDataPolicyClick(rnEvent); }]; @@ -255,6 +270,7 @@ - (void)initEvents }; self.eventEmitter.onAlternativeClick(rnEvent); }]; + [access onErrorWithOnce:false :^(ErrorEvent * _Nullable event, void (^ _Nonnull)(void)) { PaywallViewEventEmitter::OnError rnEvent = PaywallViewEventEmitter::OnError { [event.error UTF8String] @@ -283,7 +299,8 @@ - (void)initEvents }]; [access onFormSubmitWithOnce:false submitter:^(FormEvent * _Nonnull event, void (^ _Nonnull method)(NSArray * _Nonnull)) { - NSString *notifName = @"formSubmitNotification"; + NSString *notifName = @"onFormSubmitNotification"; + NSNumber *messageId = @(arc4random()); NSString *fieldsStr = [self arrayToString:event.fields.allKeys]; @@ -307,8 +324,11 @@ - (void)initEvents self.eventEmitter.onFormSubmit(rnEvent); }]; + [access onRegisterWithOnce:false :^(RegisterEvent * _Nonnull event, void (^ _Nonnull method)(NSString * _Nullable)) { - NSString *notifName = @"registerNotification"; + + NSString *notifName = @"onRegisterNotification"; + NSNumber *messageId = @(arc4random()); NSString *newsletterId = event.newsletterId ? event.newsletterId : @""; @@ -332,6 +352,40 @@ - (void)initEvents }]; } +-(NSNumber*)setupObserverForNotif:(NSString*)notifName method:(void (^ _Nonnull)())prevent +{ + NSNumber *messageId = @(arc4random()); + + id observer = [NSNotificationCenter.defaultCenter addObserverForName:notifName + object:nil + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification * _Nonnull notification) { + [self onClickNotification:notification messageId:messageId method:prevent]; + }]; + + [self->_preventActionObservers setObject:observer forKey:messageId]; + + return messageId; +} + +-(void)onClickNotification:(NSNotification*)notification + messageId:(NSNumber*)messageId + method:(void (^ _Nonnull)())prevent +{ + NSDictionary *dataDict = [self extractDictFromNotifObject: notification.userInfo[@"object"]]; + + if (dataDict[@"_messageId"] == messageId) { + [NSNotificationCenter.defaultCenter removeObserver:_preventActionObservers[messageId]]; + [_preventActionObservers removeObjectForKey:messageId]; + + BOOL prevented = [(NSNumber*)dataDict[@"prevented"] boolValue]; + + if (prevented) { + prevent(); + } + } +} + -(void)onFormSubmitNotification:(NSNotification*)notification messageId:(NSNumber*)messageId method:(void (^ _Nonnull)(NSArray * _Nonnull))method @@ -369,9 +423,9 @@ -(void)onRegisterNotification:(NSNotification*)notification if (dict[@"_messageId"] == messageId) { NSArray *remoteArray = dict[@"data"]; - NSString* message; + NSString* message = nil; - if (dict.count > 0) { + if (remoteArray.count > 0) { message = remoteArray[0][@"message"]; } diff --git a/src/Paywall/index.tsx b/src/Paywall/index.tsx index dd5e298c..b2f49c53 100644 --- a/src/Paywall/index.tsx +++ b/src/Paywall/index.tsx @@ -11,6 +11,7 @@ import PaywallView, { type NativeProps, type FormEvent, type RegisterEvent, + type ClickEvent, } from '../specs/PaywallViewNativeComponent'; import NativePaywallModule from '../specs/NativePaywallModule'; import type { NativeSyntheticEvent } from 'react-native'; @@ -18,7 +19,8 @@ import type { NativeSyntheticEvent } from 'react-native'; export interface PaywallProps extends Omit< NativeProps, 'appId' | 'config' | 'texts' | 'styles' | 'variables' | - 'onFormSubmit' | 'onRegister' + 'onFormSubmit' | 'onRegister' | + 'onSubscribeClick' | 'onLoginClick' | 'onDiscoveryLinkClick' | 'onDataPolicyClick' > { /** * Optional unique paywall id. When released, the snippet with the same id @@ -32,6 +34,11 @@ export interface PaywallProps extends Omit< onFormSubmit?: DirectEventHandlerWithResult; onRegister?: DirectEventHandlerWithResult; + + onSubscribeClick?: (event: NativeSyntheticEvent, prevent: () => void) => void | Promise; + onLoginClick?: (event: NativeSyntheticEvent, prevent: () => void) => void | Promise; + onDiscoveryLinkClick?: (event: NativeSyntheticEvent, prevent: () => void) => void | Promise; + onDataPolicyClick?: (event: NativeSyntheticEvent, prevent: () => void) => void | Promise; } export interface PaywallState { @@ -51,6 +58,10 @@ const Paywall = ({ onRelease, onFormSubmit, onRegister, + onSubscribeClick, + onLoginClick, + onDiscoveryLinkClick, + onDataPolicyClick, ...rest }: PaywallProps) => { const { @@ -72,6 +83,7 @@ const Paywall = ({ ...factoryConfig, ...config, }), [config, factoryConfig]); + const serializedConfig = useMemo(() => ( JSON.stringify({ ...rawConfig, @@ -92,13 +104,14 @@ const Paywall = ({ const innerRef = useRef(null); const sendMessage = ( - e: NativeSyntheticEvent, + e: NativeSyntheticEvent, type: string, data: any, ) => { const message = JSON.stringify({ type, data, + prevented: (e.nativeEvent as any).prevented, _messageId: e.nativeEvent._messageId }); @@ -114,6 +127,26 @@ const Paywall = ({ NativePaywallModule.emit('poool:rn:event.' + type, message); }; + const sendClickEvent = async ( + event: NativeSyntheticEvent, + eventName: string, + onClick: ((e: NativeSyntheticEvent, prevent: () => void) => void) | undefined, + ) => { + event.persist(); + + try { + event.nativeEvent.prevented = false; + await onClick?.(event, () => { + event.nativeEvent.prevented = true; + }); + sendMessage(event, eventName + ':resolve', {}); + } catch (error) { + sendMessage(event, eventName + ':reject', { + message: (error as Error).message || error, + }); + } + }; + return ( { + await sendClickEvent(e, 'onSubscribeClick', onSubscribeClick); + }} + onLoginClick={ async (e) => { + await sendClickEvent(e, 'onLoginClick', onLoginClick); + }} + onDataPolicyClick={ async (e) => { + await sendClickEvent(e, 'onDataPolicyClick', onDataPolicyClick); + }} + onDiscoveryLinkClick={ async (e) => { + await sendClickEvent(e, 'onDiscoveryLinkClick', onDiscoveryLinkClick); + }} /> ); }; diff --git a/src/specs/PaywallViewNativeComponent.ts b/src/specs/PaywallViewNativeComponent.ts index b2ce12a3..cc2ac0ca 100644 --- a/src/specs/PaywallViewNativeComponent.ts +++ b/src/specs/PaywallViewNativeComponent.ts @@ -61,6 +61,8 @@ export interface RegisterEvent { export interface ClickEvent extends WidgetEvent { url: string; button: string; + _messageId: CodegenTypes.Double; + prevented: boolean; } export interface AlternativeClickEvent extends WidgetEvent {