Skip to content

Commit 955f02c

Browse files
committed
cr feedback. final pass
1 parent 70bd38d commit 955f02c

File tree

5 files changed

+138
-79
lines changed

5 files changed

+138
-79
lines changed

app/src/navigation/index.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,29 @@ const AppNavigation = createNativeStackNavigator({
4848
screens: navigationScreens,
4949
});
5050

51-
export type RootStackParamList = StaticParamList<typeof AppNavigation>;
51+
type BaseRootStackParamList = StaticParamList<typeof AppNavigation>;
52+
53+
// Explicitly declare route params that are not inferred from initialParams
54+
export type RootStackParamList = Omit<
55+
BaseRootStackParamList,
56+
'ComingSoon' | 'IDPicker' | 'AadhaarUpload' | 'AadhaarUploadError'
57+
> & {
58+
ComingSoon: {
59+
countryCode: string;
60+
documentCategory?: string;
61+
};
62+
IDPicker: {
63+
countryCode: string;
64+
documentTypes: string[];
65+
};
66+
AadhaarUpload: {
67+
countryCode: string;
68+
};
69+
AadhaarUploadError: {
70+
errorType: string;
71+
};
72+
};
73+
5274
export type RootStackScreenProps<T extends keyof RootStackParamList> =
5375
NativeStackScreenProps<RootStackParamList, T>;
5476

app/src/providers/selfClientProvider.tsx

Lines changed: 37 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,6 @@ import { useSettingStore } from '@/stores/settingStore';
2828
import analytics from '@/utils/analytics';
2929

3030
type GlobalCrypto = { crypto?: { subtle?: Crypto['subtle'] } };
31-
32-
type RouteParams<RouteName extends keyof RootStackParamList> = Extract<
33-
RootStackParamList[RouteName],
34-
object
35-
>;
36-
3731
/**
3832
* Provides a configured Self SDK client instance to all descendants.
3933
*
@@ -42,20 +36,24 @@ type RouteParams<RouteName extends keyof RootStackParamList> = Extract<
4236
* - `fetch`/`WebSocket` for network communication
4337
* - Web Crypto hashing with a stub signer
4438
*/
45-
const navigateIfReady = <
46-
RouteName extends keyof RootStackParamList,
47-
Params extends RootStackParamList[RouteName],
48-
>(
49-
...args: undefined extends Params
50-
? [route: RouteName, params?: Params]
51-
: [route: RouteName, params: Params]
52-
) => {
39+
function navigateIfReady<RouteName extends keyof RootStackParamList>(
40+
route: RouteName,
41+
...args: undefined extends RootStackParamList[RouteName]
42+
? [params?: RootStackParamList[RouteName]]
43+
: [params: RootStackParamList[RouteName]]
44+
): void {
5345
if (navigationRef.isReady()) {
54-
const [route, params] = args;
55-
// @ts-expect-error-next-line
56-
navigationRef.navigate(route, params);
46+
const params = args[0];
47+
if (params !== undefined) {
48+
(navigationRef.navigate as (r: RouteName, p: typeof params) => void)(
49+
route,
50+
params,
51+
);
52+
} else {
53+
navigationRef.navigate(route as never);
54+
}
5755
}
58-
};
56+
}
5957

6058
export const SelfClientProvider = ({ children }: PropsWithChildren) => {
6159
const config = useMemo(() => ({}), []);
@@ -165,10 +163,13 @@ export const SelfClientProvider = ({ children }: PropsWithChildren) => {
165163
countryCode: string | null;
166164
documentCategory: string | null;
167165
}) => {
168-
navigateIfReady('ComingSoon', {
169-
countryCode,
170-
documentCategory,
171-
} as never);
166+
// Only navigate if we have a valid country code
167+
if (countryCode) {
168+
navigateIfReady('ComingSoon', {
169+
countryCode,
170+
documentCategory: documentCategory ?? undefined,
171+
});
172+
}
172173
},
173174
);
174175

@@ -235,17 +236,19 @@ export const SelfClientProvider = ({ children }: PropsWithChildren) => {
235236
}
236237
});
237238
addListener(SdkEvents.PROVING_AADHAAR_UPLOAD_FAILURE, ({ errorType }) => {
238-
const params: RouteParams<'AadhaarUploadError'> = {
239-
errorType,
240-
} as RouteParams<'AadhaarUploadError'>;
241-
navigateIfReady('AadhaarUploadError', params);
239+
navigateIfReady('AadhaarUploadError', { errorType });
242240
});
243241

244242
addListener(
245243
SdkEvents.DOCUMENT_COUNTRY_SELECTED,
246-
({ countryCode, documentTypes }) => {
244+
({
245+
countryCode,
246+
documentTypes,
247+
}: {
248+
countryCode: string;
249+
documentTypes: string[];
250+
}) => {
247251
if (navigationRef.isReady()) {
248-
// @ts-expect-error
249252
navigationRef.navigate('IDPicker', { countryCode, documentTypes });
250253
}
251254
},
@@ -262,10 +265,14 @@ export const SelfClientProvider = ({ children }: PropsWithChildren) => {
262265
navigationRef.navigate('DocumentOnboarding');
263266
break;
264267
case 'a':
265-
navigationRef.navigate('AadhaarUpload', { countryCode } as never);
268+
if (countryCode) {
269+
navigationRef.navigate('AadhaarUpload', { countryCode });
270+
}
266271
break;
267272
default:
268-
navigationRef.navigate('ComingSoon', { countryCode } as never);
273+
if (countryCode) {
274+
navigationRef.navigate('ComingSoon', { countryCode });
275+
}
269276
break;
270277
}
271278
}

app/src/types/elliptic.d.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,39 @@
22
// SPDX-License-Identifier: BUSL-1.1
33
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
44

5+
import type { Buffer } from 'buffer';
6+
57
declare module 'elliptic' {
6-
const elliptic: unknown;
7-
export = elliptic;
8+
export interface KeyPair {
9+
getPrivate(enc?: string): Buffer | string;
10+
getPublic(compact?: boolean, enc?: string): Buffer | string | object;
11+
sign(msg: Buffer | string, enc?: string, options?: object): Signature;
12+
verify(
13+
msg: Buffer | string,
14+
signature: Signature | string | object,
15+
): boolean;
16+
derive(pub: KeyPair): Buffer;
17+
}
18+
19+
export interface Signature {
20+
r: Buffer;
21+
s: Buffer;
22+
recoveryParam?: number;
23+
toDER(enc?: string): Buffer | string;
24+
}
25+
26+
export const curves: {
27+
secp256k1: object;
28+
p256: object;
29+
p384: object;
30+
p521: object;
31+
[key: string]: object;
32+
};
33+
34+
export class ec {
35+
constructor(curve: string);
36+
keyFromPrivate(priv: string | Buffer | number[], enc?: string): KeyPair;
37+
keyFromPublic(pub: string | Buffer | object, enc?: string): KeyPair;
38+
genKeyPair(options?: object): KeyPair;
39+
}
840
}

app/src/utils/analytics.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -187,26 +187,37 @@ const flushMixpanelEvents = async () => {
187187
if (__DEV__) console.log('[Mixpanel] flush skipped - NFC scanning active');
188188
return;
189189
}
190+
191+
// Ensure we don't drop events if the native reader isn't available
192+
if (!PassportReader?.trackEvent) {
193+
if (__DEV__)
194+
console.warn('[Mixpanel] flush skipped - NFC module unavailable');
195+
return;
196+
}
197+
190198
try {
191199
if (__DEV__) console.log('[Mixpanel] flush');
192200
// Send any queued events before flushing
193201
while (eventQueue.length > 0) {
194202
const evt = eventQueue.shift()!;
195-
if (PassportReader && PassportReader.trackEvent) {
203+
try {
196204
await Promise.resolve(
197205
PassportReader.trackEvent(evt.name, evt.properties),
198206
);
207+
} catch (trackErr) {
208+
// Put the event back and abort; we'll retry on the next flush
209+
eventQueue.unshift(evt);
210+
throw trackErr;
199211
}
200212
}
201-
if (PassportReader && PassportReader.flush)
213+
if (PassportReader.flush) {
202214
await Promise.resolve(PassportReader.flush());
215+
}
216+
// Only reset event count after successful send/flush
203217
eventCount = 0;
204218
} catch (err) {
205219
if (__DEV__) console.warn('Mixpanel flush failed', err);
206-
// re-queue on failure
207-
if (typeof err !== 'undefined') {
208-
// no-op, events are already queued if failure happened before flush
209-
}
220+
// Events have been re-queued on failure, so they're not lost
210221
}
211222
};
212223

app/src/utils/nfcScanner.ts

Lines changed: 27 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import type { NFCScanContext } from '@selfxyz/mobile-sdk-alpha';
1111
import { logNFCEvent } from '@/Sentry';
1212
import {
1313
type AndroidScanResponse,
14-
PassportReader,
1514
reset,
1615
scan as scanDocument,
1716
} from '@/utils/passportReader';
@@ -94,24 +93,8 @@ const scanIOS = async (
9493
inputs: Inputs,
9594
context: Omit<NFCScanContext, 'stage'>,
9695
) => {
97-
// Narrow type for iOS-specific method availability
98-
const iosReader = PassportReader as
99-
| (typeof PassportReader & {
100-
scanPassport?: (
101-
passportNumber: string,
102-
dateOfBirth: string,
103-
dateOfExpiry: string,
104-
canNumber: string,
105-
useCan: boolean,
106-
skipPACE: boolean,
107-
skipCA: boolean,
108-
extendedMode: boolean,
109-
usePacePolling: boolean,
110-
sessionId: string,
111-
) => unknown;
112-
})
113-
| null;
114-
if (!iosReader?.scanPassport) {
96+
// Use normalized cross-platform scan to avoid duplicated iOS code paths
97+
if (!scanDocument) {
11598
console.warn(
11699
'iOS passport scanner is not available - native module failed to load',
117100
);
@@ -126,25 +109,24 @@ const scanIOS = async (
126109
);
127110
}
128111

129-
return await Promise.resolve(
130-
iosReader.scanPassport(
131-
inputs.passportNumber,
132-
inputs.dateOfBirth,
133-
inputs.dateOfExpiry,
134-
inputs.canNumber ?? '',
135-
inputs.useCan ?? false,
136-
inputs.skipPACE ?? false,
137-
inputs.skipCA ?? false,
138-
inputs.extendedMode ?? false,
139-
inputs.usePacePolling ?? false,
140-
inputs.sessionId,
141-
),
142-
);
112+
return await scanDocument({
113+
documentNumber: inputs.passportNumber,
114+
dateOfBirth: inputs.dateOfBirth,
115+
dateOfExpiry: inputs.dateOfExpiry,
116+
canNumber: inputs.canNumber ?? '',
117+
useCan: inputs.useCan ?? false,
118+
skipPACE: inputs.skipPACE ?? false,
119+
skipCA: inputs.skipCA ?? false,
120+
extendedMode: inputs.extendedMode ?? false,
121+
usePacePolling: inputs.usePacePolling ?? false,
122+
sessionId: inputs.sessionId,
123+
});
143124
};
144125

145126
const handleResponseIOS = (response: unknown) => {
146-
const parsed = JSON.parse(String(response));
147-
const dgHashesObj = JSON.parse(parsed?.dataGroupHashes);
127+
// Response is already parsed as an object by the normalized scan function
128+
const parsed = response as Record<string, unknown>;
129+
const dgHashesObj = JSON.parse(String(parsed?.dataGroupHashes ?? '{}'));
148130
const dg1HashString = dgHashesObj?.DG1?.sodHash;
149131
const dg1Hash = Array.from(Buffer.from(dg1HashString, 'hex'));
150132
const dg2HashString = dgHashesObj?.DG2?.sodHash;
@@ -164,24 +146,29 @@ const handleResponseIOS = (response: unknown) => {
164146
// const _encapsulatedContentDigestAlgorithm =
165147
// parsed?.encapsulatedContentDigestAlgorithm;
166148
const documentSigningCertificate = parsed?.documentSigningCertificate;
167-
const pem = JSON.parse(documentSigningCertificate).PEM.replace(/\n/g, '');
168-
const eContentArray = Array.from(Buffer.from(signedAttributes, 'base64'));
149+
const pem = JSON.parse(String(documentSigningCertificate)).PEM.replace(
150+
/\n/g,
151+
'',
152+
);
153+
const eContentArray = Array.from(
154+
Buffer.from(String(signedAttributes), 'base64'),
155+
);
169156
const signedEContentArray = eContentArray.map(byte =>
170157
byte > 127 ? byte - 256 : byte,
171158
);
172159

173160
const concatenatedDataHashesArray = Array.from(
174-
Buffer.from(eContentBase64, 'base64'),
161+
Buffer.from(String(eContentBase64), 'base64'),
175162
);
176163
const concatenatedDataHashesArraySigned = concatenatedDataHashesArray.map(
177164
byte => (byte > 127 ? byte - 256 : byte),
178165
);
179166

180167
const encryptedDigestArray = Array.from(
181-
Buffer.from(signatureBase64, 'base64'),
168+
Buffer.from(String(signatureBase64), 'base64'),
182169
).map(byte => (byte > 127 ? byte - 256 : byte));
183170

184-
const document_type = mrz.length === 88 ? 'passport' : 'id_card';
171+
const document_type = String(mrz).length === 88 ? 'passport' : 'id_card';
185172

186173
return {
187174
mrz,

0 commit comments

Comments
 (0)