diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
index ee5a147c..b7988a4f 100644
--- a/android/src/main/AndroidManifest.xml
+++ b/android/src/main/AndroidManifest.xml
@@ -4,7 +4,7 @@
+ android:value="7.3.0" />
diff --git a/ios/RNLinksdk.m b/ios/RNLinksdk.m
index 5b8b6344..545c2f04 100644
--- a/ios/RNLinksdk.m
+++ b/ios/RNLinksdk.m
@@ -117,7 +117,7 @@ @implementation RNLinksdk
RCT_EXPORT_MODULE();
+ (NSString*)sdkVersion {
- return @"7.2.1"; // SDK_VERSION
+ return @"7.3.0"; // SDK_VERSION
}
+ (NSString*)objCBridgeVersion {
diff --git a/ios/RNLinksdk.m-e b/ios/RNLinksdk.m-e
new file mode 100644
index 00000000..06a3cea7
--- /dev/null
+++ b/ios/RNLinksdk.m-e
@@ -0,0 +1,905 @@
+#import "RNLinksdk.h"
+
+#import
+#import
+#import
+#import
+
+#import
+
+static NSString* const kRNLinkKitConfigPublicKeyKey = @"publicKey";
+static NSString* const kRNLinkKitConfigEnvKey = @"environment";
+static NSString* const kRNLinkKitConfigProductsKey = @"products";
+static NSString* const kRNLinkKitConfigClientNameKey = @"clientName";
+static NSString* const kRNLinkKitConfigWebhookKey = @"webhook";
+static NSString* const kRNLinkKitConfigLinkCustomizationName = @"linkCustomizationName";
+static NSString* const kRNLinkKitConfigLinkTokenKey = @"token";
+static NSString* const kRNLinkKitConfigSelectAccountKey = @"selectAccount";
+static NSString* const kRNLinkKitConfigUserLegalNameKey = @"userLegalName";
+static NSString* const kRNLinkKitConfigUserEmailAddressKey = @"userEmailAddress";
+static NSString* const kRNLinkKitConfigUserPhoneNumberKey = @"userPhoneNumber";
+static NSString* const kRNLinkKitConfigAccountSubtypes = @"accountSubtypes";
+static NSString* const kRNLinkKitConfigCountryCodesKey = @"countryCodes";
+static NSString* const kRNLinkKitConfigLanguageKey = @"language";
+static NSString* const kRNLinkKitConfigInstitutionKey = @"institution";
+static NSString* const kRNLinkKitConfigNoLoadingStateKey = @"noLoadingState";
+static NSString* const kRNLinkKitConfigLongtailAuthKey = @"longtailAuth";
+static NSString* const kRNLinkKitConfigApiVersionKey = @"apiVersion";
+static NSString* const kRNLinkKitConfigOAuthRedirectUriKeyPath = @"oauthConfiguration.redirectUri";
+static NSString* const kRNLinkKitConfigOAuthNonceKeyPath = @"oauthConfiguration.nonce";
+
+static NSString* const kRNLinkKitOnEventEvent = @"onEvent";
+static NSString* const kRNLinkKitEventErrorKey = @"error";
+static NSString* const kRNLinkKitEventNameKey = @"event";
+static NSString* const kRNLinkKitEventMetadataKey = @"metadata";
+static NSString* const kRNLinkKitVersionConstant = @"version";
+
+NSString* const kRNLinkKitLinkTokenPrefix = @"link-";
+NSString* const kRNLinkKitItemAddTokenPrefix = @"item-add-";
+NSString* const kRNLinkKitPaymentTokenPrefix = @"payment";
+NSString* const kRNLinkKitDepositSwitchTokenPrefix = @"deposit-switch-";
+NSString* const kRNLinkKitPublicTokenPrefix = @"public-";
+
+@interface RNLinksdk ()
+@property (nonatomic, strong) id linkHandler;
+@property (nonatomic, strong) UIViewController* presentingViewController;
+@property (nonatomic, strong) RCTResponseSenderBlock successCallback;
+@property (nonatomic, strong) RCTResponseSenderBlock exitCallback;
+@property (nonatomic, assign) BOOL hasObservers;
+@property (nonatomic, copy) NSString *institutionID;
+@property (nonatomic, nullable, strong) NSError *creationError;
+@end
+
+#pragma mark -
+
+// Category to ensure both the old, typo spelling of `insitution` and
+// the corrected spelling `institution` are visible to the implementation
+// to allow compiling against any LinkKit dependency
+@interface PLKSuccessMetadata (InstitutionTypoFix)
+
+- (PLKInstitution * __nonnull)institution;
+- (PLKInstitution * __nonnull)insitution;
+
+@end
+
+// Class to have a distinct +load method to hook into the runtime before
+// SDK logic executes
+@interface PLKSuccessMetadataTypoFix : NSObject
+
+@end
+
+@implementation PLKSuccessMetadataTypoFix
+
++ (void)load {
+ static dispatch_once_t onceToken;
+ // dispatch_once out of an abundance of caution in case +load is ever called multiple times
+ dispatch_once(&onceToken, ^{
+ Class targetClass = NSClassFromString(@"PLKSuccessMetadata");
+ if (targetClass == Nil) {
+ return;
+ }
+
+ SEL typoSel = NSSelectorFromString(@"insitution");
+ SEL correctSel = NSSelectorFromString(@"institution");
+
+ BOOL respondsToTypoSel = class_respondsToSelector(targetClass, typoSel);
+ BOOL respondsToCorrectSel = class_respondsToSelector(targetClass, correctSel);
+
+ BOOL respondsToBoth = respondsToCorrectSel && respondsToTypoSel;
+
+ // If PLKSuccessMetadata responds to both, no swizzling is necessary
+ if (respondsToBoth) {
+ return;
+ }
+
+ BOOL respondsToNeither = !(respondsToCorrectSel || respondsToTypoSel);
+ // If PLKSuccessMetadata responds to neither, swizzling cannot fix this
+ if (respondsToNeither) {
+ NSString *githubIssueURLString = @"https://github.com/plaid/react-native-plaid-link-sdk/issues/new?assignees=&labels=&template=bug_report.md&title=";
+ NSAssert(NO, @"%@ does not respond to correctly spelled %@ or legacy, typo %@. This is a bug in either react-native-plaid-link-sdk, or LinkKit. Please file an issue at: %@", NSStringFromClass(targetClass), NSStringFromSelector(correctSel), NSStringFromSelector(typoSel), githubIssueURLString);
+ return;
+ }
+
+ SEL existingSel = respondsToCorrectSel ? correctSel : typoSel;
+ SEL missingSel = respondsToTypoSel ? correctSel : typoSel;
+
+ Method method = class_getInstanceMethod(targetClass, existingSel);
+ const char* types = method_getTypeEncoding(method);
+ IMP implementation = class_getMethodImplementation(targetClass, existingSel);
+ class_addMethod(targetClass, missingSel, implementation, types);
+ });
+}
+
+@end
+
+@implementation RNLinksdk
+
+RCT_EXPORT_MODULE();
+
++ (NSString*)sdkVersion {
+ return @"7.3.0"; // SDK_VERSION
+}
+
++ (NSString*)objCBridgeVersion {
+ return @"1.1.0";
+}
+
++ (BOOL)requiresMainQueueSetup
+{
+ // Because LinkKit relies on UIKit.
+ return YES;
+}
+
+- (dispatch_queue_t)methodQueue
+{
+ return dispatch_get_main_queue();
+}
+
+- (NSArray *)supportedEvents
+{
+ return @[kRNLinkKitOnEventEvent];
+}
+
+- (NSDictionary *)constantsToExport {
+ return @{
+ kRNLinkKitVersionConstant: [NSString stringWithFormat:@"%s+%.0f", LinkKitVersionString, LinkKitVersionNumber],
+ };
+}
+
+- (void)startObserving {
+ self.hasObservers = YES;
+ [super startObserving];
+}
+
+- (void)stopObserving {
+ [super stopObserving];
+ self.hasObservers = NO;
+}
+
+RCT_EXPORT_METHOD(continueFromRedirectUriString:(NSString *)redirectUriString) {
+ NSURL *receivedRedirectUri = (id)redirectUriString == [NSNull null] ? nil : [NSURL URLWithString:redirectUriString];
+
+ if (receivedRedirectUri && self.linkHandler) {
+ [self.linkHandler continueFromRedirectUri:receivedRedirectUri];
+ }
+}
+
+RCT_EXPORT_METHOD(create:(NSDictionary*)configuration) {
+ // Configuration
+ NSString *linkTokenInput = [RCTConvert NSString:configuration[kRNLinkKitConfigLinkTokenKey]];
+ NSString *institution = [RCTConvert NSString:configuration[kRNLinkKitConfigInstitutionKey]];
+
+ BOOL isUsingLinkToken = [linkTokenInput length] && [linkTokenInput hasPrefix:kRNLinkKitLinkTokenPrefix];
+
+ __weak typeof(self) weakSelf = self;
+ void (^onSuccess)(PLKLinkSuccess *) = ^(PLKLinkSuccess *success) {
+ __typeof(weakSelf) strongSelf = weakSelf;
+
+ if (strongSelf.successCallback) {
+ NSDictionary *jsMetadata = [RNLinksdk dictionaryFromSuccess:success];
+ strongSelf.successCallback(@[jsMetadata]);
+ strongSelf.successCallback = nil;
+ }
+ };
+
+ void (^onExit)(PLKLinkExit *) = ^(PLKLinkExit *exit) {
+ __typeof(weakSelf) strongSelf = weakSelf;
+
+ if (strongSelf.exitCallback) {
+ NSDictionary *exitMetadata = [RNLinksdk dictionaryFromExit:exit];
+ if (exit.error) {
+ strongSelf.exitCallback(@[exitMetadata[@"error"], exitMetadata]);
+ } else {
+ strongSelf.exitCallback(@[[NSNull null], exitMetadata]);
+ }
+ strongSelf.exitCallback = nil;
+ }
+ };
+
+ void (^onEvent)(PLKLinkEvent *) = ^(PLKLinkEvent *event) {
+ __typeof(weakSelf) strongSelf = weakSelf;
+ if (strongSelf.hasObservers) {
+ NSDictionary *eventDictionary = [RNLinksdk dictionaryFromEvent:event];
+ [strongSelf sendEventWithName:kRNLinkKitOnEventEvent
+ body:eventDictionary];
+ }
+ };
+
+ if (isUsingLinkToken) {
+ PLKLinkTokenConfiguration *config = [self getLinkTokenConfiguration:configuration
+ onSuccessHandler:onSuccess];
+ config.onEvent = onEvent;
+ config.onExit = onExit;
+ config.noLoadingState = configuration[kRNLinkKitConfigNoLoadingStateKey];
+
+ NSError *creationError = nil;
+ self.linkHandler = [PLKPlaid createWithLinkTokenConfiguration:config
+ error:&creationError];
+ self.creationError = creationError;
+ } else {
+ PLKLinkPublicKeyConfiguration *config = [self getLegacyLinkConfiguration:configuration
+ onSuccessHandler:onSuccess];
+ config.onEvent = onEvent;
+ config.onExit = onExit;
+ NSError *creationError = nil;
+ self.linkHandler = [PLKPlaid createWithLinkPublicKeyConfiguration:config
+ error:&creationError];
+ self.creationError = creationError;
+ }
+
+ if ([institution length] > 0) {
+ self.institutionID = institution;
+ }
+}
+
+RCT_EXPORT_METHOD(open:(RCTResponseSenderBlock)onSuccess :(RCTResponseSenderBlock)onExit) {
+ if (self.linkHandler) {
+ self.successCallback = onSuccess;
+ self.exitCallback = onExit;
+ self.presentingViewController = RCTPresentedViewController();
+ NSDictionary *options = self.institutionID.length > 0 ? @{@"institution_id": self.institutionID} : @{};
+
+ __weak typeof(self) weakSelf = self;
+ void(^presentationHandler)(UIViewController *) = ^(UIViewController *linkViewController) {
+ [weakSelf.presentingViewController presentViewController:linkViewController animated:YES completion:nil];
+ };
+ void(^dismissalHandler)(UIViewController *) = ^(UIViewController *linkViewController) {
+ [weakSelf dismiss];
+ };
+ [self.linkHandler openWithPresentationHandler:presentationHandler dismissalHandler:dismissalHandler options:options];
+ } else {
+ id error = self.creationError ? RCTJSErrorFromNSError(self.creationError) : RCTMakeError(@"create was not called", nil, nil);
+ onExit(@[error]);
+ }
+}
+
+RCT_EXPORT_METHOD(dismiss) {
+ [self.presentingViewController dismissViewControllerAnimated:YES
+ completion:nil];
+ self.presentingViewController = nil;
+ self.linkHandler = nil;
+}
+
+- (PLKLinkTokenConfiguration *)getLinkTokenConfiguration:(NSDictionary *)configuration
+ onSuccessHandler:(void(^)(PLKLinkSuccess *))onSuccessHandler {
+ NSString *linkTokenInput = [RCTConvert NSString:configuration[kRNLinkKitConfigLinkTokenKey]];
+
+ return [PLKLinkTokenConfiguration createWithToken:linkTokenInput onSuccess:onSuccessHandler];
+}
+
+- (PLKLinkPublicKeyConfiguration *)getLegacyLinkConfiguration:(NSDictionary *)configuration
+ onSuccessHandler:(void(^)(PLKLinkSuccess *))onSuccessHandler {
+ NSString *key = [RCTConvert NSString:configuration[kRNLinkKitConfigPublicKeyKey]];
+ NSString *tokenInput = [RCTConvert NSString:configuration[kRNLinkKitConfigLinkTokenKey]];
+ NSString *env = [RCTConvert NSString:configuration[kRNLinkKitConfigEnvKey]];
+ NSArray *productsInput = [RCTConvert NSStringArray:configuration[kRNLinkKitConfigProductsKey]];
+ NSString *clientName = [RCTConvert NSString:configuration[kRNLinkKitConfigClientNameKey]];
+ NSString *webhook = [RCTConvert NSString:configuration[kRNLinkKitConfigWebhookKey]];
+ NSString *linkCustomizationName = [RCTConvert NSString:configuration[kRNLinkKitConfigLinkCustomizationName]];
+ NSString *userLegalName = [RCTConvert NSString:configuration[kRNLinkKitConfigUserLegalNameKey]];
+ NSString *userEmailAddress = [RCTConvert NSString:configuration[kRNLinkKitConfigUserEmailAddressKey]];
+ NSString *userPhoneNumber = [RCTConvert NSString:configuration[kRNLinkKitConfigUserPhoneNumberKey]];
+ NSString *oauthRedirectUriInput = [configuration valueForKeyPath:kRNLinkKitConfigOAuthRedirectUriKeyPath];
+ NSString *oauthRedirectUriString = [RCTConvert NSString:oauthRedirectUriInput];
+ NSString *oauthNonceInput = [configuration valueForKeyPath:kRNLinkKitConfigOAuthNonceKeyPath];
+ NSString *oauthNonce = [RCTConvert NSString:oauthNonceInput];
+ id accountSubtypesInput = configuration[kRNLinkKitConfigAccountSubtypes];
+ NSArray*> *accountSubtypeDictionaries = [RCTConvert NSDictionaryArray:accountSubtypesInput];
+ NSArray *countryCodes = [RCTConvert NSStringArray:configuration[kRNLinkKitConfigCountryCodesKey]];
+ NSString *language = [RCTConvert NSString:configuration[kRNLinkKitConfigLanguageKey]];
+
+ PLKLinkPublicKeyConfigurationToken *token;
+ BOOL isPaymentToken = [tokenInput hasPrefix:kRNLinkKitPaymentTokenPrefix];
+ BOOL isItemAddToken = [tokenInput hasPrefix:kRNLinkKitItemAddTokenPrefix];
+ BOOL isDepositSwitchToken = [tokenInput hasPrefix:kRNLinkKitDepositSwitchTokenPrefix];
+ BOOL isPublicToken = [tokenInput hasPrefix:kRNLinkKitPublicTokenPrefix];
+ if (isPaymentToken) {
+ token = [PLKLinkPublicKeyConfigurationToken createWithPaymentToken:tokenInput publicKey:key];
+ } else if (isItemAddToken) {
+ token = [PLKLinkPublicKeyConfigurationToken createWithPublicToken:tokenInput publicKey:key];
+ } else if (isDepositSwitchToken) {
+ token = [PLKLinkPublicKeyConfigurationToken createWithDepositSwitchToken:tokenInput publicKey:key];
+ } else if (isPublicToken) {
+ token = [PLKLinkPublicKeyConfigurationToken createWithPublicToken:tokenInput publicKey:key];
+ } else {
+ token = [PLKLinkPublicKeyConfigurationToken createWithPublicKey:key];
+ }
+
+ PLKEnvironment environment = [RNLinksdk environmentFromString:env];
+ NSArray *products = [RNLinksdk productsArrayFromProductsStringArray:productsInput];
+ PLKLinkPublicKeyConfiguration *linkConfiguration = [[PLKLinkPublicKeyConfiguration alloc] initWithClientName:clientName
+ environment:environment
+ products:products
+ language:language
+ token:token
+ countryCodes:countryCodes
+ onSuccess:onSuccessHandler];
+ if ([linkCustomizationName length] > 0) {
+ linkConfiguration.linkCustomizationName = linkCustomizationName;
+ }
+ if ([webhook length] > 0) {
+ linkConfiguration.webhook = [NSURL URLWithString:webhook];
+ }
+ if ([userLegalName length] > 0) {
+ linkConfiguration.userLegalName = userLegalName;
+ }
+ if ([userEmailAddress length] > 0) {
+ linkConfiguration.userEmailAddress = userEmailAddress;
+ }
+ if ([userPhoneNumber length] > 0) {
+ linkConfiguration.userPhoneNumber = userPhoneNumber;
+ }
+ if ([oauthRedirectUriString length] > 0 && [oauthNonce length] > 0) {
+ NSURL* oauthRedirectUri = [NSURL URLWithString:oauthRedirectUriString];
+ linkConfiguration.oauthConfiguration = [PLKOAuthNonceConfiguration createWithNonce:oauthNonce
+ redirectUri:oauthRedirectUri];
+ }
+ if ([accountSubtypeDictionaries count] > 0) {
+ linkConfiguration.accountSubtypes = [RNLinksdk accountSubtypesArrayFromAccountSubtypeDictionaries:accountSubtypeDictionaries];
+ }
+
+ return linkConfiguration;
+}
+
+#pragma mark - Bridging
+
++ (PLKEnvironment)environmentFromString:(NSString *)string {
+ if ([string isEqualToString:@"production"]) {
+ return PLKEnvironmentProduction;
+ }
+
+ if ([string isEqualToString:@"sandbox"]) {
+ return PLKEnvironmentSandbox;
+ }
+
+ if ([string isEqualToString:@"development"]) {
+ return PLKEnvironmentDevelopment;
+ }
+
+ // Default to Development
+ NSLog(@"Unexpected environment string value: %@. Expected one of: production, sandbox, or development.", string);
+ return PLKEnvironmentDevelopment;
+}
+
++ (NSArray *)productsArrayFromProductsStringArray:(NSArray *)productsStringArray {
+ NSMutableArray *results = [NSMutableArray arrayWithCapacity:productsStringArray.count];
+
+ for (NSString *productString in productsStringArray) {
+ NSNumber *product = [self productFromProductString:productString];
+ if (product) {
+ [results addObject:product];
+ }
+ }
+
+ return [results copy];
+}
+
++ (NSNumber * __nullable)productFromProductString:(NSString *)productString {
+ NSDictionary *productStringMap = @{
+ @"auth": @(PLKProductAuth),
+ @"identity": @(PLKProductIdentity),
+ @"income": @(PLKProductIncome),
+ @"transactions": @(PLKProductTransactions),
+ @"assets": @(PLKProductAssets),
+ @"liabilities": @(PLKProductLiabilities),
+ @"investments": @(PLKProductInvestments),
+ @"deposit_switch": @(PLKProductDepositSwitch),
+ };
+ return productStringMap[productString.lowercaseString];
+}
+
++ (NSArray> *)accountSubtypesArrayFromAccountSubtypeDictionaries:(NSArray *> *)accountSubtypeDictionaries {
+ __block NSMutableArray> *results = [NSMutableArray array];
+
+ for (NSDictionary *accountSubtypeDictionary in accountSubtypeDictionaries) {
+ NSString *type = accountSubtypeDictionary[@"type"];
+ NSString *subtype = accountSubtypeDictionary[@"subtype"];
+ id result = [self accountSubtypeFromTypeString:type subtypeString:subtype];
+ if (result) {
+ [results addObject:result];
+ }
+ }
+
+ return [results copy];
+}
+
++ (id)accountSubtypeFromTypeString:(NSString *)typeString
+ subtypeString:(NSString *)subtypeString {
+ NSString *normalizedTypeString = typeString.lowercaseString;
+ NSString *normalizedSubtypeString = subtypeString.lowercaseString;
+ if ([normalizedTypeString isEqualToString:@"other"]) {
+ if ([normalizedSubtypeString isEqualToString:@"all"]) {
+ return [PLKAccountSubtypeOther createWithValue:PLKAccountSubtypeValueOtherAll];
+ } else if ([normalizedSubtypeString isEqualToString:@"other"]) {
+ return [PLKAccountSubtypeOther createWithValue:PLKAccountSubtypeValueOtherOther];
+ } else {
+ return [PLKAccountSubtypeOther createWithRawStringValue:normalizedSubtypeString];
+ }
+ } else if ([normalizedTypeString isEqualToString:@"credit"]) {
+ if ([normalizedSubtypeString isEqualToString:@"all"]) {
+ return [PLKAccountSubtypeCredit createWithValue:PLKAccountSubtypeValueCreditAll];
+ } else if ([normalizedSubtypeString isEqualToString:@"credit card"]) {
+ return [PLKAccountSubtypeCredit createWithValue:PLKAccountSubtypeValueCreditCreditCard];
+ } else if ([normalizedSubtypeString isEqualToString:@"paypal"]) {
+ return [PLKAccountSubtypeCredit createWithValue:PLKAccountSubtypeValueCreditPaypal];
+ } else {
+ return [PLKAccountSubtypeCredit createWithUnknownValue:subtypeString];
+ }
+ } else if ([normalizedTypeString isEqualToString:@"loan"]) {
+ if ([normalizedSubtypeString isEqualToString:@"all"]) {
+ return [PLKAccountSubtypeLoan createWithValue:PLKAccountSubtypeValueLoanAll];
+ } else if ([normalizedSubtypeString isEqualToString:@"auto"]) {
+ return [PLKAccountSubtypeLoan createWithValue:PLKAccountSubtypeValueLoanAuto];
+ } else if ([normalizedSubtypeString isEqualToString:@"business"]) {
+ return [PLKAccountSubtypeLoan createWithValue:PLKAccountSubtypeValueLoanBusiness];
+ } else if ([normalizedSubtypeString isEqualToString:@"commercial"]) {
+ return [PLKAccountSubtypeLoan createWithValue:PLKAccountSubtypeValueLoanCommercial];
+ } else if ([normalizedSubtypeString isEqualToString:@"construction"]) {
+ return [PLKAccountSubtypeLoan createWithValue:PLKAccountSubtypeValueLoanConstruction];
+ } else if ([normalizedSubtypeString isEqualToString:@"consumer"]) {
+ return [PLKAccountSubtypeLoan createWithValue:PLKAccountSubtypeValueLoanConsumer];
+ } else if ([normalizedSubtypeString isEqualToString:@"home equity"]) {
+ return [PLKAccountSubtypeLoan createWithValue:PLKAccountSubtypeValueLoanHomeEquity];
+ } else if ([normalizedSubtypeString isEqualToString:@"line of credit"]) {
+ return [PLKAccountSubtypeLoan createWithValue:PLKAccountSubtypeValueLoanLineOfCredit];
+ } else if ([normalizedSubtypeString isEqualToString:@"loan"]) {
+ return [PLKAccountSubtypeLoan createWithValue:PLKAccountSubtypeValueLoanLoan];
+ } else if ([normalizedSubtypeString isEqualToString:@"mortgage"]) {
+ return [PLKAccountSubtypeLoan createWithValue:PLKAccountSubtypeValueLoanMortgage];
+ } else if ([normalizedSubtypeString isEqualToString:@"overdraft"]) {
+ return [PLKAccountSubtypeLoan createWithValue:PLKAccountSubtypeValueLoanOverdraft];
+ } else if ([normalizedSubtypeString isEqualToString:@"student"]) {
+ return [PLKAccountSubtypeLoan createWithValue:PLKAccountSubtypeValueLoanStudent];
+ } else {
+ return [PLKAccountSubtypeLoan createWithUnknownValue:subtypeString];
+ }
+ } else if ([normalizedTypeString isEqualToString:@"depository"]) {
+ if ([normalizedSubtypeString isEqualToString:@"all"]) {
+ return [PLKAccountSubtypeDepository createWithValue:PLKAccountSubtypeValueDepositoryAll];
+ } else if ([normalizedSubtypeString isEqualToString:@"cash management"]) {
+ return [PLKAccountSubtypeDepository createWithValue:PLKAccountSubtypeValueDepositoryCashManagement];
+ } else if ([normalizedSubtypeString isEqualToString:@"cd"]) {
+ return [PLKAccountSubtypeDepository createWithValue:PLKAccountSubtypeValueDepositoryCd];
+ } else if ([normalizedSubtypeString isEqualToString:@"checking"]) {
+ return [PLKAccountSubtypeDepository createWithValue:PLKAccountSubtypeValueDepositoryChecking];
+ } else if ([normalizedSubtypeString isEqualToString:@"ebt"]) {
+ return [PLKAccountSubtypeDepository createWithValue:PLKAccountSubtypeValueDepositoryEbt];
+ } else if ([normalizedSubtypeString isEqualToString:@"hsa"]) {
+ return [PLKAccountSubtypeDepository createWithValue:PLKAccountSubtypeValueDepositoryHsa];
+ } else if ([normalizedSubtypeString isEqualToString:@"money market"]) {
+ return [PLKAccountSubtypeDepository createWithValue:PLKAccountSubtypeValueDepositoryMoneyMarket];
+ } else if ([normalizedSubtypeString isEqualToString:@"paypal"]) {
+ return [PLKAccountSubtypeDepository createWithValue:PLKAccountSubtypeValueDepositoryPaypal];
+ } else if ([normalizedSubtypeString isEqualToString:@"prepaid"]) {
+ return [PLKAccountSubtypeDepository createWithValue:PLKAccountSubtypeValueDepositoryPrepaid];
+ } else if ([normalizedSubtypeString isEqualToString:@"savings"]) {
+ return [PLKAccountSubtypeDepository createWithValue:PLKAccountSubtypeValueDepositorySavings];
+ } else {
+ return [PLKAccountSubtypeDepository createWithUnknownValue:subtypeString];
+ }
+
+ } else if ([normalizedTypeString isEqualToString:@"investment"]) {
+ if ([normalizedSubtypeString isEqualToString:@"all"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentAll];
+ } else if ([normalizedSubtypeString isEqualToString:@"401a"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestment401a];
+ } else if ([normalizedSubtypeString isEqualToString:@"401k"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestment401k];
+ } else if ([normalizedSubtypeString isEqualToString:@"403B"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestment403B];
+ } else if ([normalizedSubtypeString isEqualToString:@"457b"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestment457b];
+ } else if ([normalizedSubtypeString isEqualToString:@"529"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestment529];
+ } else if ([normalizedSubtypeString isEqualToString:@"brokerage"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentBrokerage];
+ } else if ([normalizedSubtypeString isEqualToString:@"cash isa"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentCashIsa];
+ } else if ([normalizedSubtypeString isEqualToString:@"education savings account"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentEducationSavingsAccount];
+ } else if ([normalizedSubtypeString isEqualToString:@"fixed annuity"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentFixedAnnuity];
+ } else if ([normalizedSubtypeString isEqualToString:@"gic"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentGic];
+ } else if ([normalizedSubtypeString isEqualToString:@"health reimbursement arrangement"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentHealthReimbursementArrangement];
+ } else if ([normalizedSubtypeString isEqualToString:@"hsa"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentHsa];
+ } else if ([normalizedSubtypeString isEqualToString:@"ira"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentIra];
+ } else if ([normalizedSubtypeString isEqualToString:@"isa"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentIsa];
+ } else if ([normalizedSubtypeString isEqualToString:@"keogh"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentKeogh];
+ } else if ([normalizedSubtypeString isEqualToString:@"lif"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentLif];
+ } else if ([normalizedSubtypeString isEqualToString:@"lira"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentLira];
+ } else if ([normalizedSubtypeString isEqualToString:@"lrif"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentLrif];
+ } else if ([normalizedSubtypeString isEqualToString:@"lrsp"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentLrsp];
+ } else if ([normalizedSubtypeString isEqualToString:@"mutual fund"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentMutualFund];
+ } else if ([normalizedSubtypeString isEqualToString:@"non-taxable brokerage account"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentNonTaxableBrokerageAccount];
+ } else if ([normalizedSubtypeString isEqualToString:@"pension"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentPension];
+ } else if ([normalizedSubtypeString isEqualToString:@"plan"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentPlan];
+ } else if ([normalizedSubtypeString isEqualToString:@"prif"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentPrif];
+ } else if ([normalizedSubtypeString isEqualToString:@"profit sharing plan"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentProfitSharingPlan];
+ } else if ([normalizedSubtypeString isEqualToString:@"rdsp"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentRdsp];
+ } else if ([normalizedSubtypeString isEqualToString:@"resp"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentResp];
+ } else if ([normalizedSubtypeString isEqualToString:@"retirement"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentRetirement];
+ } else if ([normalizedSubtypeString isEqualToString:@"rlif"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentRlif];
+ } else if ([normalizedSubtypeString isEqualToString:@"roth 401k"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentRoth401k];
+ } else if ([normalizedSubtypeString isEqualToString:@"roth"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentRoth];
+ } else if ([normalizedSubtypeString isEqualToString:@"rrif"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentRrif];
+ } else if ([normalizedSubtypeString isEqualToString:@"rrsp"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentRrsp];
+ } else if ([normalizedSubtypeString isEqualToString:@"sarsep"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentSarsep];
+ } else if ([normalizedSubtypeString isEqualToString:@"sep ira"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentSepIra];
+ } else if ([normalizedSubtypeString isEqualToString:@"simple ira"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentSimpleIra];
+ } else if ([normalizedSubtypeString isEqualToString:@"sipp"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentSipp];
+ } else if ([normalizedSubtypeString isEqualToString:@"stock plan"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentStockPlan];
+ } else if ([normalizedSubtypeString isEqualToString:@"tfsa"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentTfsa];
+ } else if ([normalizedSubtypeString isEqualToString:@"thrift savings plan"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentThriftSavingsPlan];
+ } else if ([normalizedSubtypeString isEqualToString:@"trust"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentTrust];
+ } else if ([normalizedSubtypeString isEqualToString:@"ugma"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentUgma];
+ } else if ([normalizedSubtypeString isEqualToString:@"utma"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentUtma];
+ } else if ([normalizedSubtypeString isEqualToString:@"variable annuity"]) {
+ return [PLKAccountSubtypeInvestment createWithValue:PLKAccountSubtypeValueInvestmentVariableAnnuity];
+ } else {
+ return [PLKAccountSubtypeInvestment createWithUnknownValue:subtypeString];
+ }
+ }
+
+ return [PLKAccountSubtypeUnknown createWithRawTypeStringValue:typeString rawSubtypeStringValue:subtypeString];
+}
+
++ (NSDictionary *)dictionaryFromSuccess:(PLKLinkSuccess *)success {
+ PLKSuccessMetadata *metadata = success.metadata;
+
+ return @{
+ @"publicToken": success.publicToken ?: @"",
+ @"metadata": @{
+ @"linkSessionId": metadata.linkSessionID ?: @"",
+ @"institution": [self dictionaryFromInstitution:metadata.institution] ?: @"",
+ @"accounts": [self accountsDictionariesFromAccounts:metadata.accounts] ?: @"",
+ @"metadataJson": metadata.metadataJSON ?: @"",
+ },
+ };
+}
+
++ (NSArray *)accountsDictionariesFromAccounts:(NSArray *)accounts {
+ NSMutableArray *results = [NSMutableArray arrayWithCapacity:accounts.count];
+
+ for (PLKAccount *account in accounts) {
+ NSDictionary *accountDictionary = [self dictionaryFromAccount:account];
+ [results addObject:accountDictionary];
+ }
+ return [results copy];
+}
+
++ (NSDictionary *)dictionaryFromAccount:(PLKAccount *)account {
+ return @{
+ @"id": account.ID ?: @"",
+ @"name": account.name ?: @"",
+ @"mask": account.mask ?: @"",
+ @"subtype": [self subtypeNameForAccountSubtype:account.subtype] ?: @"",
+ @"type": [self typeNameForAccountSubtype:account.subtype] ?: @"",
+ @"verificationStatus": [self stringForVerificationStatus:account.verificationStatus] ?: @"",
+ };
+}
+
++ (NSString *)stringForVerificationStatus:(PLKVerificationStatus *)verificationStatus {
+ if (!verificationStatus) {
+ return @"";
+ }
+
+ if (verificationStatus.unknownStringValue) {
+ return verificationStatus.unknownStringValue;
+ }
+
+ switch (verificationStatus.value) {
+ case PLKVerificationStatusValueNone:
+ return @"";
+ case PLKVerificationStatusValuePendingAutomaticVerification:
+ return @"pending_automatic_verification";
+ case PLKVerificationStatusValuePendingManualVerification:
+ return @"pending_manual_verification";
+ case PLKVerificationStatusValueManuallyVerified:
+ return @"manually_verified";
+ }
+
+ return @"unknown";
+}
+
++ (NSString *)typeNameForAccountSubtype:(id)accountSubtype {
+ if ([accountSubtype isKindOfClass:[PLKAccountSubtypeUnknown class]]) {
+ return ((PLKAccountSubtypeUnknown *)accountSubtype).rawStringValue;
+ } else if ([accountSubtype isKindOfClass:[PLKAccountSubtypeOther class]]) {
+ return @"other";
+ } else if ([accountSubtype isKindOfClass:[PLKAccountSubtypeCredit class]]) {
+ return @"credit";
+ } else if ([accountSubtype isKindOfClass:[PLKAccountSubtypeLoan class]]) {
+ return @"loan";
+ } else if ([accountSubtype isKindOfClass:[PLKAccountSubtypeDepository class]]) {
+ return @"depository";
+ } else if ([accountSubtype isKindOfClass:[PLKAccountSubtypeInvestment class]]) {
+ return @"investment";
+ }
+ return @"unknown";
+}
+
++ (NSString *)subtypeNameForAccountSubtype:(id)accountSubtype {
+ if ([accountSubtype isKindOfClass:[PLKAccountSubtypeUnknown class]]) {
+ return ((PLKAccountSubtypeUnknown *)accountSubtype).rawSubtypeStringValue;
+ }
+ return accountSubtype.rawStringValue;
+}
+
++ (NSDictionary *)dictionaryFromInstitution:(PLKInstitution *)institution {
+ return @{
+ @"name": institution.name ?: @"",
+ @"id": institution.ID ?: @"",
+ };
+}
+
++ (NSDictionary *)dictionaryFromError:(PLKExitError *)error {
+ return @{
+ @"errorType": [self errorTypeStringFromError:error] ?: @"",
+ @"errorCode": [self errorCodeStringFromError:error] ?: @"",
+ @"errorMessage": [self errorMessageFromError:error] ?: @"",
+ // errorDisplayMessage is the deprecated name for displayMessage, both have to be populated
+ // until errorDisplayMessage is fully removed to avoid breaking the API
+ @"errorDisplayMessage": [self errorDisplayMessageFromError:error] ?: @"",
+ @"displayMessage": [self errorDisplayMessageFromError:error] ?: @"",
+ };
+}
+
++ (NSDictionary *)dictionaryFromEvent:(PLKLinkEvent *)event {
+ PLKEventMetadata *metadata = event.eventMetadata;
+
+ return @{
+ @"eventName": [self stringForEventName:event.eventName] ?: @"",
+ @"metadata": @{
+ @"errorType": [self errorTypeStringFromError:metadata.error] ?: @"",
+ @"errorCode": [self errorCodeStringFromError:metadata.error] ?: @"",
+ @"errorMessage": [self errorMessageFromError:metadata.error] ?: @"",
+ @"exitStatus": [self stringForExitStatus:metadata.exitStatus] ?: @"",
+ @"institutionId": metadata.institutionID ?: @"",
+ @"institutionName": metadata.institutionName ?: @"",
+ @"institutionSearchQuery": metadata.institutionSearchQuery ?: @"",
+ @"linkSessionId": metadata.linkSessionID ?: @"",
+ @"mfaType": [self stringForMfaType:metadata.mfaType] ?: @"",
+ @"requestId": metadata.requestID ?: @"",
+ @"timestamp": [self iso8601StringFromDate:metadata.timestamp] ?: @"",
+ @"viewName": [self stringForViewName:metadata.viewName] ?: @"",
+ @"metadataJson": metadata.metadataJSON ?: @"",
+ },
+ };
+}
+
++ (NSString *)errorDisplayMessageFromError:(PLKExitError *)error {
+ return error.userInfo[kPLKExitErrorDisplayMessageKey] ?: @"";
+}
+
++ (NSString *)errorTypeStringFromError:(PLKExitError *)error {
+ NSString *errorDomain = error.domain;
+ if (!error || !errorDomain) {
+ return @"";
+ }
+
+ NSString *normalizedErrorDomain = errorDomain;
+
+ return @{
+ kPLKExitErrorInvalidRequestDomain: @"INVALID_REQUEST",
+ kPLKExitErrorInvalidInputDomain: @"INVALID_INPUT",
+ kPLKExitErrorInstitutionErrorDomain: @"INSTITUTION_ERROR",
+ kPLKExitErrorRateLimitExceededDomain: @"RATE_LIMIT_EXCEEDED",
+ kPLKExitErrorApiDomain: @"API_ERROR",
+ kPLKExitErrorItemDomain: @"ITEM_ERROR",
+ kPLKExitErrorAuthDomain: @"AUTH_ERROR",
+ kPLKExitErrorAssetReportDomain: @"ASSET_REPORT_ERROR",
+ kPLKExitErrorInternalDomain: @"INTERNAL",
+ kPLKExitErrorUnknownDomain: error.userInfo[kPLKExitErrorUnknownTypeKey] ?: @"UNKNOWN",
+ }[normalizedErrorDomain] ?: @"UNKNOWN";
+}
+
++ (NSString *)errorCodeStringFromError:(PLKExitError *)error {
+ NSString *errorDomain = error.domain;
+
+ if (!error || !errorDomain) {
+ return @"";
+ }
+ return error.userInfo[kPLKExitErrorCodeKey];
+}
+
++ (NSString *)errorMessageFromError:(PLKExitError *)error {
+ return error.userInfo[kPLKExitErrorMessageKey] ?: @"";
+}
+
++ (NSString *)stringForEventName:(PLKEventName *)eventName {
+ if (!eventName) {
+ return @"";
+ }
+
+ if (eventName.unknownStringValue) {
+ return eventName.unknownStringValue;
+ }
+
+ switch (eventName.value) {
+ case PLKEventNameValueNone:
+ return @"";
+ case PLKEventNameValueBankIncomeInsightsCompleted:
+ return @"BANK_INCOME_INSIGHTS_COMPLETED";
+ case PLKEventNameValueCloseOAuth:
+ return @"CLOSE_OAUTH";
+ case PLKEventNameValueError:
+ return @"ERROR";
+ case PLKEventNameValueExit:
+ return @"EXIT";
+ case PLKEventNameValueFailOAuth:
+ return @"FAIL_OAUTH";
+ case PLKEventNameValueHandoff:
+ return @"HANDOFF";
+ case PLKEventNameValueMatchedSelectInstitution:
+ return @"MATCHED_SELECT_INSTITUTION";
+ case PLKEventNameValueMatchedSelectVerifyMethod:
+ return @"MATCHED_SELECT_VERIFY_METHOD";
+ case PLKEventNameValueOpen:
+ return @"OPEN";
+ case PLKEventNameValueOpenMyPlaid:
+ return @"OPEN_MY_PLAID";
+ case PLKEventNameValueOpenOAuth:
+ return @"OPEN_OAUTH";
+ case PLKEventNameValueSearchInstitution:
+ return @"SEARCH_INSTITUTION";
+ case PLKEventNameValueSelectInstitution:
+ return @"SELECT_INSTITUTION";
+ case PLKEventNameValueSubmitCredentials:
+ return @"SUBMIT_CREDENTIALS";
+ case PLKEventNameValueSubmitMFA:
+ return @"SUBMIT_MFA";
+ case PLKEventNameValueTransitionView:
+ return @"TRANSITION_VIEW";
+ }
+ return @"unknown";
+}
+
++ (NSString *)iso8601StringFromDate:(NSDate *)date {
+ static NSISO8601DateFormatter *dateFormatter = nil;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ dateFormatter = [[NSISO8601DateFormatter alloc] init];
+ dateFormatter.formatOptions |= NSISO8601DateFormatWithFractionalSeconds;
+ });
+ return [dateFormatter stringFromDate:date];
+}
+
++ (NSString *)stringForExitStatus:(PLKExitStatus *)exitStatus {
+ if (!exitStatus) {
+ return @"";
+ }
+
+ if (exitStatus.unknownStringValue) {
+ return exitStatus.unknownStringValue;
+ }
+
+ switch (exitStatus.value) {
+ case PLKExitStatusValueNone:
+ return @"";
+ case PLKExitStatusValueRequiresQuestions:
+ return @"requires_questions";
+ case PLKExitStatusValueRequiresSelections:
+ return @"requires_selections";
+ case PLKExitStatusValueRequiresCode:
+ return @"requires_code";
+ case PLKExitStatusValueChooseDevice:
+ return @"choose_device";
+ case PLKExitStatusValueRequiresCredentials:
+ return @"requires_credentials";
+ case PLKExitStatusValueInstitutionNotFound:
+ return @"institution_not_found";
+ }
+ return @"unknown";
+}
+
++ (NSString *)stringForMfaType:(PLKMFAType)mfaType {
+ switch (mfaType) {
+ case PLKMFATypeNone:
+ return @"";
+ case PLKMFATypeCode:
+ return @"code";
+ case PLKMFATypeDevice:
+ return @"device";
+ case PLKMFATypeQuestions:
+ return @"questions";
+ case PLKMFATypeSelections:
+ return @"selections";
+ }
+
+ return @"unknown";
+}
+
++ (NSString *)stringForViewName:(PLKViewName *)viewName {
+ if (!viewName) {
+ return @"";
+ }
+
+ if (viewName.unknownStringValue) {
+ return viewName.unknownStringValue;
+ }
+
+ switch (viewName.value) {
+ case PLKViewNameValueNone:
+ return @"";
+ case PLKViewNameValueConnected:
+ return @"CONNECTED";
+ case PLKViewNameValueConsent:
+ return @"CONSENT";
+ case PLKViewNameValueCredential:
+ return @"CREDENTIAL";
+ case PLKViewNameValueError:
+ return @"ERROR";
+ case PLKViewNameValueExit:
+ return @"EXIT";
+ case PLKViewNameValueLoading:
+ return @"LOADING";
+ case PLKViewNameValueMatchedConsent:
+ return @"MATCHED_CONSENT";
+ case PLKViewNameValueMatchedCredential:
+ return @"MATCHED_CREDENTIAL";
+ case PLKViewNameValueMatchedMFA:
+ return @"MATCHED_MFA";
+ case PLKViewNameValueMFA:
+ return @"MFA";
+ case PLKViewNameValueNumbers:
+ return @"NUMBERS";
+ case PLKViewNameValueRecaptcha:
+ return @"RECAPTCHA";
+ case PLKViewNameValueSelectAccount:
+ return @"SELECT_ACCOUNT";
+ case PLKViewNameValueSelectInstitution:
+ return @"SELECT_INSTITUTION";
+ }
+
+ return @"unknown";
+}
+
++ (NSDictionary *)dictionaryFromExit:(PLKLinkExit *)exit {
+ PLKExitMetadata *metadata = exit.metadata;
+ return @{
+ @"error": [self dictionaryFromError:exit.error] ?: @{},
+ @"metadata": @{
+ @"status": [self stringForExitStatus:metadata.status] ?: @"",
+ @"institution": [self dictionaryFromInstitution:metadata.institution] ?: @"",
+ @"requestId": metadata.requestID ?: @"",
+ @"linkSessionId": metadata.linkSessionID ?: @"",
+ @"metadataJson": metadata.metadataJSON ?: @"",
+ },
+ };
+}
+
+@end