diff --git a/FabricExample/ios/Podfile.lock b/FabricExample/ios/Podfile.lock index 96ec8a15..58221f86 100644 --- a/FabricExample/ios/Podfile.lock +++ b/FabricExample/ios/Podfile.lock @@ -1537,7 +1537,7 @@ SPEC CHECKSUMS: React-jsitracing: 39abf32718d8ea6e634f01c7604be086185b78bd React-logger: a790d16be67220e42ba1de47fd5f3bf694961307 React-Mapbuffer: 30f85b69520bd0d33e0da391b756cc213a554352 - react-native-plaid-link-sdk: 41c57993bc36296d0dc2e29a6524bd48d1a6c1c3 + react-native-plaid-link-sdk: 703985c98dc91afaf638f32f0fe4f2904defc94e react-native-safe-area-context: fc0424ee0e1ae0c321c7aeb19c215e75da465676 React-nativeconfig: c9e21ec2c4fdd9965ed627c896f8b62ce75a482a React-NativeModulesApple: f92d16846e045bc4cec6843994e64e497f49173d diff --git a/android/src/main/java/com/plaid/PLKEmbeddedViewManager.kt b/android/src/main/java/com/plaid/PLKEmbeddedViewManager.kt index 66fd266f..bf443aa5 100644 --- a/android/src/main/java/com/plaid/PLKEmbeddedViewManager.kt +++ b/android/src/main/java/com/plaid/PLKEmbeddedViewManager.kt @@ -1,10 +1,22 @@ package com.plaid +import com.facebook.react.module.annotations.ReactModule import com.facebook.react.uimanager.SimpleViewManager import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.uimanager.ViewManagerDelegate import com.facebook.react.uimanager.annotations.ReactProp +import com.facebook.react.viewmanagers.PLKEmbeddedViewManagerDelegate +import com.facebook.react.viewmanagers.PLKEmbeddedViewManagerInterface + +@ReactModule(name = PLKEmbeddedViewManager.REACT_CLASS) +class PLKEmbeddedViewManager : SimpleViewManager(), + PLKEmbeddedViewManagerInterface { + private val delegate: ViewManagerDelegate + + init { + delegate = PLKEmbeddedViewManagerDelegate(this) + } -class PLKEmbeddedViewManager : SimpleViewManager() { override fun getName(): String { return REACT_CLASS } @@ -14,10 +26,14 @@ class PLKEmbeddedViewManager : SimpleViewManager() { } @ReactProp(name = "token") - fun setToken(view: PLKEmbeddedView, token: String?) { + override fun setToken(view: PLKEmbeddedView, token: String?) { view.setToken(token ?: "") } + override fun setIOSPresentationStyle(view: PLKEmbeddedView, value: String?) { + // Unsupported on Android + } + override fun getExportedCustomBubblingEventTypeConstants(): Map { return mutableMapOf( EVENT_NAME to mutableMapOf( diff --git a/android/src/main/java/com/plaid/PlaidModule.kt b/android/src/main/java/com/plaid/PlaidModule.kt index 9c00b4b9..b87bf503 100644 --- a/android/src/main/java/com/plaid/PlaidModule.kt +++ b/android/src/main/java/com/plaid/PlaidModule.kt @@ -8,7 +8,6 @@ import android.util.Log import com.facebook.react.bridge.ActivityEventListener import com.facebook.react.bridge.Callback import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.bridge.ReactContextBaseJavaModule import com.facebook.react.bridge.ReactMethod import com.facebook.react.module.annotations.ReactModule import com.facebook.react.modules.core.DeviceEventManagerModule @@ -19,11 +18,9 @@ import com.plaid.link.configuration.LinkLogLevel import com.plaid.link.configuration.LinkTokenConfiguration import com.plaid.link.event.LinkEvent import com.plaid.link.exception.LinkException -import com.plaid.link.result.LinkAccountSubtype import com.plaid.link.result.LinkResultHandler import org.json.JSONException import org.json.JSONObject -import java.util.ArrayList @ReactModule(name = PlaidModule.NAME) class PlaidModule internal constructor(reactContext: ReactApplicationContext) : @@ -164,7 +161,7 @@ class PlaidModule internal constructor(reactContext: ReactApplicationContext) : override fun addListener(eventName: String?) = Unit - override fun removeListeners(count: Int) = Unit + override fun removeListeners(count: Double) = Unit private fun maybeGetStringField(obj: JSONObject, fieldName: String): String? { if (obj.has(fieldName) && !TextUtils.isEmpty(obj.getString(fieldName))) { diff --git a/android/src/main/java/com/plaid/PlaidPackage.java b/android/src/main/java/com/plaid/PlaidPackage.java index 2343534b..7a259163 100644 --- a/android/src/main/java/com/plaid/PlaidPackage.java +++ b/android/src/main/java/com/plaid/PlaidPackage.java @@ -1,6 +1,6 @@ package com.plaid; -import java.util.Arrays; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -10,6 +10,7 @@ import com.facebook.react.bridge.ModuleSpec; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.common.MapBuilder; import com.facebook.react.module.annotations.ReactModule; import com.facebook.react.module.annotations.ReactModuleList; import com.facebook.react.module.model.ReactModuleInfo; @@ -18,31 +19,47 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +import javax.inject.Provider; @ReactModuleList(nativeModules = {PlaidModule.class}) public class PlaidPackage extends TurboReactPackage implements ViewManagerOnDemandReactPackage { - /** - * {@inheritDoc} - */ + private @Nullable Map mViewManagers; + + private Map getViewManagersMap(final ReactApplicationContext reactContext) { + if (mViewManagers == null) { + Map specs = MapBuilder.newHashMap(); + specs.put( + PLKEmbeddedViewManager.REACT_CLASS, + ModuleSpec.viewManagerSpec( + new Provider() { + @Override + public NativeModule get() { + return new PLKEmbeddedViewManager(); + } + })); + mViewManagers = specs; + } + return mViewManagers; + } + + /** {@inheritDoc} */ @Override public List getViewManagerNames(ReactApplicationContext reactContext) { - return null; + return new ArrayList<>(getViewManagersMap(reactContext).keySet()); } @Override protected List getViewManagers(ReactApplicationContext reactContext) { - return null; + return new ArrayList<>(getViewManagersMap(reactContext).values()); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override - public @Nullable - ViewManager createViewManager( + public @Nullable ViewManager createViewManager( ReactApplicationContext reactContext, String viewManagerName) { - return null; + ModuleSpec spec = getViewManagersMap(reactContext).get(viewManagerName); + return spec != null ? (ViewManager) spec.getProvider().get() : null; } @Override @@ -55,11 +72,6 @@ public NativeModule getModule(String name, @Nonnull ReactApplicationContext reac } } - @Override - public List createViewManagers(ReactApplicationContext reactContext) { - return Arrays.asList( new PLKEmbeddedViewManager() ); - } - @Override public ReactModuleInfoProvider getReactModuleInfoProvider() { try { diff --git a/android/src/paper/java/com/plaid/NativePlaidLinkModuleAndroidSpec.java b/android/src/paper/java/com/plaid/NativePlaidLinkModuleAndroidSpec.java index 57d3f506..bbd7e4c1 100644 --- a/android/src/paper/java/com/plaid/NativePlaidLinkModuleAndroidSpec.java +++ b/android/src/paper/java/com/plaid/NativePlaidLinkModuleAndroidSpec.java @@ -50,5 +50,5 @@ public NativePlaidLinkModuleAndroidSpec(ReactApplicationContext reactContext) { @ReactMethod @DoNotStrip - public abstract void removeListeners(int count); + public abstract void removeListeners(double count); } diff --git a/ios/PLKEmbeddedView.m b/ios/PLKEmbeddedView.m index 01f90e63..01d3adfd 100644 --- a/ios/PLKEmbeddedView.m +++ b/ios/PLKEmbeddedView.m @@ -4,4 +4,4 @@ @interface RCT_EXTERN_MODULE(PLKEmbeddedViewManager, RCTViewManager) RCT_EXPORT_VIEW_PROPERTY(token, NSString) RCT_EXPORT_VIEW_PROPERTY(iOSPresentationStyle, NSString) RCT_EXPORT_VIEW_PROPERTY(onEmbeddedEvent, RCTDirectEventBlock) -@end \ No newline at end of file +@end diff --git a/ios/PLKEmbeddedView.swift b/ios/PLKEmbeddedView.swift index 4d4a4b88..141d2b36 100644 --- a/ios/PLKEmbeddedView.swift +++ b/ios/PLKEmbeddedView.swift @@ -1,23 +1,23 @@ import LinkKit import UIKit -internal final class PLKEmbeddedView: UIView { +@objc public final class PLKEmbeddedView: UIView { // Properties exposed to React Native. - @objc var iOSPresentationStyle: String = "" { + @objc public var iOSPresentationStyle: String = "" { didSet { createNativeEmbeddedView() } } - @objc var token: String = "" { + @objc public var token: String = "" { didSet { createNativeEmbeddedView() } } - @objc var onEmbeddedEvent: RCTDirectEventBlock? + @objc public var onEmbeddedEvent: RCTDirectEventBlock? // MARK: Private diff --git a/ios/PLKEmbeddedViewComponentView.h b/ios/PLKEmbeddedViewComponentView.h new file mode 100644 index 00000000..defc1b53 --- /dev/null +++ b/ios/PLKEmbeddedViewComponentView.h @@ -0,0 +1,15 @@ +#ifdef RCT_NEW_ARCH_ENABLED + +#import + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface PLKEmbeddedViewComponentView : RCTViewComponentView +@end + +NS_ASSUME_NONNULL_END + +#endif // RCT_NEW_ARCH_ENABLED diff --git a/ios/PLKEmbeddedViewComponentView.mm b/ios/PLKEmbeddedViewComponentView.mm new file mode 100644 index 00000000..f3c4e622 --- /dev/null +++ b/ios/PLKEmbeddedViewComponentView.mm @@ -0,0 +1,85 @@ +#ifdef RCT_NEW_ARCH_ENABLED + +#import "PLKEmbeddedViewComponentView.h" +#import "PLKFabricHelpers.h" + +#import +#import +#import + +#import +#import +#import +#import + +using namespace facebook::react; + +@implementation PLKEmbeddedViewComponentView { + PLKEmbeddedView *_view; +} + +- (instancetype)initWithFrame:(CGRect)frame +{ + if (self = [super initWithFrame:frame]) { + static const auto defaultProps = std::make_shared(); + _props = defaultProps; + [self prepareView]; + } + + return self; +} + +- (void)prepareView +{ + _view = [[PLKEmbeddedView alloc] init]; + + __weak __typeof__(self) weakSelf = self; + + [_view setOnEmbeddedEvent:^(NSDictionary* event) { + __typeof__(self) strongSelf = weakSelf; + + if (strongSelf != nullptr && strongSelf->_eventEmitter != nullptr) { + std::dynamic_pointer_cast(strongSelf->_eventEmitter)->onEmbeddedEvent({ + .embeddedEventName = RCTStringFromNSString(event[@"embeddedEventName"]), + .eventName = RCTStringFromNSString(event[@"eventName"]), + .error = PLKConvertIdToFollyDynamic(event[@"error"]), + .publicToken = RCTStringFromNSString(event[@"publicToken"]), + .metadata = PLKConvertIdToFollyDynamic(event[@"metadata"]), + }); + } + }]; + self.contentView = _view; +} + +#pragma mark - RCTComponentViewProtocol + ++ (ComponentDescriptorProvider)componentDescriptorProvider +{ + return concreteComponentDescriptorProvider(); +} + + +- (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &)oldProps +{ + const auto &newProps = static_cast(*props); + _view.token = RCTNSStringFromStringNilIfEmpty(newProps.token); + _view.iOSPresentationStyle = RCTNSStringFromStringNilIfEmpty(newProps.token); + + [super updateProps:props oldProps:oldProps]; +} + +- (void)prepareForRecycle +{ + [super prepareForRecycle]; + [self prepareView]; +} + + +@end + +Class PLKEmbeddedViewCls(void) +{ + return PLKEmbeddedViewComponentView.class; +} + +#endif // RCT_NEW_ARCH_ENABLED diff --git a/ios/PLKFabricHelpers.h b/ios/PLKFabricHelpers.h new file mode 100644 index 00000000..cf70d5e7 --- /dev/null +++ b/ios/PLKFabricHelpers.h @@ -0,0 +1,70 @@ +#import +#import +#import + +#if __has_include() +#import +#else +#import +#endif + +// copied from RCTFollyConvert +folly::dynamic PLKConvertIdToFollyDynamic(id json) +{ + if (json == nil || json == (id)kCFNull) { + return nullptr; + } else if ([json isKindOfClass:[NSNumber class]]) { + const char *objCType = [json objCType]; + switch (objCType[0]) { + // This is a c++ bool or C99 _Bool. On some platforms, BOOL is a bool. + case _C_BOOL: + return (bool)[json boolValue]; + case _C_CHR: + // On some platforms, objc BOOL is a signed char, but it + // might also be a small number. Use the same hack JSC uses + // to distinguish them: + // https://phabricator.intern.facebook.com/diffusion/FBS/browse/master/fbobjc/xplat/third-party/jsc/safari-600-1-4-17/JavaScriptCore/API/JSValue.mm;b8ee03916489f8b12143cd5c0bca546da5014fc9$901 + if ([json isKindOfClass:[@YES class]]) { + return (bool)[json boolValue]; + } else { + return [json longLongValue]; + } + case _C_UCHR: + case _C_SHT: + case _C_USHT: + case _C_INT: + case _C_UINT: + case _C_LNG: + case _C_ULNG: + case _C_LNG_LNG: + case _C_ULNG_LNG: + return [json longLongValue]; + + case _C_FLT: + case _C_DBL: + return [json doubleValue]; + + // default: + // fall through + } + } else if ([json isKindOfClass:[NSString class]]) { + NSData *data = [json dataUsingEncoding:NSUTF8StringEncoding]; + return std::string(reinterpret_cast(data.bytes), data.length); + } else if ([json isKindOfClass:[NSArray class]]) { + folly::dynamic array = folly::dynamic::array; + for (id element in json) { + array.push_back(PLKConvertIdToFollyDynamic(element)); + } + return array; + } else if ([json isKindOfClass:[NSDictionary class]]) { + __block folly::dynamic object = folly::dynamic::object(); + + [json enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, __unused BOOL *stop) { + object.insert(PLKConvertIdToFollyDynamic(key), PLKConvertIdToFollyDynamic(value)); + }]; + + return object; + } + + return nil; +} diff --git a/ios/react_native_plaid_link_sdk.h b/ios/react_native_plaid_link_sdk.h new file mode 100644 index 00000000..92f2c27b --- /dev/null +++ b/ios/react_native_plaid_link_sdk.h @@ -0,0 +1,2 @@ +// xcode tries to import this header in the auto-generated swift header +// same as here: https://github.com/rnmapbox/maps/blob/b76c000a237b9757a616982d6c07f6ecfd7d60a9/ios/RNMBX/rnmapbox_maps.h diff --git a/package.json b/package.json index 7d0a59da..cc1db58f 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ }, "codegenConfig": { "name": "rnplaidlink", - "type": "modules", + "type": "all", "jsSrcsDir": "./src/fabric", "android": { "javaPackageName": "com.plaid" diff --git a/react-native-plaid-link-sdk.podspec b/react-native-plaid-link-sdk.podspec index f42f878b..b8e0adcf 100644 --- a/react-native-plaid-link-sdk.podspec +++ b/react-native-plaid-link-sdk.podspec @@ -16,6 +16,10 @@ Pod::Spec.new do |s| s.source = { :git => "https://github.com/plaid/react-native-plaid-link-sdk.git", :tag => "v#{s.version}" } s.source_files = "ios/**/*.{h,m,mm,swift}" + # we need this since Swift generates import for `react_native_plaid_link_sdk/react_native_plaid_link_sdk.h` and we have to fake it + s.header_dir = "react_native_plaid_link_sdk" + # we don't want this to be seen by Swift + s.private_header_files = 'ios/PLKFabricHelpers.h' if fabric_enabled install_modules_dependencies(s) diff --git a/src/EmbeddedLink/EmbeddedLinkView.tsx b/src/EmbeddedLink/EmbeddedLinkView.tsx index 0243de20..9c36a223 100644 --- a/src/EmbeddedLink/EmbeddedLinkView.tsx +++ b/src/EmbeddedLink/EmbeddedLinkView.tsx @@ -17,7 +17,7 @@ import { } from '../Types'; type EmbeddedLinkProps = { - token: String, + token: string, iOSPresentationStyle: LinkIOSPresentationStyle, onEvent: LinkOnEventListener | undefined, onSuccess: LinkSuccessListener, diff --git a/src/EmbeddedLink/NativeEmbeddedLinkView.tsx b/src/EmbeddedLink/NativeEmbeddedLinkView.tsx index da05fb15..3b37d481 100644 --- a/src/EmbeddedLink/NativeEmbeddedLinkView.tsx +++ b/src/EmbeddedLink/NativeEmbeddedLinkView.tsx @@ -1,9 +1,3 @@ -import { requireNativeComponent } from 'react-native'; +import NativeEmbeddedLinkView from '../fabric/EmbeddedLinkViewNativeComponent'; -// Error "Tried to register two views with the same name PLKEmbeddedView" -// will be thrown during hot reload when any change is made to the -// file that is calling this requireNativeComponent('PLKEmbeddedView') call. -// Leaving this in its own file resolves this issue. -const NativeEmbeddedLinkView = requireNativeComponent('PLKEmbeddedView'); - -export default NativeEmbeddedLinkView; \ No newline at end of file +export default NativeEmbeddedLinkView; diff --git a/src/fabric/EmbeddedLinkViewNativeComponent.ts b/src/fabric/EmbeddedLinkViewNativeComponent.ts new file mode 100644 index 00000000..7167025f --- /dev/null +++ b/src/fabric/EmbeddedLinkViewNativeComponent.ts @@ -0,0 +1,24 @@ +import type { ViewProps } from 'react-native'; +import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent'; +// @ts-ignore getting the types from the codegen +import { DirectEventHandler, UnsafeMixed } from 'react-native/Libraries/Types/CodegenTypes'; + +export interface NativeProps extends ViewProps { + token?: string; + iOSPresentationStyle?: string; + onEmbeddedEvent: DirectEventHandler<{ + embeddedEventName: string; + // for EmbeddedEvent + eventName?: string; + // for EmbeddedExit + error?: UnsafeMixed; + // for EmbeddedSuccess + publicToken?: string; + // for all of them + metadata?: UnsafeMixed; + }>; +} + +export default codegenNativeComponent( + 'PLKEmbeddedView', +); diff --git a/src/fabric/NativePlaidLinkModuleAndroid.ts b/src/fabric/NativePlaidLinkModuleAndroid.ts index 4eaeddc9..ee34716c 100644 --- a/src/fabric/NativePlaidLinkModuleAndroid.ts +++ b/src/fabric/NativePlaidLinkModuleAndroid.ts @@ -1,7 +1,7 @@ // we use Object type because methods on the native side use NSDictionary and ReadableMap // and we want to stay compatible with those import {TurboModuleRegistry, TurboModule} from 'react-native'; -import {Int32} from 'react-native/Libraries/Types/CodegenTypes'; +import {Double} from 'react-native/Libraries/Types/CodegenTypes'; import {UnsafeObject} from './fabricUtils'; import {LinkSuccess, LinkExit} from '../Types'; @@ -20,7 +20,7 @@ export interface Spec extends TurboModule { ): void; // those two are here for event emitter methods addListener(eventName: string): void; - removeListeners(count: Int32): void; + removeListeners(count: Double): void; } export default TurboModuleRegistry.get('PlaidAndroid');