@tn3w/openage 1.0.4 → 1.0.6
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 +26 -6
- package/dist/openage.esm.js +276 -28
- package/dist/openage.esm.js.map +1 -1
- package/dist/openage.min.js +1 -1
- package/dist/openage.min.js.map +1 -1
- package/dist/openage.umd.js +276 -28
- package/dist/openage.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/challenge.js +86 -14
- package/src/constants.d.ts +1 -0
- package/src/constants.js +4 -4
- package/src/index.d.ts +1 -0
- package/src/index.js +17 -0
- package/src/ui.d.ts +4 -0
- package/src/ui.js +71 -0
- package/src/widget.d.ts +2 -0
- package/src/widget.js +100 -10
package/package.json
CHANGED
package/src/challenge.js
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
import {
|
|
28
28
|
BURST_FRAMES,
|
|
29
29
|
BURST_INTERVAL_MS,
|
|
30
|
+
ERROR_STEP_SECONDS,
|
|
30
31
|
MAX_RETRIES,
|
|
31
32
|
MOTION_CAPTURE_MS,
|
|
32
33
|
MOTION_SAMPLE_MS,
|
|
@@ -110,6 +111,73 @@ function sleep(ms) {
|
|
|
110
111
|
return new Promise((r) => setTimeout(r, ms));
|
|
111
112
|
}
|
|
112
113
|
|
|
114
|
+
function resolveChallengeErrorMessage(error) {
|
|
115
|
+
const name = typeof error?.name === 'string' ? error.name : '';
|
|
116
|
+
const message = typeof error?.message === 'string' ? error.message : String(error || '');
|
|
117
|
+
const normalized = `${name} ${message}`.toLowerCase();
|
|
118
|
+
|
|
119
|
+
if (
|
|
120
|
+
name === 'NotFoundError' ||
|
|
121
|
+
/request is not allowed by the user agent or the platform in the current context/.test(
|
|
122
|
+
normalized
|
|
123
|
+
) ||
|
|
124
|
+
/requested device not found|device not found|no camera|could not start video source/.test(
|
|
125
|
+
normalized
|
|
126
|
+
)
|
|
127
|
+
) {
|
|
128
|
+
return 'No camera available. Plug in a camera and try again.';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (
|
|
132
|
+
name === 'NotAllowedError' ||
|
|
133
|
+
name === 'PermissionDeniedError' ||
|
|
134
|
+
/permission|camera access|access denied/.test(normalized)
|
|
135
|
+
) {
|
|
136
|
+
return 'Camera access was blocked. Allow camera access and try again.';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (/positioning timeout/.test(normalized)) {
|
|
140
|
+
return 'Face positioning timed out. Reopen the check and try again.';
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return 'Verification failed. Please try again.';
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function isInlineLayout(widget) {
|
|
147
|
+
return widget?.params?.layout === 'inline';
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function showErrorStep(widget, error) {
|
|
151
|
+
const message = resolveChallengeErrorMessage(error);
|
|
152
|
+
|
|
153
|
+
if (isInlineLayout(widget)) {
|
|
154
|
+
widget.showResult?.('retry', message);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (!widget.popup) {
|
|
159
|
+
widget.openPopup?.();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
widget.showError?.(message);
|
|
163
|
+
|
|
164
|
+
for (let seconds = ERROR_STEP_SECONDS; seconds > 0; seconds--) {
|
|
165
|
+
if (!widget.popup) break;
|
|
166
|
+
widget.setErrorCountdown?.(seconds);
|
|
167
|
+
await sleep(1000);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
widget.closePopup?.();
|
|
171
|
+
widget.setState?.('retry');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function handleChallengeError(widget, emitter, error) {
|
|
175
|
+
console.log('Error during challenge:', error);
|
|
176
|
+
await showErrorStep(widget, error);
|
|
177
|
+
emitter.emit('error', error, widget.id);
|
|
178
|
+
widget.params.errorCallback?.(error);
|
|
179
|
+
}
|
|
180
|
+
|
|
113
181
|
export async function runChallenge(widget, emitter) {
|
|
114
182
|
const mode = widget.params.mode || 'serverless';
|
|
115
183
|
|
|
@@ -139,10 +207,14 @@ async function runServerless(widget, emitter) {
|
|
|
139
207
|
await Promise.all([loadVision(), initAgeEstimator()]);
|
|
140
208
|
|
|
141
209
|
modelBuffer = await loadModel();
|
|
142
|
-
widget.showReady();
|
|
143
210
|
|
|
144
|
-
|
|
145
|
-
|
|
211
|
+
if (isInlineLayout(widget)) {
|
|
212
|
+
await startCameraFlow(widget, modelBuffer);
|
|
213
|
+
} else {
|
|
214
|
+
widget.showReady();
|
|
215
|
+
await waitForStart(widget);
|
|
216
|
+
await startCameraFlow(widget, modelBuffer);
|
|
217
|
+
}
|
|
146
218
|
|
|
147
219
|
const transport = createTransport('serverless', params);
|
|
148
220
|
|
|
@@ -190,10 +262,7 @@ async function runServerless(widget, emitter) {
|
|
|
190
262
|
emitResult(widget, emitter, result);
|
|
191
263
|
} catch (error) {
|
|
192
264
|
cleanupLocal();
|
|
193
|
-
|
|
194
|
-
widget.showResult('fail', 'Verification failed');
|
|
195
|
-
emitter.emit('error', error, widget.id);
|
|
196
|
-
params.errorCallback?.(error);
|
|
265
|
+
await handleChallengeError(widget, emitter, error);
|
|
197
266
|
}
|
|
198
267
|
}
|
|
199
268
|
|
|
@@ -234,10 +303,16 @@ async function runServer(widget, emitter) {
|
|
|
234
303
|
captureFrame: () => (captureFrame() ? 'true' : 'null'),
|
|
235
304
|
});
|
|
236
305
|
|
|
237
|
-
|
|
238
|
-
|
|
306
|
+
let video;
|
|
307
|
+
|
|
308
|
+
if (isInlineLayout(widget)) {
|
|
309
|
+
video = widget.showCamera();
|
|
310
|
+
} else {
|
|
311
|
+
widget.showReady();
|
|
312
|
+
await waitForStart(widget);
|
|
313
|
+
video = widget.showCamera();
|
|
314
|
+
}
|
|
239
315
|
|
|
240
|
-
const video = widget.showCamera();
|
|
241
316
|
widget.setVideoStatus('Requesting camera…');
|
|
242
317
|
await startCamera(video);
|
|
243
318
|
exposeMirrorVideo(video);
|
|
@@ -324,11 +399,8 @@ async function runServer(widget, emitter) {
|
|
|
324
399
|
|
|
325
400
|
cleanupVM(transport);
|
|
326
401
|
} catch (error) {
|
|
327
|
-
console.log('Error during challenge:', error);
|
|
328
402
|
cleanupVM();
|
|
329
|
-
widget
|
|
330
|
-
emitter.emit('error', error, widget.id);
|
|
331
|
-
params.errorCallback?.(error);
|
|
403
|
+
await handleChallengeError(widget, emitter, error);
|
|
332
404
|
}
|
|
333
405
|
}
|
|
334
406
|
|
package/src/constants.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export declare const FACEAPI_MODEL_CDN: string;
|
|
|
8
8
|
export declare const DEFAULT_MIN_AGE: number;
|
|
9
9
|
export declare const FAIL_FLOOR: number;
|
|
10
10
|
export declare const MAX_RETRIES: number;
|
|
11
|
+
export declare const ERROR_STEP_SECONDS: number;
|
|
11
12
|
export declare const BURST_FRAMES: number;
|
|
12
13
|
export declare const BURST_INTERVAL_MS: number;
|
|
13
14
|
export declare const POSITION_CHECK_MS: number;
|
package/src/constants.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const VERSION = '1.0.0';
|
|
2
2
|
|
|
3
|
-
export const MEDIAPIPE_CDN = 'https://cdn.jsdelivr.net/npm
|
|
3
|
+
export const MEDIAPIPE_CDN = 'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.17';
|
|
4
4
|
|
|
5
5
|
export const MEDIAPIPE_WASM = `${MEDIAPIPE_CDN}/wasm`;
|
|
6
6
|
|
|
@@ -11,13 +11,13 @@ export const MEDIAPIPE_MODEL =
|
|
|
11
11
|
'face_landmarker/face_landmarker/float16/1/' +
|
|
12
12
|
'face_landmarker.task';
|
|
13
13
|
|
|
14
|
-
export const FACEAPI_CDN =
|
|
15
|
-
'https://cdn.jsdelivr.net/npm/' + 'face-api.js@0.22.2/dist/face-api.min.js';
|
|
14
|
+
export const FACEAPI_CDN = 'https://cdn.jsdelivr.net/npm/face-api.js@0.22.2/dist/face-api.min.js';
|
|
16
15
|
|
|
17
16
|
export const FACEAPI_MODEL_CDN =
|
|
18
|
-
'https://cdn.jsdelivr.net/gh/
|
|
17
|
+
'https://cdn.jsdelivr.net/gh/justadudewhohacks/face-api.js@master/weights';
|
|
19
18
|
|
|
20
19
|
export const MAX_RETRIES = 3;
|
|
20
|
+
export const ERROR_STEP_SECONDS = 5;
|
|
21
21
|
export const BURST_FRAMES = 5;
|
|
22
22
|
export const BURST_INTERVAL_MS = 200;
|
|
23
23
|
export const POSITION_CHECK_MS = 100;
|
package/src/index.d.ts
CHANGED
package/src/index.js
CHANGED
|
@@ -52,8 +52,19 @@ export class EventEmitter {
|
|
|
52
52
|
const emitter = new EventEmitter();
|
|
53
53
|
const widgets = new Map();
|
|
54
54
|
|
|
55
|
+
function normalizeLayout(layout) {
|
|
56
|
+
if (layout === 'inline' || layout === 'embed' || layout === 'embedded') {
|
|
57
|
+
return 'inline';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return 'widget';
|
|
61
|
+
}
|
|
62
|
+
|
|
55
63
|
function normalizeParams(params) {
|
|
56
64
|
const globalConfig = typeof window !== 'undefined' ? window.openage || {} : {};
|
|
65
|
+
const layout = normalizeLayout(
|
|
66
|
+
params.layout ?? params.display ?? globalConfig.layout ?? globalConfig.display
|
|
67
|
+
);
|
|
57
68
|
|
|
58
69
|
return {
|
|
59
70
|
mode: 'serverless',
|
|
@@ -62,6 +73,7 @@ function normalizeParams(params) {
|
|
|
62
73
|
minAge: 18,
|
|
63
74
|
...globalConfig,
|
|
64
75
|
...params,
|
|
76
|
+
layout,
|
|
65
77
|
};
|
|
66
78
|
}
|
|
67
79
|
|
|
@@ -70,6 +82,10 @@ function startWidget(widget) {
|
|
|
70
82
|
emitter.emit('opened', widget.id);
|
|
71
83
|
runChallenge(widget, emitter);
|
|
72
84
|
};
|
|
85
|
+
|
|
86
|
+
if (widget.params.layout === 'inline') {
|
|
87
|
+
widget.startChallenge();
|
|
88
|
+
}
|
|
73
89
|
}
|
|
74
90
|
|
|
75
91
|
function render(container, params = {}) {
|
|
@@ -218,6 +234,7 @@ function autoRender() {
|
|
|
218
234
|
size: element.dataset.size,
|
|
219
235
|
action: element.dataset.action,
|
|
220
236
|
mode: element.dataset.mode,
|
|
237
|
+
layout: element.dataset.layout || element.dataset.display,
|
|
221
238
|
server: element.dataset.server,
|
|
222
239
|
};
|
|
223
240
|
|
package/src/ui.d.ts
CHANGED
|
@@ -17,6 +17,10 @@ export declare function heroTemplate(
|
|
|
17
17
|
|
|
18
18
|
export declare function challengeTemplate(): string;
|
|
19
19
|
|
|
20
|
+
export declare function errorStepTemplate(
|
|
21
|
+
message: string
|
|
22
|
+
): string;
|
|
23
|
+
|
|
20
24
|
export declare function resultTemplate(
|
|
21
25
|
outcome: "fail" | "retry",
|
|
22
26
|
message: string
|
package/src/ui.js
CHANGED
|
@@ -541,6 +541,20 @@ export const STYLES = `
|
|
|
541
541
|
flex-direction: column;
|
|
542
542
|
}
|
|
543
543
|
|
|
544
|
+
.oa-inline-shell {
|
|
545
|
+
background: var(--oa-bg);
|
|
546
|
+
border: 1px solid var(--oa-border);
|
|
547
|
+
border-radius: var(--oa-radius);
|
|
548
|
+
box-shadow: 0 20px 60px rgba(0,0,0,0.18),
|
|
549
|
+
0 0 0 1px rgba(0,0,0,0.06);
|
|
550
|
+
width: min(100%, 360px);
|
|
551
|
+
max-width: 100%;
|
|
552
|
+
overflow: hidden;
|
|
553
|
+
display: flex;
|
|
554
|
+
flex-direction: column;
|
|
555
|
+
margin: 0 auto;
|
|
556
|
+
}
|
|
557
|
+
|
|
544
558
|
.oa-modal-overlay {
|
|
545
559
|
position: fixed;
|
|
546
560
|
inset: 0;
|
|
@@ -893,6 +907,52 @@ export const STYLES = `
|
|
|
893
907
|
.oa-result-fail { color: var(--oa-danger); }
|
|
894
908
|
.oa-result-retry { color: var(--oa-warn); }
|
|
895
909
|
|
|
910
|
+
.oa-error-step {
|
|
911
|
+
display: flex;
|
|
912
|
+
flex-direction: column;
|
|
913
|
+
align-items: center;
|
|
914
|
+
gap: 0.7rem;
|
|
915
|
+
padding: 2rem 1.2rem;
|
|
916
|
+
background: var(--oa-surface);
|
|
917
|
+
border: 1px solid var(--oa-border);
|
|
918
|
+
border-radius: var(--oa-radius);
|
|
919
|
+
margin: 10px;
|
|
920
|
+
animation: oa-fade-in 0.4s ease;
|
|
921
|
+
text-align: center;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
.oa-error-step-icon {
|
|
925
|
+
width: 3rem;
|
|
926
|
+
height: 3rem;
|
|
927
|
+
display: flex;
|
|
928
|
+
align-items: center;
|
|
929
|
+
justify-content: center;
|
|
930
|
+
border-radius: 999px;
|
|
931
|
+
background: rgba(239, 68, 68, 0.12);
|
|
932
|
+
color: var(--oa-danger);
|
|
933
|
+
font-size: 1.7rem;
|
|
934
|
+
line-height: 1;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
.oa-error-step-title {
|
|
938
|
+
font-size: 1rem;
|
|
939
|
+
font-weight: 700;
|
|
940
|
+
color: var(--oa-text);
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
.oa-error-step-message {
|
|
944
|
+
font-size: 0.84rem;
|
|
945
|
+
line-height: 1.5;
|
|
946
|
+
color: var(--oa-text-muted);
|
|
947
|
+
max-width: 260px;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
.oa-error-step-countdown {
|
|
951
|
+
font-size: 0.75rem;
|
|
952
|
+
font-weight: 600;
|
|
953
|
+
color: var(--oa-danger);
|
|
954
|
+
}
|
|
955
|
+
|
|
896
956
|
.oa-hidden { display: none !important; }
|
|
897
957
|
|
|
898
958
|
/* ── Animations ──────────────────────────────── */
|
|
@@ -1019,6 +1079,17 @@ export function challengeTemplate() {
|
|
|
1019
1079
|
`;
|
|
1020
1080
|
}
|
|
1021
1081
|
|
|
1082
|
+
export function errorStepTemplate(message) {
|
|
1083
|
+
return `
|
|
1084
|
+
<div class="oa-error-step">
|
|
1085
|
+
<div class="oa-error-step-icon">✕</div>
|
|
1086
|
+
<div class="oa-error-step-title">Verification stopped</div>
|
|
1087
|
+
<div class="oa-error-step-message">${message}</div>
|
|
1088
|
+
<div class="oa-error-step-countdown"></div>
|
|
1089
|
+
</div>
|
|
1090
|
+
`;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1022
1093
|
export function resultTemplate(outcome, message) {
|
|
1023
1094
|
const icons = {
|
|
1024
1095
|
fail: '✕',
|
package/src/widget.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export interface WidgetParams {
|
|
|
2
2
|
sitekey?: string;
|
|
3
3
|
server?: string;
|
|
4
4
|
mode?: "serverless" | "sitekey" | "custom";
|
|
5
|
+
layout?: "widget" | "inline" | "embed" | "embedded";
|
|
5
6
|
theme?: "light" | "dark" | "auto";
|
|
6
7
|
size?: "compact" | "normal" | "invisible";
|
|
7
8
|
action?: string;
|
|
@@ -46,6 +47,7 @@ export declare class Widget {
|
|
|
46
47
|
message: string
|
|
47
48
|
): void;
|
|
48
49
|
showError(message: string): void;
|
|
50
|
+
setErrorCountdown(seconds: number): void;
|
|
49
51
|
clearError(): void;
|
|
50
52
|
setState(state: string): void;
|
|
51
53
|
getToken(): string | null;
|
package/src/widget.js
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
checkboxTemplate,
|
|
4
4
|
heroTemplate,
|
|
5
5
|
challengeTemplate,
|
|
6
|
+
errorStepTemplate,
|
|
6
7
|
resultTemplate,
|
|
7
8
|
resolveTheme,
|
|
8
9
|
watchTheme,
|
|
@@ -26,6 +27,7 @@ export class Widget {
|
|
|
26
27
|
this.popup = null;
|
|
27
28
|
this.shadow = null;
|
|
28
29
|
this.elements = {};
|
|
30
|
+
this.popupElements = null;
|
|
29
31
|
this.onChallenge = null;
|
|
30
32
|
this.onStartClick = null;
|
|
31
33
|
this.popupFrame = 0;
|
|
@@ -38,6 +40,7 @@ export class Widget {
|
|
|
38
40
|
const host = document.createElement('div');
|
|
39
41
|
host.id = this.id;
|
|
40
42
|
this.shadow = host.attachShadow({ mode: 'open' });
|
|
43
|
+
this.host = host;
|
|
41
44
|
|
|
42
45
|
const style = document.createElement('style');
|
|
43
46
|
style.textContent = STYLES;
|
|
@@ -50,7 +53,12 @@ export class Widget {
|
|
|
50
53
|
if (this.params.size === 'invisible') {
|
|
51
54
|
host.style.display = 'none';
|
|
52
55
|
this.container.appendChild(host);
|
|
53
|
-
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (this.isInlineLayout()) {
|
|
60
|
+
this.renderInlineShell();
|
|
61
|
+
this.container.appendChild(host);
|
|
54
62
|
return;
|
|
55
63
|
}
|
|
56
64
|
|
|
@@ -86,7 +94,32 @@ export class Widget {
|
|
|
86
94
|
this.elements.errorSlot = this.shadow.querySelector('.oa-error-slot');
|
|
87
95
|
|
|
88
96
|
this.container.appendChild(host);
|
|
89
|
-
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
isInlineLayout() {
|
|
100
|
+
return this.params.layout === 'inline';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
renderInlineShell() {
|
|
104
|
+
const inlineShell = document.createElement('div');
|
|
105
|
+
inlineShell.className = 'oa-inline-shell';
|
|
106
|
+
inlineShell.innerHTML = this.buildPopupContent({ closeable: false });
|
|
107
|
+
this.shadow.appendChild(inlineShell);
|
|
108
|
+
|
|
109
|
+
this.popup = {
|
|
110
|
+
host: this.host,
|
|
111
|
+
root: inlineShell,
|
|
112
|
+
inline: true,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
this.bindPopupEvents(inlineShell);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
resetInlineShell() {
|
|
119
|
+
if (!this.popup?.root) return;
|
|
120
|
+
|
|
121
|
+
this.popup.root.innerHTML = this.buildPopupContent({ closeable: false });
|
|
122
|
+
this.bindPopupEvents(this.popup.root);
|
|
90
123
|
}
|
|
91
124
|
|
|
92
125
|
startChallenge() {
|
|
@@ -119,6 +152,14 @@ export class Widget {
|
|
|
119
152
|
}
|
|
120
153
|
|
|
121
154
|
openPopup() {
|
|
155
|
+
if (this.isInlineLayout()) {
|
|
156
|
+
if (!this.popup) {
|
|
157
|
+
this.renderInlineShell();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return this.getVideo();
|
|
161
|
+
}
|
|
162
|
+
|
|
122
163
|
if (this.popup) return this.getVideo();
|
|
123
164
|
|
|
124
165
|
const anchor = this.getPopupAnchor();
|
|
@@ -296,7 +337,7 @@ export class Widget {
|
|
|
296
337
|
this.popup.root.style.pointerEvents = 'auto';
|
|
297
338
|
}
|
|
298
339
|
|
|
299
|
-
buildPopupContent() {
|
|
340
|
+
buildPopupContent({ closeable = true } = {}) {
|
|
300
341
|
return `
|
|
301
342
|
<div class="oa-header">
|
|
302
343
|
<div class="oa-title">
|
|
@@ -307,10 +348,14 @@ export class Widget {
|
|
|
307
348
|
</a>
|
|
308
349
|
<span class="oa-badge">on-device</span>
|
|
309
350
|
</div>
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
351
|
+
${
|
|
352
|
+
closeable
|
|
353
|
+
? `<button class="oa-close-btn"
|
|
354
|
+
aria-label="Close">
|
|
355
|
+
${CLOSE_SVG}
|
|
356
|
+
</button>`
|
|
357
|
+
: ''
|
|
358
|
+
}
|
|
314
359
|
</div>
|
|
315
360
|
<div class="oa-body">
|
|
316
361
|
${heroTemplate('Initializing…')}
|
|
@@ -323,7 +368,7 @@ export class Widget {
|
|
|
323
368
|
`;
|
|
324
369
|
}
|
|
325
370
|
|
|
326
|
-
bindPopupEvents(root
|
|
371
|
+
bindPopupEvents(root) {
|
|
327
372
|
const closeBtn = root.querySelector('.oa-close-btn');
|
|
328
373
|
if (closeBtn) {
|
|
329
374
|
closeBtn.addEventListener('click', () => {
|
|
@@ -441,6 +486,22 @@ export class Widget {
|
|
|
441
486
|
}
|
|
442
487
|
|
|
443
488
|
showResult(outcome, message) {
|
|
489
|
+
if (this.isInlineLayout()) {
|
|
490
|
+
if (!this.popupElements?.body) return;
|
|
491
|
+
|
|
492
|
+
this.popupElements.body.innerHTML = resultTemplate(outcome, message);
|
|
493
|
+
this.hideActions();
|
|
494
|
+
|
|
495
|
+
if (outcome === 'pass') {
|
|
496
|
+
this.setState('verified');
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
this.setState(outcome === 'fail' ? 'failed' : 'retry');
|
|
501
|
+
this.showActions('Try Again');
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
|
|
444
505
|
if (outcome === 'pass') {
|
|
445
506
|
this.closePopup();
|
|
446
507
|
this.setState('verified');
|
|
@@ -476,8 +537,21 @@ export class Widget {
|
|
|
476
537
|
}
|
|
477
538
|
}
|
|
478
539
|
|
|
479
|
-
showError() {
|
|
480
|
-
this.
|
|
540
|
+
showError(message) {
|
|
541
|
+
if (!this.popupElements?.body) return;
|
|
542
|
+
|
|
543
|
+
this.popupElements.body.innerHTML = errorStepTemplate(message);
|
|
544
|
+
this.popupElements.errorCountdown = this.popupElements.body.querySelector(
|
|
545
|
+
'.oa-error-step-countdown'
|
|
546
|
+
);
|
|
547
|
+
this.hideActions();
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
setErrorCountdown(seconds) {
|
|
551
|
+
if (!this.popupElements?.errorCountdown) return;
|
|
552
|
+
|
|
553
|
+
const unit = seconds === 1 ? 'second' : 'seconds';
|
|
554
|
+
this.popupElements.errorCountdown.textContent = `Closing in ${seconds} ${unit}…`;
|
|
481
555
|
}
|
|
482
556
|
|
|
483
557
|
clearError() {
|
|
@@ -488,6 +562,22 @@ export class Widget {
|
|
|
488
562
|
|
|
489
563
|
closePopup() {
|
|
490
564
|
if (!this.popup) return;
|
|
565
|
+
|
|
566
|
+
if (this.popup.inline) {
|
|
567
|
+
this.popup.cleanup?.();
|
|
568
|
+
if (this.popupFrame) {
|
|
569
|
+
cancelAnimationFrame(this.popupFrame);
|
|
570
|
+
this.popupFrame = 0;
|
|
571
|
+
}
|
|
572
|
+
this.resetInlineShell();
|
|
573
|
+
|
|
574
|
+
if (this.state === 'loading') {
|
|
575
|
+
this.setState('idle');
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
|
|
491
581
|
this.popup.cleanup?.();
|
|
492
582
|
this.popup.themeCleanup?.();
|
|
493
583
|
this.popup.host.remove();
|