Skip to content

Commit 63c9139

Browse files
merge: pull latest main
# Conflicts: # src/components/LHNOptionsList/OptionRowLHN.tsx
2 parents 3bb6587 + d36e0b0 commit 63c9139

72 files changed

Lines changed: 815 additions & 517 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cspell.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,7 @@
640640
"rideshare",
641641
"RNCORE",
642642
"RNFS",
643+
"RNLinksdk",
643644
"rnmapbox",
644645
"RNTL",
645646
"RNVP",
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# `react-native-plaid-link-sdk` patches
2+
3+
### [react-native-plaid-link-sdk+12.5.3+001+fix-oauth-hybrid-app-ios.patch](react-native-plaid-link-sdk+12.5.3+001+fix-oauth-hybrid-app-ios.patch)
4+
5+
- Reason:
6+
7+
```
8+
Fixes Plaid OAuth getting stuck in a retry loop on HybridApp iOS (e.g.
9+
Banco Santander ES). HybridApp routes the bank's universal-link return
10+
through JS Linking, so LinkKit never sees the callback URL and re-fires
11+
OAuth after its internal timeout. The patch exposes [PLKHandler
12+
resumeAfterTermination:] to JS so we can forward the URL back into the SDK.
13+
14+
The patch:
15+
1. Adds RCT_EXPORT_METHOD(continueFromRedirectUri:) to RNLinksdk.mm.
16+
2. Tracks the active module via a static sSharedInstance, set when
17+
createPlaidLink succeeds and cleared on exit/handoff, so the
18+
forwarder can resolve the correct handler.
19+
3. Adds the matching continueFromRedirectUri Spec entry to
20+
src/fabric/NativePlaidLinkModuleiOS.ts (required on New Architecture
21+
so the TurboModule codegen exposes the method to JS).
22+
```
23+
24+
- Upstream PR/issue: -
25+
- E/App issue: https://github.com/Expensify/App/issues/87757
26+
- PR introducing patch: https://github.com/Expensify/App/pull/88534
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
diff --git a/node_modules/react-native-plaid-link-sdk/ios/RNLinksdk.mm b/node_modules/react-native-plaid-link-sdk/ios/RNLinksdk.mm
2+
index cca454e..a1b66d7 100644
3+
--- a/node_modules/react-native-plaid-link-sdk/ios/RNLinksdk.mm
4+
+++ b/node_modules/react-native-plaid-link-sdk/ios/RNLinksdk.mm
5+
@@ -12,6 +12,12 @@ static NSString* const kRNLinkKitEventNameKey = @"event";
6+
static NSString* const kRNLinkKitEventMetadataKey = @"metadata";
7+
static NSString* const kRNLinkKitVersionConstant = @"version";
8+
9+
+// Tracks the module instance whose linkHandler is currently active for OAuth.
10+
+// Set when createPlaidLink succeeds, cleared whenever linkHandler is nilled.
11+
+// Lets JS forward universal-link OAuth redirects back into the SDK via
12+
+// the RCT_EXPORT_METHOD continueFromRedirectUri: below.
13+
+static RNLinksdk *sSharedInstance = nil;
14+
+
15+
@interface RNLinksdk ()
16+
@property (nonatomic, strong) id<PLKHandler> linkHandler;
17+
@property (nonatomic, strong) UIViewController* presentingViewController;
18+
@@ -92,6 +98,9 @@ RCT_EXPORT_METHOD(createPlaidLink:(NSString*)token noLoadingState:(BOOL)noLoadin
19+
}
20+
strongSelf.exitCallback = nil;
21+
strongSelf.linkHandler = nil;
22+
+ if (sSharedInstance == strongSelf) {
23+
+ sSharedInstance = nil;
24+
+ }
25+
}
26+
};
27+
28+
@@ -108,6 +117,9 @@ RCT_EXPORT_METHOD(createPlaidLink:(NSString*)token noLoadingState:(BOOL)noLoadin
29+
if (strongSelf.presentingViewController == nil) {
30+
// Deallocate the handler it's no longer needed.
31+
strongSelf.linkHandler = nil;
32+
+ if (sSharedInstance == strongSelf) {
33+
+ sSharedInstance = nil;
34+
+ }
35+
}
36+
}
37+
}
38+
@@ -121,6 +133,9 @@ RCT_EXPORT_METHOD(createPlaidLink:(NSString*)token noLoadingState:(BOOL)noLoadin
39+
NSError *creationError = nil;
40+
self.linkHandler = [RNPlaidHelper createWithLinkTokenConfiguration:config error:&creationError];
41+
self.creationError = creationError;
42+
+ if (self.linkHandler) {
43+
+ sSharedInstance = self;
44+
+ }
45+
}
46+
47+
RCT_EXPORT_METHOD(open:(BOOL)fullScreen onSuccess:(RCTResponseSenderBlock)onSuccess onExit:(RCTResponseSenderBlock)onExit) {
48+
@@ -175,6 +190,15 @@ RCT_EXPORT_METHOD(open:(BOOL)fullScreen onSuccess:(RCTResponseSenderBlock)onSucc
49+
}
50+
}
51+
52+
+RCT_EXPORT_METHOD(continueFromRedirectUri:(NSString *)urlString) {
53+
+ NSURL *url = [NSURL URLWithString:urlString];
54+
+ RNLinksdk *instance = sSharedInstance;
55+
+ if (url == nil || instance == nil || instance.linkHandler == nil) {
56+
+ return;
57+
+ }
58+
+ [instance.linkHandler resumeAfterTermination:url];
59+
+}
60+
+
61+
RCT_EXPORT_METHOD(dismiss) {
62+
[self.presentingViewController dismissViewControllerAnimated:YES
63+
completion:nil];
64+
diff --git a/node_modules/react-native-plaid-link-sdk/src/fabric/NativePlaidLinkModuleiOS.ts b/node_modules/react-native-plaid-link-sdk/src/fabric/NativePlaidLinkModuleiOS.ts
65+
index a783aa9..a15e91f 100644
66+
--- a/node_modules/react-native-plaid-link-sdk/src/fabric/NativePlaidLinkModuleiOS.ts
67+
+++ b/node_modules/react-native-plaid-link-sdk/src/fabric/NativePlaidLinkModuleiOS.ts
68+
@@ -12,6 +12,7 @@ export interface Spec extends TurboModule {
69+
onSuccess: (result: UnsafeObject<LinkSuccess>) => void,
70+
onExit: (error: UnsafeObject<LinkError>, result: UnsafeObject<LinkExit>) => void,
71+
): void;
72+
+ continueFromRedirectUri(urlString: string): void;
73+
dismiss(): void;
74+
submit(
75+
phoneNumber: string | undefined,

src/CONST/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8233,6 +8233,7 @@ const CONST = {
82338233
},
82348234
},
82358235
DEFAULT_REPORT_METADATA: {isLoadingInitialReportActions: true},
8236+
DEFAULT_REPORT_LOADING_STATE: {isLoadingInitialReportActions: true},
82368237
UPGRADE_PATHS: {
82378238
CATEGORIES: 'categories',
82388239
REPORTS: 'reports',

src/ONYXKEYS.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,10 @@ const ONYXKEYS = {
484484
// Stores last visited path
485485
LAST_VISITED_PATH: 'lastVisitedPath',
486486

487+
/** Map of reportID → DB-formatted timestamp for when the user last visited each report.
488+
* Only consumed by `findLastAccessedReport` / `getMostRecentlyVisitedReport` for navigation fallbacks. */
489+
REPORT_LAST_VISIT_TIMES: 'reportLastVisitTimes',
490+
487491
// Stores the recently used report fields
488492
RECENTLY_USED_REPORT_FIELDS: 'recentlyUsedReportFields',
489493

@@ -732,10 +736,17 @@ const ONYXKEYS = {
732736
REPORT: 'report_',
733737
REPORT_NAME_VALUE_PAIRS: 'reportNameValuePairs_',
734738
REPORT_DRAFT: 'reportDraft_',
735-
// REPORT_METADATA is a perf optimization used to hold loading states (isLoadingInitialReportActions, isLoadingOlderReportActions, isLoadingNewerReportActions).
736-
// A lot of components are connected to the Report entity and do not care about the actions. Setting the loading state
737-
// directly on the report caused a lot of unnecessary re-renders
739+
// REPORT_METADATA holds report-level business state that is NOT the report itself
740+
// (optimistic flag, pending chat members, report-level errors, DEW pendingExpenseAction).
741+
// Loading flags / pagination cursors / last-visit timestamp live in dedicated
742+
// keys below (REPORT_LOADING_STATE, REPORT_PAGINATION_STATE, REPORT_LAST_VISIT_TIMES)
743+
// so they don't ripple to every subscriber of the report's business state.
738744
REPORT_METADATA: 'reportMetadata_',
745+
/** Session-scoped loading/error flags for a report's action list.
746+
* Registered as RAM-only in `setup/index.ts`. */
747+
RAM_ONLY_REPORT_LOADING_STATE: 'reportLoadingState_',
748+
/** Pagination cursors for a report's action list. */
749+
REPORT_PAGINATION_STATE: 'reportPaginationState_',
739750
REPORT_ACTIONS: 'reportActions_',
740751
REPORT_ACTIONS_DRAFTS: 'reportActionsDrafts_',
741752
REPORT_ACTIONS_PAGES: 'reportActionsPages_',
@@ -1262,6 +1273,8 @@ type OnyxCollectionValuesMapping = {
12621273
[ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS]: OnyxTypes.ReportNameValuePairs;
12631274
[ONYXKEYS.COLLECTION.REPORT_DRAFT]: OnyxTypes.Report;
12641275
[ONYXKEYS.COLLECTION.REPORT_METADATA]: OnyxTypes.ReportMetadata;
1276+
[ONYXKEYS.COLLECTION.RAM_ONLY_REPORT_LOADING_STATE]: OnyxTypes.ReportLoadingState;
1277+
[ONYXKEYS.COLLECTION.REPORT_PAGINATION_STATE]: OnyxTypes.ReportPaginationState;
12651278
[ONYXKEYS.COLLECTION.REPORT_ACTIONS]: OnyxTypes.ReportActions;
12661279
[ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS]: OnyxTypes.ReportActionsDrafts;
12671280
[ONYXKEYS.COLLECTION.REPORT_ACTIONS_PAGES]: OnyxTypes.Pages;
@@ -1457,6 +1470,7 @@ type OnyxValuesMapping = {
14571470
[ONYXKEYS.ONBOARDING_LAST_VISITED_PATH]: string;
14581471
[ONYXKEYS.RAM_ONLY_IS_SEARCHING_FOR_REPORTS]: boolean;
14591472
[ONYXKEYS.LAST_VISITED_PATH]: string | undefined;
1473+
[ONYXKEYS.REPORT_LAST_VISIT_TIMES]: OnyxTypes.ReportLastVisitTimes;
14601474
[ONYXKEYS.RECENTLY_USED_REPORT_FIELDS]: OnyxTypes.RecentlyUsedReportFields;
14611475
[ONYXKEYS.RAM_ONLY_UPDATE_REQUIRED]: boolean;
14621476
[ONYXKEYS.SUPPORTAL_PERMISSION_DENIED]: OnyxTypes.SupportalPermissionDenied | null;

0 commit comments

Comments
 (0)