truescene-face-id-capture-sdk 1.0.0 → 1.0.1
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/README.md +113 -21
- package/dist/_redirects +0 -3
- package/dist/index.js +261 -346
- package/dist/internal/regions.js +5 -0
- package/dist/sdk/CaptureExperience.js +97 -125
- package/dist/sdk/element.js +94 -65
- package/dist/sdk/react/index.js +55 -25
- package/dist/types/internal/regions.d.ts +2 -0
- package/dist/types/sdk/CaptureExperience.d.ts +14 -10
- package/dist/types/sdk/element.d.ts +21 -12
- package/dist/types/sdk/index.d.ts +1 -1
- package/dist/types/sdk/react/index.d.ts +22 -16
- package/dist/types/sdk/types.d.ts +43 -4
- package/dist/utils/config.js +1 -1
- package/package.json +1 -1
|
@@ -1,58 +1,66 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect,
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
3
|
import FaceAndIdCapture, {} from '../components/FaceAndIdCapture';
|
|
4
|
+
import { REGION_URLS } from '../internal/regions';
|
|
4
5
|
const stripDataUrl = (value) => {
|
|
5
6
|
if (!value)
|
|
6
7
|
return null;
|
|
7
8
|
const commaIndex = value.indexOf(',');
|
|
8
9
|
return commaIndex >= 0 ? value.slice(commaIndex + 1) : value;
|
|
9
10
|
};
|
|
10
|
-
const
|
|
11
|
-
if (typeof value !== 'number' || Number.isNaN(value)) {
|
|
12
|
-
return '--';
|
|
13
|
-
}
|
|
14
|
-
return value.toFixed(digits);
|
|
15
|
-
};
|
|
16
|
-
const CaptureExperience = ({ sessionToken, compareUrl = '/comparev2', tokenHeader = 'Authorization', tokenPrefix = 'Bearer', tokenField = 'session_token', autoStart = false, initialShowDebug = false, showDebugToggle = false, className, onReadyChange, onStepChange, onMetricsChange, onCapture, onCompareResult, onCompareError, onError, }) => {
|
|
11
|
+
const CaptureExperience = ({ sessionToken, region, autoStart = false, compareTimeoutMs = 1000000, className, showHeader = true, showInstructions = true, showStatus = true, headerEyebrow = 'TrueScene Capture', headerTitle = 'Fast & Deepfake Resistant', headerSubtitle = 'ID Verification', instructions = `Press Start Verification. Align your face in the oval, then place your ID under your nose so it covers your mouth. Keep your eyes visible.`, onReadyChange, onStepChange, onMetricsChange, onCapture, onVerificationCode, onVerificationError, onError, }) => {
|
|
17
12
|
const [ready, setReady] = useState({
|
|
18
13
|
faceReady: false,
|
|
19
14
|
idReady: false,
|
|
20
15
|
});
|
|
21
16
|
const [step, setStep] = useState('FACE_ALIGN');
|
|
22
|
-
const [metrics, setMetrics] = useState(null);
|
|
23
|
-
const [showDebug, setShowDebug] = useState(initialShowDebug);
|
|
24
17
|
const [started, setStarted] = useState(false);
|
|
25
18
|
const [captures, setCaptures] = useState({
|
|
26
19
|
faceImage: null,
|
|
27
20
|
idImage: null,
|
|
28
21
|
fullImage: null,
|
|
29
22
|
});
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
const
|
|
23
|
+
const onVerificationCodeRef = useRef(onVerificationCode);
|
|
24
|
+
const onVerificationErrorRef = useRef(onVerificationError);
|
|
25
|
+
const onErrorRef = useRef(onError);
|
|
26
|
+
const lastSubmissionRef = useRef(null);
|
|
34
27
|
const trimmedToken = sessionToken?.trim() ?? '';
|
|
35
28
|
const tokenMissing = trimmedToken.length === 0;
|
|
29
|
+
const resolvedCompareUrl = region ? `${REGION_URLS[region]}/comparev2` : '';
|
|
36
30
|
useEffect(() => {
|
|
37
31
|
if (!autoStart || started || tokenMissing) {
|
|
38
32
|
return;
|
|
39
33
|
}
|
|
40
34
|
setStarted(true);
|
|
41
35
|
}, [autoStart, started, tokenMissing]);
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
onVerificationCodeRef.current = onVerificationCode;
|
|
38
|
+
onVerificationErrorRef.current = onVerificationError;
|
|
39
|
+
onErrorRef.current = onError;
|
|
40
|
+
}, [onVerificationCode, onVerificationError, onError]);
|
|
41
|
+
const emitVerificationError = (error) => {
|
|
42
|
+
onVerificationErrorRef.current?.(error);
|
|
43
|
+
onErrorRef.current?.(error.message);
|
|
44
|
+
};
|
|
42
45
|
useEffect(() => {
|
|
43
46
|
if (!captures.faceImage || !captures.idImage) {
|
|
44
47
|
return;
|
|
45
48
|
}
|
|
46
49
|
if (tokenMissing) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
emitVerificationError({
|
|
51
|
+
code: 'MISSING_TOKEN',
|
|
52
|
+
message: 'Session token is required to submit verification.',
|
|
53
|
+
});
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (!region) {
|
|
57
|
+
emitVerificationError({
|
|
58
|
+
code: 'MISSING_REGION',
|
|
59
|
+
message: 'Region is required to submit verification.',
|
|
60
|
+
});
|
|
53
61
|
return;
|
|
54
62
|
}
|
|
55
|
-
const last =
|
|
63
|
+
const last = lastSubmissionRef.current;
|
|
56
64
|
if (last &&
|
|
57
65
|
last.faceImage === captures.faceImage &&
|
|
58
66
|
last.idImage === captures.idImage &&
|
|
@@ -64,39 +72,45 @@ const CaptureExperience = ({ sessionToken, compareUrl = '/comparev2', tokenHeade
|
|
|
64
72
|
if (!facePayload || !idPayload) {
|
|
65
73
|
return;
|
|
66
74
|
}
|
|
67
|
-
|
|
75
|
+
lastSubmissionRef.current = {
|
|
68
76
|
faceImage: captures.faceImage,
|
|
69
77
|
idImage: captures.idImage,
|
|
70
78
|
sessionToken: trimmedToken,
|
|
71
79
|
};
|
|
72
80
|
let isActive = true;
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
81
|
+
onVerificationErrorRef.current?.(null);
|
|
82
|
+
onVerificationCodeRef.current?.(null);
|
|
83
|
+
const controller = new AbortController();
|
|
84
|
+
let timedOut = false;
|
|
85
|
+
const timeoutId = compareTimeoutMs > 0
|
|
86
|
+
? window.setTimeout(() => {
|
|
87
|
+
timedOut = true;
|
|
88
|
+
controller.abort();
|
|
89
|
+
}, compareTimeoutMs)
|
|
90
|
+
: null;
|
|
77
91
|
const headers = {
|
|
78
92
|
'Content-Type': 'application/json',
|
|
79
93
|
};
|
|
80
|
-
|
|
81
|
-
headers[tokenHeader] = tokenPrefix
|
|
82
|
-
? `${tokenPrefix} ${trimmedToken}`
|
|
83
|
-
: trimmedToken;
|
|
84
|
-
}
|
|
94
|
+
headers.Authorization = `Bearer ${trimmedToken}`;
|
|
85
95
|
const payload = {
|
|
86
96
|
image_face: facePayload,
|
|
87
97
|
image_idcard: idPayload,
|
|
98
|
+
session_token: trimmedToken,
|
|
88
99
|
};
|
|
89
|
-
|
|
90
|
-
payload[tokenField] = trimmedToken;
|
|
91
|
-
}
|
|
92
|
-
fetch(compareUrl, {
|
|
100
|
+
fetch(resolvedCompareUrl, {
|
|
93
101
|
method: 'POST',
|
|
94
102
|
headers,
|
|
95
103
|
body: JSON.stringify(payload),
|
|
104
|
+
signal: controller.signal,
|
|
96
105
|
})
|
|
97
106
|
.then(async (res) => {
|
|
98
107
|
if (!res.ok) {
|
|
99
|
-
|
|
108
|
+
emitVerificationError({
|
|
109
|
+
code: 'HTTP_ERROR',
|
|
110
|
+
message: `Server error (${res.status})`,
|
|
111
|
+
status: res.status,
|
|
112
|
+
});
|
|
113
|
+
return null;
|
|
100
114
|
}
|
|
101
115
|
return res.json();
|
|
102
116
|
})
|
|
@@ -104,116 +118,77 @@ const CaptureExperience = ({ sessionToken, compareUrl = '/comparev2', tokenHeade
|
|
|
104
118
|
if (!isActive) {
|
|
105
119
|
return;
|
|
106
120
|
}
|
|
121
|
+
if (!data) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
107
124
|
if (typeof data?.error === 'string' && data.error.trim().length > 0) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
125
|
+
emitVerificationError({
|
|
126
|
+
code: 'INVALID_RESPONSE',
|
|
127
|
+
message: data.error,
|
|
128
|
+
});
|
|
112
129
|
return;
|
|
113
130
|
}
|
|
114
|
-
|
|
115
|
-
|
|
131
|
+
if (typeof data?.verification_code !== 'string' ||
|
|
132
|
+
data.verification_code.trim().length === 0) {
|
|
133
|
+
emitVerificationError({
|
|
134
|
+
code: 'INVALID_RESPONSE',
|
|
135
|
+
message: 'Verification code missing in response.',
|
|
136
|
+
});
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const response = {
|
|
140
|
+
verification_code: data.verification_code,
|
|
141
|
+
expires_at: typeof data?.expires_at === 'string' ? data.expires_at : '',
|
|
142
|
+
};
|
|
143
|
+
onVerificationCodeRef.current?.(response);
|
|
116
144
|
})
|
|
117
145
|
.catch((error) => {
|
|
118
146
|
if (!isActive) {
|
|
119
147
|
return;
|
|
120
148
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
149
|
+
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
150
|
+
if (timedOut) {
|
|
151
|
+
emitVerificationError({
|
|
152
|
+
code: 'REQUEST_TIMEOUT',
|
|
153
|
+
message: 'Verification request timed out.',
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const message = error instanceof Error
|
|
159
|
+
? error.message
|
|
160
|
+
: 'Verification request failed';
|
|
161
|
+
emitVerificationError({ code: 'NETWORK_ERROR', message });
|
|
125
162
|
})
|
|
126
163
|
.finally(() => {
|
|
127
|
-
if (
|
|
128
|
-
|
|
164
|
+
if (timeoutId) {
|
|
165
|
+
window.clearTimeout(timeoutId);
|
|
129
166
|
}
|
|
130
|
-
setCompareLoading(false);
|
|
131
167
|
});
|
|
132
168
|
return () => {
|
|
133
169
|
isActive = false;
|
|
170
|
+
if (timeoutId) {
|
|
171
|
+
window.clearTimeout(timeoutId);
|
|
172
|
+
}
|
|
173
|
+
controller.abort();
|
|
134
174
|
};
|
|
135
175
|
}, [
|
|
136
176
|
captures.faceImage,
|
|
137
177
|
captures.idImage,
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
tokenHeader,
|
|
178
|
+
compareTimeoutMs,
|
|
179
|
+
resolvedCompareUrl,
|
|
141
180
|
tokenMissing,
|
|
142
|
-
tokenPrefix,
|
|
143
181
|
trimmedToken,
|
|
144
|
-
onCompareError,
|
|
145
|
-
onCompareResult,
|
|
146
|
-
onError,
|
|
147
182
|
]);
|
|
148
183
|
const statusLabel = ready.idReady ? 'Ready to continue' : 'Not ready';
|
|
149
184
|
const statusClass = ready.idReady
|
|
150
185
|
? 'status-pill status-pill--ready'
|
|
151
186
|
: 'status-pill status-pill--not';
|
|
152
187
|
const stepLabel = step === 'FACE_ALIGN' ? 'Step 1 of 2: Face align' : 'Step 2 of 2: ID align';
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
return [
|
|
158
|
-
{ label: 'Step', value: metrics.step },
|
|
159
|
-
{ label: 'Face ready', value: metrics.ready.faceReady ? 'Yes' : 'No' },
|
|
160
|
-
{ label: 'ID ready', value: metrics.ready.idReady ? 'Yes' : 'No' },
|
|
161
|
-
{ label: 'Face count', value: `${metrics.face.faceCount}` },
|
|
162
|
-
{
|
|
163
|
-
label: 'Face box',
|
|
164
|
-
value: metrics.face.faceBoxNorm
|
|
165
|
-
? `${metrics.face.faceBoxNorm.x.toFixed(2)}, ${metrics.face.faceBoxNorm.y.toFixed(2)}, ${metrics.face.faceBoxNorm.w.toFixed(2)}, ${metrics.face.faceBoxNorm.h.toFixed(2)}`
|
|
166
|
-
: '--',
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
label: 'Face yaw',
|
|
170
|
-
value: metrics.face.poseValid
|
|
171
|
-
? `${metrics.face.yawDeg.toFixed(1)} deg`
|
|
172
|
-
: '--',
|
|
173
|
-
},
|
|
174
|
-
{
|
|
175
|
-
label: 'Face pitch',
|
|
176
|
-
value: metrics.face.poseValid
|
|
177
|
-
? `${metrics.face.pitchDeg.toFixed(1)} deg`
|
|
178
|
-
: '--',
|
|
179
|
-
},
|
|
180
|
-
{
|
|
181
|
-
label: 'Face roll',
|
|
182
|
-
value: metrics.face.poseValid
|
|
183
|
-
? `${metrics.face.rollDeg.toFixed(1)} deg`
|
|
184
|
-
: '--',
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
label: 'ID rect',
|
|
188
|
-
value: metrics.id.rectNorm
|
|
189
|
-
? `${metrics.id.rectNorm.x.toFixed(2)}, ${metrics.id.rectNorm.y.toFixed(2)}, ${metrics.id.rectNorm.w.toFixed(2)}, ${metrics.id.rectNorm.h.toFixed(2)}`
|
|
190
|
-
: '--',
|
|
191
|
-
},
|
|
192
|
-
{
|
|
193
|
-
label: 'ID ROI face count',
|
|
194
|
-
value: `${metrics.id.roiFaceCount}`,
|
|
195
|
-
},
|
|
196
|
-
{
|
|
197
|
-
label: 'ID ROI face box',
|
|
198
|
-
value: metrics.id.roiLargestFaceBoxNorm
|
|
199
|
-
? `${metrics.id.roiLargestFaceBoxNorm.x.toFixed(2)}, ${metrics.id.roiLargestFaceBoxNorm.y.toFixed(2)}, ${metrics.id.roiLargestFaceBoxNorm.w.toFixed(2)}, ${metrics.id.roiLargestFaceBoxNorm.h.toFixed(2)}`
|
|
200
|
-
: '--',
|
|
201
|
-
},
|
|
202
|
-
{
|
|
203
|
-
label: 'ID ROI face size',
|
|
204
|
-
value: metrics.id.roiLargestFaceSizeRatio.toFixed(3),
|
|
205
|
-
},
|
|
206
|
-
{
|
|
207
|
-
label: 'ID ROI mean lum',
|
|
208
|
-
value: metrics.id.meanLumROI.toFixed(0),
|
|
209
|
-
},
|
|
210
|
-
{
|
|
211
|
-
label: 'ID ROI blur',
|
|
212
|
-
value: metrics.id.blurScoreROI.toFixed(0),
|
|
213
|
-
},
|
|
214
|
-
{ label: 'Hint', value: metrics.hint },
|
|
215
|
-
];
|
|
216
|
-
}, [metrics]);
|
|
188
|
+
const showHeaderSection = showHeader || (showStatus && started);
|
|
189
|
+
const headerClassName = showHeader
|
|
190
|
+
? 'app__header'
|
|
191
|
+
: 'app__header app__header--status-only';
|
|
217
192
|
const handleReadyChange = (next) => {
|
|
218
193
|
setReady(next);
|
|
219
194
|
onReadyChange?.(next);
|
|
@@ -223,19 +198,16 @@ const CaptureExperience = ({ sessionToken, compareUrl = '/comparev2', tokenHeade
|
|
|
223
198
|
onStepChange?.(next);
|
|
224
199
|
};
|
|
225
200
|
const handleMetricsChange = (next) => {
|
|
226
|
-
setMetrics(next);
|
|
227
201
|
onMetricsChange?.(next);
|
|
228
202
|
};
|
|
229
203
|
const handleCapture = (images) => {
|
|
230
204
|
setCaptures(images);
|
|
231
205
|
onCapture?.(images);
|
|
232
206
|
};
|
|
233
|
-
return (_jsxs("div", { className: `ts-capture${className ? ` ${className}` : ''}`, children: [_jsxs("header", { className:
|
|
207
|
+
return (_jsxs("div", { className: `ts-capture${className ? ` ${className}` : ''}`, children: [showHeaderSection && (_jsxs("header", { className: headerClassName, children: [showHeader && (_jsxs("div", { children: [headerEyebrow && (_jsx("div", { className: "app__eyebrow", children: headerEyebrow })), (headerTitle || headerSubtitle) && (_jsxs("div", { children: [headerTitle && (_jsx("h4", { className: "app__title", children: headerTitle })), headerSubtitle && (_jsx("h4", { className: "app__title", children: headerSubtitle }))] })), showInstructions && instructions && (_jsx("p", { className: "app__subtitle", children: instructions }))] })), started && showStatus && (_jsxs("div", { className: "app__status", children: [_jsxs("div", { className: "app__status-stack", children: [_jsx("span", { className: "status-pill status-pill--step", children: stepLabel }), _jsxs("span", { className: ready.faceReady
|
|
234
208
|
? 'status-pill status-pill--ready'
|
|
235
209
|
: 'status-pill status-pill--pending', children: ["Face ", ready.faceReady ? 'ready' : 'not ready'] }), _jsxs("span", { className: ready.idReady
|
|
236
210
|
? 'status-pill status-pill--ready'
|
|
237
|
-
: 'status-pill status-pill--pending', children: ["ID ", ready.idReady ? 'ready' : 'not ready'] })] }), _jsx("span", { className: statusClass, children: statusLabel })] }))] }), _jsxs("main", { className: "app__grid", children: [_jsxs("section", { className: "app__camera", children: [!started ? (_jsxs("div", { className: "app__start", children: [_jsx("button", { className: "btn btn--primary", type: "button", disabled: tokenMissing, onClick: () => setStarted(true), children: "Start Verification" }), tokenMissing && (_jsx("p", { className: "app__token-hint", children: "Provide a session token before starting." }))] })) : captures.idImage ? (_jsxs("div", { className: "
|
|
238
|
-
? 'match-result match-result--yes'
|
|
239
|
-
: 'match-result match-result--no', children: compareResult.match ? 'YES' : 'NO' })] }), _jsxs("details", { className: "metrics-details", children: [_jsxs("summary", { className: "metrics-summary", children: [_jsxs("span", { className: "metrics-summary__top", children: [_jsx("span", { children: "Match %" }), _jsxs("span", { className: "metrics-summary__value", children: [formatNumber(compareResult.match_percentage, 2), "%"] })] }), _jsx("span", { className: "metrics-summary__arrow", "aria-hidden": "true" })] }), _jsxs("div", { className: "metrics-row", children: [_jsx("span", { children: "Cosine distance" }), _jsx("span", { children: formatNumber(compareResult.cosine_distance, 4) })] }), _jsxs("div", { className: "metrics-row", children: [_jsx("span", { children: "Cosine similarity" }), _jsx("span", { children: formatNumber(compareResult.cosine_similarity, 4) })] })] })] }))] })), captures.idImage && _jsx("br", {}), showDebug && metrics && (_jsxs("div", { className: "panel-card panel-card--metrics", children: [_jsx("h2", { children: "Debug metrics" }), _jsx("div", { className: "metrics-grid", children: metricsRows.map((row) => (_jsxs("div", { className: "metrics-row", children: [_jsx("span", { children: row.label }), _jsx("span", { children: row.value })] }, row.label))) })] })), showDebug && metrics && (_jsx("div", { children: _jsx("br", {}) })), !captures.idImage && (_jsxs("div", { className: "panel-card", children: [_jsx("h2", { children: "Quick checklist" }), _jsxs("ul", { children: [_jsx("li", { children: "Fill the oval, but avoid getting too close." }), _jsx("li", { children: "Place the ID under your nose and keep eyes visible." }), _jsx("li", { children: "Face a soft, even light source and avoid glare." }), _jsx("li", { children: "Hold steady until the status turns ready." })] })] })), _jsx("br", {}), _jsxs("div", { className: "app__cta-row", children: [_jsx("p", { children: "Already tried verification? Let's start integrating it into your app." }), _jsx("a", { className: "btn btn--ghost", href: "https://truescene.site/", target: "_blank", rel: "noreferrer", children: "Start Building" })] })] })] })] }));
|
|
211
|
+
: 'status-pill status-pill--pending', children: ["ID ", ready.idReady ? 'ready' : 'not ready'] })] }), _jsx("span", { className: statusClass, children: statusLabel })] }))] })), _jsxs("main", { className: "app__grid", children: [_jsxs("section", { className: "app__camera", children: [!started ? (_jsxs("div", { className: "app__start", children: [_jsx("button", { className: "btn btn--primary", type: "button", disabled: tokenMissing, onClick: () => setStarted(true), children: "Start Verification" }), tokenMissing && (_jsx("p", { className: "app__token-hint", children: "Provide a session token before starting." }))] })) : captures.idImage ? (_jsxs("div", { className: "panel-card panel-card--metrics", children: [_jsx("h2", { children: "Capture complete" }), _jsx("p", { children: "You can continue in the verification flow." })] })) : (_jsx(FaceAndIdCapture, { onReadyChange: handleReadyChange, onMetricsChange: handleMetricsChange, onStepChange: handleStepChange, onCapture: handleCapture })), started ? null : _jsx("br", {})] }), _jsx("aside", { className: "app__panel", children: !captures.idImage && (_jsxs("div", { className: "panel-card", children: [_jsx("h2", { children: "Quick checklist" }), _jsxs("ul", { children: [_jsx("li", { children: "Fill the oval, but avoid getting too close." }), _jsx("li", { children: "Place the ID under your nose and keep eyes visible." }), _jsx("li", { children: "Face a soft, even light source and avoid glare." }), _jsx("li", { children: "Hold steady until the status turns ready." })] })] })) })] })] }));
|
|
240
212
|
};
|
|
241
213
|
export default CaptureExperience;
|
package/dist/sdk/element.js
CHANGED
|
@@ -2,9 +2,10 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { createRoot } from 'react-dom/client';
|
|
3
3
|
import CaptureExperience, {} from './CaptureExperience';
|
|
4
4
|
import { captureStyles } from './styles';
|
|
5
|
-
|
|
5
|
+
import { REGION_URLS } from '../internal/regions';
|
|
6
|
+
const parseBoolean = (value, defaultValue = false) => {
|
|
6
7
|
if (value === null)
|
|
7
|
-
return
|
|
8
|
+
return defaultValue;
|
|
8
9
|
if (value === '')
|
|
9
10
|
return true;
|
|
10
11
|
const normalized = value.toLowerCase();
|
|
@@ -16,22 +17,32 @@ const parseBoolean = (value) => {
|
|
|
16
17
|
}
|
|
17
18
|
return true;
|
|
18
19
|
};
|
|
20
|
+
const parseNumber = (value) => {
|
|
21
|
+
if (value === null || value.trim() === '') {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
const parsed = Number(value);
|
|
25
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
26
|
+
};
|
|
27
|
+
const parseRegion = (value) => value && value in REGION_URLS ? value : undefined;
|
|
19
28
|
export class TrueSceneCaptureElement extends HTMLElement {
|
|
20
29
|
static observedAttributes = [
|
|
21
30
|
'session-token',
|
|
22
|
-
'
|
|
23
|
-
'
|
|
24
|
-
'token-prefix',
|
|
25
|
-
'token-field',
|
|
31
|
+
'region',
|
|
32
|
+
'compare-timeout-ms',
|
|
26
33
|
'auto-start',
|
|
27
|
-
'show-
|
|
28
|
-
'
|
|
34
|
+
'show-header',
|
|
35
|
+
'show-instructions',
|
|
36
|
+
'show-status',
|
|
37
|
+
'header-eyebrow',
|
|
38
|
+
'header-title',
|
|
39
|
+
'header-subtitle',
|
|
40
|
+
'instructions',
|
|
29
41
|
];
|
|
30
42
|
root = null;
|
|
31
43
|
mount = null;
|
|
32
44
|
styleEl = null;
|
|
33
45
|
connectedCallback() {
|
|
34
|
-
console.count('truescene-capture connected');
|
|
35
46
|
if (!this.shadowRoot) {
|
|
36
47
|
this.attachShadow({ mode: 'open' });
|
|
37
48
|
}
|
|
@@ -55,7 +66,6 @@ export class TrueSceneCaptureElement extends HTMLElement {
|
|
|
55
66
|
this.renderReact();
|
|
56
67
|
}
|
|
57
68
|
disconnectedCallback() {
|
|
58
|
-
console.count('truescene-capture disconnected');
|
|
59
69
|
this.root?.unmount();
|
|
60
70
|
this.root = null;
|
|
61
71
|
}
|
|
@@ -72,45 +82,25 @@ export class TrueSceneCaptureElement extends HTMLElement {
|
|
|
72
82
|
get sessionToken() {
|
|
73
83
|
return this.getAttribute('session-token') ?? '';
|
|
74
84
|
}
|
|
75
|
-
set
|
|
76
|
-
if (!value) {
|
|
77
|
-
this.removeAttribute('compare-url');
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
this.setAttribute('compare-url', value);
|
|
81
|
-
}
|
|
82
|
-
get compareUrl() {
|
|
83
|
-
return this.getAttribute('compare-url') ?? undefined;
|
|
84
|
-
}
|
|
85
|
-
set tokenHeader(value) {
|
|
85
|
+
set region(value) {
|
|
86
86
|
if (!value) {
|
|
87
|
-
this.removeAttribute('
|
|
87
|
+
this.removeAttribute('region');
|
|
88
88
|
return;
|
|
89
89
|
}
|
|
90
|
-
this.setAttribute('
|
|
90
|
+
this.setAttribute('region', value);
|
|
91
91
|
}
|
|
92
|
-
get
|
|
93
|
-
return this.getAttribute('
|
|
92
|
+
get region() {
|
|
93
|
+
return parseRegion(this.getAttribute('region'));
|
|
94
94
|
}
|
|
95
|
-
set
|
|
96
|
-
if (
|
|
97
|
-
this.removeAttribute('
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
this.setAttribute('token-prefix', value);
|
|
101
|
-
}
|
|
102
|
-
get tokenPrefix() {
|
|
103
|
-
return this.getAttribute('token-prefix') ?? undefined;
|
|
104
|
-
}
|
|
105
|
-
set tokenField(value) {
|
|
106
|
-
if (!value) {
|
|
107
|
-
this.removeAttribute('token-field');
|
|
95
|
+
set compareTimeoutMs(value) {
|
|
96
|
+
if (value === null || value === undefined) {
|
|
97
|
+
this.removeAttribute('compare-timeout-ms');
|
|
108
98
|
return;
|
|
109
99
|
}
|
|
110
|
-
this.setAttribute('
|
|
100
|
+
this.setAttribute('compare-timeout-ms', String(value));
|
|
111
101
|
}
|
|
112
|
-
get
|
|
113
|
-
return this.getAttribute('
|
|
102
|
+
get compareTimeoutMs() {
|
|
103
|
+
return parseNumber(this.getAttribute('compare-timeout-ms'));
|
|
114
104
|
}
|
|
115
105
|
set autoStart(value) {
|
|
116
106
|
if (value) {
|
|
@@ -123,27 +113,63 @@ export class TrueSceneCaptureElement extends HTMLElement {
|
|
|
123
113
|
get autoStart() {
|
|
124
114
|
return parseBoolean(this.getAttribute('auto-start'));
|
|
125
115
|
}
|
|
126
|
-
set
|
|
127
|
-
|
|
128
|
-
|
|
116
|
+
set showHeader(value) {
|
|
117
|
+
this.setAttribute('show-header', value ? '' : 'false');
|
|
118
|
+
}
|
|
119
|
+
get showHeader() {
|
|
120
|
+
return parseBoolean(this.getAttribute('show-header'), true);
|
|
121
|
+
}
|
|
122
|
+
set showInstructions(value) {
|
|
123
|
+
this.setAttribute('show-instructions', value ? '' : 'false');
|
|
124
|
+
}
|
|
125
|
+
get showInstructions() {
|
|
126
|
+
return parseBoolean(this.getAttribute('show-instructions'), true);
|
|
127
|
+
}
|
|
128
|
+
set showStatus(value) {
|
|
129
|
+
this.setAttribute('show-status', value ? '' : 'false');
|
|
130
|
+
}
|
|
131
|
+
get showStatus() {
|
|
132
|
+
return parseBoolean(this.getAttribute('show-status'), true);
|
|
133
|
+
}
|
|
134
|
+
set headerEyebrow(value) {
|
|
135
|
+
if (value === null || value === undefined) {
|
|
136
|
+
this.removeAttribute('header-eyebrow');
|
|
137
|
+
return;
|
|
129
138
|
}
|
|
130
|
-
|
|
131
|
-
|
|
139
|
+
this.setAttribute('header-eyebrow', value);
|
|
140
|
+
}
|
|
141
|
+
get headerEyebrow() {
|
|
142
|
+
return this.getAttribute('header-eyebrow') ?? undefined;
|
|
143
|
+
}
|
|
144
|
+
set headerTitle(value) {
|
|
145
|
+
if (value === null || value === undefined) {
|
|
146
|
+
this.removeAttribute('header-title');
|
|
147
|
+
return;
|
|
132
148
|
}
|
|
149
|
+
this.setAttribute('header-title', value);
|
|
133
150
|
}
|
|
134
|
-
get
|
|
135
|
-
return
|
|
151
|
+
get headerTitle() {
|
|
152
|
+
return this.getAttribute('header-title') ?? undefined;
|
|
136
153
|
}
|
|
137
|
-
set
|
|
138
|
-
if (value) {
|
|
139
|
-
this.
|
|
154
|
+
set headerSubtitle(value) {
|
|
155
|
+
if (value === null || value === undefined) {
|
|
156
|
+
this.removeAttribute('header-subtitle');
|
|
157
|
+
return;
|
|
140
158
|
}
|
|
141
|
-
|
|
142
|
-
|
|
159
|
+
this.setAttribute('header-subtitle', value);
|
|
160
|
+
}
|
|
161
|
+
get headerSubtitle() {
|
|
162
|
+
return this.getAttribute('header-subtitle') ?? undefined;
|
|
163
|
+
}
|
|
164
|
+
set instructions(value) {
|
|
165
|
+
if (value === null || value === undefined) {
|
|
166
|
+
this.removeAttribute('instructions');
|
|
167
|
+
return;
|
|
143
168
|
}
|
|
169
|
+
this.setAttribute('instructions', value);
|
|
144
170
|
}
|
|
145
|
-
get
|
|
146
|
-
return
|
|
171
|
+
get instructions() {
|
|
172
|
+
return this.getAttribute('instructions') ?? undefined;
|
|
147
173
|
}
|
|
148
174
|
renderReact() {
|
|
149
175
|
if (!this.root) {
|
|
@@ -151,13 +177,16 @@ export class TrueSceneCaptureElement extends HTMLElement {
|
|
|
151
177
|
}
|
|
152
178
|
const props = {
|
|
153
179
|
sessionToken: this.sessionToken,
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
tokenPrefix: this.tokenPrefix,
|
|
157
|
-
tokenField: this.tokenField,
|
|
180
|
+
region: this.region,
|
|
181
|
+
compareTimeoutMs: this.compareTimeoutMs,
|
|
158
182
|
autoStart: this.autoStart,
|
|
159
|
-
|
|
160
|
-
|
|
183
|
+
showHeader: this.showHeader,
|
|
184
|
+
showInstructions: this.showInstructions,
|
|
185
|
+
showStatus: this.showStatus,
|
|
186
|
+
headerEyebrow: this.headerEyebrow,
|
|
187
|
+
headerTitle: this.headerTitle,
|
|
188
|
+
headerSubtitle: this.headerSubtitle,
|
|
189
|
+
instructions: this.instructions,
|
|
161
190
|
onReadyChange: (ready) => {
|
|
162
191
|
this.dispatchEvent(new CustomEvent('ready-change', {
|
|
163
192
|
detail: ready,
|
|
@@ -186,15 +215,15 @@ export class TrueSceneCaptureElement extends HTMLElement {
|
|
|
186
215
|
composed: true,
|
|
187
216
|
}));
|
|
188
217
|
},
|
|
189
|
-
|
|
190
|
-
this.dispatchEvent(new CustomEvent('
|
|
218
|
+
onVerificationCode: (result) => {
|
|
219
|
+
this.dispatchEvent(new CustomEvent('verification-code', {
|
|
191
220
|
detail: result,
|
|
192
221
|
bubbles: true,
|
|
193
222
|
composed: true,
|
|
194
223
|
}));
|
|
195
224
|
},
|
|
196
|
-
|
|
197
|
-
this.dispatchEvent(new CustomEvent('
|
|
225
|
+
onVerificationError: (message) => {
|
|
226
|
+
this.dispatchEvent(new CustomEvent('verification-error', {
|
|
198
227
|
detail: message,
|
|
199
228
|
bubbles: true,
|
|
200
229
|
composed: true,
|