@authme/identity-verification 2.8.38 → 2.8.42
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/assets/locale/en_US.json +13 -2
- package/assets/locale/ja_JP.json +13 -2
- package/assets/locale/zh_Hant_TW.json +14 -2
- package/assets/styles/style.css +48 -1
- package/index.cjs +452 -250
- package/index.js +454 -252
- package/package.json +6 -6
- package/src/lib/interface/config.interface.d.ts +1 -0
- package/src/lib/ui/liveness-flow.d.ts +1 -2
- package/src/lib/ui/ocr-flow.d.ts +1 -1
package/index.js
CHANGED
|
@@ -7,8 +7,8 @@ import 'core-js/modules/es.promise.js';
|
|
|
7
7
|
import { getTranslateInstance, EventListenerService, TrackingEvent, generateStatus, StatusDescription, AuthmeError, ErrorCode, Feature, StatusEvent, StatusView, StatusAction, setRequestLoggingFunc, setAccessToken, getCustomerState } from '@authme/core';
|
|
8
8
|
import { EAuthMeFASServiceStatus, EAuthMeIDCardAntiFraudStage as EAuthMeIDCardAntiFraudStage$1, EAuthMeCardClass as EAuthMeCardClass$1, AuthmeFunctionModule, MlEngine, EngineModule, EAuthMeEngineReturnCode, AuthmeEngineModuleBase } from '@authme/engine';
|
|
9
9
|
import { IdRecognitionCardType, CountryCode, EAuthMeCardClass, EAuthMeIDCardAntiFraudStatus, EAuthMeIDCardAntiFraudStage, thicknessDefaultConfig, mapCardtypeToAuthmeClass, getRecognitionColumnOrder, cardTypeTitle, cardTypeConfirmTitle, cardTypeHeader, EAuthMeCardOCRStatus, EAuthMeMRZServiceStatus, saveExtraDoc, initScanDocumentResourceBase64, MRZService, option, themeUI, initScan, initScanDocument, ResourceImageType, uploadFrameBase64, finishScanDocument, CardOCR, IdCardAntiFraudService, twoWayAuthmeCardClassMap, RecognitionFileType, recognizeBase64, getCardSubTypes, getCardTypes, confirmScan } from '@authme/id-recognition';
|
|
10
|
-
import { getCssVariable, RGBToLottieColor, colorToRGB, Storage, useState, uiThemeText, uiThemeHint, clearCanvas, getImageData, isMobile, hidePopup, showPopup, waitTime, TIME_UNIT, AuthmeError as AuthmeError$1, ErrorCode as ErrorCode$1, uiThemeButton,
|
|
11
|
-
import { mergeMap, animationFrames, filter, tap, map, from, catchError, EMPTY, of, merge, fromEvent, concatAll, takeUntil, Subject, defer, throttleTime, switchMap,
|
|
10
|
+
import { getCssVariable, RGBToLottieColor, colorToRGB, Storage, useState, uiThemeText, uiThemeHint, clearCanvas, getImageData, isMobile, hidePopup, showPopup, waitTime, TIME_UNIT, AuthmeError as AuthmeError$1, ErrorCode as ErrorCode$1, uiThemeButton, requestCamera, showElement, asyncOnLineShowErrorMessage, getCanvasSize, startSpinner, stopSpinner, themeConfigDefault, uiThemeDirection, fontWeight, hideElement, dataURItoBlob, showErrorMessage, checkOnlineStatus, uiThemeSmallButton, dropMenu, hideErrorMessage, UintArrayToBlob, isIphone14proOrProMax, cropByRatio, switchCamera, asyncShowPopup, retryPromiseWithCondition, asyncShowErrorMessage, uploadModal, backgroundRequest, debugTools, RUN_FUNCTION_NAME, STORAGE_KEY, splitResult, DEVICE_TYPE, combineResult, startLoadingSDK, stopLoadingSDK } from '@authme/util';
|
|
11
|
+
import { mergeMap, animationFrames, filter, tap, map, from, catchError, EMPTY, of, merge, fromEvent, concatAll, takeUntil, Subject, defer, throttleTime, switchMap, take, concatMap, throwError, finalize, Observable, interval, mapTo, firstValueFrom, shareReplay, switchMapTo, timer, takeWhile as takeWhile$1, race } from 'rxjs';
|
|
12
12
|
import 'core-js/modules/es.regexp.to-string.js';
|
|
13
13
|
import { FasRecognitionResult, EAuthMeFASServiceStage, FasService, LivenessAPI, EAuthMeMouthStatus, LivenessResultStatus } from '@authme/liveness';
|
|
14
14
|
import 'core-js/modules/es.parse-float.js';
|
|
@@ -29079,6 +29079,7 @@ 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;
|
|
@@ -29192,7 +29193,12 @@ const sendFrame = (canvasSizeInfo, canvas, video, frameCallback, fps, bas64Forma
|
|
|
29192
29193
|
const ctx = canvas.getContext('2d', {
|
|
29193
29194
|
willReadFrequently: true
|
|
29194
29195
|
});
|
|
29195
|
-
return source$.pipe(mergeMap(() => animationFrames().pipe(limitFPS(fps), filter(() => received && !(flags === null || flags === void 0 ? void 0 : flags.animating)), tap(() => received = false), tap(() => clearCanvas(canvas)), map(() => getImageData(canvas, ctx, video, canvasSizeInfo, bas64Format, imageType)),
|
|
29196
|
+
return source$.pipe(mergeMap(() => animationFrames().pipe(limitFPS(fps), filter(() => received && !(flags === null || flags === void 0 ? void 0 : flags.animating)), tap(() => received = false), tap(() => clearCanvas(canvas)), map(() => getImageData(canvas, ctx, video, canvasSizeInfo, bas64Format, imageType)), tap(imageData => {
|
|
29197
|
+
// 如果 imageData 為 null(canvas 尺寸無效),重置 received 讓下一幀可以繼續
|
|
29198
|
+
if (imageData === null) {
|
|
29199
|
+
received = true;
|
|
29200
|
+
}
|
|
29201
|
+
}), filter(imageData => imageData !== null), mergeMap(imageData => from(frameCallback(imageData.data, imageData.base64, cardType, type)).pipe(catchError(e => {
|
|
29196
29202
|
// send to fast, ignore
|
|
29197
29203
|
if (e instanceof AuthmeError && e.code === ErrorCode.RECOGNITION_NOT_AVAILABLE) {
|
|
29198
29204
|
return EMPTY;
|
|
@@ -29354,78 +29360,6 @@ const modal = arg => {
|
|
|
29354
29360
|
authmeContainer.appendChild(domModal);
|
|
29355
29361
|
};
|
|
29356
29362
|
|
|
29357
|
-
const popupView = arg => {
|
|
29358
|
-
const authmeContainer = document.querySelector('.authme-container');
|
|
29359
|
-
if (!authmeContainer) {
|
|
29360
|
-
console.error('modal: authmeContainer not found');
|
|
29361
|
-
return;
|
|
29362
|
-
}
|
|
29363
|
-
const uiThemeConfig = Storage.getItem('themeConfig');
|
|
29364
|
-
function removePopview() {
|
|
29365
|
-
var _a;
|
|
29366
|
-
(_a = document.querySelector('.video-container__popupview')) === null || _a === void 0 ? void 0 : _a.remove();
|
|
29367
|
-
}
|
|
29368
|
-
const domPopupView = document.createElement('div');
|
|
29369
|
-
const domPopupViewContainer = document.createElement('div');
|
|
29370
|
-
const domTitleContainer = document.createElement('div');
|
|
29371
|
-
const domTitle = document.createElement('div');
|
|
29372
|
-
const domContentContainer = document.createElement('div');
|
|
29373
|
-
const domContent = document.createElement('div');
|
|
29374
|
-
const domFooterContainer = document.createElement('div');
|
|
29375
|
-
const domConfirm = document.createElement('div');
|
|
29376
|
-
const domCancel = document.createElement('div');
|
|
29377
|
-
domPopupView.classList.add('video-container__popupview');
|
|
29378
|
-
domPopupViewContainer.classList.add('video-container__popupview-container');
|
|
29379
|
-
domTitleContainer.classList.add('video-container__popupview-title-container');
|
|
29380
|
-
domTitle.classList.add('video-container__popupview-title');
|
|
29381
|
-
domContentContainer.classList.add('video-container__popupview-content-container');
|
|
29382
|
-
domContent.classList.add('video-container__popupview-content');
|
|
29383
|
-
domFooterContainer.classList.add('video-container__popupview-footer-container');
|
|
29384
|
-
domConfirm.classList.add('video-container__popupview-confirm');
|
|
29385
|
-
domCancel.classList.add('video-container__popupview-cancel');
|
|
29386
|
-
domPopupViewContainer.style.backgroundColor = uiThemeConfig.popupView.backgroundColor;
|
|
29387
|
-
domPopupViewContainer.style.borderRadius = `${uiThemeConfig.popupView.cornerRadius}px`;
|
|
29388
|
-
domPopupViewContainer.style.width = `${parseFloat(uiThemeConfig.popupView.width) * 100}%`;
|
|
29389
|
-
uiThemeText(domTitle, uiThemeConfig.titleOne);
|
|
29390
|
-
uiThemeText(domContent, uiThemeConfig.bodyTwo);
|
|
29391
|
-
console.log('deviceType', uiThemeConfig.deviceType);
|
|
29392
|
-
if (uiThemeConfig.deviceType === 'mobile') {
|
|
29393
|
-
uiThemeSmallButton(domConfirm, uiThemeConfig.smallermMajorButton);
|
|
29394
|
-
} else {
|
|
29395
|
-
uiThemeButton(domConfirm, uiThemeConfig.majorButton);
|
|
29396
|
-
}
|
|
29397
|
-
if (arg.cancel) {
|
|
29398
|
-
uiThemeSmallButton(domCancel, uiThemeConfig.smallerMinorButton);
|
|
29399
|
-
domCancel.innerHTML = arg.cancel;
|
|
29400
|
-
domCancel.addEventListener('click', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
29401
|
-
if (arg.onCancel) {
|
|
29402
|
-
arg.onCancel();
|
|
29403
|
-
}
|
|
29404
|
-
removePopview();
|
|
29405
|
-
}));
|
|
29406
|
-
}
|
|
29407
|
-
domConfirm.addEventListener('click', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
29408
|
-
if (arg.onConfirm) {
|
|
29409
|
-
arg.onConfirm();
|
|
29410
|
-
}
|
|
29411
|
-
removePopview();
|
|
29412
|
-
}));
|
|
29413
|
-
domTitle.innerHTML = arg.title;
|
|
29414
|
-
domContent.innerHTML = arg.content;
|
|
29415
|
-
domConfirm.innerHTML = arg.confirm;
|
|
29416
|
-
domPopupViewContainer.appendChild(domTitleContainer);
|
|
29417
|
-
domTitleContainer.appendChild(domTitle);
|
|
29418
|
-
domPopupViewContainer.appendChild(domContentContainer);
|
|
29419
|
-
domContentContainer.appendChild(domContent);
|
|
29420
|
-
domPopupViewContainer.appendChild(domFooterContainer);
|
|
29421
|
-
if (arg.cancel) {
|
|
29422
|
-
domFooterContainer.appendChild(domCancel);
|
|
29423
|
-
}
|
|
29424
|
-
domFooterContainer.appendChild(domConfirm);
|
|
29425
|
-
domPopupView.appendChild(domPopupViewContainer);
|
|
29426
|
-
authmeContainer.appendChild(domPopupView);
|
|
29427
|
-
};
|
|
29428
|
-
|
|
29429
29363
|
function startLiveness(config) {
|
|
29430
29364
|
return __awaiter(this, void 0, void 0, function* () {
|
|
29431
29365
|
const translateService = getTranslateInstance();
|
|
@@ -29449,7 +29383,7 @@ function startLiveness(config) {
|
|
|
29449
29383
|
setStatusEvent$2(StatusEvent.Passive);
|
|
29450
29384
|
setStatusView$1(StatusView.Init);
|
|
29451
29385
|
let engineInited = false;
|
|
29452
|
-
let
|
|
29386
|
+
let lastFasStatus = null;
|
|
29453
29387
|
// render view
|
|
29454
29388
|
// const {
|
|
29455
29389
|
// container,
|
|
@@ -29487,44 +29421,6 @@ function startLiveness(config) {
|
|
|
29487
29421
|
};
|
|
29488
29422
|
const canvas = document.createElement('canvas');
|
|
29489
29423
|
let uiThemeConfig = themeConfigDefault;
|
|
29490
|
-
// let sdkFlowTimeout: NodeJS.Timeout;
|
|
29491
|
-
let sdkFlowTimeout = null;
|
|
29492
|
-
const timeout$ = new Subject();
|
|
29493
|
-
// function makeSDKFlowTimeout(expiredIn: number) {
|
|
29494
|
-
// return setTimeout(async () => {
|
|
29495
|
-
// asyncShowErrorMessage(
|
|
29496
|
-
// translateService.translate('sdk.general.error.timeout.content'),
|
|
29497
|
-
// false
|
|
29498
|
-
// );
|
|
29499
|
-
// await waitTime(3 * TIME_UNIT.SECOND);
|
|
29500
|
-
// timeout$.next({
|
|
29501
|
-
// isSuccess: false as const,
|
|
29502
|
-
// code: `${ErrorCode.ID_RECOGNITION_TIMEOUT}`,
|
|
29503
|
-
// message: new AuthmeError(ErrorCode.ID_RECOGNITION_TIMEOUT).message,
|
|
29504
|
-
// });
|
|
29505
|
-
// }, expiredIn * TIME_UNIT.SECOND);
|
|
29506
|
-
// }
|
|
29507
|
-
function makeSDKFlowTimeout(expiredIn) {
|
|
29508
|
-
return timer(expiredIn * TIME_UNIT.SECOND).pipe(switchMap(() => new Observable(observer => {
|
|
29509
|
-
config.onDestroy();
|
|
29510
|
-
popupView({
|
|
29511
|
-
title: translateService.translate('sdk.general.error.timeout.title'),
|
|
29512
|
-
content: translateService.translate('sdk.general.error.timeout.content'),
|
|
29513
|
-
confirm: translateService.translate('sdk.general.confirm'),
|
|
29514
|
-
onConfirm: () => {
|
|
29515
|
-
observer.next(true);
|
|
29516
|
-
observer.complete();
|
|
29517
|
-
}
|
|
29518
|
-
});
|
|
29519
|
-
}))).subscribe(() => {
|
|
29520
|
-
timeout$.next({
|
|
29521
|
-
isSuccess: false,
|
|
29522
|
-
code: `${ErrorCode.ID_RECOGNITION_TIMEOUT}`,
|
|
29523
|
-
message: new AuthmeError(ErrorCode.ID_RECOGNITION_TIMEOUT).message,
|
|
29524
|
-
data: {}
|
|
29525
|
-
});
|
|
29526
|
-
});
|
|
29527
|
-
}
|
|
29528
29424
|
function initOptions() {
|
|
29529
29425
|
return __awaiter(this, void 0, void 0, function* () {
|
|
29530
29426
|
const themeConfigApi = yield config.getOptionConfig();
|
|
@@ -29560,7 +29456,6 @@ function startLiveness(config) {
|
|
|
29560
29456
|
// 需要等到介紹頁完成後,才開始執行 config.init (engine init 流程)。
|
|
29561
29457
|
const init$ = defer(() => config.init()).pipe(tap(res => {
|
|
29562
29458
|
livenessConfig = res.parameters;
|
|
29563
|
-
expiredIn = res.expiredIn;
|
|
29564
29459
|
}), catchError(error => __awaiter(this, void 0, void 0, function* () {
|
|
29565
29460
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
29566
29461
|
const EVENT_NAME_ERROR_CODE = 7;
|
|
@@ -29618,6 +29513,9 @@ function startLiveness(config) {
|
|
|
29618
29513
|
})));
|
|
29619
29514
|
const applyTextByResult = result => {
|
|
29620
29515
|
// debugLog('fas-response', result);
|
|
29516
|
+
if (lastFasStatus !== result.eStatus) {
|
|
29517
|
+
lastFasStatus = result.eStatus;
|
|
29518
|
+
}
|
|
29621
29519
|
sendStatusDescription$2(fasRecognitionResultMapping(result.eStatus));
|
|
29622
29520
|
switch (result.eStatus) {
|
|
29623
29521
|
case FasRecognitionResult.NoFace:
|
|
@@ -29675,6 +29573,10 @@ function startLiveness(config) {
|
|
|
29675
29573
|
uiComponentLiveness.statusText.textContent = translateService.translate('sdk.general.verify.success');
|
|
29676
29574
|
setBorderStatus('pass');
|
|
29677
29575
|
break;
|
|
29576
|
+
case FasRecognitionResult.Motion:
|
|
29577
|
+
uiComponentLiveness.statusText.textContent = translateService.translate('sdk.liveness.detection.motion');
|
|
29578
|
+
setBorderStatus('pass');
|
|
29579
|
+
break;
|
|
29678
29580
|
case FasRecognitionResult.Pass:
|
|
29679
29581
|
if (result.eStage === EAuthMeFASServiceStage.Scale) {
|
|
29680
29582
|
setBorderStatus(null);
|
|
@@ -29766,9 +29668,6 @@ function startLiveness(config) {
|
|
|
29766
29668
|
}
|
|
29767
29669
|
setCorrectViewHeight();
|
|
29768
29670
|
}), switchMap(() => init$), switchMap(() => requestCamera$), tap(() => {
|
|
29769
|
-
if (expiredIn) {
|
|
29770
|
-
sdkFlowTimeout = makeSDKFlowTimeout(expiredIn);
|
|
29771
|
-
}
|
|
29772
29671
|
engineInited = true;
|
|
29773
29672
|
}), switchMap(() => from(waitTime(1)).pipe(tap(() => uiComponentBasic.videoContainer.style.zIndex = '101'))), switchMap(() => from(getCanvasSize(uiComponentBasic.video))), switchMap(canvasSizeInfo => from(config.onStart(canvasSizeInfo)).pipe(tap(params => {
|
|
29774
29673
|
const {
|
|
@@ -29818,26 +29717,16 @@ function startLiveness(config) {
|
|
|
29818
29717
|
// ),
|
|
29819
29718
|
filter(({
|
|
29820
29719
|
result
|
|
29821
|
-
}) => (result.eStatus === FasRecognitionResult.Pass || result.eStatus === FasRecognitionResult.Failed) && result.eStage === EAuthMeFASServiceStage.Done),
|
|
29822
|
-
|
|
29720
|
+
}) => (result.eStatus === FasRecognitionResult.Pass || result.eStatus === FasRecognitionResult.Failed || result.eStatus === FasRecognitionResult.DepthFake) && result.eStage === EAuthMeFASServiceStage.Done), take(1),
|
|
29721
|
+
// 只處理第一個成功結果,防止 engine 繼續運行導致重複上傳
|
|
29722
|
+
map(data => {
|
|
29723
|
+
// DepthFake 和 Failed 都視為失敗
|
|
29724
|
+
const isSuccess = data.result.eStatus === FasRecognitionResult.Pass;
|
|
29823
29725
|
return Object.assign(Object.assign({}, data), {
|
|
29824
|
-
isSuccess
|
|
29726
|
+
isSuccess
|
|
29825
29727
|
});
|
|
29826
|
-
}),
|
|
29827
|
-
|
|
29828
|
-
// switchMap((resp) =>
|
|
29829
|
-
// window.navigator.onLine
|
|
29830
|
-
// ? of(resp)
|
|
29831
|
-
// : from(
|
|
29832
|
-
// asyncOnLineShowErrorMessage(
|
|
29833
|
-
// translateService.translate('sdk.general.error.alert.offline'),
|
|
29834
|
-
// translateService.translate('sdk.general.error.retry'),
|
|
29835
|
-
// false
|
|
29836
|
-
// )
|
|
29837
|
-
// ).pipe(map(() => resp))
|
|
29838
|
-
// ),
|
|
29839
|
-
tap(res => {
|
|
29840
|
-
// console.log('res', res);
|
|
29728
|
+
}))))), tap(res => {
|
|
29729
|
+
// Engine timeout 會在 res.isSuccess 為 false 時發生
|
|
29841
29730
|
if (res.isSuccess) {
|
|
29842
29731
|
sendStatusDescription$2(StatusDescription.UploadingStart);
|
|
29843
29732
|
startSpinner({
|
|
@@ -29846,12 +29735,10 @@ function startLiveness(config) {
|
|
|
29846
29735
|
backgroundOpaque: true
|
|
29847
29736
|
});
|
|
29848
29737
|
} else {
|
|
29849
|
-
|
|
29738
|
+
// Engine 返回 timeout 或其他失敗狀態
|
|
29850
29739
|
throw new AuthmeError(ErrorCode.ID_RECOGNITION_TIMEOUT);
|
|
29851
29740
|
}
|
|
29852
|
-
}), filter(res => res.isSuccess),
|
|
29853
|
-
// **只執行成功的情況**
|
|
29854
|
-
switchMap(() => defer(() => config.onSuccess())));
|
|
29741
|
+
}), filter(res => res.isSuccess), switchMap(() => defer(() => config.onSuccess())));
|
|
29855
29742
|
return of({}).pipe(() => {
|
|
29856
29743
|
eventListenerService$2.start();
|
|
29857
29744
|
setStatusView$1(StatusView.Init);
|
|
@@ -29954,11 +29841,6 @@ function startLiveness(config) {
|
|
|
29954
29841
|
} finally {
|
|
29955
29842
|
uiComponentBasic.video.srcObject = null;
|
|
29956
29843
|
}
|
|
29957
|
-
// if (sdkFlowTimeout) clearTimeout(sdkFlowTimeout);
|
|
29958
|
-
if (sdkFlowTimeout) {
|
|
29959
|
-
sdkFlowTimeout.unsubscribe();
|
|
29960
|
-
sdkFlowTimeout = null;
|
|
29961
|
-
}
|
|
29962
29844
|
eventListenerService$2.stop();
|
|
29963
29845
|
uiComponentBasic.container.remove();
|
|
29964
29846
|
unsubscribe$.next();
|
|
@@ -30483,18 +30365,20 @@ const renderOCRMask = params => {
|
|
|
30483
30365
|
borderOpacity = params.strokeOpacity;
|
|
30484
30366
|
}
|
|
30485
30367
|
if (params.type === 'bordered') {
|
|
30486
|
-
|
|
30487
|
-
|
|
30488
|
-
|
|
30489
|
-
|
|
30490
|
-
|
|
30491
|
-
|
|
30492
|
-
|
|
30493
|
-
|
|
30494
|
-
|
|
30495
|
-
|
|
30496
|
-
|
|
30497
|
-
|
|
30368
|
+
// 使用 path 畫傾斜的四邊形框線(支援防偽的傾斜效果)
|
|
30369
|
+
rectOfFrame.setAttribute('visibility', 'hidden');
|
|
30370
|
+
pathOfFrame.setAttribute('visibility', 'visible');
|
|
30371
|
+
// 計算四個角的座標
|
|
30372
|
+
const points = newCardPoints.map(p => ({
|
|
30373
|
+
x: p.x * windowWidth,
|
|
30374
|
+
y: p.y * windowHeight
|
|
30375
|
+
}));
|
|
30376
|
+
// 使用 path 畫閉合的四邊形
|
|
30377
|
+
const dBordered = `M ${points[0].x},${points[0].y} ` + `L ${points[1].x},${points[1].y} ` + `L ${points[2].x},${points[2].y} ` + `L ${points[3].x},${points[3].y} Z`;
|
|
30378
|
+
pathOfFrame.setAttribute('d', dBordered);
|
|
30379
|
+
pathOfFrame.setAttribute('stroke-opacity', borderOpacity.toString());
|
|
30380
|
+
pathOfFrame.setAttribute('stroke-width', '3');
|
|
30381
|
+
svg.appendChild(pathOfFrame);
|
|
30498
30382
|
} else {
|
|
30499
30383
|
rectOfFrame.setAttribute('visibility', 'hidden');
|
|
30500
30384
|
pathOfFrame.setAttribute('visibility', 'visible');
|
|
@@ -30584,11 +30468,10 @@ const renderOCRMask = params => {
|
|
|
30584
30468
|
path.setAttribute('d', d);
|
|
30585
30469
|
// Set the same path for the border to match the mask exactly
|
|
30586
30470
|
borderPath.setAttribute('d', d);
|
|
30471
|
+
// Anti-fraud 模式下:隱藏 pathOfFrame 和 rectOfFrame,只顯示 borderPath(傾斜的框)
|
|
30587
30472
|
if (antiFraud) {
|
|
30588
|
-
|
|
30589
|
-
|
|
30590
|
-
mirrored: _mirrored
|
|
30591
|
-
});
|
|
30473
|
+
pathOfFrame.setAttribute('visibility', 'hidden');
|
|
30474
|
+
rectOfFrame.setAttribute('visibility', 'hidden');
|
|
30592
30475
|
}
|
|
30593
30476
|
};
|
|
30594
30477
|
function setBorderSuccess(color, opacity) {
|
|
@@ -30611,12 +30494,13 @@ const renderOCRMask = params => {
|
|
|
30611
30494
|
if (borderType === 'bordered') {
|
|
30612
30495
|
rectOfFrame.setAttribute('stroke', color);
|
|
30613
30496
|
rectOfFrame.setAttribute('stroke-opacity', opacity.toString());
|
|
30614
|
-
borderPath.setAttribute('stroke', color);
|
|
30615
|
-
borderPath.setAttribute('stroke-opacity', opacity.toString());
|
|
30616
30497
|
} else {
|
|
30617
30498
|
pathOfFrame.setAttribute('stroke', color);
|
|
30618
30499
|
pathOfFrame.setAttribute('stroke-opacity', opacity.toString());
|
|
30619
30500
|
}
|
|
30501
|
+
// 同時更新 borderPath (用於 anti-fraud 模式的傾斜框線)
|
|
30502
|
+
borderPath.setAttribute('stroke', color);
|
|
30503
|
+
borderPath.setAttribute('stroke-opacity', opacity.toString());
|
|
30620
30504
|
}
|
|
30621
30505
|
function frameImage(faceMode, zIndex, base64, color, opacity) {
|
|
30622
30506
|
color = color !== null && color !== void 0 ? color : OcrFrame.imageColor;
|
|
@@ -31914,6 +31798,78 @@ const fraudScanIntroPage = arg => {
|
|
|
31914
31798
|
authmeContainer.appendChild(domModal);
|
|
31915
31799
|
};
|
|
31916
31800
|
|
|
31801
|
+
const popupView = arg => {
|
|
31802
|
+
const authmeContainer = document.querySelector('.authme-container');
|
|
31803
|
+
if (!authmeContainer) {
|
|
31804
|
+
console.error('modal: authmeContainer not found');
|
|
31805
|
+
return;
|
|
31806
|
+
}
|
|
31807
|
+
const uiThemeConfig = Storage.getItem('themeConfig');
|
|
31808
|
+
function removePopview() {
|
|
31809
|
+
var _a;
|
|
31810
|
+
(_a = document.querySelector('.video-container__popupview')) === null || _a === void 0 ? void 0 : _a.remove();
|
|
31811
|
+
}
|
|
31812
|
+
const domPopupView = document.createElement('div');
|
|
31813
|
+
const domPopupViewContainer = document.createElement('div');
|
|
31814
|
+
const domTitleContainer = document.createElement('div');
|
|
31815
|
+
const domTitle = document.createElement('div');
|
|
31816
|
+
const domContentContainer = document.createElement('div');
|
|
31817
|
+
const domContent = document.createElement('div');
|
|
31818
|
+
const domFooterContainer = document.createElement('div');
|
|
31819
|
+
const domConfirm = document.createElement('div');
|
|
31820
|
+
const domCancel = document.createElement('div');
|
|
31821
|
+
domPopupView.classList.add('video-container__popupview');
|
|
31822
|
+
domPopupViewContainer.classList.add('video-container__popupview-container');
|
|
31823
|
+
domTitleContainer.classList.add('video-container__popupview-title-container');
|
|
31824
|
+
domTitle.classList.add('video-container__popupview-title');
|
|
31825
|
+
domContentContainer.classList.add('video-container__popupview-content-container');
|
|
31826
|
+
domContent.classList.add('video-container__popupview-content');
|
|
31827
|
+
domFooterContainer.classList.add('video-container__popupview-footer-container');
|
|
31828
|
+
domConfirm.classList.add('video-container__popupview-confirm');
|
|
31829
|
+
domCancel.classList.add('video-container__popupview-cancel');
|
|
31830
|
+
domPopupViewContainer.style.backgroundColor = uiThemeConfig.popupView.backgroundColor;
|
|
31831
|
+
domPopupViewContainer.style.borderRadius = `${uiThemeConfig.popupView.cornerRadius}px`;
|
|
31832
|
+
domPopupViewContainer.style.width = `${parseFloat(uiThemeConfig.popupView.width) * 100}%`;
|
|
31833
|
+
uiThemeText(domTitle, uiThemeConfig.titleOne);
|
|
31834
|
+
uiThemeText(domContent, uiThemeConfig.bodyTwo);
|
|
31835
|
+
console.log('deviceType', uiThemeConfig.deviceType);
|
|
31836
|
+
if (uiThemeConfig.deviceType === 'mobile') {
|
|
31837
|
+
uiThemeSmallButton(domConfirm, uiThemeConfig.smallermMajorButton);
|
|
31838
|
+
} else {
|
|
31839
|
+
uiThemeButton(domConfirm, uiThemeConfig.majorButton);
|
|
31840
|
+
}
|
|
31841
|
+
if (arg.cancel) {
|
|
31842
|
+
uiThemeSmallButton(domCancel, uiThemeConfig.smallerMinorButton);
|
|
31843
|
+
domCancel.innerHTML = arg.cancel;
|
|
31844
|
+
domCancel.addEventListener('click', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
31845
|
+
if (arg.onCancel) {
|
|
31846
|
+
arg.onCancel();
|
|
31847
|
+
}
|
|
31848
|
+
removePopview();
|
|
31849
|
+
}));
|
|
31850
|
+
}
|
|
31851
|
+
domConfirm.addEventListener('click', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
31852
|
+
if (arg.onConfirm) {
|
|
31853
|
+
arg.onConfirm();
|
|
31854
|
+
}
|
|
31855
|
+
removePopview();
|
|
31856
|
+
}));
|
|
31857
|
+
domTitle.innerHTML = arg.title;
|
|
31858
|
+
domContent.innerHTML = arg.content;
|
|
31859
|
+
domConfirm.innerHTML = arg.confirm;
|
|
31860
|
+
domPopupViewContainer.appendChild(domTitleContainer);
|
|
31861
|
+
domTitleContainer.appendChild(domTitle);
|
|
31862
|
+
domPopupViewContainer.appendChild(domContentContainer);
|
|
31863
|
+
domContentContainer.appendChild(domContent);
|
|
31864
|
+
domPopupViewContainer.appendChild(domFooterContainer);
|
|
31865
|
+
if (arg.cancel) {
|
|
31866
|
+
domFooterContainer.appendChild(domCancel);
|
|
31867
|
+
}
|
|
31868
|
+
domFooterContainer.appendChild(domConfirm);
|
|
31869
|
+
domPopupView.appendChild(domPopupViewContainer);
|
|
31870
|
+
authmeContainer.appendChild(domPopupView);
|
|
31871
|
+
};
|
|
31872
|
+
|
|
31917
31873
|
const ocrResultModal = arg => {
|
|
31918
31874
|
let modifiedDetails = {};
|
|
31919
31875
|
const authmeContainer = document.querySelector('.authme-container');
|
|
@@ -32134,6 +32090,10 @@ const ocrResultModal = arg => {
|
|
|
32134
32090
|
}
|
|
32135
32091
|
|
|
32136
32092
|
function parseRegex(input, forceUnicode = false) {
|
|
32093
|
+
// 處理 undefined 或 null 輸入
|
|
32094
|
+
if (!input) {
|
|
32095
|
+
return null;
|
|
32096
|
+
}
|
|
32137
32097
|
try {
|
|
32138
32098
|
let pattern = input;
|
|
32139
32099
|
let flags = '';
|
|
@@ -32428,6 +32388,7 @@ function startOCR(config) {
|
|
|
32428
32388
|
fraudRetryTimes: 1,
|
|
32429
32389
|
fraudTimeout: 52,
|
|
32430
32390
|
fraudMaxFps: 2,
|
|
32391
|
+
ocrMaxFps: 2,
|
|
32431
32392
|
captureTimeout: config.ocrConfig.captureTimeout
|
|
32432
32393
|
};
|
|
32433
32394
|
let cardSizeInfo = {
|
|
@@ -32721,9 +32682,9 @@ function startOCR(config) {
|
|
|
32721
32682
|
uiComponentOCRMask.setCardBorderColor('error');
|
|
32722
32683
|
break;
|
|
32723
32684
|
case EAuthMeIDCardAntiFraudStatus.Error:
|
|
32724
|
-
uiComponentOCR.statusText.textContent = translateService.translate('sdk.general.error.
|
|
32725
|
-
showErrorMessage(translateService.translate('sdk.general.error.alert.serverError'), false);
|
|
32685
|
+
uiComponentOCR.statusText.textContent = translateService.translate('sdk.general.verify.error.serverError');
|
|
32726
32686
|
sendStatusDescription$1(StatusDescription.Error);
|
|
32687
|
+
uiComponentOCRMask.setCardBorderColor('error');
|
|
32727
32688
|
break;
|
|
32728
32689
|
case EAuthMeIDCardAntiFraudStatus.NeedDeformationFrontal:
|
|
32729
32690
|
needDeformationCount++;
|
|
@@ -32817,16 +32778,32 @@ function startOCR(config) {
|
|
|
32817
32778
|
return ocrSendFrameAnimation;
|
|
32818
32779
|
}
|
|
32819
32780
|
}), map(x => x.result), concatMap(x => __awaiter(this, void 0, void 0, function* () {
|
|
32781
|
+
// V9: 檢測 stage 變化,如果 stage 變化了,先顯示 pass 顏色
|
|
32782
|
+
// V9 Engine 不再返回 StagePass 狀態,而是直接切換到下一個 stage
|
|
32783
|
+
const stageChanged = x.eStage !== currentAntiFraudStage && currentAntiFraudStage !== undefined && x.eStage !== EAuthMeIDCardAntiFraudStage.Done;
|
|
32784
|
+
if (stageChanged) {
|
|
32785
|
+
// 顯示 pass 顏色表示上一個 stage 完成
|
|
32786
|
+
uiComponentOCRMask.setCardBorderColor('pass');
|
|
32787
|
+
uiComponentOCR.statusText.textContent = translateService.translate('sdk.general.verify.error.stagePass');
|
|
32788
|
+
previosAntiFraudAnimateTime = Date.now();
|
|
32789
|
+
// 等待一段時間讓用戶看到顏色變化
|
|
32790
|
+
yield new Promise(resolve => setTimeout(resolve, 1500));
|
|
32791
|
+
yield stopAnimate();
|
|
32792
|
+
needDeformationCount = 0;
|
|
32793
|
+
previosAntiFraudAnimateTime = 0;
|
|
32794
|
+
// 更新 currentAntiFraudStage 並設定新的 stage 框線
|
|
32795
|
+
currentAntiFraudStage = x.eStage;
|
|
32796
|
+
// 調用 cardRotateByStage 更新框線位置,但傳入 mandatoryRotate = true
|
|
32797
|
+
yield cardRotateByStage(x.eStage, true);
|
|
32798
|
+
// 跳過這一幀的 applyTextByResult,直接返回
|
|
32799
|
+
return x;
|
|
32800
|
+
}
|
|
32820
32801
|
yield applyTextByResult(x);
|
|
32821
32802
|
cardRotateByStage(x.eStage);
|
|
32822
32803
|
if (isTutorialFinish) {
|
|
32823
32804
|
// 文字更新移到 cardRotateAnimationProcess 中,確保與動畫同步
|
|
32824
32805
|
x.eStage;
|
|
32825
32806
|
}
|
|
32826
|
-
if (x.eStage !== currentAntiFraudStage) {
|
|
32827
|
-
needDeformationCount = 0;
|
|
32828
|
-
previosAntiFraudAnimateTime = 0;
|
|
32829
|
-
}
|
|
32830
32807
|
if (!uiThemeConfig.isFraudAnimationLoadingPageEnabled) {
|
|
32831
32808
|
// if (config.ocrConfig.disableTutorial) {
|
|
32832
32809
|
stopSpinner();
|
|
@@ -32911,6 +32888,12 @@ function startOCR(config) {
|
|
|
32911
32888
|
sendStatusDescription$1(StatusDescription.Reflective);
|
|
32912
32889
|
uiComponentOCRMask.setCardBorderColor('error');
|
|
32913
32890
|
break;
|
|
32891
|
+
case EAuthMeCardOCRStatus.Gray:
|
|
32892
|
+
// V9 新增: Gray 狀態表示灰階或顏色異常,顯示與 Blur 類似的提示
|
|
32893
|
+
uiComponentOCR.statusText.textContent = translateService.translate('sdk.general.verify.error.blur');
|
|
32894
|
+
sendStatusDescription$1(StatusDescription.Gray);
|
|
32895
|
+
uiComponentOCRMask.setCardBorderColor('error');
|
|
32896
|
+
break;
|
|
32914
32897
|
case EAuthMeCardOCRStatus.Blur:
|
|
32915
32898
|
uiComponentOCR.statusText.textContent = translateService.translate('sdk.general.verify.error.blur');
|
|
32916
32899
|
sendStatusDescription$1(StatusDescription.Blur);
|
|
@@ -32938,6 +32921,19 @@ function startOCR(config) {
|
|
|
32938
32921
|
sendStatusDescription$1(StatusDescription.Reflective);
|
|
32939
32922
|
uiComponentOCRMask.setCardBorderColor('error');
|
|
32940
32923
|
break;
|
|
32924
|
+
case EAuthMeMRZServiceStatus.Blur:
|
|
32925
|
+
// V9 新增: MRZ Blur 狀態
|
|
32926
|
+
uiComponentOCR.statusText.textContent = translateService.translate('sdk.general.verify.error.blur');
|
|
32927
|
+
sendStatusDescription$1(StatusDescription.Blur);
|
|
32928
|
+
uiComponentOCRMask.setCardBorderColor('error');
|
|
32929
|
+
blurCount++;
|
|
32930
|
+
break;
|
|
32931
|
+
case EAuthMeMRZServiceStatus.MRZNotFound:
|
|
32932
|
+
// V9 新增: MRZ區域未找到(卡片存在但無法識別MRZ區)
|
|
32933
|
+
uiComponentOCR.statusText.textContent = translateService.translate('sdk.general.verify.error.wrongCardType');
|
|
32934
|
+
sendStatusDescription$1(StatusDescription.WrongCardType);
|
|
32935
|
+
uiComponentOCRMask.setCardBorderColor('error');
|
|
32936
|
+
break;
|
|
32941
32937
|
}
|
|
32942
32938
|
if (blurCount === 2) {
|
|
32943
32939
|
showCameraSwitchButton(deviceMetas);
|
|
@@ -33070,7 +33066,12 @@ function startOCR(config) {
|
|
|
33070
33066
|
const ctx = canvas.getContext('2d', {
|
|
33071
33067
|
willReadFrequently: true
|
|
33072
33068
|
});
|
|
33073
|
-
return source$.pipe(mergeMap(() => animationFrames().pipe(limitFPS(fps), filter(() => received && !ocrSendFrameAnimation), tap(() => received = false), tap(() => clearCanvas(canvas)), map(() => getImageData(canvas, ctx, video, canvasSizeInfo, bas64Format, imageType)),
|
|
33069
|
+
return source$.pipe(mergeMap(() => animationFrames().pipe(limitFPS(fps), filter(() => received && !ocrSendFrameAnimation), tap(() => received = false), tap(() => clearCanvas(canvas)), map(() => getImageData(canvas, ctx, video, canvasSizeInfo, bas64Format, imageType)), tap(imageData => {
|
|
33070
|
+
// 如果 imageData 為 null(canvas 尺寸無效),重置 received 讓下一幀可以繼續
|
|
33071
|
+
if (imageData === null) {
|
|
33072
|
+
received = true;
|
|
33073
|
+
}
|
|
33074
|
+
}), filter(imageData => imageData !== null), mergeMap(imageData => from(frameCallback(imageData.data, imageData.base64, cardType, type)).pipe(catchError(e => {
|
|
33074
33075
|
// send to fast, ignore
|
|
33075
33076
|
if (e instanceof AuthmeError && e.code === ErrorCode.RECOGNITION_NOT_AVAILABLE) {
|
|
33076
33077
|
return EMPTY;
|
|
@@ -33082,7 +33083,7 @@ function startOCR(config) {
|
|
|
33082
33083
|
})), tap(() => received = true))))));
|
|
33083
33084
|
};
|
|
33084
33085
|
const autoCapture = canvasSizeInfo => {
|
|
33085
|
-
return of(canvasSizeInfo).pipe(handleOcrSendFrame(canvasSizeInfo, uiComponentOCR.image, uiComponentBasic.video, config.recognition,
|
|
33086
|
+
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(({
|
|
33086
33087
|
result
|
|
33087
33088
|
}) => result.eStatus === EAuthMeCardOCRStatus.Pass || result.eStatus === EAuthMeMRZServiceStatus.Success), take(1), tap(() => {
|
|
33088
33089
|
if (countdownCaptureTimer && countdownCaptureTimer.end && !countdownCaptureTimer.end()) {
|
|
@@ -33100,38 +33101,47 @@ function startOCR(config) {
|
|
|
33100
33101
|
hideElement(uiComponentOCR.scanAnimationContainer);
|
|
33101
33102
|
}), map(() => resp))), switchMap(({
|
|
33102
33103
|
result
|
|
33103
|
-
}) =>
|
|
33104
|
-
|
|
33105
|
-
|
|
33106
|
-
|
|
33107
|
-
|
|
33108
|
-
|
|
33109
|
-
|
|
33110
|
-
|
|
33104
|
+
}) => {
|
|
33105
|
+
// V9: 檢查卡片圖像是否可用
|
|
33106
|
+
const cardResult = result;
|
|
33107
|
+
const hasValidImage = cardResult.imageData && cardResult.iWidth > 0 && cardResult.iHeight > 0;
|
|
33108
|
+
if (!hasValidImage) {
|
|
33109
|
+
console.warn('[OCR] Card image not available, skipping confirm page. iWidth:', cardResult.iWidth, 'iHeight:', cardResult.iHeight, 'imageData:', cardResult.imageData ? 'exists' : 'null');
|
|
33110
|
+
}
|
|
33111
|
+
const shouldSkipConfirm = type === EAuthMeCardClass.Passport && config.ocrConfig.disablePassportConfirm || !config.ocrConfig.confirmPageEnabled || !hasValidImage; // V9: 如果沒有卡片圖像,跳過確認頁
|
|
33112
|
+
return from(shouldSkipConfirm ? of(false) : checkConfirmImage(cardResult.imageData, cardResult.iWidth, cardResult.iHeight)).pipe(switchMap(needRetry => {
|
|
33113
|
+
if (countdownCaptureTimer && countdownCaptureTimer.end) {
|
|
33114
|
+
if (needRetry && !countdownCaptureTimer.end()) {
|
|
33115
|
+
countdownCaptureTimer = countdownTimer(captureTimeoutTimer, () => {
|
|
33116
|
+
showElement(captureBtn);
|
|
33117
|
+
toastManualCapture = toast({
|
|
33118
|
+
message: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0_19494_3109)"><path d="M19.5 19.5H4.5C4.10218 19.5 3.72064 19.342 3.43934 19.0607C3.15804 18.7794 3 18.3978 3 18V7.5C3 7.10218 3.15804 6.72064 3.43934 6.43934C3.72064 6.15804 4.10218 6 4.5 6H7.5L9 3.75H15L16.5 6H19.5C19.8978 6 20.2794 6.15804 20.5607 6.43934C20.842 6.72064 21 7.10218 21 7.5V18C21 18.3978 20.842 18.7794 20.5607 19.0607C20.2794 19.342 19.8978 19.5 19.5 19.5Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M12 15.75C13.864 15.75 15.375 14.239 15.375 12.375C15.375 10.511 13.864 9 12 9C10.136 9 8.625 10.511 8.625 12.375C8.625 14.239 10.136 15.75 12 15.75Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></g><defs><clipPath id="clip0_19494_3109"><rect width="24" height="24" fill="white"/></clipPath></defs></svg>' + translateService.translate('sdk.general.manualCapture'),
|
|
33119
|
+
transition: true
|
|
33120
|
+
});
|
|
33111
33121
|
});
|
|
33112
|
-
|
|
33113
|
-
|
|
33114
|
-
|
|
33115
|
-
|
|
33116
|
-
|
|
33117
|
-
|
|
33122
|
+
countdownCaptureTimer.init();
|
|
33123
|
+
}
|
|
33124
|
+
if (!needRetry) {
|
|
33125
|
+
countdownCaptureTimer.clear();
|
|
33126
|
+
hideElement(captureBtn);
|
|
33127
|
+
}
|
|
33118
33128
|
}
|
|
33119
|
-
|
|
33120
|
-
|
|
33121
|
-
|
|
33122
|
-
|
|
33123
|
-
|
|
33124
|
-
|
|
33125
|
-
|
|
33126
|
-
|
|
33127
|
-
|
|
33128
|
-
})
|
|
33129
|
-
|
|
33130
|
-
|
|
33131
|
-
|
|
33132
|
-
|
|
33133
|
-
|
|
33134
|
-
})
|
|
33129
|
+
startSpinner({
|
|
33130
|
+
text: translateService.translate('sdk.general.uploading'),
|
|
33131
|
+
statement: translateService.translate('sdk.general.footer'),
|
|
33132
|
+
backgroundOpaque: true
|
|
33133
|
+
});
|
|
33134
|
+
return needRetry ? of(true) : from(config.confirmImage({
|
|
33135
|
+
type,
|
|
33136
|
+
cardType
|
|
33137
|
+
})).pipe(tap(() => sendStatusAction$1(StatusAction.Uploading)), map(confirmResp => !confirmResp));
|
|
33138
|
+
}), tap(() => {
|
|
33139
|
+
// hideElement(uiComponentOCR.confirmImageContainer);
|
|
33140
|
+
hideElement(uiComponentOCR.confirmContainer);
|
|
33141
|
+
stopSpinner();
|
|
33142
|
+
showVideoElement();
|
|
33143
|
+
}), switchMap(needRetry => needRetry ? recognition(true) : of(true)));
|
|
33144
|
+
}));
|
|
33135
33145
|
};
|
|
33136
33146
|
const recognition = retry => {
|
|
33137
33147
|
return init(retry).pipe(tap(() => {
|
|
@@ -33423,7 +33433,7 @@ function startOCR(config) {
|
|
|
33423
33433
|
const cardType = currentType('get', null).cardType;
|
|
33424
33434
|
const ctx = uiComponentOCR.image.getContext('2d');
|
|
33425
33435
|
const imageData = getImageData(uiComponentOCR.image, ctx, uiComponentBasic.video, canvasSizeInfo, false, config.ocrConfig.resultImageFormat);
|
|
33426
|
-
const imageBlob = UintArrayToBlob(canvasSizeInfo.width, canvasSizeInfo.height, imageData.data);
|
|
33436
|
+
const imageBlob = imageData ? UintArrayToBlob(canvasSizeInfo.width, canvasSizeInfo.height, imageData.data) : undefined;
|
|
33427
33437
|
const cancelResultObj = {
|
|
33428
33438
|
isSuccess: false,
|
|
33429
33439
|
code: `${ErrorCode.USER_CANCEL}`,
|
|
@@ -33774,7 +33784,7 @@ function startOCR(config) {
|
|
|
33774
33784
|
function cardRotateByStage(stage, mandatoryRotate = false, point) {
|
|
33775
33785
|
return __awaiter(this, void 0, void 0, function* () {
|
|
33776
33786
|
if ((stage === EAuthMeIDCardAntiFraudStage.Done || stage === currentAntiFraudStage) && mandatoryRotate === false) return;
|
|
33777
|
-
const cardMatchROI = point ? point : config.getCardMatchROI ? yield config.getCardMatchROI() : null;
|
|
33787
|
+
const cardMatchROI = point ? point : config.getCardMatchROI ? yield config.getCardMatchROI(stage) : null;
|
|
33778
33788
|
if (!cardMatchROI || cardMatchROI.length == 0) throw new AuthmeError(ErrorCode.SDK_INTERNAL_ERROR, 'getCardMatchROI is null');
|
|
33779
33789
|
uiComponentOCRMask.setCardBorderColor('error');
|
|
33780
33790
|
// 只有左右翻轉時需要正規化座標(確保左右邊平行)
|
|
@@ -34508,6 +34518,11 @@ class LivenessVerifyModule {
|
|
|
34508
34518
|
}
|
|
34509
34519
|
}
|
|
34510
34520
|
|
|
34521
|
+
/**
|
|
34522
|
+
* Debug flag for FAS (Face Authentication Service) encryption flow.
|
|
34523
|
+
* Set to true to enable detailed logging for debugging encryption issues.
|
|
34524
|
+
*/
|
|
34525
|
+
const DEBUG_FAS = false;
|
|
34511
34526
|
function handleUploadError$1(_error) {
|
|
34512
34527
|
return new Promise(resolve => {
|
|
34513
34528
|
uploadModal({
|
|
@@ -34545,12 +34560,18 @@ class LivenessModule {
|
|
|
34545
34560
|
let id = '';
|
|
34546
34561
|
let pubKey = '';
|
|
34547
34562
|
let shouldEncrypt = false;
|
|
34563
|
+
// 保存 API 返回的參數,避免在 onStart 中被覆蓋
|
|
34564
|
+
let apiTimeoutSec;
|
|
34565
|
+
let apiFasThreshold;
|
|
34548
34566
|
const encryptDataBase64 = data => __awaiter(this, void 0, void 0, function* () {
|
|
34549
34567
|
const dataString = JSON.stringify(data);
|
|
34550
34568
|
const encoder = new TextEncoder();
|
|
34551
34569
|
const uint8Array = encoder.encode(dataString);
|
|
34552
|
-
// TODO check encrypt function
|
|
34553
34570
|
const resultEncrypt = yield this.fasService.encryptBlob(uint8Array, pubKey);
|
|
34571
|
+
if (!resultEncrypt) {
|
|
34572
|
+
console.error('[encryptDataBase64] encryptBlob returned null/empty');
|
|
34573
|
+
throw new AuthmeError(ErrorCode.SDK_INTERNAL_ERROR, 'encryptBlob failed');
|
|
34574
|
+
}
|
|
34554
34575
|
return resultEncrypt;
|
|
34555
34576
|
});
|
|
34556
34577
|
const handleUpload = (id, frameList, resultList, meta, config, shouldEncrypt, encryptDataBase64) => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -34572,7 +34593,6 @@ class LivenessModule {
|
|
|
34572
34593
|
try {
|
|
34573
34594
|
const result = yield firstValueFrom(yield startLiveness({
|
|
34574
34595
|
getOptionConfig: () => __awaiter(this, void 0, void 0, function* () {
|
|
34575
|
-
console.log('config.deviceType', config.deviceType);
|
|
34576
34596
|
const res = yield LivenessAPI.IdentityVerification.option(config.deviceType);
|
|
34577
34597
|
const themeId = res.themeId;
|
|
34578
34598
|
if (!themeId) {
|
|
@@ -34587,6 +34607,10 @@ class LivenessModule {
|
|
|
34587
34607
|
id = resp.id;
|
|
34588
34608
|
pubKey = resp.parameters.pubKey;
|
|
34589
34609
|
shouldEncrypt = (_a = resp.shouldEncrypt) !== null && _a !== void 0 ? _a : shouldEncrypt;
|
|
34610
|
+
if (DEBUG_FAS) ;
|
|
34611
|
+
// 根據 shouldEncrypt 決定是否設置 report key
|
|
34612
|
+
// shouldEncrypt = true: 清空 report key,讓 getReport() 返回未加密的 base64,由 SDK 加密整個 payload
|
|
34613
|
+
// shouldEncrypt = false: 設置 report key,讓 getReport() 返回 engine 加密的 report
|
|
34590
34614
|
if (!shouldEncrypt) {
|
|
34591
34615
|
yield this.fasService.setPublicKeyForJson(pubKey);
|
|
34592
34616
|
} else {
|
|
@@ -34601,8 +34625,11 @@ class LivenessModule {
|
|
|
34601
34625
|
// fRight: 0.7,
|
|
34602
34626
|
// fBottom: 0.7,
|
|
34603
34627
|
// };
|
|
34604
|
-
|
|
34605
|
-
|
|
34628
|
+
// 保存 API 參數,在 onStart 中使用
|
|
34629
|
+
apiTimeoutSec = resp.parameters.fasTimeout || params.timeoutSec;
|
|
34630
|
+
apiFasThreshold = resp.parameters.fasThreshold || params.fFASTh;
|
|
34631
|
+
params.timeoutSec = apiTimeoutSec;
|
|
34632
|
+
params.fFASTh = apiFasThreshold;
|
|
34606
34633
|
yield this.fasService.setParams(params);
|
|
34607
34634
|
yield this.fasService.setStage(resp.parameters.fasStages.map(x => `EAuthMeFASServiceStage_${x}`));
|
|
34608
34635
|
// return resp.parameters;
|
|
@@ -34645,8 +34672,15 @@ class LivenessModule {
|
|
|
34645
34672
|
params.faceROI.fRight = 1 - (1 - widthPercent) / 2;
|
|
34646
34673
|
params.faceROI.fTop = (1 - heightPercent) / 2;
|
|
34647
34674
|
params.faceROI.fBottom = 1 - (1 - heightPercent) / 2;
|
|
34648
|
-
|
|
34675
|
+
// 保留 API 設定的 timeout 和 threshold,避免被 getParams() 的預設值覆蓋
|
|
34676
|
+
if (apiTimeoutSec !== undefined) {
|
|
34677
|
+
params.timeoutSec = apiTimeoutSec;
|
|
34678
|
+
}
|
|
34679
|
+
if (apiFasThreshold !== undefined) {
|
|
34680
|
+
params.fFASTh = apiFasThreshold;
|
|
34681
|
+
}
|
|
34649
34682
|
yield this.fasService.setFrameSize(frameWidth, frameHeight);
|
|
34683
|
+
yield this.fasService.setParams(params);
|
|
34650
34684
|
yield this.fasService.startSession();
|
|
34651
34685
|
return params;
|
|
34652
34686
|
}),
|
|
@@ -34801,12 +34835,27 @@ class LivenessModule {
|
|
|
34801
34835
|
data: meta
|
|
34802
34836
|
};
|
|
34803
34837
|
if (shouldEncrypt) {
|
|
34804
|
-
|
|
34838
|
+
// shouldEncrypt = true 時,沒有設置 report key,
|
|
34839
|
+
// getReport() 返回的是 base64 編碼的未加密 JSON
|
|
34840
|
+
// 需要先 atob 解碼成純 JSON 字串(保持為字串,與 Android SDK 一致)
|
|
34841
|
+
// 然後用 encryptDataBase64 加密整個 postData
|
|
34842
|
+
try {
|
|
34843
|
+
const decodedMeta = atob(meta);
|
|
34844
|
+
if (DEBUG_FAS) ;
|
|
34845
|
+
postData.data = decodedMeta; // 保持為字串,與 Android 一致
|
|
34846
|
+
} catch (e) {
|
|
34847
|
+
console.error('[uploadMeta] atob failed:', e);
|
|
34848
|
+
// 如果 atob 失敗,可能已經是純 JSON 字串,保持原樣
|
|
34849
|
+
}
|
|
34850
|
+
|
|
34851
|
+
const encrypted = yield encryptDataBase64(postData);
|
|
34805
34852
|
return LivenessAPI.IdentityVerification.uploadMeta({
|
|
34806
34853
|
id: id,
|
|
34807
|
-
encryptedBase64String:
|
|
34854
|
+
encryptedBase64String: encrypted
|
|
34808
34855
|
});
|
|
34809
34856
|
} else {
|
|
34857
|
+
// shouldEncrypt = false 時,已設置 report key,
|
|
34858
|
+
// getReport() 返回的是 engine 加密過的 base64 字符串
|
|
34810
34859
|
return LivenessAPI.IdentityVerification.uploadMeta(postData);
|
|
34811
34860
|
}
|
|
34812
34861
|
// return LivenessAPI.IdentityVerification.uploadMeta({
|
|
@@ -35108,13 +35157,19 @@ class MRZModule {
|
|
|
35108
35157
|
pubKey = resp.parameters.pubKey;
|
|
35109
35158
|
scanId = resp.scanId;
|
|
35110
35159
|
uploadFullFrame = (_c = (_b = resp.parameters.fraud) === null || _b === void 0 ? void 0 : _b.collectAllFrames) !== null && _c !== void 0 ? _c : config.uploadFullFrame;
|
|
35111
|
-
|
|
35160
|
+
// 根據 shouldEncrypt 決定是否設置 report key
|
|
35161
|
+
// shouldEncrypt = true: 清空 report key,讓 getReport() 返回未加密的 base64,由 SDK 加密整個 payload
|
|
35162
|
+
// shouldEncrypt = false: 設置 report key,讓 getReport() 返回 engine 加密的 report
|
|
35163
|
+
if (!shouldEncrypt) {
|
|
35112
35164
|
this.engine.setPublicKeyForJson(pubKey);
|
|
35165
|
+
} else {
|
|
35166
|
+
this.engine.setPublicKeyForJson('');
|
|
35113
35167
|
}
|
|
35114
35168
|
yield this.mrzService.init();
|
|
35115
35169
|
yield waitTime(100);
|
|
35116
35170
|
return Object.assign(Object.assign({}, resp.parameters), {
|
|
35117
|
-
expiredIn: resp.expiredIn
|
|
35171
|
+
expiredIn: resp.expiredIn,
|
|
35172
|
+
ocrMaxFps: 2
|
|
35118
35173
|
});
|
|
35119
35174
|
}),
|
|
35120
35175
|
ocrStart: (points, type, setBorderType, setCardBorderColor, setBorderSuccess, scanAnimationContainer, successAnimationContainer, frameImage, frameText, faceMode, cardType, retry = false) => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -35179,29 +35234,44 @@ class MRZModule {
|
|
|
35179
35234
|
// 1. 最後結果統一使用 getFinalResult 取得的結果。
|
|
35180
35235
|
// 2. 由於 engine-lib 限制,getFinalResult 必須要在 stop 之後呼叫。
|
|
35181
35236
|
const finalResult = yield this.mrzService.getFinalResult();
|
|
35182
|
-
|
|
35183
|
-
|
|
35184
|
-
|
|
35185
|
-
|
|
35186
|
-
|
|
35187
|
-
|
|
35188
|
-
|
|
35189
|
-
|
|
35190
|
-
|
|
35191
|
-
|
|
35192
|
-
|
|
35193
|
-
|
|
35194
|
-
|
|
35195
|
-
|
|
35196
|
-
|
|
35197
|
-
|
|
35198
|
-
|
|
35199
|
-
|
|
35200
|
-
|
|
35201
|
-
|
|
35202
|
-
|
|
35203
|
-
|
|
35204
|
-
|
|
35237
|
+
if (finalResult) {
|
|
35238
|
+
const rawField = JSON.parse(yield this.mrzService.toJson(finalResult));
|
|
35239
|
+
// V9 API 回傳 snake_case 欄位,轉換為 camelCase 以匹配翻譯 key
|
|
35240
|
+
const snakeToCamelMap = {
|
|
35241
|
+
birth_date: 'birthDate',
|
|
35242
|
+
expiry_date: 'expiryDate',
|
|
35243
|
+
document_number: 'documentNumber',
|
|
35244
|
+
document_type: 'documentType',
|
|
35245
|
+
given_name: 'givenName',
|
|
35246
|
+
personal_number: 'personalNumber',
|
|
35247
|
+
sex: 'gender',
|
|
35248
|
+
surname: 'surname',
|
|
35249
|
+
nationality: 'nationality',
|
|
35250
|
+
country: 'country'
|
|
35251
|
+
};
|
|
35252
|
+
// 需要刪除的欄位 (check digit)
|
|
35253
|
+
const fieldsToDelete = ['birthDateCheckDigit', 'documentNumberCheckDigit', 'expiryDateCheckDigit', 'optionaldataCheckDigit', 'overallCheckDigit', 'birth_date_check_digit', 'document_number_check_digit', 'expiry_date_check_digit', 'optional_data_check_digit', 'overall_check_digit'];
|
|
35254
|
+
// 轉換欄位名稱並過濾 check digit
|
|
35255
|
+
latestTField = {};
|
|
35256
|
+
for (const [key, value] of Object.entries(rawField)) {
|
|
35257
|
+
if (fieldsToDelete.includes(key)) {
|
|
35258
|
+
continue; // 跳過 check digit 欄位
|
|
35259
|
+
}
|
|
35260
|
+
|
|
35261
|
+
const newKey = snakeToCamelMap[key] || key;
|
|
35262
|
+
latestTField[newKey] = value;
|
|
35263
|
+
}
|
|
35264
|
+
// 性別轉換
|
|
35265
|
+
if (latestTField === null || latestTField === void 0 ? void 0 : latestTField.gender) {
|
|
35266
|
+
switch (latestTField.gender) {
|
|
35267
|
+
case 'M':
|
|
35268
|
+
latestTField.gender = 'male';
|
|
35269
|
+
break;
|
|
35270
|
+
case 'F':
|
|
35271
|
+
latestTField.gender = 'female';
|
|
35272
|
+
break;
|
|
35273
|
+
}
|
|
35274
|
+
}
|
|
35205
35275
|
}
|
|
35206
35276
|
} else if (uploadFullFrame) {
|
|
35207
35277
|
const image = UintArrayToBlob(frameWidth, frameHeight, data, virtualCanvas);
|
|
@@ -35337,6 +35407,8 @@ class MRZModule {
|
|
|
35337
35407
|
getAntiFraudStageList: () => []
|
|
35338
35408
|
}));
|
|
35339
35409
|
} catch (error) {
|
|
35410
|
+
console.error('[MRZ Module] Error caught:', error);
|
|
35411
|
+
console.error('[MRZ Module] Error stack:', error === null || error === void 0 ? void 0 : error.stack);
|
|
35340
35412
|
const catchedError = error instanceof AuthmeError ? error : new AuthmeError(ErrorCode.SDK_INTERNAL_ERROR, error);
|
|
35341
35413
|
return {
|
|
35342
35414
|
isSuccess: false,
|
|
@@ -35349,8 +35421,98 @@ class MRZModule {
|
|
|
35349
35421
|
}
|
|
35350
35422
|
}
|
|
35351
35423
|
|
|
35352
|
-
|
|
35424
|
+
// V9: 卡片 OCR 參數常數名稱
|
|
35425
|
+
const F_IMAGE_BLUR_TH = 'fImageBlurTh';
|
|
35426
|
+
const F_IDCARD_COLOR_TH = 'fIdCardColorTh';
|
|
35427
|
+
const F_IMAGE_REFLECTIVE_TRIGGER_TH = 'fImageReflectiveTriggerTh';
|
|
35428
|
+
const F_IMAGE_REFLECTIVE_MASK_TH = 'fImageReflectiveMaskTh';
|
|
35429
|
+
const F_CARD_MATCH_TH = 'fCardMatchTh';
|
|
35430
|
+
const F_CARD_CLASSIFICATION_TH = 'fCardClassificationTh';
|
|
35431
|
+
// V9: 不同卡片類型的預設參數 (參考 Android SDK)
|
|
35432
|
+
// 邏輯說明:
|
|
35433
|
+
// - idCardColorTh: color_detect.score > threshold 為彩色 (Pass), < threshold 為灰階 (Gray)
|
|
35434
|
+
// - imageReflectiveTriggerTh: 反光分數 > threshold 判定為反光
|
|
35435
|
+
// - imageBlurTh: 模糊分數 > threshold 判定為模糊
|
|
35436
|
+
const CARD_OCR_PARAMS_MAP = {
|
|
35437
|
+
// 身分證正面: 高反光容限 (塑膠卡)
|
|
35438
|
+
TWN_IDCard_Front: {
|
|
35439
|
+
idCardColorTh: 15,
|
|
35440
|
+
imageBlurTh: 750,
|
|
35441
|
+
imageReflectiveTriggerTh: 1.05,
|
|
35442
|
+
imageReflectiveMaskTh: 1.5,
|
|
35443
|
+
cardMatchTh: 0.3,
|
|
35444
|
+
cardClassificationTh: 0.62655
|
|
35445
|
+
},
|
|
35446
|
+
// 身分證背面: 高反光容限 (塑膠卡)
|
|
35447
|
+
TWN_IDCard_Back: {
|
|
35448
|
+
idCardColorTh: 4.5,
|
|
35449
|
+
imageBlurTh: 750,
|
|
35450
|
+
imageReflectiveTriggerTh: 1.05,
|
|
35451
|
+
imageReflectiveMaskTh: 1.5,
|
|
35452
|
+
cardMatchTh: 0.3,
|
|
35453
|
+
cardClassificationTh: 0.62655
|
|
35454
|
+
},
|
|
35455
|
+
// 健保卡正面: 中等反光容限
|
|
35456
|
+
TWN_HealthCard_Front: {
|
|
35457
|
+
idCardColorTh: 10,
|
|
35458
|
+
imageBlurTh: 750,
|
|
35459
|
+
imageReflectiveTriggerTh: 0.35,
|
|
35460
|
+
imageReflectiveMaskTh: 0.45,
|
|
35461
|
+
cardMatchTh: 0.3,
|
|
35462
|
+
cardClassificationTh: 0.62655
|
|
35463
|
+
},
|
|
35464
|
+
// 駕照正面: 低反光觸發 (紙質)
|
|
35465
|
+
TWN_DriverLicense_Front: {
|
|
35466
|
+
idCardColorTh: 15,
|
|
35467
|
+
imageBlurTh: 750,
|
|
35468
|
+
imageReflectiveTriggerTh: 0.16,
|
|
35469
|
+
imageReflectiveMaskTh: 0.45,
|
|
35470
|
+
cardMatchTh: 0.3,
|
|
35471
|
+
cardClassificationTh: 0.62655
|
|
35472
|
+
},
|
|
35473
|
+
// 駕照背面: 極低反光觸發
|
|
35474
|
+
TWN_DriverLicense_Back: {
|
|
35475
|
+
idCardColorTh: 8,
|
|
35476
|
+
imageBlurTh: 600,
|
|
35477
|
+
imageReflectiveTriggerTh: 0.045,
|
|
35478
|
+
imageReflectiveMaskTh: 0.45,
|
|
35479
|
+
cardMatchTh: 0.75,
|
|
35480
|
+
cardClassificationTh: 0.62655
|
|
35481
|
+
},
|
|
35482
|
+
// 居留證正面
|
|
35483
|
+
TWN_ResidentCard_Front: {
|
|
35484
|
+
idCardColorTh: 0,
|
|
35485
|
+
imageBlurTh: 600,
|
|
35486
|
+
imageReflectiveTriggerTh: 0.045,
|
|
35487
|
+
imageReflectiveMaskTh: 0.45,
|
|
35488
|
+
cardMatchTh: 0.75,
|
|
35489
|
+
cardClassificationTh: 0.62655
|
|
35490
|
+
},
|
|
35491
|
+
// 居留證背面
|
|
35492
|
+
TWN_ResidentCard_Back: {
|
|
35493
|
+
idCardColorTh: 0,
|
|
35494
|
+
imageBlurTh: 600,
|
|
35495
|
+
imageReflectiveTriggerTh: 0.045,
|
|
35496
|
+
imageReflectiveMaskTh: 0.45,
|
|
35497
|
+
cardMatchTh: 0.75,
|
|
35498
|
+
cardClassificationTh: 0.62655
|
|
35499
|
+
}
|
|
35500
|
+
};
|
|
35501
|
+
// V9: 預設參數 (用於未定義的卡片類型)
|
|
35502
|
+
const DEFAULT_CARD_OCR_PARAMS = {
|
|
35503
|
+
idCardColorTh: 0,
|
|
35504
|
+
imageBlurTh: 750,
|
|
35505
|
+
imageReflectiveTriggerTh: 0.05,
|
|
35506
|
+
imageReflectiveMaskTh: 0.025,
|
|
35507
|
+
cardMatchTh: 0.3,
|
|
35508
|
+
cardClassificationTh: 0.62655
|
|
35509
|
+
};
|
|
35353
35510
|
const DEFAULT_ANTI_FRAUD_TIMEOUT = 40;
|
|
35511
|
+
// V9: OCR 結果需要過濾掉的欄位 (重複/內部欄位)
|
|
35512
|
+
// 這些欄位可能是內部檢核數字或與其他欄位重複
|
|
35513
|
+
const OCR_FIELDS_TO_FILTER = ['backSideId', 'reverseId', 'dob', 'firstName', 'lastName', 'fullName', 'optionalData2' // 內部欄位
|
|
35514
|
+
];
|
|
35515
|
+
|
|
35354
35516
|
function unionMerge(a, b) {
|
|
35355
35517
|
const entries = Object.entries(a).concat(Object.entries(b));
|
|
35356
35518
|
// union merge
|
|
@@ -35415,8 +35577,8 @@ function handleUploadError(error) {
|
|
|
35415
35577
|
});
|
|
35416
35578
|
});
|
|
35417
35579
|
}
|
|
35418
|
-
//
|
|
35419
|
-
const antiFraudStageListMap = [[EAuthMeIDCardAntiFraudStage$1.Left, EAuthMeIDCardAntiFraudStage$1.Right, EAuthMeIDCardAntiFraudStage$1.Up, EAuthMeIDCardAntiFraudStage$1.Down
|
|
35580
|
+
// V9: Engine 會自動重複 stages,只需要傳入基本的 4 個方向
|
|
35581
|
+
const antiFraudStageListMap = [[EAuthMeIDCardAntiFraudStage$1.Left, EAuthMeIDCardAntiFraudStage$1.Right, EAuthMeIDCardAntiFraudStage$1.Up, EAuthMeIDCardAntiFraudStage$1.Down]
|
|
35420
35582
|
// [
|
|
35421
35583
|
// EAuthMeIDCardAntiFraudStage.Left,
|
|
35422
35584
|
// EAuthMeIDCardAntiFraudStage.Right,
|
|
@@ -35734,12 +35896,17 @@ class OCRModule {
|
|
|
35734
35896
|
pubKey = resp.parameters.pubKey;
|
|
35735
35897
|
uploadFullFrame = (_c = (_b = resp.parameters.fraud) === null || _b === void 0 ? void 0 : _b.collectAllFrames) !== null && _c !== void 0 ? _c : config.uploadFullFrame;
|
|
35736
35898
|
fraudTimeout = (_e = (_d = resp.parameters.fraud) === null || _d === void 0 ? void 0 : _d.totalTimeout) !== null && _e !== void 0 ? _e : DEFAULT_ANTI_FRAUD_TIMEOUT;
|
|
35899
|
+
// 根據 shouldEncrypt 決定是否設置 report key
|
|
35900
|
+
// shouldEncrypt = true: 清空 report key,讓 getReport() 返回未加密的 base64,由 SDK 加密整個 payload
|
|
35901
|
+
// shouldEncrypt = false: 設置 report key,讓 getReport() 返回 engine 加密的 report
|
|
35737
35902
|
if (!shouldEncrypt) {
|
|
35738
35903
|
this.engine.setPublicKeyForJson(pubKey);
|
|
35739
35904
|
} else {
|
|
35740
35905
|
this.engine.setPublicKeyForJson('');
|
|
35741
35906
|
}
|
|
35742
35907
|
if (config.type === IdRecognitionCardType.IDCard && config.needAntiFraud) {
|
|
35908
|
+
// 先設定 stage 再 init,避免使用預設的 Frontal stage
|
|
35909
|
+
yield this.antiFraudInstance.setStage(antiFraudStageList);
|
|
35743
35910
|
yield this.antiFraudInstance.init();
|
|
35744
35911
|
} else if (config.type === IdRecognitionCardType.ResidentCard) {
|
|
35745
35912
|
// workaround: resident card need MRZ, refactor later.
|
|
@@ -35751,7 +35918,8 @@ class OCRModule {
|
|
|
35751
35918
|
yield waitTime(100);
|
|
35752
35919
|
return Object.assign(Object.assign({}, resp.parameters), {
|
|
35753
35920
|
expiredIn: resp.expiredIn,
|
|
35754
|
-
captureTimeout: config.captureTimeout
|
|
35921
|
+
captureTimeout: config.captureTimeout,
|
|
35922
|
+
ocrMaxFps: 2
|
|
35755
35923
|
});
|
|
35756
35924
|
}),
|
|
35757
35925
|
ocrStart: (points, type, setBorderType, setCardBorderColor, setBorderSuccess, scanAnimationContainer, successAnimationContainer, frameImage, frameText, faceMode, cardType, retry = false) => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -35762,9 +35930,14 @@ class OCRModule {
|
|
|
35762
35930
|
_service instanceof CardOCR ? yield _service.setType(type) : null;
|
|
35763
35931
|
const newParams = {};
|
|
35764
35932
|
const oldParams = yield this.ocrService.getParams();
|
|
35765
|
-
|
|
35766
|
-
|
|
35767
|
-
newParams[
|
|
35933
|
+
// V9: 根據卡片類型設置參數 (參考 Android SDK)
|
|
35934
|
+
const cardParams = CARD_OCR_PARAMS_MAP[cardType] || DEFAULT_CARD_OCR_PARAMS;
|
|
35935
|
+
newParams[F_IMAGE_BLUR_TH] = cardParams.imageBlurTh;
|
|
35936
|
+
newParams[F_IDCARD_COLOR_TH] = cardParams.idCardColorTh;
|
|
35937
|
+
newParams[F_IMAGE_REFLECTIVE_TRIGGER_TH] = cardParams.imageReflectiveTriggerTh;
|
|
35938
|
+
newParams[F_IMAGE_REFLECTIVE_MASK_TH] = cardParams.imageReflectiveMaskTh;
|
|
35939
|
+
newParams[F_CARD_MATCH_TH] = cardParams.cardMatchTh;
|
|
35940
|
+
newParams[F_CARD_CLASSIFICATION_TH] = cardParams.cardClassificationTh;
|
|
35768
35941
|
yield this.ocrService.setParams(Object.assign(Object.assign({}, oldParams), newParams));
|
|
35769
35942
|
if (cardTypes.map(mapCardtypeToAuthmeClass).filter(n => n == EAuthMeCardClass$1.Unknown).length > 0) {
|
|
35770
35943
|
this.ocrService.setOption({
|
|
@@ -35928,9 +36101,12 @@ class OCRModule {
|
|
|
35928
36101
|
const newParams = {};
|
|
35929
36102
|
const oldParams = yield _service.getParams();
|
|
35930
36103
|
if (config.antiFraudIMetalTagValidCountTh !== false) newParams['iMetalTagValidCountTh'] = config.antiFraudIMetalTagValidCountTh;
|
|
36104
|
+
// V9: 根據卡片類型設置參數 (參考 Android SDK)
|
|
36105
|
+
const cardParams = CARD_OCR_PARAMS_MAP[cardType] || DEFAULT_CARD_OCR_PARAMS;
|
|
35931
36106
|
newParams['timeoutSec'] = 1000;
|
|
35932
|
-
newParams[
|
|
35933
|
-
newParams[
|
|
36107
|
+
newParams[F_IDCARD_COLOR_TH] = cardParams.idCardColorTh;
|
|
36108
|
+
newParams[F_IMAGE_REFLECTIVE_TRIGGER_TH] = cardParams.imageReflectiveTriggerTh;
|
|
36109
|
+
newParams[F_CARD_MATCH_TH] = cardParams.cardMatchTh;
|
|
35934
36110
|
newParams['fImageThicknessTh'] = 0.4;
|
|
35935
36111
|
newParams['fCardDeformationTh'] = 10;
|
|
35936
36112
|
newParams['enableCardInROI'] = 1;
|
|
@@ -36270,8 +36446,16 @@ class OCRModule {
|
|
|
36270
36446
|
});
|
|
36271
36447
|
const ocrOriginImg = UintArrayToBlob(frameWidth, frameHeight, data, virtualCanvas, config.resultImageFormat);
|
|
36272
36448
|
const eClass = cardType !== null && cardType !== void 0 ? cardType : '';
|
|
36273
|
-
if (result.eStatus === EAuthMeCardOCRStatus.Pass &&
|
|
36274
|
-
|
|
36449
|
+
if (result.eStatus === EAuthMeCardOCRStatus.Pass && !!docInfos[eClass].docId) {
|
|
36450
|
+
// V9: 如果有卡片圖像就使用它,否則使用原始幀圖像
|
|
36451
|
+
let resultOcrImg;
|
|
36452
|
+
if (result.imageData && result.iWidth > 0 && result.iHeight > 0) {
|
|
36453
|
+
resultOcrImg = UintArrayToBlob(result.iWidth, result.iHeight, result.imageData, virtualCanvas, config.resultImageFormat);
|
|
36454
|
+
} else {
|
|
36455
|
+
// V9 沒有返回卡片圖像,使用原始幀圖像作為替代
|
|
36456
|
+
console.warn('[OCR] V9: no card image available, using original frame image');
|
|
36457
|
+
resultOcrImg = ocrOriginImg;
|
|
36458
|
+
}
|
|
36275
36459
|
docInfos[eClass].ocrImg = resultOcrImg;
|
|
36276
36460
|
docInfos[eClass].ocrOriginImg = ocrOriginImg;
|
|
36277
36461
|
yield _service.stop();
|
|
@@ -36357,6 +36541,12 @@ class OCRModule {
|
|
|
36357
36541
|
} else {
|
|
36358
36542
|
ocrOriginImg = docInfos[option.cardType].ocrOriginImg;
|
|
36359
36543
|
}
|
|
36544
|
+
// V9: 檢查 ocrOriginImg 是否有效
|
|
36545
|
+
if (!ocrOriginImg) {
|
|
36546
|
+
console.error('[OCR] confirmImage: ocrOriginImg is null or undefined, cannot proceed');
|
|
36547
|
+
console.error('[OCR] confirmImage: option.cardType =', option.cardType, 'docInfos[cardType] =', docInfos[option.cardType]);
|
|
36548
|
+
return false;
|
|
36549
|
+
}
|
|
36360
36550
|
// const base64Image = await blobToBase64(ocrOriginImg);
|
|
36361
36551
|
// console.log('confirmImage', base64Image);
|
|
36362
36552
|
const requestImg = yield encryptImageBase64(ocrOriginImg);
|
|
@@ -36483,6 +36673,8 @@ class OCRModule {
|
|
|
36483
36673
|
const item2 = res.fields.find(item => item.name === item1.name);
|
|
36484
36674
|
return item2 ? Object.assign(Object.assign({}, item1), item2) : item1;
|
|
36485
36675
|
}).concat(res.fields.filter(item2 => !ocrResultFilds.some(item1 => item1.name === item2.name)));
|
|
36676
|
+
// V9: 過濾掉重複/內部欄位
|
|
36677
|
+
ocrResultFilds = ocrResultFilds.filter(item => !OCR_FIELDS_TO_FILTER.includes(item.name));
|
|
36486
36678
|
delete docInfos[option.cardType];
|
|
36487
36679
|
return true;
|
|
36488
36680
|
} catch (error) {
|
|
@@ -36502,6 +36694,13 @@ class OCRModule {
|
|
|
36502
36694
|
} else {
|
|
36503
36695
|
ocrOriginImg = docInfos[cardType].ocrOriginImg;
|
|
36504
36696
|
}
|
|
36697
|
+
// V9: 檢查 ocrOriginImg 是否有效
|
|
36698
|
+
if (!ocrOriginImg) {
|
|
36699
|
+
console.warn('[OCR] ocrCancel: ocrOriginImg is null, skipping upload');
|
|
36700
|
+
yield this.ocrService.stop();
|
|
36701
|
+
docInfos[cardType].docId = '';
|
|
36702
|
+
return true;
|
|
36703
|
+
}
|
|
36505
36704
|
const requestImg = yield encryptImageBase64(ocrOriginImg);
|
|
36506
36705
|
yield this.ocrService.stop();
|
|
36507
36706
|
const report = yield this.ocrService.getReport();
|
|
@@ -36548,7 +36747,9 @@ class OCRModule {
|
|
|
36548
36747
|
}
|
|
36549
36748
|
}),
|
|
36550
36749
|
antiFraudStart: (points, setBorderType, setCardBorderColor, setBorderSuccess, scanAnimationContainer, successAnimationContainer, frameImage, frameText, faceMode) => __awaiter(this, void 0, void 0, function* () {
|
|
36551
|
-
|
|
36750
|
+
// setStage() 如果服務已初始化會自動觸發 reinit
|
|
36751
|
+
// 不需要再呼叫 init()
|
|
36752
|
+
yield this.antiFraudInstance.setStage(antiFraudStageList);
|
|
36552
36753
|
yield this.antiFraudInstance.setFrameSize(frameWidth, frameHeight);
|
|
36553
36754
|
yield this.antiFraudInstance.setMaskPosition(points.map(([x, y]) => [Number(x.toFixed(2)), Number(y.toFixed(2))]));
|
|
36554
36755
|
const newParams = {};
|
|
@@ -36556,7 +36757,6 @@ class OCRModule {
|
|
|
36556
36757
|
if (config.antiFraudIMetalTagValidCountTh !== false) newParams['iMetalTagValidCountTh'] = config.antiFraudIMetalTagValidCountTh;
|
|
36557
36758
|
newParams['timeoutSec'] = fraudTimeout;
|
|
36558
36759
|
yield this.antiFraudInstance.setParams(Object.assign(Object.assign({}, oldParams), newParams));
|
|
36559
|
-
yield this.antiFraudInstance.setStage(antiFraudStageList);
|
|
36560
36760
|
yield this.antiFraudInstance.startSession();
|
|
36561
36761
|
const twnidCardFront = EAuthMeCardClass$1.TWN_IDCard_Front;
|
|
36562
36762
|
docInfos[twnidCardFront] = {
|
|
@@ -36817,8 +37017,10 @@ class OCRModule {
|
|
|
36817
37017
|
yield (_u = this.ocrService) === null || _u === void 0 ? void 0 : _u.destroy();
|
|
36818
37018
|
yield (_v = this.antiFraudInstance) === null || _v === void 0 ? void 0 : _v.destroy();
|
|
36819
37019
|
}),
|
|
36820
|
-
getCardMatchROI:
|
|
36821
|
-
return yield this.antiFraudInstance.getCardMatchROI(
|
|
37020
|
+
getCardMatchROI: stage => __awaiter(this, void 0, void 0, function* () {
|
|
37021
|
+
return yield this.antiFraudInstance.getCardMatchROI(stage ? {
|
|
37022
|
+
stage
|
|
37023
|
+
} : undefined);
|
|
36822
37024
|
})
|
|
36823
37025
|
}), eventNameWrong$.pipe(tap(() => {
|
|
36824
37026
|
throw new AuthmeError(ErrorCode.EVENT_NAME_WRONG);
|
|
@@ -37335,8 +37537,8 @@ class AuthmeIdentityVerification extends AuthmeFunctionModule {
|
|
|
37335
37537
|
}
|
|
37336
37538
|
|
|
37337
37539
|
var name = "authme/sdk";
|
|
37338
|
-
var version$1 = "2.8.
|
|
37339
|
-
var date = "2026-
|
|
37540
|
+
var version$1 = "2.8.42";
|
|
37541
|
+
var date = "2026-02-03T09:36:05+0000";
|
|
37340
37542
|
var packageInfo = {
|
|
37341
37543
|
name: name,
|
|
37342
37544
|
version: version$1,
|