@tn3w/openage 1.0.3 → 1.0.5

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.
@@ -899,6 +899,52 @@
899
899
  .oa-result-fail { color: var(--oa-danger); }
900
900
  .oa-result-retry { color: var(--oa-warn); }
901
901
 
902
+ .oa-error-step {
903
+ display: flex;
904
+ flex-direction: column;
905
+ align-items: center;
906
+ gap: 0.7rem;
907
+ padding: 2rem 1.2rem;
908
+ background: var(--oa-surface);
909
+ border: 1px solid var(--oa-border);
910
+ border-radius: var(--oa-radius);
911
+ margin: 10px;
912
+ animation: oa-fade-in 0.4s ease;
913
+ text-align: center;
914
+ }
915
+
916
+ .oa-error-step-icon {
917
+ width: 3rem;
918
+ height: 3rem;
919
+ display: flex;
920
+ align-items: center;
921
+ justify-content: center;
922
+ border-radius: 999px;
923
+ background: rgba(239, 68, 68, 0.12);
924
+ color: var(--oa-danger);
925
+ font-size: 1.7rem;
926
+ line-height: 1;
927
+ }
928
+
929
+ .oa-error-step-title {
930
+ font-size: 1rem;
931
+ font-weight: 700;
932
+ color: var(--oa-text);
933
+ }
934
+
935
+ .oa-error-step-message {
936
+ font-size: 0.84rem;
937
+ line-height: 1.5;
938
+ color: var(--oa-text-muted);
939
+ max-width: 260px;
940
+ }
941
+
942
+ .oa-error-step-countdown {
943
+ font-size: 0.75rem;
944
+ font-weight: 600;
945
+ color: var(--oa-danger);
946
+ }
947
+
902
948
  .oa-hidden { display: none !important; }
903
949
 
904
950
  /* ── Animations ──────────────────────────────── */
@@ -1025,6 +1071,17 @@
1025
1071
  `;
1026
1072
  }
1027
1073
 
1074
+ function errorStepTemplate(message) {
1075
+ return `
1076
+ <div class="oa-error-step">
1077
+ <div class="oa-error-step-icon">✕</div>
1078
+ <div class="oa-error-step-title">Verification stopped</div>
1079
+ <div class="oa-error-step-message">${message}</div>
1080
+ <div class="oa-error-step-countdown"></div>
1081
+ </div>
1082
+ `;
1083
+ }
1084
+
1028
1085
  function resultTemplate(outcome, message) {
1029
1086
  const icons = {
1030
1087
  fail: '✕',
@@ -1046,7 +1103,7 @@
1046
1103
 
1047
1104
  const VERSION = '1.0.0';
1048
1105
 
1049
- const MEDIAPIPE_CDN = 'https://cdn.jsdelivr.net/npm/' + '@mediapipe/tasks-vision@0.10.17';
1106
+ const MEDIAPIPE_CDN = 'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.17';
1050
1107
 
1051
1108
  const MEDIAPIPE_WASM = `${MEDIAPIPE_CDN}/wasm`;
1052
1109
 
@@ -1058,12 +1115,13 @@
1058
1115
  'face_landmarker.task';
1059
1116
 
1060
1117
  const FACEAPI_CDN =
1061
- 'https://cdn.jsdelivr.net/npm/' + 'face-api.js@0.22.2/dist/face-api.min.js';
1118
+ 'https://cdn.jsdelivr.net/npm/face-api.js@0.22.2/dist/face-api.min.js';
1062
1119
 
1063
1120
  const FACEAPI_MODEL_CDN =
1064
- 'https://cdn.jsdelivr.net/gh/' + 'justadudewhohacks/face-api.js@master/weights';
1121
+ 'https://cdn.jsdelivr.net/gh/justadudewhohacks/face-api.js@master/weights';
1065
1122
 
1066
1123
  const MAX_RETRIES = 3;
1124
+ const ERROR_STEP_SECONDS = 5;
1067
1125
  const BURST_FRAMES = 5;
1068
1126
  const BURST_INTERVAL_MS = 200;
1069
1127
  const POSITION_CHECK_MS = 100;
@@ -1544,8 +1602,20 @@
1544
1602
  }
1545
1603
  }
1546
1604
 
1547
- showError() {
1548
- this.setState('retry');
1605
+ showError(message) {
1606
+ if (!this.popupElements?.body) return;
1607
+
1608
+ this.popupElements.body.innerHTML = errorStepTemplate(message);
1609
+ this.popupElements.errorCountdown =
1610
+ this.popupElements.body.querySelector('.oa-error-step-countdown');
1611
+ this.hideActions();
1612
+ }
1613
+
1614
+ setErrorCountdown(seconds) {
1615
+ if (!this.popupElements?.errorCountdown) return;
1616
+
1617
+ const unit = seconds === 1 ? 'second' : 'seconds';
1618
+ this.popupElements.errorCountdown.textContent = `Closing in ${seconds} ${unit}…`;
1549
1619
  }
1550
1620
 
1551
1621
  clearError() {
@@ -2723,6 +2793,64 @@
2723
2793
  return new Promise((r) => setTimeout(r, ms));
2724
2794
  }
2725
2795
 
2796
+ function resolveChallengeErrorMessage(error) {
2797
+ const name = typeof error?.name === 'string' ? error.name : '';
2798
+ const message = typeof error?.message === 'string' ? error.message : String(error || '');
2799
+ const normalized = `${name} ${message}`.toLowerCase();
2800
+
2801
+ if (
2802
+ name === 'NotFoundError' ||
2803
+ /request is not allowed by the user agent or the platform in the current context/.test(
2804
+ normalized
2805
+ ) ||
2806
+ /requested device not found|device not found|no camera|could not start video source/.test(
2807
+ normalized
2808
+ )
2809
+ ) {
2810
+ return 'No camera available. Plug in a camera and try again.';
2811
+ }
2812
+
2813
+ if (
2814
+ name === 'NotAllowedError' ||
2815
+ name === 'PermissionDeniedError' ||
2816
+ /permission|camera access|access denied/.test(normalized)
2817
+ ) {
2818
+ return 'Camera access was blocked. Allow camera access and try again.';
2819
+ }
2820
+
2821
+ if (/positioning timeout/.test(normalized)) {
2822
+ return 'Face positioning timed out. Reopen the check and try again.';
2823
+ }
2824
+
2825
+ return 'Verification failed. Please try again.';
2826
+ }
2827
+
2828
+ async function showErrorStep(widget, error) {
2829
+ const message = resolveChallengeErrorMessage(error);
2830
+
2831
+ if (!widget.popup) {
2832
+ widget.openPopup?.();
2833
+ }
2834
+
2835
+ widget.showError?.(message);
2836
+
2837
+ for (let seconds = ERROR_STEP_SECONDS; seconds > 0; seconds--) {
2838
+ if (!widget.popup) break;
2839
+ widget.setErrorCountdown?.(seconds);
2840
+ await sleep(1000);
2841
+ }
2842
+
2843
+ widget.closePopup?.();
2844
+ widget.setState?.('retry');
2845
+ }
2846
+
2847
+ async function handleChallengeError(widget, emitter, error) {
2848
+ console.log('Error during challenge:', error);
2849
+ await showErrorStep(widget, error);
2850
+ emitter.emit('error', error, widget.id);
2851
+ widget.params.errorCallback?.(error);
2852
+ }
2853
+
2726
2854
  async function runChallenge(widget, emitter) {
2727
2855
  const mode = widget.params.mode || 'serverless';
2728
2856
 
@@ -2803,9 +2931,7 @@
2803
2931
  emitResult(widget, emitter, result);
2804
2932
  } catch (error) {
2805
2933
  cleanupLocal();
2806
- widget.showResult('fail', 'Verification failed');
2807
- emitter.emit('error', error, widget.id);
2808
- params.errorCallback?.(error);
2934
+ await handleChallengeError(widget, emitter, error);
2809
2935
  }
2810
2936
  }
2811
2937
 
@@ -2937,9 +3063,7 @@
2937
3063
  cleanupVM(transport);
2938
3064
  } catch (error) {
2939
3065
  cleanupVM();
2940
- widget.showResult('fail', 'Verification failed');
2941
- emitter.emit('error', error, widget.id);
2942
- params.errorCallback?.(error);
3066
+ await handleChallengeError(widget, emitter, error);
2943
3067
  }
2944
3068
  }
2945
3069