@authme/identity-verification 2.8.15 → 2.8.17

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/index.cjs CHANGED
@@ -33,6 +33,8 @@ require('core-js/modules/es.typed-array.set.js');
33
33
  require('core-js/modules/es.typed-array.sort.js');
34
34
  require('core-js/modules/es.typed-array.to-locale-string.js');
35
35
  require('core-js/modules/es.string.pad-start.js');
36
+ require('core-js/modules/es.string.match.js');
37
+ require('core-js/modules/es.string.starts-with.js');
36
38
  require('core-js/modules/es.regexp.constructor.js');
37
39
  require('core-js/modules/es.typed-array.uint8-array.js');
38
40
 
@@ -28132,7 +28134,12 @@ const popupView = arg => {
28132
28134
  domPopupViewContainer.style.borderRadius = `${uiThemeConfig.popupView.cornerRadius}px`;
28133
28135
  util.uiThemeText(domTitle, uiThemeConfig.titleOne);
28134
28136
  util.uiThemeText(domContent, uiThemeConfig.bodyTwo);
28135
- util.uiThemeSmallButton(domConfirm, uiThemeConfig.smallermMajorButton);
28137
+ console.log('deviceType', uiThemeConfig.deviceType);
28138
+ if (uiThemeConfig.deviceType === 'mobile') {
28139
+ util.uiThemeSmallButton(domConfirm, uiThemeConfig.smallermMajorButton);
28140
+ } else {
28141
+ util.uiThemeButton(domConfirm, uiThemeConfig.majorButton);
28142
+ }
28136
28143
  if (arg.cancel) {
28137
28144
  util.uiThemeSmallButton(domCancel, uiThemeConfig.smallerMinorButton);
28138
28145
  domCancel.innerHTML = arg.cancel;
@@ -30638,7 +30645,8 @@ const ocrResultModal = arg => {
30638
30645
  name,
30639
30646
  label,
30640
30647
  value,
30641
- placeHolder
30648
+ placeHolder,
30649
+ attribute
30642
30650
  }) {
30643
30651
  const domGroupItem = document.createElement('div');
30644
30652
  domGroupItem.classList.add('video-container__ocrResultModal-group-item');
@@ -30655,6 +30663,11 @@ const ocrResultModal = arg => {
30655
30663
  domInput.value = value;
30656
30664
  domError.innerText = `${label}格式不符`;
30657
30665
  domInput.placeholder = placeHolder;
30666
+ if (attribute) {
30667
+ Object.keys(attribute).forEach(key => {
30668
+ domInput.setAttribute(key, attribute[key]);
30669
+ });
30670
+ }
30658
30671
  domInput.addEventListener('input', () => {
30659
30672
  validateFields() ? btnConfirmStatus(false) : btnConfirmStatus(true);
30660
30673
  // btnConfirmStatus(Object.values(validateMap).some((i: any) => i.validate === false));
@@ -30728,6 +30741,15 @@ const ocrResultModal = arg => {
30728
30741
  return domGroupItem;
30729
30742
  }
30730
30743
  function formatDate(dateString, format) {
30744
+ // 檢查是否為民國年格式
30745
+ if (dateString.match(/^\d{3}\/\d{2}\/\d{2}$/)) {
30746
+ // 將民國年轉換為西元年
30747
+ const parts = dateString.split('/');
30748
+ const _year = parseInt(parts[0], 10) + 1911; // 民國年轉西元年
30749
+ const _month = parts[1].padStart(2, '0'); // 補 0 的月份
30750
+ const _day = parts[2].padStart(2, '0'); // 補 0 的日期
30751
+ dateString = `${_year}-${_month}-${_day}`;
30752
+ }
30731
30753
  // 確保日期格式為 "YYYY-MM-DD",避免瀏覽器解析錯誤
30732
30754
  const normalizedDateString = dateString.replace(/\//g, '-');
30733
30755
  // 解析日期字串
@@ -30736,19 +30758,105 @@ const ocrResultModal = arg => {
30736
30758
  throw new Error('Invalid date format');
30737
30759
  }
30738
30760
  // 取得年月日
30739
- const year = date.getFullYear().toString(); // 取得年份
30761
+ let year = date.getFullYear().toString(); // 取得年份
30740
30762
  const month = (date.getMonth() + 1).toString().padStart(2, '0'); // 補 0 的月份
30741
30763
  const day = date.getDate().toString().padStart(2, '0'); // 補 0 的日期
30764
+ // 依照格式轉換成民國年
30765
+ if (format === 'yyy/MM/dd') {
30766
+ year = (parseInt(year) - 1911).toString(); // 民國年
30767
+ if (year.length === 2) {
30768
+ year = '0' + year; // 補 0 的民國年
30769
+ }
30770
+ }
30742
30771
  // 替換格式
30743
- return format.replace(/YYYY|yyyy/g, year) // 年份 (YYYY 或 yyyy)
30772
+ return format.replace(/YYYY|yyyy|YYY|yyy/g, year) // 年份 (YYYY 或 yyyy)
30744
30773
  .replace(/MM|mm/g, month) // 月份 (MM)
30745
30774
  .replace(/dd/g, day); // 日期 (dd)
30746
30775
  }
30747
-
30748
- function isValidDateFormat(dateStr) {
30749
- const regex = /^\d{4}\/(0[1-9]|1[0-2])\/(0[1-9]|[12]\d|3[01])$/;
30776
+ // 驗證日期格式
30777
+ function isValidDateFormat(input, format) {
30778
+ let dateStr = input.value;
30779
+ if (!isNaN(Number(dateStr))) {
30780
+ let year = '';
30781
+ let month = '';
30782
+ let day = '';
30783
+ if (format === 'yyy/MM/dd') {
30784
+ if (dateStr.length === 7) {
30785
+ // 民國年格式
30786
+ year = dateStr.slice(0, 3);
30787
+ month = dateStr.slice(3, 5);
30788
+ day = dateStr.slice(5, 7);
30789
+ dateStr = `${year}/${month}/${day}`;
30790
+ }
30791
+ } else {
30792
+ if (dateStr.length === 8) {
30793
+ // 西元年格式
30794
+ year = dateStr.slice(0, 4);
30795
+ month = dateStr.slice(4, 6);
30796
+ day = dateStr.slice(6, 8);
30797
+ dateStr = `${year}/${month}/${day}`;
30798
+ }
30799
+ }
30800
+ }
30801
+ input.value = dateStr;
30802
+ let regex = /^\d{4}\/(0[1-9]|1[0-2])\/(0[1-9]|[12]\d|3[01])$/;
30803
+ if (format === 'yyy/MM/dd') {
30804
+ regex = /^\d{3}\/(0[1-9]|1[0-2])\/(0[1-9]|[12]\d|3[01])$/;
30805
+ }
30750
30806
  return regex.test(dateStr);
30751
30807
  }
30808
+ function isValidDate(dateStr, format) {
30809
+ if (format === 'yyy/MM/dd') {
30810
+ // 民國年格式轉換回西元年
30811
+ const _year2 = parseInt(dateStr.split('/')[0]) + 1911;
30812
+ dateStr = dateStr.replace(/^\d{3}/, _year2.toString());
30813
+ }
30814
+ // 先用正則檢查格式 yyyy/MM/dd
30815
+ const regex = /^\d{4}\/(0[1-9]|1[0-2])\/(0[1-9]|[12]\d|3[01])$/;
30816
+ if (!regex.test(dateStr)) return false;
30817
+ const [year, month, day] = dateStr.split('/').map(Number);
30818
+ const date = new Date(year, month - 1, day);
30819
+ // 檢查轉換後的日期是否一致(避免像 2023/02/30 自動變成 2023/03/02)
30820
+ return date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day;
30821
+ }
30822
+ function isValidDateBeforeToday(dateStr, format) {
30823
+ if (format === 'yyy/MM/dd') {
30824
+ // 民國年格式轉換回西元年
30825
+ const year = parseInt(dateStr.split('/')[0]) + 1911;
30826
+ dateStr = dateStr.replace(/^\d{3}/, year.toString());
30827
+ }
30828
+ // 判斷是不是超過今天
30829
+ const today = new Date();
30830
+ // 將時間設為 23:59:59
30831
+ today.setHours(23, 59, 59, 999);
30832
+ // 將日期字串轉換為 Date 物件
30833
+ const inputDate = new Date(dateStr.replace(/\//g, '-')); // 將日期字串轉換為 Date 物件
30834
+ if (inputDate > today) {
30835
+ return false; // 如果日期超過今天,返回 false
30836
+ }
30837
+
30838
+ return true; // 如果日期在今天之前或等於今天,返回 true
30839
+ }
30840
+
30841
+ function parseRegex(input, forceUnicode = false) {
30842
+ try {
30843
+ let pattern = input;
30844
+ let flags = '';
30845
+ if (input.startsWith('/') && input.lastIndexOf('/') > 0) {
30846
+ const lastSlash = input.lastIndexOf('/');
30847
+ pattern = input.slice(1, lastSlash);
30848
+ flags = input.slice(lastSlash + 1);
30849
+ }
30850
+ // 如果有需要強制 Unicode 模式
30851
+ if (forceUnicode && !flags.includes('u')) {
30852
+ flags += 'u';
30853
+ }
30854
+ return new RegExp(pattern, flags);
30855
+ } catch (e) {
30856
+ console.warn('⚠️ 無效的 regex:', input, e);
30857
+ return null;
30858
+ }
30859
+ }
30752
30860
  function checkModifiedData() {
30753
30861
  const items = domModal.querySelectorAll('.video-container__ocrResultModal-group-item');
30754
30862
  items.forEach(item => {
@@ -30813,7 +30921,7 @@ const ocrResultModal = arg => {
30813
30921
  }
30814
30922
  }
30815
30923
  } else if (input && item.dateFormat) {
30816
- if (isValidDateFormat(input.value)) {
30924
+ if (isValidDateFormat(input, item.dateFormat) && isValidDateBeforeToday(input.value, item.dateFormat) && isValidDate(input.value, item.dateFormat)) {
30817
30925
  // if (item.dateFormat.test(input.value)) {
30818
30926
  item.validate = true;
30819
30927
  (_g = domItem.querySelector('.video-container__ocrResultModal-input')) === null || _g === void 0 ? void 0 : _g.classList.remove('error');
@@ -30888,11 +30996,14 @@ const ocrResultModal = arg => {
30888
30996
  return ((_a = a.order) !== null && _a !== void 0 ? _a : Infinity) - ((_b = b.order) !== null && _b !== void 0 ? _b : Infinity);
30889
30997
  });
30890
30998
  arg.items.forEach(item => {
30999
+ var _a;
31000
+ const needsUnicode = ((_a = item.regex) === null || _a === void 0 ? void 0 : _a.includes('\\u')) || item.name === 'name';
31001
+ const parsed = parseRegex(item.regex, needsUnicode);
30891
31002
  // init validateMap
30892
31003
  if (item.regex) {
30893
31004
  validateMap[item.name] = {
30894
31005
  validate: false,
30895
- regex: new RegExp(item.regex.slice(1, -1))
31006
+ regex: parsed
30896
31007
  };
30897
31008
  } else if (item.selections) {
30898
31009
  validateMap[item.name] = {
@@ -30902,7 +31013,7 @@ const ocrResultModal = arg => {
30902
31013
  } else if (item.dateFormat) {
30903
31014
  validateMap[item.name] = {
30904
31015
  validate: false,
30905
- dateFormat: new RegExp(item.dateFormat.slice(1, -1))
31016
+ dateFormat: item.dateFormat
30906
31017
  };
30907
31018
  }
30908
31019
  // init UI
@@ -30920,7 +31031,23 @@ const ocrResultModal = arg => {
30920
31031
  name: item.name,
30921
31032
  label: item.name === 'issueReason' ? translateService.translate('sdk.ocr.verify.result.issueType') : translateService.translate(`sdk.ocr.verify.result.${item.name}`),
30922
31033
  value: item.dateFormat ? formatDate(item.value, item.dateFormat) : item.value,
30923
- placeHolder: item.dateFormat ? item.dateFormat.toUpperCase() : ''
31034
+ placeHolder: (() => {
31035
+ if (item.name === 'name') {
31036
+ return translateService.translate('請輸入姓名');
31037
+ }
31038
+ if (item.name === 'idNumber') {
31039
+ return translateService.translate('請輸入身份證字號');
31040
+ }
31041
+ return item.dateFormat ? item.dateFormat.toUpperCase() : '';
31042
+ })(),
31043
+ attribute: (() => {
31044
+ if (item.dateFormat) {
31045
+ return {
31046
+ inputmode: 'numeric'
31047
+ };
31048
+ }
31049
+ return {};
31050
+ })()
30924
31051
  });
30925
31052
  domBody.appendChild(domGroupItem);
30926
31053
  }
@@ -31875,7 +32002,7 @@ function startOCR(config) {
31875
32002
  title: `${'請確認'}${translateService.translate(config.ocrConfig.type ? `sdk.ocr.verify.result.title.${keyMapping[config.ocrConfig.type]}` : '')}`,
31876
32003
  subtitle: '若資料有錯誤,可點擊內容進行修改',
31877
32004
  items: result.ocrResultFilds,
31878
- confirmText: translateService.translate('sdk.general.confirm'),
32005
+ confirmText: translateService.translate('sdk.general.accept'),
31879
32006
  hintText: '逾時將自動轉跳至選單頁並無法保存此次紀錄',
31880
32007
  countDown1Text: '請在',
31881
32008
  countDown2Text: '內完成確認',
@@ -31968,6 +32095,10 @@ function startOCR(config) {
31968
32095
  return rxjs.merge(flow$, cancel$, detectScreenResizeError$, timeout$).pipe(rxjs.take(1));
31969
32096
  }
31970
32097
  rxjs.fromEvent(window, 'offline').pipe(rxjs.switchMap(() => util.asyncOnLineShowErrorMessage(translateService.translate('sdk.general.error.alert.offline'), translateService.translate('sdk.general.error.retry'), false)), rxjs.takeUntil(unsubscribe$)).subscribe();
32098
+ rxjs.fromEvent(window, 'online').pipe(rxjs.switchMap(() => {
32099
+ util.hideErrorMessage(); // 執行副作用
32100
+ return rxjs.EMPTY; // 回傳一個空的 Observable
32101
+ }), rxjs.takeUntil(unsubscribe$)).subscribe();
31971
32102
  // resize
31972
32103
  rxjs.fromEvent(window, 'resize').pipe(rxjs.switchMapTo(util.getCanvasSize(uiComponentBasic.video)), rxjs.switchMap(canvasSizeInfo => config.setFrameSize(canvasSizeInfo.canvasWidth, canvasSizeInfo.canvasHeight, getCardBorderPoint())), rxjs.tap(() => {
31973
32104
  uiComponentOCR.scanAnimationContainer.style.width = `${cardSizeInfo.width + scanPadding}px`;
@@ -33171,6 +33302,14 @@ class LivenessModule {
33171
33302
  // )
33172
33303
  // );
33173
33304
  yield handleUpload(id, frameList, resultList, meta, config, shouldEncrypt, encryptDataBase64);
33305
+ // upload finish view
33306
+ const successView = util.uploadModal({
33307
+ type: 'success',
33308
+ titleSuccess: core.getTranslateInstance().translate('sdk.general.successUpload')
33309
+ });
33310
+ // setTimeout(() => {
33311
+ // successView?.removeModal();
33312
+ // }, 900);
33174
33313
  const result = yield this.getResult(id);
33175
33314
  return result.isPass;
33176
33315
  }),
@@ -34908,6 +35047,14 @@ class OCRModule {
34908
35047
  // needFraudOption ? this.fraudResult : null
34909
35048
  // )
34910
35049
  );
35050
+ // upload finish view
35051
+ const successView = util.uploadModal({
35052
+ type: 'success',
35053
+ titleSuccess: translateService.translate('sdk.general.successUpload')
35054
+ });
35055
+ setTimeout(() => {
35056
+ successView === null || successView === void 0 ? void 0 : successView.removeModal();
35057
+ }, 300);
34911
35058
  // ocrResultFilds = res.fields.concat(ocrResultFilds);
34912
35059
  ocrResultFilds = ocrResultFilds.map(item1 => {
34913
35060
  const item2 = res.fields.find(item => item.name === item1.name);
@@ -35765,8 +35912,8 @@ class AuthmeIdentityVerification extends engine.AuthmeFunctionModule {
35765
35912
  }
35766
35913
 
35767
35914
  var name = "authme/sdk";
35768
- var version$1 = "2.8.15";
35769
- var date = "2025-05-15T12:22:35+0000";
35915
+ var version$1 = "2.8.17";
35916
+ var date = "2025-05-22T09:38:34+0000";
35770
35917
  var packageInfo = {
35771
35918
  name: name,
35772
35919
  version: version$1,
package/index.js CHANGED
@@ -7,7 +7,7 @@ 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, uiThemeSmallButton, requestCamera, showElement, asyncOnLineShowErrorMessage, getCanvasSize, startSpinner, stopSpinner, themeConfigDefault, fontWeight, hideElement, dataURItoBlob, showErrorMessage, checkOnlineStatus, dropMenu, UintArrayToBlob, isIphone14proOrProMax, cropByRatio, switchCamera, asyncShowPopup, retryPromiseWithCondition, asyncShowErrorMessage, hideErrorMessage, uploadModal, backgroundRequest, debugTools, showErrorMessageEventName, RUN_FUNCTION_NAME, STORAGE_KEY, splitResult, DEVICE_TYPE, combineResult, startLoadingSDK, stopLoadingSDK } from '@authme/util';
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, uiThemeSmallButton, requestCamera, showElement, asyncOnLineShowErrorMessage, getCanvasSize, startSpinner, stopSpinner, themeConfigDefault, fontWeight, hideElement, dataURItoBlob, showErrorMessage, checkOnlineStatus, dropMenu, hideErrorMessage, UintArrayToBlob, isIphone14proOrProMax, cropByRatio, switchCamera, asyncShowPopup, retryPromiseWithCondition, asyncShowErrorMessage, uploadModal, backgroundRequest, debugTools, showErrorMessageEventName, RUN_FUNCTION_NAME, STORAGE_KEY, splitResult, DEVICE_TYPE, combineResult, startLoadingSDK, stopLoadingSDK } from '@authme/util';
11
11
  import { mergeMap, animationFrames, filter, tap, map, from, catchError, EMPTY, of, merge, fromEvent, concatAll, takeUntil, Subject, defer, throttleTime, switchMap, raceWith, concatMap, throwError, finalize, timer, Observable, take, interval, mapTo, firstValueFrom, shareReplay, switchMapTo, 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';
@@ -29,6 +29,8 @@ import 'core-js/modules/es.typed-array.set.js';
29
29
  import 'core-js/modules/es.typed-array.sort.js';
30
30
  import 'core-js/modules/es.typed-array.to-locale-string.js';
31
31
  import 'core-js/modules/es.string.pad-start.js';
32
+ import 'core-js/modules/es.string.match.js';
33
+ import 'core-js/modules/es.string.starts-with.js';
32
34
  import 'core-js/modules/es.regexp.constructor.js';
33
35
  import 'core-js/modules/es.typed-array.uint8-array.js';
34
36
 
@@ -28124,7 +28126,12 @@ const popupView = arg => {
28124
28126
  domPopupViewContainer.style.borderRadius = `${uiThemeConfig.popupView.cornerRadius}px`;
28125
28127
  uiThemeText(domTitle, uiThemeConfig.titleOne);
28126
28128
  uiThemeText(domContent, uiThemeConfig.bodyTwo);
28127
- uiThemeSmallButton(domConfirm, uiThemeConfig.smallermMajorButton);
28129
+ console.log('deviceType', uiThemeConfig.deviceType);
28130
+ if (uiThemeConfig.deviceType === 'mobile') {
28131
+ uiThemeSmallButton(domConfirm, uiThemeConfig.smallermMajorButton);
28132
+ } else {
28133
+ uiThemeButton(domConfirm, uiThemeConfig.majorButton);
28134
+ }
28128
28135
  if (arg.cancel) {
28129
28136
  uiThemeSmallButton(domCancel, uiThemeConfig.smallerMinorButton);
28130
28137
  domCancel.innerHTML = arg.cancel;
@@ -30630,7 +30637,8 @@ const ocrResultModal = arg => {
30630
30637
  name,
30631
30638
  label,
30632
30639
  value,
30633
- placeHolder
30640
+ placeHolder,
30641
+ attribute
30634
30642
  }) {
30635
30643
  const domGroupItem = document.createElement('div');
30636
30644
  domGroupItem.classList.add('video-container__ocrResultModal-group-item');
@@ -30647,6 +30655,11 @@ const ocrResultModal = arg => {
30647
30655
  domInput.value = value;
30648
30656
  domError.innerText = `${label}格式不符`;
30649
30657
  domInput.placeholder = placeHolder;
30658
+ if (attribute) {
30659
+ Object.keys(attribute).forEach(key => {
30660
+ domInput.setAttribute(key, attribute[key]);
30661
+ });
30662
+ }
30650
30663
  domInput.addEventListener('input', () => {
30651
30664
  validateFields() ? btnConfirmStatus(false) : btnConfirmStatus(true);
30652
30665
  // btnConfirmStatus(Object.values(validateMap).some((i: any) => i.validate === false));
@@ -30720,6 +30733,15 @@ const ocrResultModal = arg => {
30720
30733
  return domGroupItem;
30721
30734
  }
30722
30735
  function formatDate(dateString, format) {
30736
+ // 檢查是否為民國年格式
30737
+ if (dateString.match(/^\d{3}\/\d{2}\/\d{2}$/)) {
30738
+ // 將民國年轉換為西元年
30739
+ const parts = dateString.split('/');
30740
+ const _year = parseInt(parts[0], 10) + 1911; // 民國年轉西元年
30741
+ const _month = parts[1].padStart(2, '0'); // 補 0 的月份
30742
+ const _day = parts[2].padStart(2, '0'); // 補 0 的日期
30743
+ dateString = `${_year}-${_month}-${_day}`;
30744
+ }
30723
30745
  // 確保日期格式為 "YYYY-MM-DD",避免瀏覽器解析錯誤
30724
30746
  const normalizedDateString = dateString.replace(/\//g, '-');
30725
30747
  // 解析日期字串
@@ -30728,19 +30750,105 @@ const ocrResultModal = arg => {
30728
30750
  throw new Error('Invalid date format');
30729
30751
  }
30730
30752
  // 取得年月日
30731
- const year = date.getFullYear().toString(); // 取得年份
30753
+ let year = date.getFullYear().toString(); // 取得年份
30732
30754
  const month = (date.getMonth() + 1).toString().padStart(2, '0'); // 補 0 的月份
30733
30755
  const day = date.getDate().toString().padStart(2, '0'); // 補 0 的日期
30756
+ // 依照格式轉換成民國年
30757
+ if (format === 'yyy/MM/dd') {
30758
+ year = (parseInt(year) - 1911).toString(); // 民國年
30759
+ if (year.length === 2) {
30760
+ year = '0' + year; // 補 0 的民國年
30761
+ }
30762
+ }
30734
30763
  // 替換格式
30735
- return format.replace(/YYYY|yyyy/g, year) // 年份 (YYYY 或 yyyy)
30764
+ return format.replace(/YYYY|yyyy|YYY|yyy/g, year) // 年份 (YYYY 或 yyyy)
30736
30765
  .replace(/MM|mm/g, month) // 月份 (MM)
30737
30766
  .replace(/dd/g, day); // 日期 (dd)
30738
30767
  }
30739
-
30740
- function isValidDateFormat(dateStr) {
30741
- const regex = /^\d{4}\/(0[1-9]|1[0-2])\/(0[1-9]|[12]\d|3[01])$/;
30768
+ // 驗證日期格式
30769
+ function isValidDateFormat(input, format) {
30770
+ let dateStr = input.value;
30771
+ if (!isNaN(Number(dateStr))) {
30772
+ let year = '';
30773
+ let month = '';
30774
+ let day = '';
30775
+ if (format === 'yyy/MM/dd') {
30776
+ if (dateStr.length === 7) {
30777
+ // 民國年格式
30778
+ year = dateStr.slice(0, 3);
30779
+ month = dateStr.slice(3, 5);
30780
+ day = dateStr.slice(5, 7);
30781
+ dateStr = `${year}/${month}/${day}`;
30782
+ }
30783
+ } else {
30784
+ if (dateStr.length === 8) {
30785
+ // 西元年格式
30786
+ year = dateStr.slice(0, 4);
30787
+ month = dateStr.slice(4, 6);
30788
+ day = dateStr.slice(6, 8);
30789
+ dateStr = `${year}/${month}/${day}`;
30790
+ }
30791
+ }
30792
+ }
30793
+ input.value = dateStr;
30794
+ let regex = /^\d{4}\/(0[1-9]|1[0-2])\/(0[1-9]|[12]\d|3[01])$/;
30795
+ if (format === 'yyy/MM/dd') {
30796
+ regex = /^\d{3}\/(0[1-9]|1[0-2])\/(0[1-9]|[12]\d|3[01])$/;
30797
+ }
30742
30798
  return regex.test(dateStr);
30743
30799
  }
30800
+ function isValidDate(dateStr, format) {
30801
+ if (format === 'yyy/MM/dd') {
30802
+ // 民國年格式轉換回西元年
30803
+ const _year2 = parseInt(dateStr.split('/')[0]) + 1911;
30804
+ dateStr = dateStr.replace(/^\d{3}/, _year2.toString());
30805
+ }
30806
+ // 先用正則檢查格式 yyyy/MM/dd
30807
+ const regex = /^\d{4}\/(0[1-9]|1[0-2])\/(0[1-9]|[12]\d|3[01])$/;
30808
+ if (!regex.test(dateStr)) return false;
30809
+ const [year, month, day] = dateStr.split('/').map(Number);
30810
+ const date = new Date(year, month - 1, day);
30811
+ // 檢查轉換後的日期是否一致(避免像 2023/02/30 自動變成 2023/03/02)
30812
+ return date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day;
30813
+ }
30814
+ function isValidDateBeforeToday(dateStr, format) {
30815
+ if (format === 'yyy/MM/dd') {
30816
+ // 民國年格式轉換回西元年
30817
+ const year = parseInt(dateStr.split('/')[0]) + 1911;
30818
+ dateStr = dateStr.replace(/^\d{3}/, year.toString());
30819
+ }
30820
+ // 判斷是不是超過今天
30821
+ const today = new Date();
30822
+ // 將時間設為 23:59:59
30823
+ today.setHours(23, 59, 59, 999);
30824
+ // 將日期字串轉換為 Date 物件
30825
+ const inputDate = new Date(dateStr.replace(/\//g, '-')); // 將日期字串轉換為 Date 物件
30826
+ if (inputDate > today) {
30827
+ return false; // 如果日期超過今天,返回 false
30828
+ }
30829
+
30830
+ return true; // 如果日期在今天之前或等於今天,返回 true
30831
+ }
30832
+
30833
+ function parseRegex(input, forceUnicode = false) {
30834
+ try {
30835
+ let pattern = input;
30836
+ let flags = '';
30837
+ if (input.startsWith('/') && input.lastIndexOf('/') > 0) {
30838
+ const lastSlash = input.lastIndexOf('/');
30839
+ pattern = input.slice(1, lastSlash);
30840
+ flags = input.slice(lastSlash + 1);
30841
+ }
30842
+ // 如果有需要強制 Unicode 模式
30843
+ if (forceUnicode && !flags.includes('u')) {
30844
+ flags += 'u';
30845
+ }
30846
+ return new RegExp(pattern, flags);
30847
+ } catch (e) {
30848
+ console.warn('⚠️ 無效的 regex:', input, e);
30849
+ return null;
30850
+ }
30851
+ }
30744
30852
  function checkModifiedData() {
30745
30853
  const items = domModal.querySelectorAll('.video-container__ocrResultModal-group-item');
30746
30854
  items.forEach(item => {
@@ -30805,7 +30913,7 @@ const ocrResultModal = arg => {
30805
30913
  }
30806
30914
  }
30807
30915
  } else if (input && item.dateFormat) {
30808
- if (isValidDateFormat(input.value)) {
30916
+ if (isValidDateFormat(input, item.dateFormat) && isValidDateBeforeToday(input.value, item.dateFormat) && isValidDate(input.value, item.dateFormat)) {
30809
30917
  // if (item.dateFormat.test(input.value)) {
30810
30918
  item.validate = true;
30811
30919
  (_g = domItem.querySelector('.video-container__ocrResultModal-input')) === null || _g === void 0 ? void 0 : _g.classList.remove('error');
@@ -30880,11 +30988,14 @@ const ocrResultModal = arg => {
30880
30988
  return ((_a = a.order) !== null && _a !== void 0 ? _a : Infinity) - ((_b = b.order) !== null && _b !== void 0 ? _b : Infinity);
30881
30989
  });
30882
30990
  arg.items.forEach(item => {
30991
+ var _a;
30992
+ const needsUnicode = ((_a = item.regex) === null || _a === void 0 ? void 0 : _a.includes('\\u')) || item.name === 'name';
30993
+ const parsed = parseRegex(item.regex, needsUnicode);
30883
30994
  // init validateMap
30884
30995
  if (item.regex) {
30885
30996
  validateMap[item.name] = {
30886
30997
  validate: false,
30887
- regex: new RegExp(item.regex.slice(1, -1))
30998
+ regex: parsed
30888
30999
  };
30889
31000
  } else if (item.selections) {
30890
31001
  validateMap[item.name] = {
@@ -30894,7 +31005,7 @@ const ocrResultModal = arg => {
30894
31005
  } else if (item.dateFormat) {
30895
31006
  validateMap[item.name] = {
30896
31007
  validate: false,
30897
- dateFormat: new RegExp(item.dateFormat.slice(1, -1))
31008
+ dateFormat: item.dateFormat
30898
31009
  };
30899
31010
  }
30900
31011
  // init UI
@@ -30912,7 +31023,23 @@ const ocrResultModal = arg => {
30912
31023
  name: item.name,
30913
31024
  label: item.name === 'issueReason' ? translateService.translate('sdk.ocr.verify.result.issueType') : translateService.translate(`sdk.ocr.verify.result.${item.name}`),
30914
31025
  value: item.dateFormat ? formatDate(item.value, item.dateFormat) : item.value,
30915
- placeHolder: item.dateFormat ? item.dateFormat.toUpperCase() : ''
31026
+ placeHolder: (() => {
31027
+ if (item.name === 'name') {
31028
+ return translateService.translate('請輸入姓名');
31029
+ }
31030
+ if (item.name === 'idNumber') {
31031
+ return translateService.translate('請輸入身份證字號');
31032
+ }
31033
+ return item.dateFormat ? item.dateFormat.toUpperCase() : '';
31034
+ })(),
31035
+ attribute: (() => {
31036
+ if (item.dateFormat) {
31037
+ return {
31038
+ inputmode: 'numeric'
31039
+ };
31040
+ }
31041
+ return {};
31042
+ })()
30916
31043
  });
30917
31044
  domBody.appendChild(domGroupItem);
30918
31045
  }
@@ -31867,7 +31994,7 @@ function startOCR(config) {
31867
31994
  title: `${'請確認'}${translateService.translate(config.ocrConfig.type ? `sdk.ocr.verify.result.title.${keyMapping[config.ocrConfig.type]}` : '')}`,
31868
31995
  subtitle: '若資料有錯誤,可點擊內容進行修改',
31869
31996
  items: result.ocrResultFilds,
31870
- confirmText: translateService.translate('sdk.general.confirm'),
31997
+ confirmText: translateService.translate('sdk.general.accept'),
31871
31998
  hintText: '逾時將自動轉跳至選單頁並無法保存此次紀錄',
31872
31999
  countDown1Text: '請在',
31873
32000
  countDown2Text: '內完成確認',
@@ -31960,6 +32087,10 @@ function startOCR(config) {
31960
32087
  return merge(flow$, cancel$, detectScreenResizeError$, timeout$).pipe(take(1));
31961
32088
  }
31962
32089
  fromEvent(window, 'offline').pipe(switchMap(() => asyncOnLineShowErrorMessage(translateService.translate('sdk.general.error.alert.offline'), translateService.translate('sdk.general.error.retry'), false)), takeUntil(unsubscribe$)).subscribe();
32090
+ fromEvent(window, 'online').pipe(switchMap(() => {
32091
+ hideErrorMessage(); // 執行副作用
32092
+ return EMPTY; // 回傳一個空的 Observable
32093
+ }), takeUntil(unsubscribe$)).subscribe();
31963
32094
  // resize
31964
32095
  fromEvent(window, 'resize').pipe(switchMapTo(getCanvasSize(uiComponentBasic.video)), switchMap(canvasSizeInfo => config.setFrameSize(canvasSizeInfo.canvasWidth, canvasSizeInfo.canvasHeight, getCardBorderPoint())), tap(() => {
31965
32096
  uiComponentOCR.scanAnimationContainer.style.width = `${cardSizeInfo.width + scanPadding}px`;
@@ -33163,6 +33294,14 @@ class LivenessModule {
33163
33294
  // )
33164
33295
  // );
33165
33296
  yield handleUpload(id, frameList, resultList, meta, config, shouldEncrypt, encryptDataBase64);
33297
+ // upload finish view
33298
+ const successView = uploadModal({
33299
+ type: 'success',
33300
+ titleSuccess: getTranslateInstance().translate('sdk.general.successUpload')
33301
+ });
33302
+ // setTimeout(() => {
33303
+ // successView?.removeModal();
33304
+ // }, 900);
33166
33305
  const result = yield this.getResult(id);
33167
33306
  return result.isPass;
33168
33307
  }),
@@ -34900,6 +35039,14 @@ class OCRModule {
34900
35039
  // needFraudOption ? this.fraudResult : null
34901
35040
  // )
34902
35041
  );
35042
+ // upload finish view
35043
+ const successView = uploadModal({
35044
+ type: 'success',
35045
+ titleSuccess: translateService.translate('sdk.general.successUpload')
35046
+ });
35047
+ setTimeout(() => {
35048
+ successView === null || successView === void 0 ? void 0 : successView.removeModal();
35049
+ }, 300);
34903
35050
  // ocrResultFilds = res.fields.concat(ocrResultFilds);
34904
35051
  ocrResultFilds = ocrResultFilds.map(item1 => {
34905
35052
  const item2 = res.fields.find(item => item.name === item1.name);
@@ -35757,8 +35904,8 @@ class AuthmeIdentityVerification extends AuthmeFunctionModule {
35757
35904
  }
35758
35905
 
35759
35906
  var name = "authme/sdk";
35760
- var version$1 = "2.8.15";
35761
- var date = "2025-05-15T12:22:35+0000";
35907
+ var version$1 = "2.8.17";
35908
+ var date = "2025-05-22T09:38:34+0000";
35762
35909
  var packageInfo = {
35763
35910
  name: name,
35764
35911
  version: version$1,
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@authme/identity-verification",
3
- "version": "2.8.15",
3
+ "version": "2.8.17",
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.15",
9
- "@authme/engine": "2.8.15",
10
- "@authme/id-recognition": "2.8.15",
11
- "@authme/liveness": "2.8.15",
12
- "@authme/util": "2.8.15"
8
+ "@authme/core": "2.8.17",
9
+ "@authme/engine": "2.8.17",
10
+ "@authme/id-recognition": "2.8.17",
11
+ "@authme/liveness": "2.8.17",
12
+ "@authme/util": "2.8.17"
13
13
  },
14
14
  "module": "./index.js",
15
15
  "main": "./index.cjs",