@trustchex/react-native-sdk 1.493.0 → 1.493.2
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/Libs/diagnosticPow.js +56 -3
- 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/Libs/diagnosticPow.d.ts +26 -3
- package/lib/typescript/src/Shared/Libs/diagnosticPow.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/Libs/diagnosticPow.ts +71 -3
- 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,19 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import React, { useState } from 'react';
|
|
4
|
-
import { Alert } from 'react-native';
|
|
3
|
+
import React, { useRef, useState } from 'react';
|
|
4
|
+
import { ActivityIndicator, Alert, Modal, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
import StyledButton from "./StyledButton.js";
|
|
7
7
|
import { sendDiagnosticReport } from "../Libs/sendDiagnosticReport.js";
|
|
8
8
|
import { SDK_VERSION } from "../../version.js";
|
|
9
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
9
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
10
10
|
/**
|
|
11
11
|
* "Report a scanning issue" button for the result screen.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
13
|
+
* Uploads the diagnostic bundle to the backend (proof-of-work authenticated,
|
|
14
|
+
* encrypted in transit + at rest). The PoW solve is the only slow step, so while
|
|
15
|
+
* sending we show a NON-DISMISSIBLE modal overlay with progress — the user can't
|
|
16
|
+
* dismiss it accidentally (tap-outside / Android-back do nothing), but can
|
|
17
|
+
* explicitly Cancel, which aborts the solve. A thank-you shows on success.
|
|
17
18
|
*
|
|
18
19
|
* Hidden when there's no session/baseUrl to upload to (e.g. before init).
|
|
19
20
|
*/
|
|
@@ -27,9 +28,36 @@ const DiagnosticReportButton = ({
|
|
|
27
28
|
t
|
|
28
29
|
} = useTranslation();
|
|
29
30
|
const [sending, setSending] = useState(false);
|
|
31
|
+
const [progress, setProgress] = useState(0);
|
|
32
|
+
// Ref so the running solve sees cancellation immediately (no stale closure).
|
|
33
|
+
const cancelledRef = useRef(false);
|
|
30
34
|
if (!baseUrl || !sessionId) {
|
|
31
35
|
return null;
|
|
32
36
|
}
|
|
37
|
+
const doSend = async () => {
|
|
38
|
+
cancelledRef.current = false;
|
|
39
|
+
setProgress(0);
|
|
40
|
+
setSending(true);
|
|
41
|
+
try {
|
|
42
|
+
const {
|
|
43
|
+
uploaded,
|
|
44
|
+
cancelled
|
|
45
|
+
} = await sendDiagnosticReport({
|
|
46
|
+
baseUrl,
|
|
47
|
+
sessionId,
|
|
48
|
+
scan,
|
|
49
|
+
sdkVersion: SDK_VERSION,
|
|
50
|
+
images,
|
|
51
|
+
onPowProgress: setProgress,
|
|
52
|
+
shouldCancel: () => cancelledRef.current
|
|
53
|
+
});
|
|
54
|
+
setSending(false);
|
|
55
|
+
if (cancelled) return; // user cancelled — no alert
|
|
56
|
+
Alert.alert(uploaded ? t('diagnosticReport.sentTitle') : t('diagnosticReport.failedTitle'), uploaded ? t('diagnosticReport.sentBody') : t('diagnosticReport.failedBody'));
|
|
57
|
+
} catch {
|
|
58
|
+
setSending(false);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
33
61
|
const onPress = () => {
|
|
34
62
|
if (sending) return;
|
|
35
63
|
Alert.alert(t('diagnosticReport.noticeTitle'), t('diagnosticReport.noticeBody'), [{
|
|
@@ -37,30 +65,91 @@ const DiagnosticReportButton = ({
|
|
|
37
65
|
style: 'cancel'
|
|
38
66
|
}, {
|
|
39
67
|
text: t('diagnosticReport.continue'),
|
|
40
|
-
onPress:
|
|
41
|
-
setSending(true);
|
|
42
|
-
try {
|
|
43
|
-
const {
|
|
44
|
-
uploaded
|
|
45
|
-
} = await sendDiagnosticReport({
|
|
46
|
-
baseUrl,
|
|
47
|
-
sessionId,
|
|
48
|
-
scan,
|
|
49
|
-
sdkVersion: SDK_VERSION,
|
|
50
|
-
images
|
|
51
|
-
});
|
|
52
|
-
Alert.alert(uploaded ? t('diagnosticReport.sentTitle') : t('diagnosticReport.failedTitle'), uploaded ? t('diagnosticReport.sentBody') : t('diagnosticReport.failedBody'));
|
|
53
|
-
} finally {
|
|
54
|
-
setSending(false);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
68
|
+
onPress: doSend
|
|
57
69
|
}]);
|
|
58
70
|
};
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
71
|
+
const pct = Math.round(progress * 100);
|
|
72
|
+
return /*#__PURE__*/_jsxs(_Fragment, {
|
|
73
|
+
children: [/*#__PURE__*/_jsx(StyledButton, {
|
|
74
|
+
mode: "outlined",
|
|
75
|
+
onPress: onPress,
|
|
76
|
+
children: t('diagnosticReport.button')
|
|
77
|
+
}), /*#__PURE__*/_jsx(Modal, {
|
|
78
|
+
visible: sending,
|
|
79
|
+
transparent: true,
|
|
80
|
+
animationType: "fade"
|
|
81
|
+
// Block accidental dismissal: Android hardware-back does nothing while
|
|
82
|
+
// sending. There is no tap-outside-to-close (we render our own backdrop).
|
|
83
|
+
,
|
|
84
|
+
onRequestClose: () => {},
|
|
85
|
+
children: /*#__PURE__*/_jsx(View, {
|
|
86
|
+
style: styles.backdrop,
|
|
87
|
+
children: /*#__PURE__*/_jsxs(View, {
|
|
88
|
+
style: styles.card,
|
|
89
|
+
children: [/*#__PURE__*/_jsx(ActivityIndicator, {
|
|
90
|
+
size: "large"
|
|
91
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
92
|
+
style: styles.title,
|
|
93
|
+
children: t('diagnosticReport.sending')
|
|
94
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
95
|
+
style: styles.progress,
|
|
96
|
+
children: pct > 0 ? t('diagnosticReport.sendingProgress', {
|
|
97
|
+
pct
|
|
98
|
+
}) : t('diagnosticReport.sendingPreparing')
|
|
99
|
+
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
100
|
+
accessibilityRole: "button",
|
|
101
|
+
style: styles.cancelBtn,
|
|
102
|
+
onPress: () => {
|
|
103
|
+
cancelledRef.current = true;
|
|
104
|
+
},
|
|
105
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
106
|
+
style: styles.cancelText,
|
|
107
|
+
children: t('diagnosticReport.cancel')
|
|
108
|
+
})
|
|
109
|
+
})]
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
})]
|
|
64
113
|
});
|
|
65
114
|
};
|
|
115
|
+
const styles = StyleSheet.create({
|
|
116
|
+
backdrop: {
|
|
117
|
+
flex: 1,
|
|
118
|
+
backgroundColor: 'rgba(0,0,0,0.45)',
|
|
119
|
+
alignItems: 'center',
|
|
120
|
+
justifyContent: 'center',
|
|
121
|
+
padding: 24
|
|
122
|
+
},
|
|
123
|
+
card: {
|
|
124
|
+
width: '100%',
|
|
125
|
+
maxWidth: 320,
|
|
126
|
+
backgroundColor: 'white',
|
|
127
|
+
borderRadius: 16,
|
|
128
|
+
paddingVertical: 24,
|
|
129
|
+
paddingHorizontal: 20,
|
|
130
|
+
alignItems: 'center',
|
|
131
|
+
gap: 12
|
|
132
|
+
},
|
|
133
|
+
title: {
|
|
134
|
+
fontSize: 16,
|
|
135
|
+
fontWeight: '600',
|
|
136
|
+
color: '#111827',
|
|
137
|
+
textAlign: 'center'
|
|
138
|
+
},
|
|
139
|
+
progress: {
|
|
140
|
+
fontSize: 14,
|
|
141
|
+
color: '#6B7280',
|
|
142
|
+
textAlign: 'center'
|
|
143
|
+
},
|
|
144
|
+
cancelBtn: {
|
|
145
|
+
marginTop: 8,
|
|
146
|
+
paddingVertical: 10,
|
|
147
|
+
paddingHorizontal: 24
|
|
148
|
+
},
|
|
149
|
+
cancelText: {
|
|
150
|
+
fontSize: 15,
|
|
151
|
+
fontWeight: '600',
|
|
152
|
+
color: '#DC2626'
|
|
153
|
+
}
|
|
154
|
+
});
|
|
66
155
|
export default DiagnosticReportButton;
|
|
@@ -29,9 +29,11 @@ const leadingZeroBits = bytes => {
|
|
|
29
29
|
};
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
* Solve the PoW for a challenge. Returns the solution nonce as a
|
|
33
|
-
* Bounded by `maxIterations` so a pathological difficulty can't hang the
|
|
34
|
-
* returns null if unsolved within the bound.
|
|
32
|
+
* Solve the PoW for a challenge (synchronous). Returns the solution nonce as a
|
|
33
|
+
* string. Bounded by `maxIterations` so a pathological difficulty can't hang the
|
|
34
|
+
* app; returns null if unsolved within the bound.
|
|
35
|
+
*
|
|
36
|
+
* Prefer `solvePowAsync` on the UI thread — this blocks until solved.
|
|
35
37
|
*/
|
|
36
38
|
export const solvePow = (challenge, difficulty, maxIterations = 8_000_000) => {
|
|
37
39
|
for (let i = 0; i < maxIterations; i++) {
|
|
@@ -42,4 +44,55 @@ export const solvePow = (challenge, difficulty, maxIterations = 8_000_000) => {
|
|
|
42
44
|
}
|
|
43
45
|
}
|
|
44
46
|
return null;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/** Thrown by solvePowAsync / sendDiagnosticReport when the user cancels. */
|
|
50
|
+
export class PowCancelledError extends Error {
|
|
51
|
+
constructor() {
|
|
52
|
+
super('Proof-of-work cancelled');
|
|
53
|
+
this.name = 'PowCancelledError';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Async, UI-friendly PoW solver. Hashes in small chunks and yields to the event
|
|
58
|
+
* loop between chunks so the JS thread (and the UI) stays responsive instead of
|
|
59
|
+
* freezing during the solve. Reports coarse progress (0..1, best-effort against
|
|
60
|
+
* the expected work for the difficulty) via `onProgress`, and aborts (throwing
|
|
61
|
+
* PowCancelledError) when `shouldCancel` returns true.
|
|
62
|
+
*
|
|
63
|
+
* Returns the solution nonce, or null if unsolved within `maxIterations`.
|
|
64
|
+
*/
|
|
65
|
+
export const solvePowAsync = async (challenge, difficulty, options = {}) => {
|
|
66
|
+
const {
|
|
67
|
+
onProgress,
|
|
68
|
+
shouldCancel,
|
|
69
|
+
maxIterations = 8_000_000,
|
|
70
|
+
// Hash a large chunk between yields: the per-yield setTimeout(0) round-trip
|
|
71
|
+
// on Hermes costs ~ms, so yielding every 2k hashes dominated wall-clock.
|
|
72
|
+
// 25k keeps the UI responsive + cancel snappy (a chunk is a few tens of ms)
|
|
73
|
+
// while spending almost all time hashing, not bouncing through the event loop.
|
|
74
|
+
chunkSize = 25_000
|
|
75
|
+
} = options;
|
|
76
|
+
// Expected hashes ≈ 2^difficulty; used only to render a smooth-ish bar.
|
|
77
|
+
const expected = Math.max(1, Math.pow(2, difficulty));
|
|
78
|
+
// Encode the fixed "challenge." prefix once; per iteration we only encode the
|
|
79
|
+
// small varying nonce and concat, instead of re-UTF-8-encoding the whole
|
|
80
|
+
// string (the challenge is 32+ chars) on every hash.
|
|
81
|
+
const prefix = Buffer.from(`${challenge}.`, 'utf-8');
|
|
82
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
83
|
+
const digest = sha256(Buffer.concat([prefix, Buffer.from(i.toString(), 'utf-8')]));
|
|
84
|
+
if (leadingZeroBits(digest) >= difficulty) {
|
|
85
|
+
onProgress?.(1);
|
|
86
|
+
return i.toString();
|
|
87
|
+
}
|
|
88
|
+
if (i % chunkSize === chunkSize - 1) {
|
|
89
|
+
if (shouldCancel?.()) {
|
|
90
|
+
throw new PowCancelledError();
|
|
91
|
+
}
|
|
92
|
+
onProgress?.(Math.min(0.99, i / expected));
|
|
93
|
+
// Yield so React can paint the progress and the UI stays responsive.
|
|
94
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
45
98
|
};
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
import { diagnostics } from "./diagnostics.js";
|
|
23
23
|
import { gatherDeviceDetails } from "./native-device-info.utils.js";
|
|
24
24
|
import { buildDiagnosticReport } from "./diagnosticReport.js";
|
|
25
|
-
import {
|
|
25
|
+
import { solvePowAsync, PowCancelledError } from "./diagnosticPow.js";
|
|
26
26
|
import { logError } from "./debug.utils.js";
|
|
27
27
|
const mimeToType = mime => mime === 'png' ? 'image/png' : 'image/jpeg';
|
|
28
28
|
|
|
@@ -37,7 +37,9 @@ export const sendDiagnosticReport = async params => {
|
|
|
37
37
|
sessionId,
|
|
38
38
|
scan,
|
|
39
39
|
sdkVersion,
|
|
40
|
-
images = []
|
|
40
|
+
images = [],
|
|
41
|
+
onPowProgress,
|
|
42
|
+
shouldCancel
|
|
41
43
|
} = params;
|
|
42
44
|
if (!baseUrl) {
|
|
43
45
|
return {
|
|
@@ -95,12 +97,21 @@ export const sendDiagnosticReport = async params => {
|
|
|
95
97
|
};
|
|
96
98
|
}
|
|
97
99
|
const challenge = await challengeRes.json();
|
|
98
|
-
const solution =
|
|
100
|
+
const solution = await solvePowAsync(challenge.challenge, challenge.difficulty, {
|
|
101
|
+
onProgress: onPowProgress,
|
|
102
|
+
shouldCancel
|
|
103
|
+
});
|
|
99
104
|
if (!solution) {
|
|
100
105
|
return {
|
|
101
106
|
uploaded: false
|
|
102
107
|
};
|
|
103
108
|
}
|
|
109
|
+
if (shouldCancel?.()) {
|
|
110
|
+
return {
|
|
111
|
+
uploaded: false,
|
|
112
|
+
cancelled: true
|
|
113
|
+
};
|
|
114
|
+
}
|
|
104
115
|
const response = await fetch(`${baseUrl}/api/app/mobile/diagnostics`, {
|
|
105
116
|
method: 'POST',
|
|
106
117
|
headers: {
|
|
@@ -114,6 +125,12 @@ export const sendDiagnosticReport = async params => {
|
|
|
114
125
|
uploaded: response.ok
|
|
115
126
|
};
|
|
116
127
|
} catch (e) {
|
|
128
|
+
if (e instanceof PowCancelledError) {
|
|
129
|
+
return {
|
|
130
|
+
uploaded: false,
|
|
131
|
+
cancelled: true
|
|
132
|
+
};
|
|
133
|
+
}
|
|
117
134
|
logError('Diagnostics', 'Failed to upload diagnostic report', e instanceof Error ? e.message : String(e));
|
|
118
135
|
return {
|
|
119
136
|
uploaded: false
|
|
@@ -50,8 +50,11 @@ export default {
|
|
|
50
50
|
'diagnosticReport.noticeBody': 'This sends your scan data, document images and technical details to support so they can help resolve a scanning issue.',
|
|
51
51
|
'diagnosticReport.continue': 'Continue',
|
|
52
52
|
'diagnosticReport.cancel': 'Cancel',
|
|
53
|
-
'diagnosticReport.
|
|
54
|
-
'diagnosticReport.
|
|
53
|
+
'diagnosticReport.sending': 'Sending diagnostic report',
|
|
54
|
+
'diagnosticReport.sendingPreparing': 'Preparing…',
|
|
55
|
+
'diagnosticReport.sendingProgress': 'Preparing… {{pct}}%',
|
|
56
|
+
'diagnosticReport.sentTitle': 'Thank you!',
|
|
57
|
+
'diagnosticReport.sentBody': 'Thanks for sending your diagnostic data — it helps us improve scanning. Our team will look into the issue.',
|
|
55
58
|
'diagnosticReport.failedTitle': "Couldn't send the report",
|
|
56
59
|
'diagnosticReport.failedBody': 'Please try again later.',
|
|
57
60
|
'livenessDetectionScreen.guideHeader': 'Face Verification',
|
|
@@ -50,8 +50,11 @@ export default {
|
|
|
50
50
|
'diagnosticReport.noticeBody': 'Bu işlem; tarama verilerinizi, belge görüntülerinizi ve teknik ayrıntıları destek ekibine gönderir; böylece tarama sorununu çözebilirler.',
|
|
51
51
|
'diagnosticReport.continue': 'Devam et',
|
|
52
52
|
'diagnosticReport.cancel': 'İptal',
|
|
53
|
-
'diagnosticReport.
|
|
54
|
-
'diagnosticReport.
|
|
53
|
+
'diagnosticReport.sending': 'Tanılama raporu gönderiliyor',
|
|
54
|
+
'diagnosticReport.sendingPreparing': 'Hazırlanıyor…',
|
|
55
|
+
'diagnosticReport.sendingProgress': 'Hazırlanıyor… %{{pct}}',
|
|
56
|
+
'diagnosticReport.sentTitle': 'Teşekkürler!',
|
|
57
|
+
'diagnosticReport.sentBody': 'Tanılama verilerinizi gönderdiğiniz için teşekkürler — taramayı iyileştirmemize yardımcı oluyor. Ekibimiz sorunu inceleyecek.',
|
|
55
58
|
'diagnosticReport.failedTitle': 'Rapor gönderilemedi',
|
|
56
59
|
'diagnosticReport.failedBody': 'Lütfen daha sonra tekrar deneyin.',
|
|
57
60
|
'livenessDetectionScreen.guideHeader': 'Yüz Doğrulaması',
|
package/lib/module/version.js
CHANGED
|
@@ -11,10 +11,11 @@ export interface DiagnosticReportButtonProps {
|
|
|
11
11
|
/**
|
|
12
12
|
* "Report a scanning issue" button for the result screen.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
* Uploads the diagnostic bundle to the backend (proof-of-work authenticated,
|
|
15
|
+
* encrypted in transit + at rest). The PoW solve is the only slow step, so while
|
|
16
|
+
* sending we show a NON-DISMISSIBLE modal overlay with progress — the user can't
|
|
17
|
+
* dismiss it accidentally (tap-outside / Android-back do nothing), but can
|
|
18
|
+
* explicitly Cancel, which aborts the solve. A thank-you shows on success.
|
|
18
19
|
*
|
|
19
20
|
* Hidden when there's no session/baseUrl to upload to (e.g. before init).
|
|
20
21
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DiagnosticReportButton.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Components/DiagnosticReportButton.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"DiagnosticReportButton.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Components/DiagnosticReportButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA2B,MAAM,OAAO,CAAC;AAYhD,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAGlE,MAAM,WAAW,2BAA2B;IAC1C,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,iBAAiB,CAAC;IACxB,MAAM,CAAC,EAAE,eAAe,EAAE,CAAC;CAC5B;AAED;;;;;;;;;;GAUG;AACH,QAAA,MAAM,sBAAsB,EAAE,KAAK,CAAC,EAAE,CAAC,2BAA2B,CAkGjE,CAAC;AA2CF,eAAe,sBAAsB,CAAC"}
|
|
@@ -14,9 +14,32 @@ export interface PowChallenge {
|
|
|
14
14
|
expiresAt: number;
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
-
* Solve the PoW for a challenge. Returns the solution nonce as a
|
|
18
|
-
* Bounded by `maxIterations` so a pathological difficulty can't hang the
|
|
19
|
-
* returns null if unsolved within the bound.
|
|
17
|
+
* Solve the PoW for a challenge (synchronous). Returns the solution nonce as a
|
|
18
|
+
* string. Bounded by `maxIterations` so a pathological difficulty can't hang the
|
|
19
|
+
* app; returns null if unsolved within the bound.
|
|
20
|
+
*
|
|
21
|
+
* Prefer `solvePowAsync` on the UI thread — this blocks until solved.
|
|
20
22
|
*/
|
|
21
23
|
export declare const solvePow: (challenge: string, difficulty: number, maxIterations?: number) => string | null;
|
|
24
|
+
/** Thrown by solvePowAsync / sendDiagnosticReport when the user cancels. */
|
|
25
|
+
export declare class PowCancelledError extends Error {
|
|
26
|
+
constructor();
|
|
27
|
+
}
|
|
28
|
+
export interface SolvePowAsyncOptions {
|
|
29
|
+
onProgress?: (fraction: number) => void;
|
|
30
|
+
/** Polled between chunks; return true to abort. Throws PowCancelledError. */
|
|
31
|
+
shouldCancel?: () => boolean;
|
|
32
|
+
maxIterations?: number;
|
|
33
|
+
chunkSize?: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Async, UI-friendly PoW solver. Hashes in small chunks and yields to the event
|
|
37
|
+
* loop between chunks so the JS thread (and the UI) stays responsive instead of
|
|
38
|
+
* freezing during the solve. Reports coarse progress (0..1, best-effort against
|
|
39
|
+
* the expected work for the difficulty) via `onProgress`, and aborts (throwing
|
|
40
|
+
* PowCancelledError) when `shouldCancel` returns true.
|
|
41
|
+
*
|
|
42
|
+
* Returns the solution nonce, or null if unsolved within `maxIterations`.
|
|
43
|
+
*/
|
|
44
|
+
export declare const solvePowAsync: (challenge: string, difficulty: number, options?: SolvePowAsyncOptions) => Promise<string | null>;
|
|
22
45
|
//# sourceMappingURL=diagnosticPow.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diagnosticPow.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Libs/diagnosticPow.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AAEH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAiBD
|
|
1
|
+
{"version":3,"file":"diagnosticPow.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Libs/diagnosticPow.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AAEH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAiBD;;;;;;GAMG;AACH,eAAO,MAAM,QAAQ,GACnB,WAAW,MAAM,EACjB,YAAY,MAAM,EAClB,sBAAyB,KACxB,MAAM,GAAG,IASX,CAAC;AAEF,4EAA4E;AAC5E,qBAAa,iBAAkB,SAAQ,KAAK;;CAK3C;AAED,MAAM,WAAW,oBAAoB;IACnC,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,6EAA6E;IAC7E,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,GACxB,WAAW,MAAM,EACjB,YAAY,MAAM,EAClB,UAAS,oBAAyB,KACjC,OAAO,CAAC,MAAM,GAAG,IAAI,CAmCvB,CAAC"}
|
|
@@ -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.493.
|
|
1
|
+
export declare const SDK_VERSION = "1.493.2";
|
|
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;
|
|
@@ -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
|
+
};
|
|
@@ -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',
|
|
@@ -59,8 +59,12 @@ export default {
|
|
|
59
59
|
'This sends your scan data, document images and technical details to support so they can help resolve a scanning issue.',
|
|
60
60
|
'diagnosticReport.continue': 'Continue',
|
|
61
61
|
'diagnosticReport.cancel': 'Cancel',
|
|
62
|
-
'diagnosticReport.
|
|
63
|
-
'diagnosticReport.
|
|
62
|
+
'diagnosticReport.sending': 'Sending diagnostic report',
|
|
63
|
+
'diagnosticReport.sendingPreparing': 'Preparing…',
|
|
64
|
+
'diagnosticReport.sendingProgress': 'Preparing… {{pct}}%',
|
|
65
|
+
'diagnosticReport.sentTitle': 'Thank you!',
|
|
66
|
+
'diagnosticReport.sentBody':
|
|
67
|
+
'Thanks for sending your diagnostic data — it helps us improve scanning. Our team will look into the issue.',
|
|
64
68
|
'diagnosticReport.failedTitle': "Couldn't send the report",
|
|
65
69
|
'diagnosticReport.failedBody': 'Please try again later.',
|
|
66
70
|
'livenessDetectionScreen.guideHeader': 'Face Verification',
|
|
@@ -59,8 +59,12 @@ export default {
|
|
|
59
59
|
'Bu işlem; tarama verilerinizi, belge görüntülerinizi ve teknik ayrıntıları destek ekibine gönderir; böylece tarama sorununu çözebilirler.',
|
|
60
60
|
'diagnosticReport.continue': 'Devam et',
|
|
61
61
|
'diagnosticReport.cancel': 'İptal',
|
|
62
|
-
'diagnosticReport.
|
|
63
|
-
'diagnosticReport.
|
|
62
|
+
'diagnosticReport.sending': 'Tanılama raporu gönderiliyor',
|
|
63
|
+
'diagnosticReport.sendingPreparing': 'Hazırlanıyor…',
|
|
64
|
+
'diagnosticReport.sendingProgress': 'Hazırlanıyor… %{{pct}}',
|
|
65
|
+
'diagnosticReport.sentTitle': 'Teşekkürler!',
|
|
66
|
+
'diagnosticReport.sentBody':
|
|
67
|
+
'Tanılama verilerinizi gönderdiğiniz için teşekkürler — taramayı iyileştirmemize yardımcı oluyor. Ekibimiz sorunu inceleyecek.',
|
|
64
68
|
'diagnosticReport.failedTitle': 'Rapor gönderilemedi',
|
|
65
69
|
'diagnosticReport.failedBody': 'Lütfen daha sonra tekrar deneyin.',
|
|
66
70
|
'livenessDetectionScreen.guideHeader': 'Yüz Doğrulaması',
|
package/src/version.ts
CHANGED