@authme/identity-verification 2.8.37 → 2.8.41

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.
@@ -193,6 +193,7 @@
193
193
  "sdk.general.verify.error.getCloser": "Move closer",
194
194
  "sdk.general.verify.error.missmatch": "Align your ID within the frame",
195
195
  "sdk.general.verify.error.motion": "Please keep the ID card and the device still",
196
+ "sdk.liveness.detection.motion": "Please keep your face and device still",
196
197
  "sdk.general.verify.error.needCloseMouth": "Close your mouth",
197
198
  "sdk.general.verify.error.needDeformationFrontal": "Align the card with the designated shape",
198
199
  "sdk.general.verify.error.needFaceToCamera": "Look straight ahead",
@@ -209,7 +210,7 @@
209
210
  "sdk.general.verify.error.stagePass": "Starting next direction",
210
211
  "sdk.general.verify.error.switchCamera": "Blurry document? Tap to switch the camera",
211
212
  "sdk.general.verify.error.wrongCardType": "Wrong document",
212
- "sdk.general.verify.success": "Detection successful, identifying...",
213
+ "sdk.general.verify.success": "Identifying...",
213
214
  "sdk.guard.verify.allowCamera": "Please allow camera access for identity verification",
214
215
  "sdk.guard.verify.content.step1": "Please hold the ID within the frame, ensure the edges are not obscured, and verify the document is clear and without glare.\n\n\n\n\n\n\n",
215
216
  "sdk.guard.verify.guide.fraudDescribe": "Please hold your ID, follow the animation prompts to flip it, and make sure each angle turns into a green frame before proceeding",
@@ -233,7 +234,7 @@
233
234
  "sdk.liveness.detection.header": "Liveness Detection",
234
235
  "sdk.liveness.detection.infopageHint.environment": "Detect in well-lit area",
235
236
  "sdk.liveness.detection.infopageHint.face": "Please ensure there are no obstructions on your face",
236
- "sdk.liveness.detection.needMoreFrame": "Detection successful, identifying...",
237
+ "sdk.liveness.detection.needMoreFrame": "Identifying...",
237
238
  "sdk.liveness.detection.step": "Position your face within the frame",
238
239
  "sdk.liveness.detection.subtitle": "Ready to start capturing your face",
239
240
  "sdk.liveness.detection.title": "Face Verification",
@@ -193,6 +193,7 @@
193
193
  "sdk.general.verify.error.getCloser": "もっと近づけてください",
194
194
  "sdk.general.verify.error.missmatch": "書類を枠内に入れて撮影してください",
195
195
  "sdk.general.verify.error.motion": "身分証明書とデバイスを動かさずに静止させてください",
196
+ "sdk.liveness.detection.motion": "顔とデバイスを静止させてください",
196
197
  "sdk.general.verify.error.needCloseMouth": "口を閉じてください",
197
198
  "sdk.general.verify.error.needDeformationFrontal": "ガイド枠にカードを合わせてください。",
198
199
  "sdk.general.verify.error.needFaceToCamera": "カメラに向かって",
@@ -209,7 +210,7 @@
209
210
  "sdk.general.verify.error.stagePass": "次の方向へ進み始めます",
210
211
  "sdk.general.verify.error.switchCamera": "身分証がぼやけていませんか?\nクリックしてレンズを切り替えましょう",
211
212
  "sdk.general.verify.error.wrongCardType": "誤った書類です",
212
- "sdk.general.verify.success": "検出に成功しました。識別中です...",
213
+ "sdk.general.verify.success": "識別中です...",
213
214
  "sdk.guard.verify.allowCamera": "カメラへのアクセスを許可して身分認証を行ってください",
214
215
  "sdk.guard.verify.content.step1": "書類を手に持ち枠内に置いてください。枠を覆わないようにし、証明書がはっきりして反射していないことを確認してください\"。",
215
216
  "sdk.guard.verify.guide.fraudDescribe": "身分証明書を手に持ち、アニメーションの指示に従って回転させてください。すべての角度が緑の枠になると通過できます",
@@ -233,7 +234,7 @@
233
234
  "sdk.liveness.detection.header": "顔認証",
234
235
  "sdk.liveness.detection.infopageHint.environment": "明るい場所で撮影してください",
235
236
  "sdk.liveness.detection.infopageHint.face": "顔には何も覆っていないことを確認してください",
236
- "sdk.liveness.detection.needMoreFrame": "検出に成功しました。識別中です...",
237
+ "sdk.liveness.detection.needMoreFrame": "識別中です...",
237
238
  "sdk.liveness.detection.step": "顔を枠内に入れます",
238
239
  "sdk.liveness.detection.subtitle": "顔写真撮影を行います",
239
240
  "sdk.liveness.detection.title": "顔認識",
@@ -193,6 +193,7 @@
193
193
  "sdk.general.verify.error.getCloser": "靠近一點",
194
194
  "sdk.general.verify.error.missmatch": "請將證件置於框內",
195
195
  "sdk.general.verify.error.motion": "請保持身分證與裝置靜止",
196
+ "sdk.liveness.detection.motion": "請保持人臉和裝置靜止",
196
197
  "sdk.general.verify.error.needCloseMouth": "緊閉嘴巴",
197
198
  "sdk.general.verify.error.needDeformationFrontal": "對齊卡片形狀",
198
199
  "sdk.general.verify.error.needFaceToCamera": "將臉正對相機",
@@ -209,7 +210,7 @@
209
210
  "sdk.general.verify.error.stagePass": "即將開始下個方向",
210
211
  "sdk.general.verify.error.switchCamera": "證件模糊?點擊切換鏡頭",
211
212
  "sdk.general.verify.error.wrongCardType": "錯誤證件",
212
- "sdk.general.verify.success": "偵測成功,辨識中…",
213
+ "sdk.general.verify.success": "辨識中…",
213
214
  "sdk.guard.verify.allowCamera": "請允許相機存取,以進行身分驗證",
214
215
  "sdk.guard.verify.content.step1": "請手持身分證置於方框內,盡量不遮蔽邊框,並確認證件清晰無反光",
215
216
  "sdk.guard.verify.guide.fraudDescribe": "請手持證件,依動畫提示翻轉,每個角度皆須變為綠框才能通過",
@@ -233,7 +234,7 @@
233
234
  "sdk.liveness.detection.header": "人臉辨識",
234
235
  "sdk.liveness.detection.infopageHint.environment": "在環境光線充足的地方進行",
235
236
  "sdk.liveness.detection.infopageHint.face": "請確保臉上沒有任何遮蔽物",
236
- "sdk.liveness.detection.needMoreFrame": "偵測成功,辨識中…",
237
+ "sdk.liveness.detection.needMoreFrame": "辨識中…",
237
238
  "sdk.liveness.detection.step": "請依指示拍攝人臉",
238
239
  "sdk.liveness.detection.subtitle": "即將進行人臉拍攝",
239
240
  "sdk.liveness.detection.title": "人臉辨識",
package/index.cjs CHANGED
@@ -29087,18 +29087,22 @@ const fasRecognitionResultMapping = fasRecognitionResult => {
29087
29087
  [liveness.FasRecognitionResult.Pass]: core.StatusDescription.Pass,
29088
29088
  [liveness.FasRecognitionResult.Error]: core.StatusDescription.Error,
29089
29089
  [liveness.FasRecognitionResult.NeedMoreFrame]: core.StatusDescription.NeedMoreFrame,
29090
+ [liveness.FasRecognitionResult.Motion]: core.StatusDescription.Motion,
29090
29091
  [liveness.FasRecognitionResult.NeedFaceToCamera]: core.StatusDescription.NeedFaceToCamera
29091
29092
  };
29092
29093
  return (_a = fasServiceStageMap[fasRecognitionResult]) !== null && _a !== void 0 ? _a : core.StatusDescription.Failed;
29093
29094
  };
29094
29095
 
29096
+ /**
29097
+ * 對齊 Android SDK 的 ROI 參數
29098
+ * @see /Users/leoliu/Git/Android/ekyc-sdk-android/AuthMeLiveness/src/main/java/com/authme/lib/liveness/LivenessUseCase.kt
29099
+ */
29095
29100
  var MASK_STYLE;
29096
29101
  (function (MASK_STYLE) {
29097
29102
  MASK_STYLE[MASK_STYLE["DESKTOP_LIMIT"] = 1024] = "DESKTOP_LIMIT";
29098
- MASK_STYLE[MASK_STYLE["DESKTOP_RATIO"] = 1.2] = "DESKTOP_RATIO";
29099
- MASK_STYLE[MASK_STYLE["DEFAULT_RATIO"] = 0.72] = "DEFAULT_RATIO";
29100
- MASK_STYLE[MASK_STYLE["HEIGHT_RATIO"] = 0.48] = "HEIGHT_RATIO";
29101
- MASK_STYLE[MASK_STYLE["MASK_TOP_PERCENT"] = 25.5] = "MASK_TOP_PERCENT";
29103
+ // Android SDK 參數: widthRatio = 0.7, ratio (height/width) = 1.3
29104
+ MASK_STYLE[MASK_STYLE["WIDTH_RATIO"] = 0.7] = "WIDTH_RATIO";
29105
+ MASK_STYLE[MASK_STYLE["HEIGHT_WIDTH_RATIO"] = 1.3] = "HEIGHT_WIDTH_RATIO";
29102
29106
  MASK_STYLE[MASK_STYLE["MASK_TITLE_MARGIN"] = 24] = "MASK_TITLE_MARGIN";
29103
29107
  MASK_STYLE[MASK_STYLE["TITLE_TEXT_FONT_SIZE"] = 18] = "TITLE_TEXT_FONT_SIZE";
29104
29108
  })(MASK_STYLE || (MASK_STYLE = {}));
@@ -29123,10 +29127,20 @@ const renderLivenessUI = config => {
29123
29127
  const faceMask = document.createElement('div');
29124
29128
  faceMask.classList.add('face-mask');
29125
29129
  config.videoContainer.appendChild(faceMask);
29126
- //TODO: setup mask width/height
29127
- const maskHeight = window.outerHeight * MASK_STYLE.HEIGHT_RATIO;
29128
- const maskRatio = window.innerWidth > MASK_STYLE.DESKTOP_LIMIT ? MASK_STYLE.DESKTOP_RATIO : MASK_STYLE.DEFAULT_RATIO;
29129
- const maskWidth = Math.round(maskHeight * maskRatio);
29130
+ // 對齊 Android SDK: 橢圓寬度 = 螢幕寬度 × 0.7, 高度 = 寬度 × 1.3
29131
+ // 橫式螢幕:以高度為基準反推寬度,確保橢圓不會超出螢幕
29132
+ const isLandscape = window.innerWidth > window.innerHeight;
29133
+ let maskWidth;
29134
+ let maskHeight;
29135
+ if (isLandscape) {
29136
+ // 橫式螢幕:以高度為基準
29137
+ maskHeight = window.innerHeight * MASK_STYLE.WIDTH_RATIO;
29138
+ maskWidth = maskHeight / MASK_STYLE.HEIGHT_WIDTH_RATIO;
29139
+ } else {
29140
+ // 直式螢幕:以寬度為基準
29141
+ maskWidth = window.innerWidth * MASK_STYLE.WIDTH_RATIO;
29142
+ maskHeight = maskWidth * MASK_STYLE.HEIGHT_WIDTH_RATIO;
29143
+ }
29130
29144
  faceMask.style.width = `${maskWidth}px`;
29131
29145
  faceMask.style.height = `${maskHeight}px`;
29132
29146
  faceMask.style.setProperty('--authme-liveness-scanner-pass-color', uiThemeConfig.livenessSuccessScanFrame.color);
@@ -29151,8 +29165,11 @@ const renderLivenessUI = config => {
29151
29165
  if (uiThemeConfig.isStatementEnabled) {
29152
29166
  config.videoContainer.appendChild(statementContainer);
29153
29167
  }
29154
- util.uiThemeText(titleText, uiThemeConfig.titleThree, '8vh');
29155
- util.uiThemeHint(statusText, uiThemeConfig.hint, '15vh');
29168
+ // 根據橢圓大小動態計算文字位置,避免壓到橢圓
29169
+ // titleThree (direction: bottom) - 值越小越靠近底部
29170
+ // hint (direction: top) - 值越大越往下
29171
+ util.uiThemeText(titleText, uiThemeConfig.titleThree, '-2vh');
29172
+ util.uiThemeHint(statusText, uiThemeConfig.hint, '7vh');
29156
29173
  // Set init text
29157
29174
  titleText.textContent = translateService.translate('sdk.liveness.detection.step');
29158
29175
  statusText.textContent = translateService.translate('sdk.general.verify.error.noFace');
@@ -29610,6 +29627,7 @@ function startLiveness(config) {
29610
29627
  })));
29611
29628
  const applyTextByResult = result => {
29612
29629
  // debugLog('fas-response', result);
29630
+ console.log('🔍 FAS Status:', result.eStatus, result);
29613
29631
  sendStatusDescription$2(fasRecognitionResultMapping(result.eStatus));
29614
29632
  switch (result.eStatus) {
29615
29633
  case liveness.FasRecognitionResult.NoFace:
@@ -29667,6 +29685,10 @@ function startLiveness(config) {
29667
29685
  uiComponentLiveness.statusText.textContent = translateService.translate('sdk.general.verify.success');
29668
29686
  setBorderStatus('pass');
29669
29687
  break;
29688
+ case liveness.FasRecognitionResult.Motion:
29689
+ uiComponentLiveness.statusText.textContent = translateService.translate('sdk.liveness.detection.motion');
29690
+ setBorderStatus('pass');
29691
+ break;
29670
29692
  case liveness.FasRecognitionResult.Pass:
29671
29693
  if (result.eStage === liveness.EAuthMeFASServiceStage.Scale) {
29672
29694
  setBorderStatus(null);
@@ -32420,6 +32442,7 @@ function startOCR(config) {
32420
32442
  fraudRetryTimes: 1,
32421
32443
  fraudTimeout: 52,
32422
32444
  fraudMaxFps: 2,
32445
+ ocrMaxFps: 2,
32423
32446
  captureTimeout: config.ocrConfig.captureTimeout
32424
32447
  };
32425
32448
  let cardSizeInfo = {
@@ -33074,7 +33097,7 @@ function startOCR(config) {
33074
33097
  })), rxjs.tap(() => received = true))))));
33075
33098
  };
33076
33099
  const autoCapture = canvasSizeInfo => {
33077
- return rxjs.of(canvasSizeInfo).pipe(handleOcrSendFrame(canvasSizeInfo, uiComponentOCR.image, uiComponentBasic.video, config.recognition, 30, false, config.ocrConfig.resultImageFormat, cardType, type), rxjs.tap(x => applyTextByResult(x.result)), rxjs.filter(({
33100
+ return rxjs.of(canvasSizeInfo).pipe(handleOcrSendFrame(canvasSizeInfo, uiComponentOCR.image, uiComponentBasic.video, config.recognition, ocrEngineConfig.ocrMaxFps, false, config.ocrConfig.resultImageFormat, cardType, type), rxjs.tap(x => applyTextByResult(x.result)), rxjs.filter(({
33078
33101
  result
33079
33102
  }) => result.eStatus === idRecognition.EAuthMeCardOCRStatus.Pass || result.eStatus === idRecognition.EAuthMeMRZServiceStatus.Success), rxjs.take(1), rxjs.tap(() => {
33080
33103
  if (countdownCaptureTimer && countdownCaptureTimer.end && !countdownCaptureTimer.end()) {
@@ -34604,40 +34627,39 @@ class LivenessModule {
34604
34627
  frameWidth = canvasSizeInfoCan.canvasWidth;
34605
34628
  frameHeight = canvasSizeInfoCan.canvasHeight;
34606
34629
  const params = yield this.fasService.getParams();
34607
- const oldROI = params.faceROI;
34608
- const actualHeight = frameHeight * (oldROI.fBottom - oldROI.fTop);
34609
- const actualWidth = frameWidth * (oldROI.fRight - oldROI.fLeft);
34610
- let targetWidth = actualWidth;
34611
- // //we change the ROI when horizontal view for desktop camera.
34612
- // if (actualWidth > actualHeight) {
34613
- // targetWidth = actualHeight;
34614
- // const widthPercent = targetWidth / frameWidth;
34615
- // params.faceROI.fLeft = (1 - widthPercent) / 2;
34616
- // params.faceROI.fRight = 1 - (1 - widthPercent) / 2;
34617
- // }
34618
- // 如果是橫向模式 (寬度大於高度)
34619
- if (actualWidth > actualHeight) {
34620
- // 計算目標寬度,使得高度與寬度的比例為 9:16
34621
- // 即 actualHeight : targetWidth = 9 : 16
34622
- // 所以 targetWidth = (16 / 9) * actualHeight
34623
- targetWidth = 3 / 4 * actualHeight;
34624
- // 計算寬度比例
34625
- const widthPercent = targetWidth / frameWidth;
34626
- // 如果計算出的寬度比例小於1,表示需要在兩側留白
34627
- if (widthPercent < 1) {
34628
- // 調整左右邊界,使ROI在中間
34629
- params.faceROI.fLeft = (1 - widthPercent) / 2;
34630
- params.faceROI.fRight = 1 - (1 - widthPercent) / 2;
34631
- }
34632
- // 如果計算出的寬度比例大於1,表示需要裁剪寬度
34633
- else {
34634
- // 維持完整的寬度,但可能需要調整其他參數以保持9:16的比例
34635
- params.faceROI.fLeft = 0;
34636
- params.faceROI.fRight = 1;
34637
- // 這裡可能需要調整垂直方向的ROI,根據實際需求添加
34638
- }
34630
+ /**
34631
+ * 對齊 Android SDK ROI 參數
34632
+ * @see /Users/leoliu/Git/Android/ekyc-sdk-android/AuthMeLiveness/src/main/java/com/authme/lib/liveness/LivenessUseCase.kt
34633
+ *
34634
+ * Android SDK 參數:
34635
+ * - widthRatio = 0.7 (ROI 寬度 = 螢幕寬度 × 0.7)
34636
+ * - ratio = 1.3 (ROI 高度 = ROI 寬度 × 1.3)
34637
+ *
34638
+ * 橫式螢幕處理:以高度為基準反推寬度,確保橢圓不會超出螢幕
34639
+ */
34640
+ const WIDTH_RATIO = 0.7;
34641
+ const HEIGHT_WIDTH_RATIO = 1.3;
34642
+ const isLandscape = frameWidth > frameHeight;
34643
+ let ellipseWidth;
34644
+ let ellipseHeight;
34645
+ if (isLandscape) {
34646
+ // 橫式螢幕:以高度為基準,反推寬度
34647
+ // 橢圓高度最多佔螢幕高度的 70%,然後反推寬度
34648
+ ellipseHeight = frameHeight * WIDTH_RATIO;
34649
+ ellipseWidth = ellipseHeight / HEIGHT_WIDTH_RATIO;
34650
+ } else {
34651
+ // 直式螢幕:以寬度為基準(對齊 Android SDK)
34652
+ ellipseWidth = frameWidth * WIDTH_RATIO;
34653
+ ellipseHeight = ellipseWidth * HEIGHT_WIDTH_RATIO;
34639
34654
  }
34640
-
34655
+ // 計算正規化 ROI (0-1 範圍)
34656
+ const widthPercent = ellipseWidth / frameWidth;
34657
+ const heightPercent = ellipseHeight / frameHeight;
34658
+ // 置中計算
34659
+ params.faceROI.fLeft = (1 - widthPercent) / 2;
34660
+ params.faceROI.fRight = 1 - (1 - widthPercent) / 2;
34661
+ params.faceROI.fTop = (1 - heightPercent) / 2;
34662
+ params.faceROI.fBottom = 1 - (1 - heightPercent) / 2;
34641
34663
  yield this.fasService.setParams(params);
34642
34664
  yield this.fasService.setFrameSize(frameWidth, frameHeight);
34643
34665
  yield this.fasService.startSession();
@@ -35107,7 +35129,8 @@ class MRZModule {
35107
35129
  yield this.mrzService.init();
35108
35130
  yield util.waitTime(100);
35109
35131
  return Object.assign(Object.assign({}, resp.parameters), {
35110
- expiredIn: resp.expiredIn
35132
+ expiredIn: resp.expiredIn,
35133
+ ocrMaxFps: 2
35111
35134
  });
35112
35135
  }),
35113
35136
  ocrStart: (points, type, setBorderType, setCardBorderColor, setBorderSuccess, scanAnimationContainer, successAnimationContainer, frameImage, frameText, faceMode, cardType, retry = false) => __awaiter(this, void 0, void 0, function* () {
@@ -35744,7 +35767,8 @@ class OCRModule {
35744
35767
  yield util.waitTime(100);
35745
35768
  return Object.assign(Object.assign({}, resp.parameters), {
35746
35769
  expiredIn: resp.expiredIn,
35747
- captureTimeout: config.captureTimeout
35770
+ captureTimeout: config.captureTimeout,
35771
+ ocrMaxFps: 2
35748
35772
  });
35749
35773
  }),
35750
35774
  ocrStart: (points, type, setBorderType, setCardBorderColor, setBorderSuccess, scanAnimationContainer, successAnimationContainer, frameImage, frameText, faceMode, cardType, retry = false) => __awaiter(this, void 0, void 0, function* () {
@@ -37328,8 +37352,8 @@ class AuthmeIdentityVerification extends engine.AuthmeFunctionModule {
37328
37352
  }
37329
37353
 
37330
37354
  var name = "authme/sdk";
37331
- var version$1 = "2.8.37";
37332
- var date = "2025-12-11T09:36:46+0800";
37355
+ var version$1 = "2.8.41";
37356
+ var date = "2026-01-30T10:27:23+0000";
37333
37357
  var packageInfo = {
37334
37358
  name: name,
37335
37359
  version: version$1,
package/index.js CHANGED
@@ -29079,18 +29079,22 @@ const fasRecognitionResultMapping = fasRecognitionResult => {
29079
29079
  [FasRecognitionResult.Pass]: StatusDescription.Pass,
29080
29080
  [FasRecognitionResult.Error]: StatusDescription.Error,
29081
29081
  [FasRecognitionResult.NeedMoreFrame]: StatusDescription.NeedMoreFrame,
29082
+ [FasRecognitionResult.Motion]: StatusDescription.Motion,
29082
29083
  [FasRecognitionResult.NeedFaceToCamera]: StatusDescription.NeedFaceToCamera
29083
29084
  };
29084
29085
  return (_a = fasServiceStageMap[fasRecognitionResult]) !== null && _a !== void 0 ? _a : StatusDescription.Failed;
29085
29086
  };
29086
29087
 
29088
+ /**
29089
+ * 對齊 Android SDK 的 ROI 參數
29090
+ * @see /Users/leoliu/Git/Android/ekyc-sdk-android/AuthMeLiveness/src/main/java/com/authme/lib/liveness/LivenessUseCase.kt
29091
+ */
29087
29092
  var MASK_STYLE;
29088
29093
  (function (MASK_STYLE) {
29089
29094
  MASK_STYLE[MASK_STYLE["DESKTOP_LIMIT"] = 1024] = "DESKTOP_LIMIT";
29090
- MASK_STYLE[MASK_STYLE["DESKTOP_RATIO"] = 1.2] = "DESKTOP_RATIO";
29091
- MASK_STYLE[MASK_STYLE["DEFAULT_RATIO"] = 0.72] = "DEFAULT_RATIO";
29092
- MASK_STYLE[MASK_STYLE["HEIGHT_RATIO"] = 0.48] = "HEIGHT_RATIO";
29093
- MASK_STYLE[MASK_STYLE["MASK_TOP_PERCENT"] = 25.5] = "MASK_TOP_PERCENT";
29095
+ // Android SDK 參數: widthRatio = 0.7, ratio (height/width) = 1.3
29096
+ MASK_STYLE[MASK_STYLE["WIDTH_RATIO"] = 0.7] = "WIDTH_RATIO";
29097
+ MASK_STYLE[MASK_STYLE["HEIGHT_WIDTH_RATIO"] = 1.3] = "HEIGHT_WIDTH_RATIO";
29094
29098
  MASK_STYLE[MASK_STYLE["MASK_TITLE_MARGIN"] = 24] = "MASK_TITLE_MARGIN";
29095
29099
  MASK_STYLE[MASK_STYLE["TITLE_TEXT_FONT_SIZE"] = 18] = "TITLE_TEXT_FONT_SIZE";
29096
29100
  })(MASK_STYLE || (MASK_STYLE = {}));
@@ -29115,10 +29119,20 @@ const renderLivenessUI = config => {
29115
29119
  const faceMask = document.createElement('div');
29116
29120
  faceMask.classList.add('face-mask');
29117
29121
  config.videoContainer.appendChild(faceMask);
29118
- //TODO: setup mask width/height
29119
- const maskHeight = window.outerHeight * MASK_STYLE.HEIGHT_RATIO;
29120
- const maskRatio = window.innerWidth > MASK_STYLE.DESKTOP_LIMIT ? MASK_STYLE.DESKTOP_RATIO : MASK_STYLE.DEFAULT_RATIO;
29121
- const maskWidth = Math.round(maskHeight * maskRatio);
29122
+ // 對齊 Android SDK: 橢圓寬度 = 螢幕寬度 × 0.7, 高度 = 寬度 × 1.3
29123
+ // 橫式螢幕:以高度為基準反推寬度,確保橢圓不會超出螢幕
29124
+ const isLandscape = window.innerWidth > window.innerHeight;
29125
+ let maskWidth;
29126
+ let maskHeight;
29127
+ if (isLandscape) {
29128
+ // 橫式螢幕:以高度為基準
29129
+ maskHeight = window.innerHeight * MASK_STYLE.WIDTH_RATIO;
29130
+ maskWidth = maskHeight / MASK_STYLE.HEIGHT_WIDTH_RATIO;
29131
+ } else {
29132
+ // 直式螢幕:以寬度為基準
29133
+ maskWidth = window.innerWidth * MASK_STYLE.WIDTH_RATIO;
29134
+ maskHeight = maskWidth * MASK_STYLE.HEIGHT_WIDTH_RATIO;
29135
+ }
29122
29136
  faceMask.style.width = `${maskWidth}px`;
29123
29137
  faceMask.style.height = `${maskHeight}px`;
29124
29138
  faceMask.style.setProperty('--authme-liveness-scanner-pass-color', uiThemeConfig.livenessSuccessScanFrame.color);
@@ -29143,8 +29157,11 @@ const renderLivenessUI = config => {
29143
29157
  if (uiThemeConfig.isStatementEnabled) {
29144
29158
  config.videoContainer.appendChild(statementContainer);
29145
29159
  }
29146
- uiThemeText(titleText, uiThemeConfig.titleThree, '8vh');
29147
- uiThemeHint(statusText, uiThemeConfig.hint, '15vh');
29160
+ // 根據橢圓大小動態計算文字位置,避免壓到橢圓
29161
+ // titleThree (direction: bottom) - 值越小越靠近底部
29162
+ // hint (direction: top) - 值越大越往下
29163
+ uiThemeText(titleText, uiThemeConfig.titleThree, '-2vh');
29164
+ uiThemeHint(statusText, uiThemeConfig.hint, '7vh');
29148
29165
  // Set init text
29149
29166
  titleText.textContent = translateService.translate('sdk.liveness.detection.step');
29150
29167
  statusText.textContent = translateService.translate('sdk.general.verify.error.noFace');
@@ -29602,6 +29619,7 @@ function startLiveness(config) {
29602
29619
  })));
29603
29620
  const applyTextByResult = result => {
29604
29621
  // debugLog('fas-response', result);
29622
+ console.log('🔍 FAS Status:', result.eStatus, result);
29605
29623
  sendStatusDescription$2(fasRecognitionResultMapping(result.eStatus));
29606
29624
  switch (result.eStatus) {
29607
29625
  case FasRecognitionResult.NoFace:
@@ -29659,6 +29677,10 @@ function startLiveness(config) {
29659
29677
  uiComponentLiveness.statusText.textContent = translateService.translate('sdk.general.verify.success');
29660
29678
  setBorderStatus('pass');
29661
29679
  break;
29680
+ case FasRecognitionResult.Motion:
29681
+ uiComponentLiveness.statusText.textContent = translateService.translate('sdk.liveness.detection.motion');
29682
+ setBorderStatus('pass');
29683
+ break;
29662
29684
  case FasRecognitionResult.Pass:
29663
29685
  if (result.eStage === EAuthMeFASServiceStage.Scale) {
29664
29686
  setBorderStatus(null);
@@ -32412,6 +32434,7 @@ function startOCR(config) {
32412
32434
  fraudRetryTimes: 1,
32413
32435
  fraudTimeout: 52,
32414
32436
  fraudMaxFps: 2,
32437
+ ocrMaxFps: 2,
32415
32438
  captureTimeout: config.ocrConfig.captureTimeout
32416
32439
  };
32417
32440
  let cardSizeInfo = {
@@ -33066,7 +33089,7 @@ function startOCR(config) {
33066
33089
  })), tap(() => received = true))))));
33067
33090
  };
33068
33091
  const autoCapture = canvasSizeInfo => {
33069
- return of(canvasSizeInfo).pipe(handleOcrSendFrame(canvasSizeInfo, uiComponentOCR.image, uiComponentBasic.video, config.recognition, 30, false, config.ocrConfig.resultImageFormat, cardType, type), tap(x => applyTextByResult(x.result)), filter(({
33092
+ return of(canvasSizeInfo).pipe(handleOcrSendFrame(canvasSizeInfo, uiComponentOCR.image, uiComponentBasic.video, config.recognition, ocrEngineConfig.ocrMaxFps, false, config.ocrConfig.resultImageFormat, cardType, type), tap(x => applyTextByResult(x.result)), filter(({
33070
33093
  result
33071
33094
  }) => result.eStatus === EAuthMeCardOCRStatus.Pass || result.eStatus === EAuthMeMRZServiceStatus.Success), take(1), tap(() => {
33072
33095
  if (countdownCaptureTimer && countdownCaptureTimer.end && !countdownCaptureTimer.end()) {
@@ -34596,40 +34619,39 @@ class LivenessModule {
34596
34619
  frameWidth = canvasSizeInfoCan.canvasWidth;
34597
34620
  frameHeight = canvasSizeInfoCan.canvasHeight;
34598
34621
  const params = yield this.fasService.getParams();
34599
- const oldROI = params.faceROI;
34600
- const actualHeight = frameHeight * (oldROI.fBottom - oldROI.fTop);
34601
- const actualWidth = frameWidth * (oldROI.fRight - oldROI.fLeft);
34602
- let targetWidth = actualWidth;
34603
- // //we change the ROI when horizontal view for desktop camera.
34604
- // if (actualWidth > actualHeight) {
34605
- // targetWidth = actualHeight;
34606
- // const widthPercent = targetWidth / frameWidth;
34607
- // params.faceROI.fLeft = (1 - widthPercent) / 2;
34608
- // params.faceROI.fRight = 1 - (1 - widthPercent) / 2;
34609
- // }
34610
- // 如果是橫向模式 (寬度大於高度)
34611
- if (actualWidth > actualHeight) {
34612
- // 計算目標寬度,使得高度與寬度的比例為 9:16
34613
- // 即 actualHeight : targetWidth = 9 : 16
34614
- // 所以 targetWidth = (16 / 9) * actualHeight
34615
- targetWidth = 3 / 4 * actualHeight;
34616
- // 計算寬度比例
34617
- const widthPercent = targetWidth / frameWidth;
34618
- // 如果計算出的寬度比例小於1,表示需要在兩側留白
34619
- if (widthPercent < 1) {
34620
- // 調整左右邊界,使ROI在中間
34621
- params.faceROI.fLeft = (1 - widthPercent) / 2;
34622
- params.faceROI.fRight = 1 - (1 - widthPercent) / 2;
34623
- }
34624
- // 如果計算出的寬度比例大於1,表示需要裁剪寬度
34625
- else {
34626
- // 維持完整的寬度,但可能需要調整其他參數以保持9:16的比例
34627
- params.faceROI.fLeft = 0;
34628
- params.faceROI.fRight = 1;
34629
- // 這裡可能需要調整垂直方向的ROI,根據實際需求添加
34630
- }
34622
+ /**
34623
+ * 對齊 Android SDK ROI 參數
34624
+ * @see /Users/leoliu/Git/Android/ekyc-sdk-android/AuthMeLiveness/src/main/java/com/authme/lib/liveness/LivenessUseCase.kt
34625
+ *
34626
+ * Android SDK 參數:
34627
+ * - widthRatio = 0.7 (ROI 寬度 = 螢幕寬度 × 0.7)
34628
+ * - ratio = 1.3 (ROI 高度 = ROI 寬度 × 1.3)
34629
+ *
34630
+ * 橫式螢幕處理:以高度為基準反推寬度,確保橢圓不會超出螢幕
34631
+ */
34632
+ const WIDTH_RATIO = 0.7;
34633
+ const HEIGHT_WIDTH_RATIO = 1.3;
34634
+ const isLandscape = frameWidth > frameHeight;
34635
+ let ellipseWidth;
34636
+ let ellipseHeight;
34637
+ if (isLandscape) {
34638
+ // 橫式螢幕:以高度為基準,反推寬度
34639
+ // 橢圓高度最多佔螢幕高度的 70%,然後反推寬度
34640
+ ellipseHeight = frameHeight * WIDTH_RATIO;
34641
+ ellipseWidth = ellipseHeight / HEIGHT_WIDTH_RATIO;
34642
+ } else {
34643
+ // 直式螢幕:以寬度為基準(對齊 Android SDK)
34644
+ ellipseWidth = frameWidth * WIDTH_RATIO;
34645
+ ellipseHeight = ellipseWidth * HEIGHT_WIDTH_RATIO;
34631
34646
  }
34632
-
34647
+ // 計算正規化 ROI (0-1 範圍)
34648
+ const widthPercent = ellipseWidth / frameWidth;
34649
+ const heightPercent = ellipseHeight / frameHeight;
34650
+ // 置中計算
34651
+ params.faceROI.fLeft = (1 - widthPercent) / 2;
34652
+ params.faceROI.fRight = 1 - (1 - widthPercent) / 2;
34653
+ params.faceROI.fTop = (1 - heightPercent) / 2;
34654
+ params.faceROI.fBottom = 1 - (1 - heightPercent) / 2;
34633
34655
  yield this.fasService.setParams(params);
34634
34656
  yield this.fasService.setFrameSize(frameWidth, frameHeight);
34635
34657
  yield this.fasService.startSession();
@@ -35099,7 +35121,8 @@ class MRZModule {
35099
35121
  yield this.mrzService.init();
35100
35122
  yield waitTime(100);
35101
35123
  return Object.assign(Object.assign({}, resp.parameters), {
35102
- expiredIn: resp.expiredIn
35124
+ expiredIn: resp.expiredIn,
35125
+ ocrMaxFps: 2
35103
35126
  });
35104
35127
  }),
35105
35128
  ocrStart: (points, type, setBorderType, setCardBorderColor, setBorderSuccess, scanAnimationContainer, successAnimationContainer, frameImage, frameText, faceMode, cardType, retry = false) => __awaiter(this, void 0, void 0, function* () {
@@ -35736,7 +35759,8 @@ class OCRModule {
35736
35759
  yield waitTime(100);
35737
35760
  return Object.assign(Object.assign({}, resp.parameters), {
35738
35761
  expiredIn: resp.expiredIn,
35739
- captureTimeout: config.captureTimeout
35762
+ captureTimeout: config.captureTimeout,
35763
+ ocrMaxFps: 2
35740
35764
  });
35741
35765
  }),
35742
35766
  ocrStart: (points, type, setBorderType, setCardBorderColor, setBorderSuccess, scanAnimationContainer, successAnimationContainer, frameImage, frameText, faceMode, cardType, retry = false) => __awaiter(this, void 0, void 0, function* () {
@@ -37320,8 +37344,8 @@ class AuthmeIdentityVerification extends AuthmeFunctionModule {
37320
37344
  }
37321
37345
 
37322
37346
  var name = "authme/sdk";
37323
- var version$1 = "2.8.37";
37324
- var date = "2025-12-11T09:36:46+0800";
37347
+ var version$1 = "2.8.41";
37348
+ var date = "2026-01-30T10:27:23+0000";
37325
37349
  var packageInfo = {
37326
37350
  name: name,
37327
37351
  version: version$1,
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@authme/identity-verification",
3
- "version": "2.8.37",
3
+ "version": "2.8.41",
4
4
  "peerDependencies": {
5
5
  "core-js": "^3.6.0",
6
6
  "lottie-web": "^5.9.2",
7
7
  "rxjs": "^7.4.0",
8
- "@authme/core": "2.8.37",
9
- "@authme/engine": "2.8.37",
10
- "@authme/id-recognition": "2.8.37",
11
- "@authme/liveness": "2.8.37",
12
- "@authme/util": "2.8.37"
8
+ "@authme/core": "2.8.41",
9
+ "@authme/engine": "2.8.41",
10
+ "@authme/id-recognition": "2.8.41",
11
+ "@authme/liveness": "2.8.41",
12
+ "@authme/util": "2.8.41"
13
13
  },
14
14
  "module": "./index.js",
15
15
  "main": "./index.cjs",
@@ -81,6 +81,7 @@ export interface OcrEngineConfig {
81
81
  fraudRetryTimes: number;
82
82
  fraudTimeout: number;
83
83
  fraudMaxFps: number;
84
+ ocrMaxFps: number;
84
85
  expiredIn?: number;
85
86
  captureTimeout?: number;
86
87
  }