@trustchex/react-native-sdk 1.493.1 → 1.494.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/module/Shared/Components/DiagnosticReportButton.js +118 -29
- package/lib/module/Shared/Components/NavigationManager.js +12 -0
- package/lib/module/Shared/Libs/diagnosticPow.js +56 -3
- package/lib/module/Shared/Libs/diagnosticReport.js +8 -1
- package/lib/module/Shared/Libs/diagnostics.js +61 -2
- package/lib/module/Shared/Libs/sendDiagnosticReport.js +20 -3
- package/lib/module/Translation/Resources/en.js +5 -2
- package/lib/module/Translation/Resources/tr.js +5 -2
- package/lib/module/version.js +1 -1
- package/lib/typescript/src/Shared/Components/DiagnosticReportButton.d.ts +5 -4
- package/lib/typescript/src/Shared/Components/DiagnosticReportButton.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/NavigationManager.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/diagnosticPow.d.ts +26 -3
- package/lib/typescript/src/Shared/Libs/diagnosticPow.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/diagnosticReport.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/diagnostics.d.ts +39 -1
- package/lib/typescript/src/Shared/Libs/diagnostics.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/sendDiagnosticReport.d.ts +9 -0
- package/lib/typescript/src/Shared/Libs/sendDiagnosticReport.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/en.d.ts +3 -0
- package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/tr.d.ts +3 -0
- package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
- package/lib/typescript/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/Shared/Components/DiagnosticReportButton.tsx +128 -34
- package/src/Shared/Components/NavigationManager.tsx +12 -0
- package/src/Shared/Libs/diagnosticPow.ts +71 -3
- package/src/Shared/Libs/diagnosticReport.ts +21 -3
- package/src/Shared/Libs/diagnostics.ts +77 -2
- package/src/Shared/Libs/sendDiagnosticReport.ts +37 -3
- package/src/Translation/Resources/en.ts +6 -2
- package/src/Translation/Resources/tr.ts +6 -2
- package/src/version.ts +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Libs/diagnostics.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Libs/diagnostics.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,10 +14,19 @@ export interface SendDiagnosticParams {
|
|
|
14
14
|
sdkVersion: string;
|
|
15
15
|
/** base64 images to attach (MRZ side / front / face / NFC chip face). */
|
|
16
16
|
images?: DiagnosticImage[];
|
|
17
|
+
/**
|
|
18
|
+
* Progress callback for the proof-of-work step (0..1). The PoW solve is the
|
|
19
|
+
* only slow part; the UI uses this to show a progress bar instead of freezing.
|
|
20
|
+
*/
|
|
21
|
+
onPowProgress?: (fraction: number) => void;
|
|
22
|
+
/** Polled during the solve; return true to cancel. */
|
|
23
|
+
shouldCancel?: () => boolean;
|
|
17
24
|
}
|
|
18
25
|
export interface SendResult {
|
|
19
26
|
/** True when the report was uploaded and stored. */
|
|
20
27
|
uploaded: boolean;
|
|
28
|
+
/** True when the user cancelled before completion. */
|
|
29
|
+
cancelled?: boolean;
|
|
21
30
|
}
|
|
22
31
|
/**
|
|
23
32
|
* Build the report and upload it (over HTTPS) to the demo-diagnostics endpoint.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sendDiagnosticReport.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Libs/sendDiagnosticReport.ts"],"names":[],"mappings":"AAqBA,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"sendDiagnosticReport.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Libs/sendDiagnosticReport.ts"],"names":[],"mappings":"AAqBA,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,oBAAoB,CAAC;AAQ5B,MAAM,WAAW,eAAe;IAC9B,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,KAAK,GAAG,KAAK,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,iBAAiB,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,MAAM,CAAC,EAAE,eAAe,EAAE,CAAC;IAC3B;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC;CAC9B;AAED,MAAM,WAAW,UAAU;IACzB,oDAAoD;IACpD,QAAQ,EAAE,OAAO,CAAC;IAClB,sDAAsD;IACtD,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAKD;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,oBAAoB,KAC3B,OAAO,CAAC,UAAU,CA2GpB,CAAC"}
|
|
@@ -48,6 +48,9 @@ declare const _default: {
|
|
|
48
48
|
'diagnosticReport.noticeBody': string;
|
|
49
49
|
'diagnosticReport.continue': string;
|
|
50
50
|
'diagnosticReport.cancel': string;
|
|
51
|
+
'diagnosticReport.sending': string;
|
|
52
|
+
'diagnosticReport.sendingPreparing': string;
|
|
53
|
+
'diagnosticReport.sendingProgress': string;
|
|
51
54
|
'diagnosticReport.sentTitle': string;
|
|
52
55
|
'diagnosticReport.sentBody': string;
|
|
53
56
|
'diagnosticReport.failedTitle': string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../../../../src/Translation/Resources/en.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../../../../src/Translation/Resources/en.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,wBAoRE"}
|
|
@@ -48,6 +48,9 @@ declare const _default: {
|
|
|
48
48
|
'diagnosticReport.noticeBody': string;
|
|
49
49
|
'diagnosticReport.continue': string;
|
|
50
50
|
'diagnosticReport.cancel': string;
|
|
51
|
+
'diagnosticReport.sending': string;
|
|
52
|
+
'diagnosticReport.sendingPreparing': string;
|
|
53
|
+
'diagnosticReport.sendingProgress': string;
|
|
51
54
|
'diagnosticReport.sentTitle': string;
|
|
52
55
|
'diagnosticReport.sentBody': string;
|
|
53
56
|
'diagnosticReport.failedTitle': string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tr.d.ts","sourceRoot":"","sources":["../../../../../src/Translation/Resources/tr.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tr.d.ts","sourceRoot":"","sources":["../../../../../src/Translation/Resources/tr.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,wBAuRE"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "1.
|
|
1
|
+
export declare const SDK_VERSION = "1.494.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import {
|
|
1
|
+
import React, { useRef, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
ActivityIndicator,
|
|
4
|
+
Alert,
|
|
5
|
+
Modal,
|
|
6
|
+
StyleSheet,
|
|
7
|
+
Text,
|
|
8
|
+
TouchableOpacity,
|
|
9
|
+
View,
|
|
10
|
+
} from 'react-native';
|
|
3
11
|
import { useTranslation } from 'react-i18next';
|
|
4
12
|
import StyledButton from './StyledButton';
|
|
5
13
|
import {
|
|
@@ -20,10 +28,11 @@ export interface DiagnosticReportButtonProps {
|
|
|
20
28
|
/**
|
|
21
29
|
* "Report a scanning issue" button for the result screen.
|
|
22
30
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
31
|
+
* Uploads the diagnostic bundle to the backend (proof-of-work authenticated,
|
|
32
|
+
* encrypted in transit + at rest). The PoW solve is the only slow step, so while
|
|
33
|
+
* sending we show a NON-DISMISSIBLE modal overlay with progress — the user can't
|
|
34
|
+
* dismiss it accidentally (tap-outside / Android-back do nothing), but can
|
|
35
|
+
* explicitly Cancel, which aborts the solve. A thank-you shows on success.
|
|
27
36
|
*
|
|
28
37
|
* Hidden when there's no session/baseUrl to upload to (e.g. before init).
|
|
29
38
|
*/
|
|
@@ -35,11 +44,43 @@ const DiagnosticReportButton: React.FC<DiagnosticReportButtonProps> = ({
|
|
|
35
44
|
}) => {
|
|
36
45
|
const { t } = useTranslation();
|
|
37
46
|
const [sending, setSending] = useState(false);
|
|
47
|
+
const [progress, setProgress] = useState(0);
|
|
48
|
+
// Ref so the running solve sees cancellation immediately (no stale closure).
|
|
49
|
+
const cancelledRef = useRef(false);
|
|
38
50
|
|
|
39
51
|
if (!baseUrl || !sessionId) {
|
|
40
52
|
return null;
|
|
41
53
|
}
|
|
42
54
|
|
|
55
|
+
const doSend = async () => {
|
|
56
|
+
cancelledRef.current = false;
|
|
57
|
+
setProgress(0);
|
|
58
|
+
setSending(true);
|
|
59
|
+
try {
|
|
60
|
+
const { uploaded, cancelled } = await sendDiagnosticReport({
|
|
61
|
+
baseUrl,
|
|
62
|
+
sessionId,
|
|
63
|
+
scan,
|
|
64
|
+
sdkVersion: SDK_VERSION,
|
|
65
|
+
images,
|
|
66
|
+
onPowProgress: setProgress,
|
|
67
|
+
shouldCancel: () => cancelledRef.current,
|
|
68
|
+
});
|
|
69
|
+
setSending(false);
|
|
70
|
+
if (cancelled) return; // user cancelled — no alert
|
|
71
|
+
Alert.alert(
|
|
72
|
+
uploaded
|
|
73
|
+
? t('diagnosticReport.sentTitle')
|
|
74
|
+
: t('diagnosticReport.failedTitle'),
|
|
75
|
+
uploaded
|
|
76
|
+
? t('diagnosticReport.sentBody')
|
|
77
|
+
: t('diagnosticReport.failedBody')
|
|
78
|
+
);
|
|
79
|
+
} catch {
|
|
80
|
+
setSending(false);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
43
84
|
const onPress = () => {
|
|
44
85
|
if (sending) return;
|
|
45
86
|
Alert.alert(
|
|
@@ -47,40 +88,93 @@ const DiagnosticReportButton: React.FC<DiagnosticReportButtonProps> = ({
|
|
|
47
88
|
t('diagnosticReport.noticeBody'),
|
|
48
89
|
[
|
|
49
90
|
{ text: t('diagnosticReport.cancel'), style: 'cancel' },
|
|
50
|
-
{
|
|
51
|
-
text: t('diagnosticReport.continue'),
|
|
52
|
-
onPress: async () => {
|
|
53
|
-
setSending(true);
|
|
54
|
-
try {
|
|
55
|
-
const { uploaded } = await sendDiagnosticReport({
|
|
56
|
-
baseUrl,
|
|
57
|
-
sessionId,
|
|
58
|
-
scan,
|
|
59
|
-
sdkVersion: SDK_VERSION,
|
|
60
|
-
images,
|
|
61
|
-
});
|
|
62
|
-
Alert.alert(
|
|
63
|
-
uploaded
|
|
64
|
-
? t('diagnosticReport.sentTitle')
|
|
65
|
-
: t('diagnosticReport.failedTitle'),
|
|
66
|
-
uploaded
|
|
67
|
-
? t('diagnosticReport.sentBody')
|
|
68
|
-
: t('diagnosticReport.failedBody')
|
|
69
|
-
);
|
|
70
|
-
} finally {
|
|
71
|
-
setSending(false);
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
},
|
|
91
|
+
{ text: t('diagnosticReport.continue'), onPress: doSend },
|
|
75
92
|
]
|
|
76
93
|
);
|
|
77
94
|
};
|
|
78
95
|
|
|
96
|
+
const pct = Math.round(progress * 100);
|
|
97
|
+
|
|
79
98
|
return (
|
|
80
|
-
|
|
81
|
-
{
|
|
82
|
-
|
|
99
|
+
<>
|
|
100
|
+
<StyledButton mode="outlined" onPress={onPress}>
|
|
101
|
+
{t('diagnosticReport.button')}
|
|
102
|
+
</StyledButton>
|
|
103
|
+
|
|
104
|
+
<Modal
|
|
105
|
+
visible={sending}
|
|
106
|
+
transparent
|
|
107
|
+
animationType="fade"
|
|
108
|
+
// Block accidental dismissal: Android hardware-back does nothing while
|
|
109
|
+
// sending. There is no tap-outside-to-close (we render our own backdrop).
|
|
110
|
+
onRequestClose={() => {}}
|
|
111
|
+
>
|
|
112
|
+
<View style={styles.backdrop}>
|
|
113
|
+
<View style={styles.card}>
|
|
114
|
+
<ActivityIndicator size="large" />
|
|
115
|
+
<Text style={styles.title}>{t('diagnosticReport.sending')}</Text>
|
|
116
|
+
<Text style={styles.progress}>
|
|
117
|
+
{pct > 0
|
|
118
|
+
? t('diagnosticReport.sendingProgress', { pct })
|
|
119
|
+
: t('diagnosticReport.sendingPreparing')}
|
|
120
|
+
</Text>
|
|
121
|
+
<TouchableOpacity
|
|
122
|
+
accessibilityRole="button"
|
|
123
|
+
style={styles.cancelBtn}
|
|
124
|
+
onPress={() => {
|
|
125
|
+
cancelledRef.current = true;
|
|
126
|
+
}}
|
|
127
|
+
>
|
|
128
|
+
<Text style={styles.cancelText}>
|
|
129
|
+
{t('diagnosticReport.cancel')}
|
|
130
|
+
</Text>
|
|
131
|
+
</TouchableOpacity>
|
|
132
|
+
</View>
|
|
133
|
+
</View>
|
|
134
|
+
</Modal>
|
|
135
|
+
</>
|
|
83
136
|
);
|
|
84
137
|
};
|
|
85
138
|
|
|
139
|
+
const styles = StyleSheet.create({
|
|
140
|
+
backdrop: {
|
|
141
|
+
flex: 1,
|
|
142
|
+
backgroundColor: 'rgba(0,0,0,0.45)',
|
|
143
|
+
alignItems: 'center',
|
|
144
|
+
justifyContent: 'center',
|
|
145
|
+
padding: 24,
|
|
146
|
+
},
|
|
147
|
+
card: {
|
|
148
|
+
width: '100%',
|
|
149
|
+
maxWidth: 320,
|
|
150
|
+
backgroundColor: 'white',
|
|
151
|
+
borderRadius: 16,
|
|
152
|
+
paddingVertical: 24,
|
|
153
|
+
paddingHorizontal: 20,
|
|
154
|
+
alignItems: 'center',
|
|
155
|
+
gap: 12,
|
|
156
|
+
},
|
|
157
|
+
title: {
|
|
158
|
+
fontSize: 16,
|
|
159
|
+
fontWeight: '600',
|
|
160
|
+
color: '#111827',
|
|
161
|
+
textAlign: 'center',
|
|
162
|
+
},
|
|
163
|
+
progress: {
|
|
164
|
+
fontSize: 14,
|
|
165
|
+
color: '#6B7280',
|
|
166
|
+
textAlign: 'center',
|
|
167
|
+
},
|
|
168
|
+
cancelBtn: {
|
|
169
|
+
marginTop: 8,
|
|
170
|
+
paddingVertical: 10,
|
|
171
|
+
paddingHorizontal: 24,
|
|
172
|
+
},
|
|
173
|
+
cancelText: {
|
|
174
|
+
fontSize: 15,
|
|
175
|
+
fontWeight: '600',
|
|
176
|
+
color: '#DC2626',
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
|
|
86
180
|
export default DiagnosticReportButton;
|
|
@@ -16,6 +16,7 @@ import { useTranslation } from 'react-i18next';
|
|
|
16
16
|
import i18n from '../../Translation';
|
|
17
17
|
import StyledButton from './StyledButton';
|
|
18
18
|
import { analyticsService } from '../Services/AnalyticsService';
|
|
19
|
+
import { diagnostics } from '../Libs/diagnostics';
|
|
19
20
|
|
|
20
21
|
// Navigation lock - use a ref-like pattern so unmounting resets state.
|
|
21
22
|
// The module-level object is shared across all instances but the
|
|
@@ -87,12 +88,23 @@ const NavigationManager = forwardRef(
|
|
|
87
88
|
? workflowSteps[currentStepIndex + 1]
|
|
88
89
|
: null;
|
|
89
90
|
|
|
91
|
+
// Diagnostics: advancing past the current step means it completed. Record
|
|
92
|
+
// its outcome (success — the user moved on) and start-time the next step,
|
|
93
|
+
// so the report captures the WHOLE flow, not just the NFC read.
|
|
94
|
+
const now = Date.now();
|
|
95
|
+
if (currentWorkFlowStep?.type) {
|
|
96
|
+
diagnostics.stepEnded(currentWorkFlowStep.type, 'success', now);
|
|
97
|
+
}
|
|
98
|
+
|
|
90
99
|
if (!nextStep) {
|
|
91
100
|
appContext.currentWorkflowStep = undefined;
|
|
92
101
|
return routes.RESULT;
|
|
93
102
|
}
|
|
94
103
|
|
|
95
104
|
appContext.currentWorkflowStep = nextStep;
|
|
105
|
+
if (nextStep.type) {
|
|
106
|
+
diagnostics.stepStarted(nextStep.type, now);
|
|
107
|
+
}
|
|
96
108
|
|
|
97
109
|
if (nextStep.type === 'CONTRACT_ACCEPTANCE') {
|
|
98
110
|
return routes.DYNAMIC_ROUTES.CONTRACT_ACCEPTANCE;
|
|
@@ -34,9 +34,11 @@ const leadingZeroBits = (bytes: Uint8Array): number => {
|
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
|
-
* Solve the PoW for a challenge. Returns the solution nonce as a
|
|
38
|
-
* Bounded by `maxIterations` so a pathological difficulty can't hang the
|
|
39
|
-
* returns null if unsolved within the bound.
|
|
37
|
+
* Solve the PoW for a challenge (synchronous). Returns the solution nonce as a
|
|
38
|
+
* string. Bounded by `maxIterations` so a pathological difficulty can't hang the
|
|
39
|
+
* app; returns null if unsolved within the bound.
|
|
40
|
+
*
|
|
41
|
+
* Prefer `solvePowAsync` on the UI thread — this blocks until solved.
|
|
40
42
|
*/
|
|
41
43
|
export const solvePow = (
|
|
42
44
|
challenge: string,
|
|
@@ -52,3 +54,69 @@ export const solvePow = (
|
|
|
52
54
|
}
|
|
53
55
|
return null;
|
|
54
56
|
};
|
|
57
|
+
|
|
58
|
+
/** Thrown by solvePowAsync / sendDiagnosticReport when the user cancels. */
|
|
59
|
+
export class PowCancelledError extends Error {
|
|
60
|
+
constructor() {
|
|
61
|
+
super('Proof-of-work cancelled');
|
|
62
|
+
this.name = 'PowCancelledError';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface SolvePowAsyncOptions {
|
|
67
|
+
onProgress?: (fraction: number) => void;
|
|
68
|
+
/** Polled between chunks; return true to abort. Throws PowCancelledError. */
|
|
69
|
+
shouldCancel?: () => boolean;
|
|
70
|
+
maxIterations?: number;
|
|
71
|
+
chunkSize?: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Async, UI-friendly PoW solver. Hashes in small chunks and yields to the event
|
|
76
|
+
* loop between chunks so the JS thread (and the UI) stays responsive instead of
|
|
77
|
+
* freezing during the solve. Reports coarse progress (0..1, best-effort against
|
|
78
|
+
* the expected work for the difficulty) via `onProgress`, and aborts (throwing
|
|
79
|
+
* PowCancelledError) when `shouldCancel` returns true.
|
|
80
|
+
*
|
|
81
|
+
* Returns the solution nonce, or null if unsolved within `maxIterations`.
|
|
82
|
+
*/
|
|
83
|
+
export const solvePowAsync = async (
|
|
84
|
+
challenge: string,
|
|
85
|
+
difficulty: number,
|
|
86
|
+
options: SolvePowAsyncOptions = {}
|
|
87
|
+
): Promise<string | null> => {
|
|
88
|
+
const {
|
|
89
|
+
onProgress,
|
|
90
|
+
shouldCancel,
|
|
91
|
+
maxIterations = 8_000_000,
|
|
92
|
+
// Hash a large chunk between yields: the per-yield setTimeout(0) round-trip
|
|
93
|
+
// on Hermes costs ~ms, so yielding every 2k hashes dominated wall-clock.
|
|
94
|
+
// 25k keeps the UI responsive + cancel snappy (a chunk is a few tens of ms)
|
|
95
|
+
// while spending almost all time hashing, not bouncing through the event loop.
|
|
96
|
+
chunkSize = 25_000,
|
|
97
|
+
} = options;
|
|
98
|
+
// Expected hashes ≈ 2^difficulty; used only to render a smooth-ish bar.
|
|
99
|
+
const expected = Math.max(1, Math.pow(2, difficulty));
|
|
100
|
+
// Encode the fixed "challenge." prefix once; per iteration we only encode the
|
|
101
|
+
// small varying nonce and concat, instead of re-UTF-8-encoding the whole
|
|
102
|
+
// string (the challenge is 32+ chars) on every hash.
|
|
103
|
+
const prefix = Buffer.from(`${challenge}.`, 'utf-8');
|
|
104
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
105
|
+
const digest = sha256(
|
|
106
|
+
Buffer.concat([prefix, Buffer.from(i.toString(), 'utf-8')])
|
|
107
|
+
);
|
|
108
|
+
if (leadingZeroBits(digest) >= difficulty) {
|
|
109
|
+
onProgress?.(1);
|
|
110
|
+
return i.toString();
|
|
111
|
+
}
|
|
112
|
+
if (i % chunkSize === chunkSize - 1) {
|
|
113
|
+
if (shouldCancel?.()) {
|
|
114
|
+
throw new PowCancelledError();
|
|
115
|
+
}
|
|
116
|
+
onProgress?.(Math.min(0.99, i / expected));
|
|
117
|
+
// Yield so React can paint the progress and the UI stays responsive.
|
|
118
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 0));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
};
|
|
@@ -155,15 +155,23 @@ export const buildDiagnosticReport = (params: {
|
|
|
155
155
|
// Set the Turkish-letter-prefix flag from the final accepted MRZ (the collector
|
|
156
156
|
// can't know it during scanning; the report does, from the MRZ text).
|
|
157
157
|
const turkishLetterPrefixApplied =
|
|
158
|
-
diag.mrz.turkishLetterPrefixApplied ||
|
|
158
|
+
diag.mrz.turkishLetterPrefixApplied ||
|
|
159
|
+
computed?.turkishLetterPrefix === true;
|
|
159
160
|
|
|
160
161
|
// --- diagnostics.json (technical, low-PII) ---
|
|
161
162
|
const diagnosticsObj = {
|
|
162
163
|
schemaVersion: diag.schemaVersion,
|
|
163
164
|
generatedAt: diag.generatedAt,
|
|
164
165
|
sessionId: sessionId ?? null,
|
|
165
|
-
sdk: {
|
|
166
|
+
sdk: {
|
|
167
|
+
platform: device.platform,
|
|
168
|
+
sdkVersion,
|
|
169
|
+
buildNumber: device.buildNumber,
|
|
170
|
+
},
|
|
166
171
|
device,
|
|
172
|
+
// Whole-flow, per-step record (contract → document → eID → liveness →
|
|
173
|
+
// consent → video). Diagnostics are NOT just the NFC read.
|
|
174
|
+
steps: diag.steps,
|
|
167
175
|
scan: {
|
|
168
176
|
documentType: scan.documentType ?? diag.documentType ?? null,
|
|
169
177
|
dataSource: scan.dataSource ?? diag.dataSource ?? null,
|
|
@@ -208,11 +216,18 @@ export const buildDiagnosticReport = (params: {
|
|
|
208
216
|
.join(' ')
|
|
209
217
|
: 'n/a';
|
|
210
218
|
|
|
219
|
+
// Compact per-step line, e.g. "Steps: CONTRACT_ACCEPTANCE:success
|
|
220
|
+
// IDENTITY_DOCUMENT_SCAN:success LIVENESS_CHECK:failed".
|
|
221
|
+
const stepsLine = diag.steps.length
|
|
222
|
+
? diag.steps.map((s) => `${s.step}:${s.outcome}`).join(' ')
|
|
223
|
+
: 'none recorded';
|
|
224
|
+
|
|
211
225
|
const body = [
|
|
212
226
|
'TRUSTCHEX SCAN REPORT',
|
|
213
227
|
`Device: ${device.brand} ${device.model} · ${device.systemName} ${device.systemVersion}${device.apiLevel ? ` (API ${device.apiLevel})` : ''}`,
|
|
214
228
|
`SDK: ${sdkVersion} (build ${device.buildNumber}) · ${device.manufacturer} · ${device.deviceId}`,
|
|
215
229
|
`Document: ${scanDataObj.documentType ?? 'unknown'} · source: ${scanDataObj.dataSource ?? 'unknown'}`,
|
|
230
|
+
`Steps: ${stepsLine}`,
|
|
216
231
|
`MRZ: ${diag.mrz.reachedStable ? 'stable' : 'NOT stable'} after ${diag.mrz.framesProcessed} frames · checks: ${checksLine}`,
|
|
217
232
|
`Camera: ${diag.camera.frames} frames · brightness ${diag.camera.avgBrightness} (low:${yesNo(diag.camera.hadLowBrightness)} blur:${yesNo(diag.camera.hadBlurryFrames)})`,
|
|
218
233
|
`NFC: ${nfcLine}`,
|
|
@@ -231,7 +246,10 @@ export const buildDiagnosticReport = (params: {
|
|
|
231
246
|
return {
|
|
232
247
|
diagnosticsJson: JSON.stringify(diagnosticsObj, null, 2),
|
|
233
248
|
scanDataJson: JSON.stringify(scanDataObj, null, 2),
|
|
234
|
-
body:
|
|
249
|
+
body:
|
|
250
|
+
body.length > MAX_BODY_BYTES
|
|
251
|
+
? body.slice(0, MAX_BODY_BYTES - 1) + '…'
|
|
252
|
+
: body,
|
|
235
253
|
subject,
|
|
236
254
|
};
|
|
237
255
|
};
|
|
@@ -95,12 +95,33 @@ export interface MrzDiagnostics {
|
|
|
95
95
|
turkishLetterPrefixApplied: boolean;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
/**
|
|
99
|
+
* A single workflow step's outcome. Diagnostics are NOT just about NFC — every
|
|
100
|
+
* step of the verification flow (contract, document scan, eID, liveness, verbal
|
|
101
|
+
* consent, video call) records one of these so a support/AI reader sees the
|
|
102
|
+
* whole journey: what ran, how long it took, whether it succeeded, and a small
|
|
103
|
+
* non-PII `details` bag specific to the step.
|
|
104
|
+
*/
|
|
105
|
+
export interface WorkflowStepRecord {
|
|
106
|
+
/** Step type, e.g. 'CONTRACT_ACCEPTANCE' | 'IDENTITY_DOCUMENT_SCAN' |
|
|
107
|
+
* 'IDENTITY_DOCUMENT_EID_SCAN' | 'LIVENESS_CHECK' | 'VERBAL_CONSENT' |
|
|
108
|
+
* 'VIDEO_CALL'. */
|
|
109
|
+
step: string;
|
|
110
|
+
/** 'success' | 'failed' | 'skipped' | 'cancelled' | 'started' (still running). */
|
|
111
|
+
outcome: 'success' | 'failed' | 'skipped' | 'cancelled' | 'started';
|
|
112
|
+
durationMs: number;
|
|
113
|
+
/** Coarse, non-PII per-step signals (counts, flags, retries — never PII). */
|
|
114
|
+
details?: Record<string, string | number | boolean | null>;
|
|
115
|
+
}
|
|
116
|
+
|
|
98
117
|
export interface DiagnosticsSnapshot {
|
|
99
118
|
schemaVersion: number;
|
|
100
119
|
generatedAt: string;
|
|
101
120
|
documentType?: string;
|
|
102
121
|
dataSource?: string;
|
|
103
122
|
totalDurationMs: number;
|
|
123
|
+
/** Per-step record of the WHOLE flow, in the order steps started. */
|
|
124
|
+
steps: WorkflowStepRecord[];
|
|
104
125
|
camera: CameraDiagnostics;
|
|
105
126
|
mrz: MrzDiagnostics;
|
|
106
127
|
nfc: NfcDiagnostics;
|
|
@@ -149,9 +170,28 @@ export class DiagnosticsCollector {
|
|
|
149
170
|
private mrz = emptyMrz();
|
|
150
171
|
private nfc = emptyNfc();
|
|
151
172
|
private errors: DiagnosticsSnapshot['errors'] = [];
|
|
173
|
+
private steps: WorkflowStepRecord[] = [];
|
|
174
|
+
// Open steps keyed by step name → start time, for duration on stepEnded.
|
|
175
|
+
private openSteps = new Map<string, number>();
|
|
152
176
|
|
|
153
|
-
/**
|
|
177
|
+
/**
|
|
178
|
+
* Reset the per-SCAN signals (camera/MRZ/NFC) — called when a scan flow
|
|
179
|
+
* starts. Deliberately PRESERVES the workflow `steps` log and accumulated
|
|
180
|
+
* errors: those span the whole verification journey (contract → … → result),
|
|
181
|
+
* and a scan starting mid-flow must not erase steps already completed. Use
|
|
182
|
+
* `resetSession` to clear everything for a brand-new session.
|
|
183
|
+
*/
|
|
154
184
|
reset(nowMs: number): void {
|
|
185
|
+
if (!this.startedAtMs) this.startedAtMs = nowMs;
|
|
186
|
+
this.documentType = undefined;
|
|
187
|
+
this.dataSource = undefined;
|
|
188
|
+
this.camera = emptyCamera();
|
|
189
|
+
this.mrz = emptyMrz();
|
|
190
|
+
this.nfc = emptyNfc();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Clear the ENTIRE session (steps, errors, timings) — new verification. */
|
|
194
|
+
resetSession(nowMs: number): void {
|
|
155
195
|
this.startedAtMs = nowMs;
|
|
156
196
|
this.documentType = undefined;
|
|
157
197
|
this.dataSource = undefined;
|
|
@@ -159,6 +199,8 @@ export class DiagnosticsCollector {
|
|
|
159
199
|
this.mrz = emptyMrz();
|
|
160
200
|
this.nfc = emptyNfc();
|
|
161
201
|
this.errors = [];
|
|
202
|
+
this.steps = [];
|
|
203
|
+
this.openSteps.clear();
|
|
162
204
|
}
|
|
163
205
|
|
|
164
206
|
setDocument(documentType?: string, dataSource?: string): void {
|
|
@@ -166,6 +208,35 @@ export class DiagnosticsCollector {
|
|
|
166
208
|
if (dataSource) this.dataSource = dataSource;
|
|
167
209
|
}
|
|
168
210
|
|
|
211
|
+
// ---- Workflow steps (whole-flow, not just NFC) ----
|
|
212
|
+
|
|
213
|
+
/** Mark a workflow step as started (records start time for duration). */
|
|
214
|
+
stepStarted(step: string, nowMs: number): void {
|
|
215
|
+
this.openSteps.set(step, nowMs);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Record a step's outcome. Pairs with `stepStarted` for duration; if the step
|
|
220
|
+
* was never started, durationMs is 0. `details` is a small non-PII bag of
|
|
221
|
+
* step-specific signals (counts, flags, retries).
|
|
222
|
+
*/
|
|
223
|
+
stepEnded(
|
|
224
|
+
step: string,
|
|
225
|
+
outcome: WorkflowStepRecord['outcome'],
|
|
226
|
+
nowMs: number,
|
|
227
|
+
details?: WorkflowStepRecord['details']
|
|
228
|
+
): void {
|
|
229
|
+
const startedAt = this.openSteps.get(step);
|
|
230
|
+
this.openSteps.delete(step);
|
|
231
|
+
this.steps.push({
|
|
232
|
+
step,
|
|
233
|
+
outcome,
|
|
234
|
+
// `startedAt` can legitimately be 0 (session t0), so check for undefined.
|
|
235
|
+
durationMs: startedAt !== undefined ? Math.max(0, nowMs - startedAt) : 0,
|
|
236
|
+
details: details && Object.keys(details).length ? details : undefined,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
169
240
|
/** Merge a camera snapshot (called periodically as frames arrive). */
|
|
170
241
|
setCamera(c: Partial<CameraDiagnostics>): void {
|
|
171
242
|
this.camera = { ...this.camera, ...c };
|
|
@@ -256,13 +327,17 @@ export class DiagnosticsCollector {
|
|
|
256
327
|
/** Build the serialisable snapshot. */
|
|
257
328
|
snapshot(nowMs: number, isoNow: string): DiagnosticsSnapshot {
|
|
258
329
|
return {
|
|
259
|
-
schemaVersion:
|
|
330
|
+
schemaVersion: 2,
|
|
260
331
|
generatedAt: isoNow,
|
|
261
332
|
documentType: this.documentType,
|
|
262
333
|
dataSource: this.dataSource,
|
|
263
334
|
totalDurationMs: this.startedAtMs
|
|
264
335
|
? Math.max(0, nowMs - this.startedAtMs)
|
|
265
336
|
: 0,
|
|
337
|
+
steps: this.steps.map((s) => ({
|
|
338
|
+
...s,
|
|
339
|
+
details: s.details ? { ...s.details } : undefined,
|
|
340
|
+
})),
|
|
266
341
|
camera: { ...this.camera },
|
|
267
342
|
mrz: { ...this.mrz },
|
|
268
343
|
nfc: { ...this.nfc, steps: this.nfc.steps.map((s) => ({ ...s })) },
|
|
@@ -23,7 +23,11 @@ import {
|
|
|
23
23
|
buildDiagnosticReport,
|
|
24
24
|
type ScanDataForReport,
|
|
25
25
|
} from './diagnosticReport';
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
solvePowAsync,
|
|
28
|
+
PowCancelledError,
|
|
29
|
+
type PowChallenge,
|
|
30
|
+
} from './diagnosticPow';
|
|
27
31
|
import { logError } from './debug.utils';
|
|
28
32
|
|
|
29
33
|
export interface DiagnosticImage {
|
|
@@ -42,11 +46,20 @@ export interface SendDiagnosticParams {
|
|
|
42
46
|
sdkVersion: string;
|
|
43
47
|
/** base64 images to attach (MRZ side / front / face / NFC chip face). */
|
|
44
48
|
images?: DiagnosticImage[];
|
|
49
|
+
/**
|
|
50
|
+
* Progress callback for the proof-of-work step (0..1). The PoW solve is the
|
|
51
|
+
* only slow part; the UI uses this to show a progress bar instead of freezing.
|
|
52
|
+
*/
|
|
53
|
+
onPowProgress?: (fraction: number) => void;
|
|
54
|
+
/** Polled during the solve; return true to cancel. */
|
|
55
|
+
shouldCancel?: () => boolean;
|
|
45
56
|
}
|
|
46
57
|
|
|
47
58
|
export interface SendResult {
|
|
48
59
|
/** True when the report was uploaded and stored. */
|
|
49
60
|
uploaded: boolean;
|
|
61
|
+
/** True when the user cancelled before completion. */
|
|
62
|
+
cancelled?: boolean;
|
|
50
63
|
}
|
|
51
64
|
|
|
52
65
|
const mimeToType = (mime: 'jpg' | 'png'): string =>
|
|
@@ -60,7 +73,15 @@ const mimeToType = (mime: 'jpg' | 'png'): string =>
|
|
|
60
73
|
export const sendDiagnosticReport = async (
|
|
61
74
|
params: SendDiagnosticParams
|
|
62
75
|
): Promise<SendResult> => {
|
|
63
|
-
const {
|
|
76
|
+
const {
|
|
77
|
+
baseUrl,
|
|
78
|
+
sessionId,
|
|
79
|
+
scan,
|
|
80
|
+
sdkVersion,
|
|
81
|
+
images = [],
|
|
82
|
+
onPowProgress,
|
|
83
|
+
shouldCancel,
|
|
84
|
+
} = params;
|
|
64
85
|
if (!baseUrl) {
|
|
65
86
|
return { uploaded: false };
|
|
66
87
|
}
|
|
@@ -121,10 +142,20 @@ export const sendDiagnosticReport = async (
|
|
|
121
142
|
return { uploaded: false };
|
|
122
143
|
}
|
|
123
144
|
const challenge = (await challengeRes.json()) as PowChallenge;
|
|
124
|
-
const solution =
|
|
145
|
+
const solution = await solvePowAsync(
|
|
146
|
+
challenge.challenge,
|
|
147
|
+
challenge.difficulty,
|
|
148
|
+
{
|
|
149
|
+
onProgress: onPowProgress,
|
|
150
|
+
shouldCancel,
|
|
151
|
+
}
|
|
152
|
+
);
|
|
125
153
|
if (!solution) {
|
|
126
154
|
return { uploaded: false };
|
|
127
155
|
}
|
|
156
|
+
if (shouldCancel?.()) {
|
|
157
|
+
return { uploaded: false, cancelled: true };
|
|
158
|
+
}
|
|
128
159
|
|
|
129
160
|
const response = await fetch(`${baseUrl}/api/app/mobile/diagnostics`, {
|
|
130
161
|
method: 'POST',
|
|
@@ -138,6 +169,9 @@ export const sendDiagnosticReport = async (
|
|
|
138
169
|
|
|
139
170
|
return { uploaded: response.ok };
|
|
140
171
|
} catch (e) {
|
|
172
|
+
if (e instanceof PowCancelledError) {
|
|
173
|
+
return { uploaded: false, cancelled: true };
|
|
174
|
+
}
|
|
141
175
|
logError(
|
|
142
176
|
'Diagnostics',
|
|
143
177
|
'Failed to upload diagnostic report',
|