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.
@@ -0,0 +1,5 @@
1
+ export const REGION_URLS = {
2
+ eu: 'https://eu.api.truescene.ai',
3
+ us: 'https://us.api.truescene.ai',
4
+ sandbox: 'http://13.60.232.144:3031',
5
+ };
@@ -1,58 +1,66 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useMemo, useRef, useState } from 'react';
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 formatNumber = (value, digits) => {
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 [compareResult, setCompareResult] = useState(null);
31
- const [compareError, setCompareError] = useState(null);
32
- const [compareLoading, setCompareLoading] = useState(false);
33
- const lastCompareRef = useRef(null);
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
- const message = 'Session token is required to compare results.';
48
- setCompareError(message);
49
- setCompareResult(null);
50
- setCompareLoading(false);
51
- onCompareError?.(message);
52
- onError?.(message);
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 = lastCompareRef.current;
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
- lastCompareRef.current = {
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
- setCompareLoading(true);
74
- setCompareError(null);
75
- setCompareResult(null);
76
- onCompareResult?.(null);
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
- if (tokenHeader) {
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
- if (tokenField) {
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
- throw new Error(`Server error (${res.status})`);
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
- setCompareError(data.error);
109
- setCompareResult(null);
110
- onCompareError?.(data.error);
111
- onError?.(data.error);
125
+ emitVerificationError({
126
+ code: 'INVALID_RESPONSE',
127
+ message: data.error,
128
+ });
112
129
  return;
113
130
  }
114
- setCompareResult(data);
115
- onCompareResult?.(data);
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
- const message = error instanceof Error ? error.message : 'Compare failed';
122
- setCompareError(message);
123
- onCompareError?.(message);
124
- onError?.(message);
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 (!isActive) {
128
- return;
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
- compareUrl,
139
- tokenField,
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 metricsRows = useMemo(() => {
154
- if (!metrics) {
155
- return [];
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: "app__header", children: [_jsxs("div", { children: [_jsx("div", { className: "app__eyebrow", children: "TrueScene Capture" }), _jsxs("div", { children: [_jsx("h4", { className: "app__title", children: "Fast & Deepfake Resistant" }), _jsx("h4", { className: "app__title", children: "ID Verification" })] }), _jsx("p", { className: "app__subtitle", children: "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." })] }), started && (_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
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: "app__captures", children: [_jsxs("div", { className: "capture-card", children: [_jsx("span", { children: "Face capture" }), _jsx("img", { src: captures.faceImage ?? '', alt: "Face capture" })] }), _jsxs("div", { className: "capture-card", children: [_jsx("span", { children: "ID capture (cropped)" }), _jsx("img", { src: captures.idImage, alt: "ID capture" })] }), _jsxs("div", { className: "capture-card", children: [_jsx("span", { children: "Full frame" }), _jsx("img", { src: captures.fullImage ?? '', alt: "Full frame capture" })] })] })) : (_jsx(FaceAndIdCapture, { onReadyChange: handleReadyChange, onMetricsChange: handleMetricsChange, onStepChange: handleStepChange, onCapture: handleCapture })), started ? null : _jsx("br", {}), started && showDebugToggle && (_jsx("div", { className: "app__actions", children: _jsx("button", { className: "btn btn--ghost", type: "button", onClick: () => setShowDebug((prev) => !prev), children: showDebug ? 'Hide Capture debug' : 'Show Capture Debug' }) }))] }), _jsxs("aside", { className: "app__panel", children: [captures.idImage && (_jsxs("div", { className: "panel-card panel-card--accent", children: [_jsx("h2", { children: "Match result" }), compareLoading && (_jsxs("div", { className: "metrics-row", children: [_jsx("span", { children: "Comparing face to ID..." }), _jsx("span", { className: "spinner", "aria-hidden": "true" })] })), compareError && _jsx("p", { children: compareError }), compareResult && (_jsxs("div", { className: "metrics-grid", children: [_jsxs("div", { className: "metrics-row", children: [_jsx("span", { children: "Match" }), _jsx("span", { className: compareResult.match
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;
@@ -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
- const parseBoolean = (value) => {
5
+ import { REGION_URLS } from '../internal/regions';
6
+ const parseBoolean = (value, defaultValue = false) => {
6
7
  if (value === null)
7
- return false;
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
- 'compare-url',
23
- 'token-header',
24
- 'token-prefix',
25
- 'token-field',
31
+ 'region',
32
+ 'compare-timeout-ms',
26
33
  'auto-start',
27
- 'show-debug',
28
- 'debug-toggle',
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 compareUrl(value) {
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('token-header');
87
+ this.removeAttribute('region');
88
88
  return;
89
89
  }
90
- this.setAttribute('token-header', value);
90
+ this.setAttribute('region', value);
91
91
  }
92
- get tokenHeader() {
93
- return this.getAttribute('token-header') ?? undefined;
92
+ get region() {
93
+ return parseRegion(this.getAttribute('region'));
94
94
  }
95
- set tokenPrefix(value) {
96
- if (!value) {
97
- this.removeAttribute('token-prefix');
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('token-field', value);
100
+ this.setAttribute('compare-timeout-ms', String(value));
111
101
  }
112
- get tokenField() {
113
- return this.getAttribute('token-field') ?? undefined;
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 initialShowDebug(value) {
127
- if (value) {
128
- this.setAttribute('show-debug', '');
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
- else {
131
- this.removeAttribute('show-debug');
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 initialShowDebug() {
135
- return parseBoolean(this.getAttribute('show-debug'));
151
+ get headerTitle() {
152
+ return this.getAttribute('header-title') ?? undefined;
136
153
  }
137
- set showDebugToggle(value) {
138
- if (value) {
139
- this.setAttribute('debug-toggle', '');
154
+ set headerSubtitle(value) {
155
+ if (value === null || value === undefined) {
156
+ this.removeAttribute('header-subtitle');
157
+ return;
140
158
  }
141
- else {
142
- this.removeAttribute('debug-toggle');
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 showDebugToggle() {
146
- return parseBoolean(this.getAttribute('debug-toggle'));
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
- compareUrl: this.compareUrl,
155
- tokenHeader: this.tokenHeader,
156
- tokenPrefix: this.tokenPrefix,
157
- tokenField: this.tokenField,
180
+ region: this.region,
181
+ compareTimeoutMs: this.compareTimeoutMs,
158
182
  autoStart: this.autoStart,
159
- initialShowDebug: this.initialShowDebug,
160
- showDebugToggle: this.showDebugToggle,
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
- onCompareResult: (result) => {
190
- this.dispatchEvent(new CustomEvent('compare-result', {
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
- onCompareError: (message) => {
197
- this.dispatchEvent(new CustomEvent('compare-error', {
225
+ onVerificationError: (message) => {
226
+ this.dispatchEvent(new CustomEvent('verification-error', {
198
227
  detail: message,
199
228
  bubbles: true,
200
229
  composed: true,