@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/README.md
CHANGED
|
@@ -16,11 +16,11 @@ button binding.
|
|
|
16
16
|
|
|
17
17
|
## At a Glance
|
|
18
18
|
|
|
19
|
-
| Browser-side | Server-backed | UI
|
|
20
|
-
| ----------------------- | -------------------------- |
|
|
21
|
-
| On-device face analysis | Optional WASM verification |
|
|
22
|
-
| No raw camera upload | Signed sessions and tokens | Normal, compact, invisible
|
|
23
|
-
| Serverless soft gates | Hosted or custom backend | Auto, light, dark
|
|
19
|
+
| Browser-side | Server-backed | UI |
|
|
20
|
+
| ----------------------- | -------------------------- | -------------------------------- |
|
|
21
|
+
| On-device face analysis | Optional WASM verification | Widget popup, inline embed, bind |
|
|
22
|
+
| No raw camera upload | Signed sessions and tokens | Normal, compact, invisible |
|
|
23
|
+
| Serverless soft gates | Hosted or custom backend | Auto, light, dark |
|
|
24
24
|
|
|
25
25
|
## Install
|
|
26
26
|
|
|
@@ -54,12 +54,27 @@ import OpenAge from '@tn3w/openage';
|
|
|
54
54
|
|
|
55
55
|
OpenAge.render('#gate', {
|
|
56
56
|
mode: 'serverless',
|
|
57
|
+
layout: 'inline',
|
|
57
58
|
minAge: 18,
|
|
58
59
|
callback: (token) => console.log(token),
|
|
59
60
|
errorCallback: (error) => console.error(error),
|
|
60
61
|
});
|
|
61
62
|
```
|
|
62
63
|
|
|
64
|
+
### Inline Embed
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
OpenAge.render('#gate', {
|
|
68
|
+
mode: 'serverless',
|
|
69
|
+
layout: 'inline',
|
|
70
|
+
minAge: 18,
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`layout: 'inline'` removes the checkbox shell and renders the verification
|
|
75
|
+
panel directly in the container. The first verification step starts
|
|
76
|
+
immediately after loading.
|
|
77
|
+
|
|
63
78
|
### Bound Flow
|
|
64
79
|
|
|
65
80
|
```js
|
|
@@ -95,11 +110,16 @@ OpenAge.execute(widgetId);
|
|
|
95
110
|
await OpenAge.challenge(params);
|
|
96
111
|
```
|
|
97
112
|
|
|
113
|
+
Runtime errors keep the popup open long enough to explain what happened.
|
|
114
|
+
If no camera is available, OpenAge tells the user to plug one in and closes
|
|
115
|
+
the popup automatically after 5 seconds.
|
|
116
|
+
|
|
98
117
|
## Main Params
|
|
99
118
|
|
|
100
119
|
| Param | Values |
|
|
101
120
|
| --------- | --------------------------------- |
|
|
102
121
|
| `mode` | `serverless`, `sitekey`, `custom` |
|
|
122
|
+
| `layout` | `widget`, `inline` |
|
|
103
123
|
| `theme` | `light`, `dark`, `auto` |
|
|
104
124
|
| `size` | `normal`, `compact`, `invisible` |
|
|
105
125
|
| `minAge` | number, default `18` |
|
|
@@ -118,7 +138,7 @@ python server.py
|
|
|
118
138
|
```
|
|
119
139
|
|
|
120
140
|
The repository also includes `demo/`, a minimal GitHub Pages build that loads
|
|
121
|
-
the jsDelivr bundle for `@tn3w/openage` in embedded `serverless` mode.
|
|
141
|
+
the jsDelivr bundle for `@tn3w/openage` in inline embedded `serverless` mode.
|
|
122
142
|
|
|
123
143
|
## Development
|
|
124
144
|
|
package/dist/openage.esm.js
CHANGED
|
@@ -541,6 +541,20 @@ 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 @@ 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 @@ function challengeTemplate() {
|
|
|
1019
1079
|
`;
|
|
1020
1080
|
}
|
|
1021
1081
|
|
|
1082
|
+
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
|
function resultTemplate(outcome, message) {
|
|
1023
1094
|
const icons = {
|
|
1024
1095
|
fail: '✕',
|
|
@@ -1040,7 +1111,7 @@ function resultTemplate(outcome, message) {
|
|
|
1040
1111
|
|
|
1041
1112
|
const VERSION = '1.0.0';
|
|
1042
1113
|
|
|
1043
|
-
const MEDIAPIPE_CDN = 'https://cdn.jsdelivr.net/npm
|
|
1114
|
+
const MEDIAPIPE_CDN = 'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.17';
|
|
1044
1115
|
|
|
1045
1116
|
const MEDIAPIPE_WASM = `${MEDIAPIPE_CDN}/wasm`;
|
|
1046
1117
|
|
|
@@ -1051,13 +1122,13 @@ const MEDIAPIPE_MODEL =
|
|
|
1051
1122
|
'face_landmarker/face_landmarker/float16/1/' +
|
|
1052
1123
|
'face_landmarker.task';
|
|
1053
1124
|
|
|
1054
|
-
const FACEAPI_CDN =
|
|
1055
|
-
'https://cdn.jsdelivr.net/npm/' + 'face-api.js@0.22.2/dist/face-api.min.js';
|
|
1125
|
+
const FACEAPI_CDN = 'https://cdn.jsdelivr.net/npm/face-api.js@0.22.2/dist/face-api.min.js';
|
|
1056
1126
|
|
|
1057
1127
|
const FACEAPI_MODEL_CDN =
|
|
1058
|
-
'https://cdn.jsdelivr.net/gh/
|
|
1128
|
+
'https://cdn.jsdelivr.net/gh/justadudewhohacks/face-api.js@master/weights';
|
|
1059
1129
|
|
|
1060
1130
|
const MAX_RETRIES = 3;
|
|
1131
|
+
const ERROR_STEP_SECONDS = 5;
|
|
1061
1132
|
const BURST_FRAMES = 5;
|
|
1062
1133
|
const BURST_INTERVAL_MS = 200;
|
|
1063
1134
|
const POSITION_CHECK_MS = 100;
|
|
@@ -1088,6 +1159,7 @@ class Widget {
|
|
|
1088
1159
|
this.popup = null;
|
|
1089
1160
|
this.shadow = null;
|
|
1090
1161
|
this.elements = {};
|
|
1162
|
+
this.popupElements = null;
|
|
1091
1163
|
this.onChallenge = null;
|
|
1092
1164
|
this.onStartClick = null;
|
|
1093
1165
|
this.popupFrame = 0;
|
|
@@ -1100,6 +1172,7 @@ class Widget {
|
|
|
1100
1172
|
const host = document.createElement('div');
|
|
1101
1173
|
host.id = this.id;
|
|
1102
1174
|
this.shadow = host.attachShadow({ mode: 'open' });
|
|
1175
|
+
this.host = host;
|
|
1103
1176
|
|
|
1104
1177
|
const style = document.createElement('style');
|
|
1105
1178
|
style.textContent = STYLES;
|
|
@@ -1112,7 +1185,12 @@ class Widget {
|
|
|
1112
1185
|
if (this.params.size === 'invisible') {
|
|
1113
1186
|
host.style.display = 'none';
|
|
1114
1187
|
this.container.appendChild(host);
|
|
1115
|
-
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
if (this.isInlineLayout()) {
|
|
1192
|
+
this.renderInlineShell();
|
|
1193
|
+
this.container.appendChild(host);
|
|
1116
1194
|
return;
|
|
1117
1195
|
}
|
|
1118
1196
|
|
|
@@ -1148,7 +1226,32 @@ class Widget {
|
|
|
1148
1226
|
this.elements.errorSlot = this.shadow.querySelector('.oa-error-slot');
|
|
1149
1227
|
|
|
1150
1228
|
this.container.appendChild(host);
|
|
1151
|
-
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
isInlineLayout() {
|
|
1232
|
+
return this.params.layout === 'inline';
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
renderInlineShell() {
|
|
1236
|
+
const inlineShell = document.createElement('div');
|
|
1237
|
+
inlineShell.className = 'oa-inline-shell';
|
|
1238
|
+
inlineShell.innerHTML = this.buildPopupContent({ closeable: false });
|
|
1239
|
+
this.shadow.appendChild(inlineShell);
|
|
1240
|
+
|
|
1241
|
+
this.popup = {
|
|
1242
|
+
host: this.host,
|
|
1243
|
+
root: inlineShell,
|
|
1244
|
+
inline: true,
|
|
1245
|
+
};
|
|
1246
|
+
|
|
1247
|
+
this.bindPopupEvents(inlineShell);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
resetInlineShell() {
|
|
1251
|
+
if (!this.popup?.root) return;
|
|
1252
|
+
|
|
1253
|
+
this.popup.root.innerHTML = this.buildPopupContent({ closeable: false });
|
|
1254
|
+
this.bindPopupEvents(this.popup.root);
|
|
1152
1255
|
}
|
|
1153
1256
|
|
|
1154
1257
|
startChallenge() {
|
|
@@ -1181,6 +1284,14 @@ class Widget {
|
|
|
1181
1284
|
}
|
|
1182
1285
|
|
|
1183
1286
|
openPopup() {
|
|
1287
|
+
if (this.isInlineLayout()) {
|
|
1288
|
+
if (!this.popup) {
|
|
1289
|
+
this.renderInlineShell();
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
return this.getVideo();
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1184
1295
|
if (this.popup) return this.getVideo();
|
|
1185
1296
|
|
|
1186
1297
|
const anchor = this.getPopupAnchor();
|
|
@@ -1358,7 +1469,7 @@ class Widget {
|
|
|
1358
1469
|
this.popup.root.style.pointerEvents = 'auto';
|
|
1359
1470
|
}
|
|
1360
1471
|
|
|
1361
|
-
buildPopupContent() {
|
|
1472
|
+
buildPopupContent({ closeable = true } = {}) {
|
|
1362
1473
|
return `
|
|
1363
1474
|
<div class="oa-header">
|
|
1364
1475
|
<div class="oa-title">
|
|
@@ -1369,10 +1480,14 @@ class Widget {
|
|
|
1369
1480
|
</a>
|
|
1370
1481
|
<span class="oa-badge">on-device</span>
|
|
1371
1482
|
</div>
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1483
|
+
${
|
|
1484
|
+
closeable
|
|
1485
|
+
? `<button class="oa-close-btn"
|
|
1486
|
+
aria-label="Close">
|
|
1487
|
+
${CLOSE_SVG}
|
|
1488
|
+
</button>`
|
|
1489
|
+
: ''
|
|
1490
|
+
}
|
|
1376
1491
|
</div>
|
|
1377
1492
|
<div class="oa-body">
|
|
1378
1493
|
${heroTemplate('Initializing…')}
|
|
@@ -1385,7 +1500,7 @@ class Widget {
|
|
|
1385
1500
|
`;
|
|
1386
1501
|
}
|
|
1387
1502
|
|
|
1388
|
-
bindPopupEvents(root
|
|
1503
|
+
bindPopupEvents(root) {
|
|
1389
1504
|
const closeBtn = root.querySelector('.oa-close-btn');
|
|
1390
1505
|
if (closeBtn) {
|
|
1391
1506
|
closeBtn.addEventListener('click', () => {
|
|
@@ -1503,6 +1618,22 @@ class Widget {
|
|
|
1503
1618
|
}
|
|
1504
1619
|
|
|
1505
1620
|
showResult(outcome, message) {
|
|
1621
|
+
if (this.isInlineLayout()) {
|
|
1622
|
+
if (!this.popupElements?.body) return;
|
|
1623
|
+
|
|
1624
|
+
this.popupElements.body.innerHTML = resultTemplate(outcome, message);
|
|
1625
|
+
this.hideActions();
|
|
1626
|
+
|
|
1627
|
+
if (outcome === 'pass') {
|
|
1628
|
+
this.setState('verified');
|
|
1629
|
+
return;
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
this.setState(outcome === 'fail' ? 'failed' : 'retry');
|
|
1633
|
+
this.showActions('Try Again');
|
|
1634
|
+
return;
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1506
1637
|
if (outcome === 'pass') {
|
|
1507
1638
|
this.closePopup();
|
|
1508
1639
|
this.setState('verified');
|
|
@@ -1538,8 +1669,21 @@ class Widget {
|
|
|
1538
1669
|
}
|
|
1539
1670
|
}
|
|
1540
1671
|
|
|
1541
|
-
showError() {
|
|
1542
|
-
this.
|
|
1672
|
+
showError(message) {
|
|
1673
|
+
if (!this.popupElements?.body) return;
|
|
1674
|
+
|
|
1675
|
+
this.popupElements.body.innerHTML = errorStepTemplate(message);
|
|
1676
|
+
this.popupElements.errorCountdown = this.popupElements.body.querySelector(
|
|
1677
|
+
'.oa-error-step-countdown'
|
|
1678
|
+
);
|
|
1679
|
+
this.hideActions();
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
setErrorCountdown(seconds) {
|
|
1683
|
+
if (!this.popupElements?.errorCountdown) return;
|
|
1684
|
+
|
|
1685
|
+
const unit = seconds === 1 ? 'second' : 'seconds';
|
|
1686
|
+
this.popupElements.errorCountdown.textContent = `Closing in ${seconds} ${unit}…`;
|
|
1543
1687
|
}
|
|
1544
1688
|
|
|
1545
1689
|
clearError() {
|
|
@@ -1550,6 +1694,22 @@ class Widget {
|
|
|
1550
1694
|
|
|
1551
1695
|
closePopup() {
|
|
1552
1696
|
if (!this.popup) return;
|
|
1697
|
+
|
|
1698
|
+
if (this.popup.inline) {
|
|
1699
|
+
this.popup.cleanup?.();
|
|
1700
|
+
if (this.popupFrame) {
|
|
1701
|
+
cancelAnimationFrame(this.popupFrame);
|
|
1702
|
+
this.popupFrame = 0;
|
|
1703
|
+
}
|
|
1704
|
+
this.resetInlineShell();
|
|
1705
|
+
|
|
1706
|
+
if (this.state === 'loading') {
|
|
1707
|
+
this.setState('idle');
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1553
1713
|
this.popup.cleanup?.();
|
|
1554
1714
|
this.popup.themeCleanup?.();
|
|
1555
1715
|
this.popup.host.remove();
|
|
@@ -2717,6 +2877,73 @@ function sleep(ms) {
|
|
|
2717
2877
|
return new Promise((r) => setTimeout(r, ms));
|
|
2718
2878
|
}
|
|
2719
2879
|
|
|
2880
|
+
function resolveChallengeErrorMessage(error) {
|
|
2881
|
+
const name = typeof error?.name === 'string' ? error.name : '';
|
|
2882
|
+
const message = typeof error?.message === 'string' ? error.message : String(error || '');
|
|
2883
|
+
const normalized = `${name} ${message}`.toLowerCase();
|
|
2884
|
+
|
|
2885
|
+
if (
|
|
2886
|
+
name === 'NotFoundError' ||
|
|
2887
|
+
/request is not allowed by the user agent or the platform in the current context/.test(
|
|
2888
|
+
normalized
|
|
2889
|
+
) ||
|
|
2890
|
+
/requested device not found|device not found|no camera|could not start video source/.test(
|
|
2891
|
+
normalized
|
|
2892
|
+
)
|
|
2893
|
+
) {
|
|
2894
|
+
return 'No camera available. Plug in a camera and try again.';
|
|
2895
|
+
}
|
|
2896
|
+
|
|
2897
|
+
if (
|
|
2898
|
+
name === 'NotAllowedError' ||
|
|
2899
|
+
name === 'PermissionDeniedError' ||
|
|
2900
|
+
/permission|camera access|access denied/.test(normalized)
|
|
2901
|
+
) {
|
|
2902
|
+
return 'Camera access was blocked. Allow camera access and try again.';
|
|
2903
|
+
}
|
|
2904
|
+
|
|
2905
|
+
if (/positioning timeout/.test(normalized)) {
|
|
2906
|
+
return 'Face positioning timed out. Reopen the check and try again.';
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2909
|
+
return 'Verification failed. Please try again.';
|
|
2910
|
+
}
|
|
2911
|
+
|
|
2912
|
+
function isInlineLayout(widget) {
|
|
2913
|
+
return widget?.params?.layout === 'inline';
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2916
|
+
async function showErrorStep(widget, error) {
|
|
2917
|
+
const message = resolveChallengeErrorMessage(error);
|
|
2918
|
+
|
|
2919
|
+
if (isInlineLayout(widget)) {
|
|
2920
|
+
widget.showResult?.('retry', message);
|
|
2921
|
+
return;
|
|
2922
|
+
}
|
|
2923
|
+
|
|
2924
|
+
if (!widget.popup) {
|
|
2925
|
+
widget.openPopup?.();
|
|
2926
|
+
}
|
|
2927
|
+
|
|
2928
|
+
widget.showError?.(message);
|
|
2929
|
+
|
|
2930
|
+
for (let seconds = ERROR_STEP_SECONDS; seconds > 0; seconds--) {
|
|
2931
|
+
if (!widget.popup) break;
|
|
2932
|
+
widget.setErrorCountdown?.(seconds);
|
|
2933
|
+
await sleep(1000);
|
|
2934
|
+
}
|
|
2935
|
+
|
|
2936
|
+
widget.closePopup?.();
|
|
2937
|
+
widget.setState?.('retry');
|
|
2938
|
+
}
|
|
2939
|
+
|
|
2940
|
+
async function handleChallengeError(widget, emitter, error) {
|
|
2941
|
+
console.log('Error during challenge:', error);
|
|
2942
|
+
await showErrorStep(widget, error);
|
|
2943
|
+
emitter.emit('error', error, widget.id);
|
|
2944
|
+
widget.params.errorCallback?.(error);
|
|
2945
|
+
}
|
|
2946
|
+
|
|
2720
2947
|
async function runChallenge(widget, emitter) {
|
|
2721
2948
|
const mode = widget.params.mode || 'serverless';
|
|
2722
2949
|
|
|
@@ -2746,10 +2973,14 @@ async function runServerless(widget, emitter) {
|
|
|
2746
2973
|
await Promise.all([loadVision(), initAgeEstimator()]);
|
|
2747
2974
|
|
|
2748
2975
|
modelBuffer = await loadModel();
|
|
2749
|
-
widget.showReady();
|
|
2750
2976
|
|
|
2751
|
-
|
|
2752
|
-
|
|
2977
|
+
if (isInlineLayout(widget)) {
|
|
2978
|
+
await startCameraFlow(widget, modelBuffer);
|
|
2979
|
+
} else {
|
|
2980
|
+
widget.showReady();
|
|
2981
|
+
await waitForStart(widget);
|
|
2982
|
+
await startCameraFlow(widget, modelBuffer);
|
|
2983
|
+
}
|
|
2753
2984
|
|
|
2754
2985
|
const transport = createTransport('serverless', params);
|
|
2755
2986
|
|
|
@@ -2797,10 +3028,7 @@ async function runServerless(widget, emitter) {
|
|
|
2797
3028
|
emitResult(widget, emitter, result);
|
|
2798
3029
|
} catch (error) {
|
|
2799
3030
|
cleanupLocal();
|
|
2800
|
-
|
|
2801
|
-
widget.showResult('fail', 'Verification failed');
|
|
2802
|
-
emitter.emit('error', error, widget.id);
|
|
2803
|
-
params.errorCallback?.(error);
|
|
3031
|
+
await handleChallengeError(widget, emitter, error);
|
|
2804
3032
|
}
|
|
2805
3033
|
}
|
|
2806
3034
|
|
|
@@ -2841,10 +3069,16 @@ async function runServer(widget, emitter) {
|
|
|
2841
3069
|
captureFrame: () => (captureFrame() ? 'true' : 'null'),
|
|
2842
3070
|
});
|
|
2843
3071
|
|
|
2844
|
-
|
|
2845
|
-
|
|
3072
|
+
let video;
|
|
3073
|
+
|
|
3074
|
+
if (isInlineLayout(widget)) {
|
|
3075
|
+
video = widget.showCamera();
|
|
3076
|
+
} else {
|
|
3077
|
+
widget.showReady();
|
|
3078
|
+
await waitForStart(widget);
|
|
3079
|
+
video = widget.showCamera();
|
|
3080
|
+
}
|
|
2846
3081
|
|
|
2847
|
-
const video = widget.showCamera();
|
|
2848
3082
|
widget.setVideoStatus('Requesting camera…');
|
|
2849
3083
|
await startCamera(video);
|
|
2850
3084
|
exposeMirrorVideo(video);
|
|
@@ -2931,11 +3165,8 @@ async function runServer(widget, emitter) {
|
|
|
2931
3165
|
|
|
2932
3166
|
cleanupVM(transport);
|
|
2933
3167
|
} catch (error) {
|
|
2934
|
-
console.log('Error during challenge:', error);
|
|
2935
3168
|
cleanupVM();
|
|
2936
|
-
widget
|
|
2937
|
-
emitter.emit('error', error, widget.id);
|
|
2938
|
-
params.errorCallback?.(error);
|
|
3169
|
+
await handleChallengeError(widget, emitter, error);
|
|
2939
3170
|
}
|
|
2940
3171
|
}
|
|
2941
3172
|
|
|
@@ -3150,8 +3381,19 @@ class EventEmitter {
|
|
|
3150
3381
|
const emitter = new EventEmitter();
|
|
3151
3382
|
const widgets = new Map();
|
|
3152
3383
|
|
|
3384
|
+
function normalizeLayout(layout) {
|
|
3385
|
+
if (layout === 'inline' || layout === 'embed' || layout === 'embedded') {
|
|
3386
|
+
return 'inline';
|
|
3387
|
+
}
|
|
3388
|
+
|
|
3389
|
+
return 'widget';
|
|
3390
|
+
}
|
|
3391
|
+
|
|
3153
3392
|
function normalizeParams(params) {
|
|
3154
3393
|
const globalConfig = typeof window !== 'undefined' ? window.openage || {} : {};
|
|
3394
|
+
const layout = normalizeLayout(
|
|
3395
|
+
params.layout ?? params.display ?? globalConfig.layout ?? globalConfig.display
|
|
3396
|
+
);
|
|
3155
3397
|
|
|
3156
3398
|
return {
|
|
3157
3399
|
mode: 'serverless',
|
|
@@ -3160,6 +3402,7 @@ function normalizeParams(params) {
|
|
|
3160
3402
|
minAge: 18,
|
|
3161
3403
|
...globalConfig,
|
|
3162
3404
|
...params,
|
|
3405
|
+
layout,
|
|
3163
3406
|
};
|
|
3164
3407
|
}
|
|
3165
3408
|
|
|
@@ -3168,6 +3411,10 @@ function startWidget(widget) {
|
|
|
3168
3411
|
emitter.emit('opened', widget.id);
|
|
3169
3412
|
runChallenge(widget, emitter);
|
|
3170
3413
|
};
|
|
3414
|
+
|
|
3415
|
+
if (widget.params.layout === 'inline') {
|
|
3416
|
+
widget.startChallenge();
|
|
3417
|
+
}
|
|
3171
3418
|
}
|
|
3172
3419
|
|
|
3173
3420
|
function render(container, params = {}) {
|
|
@@ -3316,6 +3563,7 @@ function autoRender() {
|
|
|
3316
3563
|
size: element.dataset.size,
|
|
3317
3564
|
action: element.dataset.action,
|
|
3318
3565
|
mode: element.dataset.mode,
|
|
3566
|
+
layout: element.dataset.layout || element.dataset.display,
|
|
3319
3567
|
server: element.dataset.server,
|
|
3320
3568
|
};
|
|
3321
3569
|
|