diff --git a/.gitignore b/.gitignore
index 8a35199d..ddbb826b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -51,4 +51,8 @@ dist/
.vscode
example/android/build/
example/.yalc
-example/yalc.lock
\ No newline at end of file
+example/yalc.lock
+
+FabricExample/android/build/
+FabricExample/.yalc
+FabricExample/yalc.lock
\ No newline at end of file
diff --git a/FabricExample/ios/Podfile.lock b/FabricExample/ios/Podfile.lock
index 1a293fe6..50c0871b 100644
--- a/FabricExample/ios/Podfile.lock
+++ b/FabricExample/ios/Podfile.lock
@@ -7,7 +7,7 @@ PODS:
- hermes-engine (0.74.1):
- hermes-engine/Pre-built (= 0.74.1)
- hermes-engine/Pre-built (0.74.1)
- - Plaid (5.5.1)
+ - Plaid (6.0.0)
- RCT-Folly (2024.01.01.00):
- boost
- DoubleConversion
@@ -936,11 +936,11 @@ PODS:
- React-Mapbuffer (0.74.1):
- glog
- React-debug
- - react-native-plaid-link-sdk (11.10.2):
+ - react-native-plaid-link-sdk (12.0.0):
- DoubleConversion
- glog
- hermes-engine
- - Plaid (~> 5.5.1)
+ - Plaid (~> 6.0.0)
- RCT-Folly (= 2024.01.01.00)
- RCTRequired
- RCTTypeSafety
@@ -1515,7 +1515,7 @@ SPEC CHECKSUMS:
fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120
glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2
hermes-engine: 16b8530de1b383cdada1476cf52d1b52f0692cbc
- Plaid: 276eb2892728a7b33a89f54aa0d62cf532af2c1d
+ Plaid: ae1b67f78e433a5465939fd093c6af6d024c38b2
RCT-Folly: 02617c592a293bd6d418e0a88ff4ee1f88329b47
RCTDeprecation: efb313d8126259e9294dc4ee0002f44a6f676aba
RCTRequired: f49ea29cece52aee20db633ae7edc4b271435562
@@ -1540,7 +1540,7 @@ SPEC CHECKSUMS:
React-jsitracing: 233d1a798fe0ff33b8e630b8f00f62c4a8115fbc
React-logger: 7e7403a2b14c97f847d90763af76b84b152b6fce
React-Mapbuffer: 11029dcd47c5c9e057a4092ab9c2a8d10a496a33
- react-native-plaid-link-sdk: aa721fc10b14926ee16f805083c2a61ad0c693a5
+ react-native-plaid-link-sdk: 14d024322c284481ca74925c23c851faa218a126
react-native-safe-area-context: 7f54ad0a774de306ab790c70d9d950321e5c5449
React-nativeconfig: b0073a590774e8b35192fead188a36d1dca23dec
React-NativeModulesApple: df46ff3e3de5b842b30b4ca8a6caae6d7c8ab09f
diff --git a/FabricExample/package-lock.json b/FabricExample/package-lock.json
index 5d888834..b958e429 100644
--- a/FabricExample/package-lock.json
+++ b/FabricExample/package-lock.json
@@ -16,7 +16,7 @@
"react": "18.2.0",
"react-native": "0.74.1",
"react-native-gesture-handler": "^2.16.2",
- "react-native-plaid-link-sdk": "^11.10.3",
+ "react-native-plaid-link-sdk": "file:.yalc/react-native-plaid-link-sdk",
"react-native-safe-area-context": "4.10.1",
"react-native-screens": "3.31.1"
},
@@ -41,6 +41,14 @@
"node": ">=18"
}
},
+ ".yalc/react-native-plaid-link-sdk": {
+ "version": "12.0.0",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "*",
+ "react-native": "*"
+ }
+ },
"node_modules/@aashutoshrathi/word-wrap": {
"version": "1.2.6",
"license": "MIT",
@@ -11577,13 +11585,8 @@
}
},
"node_modules/react-native-plaid-link-sdk": {
- "version": "11.10.3",
- "resolved": "https://registry.npmjs.org/react-native-plaid-link-sdk/-/react-native-plaid-link-sdk-11.10.3.tgz",
- "integrity": "sha512-cklgvgs5p4yxIrP6vEgFegNEP9lSPasAcErS5pZVZs5oOw7VudW4GcHvYUabG6YO4vY2fq8EBKGiNDxCu97Pyw==",
- "peerDependencies": {
- "react": "*",
- "react-native": "*"
- }
+ "resolved": ".yalc/react-native-plaid-link-sdk",
+ "link": true
},
"node_modules/react-native-safe-area-context": {
"version": "4.10.1",
diff --git a/FabricExample/package.json b/FabricExample/package.json
index 0c758425..3074c025 100644
--- a/FabricExample/package.json
+++ b/FabricExample/package.json
@@ -18,7 +18,7 @@
"react": "18.2.0",
"react-native": "0.74.1",
"react-native-gesture-handler": "^2.16.2",
- "react-native-plaid-link-sdk": "^11.10.3",
+ "react-native-plaid-link-sdk": "file:.yalc/react-native-plaid-link-sdk",
"react-native-safe-area-context": "4.10.1",
"react-native-screens": "3.31.1"
},
diff --git a/FabricExample/src/Screens/PlaidLinkScreen.tsx b/FabricExample/src/Screens/PlaidLinkScreen.tsx
index e2de3ca3..2f776e17 100644
--- a/FabricExample/src/Screens/PlaidLinkScreen.tsx
+++ b/FabricExample/src/Screens/PlaidLinkScreen.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import {TextInput, Text, TouchableOpacity} from 'react-native';
+import {Platform, TextInput, Text, TouchableOpacity} from 'react-native';
import {styles} from '../Styles';
import {
@@ -12,10 +12,14 @@ import {
usePlaidEmitter,
LinkIOSPresentationStyle,
LinkTokenConfiguration,
+ FinanceKitError,
+ create,
+ open,
+ syncFinanceKit,
+ submit,
+ SubmissionData,
} from 'react-native-plaid-link-sdk';
-import {create, open} from 'react-native-plaid-link-sdk/dist/PlaidLink';
-
function isValidString(str: string): boolean {
if (str && str.trim() !== '') {
return true;
@@ -35,6 +39,12 @@ function createLinkTokenConfiguration(
};
}
+function createSubmissionData(phoneNumber: string): SubmissionData {
+ return {
+ phoneNumber: phoneNumber,
+ };
+}
+
function createLinkOpenProps(): LinkOpenProps {
return {
onSuccess: (success: LinkSuccess) => {
@@ -53,7 +63,8 @@ function createLinkOpenProps(): LinkOpenProps {
};
}
-export function PlaidLinkScreen(): React.JSX.Element {
+// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+export function PlaidLinkScreen() {
// Render using the link_token integration. Refer to the docs
// https://plaid.com/docs/#create-link-token on how to create
// a new link_token.
@@ -67,6 +78,32 @@ export function PlaidLinkScreen(): React.JSX.Element {
const [text, onChangeText] = React.useState('');
const [disabled, setDisabled] = React.useState(true);
+ const iOSVersionParts = String(Platform.Version).split('.');
+ const [majorVersion, minorVersion] =
+ iOSVersionParts.length >= 2 ? iOSVersionParts : [null, null];
+
+ const financeKitText = () => {
+ if (majorVersion && minorVersion) {
+ const majorInt = parseInt(majorVersion, 10);
+ const minorInt = parseInt(minorVersion, 10);
+
+ if (majorInt > 17) {
+ return Sync FinanceKit;
+ } else if (majorInt === 17 && minorInt >= 4) {
+ return Sync FinanceKit;
+ } else {
+ return (
+
+ FinanceKit not supported on this version of iOS
+
+ );
+ }
+ } else {
+ // Fallback return if majorVersion or minorVersion are not provided.
+ return Invalid iOS version;
+ }
+ };
+
return (
<>
Create Link
+ {
+ const submissionData = createSubmissionData('415-555-0015');
+ submit(submissionData);
+ }}>
+ Submit Layer Phone Number
+
Open Link
+ {
+ const completionHandler = (error?: FinanceKitError) => {
+ if (error) {
+ console.error('Error:', error);
+ } else {
+ console.log('Sync completed successfully');
+ }
+ };
+ const requestAuthorizationIfNeeded = true;
+ syncFinanceKit(text, requestAuthorizationIfNeeded, completionHandler);
+ }}>
+ {financeKitText()}
+
>
);
-}
+}
\ No newline at end of file
diff --git a/example/src/Screens/PlaidEmbeddedLinkScreen.tsx b/example/src/Screens/PlaidEmbeddedLinkScreen.tsx
index 659106bb..836ba1cc 100644
--- a/example/src/Screens/PlaidEmbeddedLinkScreen.tsx
+++ b/example/src/Screens/PlaidEmbeddedLinkScreen.tsx
@@ -2,7 +2,7 @@ import * as React from 'react';
import {TextInput, Text, View} from 'react-native';
import {styles} from '../Styles';
import {
- EmbeddedLinkView,
+ // EmbeddedLinkView,
LinkIOSPresentationStyle,
LinkEvent,
LinkExit,
@@ -57,7 +57,7 @@ export function PlaidEmbeddedLinkScreen() {
placeholder="link-sandbox-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
placeholderTextColor={'#D3D3D3'}
/>
-
+ {/* */}
>
);
}
diff --git a/ios/Extensions.swift b/ios/Extensions.swift
new file mode 100644
index 00000000..ea65bae3
--- /dev/null
+++ b/ios/Extensions.swift
@@ -0,0 +1,41 @@
+import Foundation
+import LinkKit
+
+extension LinkKit.Plaid.CreateError {
+ var code: Int {
+ switch self {
+ case .configurationError(let nestedError):
+ return nestedError.code
+ @unknown default:
+ return -1
+ }
+ }
+}
+
+extension LinkKit.ConfigurationError {
+ var code: Int {
+ switch self {
+ case .malformedClientID: return 0
+ case .missingAuthorization: return 1
+ case .noProduct: return 2
+ case .invalidOptionValue: return 3
+ case .invalidOptionCombination: return 4
+ case .invalidToken: return 5
+ @unknown default: return -1
+ }
+ }
+}
+
+@available(iOS 17.4, *)
+extension LinkKit.FinanceKitError {
+ var code: Int {
+ switch self {
+ case .invalidToken: return 0
+ case .permissionError: return 1
+ case .linkApiError: return 2
+ case .permissionAccessError: return 3
+ case .unknown: return 4
+ @unknown default: return -1
+ }
+ }
+}
diff --git a/ios/JSONHelper.swift b/ios/JSONHelper.swift
new file mode 100644
index 00000000..03f167da
--- /dev/null
+++ b/ios/JSONHelper.swift
@@ -0,0 +1,394 @@
+import Foundation
+import LinkKit
+
+struct JSONHelper {
+ static let dateFormatter: ISO8601DateFormatter = {
+ let formatter = ISO8601DateFormatter()
+ formatter.formatOptions.insert(.withFractionalSeconds)
+ return formatter
+ }()
+
+ // MARK: Success
+
+ static func dictionaryFromSuccess(success: LinkSuccess) -> [String: Any] {
+ let metadata = success.metadata
+
+ return [
+ "publicToken": success.publicToken,
+ "metadata": [
+ "linkSessionId": metadata.linkSessionID,
+ "institution": dictionaryFromInstitution(institution: metadata.institution),
+ "accounts": accountsDictionariesFromAccounts(accounts: metadata.accounts),
+ "metadataJson": metadata.metadataJSON ?? "",
+ ],
+ ]
+ }
+
+ static func accountsDictionariesFromAccounts(accounts: [Account]) -> [[String: Any]] {
+ var results = [[String: Any]]()
+
+ for account in accounts {
+ let accountDictionary = dictionaryFromAccount(account: account)
+ results.append(accountDictionary)
+ }
+
+ return results
+ }
+
+ static func dictionaryFromAccount(account: Account) -> [String: Any] {
+ return [
+ "id": account.id,
+ "name": account.name,
+ "mask": account.mask ?? "",
+ "subtype": subtypeNameForAccountSubtype(accountSubtype: account.subtype),
+ "type": typeNameForAccountSubtype(accountSubtype: account.subtype),
+ "verificationStatus": stringForVerificationStatus(verificationStatus: account.verificationStatus),
+ ]
+ }
+
+ static func stringForVerificationStatus(verificationStatus: VerificationStatus?) -> String {
+ guard let verificationStatus = verificationStatus else { return "" }
+ switch verificationStatus {
+ case .pendingAutomaticVerification: return "pending_automatic_verification"
+ case .pendingManualVerification: return "pending_manual_verification"
+ case .manuallyVerified: return "manually_verified"
+ case .unknown(let unknownString): return unknownString
+ @unknown default: return "unknown"
+ }
+ }
+
+ static func typeNameForAccountSubtype(accountSubtype: AccountSubtype) -> String {
+ switch accountSubtype {
+ case .unknown(let type, subtype: _): return type
+ case .other: return "other"
+ case .credit: return "credit"
+ case .loan: return "loan"
+ case .depository: return "depository"
+ case .investment: return "investment"
+ @unknown default: return "unknown"
+ }
+ }
+
+ static func subtypeNameForAccountSubtype(accountSubtype: AccountSubtype) -> String {
+ switch accountSubtype {
+ case .unknown(_, let subtype): return subtype
+ case .other(let other):
+ switch other {
+ case .all: return "all"
+ case .other: return "other"
+ case .unknown(let unknown): return unknown
+ @unknown default: return "unknown"
+ }
+ case .credit(let credit):
+ switch credit {
+ case .all: return "all"
+ case .creditCard: return "credit card"
+ case .paypal: return "paypal"
+ case .unknown(let unknown): return unknown
+ @unknown default: return "unknown"
+
+ }
+ case .loan(let loan):
+ switch loan {
+ case .all: return "all"
+ case .auto: return "auto"
+ case .business: return "business"
+ case .commercial: return "commercial"
+ case .construction: return "construction"
+ case .consumer: return "consumer"
+ case .homeEquity: return "home equity"
+ case .lineOfCredit: return "line of credit"
+ case .loan: return "loan"
+ case .mortgage: return "mortgage"
+ case .overdraft: return "overdraft"
+ case .student: return "student"
+ case .unknown(let unknown): return unknown
+ @unknown default: return "unknown"
+ }
+
+ case .depository(let depository):
+ switch depository {
+ case .all: return "all"
+ case .cashManagement: return "cash management"
+ case .cd: return "cd"
+ case .checking: return "checking"
+ case .ebt: return "ebt"
+ case .hsa: return "hsa"
+ case .moneyMarket: return "money market"
+ case .paypal: return "paypal"
+ case .prepaid: return "prepaid"
+ case .savings: return "savings"
+ case .unknown(let unknown): return unknown
+ @unknown default: return "unknown"
+ }
+
+ case .investment(let investment):
+ switch investment {
+ case .all: return "all"
+ case .brokerage: return "brokerage"
+ case .cashIsa: return "cash isa"
+ case .educationSavingsAccount: return "education savings account"
+ case .fixedAnnuity: return "fixed annuity"
+ case .gic: return "gic"
+ case .healthReimbursementArrangement: return "health reimbursement arrangement"
+ case .hsa: return "hsa"
+ case .investment401a: return "401a"
+ case .investment401k: return "401k"
+ case .investment403B: return "403b"
+ case .investment457b: return "457b"
+ case .investment529: return "529"
+ case .ira: return "ira"
+ case .isa: return "isa"
+ case .keogh: return "keogh"
+ case .lif: return "lif"
+ case .lira: return "lira"
+ case .lrif: return "lrif"
+ case .lrsp: return "lrsp"
+ case .mutualFund: return "mutual fund"
+ case .nonTaxableBrokerageAccount: return "non-taxable brokerage account"
+ case .pension: return "pension"
+ case .plan: return "plan"
+ case .prif: return "prif"
+ case .profitSharingPlan: return "profit sharing plan"
+ case .rdsp: return "rdsp"
+ case .resp: return "resp"
+ case .retirement: return "retirement"
+ case .rlif: return "rlif"
+ case .roth: return "roth"
+ case .roth401k: return "roth 401k"
+ case .rrif: return "rrif"
+ case .rrsp: return "rrsp"
+ case .sarsep: return "sarsep"
+ case .sepIra: return "sep ira"
+ case .simpleIra: return "simple ira"
+ case .sipp: return "sipp"
+ case .stockPlan: return "stock plan"
+ case .tfsa: return "tfsa"
+ case .thriftSavingsPlan: return "thrift savings plan"
+ case .trust: return "trust"
+ case .ugma: return "ugma"
+ case .unknown(let unknown): return unknown
+ case .utma: return "utma"
+ case .variableAnnuity: return "variable annuity"
+ @unknown default: return "unknown"
+ }
+ @unknown default: return "uknown"
+ }
+ }
+
+ static func dictionaryFromInstitution(institution: Institution?) -> [String: Any] {
+ return [
+ "name": institution?.name ?? "",
+ "id": institution?.id ?? "",
+ ]
+ }
+
+ // MARK: Exit
+
+ static func dictionaryFromExit(exit: LinkExit) -> [String: Any] {
+ let metadata = exit.metadata
+ return [
+ "error": dictionaryFromError(error: exit.error),
+ "metadata": [
+ "status": stringForExitStatus(exitStatus: metadata.status),
+ "institution": dictionaryFromInstitution(institution: metadata.institution),
+ "requestId": metadata.requestID ?? "",
+ "linkSessionId": metadata.linkSessionID ?? "",
+ "metadataJson": metadata.metadataJSON ?? "",
+ ],
+ ]
+ }
+
+ static func stringForExitStatus(exitStatus: ExitStatus?) -> String {
+ guard let exitStatus = exitStatus else { return "" }
+
+ switch exitStatus {
+ case .requiresQuestions:
+ return "requires_questions"
+ case .requiresSelections:
+ return "requires_selections"
+ case .requiresCode:
+ return "requires_code"
+ case .chooseDevice:
+ return "choose_device"
+ case .requiresCredentials:
+ return "requires_credentials"
+ case .institutionNotFound:
+ return "institution_not_found"
+ case .requiresAccountSelection:
+ return "requires_account_selection"
+ case .continueToThirdParty:
+ return "continue_to_third_party"
+ case .unknown(let unknown): return unknown
+ @unknown default: return "unknown"
+ }
+ }
+
+ static func dictionaryFromError(error: ExitError?) -> [String: String] {
+ return [
+ "errorType": errorTypeStringFromError(error: error),
+ "errorCode": errorCodeStringFromError(error: error),
+ "errorMessage": errorMessageFromError(error: error),
+ // errorDisplayMessage is the deprecated name for displayMessage, both have to be populated
+ // until errorDisplayMessage is fully removed to avoid breaking the API
+ "errorDisplayMessage": errorDisplayMessageFromError(error: error),
+ "displayMessage": errorDisplayMessageFromError(error: error),
+ ]
+ }
+
+ static func errorTypeStringFromError(error: ExitError?) -> String {
+ guard let error = error else { return "" }
+ switch error.errorCode {
+ case .apiError:
+ return "API_ERROR"
+ case .authError:
+ return "AUTH_ERROR"
+ case .assetReportError:
+ return "ASSET_REPORT_ERROR"
+ case .internal:
+ return "INTERNAL"
+ case .institutionError:
+ return "INSTITUTION_ERROR"
+ case .itemError:
+ return "ITEM_ERROR"
+ case .invalidInput:
+ return "INVALID_INPUT"
+ case .invalidRequest:
+ return "INVALID_REQUEST"
+ case .rateLimitExceeded:
+ return "RATE_LIMIT_EXCEEDED"
+ case .unknown:
+ return "UNKNOWN"
+ @unknown default:
+ return "UNKNOWN"
+ }
+ }
+
+ static func errorTypeStringFromError(error: ExitErrorCode?) -> String {
+ guard let error = error else { return "" }
+ switch error {
+ case .apiError:
+ return "API_ERROR"
+ case .authError:
+ return "AUTH_ERROR"
+ case .assetReportError:
+ return "ASSET_REPORT_ERROR"
+ case .internal:
+ return "INTERNAL"
+ case .institutionError:
+ return "INSTITUTION_ERROR"
+ case .itemError:
+ return "ITEM_ERROR"
+ case .invalidInput:
+ return "INVALID_INPUT"
+ case .invalidRequest:
+ return "INVALID_REQUEST"
+ case .rateLimitExceeded:
+ return "RATE_LIMIT_EXCEEDED"
+ case .unknown:
+ return "UNKNOWN"
+ @unknown default:
+ return "UNKNOWN"
+ }
+ }
+
+ static func errorCodeStringFromError(error: ExitError?) -> String {
+ guard let error = error else { return "" }
+
+ switch error.errorCode {
+ case .apiError(let apiError):
+ return apiError.description
+ case .authError(let authError):
+ return authError.description
+ case .assetReportError(let assetReportError):
+ return assetReportError.description
+ case .internal(let internalError):
+ return internalError.description
+ case .institutionError(let institutionError):
+ return institutionError.description
+ case .itemError(let itemError):
+ return itemError.description
+ case .invalidInput(let invalidInputError):
+ return invalidInputError.description
+ case .invalidRequest(let invalidRequestError):
+ return invalidRequestError.description
+ case .rateLimitExceeded(let rateLimitExceededError):
+ return rateLimitExceededError.description
+ case .unknown(let type, _):
+ return type
+ @unknown default:
+ return "Unknown"
+ }
+ }
+
+ static func errorCodeStringFromError(error: ExitErrorCode?) -> String {
+ guard let error = error else { return "" }
+
+ switch error {
+ case .apiError(let apiError):
+ return apiError.description
+ case .authError(let authError):
+ return authError.description
+ case .assetReportError(let assetReportError):
+ return assetReportError.description
+ case .internal(let internalError):
+ return internalError.description
+ case .institutionError(let institutionError):
+ return institutionError.description
+ case .itemError(let itemError):
+ return itemError.description
+ case .invalidInput(let invalidInputError):
+ return invalidInputError.description
+ case .invalidRequest(let invalidRequestError):
+ return invalidRequestError.description
+ case .rateLimitExceeded(let rateLimitExceededError):
+ return rateLimitExceededError.description
+ case .unknown(let type, _):
+ return type
+ @unknown default:
+ return "Unknown"
+ }
+ }
+
+ static func errorMessageFromError(error: ExitError?) -> String {
+ guard let error = error else { return "" }
+ return error.errorMessage
+ }
+
+ static func errorDisplayMessageFromError(error: ExitError?) -> String {
+ guard let error = error else { return "" }
+ return error.displayMessage ?? ""
+ }
+
+ // MARK: Event
+
+ static func dictionaryFromEvent(_ event: LinkEvent) -> [String: Any] {
+ return [
+ "eventName": event.eventName.description,
+ "metadata": dictionaryFromEventMetadata(event.metadata),
+ ]
+ }
+
+ static func dictionaryFromEventMetadata(_ metadata: EventMetadata) -> [String: Any] {
+ return [
+ "errorType": errorTypeStringFromError(error: metadata.errorCode),
+ "errorCode": errorCodeStringFromError(error: metadata.errorCode),
+ "errorMessage": metadata.errorMessage ?? "",
+ "exitStatus": stringForExitStatus(exitStatus: metadata.exitStatus),
+ "institutionId": metadata.institutionID ?? "",
+ "institutionName": metadata.institutionName ?? "",
+ "institutionSearchQuery": metadata.institutionSearchQuery ?? "",
+ "accountNumberMask": metadata.accountNumberMask ?? "",
+ "isUpdateMode": metadata.isUpdateMode ?? "",
+ "matchReason": metadata.matchReason ?? "",
+ "routingNumber": metadata.routingNumber ?? "",
+ "selection": metadata.selection ?? "",
+ "linkSessionId": metadata.linkSessionID,
+ "mfaType": metadata.mfaType?.description ?? "",
+ "requestId": metadata.requestID ?? "",
+ "timestamp": dateFormatter.string(from: metadata.timestamp),
+ "viewName": metadata.viewName?.description ?? "",
+ "metadata_json": metadata.metadataJSON ?? "",
+ ]
+ }
+}
diff --git a/ios/LayerSubmissionData.swift b/ios/LayerSubmissionData.swift
new file mode 100644
index 00000000..8e4a468d
--- /dev/null
+++ b/ios/LayerSubmissionData.swift
@@ -0,0 +1,6 @@
+import Foundation
+import LinkKit
+
+struct LayerSubmissionData: SubmissionData {
+ let phoneNumber: String?
+}
diff --git a/ios/PLKEmbeddedView.m b/ios/PLKEmbeddedView.m
deleted file mode 100644
index 01d3adfd..00000000
--- a/ios/PLKEmbeddedView.m
+++ /dev/null
@@ -1,7 +0,0 @@
-#import
-
-@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
diff --git a/ios/PLKEmbeddedView.swift b/ios/PLKEmbeddedView.swift
deleted file mode 100644
index 141d2b36..00000000
--- a/ios/PLKEmbeddedView.swift
+++ /dev/null
@@ -1,116 +0,0 @@
-import LinkKit
-import UIKit
-
-@objc public final class PLKEmbeddedView: UIView {
-
- // Properties exposed to React Native.
-
- @objc public var iOSPresentationStyle: String = "" {
- didSet {
- createNativeEmbeddedView()
- }
- }
-
- @objc public var token: String = "" {
- didSet {
- createNativeEmbeddedView()
- }
- }
-
- @objc public var onEmbeddedEvent: RCTDirectEventBlock?
-
- // MARK: Private
-
- private var linkHandler: Handler?
- private let embeddedEventName: String = "embeddedEventName"
-
- private func makeHandler() throws -> Handler {
- var config = LinkTokenConfiguration(
- token: token,
- onSuccess: { [weak self] success in
- guard let self = self else { return }
-
- let plkLinkSuccess = success.toObjC
- var dictionary = RNLinksdk.dictionary(from: plkLinkSuccess) ?? [:]
- dictionary[self.embeddedEventName] = "onSuccess"
- self.onEmbeddedEvent?(dictionary)
- }
- )
-
- config.onEvent = { [weak self] event in
- guard let self = self else { return }
-
- let plkLinkEvent = event.toObjC
- var dictionary = RNLinksdk.dictionary(from: plkLinkEvent) ?? [:]
- dictionary[self.embeddedEventName] = "onEvent"
- self.onEmbeddedEvent?(dictionary)
- }
-
- config.onExit = { [weak self] exit in
- guard let self = self else { return }
-
- let plkLinkExit = exit.toObjC
- var dictionary = RNLinksdk.dictionary(from: plkLinkExit) ?? [:]
- dictionary[self.embeddedEventName] = "onExit"
- self.onEmbeddedEvent?(dictionary)
- }
-
- let handlerCreationResult = Plaid.create(config)
-
- switch handlerCreationResult {
- case .failure(let error):
- throw (error)
- case .success(let handler):
- return handler
- }
- }
-
- private func makeEmbeddedView(rctViewController: UIViewController, handler: Handler) -> UIView {
- self.linkHandler = handler
-
- let presentationMethod: PresentationMethod
-
- if iOSPresentationStyle.uppercased() == "FULL_SCREEN" {
- presentationMethod = .custom({ viewController in
- viewController.modalPresentationStyle = .overFullScreen
- viewController.modalTransitionStyle = .coverVertical
-
- rctViewController.present(viewController, animated: true)
- })
- } else {
- presentationMethod = .viewController(rctViewController)
- }
-
- return handler.createEmbeddedView(presentUsing: presentationMethod)
- }
-
- private func createNativeEmbeddedView() {
- guard let rctViewController = RCTPresentedViewController() else { return }
- guard !token.isEmpty, !iOSPresentationStyle.isEmpty, linkHandler == nil else { return }
-
- do {
- let handler = try makeHandler()
- let embeddedView = makeEmbeddedView(rctViewController: rctViewController, handler: handler)
- setup(embeddedView: embeddedView)
- } catch {
- let dict: [String: Any] = [
- embeddedEventName: "onExit",
- "error": "\(error)",
- ]
-
- onEmbeddedEvent?(dict)
- }
- }
-
- private func setup(embeddedView: UIView) {
- embeddedView.translatesAutoresizingMaskIntoConstraints = false
- addSubview(embeddedView)
-
- NSLayoutConstraint.activate([
- embeddedView.topAnchor.constraint(equalTo: topAnchor),
- embeddedView.leadingAnchor.constraint(equalTo: leadingAnchor),
- embeddedView.trailingAnchor.constraint(equalTo: trailingAnchor),
- embeddedView.bottomAnchor.constraint(equalTo: bottomAnchor),
- ])
- }
-}
diff --git a/ios/PLKEmbeddedViewComponentView.h b/ios/PLKEmbeddedViewComponentView.h
deleted file mode 100644
index 9cf089aa..00000000
--- a/ios/PLKEmbeddedViewComponentView.h
+++ /dev/null
@@ -1,28 +0,0 @@
-#ifdef RCT_NEW_ARCH_ENABLED
-
-#import
-#import
-#import
-
-NS_ASSUME_NONNULL_BEGIN
-
-/**
- * This file is required for compatibility with React Native's New Architecture (Fabric Renderer).
- *
- * - PLKEmbeddedViewComponentView extends `RCTViewComponentView` to define a custom native view
- * that works with the Fabric rendering system, improving UI performance and concurrency.
- * - The `#ifdef RCT_NEW_ARCH_ENABLED` directive ensures this code is only compiled when the
- * New Architecture is enabled, avoiding compatibility issues with older architectures.
- * - Custom native views like this are essential when integrating UIKit-based components into
- * React Native apps under the New Architecture.
- * - `RCTUIManager` handles the interaction between the native view and the React Native bridge,
- * enabling updates and commands from the JavaScript side.
- *
- * Ref - https://github.com/reactwg/react-native-new-architecture/blob/main/docs/backwards-compat-turbo-modules.md
- */
-@interface PLKEmbeddedViewComponentView : RCTViewComponentView
-@end
-
-NS_ASSUME_NONNULL_END
-
-#endif // RCT_NEW_ARCH_ENABLED
diff --git a/ios/PLKEmbeddedViewComponentView.mm b/ios/PLKEmbeddedViewComponentView.mm
deleted file mode 100644
index 155deda7..00000000
--- a/ios/PLKEmbeddedViewComponentView.mm
+++ /dev/null
@@ -1,91 +0,0 @@
-#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;
-}
-
-// Needed because of this: https://github.com/facebook/react-native/pull/37274
-+ (void)load
-{
- [super load];
-}
-
-- (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/PLKEmbeddedViewManager.swift b/ios/PLKEmbeddedViewManager.swift
deleted file mode 100644
index 49e99d66..00000000
--- a/ios/PLKEmbeddedViewManager.swift
+++ /dev/null
@@ -1,14 +0,0 @@
-import Foundation
-
-@objc(PLKEmbeddedViewManager)
-class PLKEmbeddedViewManager: RCTViewManager {
-
- override static func requiresMainQueueSetup() -> Bool {
- return true
- }
-
- override func view() -> UIView! {
- return PLKEmbeddedView()
- }
-
-}
diff --git a/ios/PLKFabricHelpers.h b/ios/PLKFabricHelpers.h
deleted file mode 100644
index c432a18f..00000000
--- a/ios/PLKFabricHelpers.h
+++ /dev/null
@@ -1,78 +0,0 @@
-#import
-#import
-#import
-
-#if __has_include()
-#import
-#else
-#ifdef USE_FRAMEWORKS
-#import
-#else
-#ifdef RCT_NEW_ARCH_ENABLED
-#import
-#else
-#import
-#endif
-#endif
-#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/RNLinksdk-Bridging-Header.h b/ios/RNLinksdk-Bridging-Header.h
index 45d7beea..51f60dac 100644
--- a/ios/RNLinksdk-Bridging-Header.h
+++ b/ios/RNLinksdk-Bridging-Header.h
@@ -1,8 +1,7 @@
-#import
-#import
-#import
#import
+#import
#import
-#import
-#import "RNLinksdk.h"
+#ifdef RCT_NEW_ARCH_ENABLED
+#import
+#endif
\ No newline at end of file
diff --git a/ios/RNLinksdk.h b/ios/RNLinksdk.h
deleted file mode 100644
index 035b91ce..00000000
--- a/ios/RNLinksdk.h
+++ /dev/null
@@ -1,19 +0,0 @@
-#ifdef RCT_NEW_ARCH_ENABLED
-#import
-#endif
-#import
-#import "RCTEventEmitter.h"
-
-#import
-
-@interface RNLinksdk : RCTEventEmitter
-#ifdef RCT_NEW_ARCH_ENABLED
-
-#else
-
-#endif
-
-+ (NSDictionary *)dictionaryFromSuccess:(PLKLinkSuccess *)success;
-+ (NSDictionary *)dictionaryFromEvent:(PLKLinkEvent *)event;
-+ (NSDictionary *)dictionaryFromExit:(PLKLinkExit *)exit;
-@end
diff --git a/ios/RNLinksdk.mm b/ios/RNLinksdk.mm
index d057f2d0..59896f8e 100644
--- a/ios/RNLinksdk.mm
+++ b/ios/RNLinksdk.mm
@@ -1,707 +1,51 @@
-#import "RNLinksdk.h"
-#import "RNPlaidHelper.h"
-
-#import
-#import
-#import
-#import
-
-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";
-
-@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
-
-
-@implementation RNLinksdk
-
-RCT_EXPORT_MODULE();
-
-+ (NSString*)sdkVersion {
- return @"12.0.0"; // SDK_VERSION
-}
-
-+ (NSString*)objCBridgeVersion {
- return @"2.0.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(create:(NSString*)token noLoadingState:(BOOL)noLoadingState) {
- __weak RNLinksdk *weakSelf = self;
-
- void (^onSuccess)(PLKLinkSuccess *) = ^(PLKLinkSuccess *success) {
- RNLinksdk *strongSelf = weakSelf;
-
- if (strongSelf.successCallback) {
- NSDictionary *jsMetadata = [RNLinksdk dictionaryFromSuccess:success];
- strongSelf.successCallback(@[jsMetadata]);
- strongSelf.successCallback = nil;
- }
- };
-
- void (^onExit)(PLKLinkExit *) = ^(PLKLinkExit *exit) {
- RNLinksdk *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;
- strongSelf.linkHandler = nil;
- }
- };
-
- void (^onEvent)(PLKLinkEvent *) = ^(PLKLinkEvent *event) {
- RNLinksdk *strongSelf = weakSelf;
- if (strongSelf.hasObservers) {
- NSDictionary *eventDictionary = [RNLinksdk dictionaryFromEvent:event];
- [strongSelf sendEventWithName:kRNLinkKitOnEventEvent
- body:eventDictionary];
-
- // If this is the HANDOFF event.
- if (event.eventName.value == PLKEventNameValueHandoff) {
- // If we have dismissed Link.
- if (strongSelf.presentingViewController == nil) {
- // Deallocate the handler it's no longer needed.
- strongSelf.linkHandler = nil;
- }
- }
- }
- };
-
- PLKLinkTokenConfiguration *config = [PLKLinkTokenConfiguration createWithToken:token onSuccess:onSuccess];
- config.onEvent = onEvent;
- config.onExit = onExit;
- config.noLoadingState = noLoadingState;
-
- NSError *creationError = nil;
- self.linkHandler = [RNPlaidHelper createWithLinkTokenConfiguration:config error:&creationError];
- self.creationError = creationError;
-}
-
-RCT_EXPORT_METHOD(open:(BOOL)fullScreen onSuccess:(RCTResponseSenderBlock)onSuccess onExit:(RCTResponseSenderBlock)onExit) {
- if (self.linkHandler) {
- self.successCallback = onSuccess;
- self.exitCallback = onExit;
- self.presentingViewController = RCTPresentedViewController();
-
- // Some link flows do not need to present UI, so track if presentation happened so dismissal isn't
- // unnecessarily invoked.
- __block bool didPresent = NO;
-
- __weak RNLinksdk *weakSelf = self;
- void(^presentationHandler)(UIViewController *) = ^(UIViewController *linkViewController) {
-
- if (fullScreen) {
- [linkViewController setModalPresentationStyle:UIModalPresentationOverFullScreen];
- [linkViewController setModalTransitionStyle:UIModalTransitionStyleCoverVertical];
- }
-
- [weakSelf.presentingViewController presentViewController:linkViewController animated:YES completion:nil];
- didPresent = YES;
- };
- void(^dismissalHandler)(UIViewController *) = ^(UIViewController *linkViewController) {
- if (didPresent) {
- [weakSelf dismiss];
- didPresent = NO;
- }
- };
- [self.linkHandler openWithPresentationHandler:presentationHandler dismissalHandler:dismissalHandler];
- } else {
- NSString *errorMessage = self.creationError ? self.creationError.userInfo[@"message"] : @"Create was not called.";
- NSString *errorCode = self.creationError ? [@(self.creationError.code) stringValue] : @"-1";
-
- NSDictionary *linkExit = @{
- @"displayMessage": errorMessage,
- @"errorCode": errorCode,
- @"errorType": @"creation error",
- @"errorMessage": errorMessage,
- @"errorDisplayMessage": errorMessage,
- @"errorJson": [NSNull null],
- @"metadata": @{
- @"linkSessionId": [NSNull null],
- @"institution": [NSNull null],
- @"status": [NSNull null],
- @"requestId": [NSNull null],
- @"metadataJson": [NSNull null],
- },
- };
-
- onExit(@[linkExit]);
- }
-}
-
-RCT_EXPORT_METHOD(dismiss) {
- [self.presentingViewController dismissViewControllerAnimated:YES
- completion:nil];
- self.presentingViewController = nil;
-}
-
-RCT_EXPORT_METHOD(syncFinanceKit:(NSString *)token
+#import
+
+/// This interface exposes native functions for React Native, compatible with both the old architecture (Bridge)
+/// and the new architecture (TurboModules & Fabric). These methods are implemented in `RNLinksdk.swift`
+/// and provide functionality to interact with the Link SDK.
+@interface RCT_EXTERN_MODULE(RNLinksdk, NSObject)
+
+// Creates a Link handler with a specified token.
+// The handler initializes the Link SDK experience and prepares it for subsequent operations.
+//
+// Parameters:
+// - token: A unique string used to authenticate and initialize the Link SDK.
+// - noLoadingState: A boolean indicating whether to suppress the loading state during initialization.
+RCT_EXTERN_METHOD(create:(NSString *)token noLoadingState:(BOOL)noLoadingState)
+
+// Opens the Link experience after the handler has been created.
+// This function is used to present the Link UI to the user.
+//
+// Parameters:
+// - fullScreen: A boolean indicating whether the Link UI should be displayed in full-screen mode.
+// - onSuccess: A callback triggered when the Link flow is successfully completed.
+// - onExit: A callback triggered when the user exits the Link flow without completing it.
+RCT_EXTERN_METHOD(open:(BOOL)fullScreen
+ onSuccess:(RCTResponseSenderBlock)onSuccess
+ onExit:(RCTResponseSenderBlock)onExit)
+
+// Dismisses the currently displayed Link UI.
+// Use this function to programmatically close the Link experience if necessary.
+RCT_EXTERN_METHOD(dismiss)
+
+// Submits a user's phone number for an eligibility check for Layer services.
+// This function checks if the provided phone number is eligible for additional features or services.
+//
+// Parameters:
+// - phoneNumber: The user's phone number to be submitted for eligibility verification.
+RCT_EXTERN_METHOD(submit:(NSString *)phoneNumber)
+
+// Syncs transactions from the user's Apple Card using FinanceKit.
+// This function fetches recent Apple Card transactions and updates the user's linked accounts.
+//
+// Parameters:
+// - token: A string used to authenticate and authorize the sync operation.
+// - requestAuthorizationIfNeeded: A boolean indicating whether to request user authorization if it's not already granted.
+// - onSuccess: A callback triggered when the sync operation completes successfully.
+// - onError: A callback triggered when the sync operation fails due to an error.
+RCT_EXTERN_METHOD(syncFinanceKit:(NSString *)token
requestAuthorizationIfNeeded:(BOOL)requestAuthorizationIfNeeded
onSuccess:(RCTResponseSenderBlock)onSuccess
- onError:(RCTResponseSenderBlock)onError) {
-
- [RNPlaidHelper syncFinanceKit:token
- requestAuthorizationIfNeeded:requestAuthorizationIfNeeded
- onSuccess:^{
- onSuccess(@[]);
- }
- onError:^(NSError *error) {
-
- NSDictionary *financeKitError = @{
- @"type": [NSNumber numberWithInteger: error.code],
- @"message": error.localizedDescription
- };
-
- onError(@[financeKitError]);
- }
- ];
-}
-
-
-RCT_EXPORT_METHOD(submit:(NSString * _Nullable)phoneNumber) {
- if (self.linkHandler) {
- PLKSubmissionData *submissionData = [[PLKSubmissionData alloc] init];
- submissionData.phoneNumber = phoneNumber;
- [self.linkHandler submit: submissionData];
- }
-}
-
-#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;
-}
-
-+ (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 ?: @"",
- @"accountNumberMask": metadata.accountNumberMask ?: @"",
- @"isUpdateMode": metadata.isUpdateMode ?: @"",
- @"matchReason": metadata.matchReason ?: @"",
- @"routingNumber": metadata.routingNumber ?: @"",
- @"selection": metadata.selection ?: @"",
- @"linkSessionId": metadata.linkSessionID ?: @"",
- @"mfaType": [self stringForMfaType:metadata.mfaType] ?: @"",
- @"requestId": metadata.requestID ?: @"",
- @"timestamp": [self iso8601StringFromDate:metadata.timestamp] ?: @"",
- @"viewName": [self stringForViewName:metadata.viewName] ?: @"",
- @"metadata_json": 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 PLKEventNameValueIdentityVerificationStartStep:
- return @"IDENTITY_VERIFICATION_START_STEP";
- case PLKEventNameValueIdentityVerificationPassStep:
- return @"IDENTITY_VERIFICATION_PASS_STEP";
- case PLKEventNameValueIdentityVerificationFailStep:
- return @"IDENTITY_VERIFICATION_FAIL_STEP";
- case PLKEventNameValueIdentityVerificationPendingReviewStep:
- return @"IDENTITY_VERIFICATION_PENDING_REVIEW_STEP";
- case PLKEventNameValueIdentityVerificationCreateSession:
- return @"IDENTITY_VERIFICATION_CREATE_SESSION";
- case PLKEventNameValueIdentityVerificationResumeSession:
- return @"IDENTITY_VERIFICATION_RESUME_SESSION";
- case PLKEventNameValueIdentityVerificationPassSession:
- return @"IDENTITY_VERIFICATION_PASS_SESSION";
- case PLKEventNameValueIdentityVerificationFailSession:
- return @"IDENTITY_VERIFICATION_FAIL_SESSION";
- case PLKEventNameValueIdentityVerificationOpenUI:
- return @"IDENTITY_VERIFICATION_OPEN_UI";
- case PLKEventNameValueIdentityVerificationResumeUI:
- return @"IDENTITY_VERIFICATION_RESUME_UI";
- case PLKEventNameValueIdentityVerificationCloseUI:
- return @"IDENTITY_VERIFICATION_CLOSE_UI";
- 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 PLKEventNameValueProfileEligibilityCheckReady:
- return @"PROFILE_ELIGIBILITY_CHECK_READY";
- case PLKEventNameValueProfileEligibilityCheckError:
- return @"PROFILE_ELIGIBILITY_CHECK_ERROR";
- case PLKEventNameValueSearchInstitution:
- return @"SEARCH_INSTITUTION";
- case PLKEventNameValueSelectDegradedInstitution:
- return @"SELECT_DEGRADED_INSTITUTION";
- case PLKEventNameValueSelectDownInstitution:
- return @"SELECT_DOWN_INSTITUTION";
- case PLKEventNameValueSelectInstitution:
- return @"SELECT_INSTITUTION";
- case PLKEventNameValueSubmitCredentials:
- return @"SUBMIT_CREDENTIALS";
- case PLKEventNameValueSubmitMFA:
- return @"SUBMIT_MFA";
- case PLKEventNameValueTransitionView:
- return @"TRANSITION_VIEW";
- case PLKEventNameValueIdentityVerificationPendingReviewSession:
- return @"IDENTITY_VERIFICATION_PENDING_REVIEW_SESSION";
- case PLKEventNameValueSelectFilteredInstitution:
- return @"SELECT_FILTERED_INSTITUTION";
- case PLKEventNameValueSelectBrand:
- return @"SELECT_BRAND";
- case PLKEventNameValueSelectAuthType:
- return @"SELECT_AUTH_TYPE";
- case PLKEventNameValueSubmitAccountNumber:
- return @"SUBMIT_ACCOUNT_NUMBER";
- case PLKEventNameValueSubmitDocuments:
- return @"SUBMIT_DOCUMENTS";
- case PLKEventNameValueSubmitDocumentsSuccess:
- return @"SUBMIT_DOCUMENTS_SUCCESS";
- case PLKEventNameValueSubmitDocumentsError:
- return @"SUBMIT_DOCUMENTS_ERROR";
- case PLKEventNameValueSubmitRoutingNumber:
- return @"SUBMIT_ROUTING_NUMBER";
- case PLKEventNameValueViewDataTypes:
- return @"VIEW_DATA_TYPES";
- case PLKEventNameValueSubmitPhone:
- return @"SUBMIT_PHONE";
- case PLKEventNameValueSkipSubmitPhone:
- return @"SKIP_SUBMIT_PHONE";
- case PLKEventNameValueVerifyPhone:
- return @"VERIFY_PHONE";
- case PLKEventNameValueConnectNewInstitution:
- return @"CONNECT_NEW_INSTITUTION";
- case PLKEventNameValueSubmitOTP:
- return @"SUBMIT_OTP";
- case PLKEventNameValueLayerReady:
- return @"LAYER_READY";
- case PLKEventNameValueLayerNotAvailable:
- return @"LAYER_NOT_AVAILABLE";
- case PLKEventNameValueSubmitEmail:
- return @"SUBMIT_EMAIL";
- case PLKEventNameValueSkipSubmitEmail:
- return @"SKIP_SUBMIT_EMAIL";
- case PLKEventNameValueRememberMeEnabled:
- return @"REMEMBER_ME_ENABLED";
- case PLKEventNameValueRememberMeDisabled:
- return @"REMEMBER_ME_DISABLED";
- case PLKEventNameValueRememberMeHoldout:
- return @"REMEMBER_ME_HOLDOUT";
- case PLKEventNameValueSelectSavedInstitution:
- return @"SELECT_SAVED_INSTITUTION";
- case PLKEventNameValueSelectSavedAccount:
- return @"SELECT_SAVED_ACCOUNT";
- case PLKEventNameValueAutoSelectSavedInstitution:
- return @"AUTO_SELECT_SAVED_INSTITUTION";
- case PLKEventNameValuePlaidCheckPane:
- return @"PLAID_CHECK_PANE";
- }
- 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";
- case PLKExitStatusValueRequiresAccountSelection:
- return @"requires_account_selection";
- case PLKExitStatusValueContinueToThridParty:
- return @"continue_to_third_party";
- }
- 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";
- case PLKViewNameValueUploadDocuments:
- return @"UPLOAD_DOCUMENTS";
- case PLKViewNameValueSubmitDocuments:
- return @"SUBMIT_DOCUMENTS";
- case PLKViewNameValueSubmitDocumentsSuccess:
- return @"SUBMIT_DOCUMENTS_SUCCESS";
- case PLKViewNameValueSubmitDocumentsError:
- return @"SUBMIT_DOCUMENTS_ERROR";
- case PLKViewNameValueOauth:
- return @"OAUTH";
- case PLKViewNameValueAcceptTOS:
- return @"ACCEPT_TOS";
- case PLKViewNameValueDocumentaryVerification:
- return @"DOCUMENTARY_VERIFICATION";
- case PLKViewNameValueKYCCheck:
- return @"KYC_CHECK";
- case PLKViewNameValueSelfieCheck:
- return @"SELFIE_CHECK";
- case PLKViewNameValueRiskCheck:
- return @"RISK_CHECK";
- case PLKViewNameValueScreening:
- return @"SCREENING";
- case PLKViewNameValueVerifySMS:
- return @"VERIFY_SMS";
- case PLKViewNameValueDataTransparency:
- return @"DATA_TRANSPARENCY";
- case PLKViewNameValueDataTransparencyConsent:
- return @"DATA_TRANSPARENCY_CONSENT";
- case PLKViewNameValueSelectAuthType:
- return @"SELECT_AUTH_TYPE";
- case PLKViewNameValueSelectBrand:
- return @"SELECT_BRAND";
- case PLKViewNameValueNumbersSelectInstitution:
- return @"NUMBERS_SELECT_INSTITUTION";
- case PLKViewNameValueSubmitPhone:
- return @"SUBMIT_PHONE";
- case PLKViewNameValueVerifyPhone:
- return @"VERIFY_PHONE";
- case PLKViewNameValueSelectSavedInstitution:
- return @"SELECT_SAVED_INSTITUTION";
- case PLKViewNameValueSelectSavedAccount:
- return @"SELECT_SAVED_ACCOUNT";
- case PLKViewNameValueProfileDataReview:
- return @"PROFILE_DATA_REVIEW";
- case PLKViewNameValueSubmitEmail:
- return @"SUBMIT_EMAIL";
- case PLKViewNameValueVerifyEmail:
- return @"VERIFY_EMAIL";
- }
-
- 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 ?: @"",
- },
- };
-}
-
-#if RCT_NEW_ARCH_ENABLED
-- (std::shared_ptr)getTurboModule:
- (const facebook::react::ObjCTurboModule::InitParams &)params
-{
- return std::make_shared(params);
-}
-#endif
+ onError:(RCTResponseSenderBlock)onError)
@end
diff --git a/ios/RNLinksdk.swift b/ios/RNLinksdk.swift
new file mode 100644
index 00000000..7a82c5f2
--- /dev/null
+++ b/ios/RNLinksdk.swift
@@ -0,0 +1,216 @@
+import LinkKit
+import React
+
+@objc(RNLinksdk)
+class RNLinksdk: RCTEventEmitter {
+
+ // MARK: Internal Constants
+
+ let linkKitOnEventNotification = "onEvent"
+ let linkKitVersionConstant = "version"
+
+ // MARK: RCTEventEmitter
+
+ override func supportedEvents() -> [String] {
+ return [linkKitOnEventNotification]
+ }
+
+ override func startObserving() {
+ self.hasOnEventObserver = true
+ super.startObserving()
+ }
+
+ override func stopObserving() {
+ self.hasOnEventObserver = false
+ super.stopObserving()
+ }
+
+ override class func requiresMainQueueSetup() -> Bool {
+ // LinkKit relies on UIKit.
+ return true
+ }
+
+ class func sdkVersion() -> String {
+ // SDK_VERSION
+ return "11.13.2"
+ }
+
+ class func objCBridgeVersion() -> String {
+ return "3.0.0"
+ }
+
+ override func constantsToExport() -> [AnyHashable: Any]! {
+ [
+ linkKitVersionConstant: String(format: "%s+%.0f", Plaid.version, LinkKitBuild)
+ ]
+ }
+
+ // MARK: LinkKit API
+
+ @objc(create:noLoadingState:)
+ func create(token: String, noLoadingState: Bool) {
+
+ let onSuccess: OnSuccessHandler = { [weak self] success in
+ guard let self = self else { return }
+
+ if let successCallback = self.successCallback {
+ let successsMetadata = JSONHelper.dictionaryFromSuccess(success: success)
+ successCallback([successsMetadata])
+ self.successCallback = nil
+ }
+ }
+
+ let onExit: OnExitHandler = { [weak self] exit in
+ guard let self = self else { return }
+
+ if let exitCallback = self.exitCallback {
+ let exitMetadata = JSONHelper.dictionaryFromExit(exit: exit)
+ if exit.error != nil {
+ exitCallback([exitMetadata["error"] ?? [:], exitMetadata])
+ } else {
+ exitCallback([NSNull(), exitMetadata])
+ }
+ }
+ }
+
+ let onEvent: OnEventHandler = { [weak self] event in
+ guard let self = self, self.hasOnEventObserver else { return }
+
+ let eventDictionary = JSONHelper.dictionaryFromEvent(event)
+ sendLinkEvent(eventDictionary: eventDictionary)
+ }
+
+ var configuration = LinkTokenConfiguration(token: token, onSuccess: onSuccess)
+ configuration.onExit = onExit
+ configuration.onEvent = onEvent
+
+ let result = Plaid.create(configuration)
+ switch result {
+ case .success(let handler):
+ self.handler = handler
+ case .failure(let error):
+ self.createError = error
+ }
+ }
+
+ @objc(open:onSuccess:onExit:)
+ func open(fullScreen: Bool, onSuccess: @escaping RCTResponseSenderBlock, onExit: @escaping RCTResponseSenderBlock) {
+ DispatchQueue.main.async {
+ guard let handler = self.handler else {
+ let errorMessage = self.createError?.localizedDescription ?? "Create was not called."
+ let errorCode = self.createError.map { String($0.code) } ?? "-1"
+
+ let linkExit: [String: Any] = [
+ "displayMessage": errorMessage,
+ "errorCode": errorCode,
+ "errorType": "creation error",
+ "errorMessage": errorMessage,
+ "errorDisplayMessage": errorMessage,
+ "errorJson": NSNull(),
+ "metadata": [
+ "linkSessionId": NSNull(),
+ "institution": NSNull(),
+ "status": NSNull(),
+ "requestId": NSNull(),
+ "metadataJson": NSNull(),
+ ],
+ ]
+
+ onExit([linkExit])
+ return
+ }
+
+ self.successCallback = onSuccess
+ self.exitCallback = onExit
+ self.presentingViewController = RCTPresentedViewController()
+
+ // Track if presentation happened to avoid unnecessary dismissal invocation
+ var didPresent = false
+
+ // Capture weak reference to self to avoid retain cycles
+ weak var weakSelf = self
+
+ // Define the presentation handler
+ let presentationHandler: (UIViewController) -> Void = { linkViewController in
+ if fullScreen {
+ linkViewController.modalPresentationStyle = .overFullScreen
+ linkViewController.modalTransitionStyle = .coverVertical
+ }
+
+ weakSelf?.presentingViewController?.present(linkViewController, animated: true, completion: nil)
+ didPresent = true
+ }
+
+ // Define the dismissal handler
+ let dismissalHandler: (UIViewController) -> Void = { linkViewController in
+ if didPresent {
+ didPresent = false
+
+ DispatchQueue.main.async {
+ weakSelf?.presentingViewController?.dismiss(animated: true, completion: nil)
+ }
+ }
+ }
+
+ // Open with the defined handlers
+ handler.open(presentUsing: .custom(presentationHandler, dismissalHandler))
+ }
+ }
+
+ @objc(dismiss)
+ func dismiss() {
+ DispatchQueue.main.async {
+ self.presentingViewController?.dismiss(animated: true)
+ }
+ }
+
+ @objc(submit:)
+ func submit(phoneNumber: String) {
+ let submissionData = LayerSubmissionData(phoneNumber: phoneNumber)
+ handler?.submit(data: submissionData)
+ }
+
+ @objc(syncFinanceKit:requestAuthorizationIfNeeded:onSuccess:onError:)
+ @available(iOS 17.4, *)
+ func syncFinanceKit(
+ token: String,
+ requestAuthorizationIfNeeded: Bool,
+ onSuccess: @escaping RCTResponseSenderBlock,
+ onError: @escaping RCTResponseSenderBlock
+ ) {
+
+ Plaid.syncFinanceKit(
+ token: token,
+ requestAuthorizationIfNeeded: requestAuthorizationIfNeeded,
+ completion: { result in
+ switch result {
+ case .success:
+ onSuccess([])
+ case .failure(let error):
+ let financeKitError: [String: Any] = [
+ "type": error.code,
+ "message": error.localizedDescription,
+ ]
+
+ onError([financeKitError])
+ }
+ }
+ )
+ }
+
+ // MARK: Private
+
+ private var successCallback: RCTResponseSenderBlock?
+ private var exitCallback: RCTResponseSenderBlock?
+
+ private var handler: LinkKit.Handler?
+ private var createError: LinkKit.Plaid.CreateError?
+
+ private var presentingViewController: UIViewController?
+
+ private var hasOnEventObserver: Bool = false
+
+ private func sendLinkEvent(eventDictionary: [String: Any]) {
+ sendEvent(withName: linkKitOnEventNotification, body: eventDictionary)
+ }
+}
diff --git a/ios/RNPlaidHelper.h b/ios/RNPlaidHelper.h
deleted file mode 100644
index 82aa7ab6..00000000
--- a/ios/RNPlaidHelper.h
+++ /dev/null
@@ -1,11 +0,0 @@
-#import
-
-@interface RNPlaidHelper : NSObject
-
-+ (id _Nullable)createWithLinkTokenConfiguration:(PLKLinkTokenConfiguration * _Nonnull)linkTokenConfiguration error:(NSError * _Nullable * _Nullable)error;
-
-+ (void) syncFinanceKit:(NSString *_Nonnull)token
- requestAuthorizationIfNeeded:(BOOL)requestAuthorizationIfNeeded
- onSuccess:(void (^_Nonnull)(void))onSuccess
- onError:(void (^_Nonnull)(NSError * _Nonnull error))onError;
-@end
diff --git a/ios/RNPlaidHelper.m b/ios/RNPlaidHelper.m
deleted file mode 100644
index dc19513a..00000000
--- a/ios/RNPlaidHelper.m
+++ /dev/null
@@ -1,21 +0,0 @@
-#import "RNPlaidHelper.h"
-
-@implementation RNPlaidHelper
-
-+ (id _Nullable)createWithLinkTokenConfiguration:(PLKLinkTokenConfiguration * _Nonnull)linkTokenConfiguration error:(NSError * _Nullable * _Nullable)error
-{
- return [PLKPlaid createWithLinkTokenConfiguration:linkTokenConfiguration error:error];
-}
-
-+ (void)syncFinanceKit:(NSString *)token requestAuthorizationIfNeeded:(BOOL)requestAuthorizationIfNeeded onSuccess:(void (^)(void))onSuccess onError:(void (^)(NSError * _Nonnull))onError {
- if (@available(iOS 17.4, *)) {
- [PLKPlaid syncFinanceKitWithToken:token requestAuthorizationIfNeeded:requestAuthorizationIfNeeded onSuccess:onSuccess onError:onError];
- } else {
- NSError *error = [NSError errorWithDomain:@"com.plaid.financeKit"
- code:1001
- userInfo:@{ NSLocalizedDescriptionKey: @"FinanceKit Requires iOS >= 17.4" }];
- onError(error);
- }
-}
-
-@end
diff --git a/src/EmbeddedLink/EmbeddedLinkView.tsx b/src/EmbeddedLink/EmbeddedLinkView.tsx
deleted file mode 100644
index 9c36a223..00000000
--- a/src/EmbeddedLink/EmbeddedLinkView.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-import React from 'react';
-import { StyleProp, ViewStyle } from 'react-native';
-import NativeEmbeddedLinkView from './NativeEmbeddedLinkView';
-import {
- LinkSuccessListener,
- LinkSuccess,
- LinkExitListener,
- LinkExit,
- LinkIOSPresentationStyle,
- LinkOnEventListener,
- LinkEvent,
- LinkEventName,
- LinkEventMetadata,
- LinkError,
- LinkExitMetadata,
- LinkSuccessMetadata,
-} from '../Types';
-
-type EmbeddedLinkProps = {
- token: string,
- iOSPresentationStyle: LinkIOSPresentationStyle,
- onEvent: LinkOnEventListener | undefined,
- onSuccess: LinkSuccessListener,
- onExit: LinkExitListener | undefined,
- style: StyleProp | undefined,
-}
-
-class EmbeddedEvent implements LinkEvent {
- eventName: LinkEventName;
- metadata: LinkEventMetadata;
-
- constructor(event: any) {
- this.eventName = event.eventName
- this.metadata = event.metadata
- }
-}
-
-class EmbeddedExit implements LinkExit {
- error: LinkError | undefined;
- metadata: LinkExitMetadata;
-
- constructor(event: any) {
- this.error = event.error;
- this.metadata = event.metadata;
- }
-}
-
-class EmbeddedSuccess implements LinkSuccess {
- publicToken: string;
- metadata: LinkSuccessMetadata;
-
- constructor(event: any) {
- this.publicToken = event.publicToken;
- this.metadata = event.metadata;
- }
-}
-
-export const EmbeddedLinkView: React.FC = (props) => {
-
- const {token, iOSPresentationStyle, onEvent, onSuccess, onExit, style} = props;
-
- const onEmbeddedEvent = (event: any) => {
-
- switch (event.nativeEvent.embeddedEventName) {
- case 'onSuccess': {
- if (!onSuccess) { return; }
- const embeddedSuccess = new EmbeddedSuccess(event.nativeEvent);
- onSuccess(embeddedSuccess);
- break;
- }
- case 'onExit': {
- if (!onExit) {return; }
- const embeddedExit = new EmbeddedExit(event.nativeEvent);
- onExit(embeddedExit);
- break;
- }
- case 'onEvent': {
- if (!onEvent) { return; }
- const embeddedEvent = new EmbeddedEvent(event.nativeEvent);
- onEvent(embeddedEvent);
- break;
- }
- default: {
- return;
- }
- }
- }
-
- return
-};
\ No newline at end of file
diff --git a/src/EmbeddedLink/EmbeddedLinkView.web.tsx b/src/EmbeddedLink/EmbeddedLinkView.web.tsx
deleted file mode 100644
index 7a716097..00000000
--- a/src/EmbeddedLink/EmbeddedLinkView.web.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-// EmbeddedLinkView.web.tsx is a shim file which causes web bundlers to ignore the EmbeddedLinkView.tsx file
-// which imports requireNativeComponent (causing a runtime error with react-native-web).
-// Ref - https://github.com/plaid/react-native-plaid-link-sdk/issues/564
-import React from 'react';
-export const EmbeddedLinkView = () => null;
diff --git a/src/EmbeddedLink/NativeEmbeddedLinkView.tsx b/src/EmbeddedLink/NativeEmbeddedLinkView.tsx
deleted file mode 100644
index 3b37d481..00000000
--- a/src/EmbeddedLink/NativeEmbeddedLinkView.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-import NativeEmbeddedLinkView from '../fabric/EmbeddedLinkViewNativeComponent';
-
-export default NativeEmbeddedLinkView;
diff --git a/src/PlaidLink.tsx b/src/PlaidLink.tsx
index b2ef8ac7..d0b4ac29 100644
--- a/src/PlaidLink.tsx
+++ b/src/PlaidLink.tsx
@@ -16,91 +16,121 @@ import {
import RNLinksdkAndroid from './fabric/NativePlaidLinkModuleAndroid';
import RNLinksdkiOS from './fabric/NativePlaidLinkModuleiOS';
+// Choose the correct native module based on the platform.
+// Use the Android or iOS implementation if available; otherwise, fallback to `undefined`.
const RNLinksdk =
(Platform.OS === 'android' ? RNLinksdkAndroid : RNLinksdkiOS) ?? undefined;
/**
* A hook that registers a listener on the Plaid emitter for the 'onEvent' type.
- * The listener is cleaned up when this view is unmounted
+ * The listener is cleaned up when this component is unmounted.
*
- * @param LinkEventListener the listener to call
+ * @param {LinkEventListener} linkEventListener - The event listener to be called on 'onEvent'.
*/
export const usePlaidEmitter = (linkEventListener: LinkEventListener) => {
useEffect(() => {
+ // Create a new event emitter for the Plaid native module
const emitter = new NativeEventEmitter(RNLinksdk);
const listener = emitter.addListener('onEvent', linkEventListener);
- // Clean up after this effect:
+
+ // Clean up the event listener when the component unmounts
return function cleanup() {
listener.remove();
};
}, []);
};
+/**
+ * Initializes the Plaid Link SDK with the provided token configuration.
+ *
+ * @param {LinkTokenConfiguration} props - Configuration object containing the token and options.
+ */
export const create = (props: LinkTokenConfiguration) => {
- let token = props.token;
- let noLoadingState = props.noLoadingState ?? false;
+ let token = props.token; // The Link token to be used for initialization.
+ let noLoadingState = props.noLoadingState ?? false; // Default to `false` if `noLoadingState` is undefined.
if (Platform.OS === 'android') {
+ // Android-specific initialization with log level
RNLinksdkAndroid?.create(
token,
noLoadingState,
- props.logLevel ?? LinkLogLevel.ERROR,
+ props.logLevel ?? LinkLogLevel.ERROR // Default log level is ERROR.
);
} else {
+ // iOS-specific initialization
RNLinksdkiOS?.create(token, noLoadingState);
}
};
+/**
+ * Opens the Plaid Link interface.
+ *
+ * @param {LinkOpenProps} props - Properties required to open the Plaid Link.
+ */
export const open = async (props: LinkOpenProps) => {
if (Platform.OS === 'android') {
+ // Android-specific open implementation
RNLinksdkAndroid?.open(
(result: LinkSuccess) => {
+ // Handle successful session
if (props.onSuccess != null) {
props.onSuccess(result);
}
},
(result: LinkExit) => {
+ // Handle session exit with possible error
if (props.onExit != null) {
if (result.error != null && result.error.displayMessage != null) {
- //TODO(RNSDK-118): Remove errorDisplayMessage field in next major update.
+ // Legacy field for error display message (to be removed in the next major update)
result.error.errorDisplayMessage = result.error.displayMessage;
}
props.onExit(result);
}
- },
+ }
);
} else {
+ // Determine iOS presentation style
let presentFullScreen =
props.iOSPresentationStyle == LinkIOSPresentationStyle.FULL_SCREEN;
RNLinksdkiOS?.open(
presentFullScreen,
(result: LinkSuccess) => {
+ // Handle successful session
if (props.onSuccess != null) {
props.onSuccess(result);
}
},
(error: LinkError, result: LinkExit) => {
+ // Handle session exit with possible error
if (props.onExit != null) {
if (error) {
var data = result || {};
- data.error = error;
+ data.error = error; // Attach error details to the result.
props.onExit(data);
} else {
props.onExit(result);
}
}
- },
+ }
);
}
};
+/**
+ * Dismisses the Plaid Link interface on iOS.
+ */
export const dismissLink = () => {
if (Platform.OS === 'ios') {
RNLinksdkiOS?.dismiss();
}
};
+/**
+ * Submits additional data, such as a phone number, to the Plaid Link interface.
+ *
+ * @param {SubmissionData} data - Data to be submitted.
+ */
export const submit = (data: SubmissionData): void => {
if (Platform.OS === 'android') {
RNLinksdkAndroid?.submit(data.phoneNumber);
@@ -110,42 +140,51 @@ export const submit = (data: SubmissionData): void => {
};
/**
- * Function to sync the user's transactions from their Apple card.
+ * Syncs the user's transactions from their Apple Card.
*
- * @param {string} token - The `LinkToken` your server retrieved from the /link/token/create endpoint from the Plaid API.
- * This token must be associated with an accessToken.
- * @param {boolean} requestAuthorizationIfNeeded - Indicates if the user should be prompted to authorize the sync if
- * they have not already done so.
- * @param {function} completion - A callback function that is called when the sync has completed.
+ * @param {string} token - The LinkToken retrieved from Plaid's /link/token/create endpoint.
+ * Must be associated with an accessToken.
+ * @param {boolean} requestAuthorizationIfNeeded - Whether to prompt the user for authorization if required.
+ * @param {function} completion - A callback function invoked upon completion with an error (if any).
*
* @warning This method only works on iOS >= 17.4.
- * @warning This method is not supported on Android or MacCatalyst.
- * @warning This method can only be used once the user has granted access to their Apple card via a standard Link Session.
- * @warning This method requires that your app has been granted FinanceKit access from Apple.
+ * @warning Not supported on Android or MacCatalyst.
+ * @warning Requires prior authorization via a standard Link session and FinanceKit access.
*/
export const syncFinanceKit = (
token: string,
requestAuthorizationIfNeeded: boolean,
completion: (error?: FinanceKitError) => void
): void => {
- if (Platform.OS === 'android') {
- completion({
- type: FinanceKitErrorType.Unknown,
- message: "FinanceKit is unavailable on Android!",
- })
- } else {
- RNLinksdkiOS?.syncFinanceKit(
- token,
- requestAuthorizationIfNeeded,
- () => {
- completion()
- },
- (error: FinanceKitError) => {
- completion({
- type: error.type,
- message: error.message,
- })
- }
- )
+ // Check platform compatibility
+ if (Platform.OS === 'android') {
+ completion({
+ type: FinanceKitErrorType.Unknown,
+ message: 'FinanceKit is unavailable on Android!',
+ });
+ return;
+ }
+
+ // Ensure the iOS native module is available
+ if (!RNLinksdkiOS) {
+ completion({
+ type: FinanceKitErrorType.Unknown,
+ message: 'FinanceKit module is not available on this platform!',
+ });
+ return;
+ }
+
+ // Call the iOS native method
+ RNLinksdkiOS.syncFinanceKit(
+ token,
+ requestAuthorizationIfNeeded,
+ () => {
+ // Success callback
+ completion();
+ },
+ (error: FinanceKitError) => {
+ // Error callback
+ completion(error);
}
+ );
};
diff --git a/src/Types.ts b/src/Types.ts
index fccb5a61..2318a587 100644
--- a/src/Types.ts
+++ b/src/Types.ts
@@ -579,37 +579,10 @@ export enum FinanceKitErrorType {
Unknown = 4
}
-interface InvalidTokenError {
- type: FinanceKitErrorType.InvalidToken;
- message: string;
+export interface FinanceKitError {
+ type: FinanceKitErrorType; // Matches the error codes defined in the FinanceKitErrorType enum
+ message: string; // The error message provided by native
}
-
-interface PermissionError {
- type: FinanceKitErrorType.PermissionError;
- message: string;
-}
-
-interface LinkApiError {
- type: FinanceKitErrorType.LinkApiError;
- message: string;
-}
-
-interface PermissionAccessError {
- type: FinanceKitErrorType.PermissionAccessError;
- message: string;
-}
-
-interface UnknownError {
- type: FinanceKitErrorType.Unknown;
- message: string;
-}
-
-export type FinanceKitError =
- | InvalidTokenError
- | PermissionError
- | LinkApiError
- | PermissionAccessError
- | UnknownError;
export interface SubmissionData {
phoneNumber?: string;
diff --git a/src/fabric/EmbeddedLinkViewNativeComponent.ts b/src/fabric/EmbeddedLinkViewNativeComponent.ts
index 7167025f..c0e65037 100644
--- a/src/fabric/EmbeddedLinkViewNativeComponent.ts
+++ b/src/fabric/EmbeddedLinkViewNativeComponent.ts
@@ -1,24 +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';
+// 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 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',
-);
+// export default codegenNativeComponent(
+// 'PLKEmbeddedView',
+// );
diff --git a/src/fabric/NativePlaidLinkModuleAndroid.ts b/src/fabric/NativePlaidLinkModuleAndroid.ts
index 1f912b94..ac9b0f40 100644
--- a/src/fabric/NativePlaidLinkModuleAndroid.ts
+++ b/src/fabric/NativePlaidLinkModuleAndroid.ts
@@ -1,16 +1,43 @@
-// we use Object type because methods on the native side use NSDictionary and ReadableMap
-// and we want to stay compatible with those
+// Import necessary modules and types from React Native and other utilities
+// TurboModuleRegistry and TurboModule are used to define and register TurboModules, which are native modules
+// UnsafeObject is a utility type used to maintain compatibility with native objects like NSDictionary and ReadableMap
import {TurboModuleRegistry, TurboModule} from 'react-native';
import {Double} from 'react-native/Libraries/Types/CodegenTypes';
import {UnsafeObject} from './fabricUtils';
import {LinkSuccess, LinkExit} from '../Types';
+// Define the Spec interface, which extends TurboModule
+// This interface represents the shape of the native module that we are exposing to JavaScript
export interface Spec extends TurboModule {
+ /**
+ * Initializes the Plaid module with the provided Link token and configuration.
+ *
+ * @param token - The Link token used to authenticate the session.
+ * @param noLoadingState - Whether to disable the loading state in the UI.
+ * @param logLevel - The logging level for debugging or error reporting.
+ */
create(token: string, noLoadingState: boolean, logLevel: string): void;
+
+ /**
+ * Opens the Plaid Link session and handles the success and exit callbacks.
+ *
+ * @param onSuccess - Callback function executed when the session completes successfully.
+ * @param onExit - Callback function executed when the session exits, either by user action or an error.
+ */
open(
onSuccess: (result: UnsafeObject) => void,
onExit: (result: UnsafeObject) => void,
): void;
+
+ /**
+ * Starts the Link activity for result on Android, allowing users to link accounts.
+ *
+ * @param token - The Link token used to authenticate the session.
+ * @param noLoadingState - Whether to disable the loading state in the UI.
+ * @param logLevel - The logging level for debugging or error reporting.
+ * @param onSuccessCallback - Callback function executed when the session completes successfully.
+ * @param onExitCallback - Callback function executed when the session exits, either by user action or an error.
+ */
startLinkActivityForResult(
token: string,
noLoadingState: boolean,
@@ -18,10 +45,29 @@ export interface Spec extends TurboModule {
onSuccessCallback: (result: UnsafeObject) => void,
onExitCallback: (result: UnsafeObject) => void
): void;
+
+ /**
+ * Submits a phone number to the Plaid module, typically used for verification or similar purposes.
+ *
+ * @param phoneNumber - The phone number to submit, or undefined if not available.
+ */
submit(phoneNumber: string | undefined): void;
- // those two are here for event emitter methods
+
+ /**
+ * Adds an event listener for the specified event name.
+ *
+ * @param eventName - The name of the event to listen for.
+ */
addListener(eventName: string): void;
+
+ /**
+ * Removes the specified number of event listeners.
+ *
+ * @param count - The number of listeners to remove.
+ */
removeListeners(count: Double): void;
}
+// Export the default instance of the PlaidAndroid TurboModule
+// This retrieves the native module implementation, if available, and provides it to the JavaScript layer.
export default TurboModuleRegistry.get('PlaidAndroid');
diff --git a/src/fabric/NativePlaidLinkModuleiOS.ts b/src/fabric/NativePlaidLinkModuleiOS.ts
index a3e7de4a..76cb2e3f 100644
--- a/src/fabric/NativePlaidLinkModuleiOS.ts
+++ b/src/fabric/NativePlaidLinkModuleiOS.ts
@@ -1,28 +1,81 @@
-// 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 {UnsafeObject} from './fabricUtils';
-import {LinkSuccess, LinkExit, LinkError, FinanceKitError} from '../Types';
+// Import necessary modules for defining the TurboModule interface
+import {TurboModuleRegistry, TurboModule} from 'react-native'; // TurboModule system for native modules
+import {Int32} from 'react-native/Libraries/Types/CodegenTypes'; // Codegen-friendly integer type
+import {UnsafeObject} from './fabricUtils'; // Utility for handling dynamic or loosely-typed objects
+import {LinkSuccess, LinkExit, LinkError, FinanceKitError} from '../Types'; // Type definitions for Link SDK operations
+// Define the Spec interface, which represents the methods exposed by the native RNLinksdk module
export interface Spec extends TurboModule {
+
+ /**
+ * Initializes the Link handler with the provided token.
+ * @param token - The authentication token used to configure Link.
+ * @param noLoadingState - If true, skips showing the loading state in the UI.
+ */
create(token: string, noLoadingState: boolean): void;
+
+ /**
+ * Opens the Link experience.
+ * Must be called after the `create` method has been successfully invoked.
+ * @param fullScreen - If true, opens the Link UI in full-screen mode.
+ * @param onSuccess - Callback invoked when Link completes successfully.
+ * @param onExit - Callback invoked when the user exits the Link UI.
+ */
open(
fullScreen: boolean,
onSuccess: (result: UnsafeObject) => void,
onExit: (error: UnsafeObject, result: UnsafeObject) => void,
- ): void;
+ ): void;
+
+ /**
+ * Dismisses the Link UI.
+ * Can be used to programmatically close the Link experience.
+ */
dismiss(): void;
+
+ /**
+ * Submits the provided phone number for eligibility checks.
+ * @param phoneNumber - The user's phone number, or undefined if not provided.
+ */
submit(phoneNumber: string | undefined): void;
- // those two are here for event emitter methods
+
+ /**
+ * Registers a listener for a specific event emitted by the module.
+ * @param eventName - The name of the event to listen for.
+ */
addListener(eventName: string): void;
+
+ /**
+ * Removes a specified number of event listeners.
+ * @param count - The number of listeners to remove.
+ */
removeListeners(count: Int32): void;
+
+ /**
+ * Synchronizes the user's transactions and data using FinanceKit.
+ *
+ * @param token - The authentication token required to access FinanceKit services.
+ * This token must be linked to an access token from the Plaid API.
+ * @param requestAuthorizationIfNeeded - If true, prompts the user to authorize the sync
+ * if permissions have not been previously granted.
+ * @param onSuccess - Callback function invoked when the synchronization completes successfully.
+ * This function is called with no arguments.
+ * @param onError - Callback function invoked when an error occurs during synchronization.
+ * The error object includes the following properties:
+ * - `type`: A string representing the specific FinanceKit error type.
+ * - `message`: A human-readable description of the error.
+ *
+ * @warning This method is supported only on iOS devices running version 17.4 or higher.
+ * @warning Ensure that your app has been granted FinanceKit access from Apple.
+ * @warning This method cannot be used on Android or Mac Catalyst platforms.
+ */
syncFinanceKit(
- token: string,
- requestAuthorizationIfNeeded: boolean,
- onSuccess: (success: void) => void,
- onError: (error: FinanceKitError) => void
- ): void
+ token: string,
+ requestAuthorizationIfNeeded: boolean,
+ onSuccess: () => void,
+ onError: (error: UnsafeObject) => void
+ ): void;
}
+// Export the TurboModule registry entry for this module, enabling JavaScript access.
export default TurboModuleRegistry.get('RNLinksdk');
diff --git a/src/index.ts b/src/index.ts
index c5edfa46..79893529 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -20,4 +20,4 @@ export {
// Components
-export { EmbeddedLinkView } from './EmbeddedLink/EmbeddedLinkView';
+// export { EmbeddedLinkView } from './EmbeddedLink/EmbeddedLinkView';