pipwave-ekyc-uikit 3.1.0 → 3.1.1

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/dist/pw-bundle.js CHANGED
@@ -4000,7 +4000,7 @@ eval("\nvar __awaiter = (this && this.__awaiter) || function (thisArg, _argument
4000
4000
  /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
4001
4001
 
4002
4002
  "use strict";
4003
- eval("\nvar __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n};\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nconst card_detection_1 = __importDefault(__webpack_require__(/*! ./card-detection */ \"./src/plugins/card-detection.ts\"));\nconst translations_1 = __webpack_require__(/*! ../translations */ \"./src/translations/index.ts\");\nconst deviceDetection_1 = __webpack_require__(/*! ../helper/deviceDetection */ \"./src/helper/deviceDetection.ts\");\nconst camera_1 = __webpack_require__(/*! ../models/camera */ \"./src/models/camera.ts\");\nconst camera_instruction_1 = __webpack_require__(/*! ../constant/camera-instruction */ \"./src/constant/camera-instruction.ts\");\nconst baseDialog_1 = __importDefault(__webpack_require__(/*! ../components/baseDialog */ \"./src/components/baseDialog.ts\"));\nconst components_1 = __webpack_require__(/*! ../components/components */ \"./src/components/components.ts\");\nconst navigationBar_1 = __importDefault(__webpack_require__(/*! ../components/navigationBar */ \"./src/components/navigationBar.ts\"));\nconst cameraViewport_1 = __importDefault(__webpack_require__(/*! ../components/cameraViewport */ \"./src/components/cameraViewport.ts\"));\nconst footer_1 = __importDefault(__webpack_require__(/*! ../components/footer */ \"./src/components/footer.ts\"));\nconst MainPage_1 = __webpack_require__(/*! ../classes/MainPage */ \"./src/classes/MainPage.ts\");\nconst imagesHelper_1 = __webpack_require__(/*! ../helper/imagesHelper */ \"./src/helper/imagesHelper.ts\");\nclass CardCamera {\n constructor(cameraId, cardType, mainScreenId = \"\") {\n this.cameraTimeLimit = 90000;\n this.holdRafId = null;\n this.hasAutoCaptured = false;\n this.isFramed = false;\n this.holdProgress = 0;\n this.lastHoldTick = 0;\n this.tickTimer = null;\n this._onWindowResize = () => { this.resizeElement(); };\n this._onBeforeUnload = () => { this._cleanup(); };\n this.videoElement = null;\n this.canvasElement = null;\n this.overlayElement = null;\n this.titleElement = null;\n this.footerElement = null;\n this.cameraId = cameraId;\n this.cardType = cardType;\n this.mainScreenId = mainScreenId;\n }\n isMobile() {\n // Use extracted helper function\n return (0, deviceDetection_1.isMobileDevice)();\n }\n captureImage() {\n var _a, _b, _c, _d, _e, _f, _g;\n return __awaiter(this, void 0, void 0, function* () {\n const bitmap = this.videoElement;\n // 1. full image\n let canvas = document.createElement(\"canvas\");\n let context = canvas.getContext(\"2d\");\n canvas.width = bitmap.videoWidth;\n canvas.height = bitmap.videoHeight;\n context.drawImage(bitmap, 0, 0, bitmap.videoWidth, bitmap.videoHeight);\n // 2. cropped image — region corresponds exactly to what the overlay shows\n let cropCanvas = document.createElement(\"canvas\");\n let cropContext = cropCanvas.getContext(\"2d\");\n cropCanvas.width = bitmap.videoWidth;\n cropCanvas.height = bitmap.videoHeight;\n cropContext.drawImage(bitmap, 0, 0, bitmap.videoWidth, bitmap.videoHeight);\n // Scale factor: stream pixels per display pixel\n const scaleX = bitmap.videoWidth / ((_b = (_a = this.videoElement) === null || _a === void 0 ? void 0 : _a.clientWidth) !== null && _b !== void 0 ? _b : bitmap.videoWidth);\n const scaleY = bitmap.videoHeight / ((_d = (_c = this.videoElement) === null || _c === void 0 ? void 0 : _c.clientHeight) !== null && _d !== void 0 ? _d : bitmap.videoHeight);\n // Use overlay's actual on-screen position as the crop region\n const overlayRect = (_e = this.overlayElement) === null || _e === void 0 ? void 0 : _e.getBoundingClientRect();\n const videoRect = (_f = this.videoElement) === null || _f === void 0 ? void 0 : _f.getBoundingClientRect();\n let cropX, cropY, cropW, cropH;\n if (overlayRect && videoRect) {\n cropX = Math.round((overlayRect.left - videoRect.left) * scaleX);\n cropY = Math.round((overlayRect.top - videoRect.top) * scaleY);\n cropW = Math.round(overlayRect.width * scaleX);\n cropH = Math.round(overlayRect.height * scaleY);\n }\n else {\n // Fallback: derive from calculateHeightNWidth + manual scale\n const { width, height } = this.cardDetectionSdk.calculateHeightNWidth(bitmap.videoWidth, bitmap.videoHeight);\n cropW = Math.round(width * scaleX);\n cropH = Math.round(height * scaleY);\n cropX = Math.round((bitmap.videoWidth - cropW) / 2);\n cropY = Math.round((bitmap.videoHeight - cropH) / 2);\n }\n // Clamp to stream bounds\n cropX = Math.max(0, cropX);\n cropY = Math.max(0, cropY);\n cropW = Math.min(cropW, bitmap.videoWidth - cropX);\n cropH = Math.min(cropH, bitmap.videoHeight - cropY);\n // crop the selected part image (viewport frame region)\n const imgData = cropContext.getImageData(cropX, cropY, cropW, cropH);\n cropCanvas.width = cropW;\n cropCanvas.height = cropH;\n cropContext.putImageData(imgData, 0, 0);\n // Determine card type prefix for file naming\n const cardTypePrefix = this.cardType === camera_1.CARD_TYPES.ID ? 'id-front' :\n this.cardType === camera_1.CARD_TYPES.ID_BACK ? 'id-back' :\n this.cardType === camera_1.CARD_TYPES.LICENSE ? 'license-front' :\n this.cardType === camera_1.CARD_TYPES.LICENSE_BACK ? 'license-back' :\n this.cardType === camera_1.CARD_TYPES.PASSPORT ? 'passport' :\n this.cardType === camera_1.CARD_TYPES.CC ? 'cc-front' : 'cc-back';\n // TEMP: Download BEFORE resize for comparison (only if debug enabled)\n if (MainPage_1.enableImageDebug) {\n const beforeResizeFullCanvas = canvas.cloneNode();\n const beforeResizeFullCtx = beforeResizeFullCanvas.getContext(\"2d\");\n beforeResizeFullCanvas.width = canvas.width;\n beforeResizeFullCanvas.height = canvas.height;\n beforeResizeFullCtx.drawImage(canvas, 0, 0);\n const beforeResizeCropCanvas = cropCanvas.cloneNode();\n const beforeResizeCropCtx = beforeResizeCropCanvas.getContext(\"2d\");\n beforeResizeCropCanvas.width = cropCanvas.width;\n beforeResizeCropCanvas.height = cropCanvas.height;\n beforeResizeCropCtx.drawImage(cropCanvas, 0, 0);\n console.log(`[DEBUG] ${cardTypePrefix} BEFORE resize - Full: ${beforeResizeFullCanvas.width}x${beforeResizeFullCanvas.height}, Cropped: ${beforeResizeCropCanvas.width}x${beforeResizeCropCanvas.height}`);\n this.downloadImage(beforeResizeFullCanvas.toDataURL(\"image/jpeg\"), `${cardTypePrefix}-full-BEFORE-${beforeResizeFullCanvas.width}x${beforeResizeFullCanvas.height}.jpg`);\n this.downloadImage(beforeResizeCropCanvas.toDataURL(\"image/jpeg\"), `${cardTypePrefix}-cropped-BEFORE-${beforeResizeCropCanvas.width}x${beforeResizeCropCanvas.height}.jpg`);\n }\n // 3. resize the canvas\n try {\n canvas = (0, imagesHelper_1.resizeCanvas)(canvas, imagesHelper_1.maxFullImage.width, imagesHelper_1.maxFullImage.height);\n cropCanvas = (0, imagesHelper_1.resizeCanvas)(cropCanvas, imagesHelper_1.maxCroppedImage.width, imagesHelper_1.maxCroppedImage.height);\n }\n catch (err) {\n console.error(\"doc resize image error\", err);\n }\n // 4. construct payload - convert to desired file format\n const { width: displayWidth = \"\", height: displayHeight = \"\" } = ((_g = this.overlayElement) === null || _g === void 0 ? void 0 : _g.style) || {};\n const payload = {\n fullImage: canvas.toDataURL(\"image/jpeg\"),\n croppedImage: cropCanvas.toDataURL(\"image/jpeg\"),\n displaySize: {\n width: Number(displayWidth.replace(\"px\", \"\")),\n height: Number(displayHeight.replace(\"px\", \"\")),\n },\n };\n // TEMP: Download AFTER resize for comparison (only if debug enabled)\n if (MainPage_1.enableImageDebug) {\n console.log(`[DEBUG] ${cardTypePrefix} AFTER resize - Full: ${canvas.width}x${canvas.height}, Cropped: ${cropCanvas.width}x${cropCanvas.height}`);\n if (payload.fullImage)\n this.downloadImage(payload.fullImage, `${cardTypePrefix}-full-AFTER-${canvas.width}x${canvas.height}.jpg`);\n if (payload.croppedImage)\n this.downloadImage(payload.croppedImage, `${cardTypePrefix}-cropped-AFTER-${cropCanvas.width}x${cropCanvas.height}.jpg`);\n }\n return payload;\n });\n }\n // TEMP: Helper method to download image\n downloadImage(dataUrl, filename) {\n const link = document.createElement(\"a\");\n link.href = dataUrl;\n link.download = filename;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n }\n init() {\n try {\n // show loading spinner in full pg\n (0, components_1.createFullLoading)(this.cameraId, \"Initializing Camera...\");\n // 1. Draw the camera display\n this.drawCameraDisplay();\n // 2. Setup related SDK\n const { videoElement, canvasElement, overlayElement, titleElement } = this;\n this.cardDetectionSdk = new card_detection_1.default({\n videoElement,\n canvasElement,\n overlayElement,\n titleElement,\n });\n // 3. Get the camera permission and trigger the card detection\n this.cameraInit();\n // 4. Start timer to close camera\n this.timer = setTimeout(() => {\n this.stopVideoStream();\n // open idle dialog\n const idleDialog = new baseDialog_1.default();\n idleDialog.render(this.mainScreenId, (0, translations_1.$t)(\"idle_message\"), (0, translations_1.$t)(\"btn_okay\"));\n }, this.cameraTimeLimit);\n }\n finally {\n // hide loading spinner in full pg\n (0, components_1.hideFullLoading)(this.cameraId);\n }\n }\n drawCameraDisplay() {\n // 1. header section\n const header = document.createElement(\"div\");\n header.id = camera_1.ELEMENT_IDS.HEADER;\n // header.classList.add(\"camera-header-area\");\n header.classList.add(\"pw-absolute\", \"pw-z-[2]\", \"pw-w-full\", \"pw-text-white\");\n const navigationDiv = new navigationBar_1.default().render(() => {\n this.stopVideoStream();\n });\n header.appendChild(navigationDiv);\n const body = document.createElement(\"div\");\n body.id = camera_1.ELEMENT_IDS.BODY;\n body.classList.add(\"camera-body-area\");\n const videoContainer = document.createElement(\"div\");\n videoContainer.id = camera_1.ELEMENT_IDS.VIDEO_CONTAINER;\n videoContainer.classList.add(\"video-container\");\n const overlayContainer = document.createElement(\"div\");\n overlayContainer.id = camera_1.ELEMENT_IDS.OVERLAY_CONTAINER;\n overlayContainer.classList.add(\"overlay-container\");\n // Use CameraViewport component for consistent viewport design\n const cameraViewport = new cameraViewport_1.default(this.cardType);\n const overlayElement = cameraViewport.render();\n overlayElement.classList.add(\"pw-relative\");\n // Container for title and description - positioned above the overlay\n const headerContainer = document.createElement(\"div\");\n headerContainer.classList.add(\"pw-absolute\");\n headerContainer.style.zIndex = \"1\";\n headerContainer.style.left = \"50%\";\n headerContainer.style.bottom = \"100%\";\n headerContainer.style.transform = \"translate(-50%, -20px)\";\n headerContainer.style.width = \"calc(100vw - 48px)\";\n headerContainer.style.maxWidth = \"1400px\";\n // Set Title\n const titleDiv = document.createElement(\"div\");\n titleDiv.id = \"pw-camera-title\";\n titleDiv.classList.add(\"pw-text-2xl\", \"pw-text-center\", \"pw-text-white\", \"pw-mb-4\");\n titleDiv.innerHTML = camera_instruction_1.CAMERA_INSTRUCTION[this.cardType].title;\n this.titleElement = titleDiv;\n headerContainer.appendChild(titleDiv);\n // Set Description\n const descriptionDiv = document.createElement(\"div\");\n descriptionDiv.classList.add(\"pw-text-center\", \"pw-px-5\", \"pw-text-xl\", \"pw-text-white\");\n descriptionDiv.innerHTML = camera_instruction_1.CAMERA_INSTRUCTION[this.cardType].description;\n headerContainer.appendChild(descriptionDiv);\n overlayElement.appendChild(headerContainer);\n const canvasElement = document.createElement(\"canvas\");\n canvasElement.id = camera_1.ELEMENT_IDS.CANVAS;\n canvasElement.classList.add(\"video-element\");\n canvasElement.classList.add(\"canvas-element\");\n // Mirror canvas to match video when not on mobile device (desktop/laptop)\n if (!(0, deviceDetection_1.isMobileDevice)()) {\n canvasElement.style.transform = \"scaleX(-1)\";\n }\n const videoElement = document.createElement(\"video\");\n videoElement.id = camera_1.ELEMENT_IDS.VIDEO;\n videoElement.autoplay = true;\n videoElement.playsInline = true;\n videoElement.preload = \"auto\";\n videoElement.loop = true;\n videoElement.classList.add(\"video-element\");\n // Mirror horizontally when not on mobile device (desktop/laptop front camera UX)\n if (!(0, deviceDetection_1.isMobileDevice)()) {\n videoElement.style.transform = \"scaleX(-1)\";\n }\n // 2.1\n this.videoElement = videoElement;\n this.overlayElement = overlayElement;\n this.canvasElement = canvasElement;\n overlayContainer.appendChild(overlayElement);\n videoContainer.appendChild(overlayContainer);\n videoContainer.appendChild(canvasElement);\n videoContainer.appendChild(videoElement);\n body.appendChild(videoContainer);\n // 3. append child\n const mainBody = document.getElementById(this.cameraId);\n if (!mainBody)\n return;\n mainBody.classList.add(\"camera-bg\", \"pw-overflow-y-hidden\");\n mainBody.appendChild(header);\n mainBody.appendChild(body);\n }\n listenToCardCapture(cb) {\n const cameraBody = document.getElementById(this.cameraId);\n cameraBody === null || cameraBody === void 0 ? void 0 : cameraBody.addEventListener(\"captureImage\", (e) => {\n cb(e);\n });\n }\n // here also is where the take picture button resides\n renderCameraBottomUi() {\n const footer = document.createElement(\"div\");\n footer.id = camera_1.ELEMENT_IDS.FOOTER;\n footer.classList.add(\"pw-absolute\", \"pw-bottom-[10%]\", \"pw-z-[2]\", \"pw-w-full\", \"pw-text-white\", \"pw-relative\");\n const hint = document.createElement(\"div\");\n hint.id = \"pw-doc-hint\";\n hint.classList.add(\"pw-absolute\", \"pw-bottom-[9.5rem]\", \"pw-w-full\", \"pw-text-center\", \"pw-text-lg\", \"pw-text-white\");\n hint.innerHTML = (0, translations_1.$t)(\"doc_align_card\");\n footer.appendChild(hint);\n const takePicButton = document.createElement(\"div\");\n takePicButton.id = \"takePicButton\";\n takePicButton.classList.add(\"pw-box-border\", \"pw-mx-auto\", \"pw-absolute\", \"pw-bottom-16\", \"pw-relative\", \"pw-rounded-full\", \"pw-h-16\", \"pw-w-16\", \"pw-border-solid\", \"pw-border-white\", \"pw-border-4\", \"pw-pointer-events-none\");\n // SVG progress ring sitting on top of the white border circle. \n // The progress arc is animated from empty -> full over AUTO_CAPTURE_HOLD_MS.\n const svgNS = \"http://www.w3.org/2000/svg\";\n const ringSvg = document.createElementNS(svgNS, \"svg\");\n ringSvg.setAttribute(\"viewBox\", \"0 0 64 64\");\n ringSvg.classList.add(\"pw-absolute\", \"pw-inset-0\", \"pw-w-full\", \"pw-h-full\");\n ringSvg.style.transform = \"rotate(-90deg)\";\n const progressCircle = document.createElementNS(svgNS, \"circle\");\n progressCircle.id = \"takePicProgress\";\n progressCircle.setAttribute(\"cx\", \"32\");\n progressCircle.setAttribute(\"cy\", \"32\");\n progressCircle.setAttribute(\"r\", `${CardCamera.RING_RADIUS}`);\n progressCircle.setAttribute(\"fill\", \"none\");\n progressCircle.setAttribute(\"stroke\", \"#ffffff\");\n progressCircle.setAttribute(\"stroke-width\", \"6\");\n progressCircle.setAttribute(\"stroke-linecap\", \"round\");\n progressCircle.setAttribute(\"stroke-dasharray\", `${CardCamera.RING_CIRCUMFERENCE}`);\n progressCircle.setAttribute(\"stroke-dashoffset\", `${CardCamera.RING_CIRCUMFERENCE}`);\n progressCircle.style.transition = \"stroke-dashoffset 80ms linear, stroke 150ms linear\";\n ringSvg.appendChild(progressCircle);\n takePicButton.appendChild(ringSvg);\n const smallCircleContainer = document.createElement(\"div\");\n smallCircleContainer.classList.add(\"pw-absolute\", \"pw-top-1/2\", \"-pw-translate-y-1/2\", \"pw-left-1/2\", \"-pw-translate-x-1/2\");\n takePicButton.appendChild(smallCircleContainer);\n const smallCircle = document.createElement(\"div\");\n smallCircle.id = \"takePicInner\";\n smallCircle.classList.add(\"pw-w-11\", \"pw-h-11\", \"pw-bg-white\", \"pw-rounded-full\", \"pw-opacity-60\");\n smallCircleContainer.appendChild(smallCircle);\n footer.appendChild(takePicButton);\n const mainBody = document.getElementById(this.cameraId);\n this.footerElement = footer;\n mainBody === null || mainBody === void 0 ? void 0 : mainBody.appendChild(footer);\n // Powered by pipwave at bottom of viewport\n const poweredByWrapper = document.createElement(\"div\");\n poweredByWrapper.classList.add(\"pw-absolute\", \"pw-bottom-0\", \"pw-left-0\", \"pw-right-0\", \"pw-pb-4\", \"pw-z-[2]\", \"pw-flex\", \"pw-justify-center\", \"pw-items-center\", \"pw-text-white\");\n const poweredByFooter = new footer_1.default().render(true);\n poweredByWrapper.appendChild(poweredByFooter);\n mainBody === null || mainBody === void 0 ? void 0 : mainBody.appendChild(poweredByWrapper);\n }\n pauseVideoStream() {\n var _a, _b;\n return __awaiter(this, void 0, void 0, function* () {\n (_a = this.cardDetectionSdk) === null || _a === void 0 ? void 0 : _a.stopDetectionLoop();\n this.cancelHold();\n // stop the video\n (_b = this.videoElement) === null || _b === void 0 ? void 0 : _b.pause();\n // stop idle timeout\n if (this.timer) {\n clearTimeout(this.timer);\n }\n });\n }\n resumeVideoStream(cardType) {\n var _a;\n return __awaiter(this, void 0, void 0, function* () {\n // resume the video first so detection grabs live frames\n (_a = this.videoElement) === null || _a === void 0 ? void 0 : _a.play();\n // updateType re-arms auto-capture and (re)starts the detection loop for this side\n this.updateType(cardType);\n // start idle timeout\n this.timer = setTimeout(() => {\n this.stopVideoStream();\n // open idle dialog\n const idleDialog = new baseDialog_1.default();\n idleDialog.render(this.mainScreenId, (0, translations_1.$t)(\"idle_message\"), (0, translations_1.$t)(\"btn_okay\"));\n }, this.cameraTimeLimit);\n (0, components_1.hideFullLoading)(this.cameraId);\n });\n }\n stopVideoStream() {\n this._cleanup();\n const cameraDiv = document.getElementById(this.cameraId);\n cameraDiv === null || cameraDiv === void 0 ? void 0 : cameraDiv.remove();\n (0, components_1.hideFullLoading)(this.cameraId);\n }\n _cleanup() {\n var _a;\n (_a = this.cardDetectionSdk) === null || _a === void 0 ? void 0 : _a.dispose();\n if (this.videoElement && this.videoElement.srcObject) {\n const stream = this.videoElement.srcObject;\n stream.getTracks().forEach(t => t.stop());\n this.videoElement.srcObject = null;\n }\n if (this.timer)\n clearTimeout(this.timer);\n window.removeEventListener(\"resize\", this._onWindowResize);\n window.removeEventListener(\"beforeunload\", this._onBeforeUnload);\n }\n updateType(cardType) {\n this.cardType = cardType;\n // New side (card flipped to back) or resumed session — re-arm auto-capture.\n this.hasAutoCaptured = false;\n this.cancelHold();\n // update title\n const title = document.getElementById(\"pw-camera-title\");\n if (title)\n title.innerHTML = camera_instruction_1.CAMERA_INSTRUCTION[this.cardType].title;\n // triggerAutoCapture stops the loop on every capture, so (re)start it for this\n // side. The flip flow (VerifyId) only calls updateType — not resumeVideoStream —\n // so this is what brings detection back for the back of the card.\n this.startDetection();\n }\n startDetection() {\n var _a;\n (_a = this.cardDetectionSdk) === null || _a === void 0 ? void 0 : _a.startDetectionLoop((framed) => this.onFramedChange(framed));\n }\n loadVideoStream(videoElement) {\n const initialConstraints = {\n facingMode: \"environment\",\n width: {\n ideal: 1280, // 1280, 1024, 640\n },\n height: {\n ideal: 960, // 960, 768, 480\n },\n video: true,\n };\n // Mirror video when not on mobile device (desktop/laptop front camera UX)\n if (!(0, deviceDetection_1.isMobileDevice)()) {\n videoElement.style.transform = \"scaleX(-1)\";\n }\n // Load the video stream\n const nav = window.navigator.mediaDevices\n ? window.navigator.mediaDevices\n : window.navigator;\n if (!nav)\n return;\n nav\n .getUserMedia({ video: initialConstraints })\n .then((stream) => {\n videoElement.srcObject = stream;\n })\n .catch((err) => {\n console.error(\"Error accessing the camera:\", err);\n });\n }\n cameraInit() {\n var _a;\n if (!this.videoElement)\n return;\n this.loadVideoStream(this.videoElement);\n // Fire model load concurrently — typically ready by the time video starts.\n this.cardDetectionSdk.loadModel();\n // video event listeners\n this.videoElement.addEventListener(\"loadedmetadata\", () => {\n var _a;\n this.cardDetectionSdk.init(this.cardType);\n (_a = this.overlayElement) === null || _a === void 0 ? void 0 : _a.classList.remove(\"pw-hidden\");\n // Set initial video dimensions based on orientation\n this.resizeElement();\n this.startDetection();\n });\n this.videoElement.addEventListener(\"resize\", () => {\n const isFooterExist = document.getElementById(camera_1.ELEMENT_IDS.FOOTER);\n if (!isFooterExist) {\n this.renderCameraBottomUi();\n }\n this.resizeElement();\n });\n window.addEventListener(\"resize\", this._onWindowResize);\n window.addEventListener(\"beforeunload\", this._onBeforeUnload);\n (_a = screen.orientation) === null || _a === void 0 ? void 0 : _a.addEventListener(\"change\", () => {\n const orientationType = screen.orientation.type;\n if (!orientationType || !this.isMobile)\n return;\n if ([\"landscape-primary\", \"landscape-secondary\"].includes(orientationType)) {\n // TO ADD DIALOG ASK USER TO USE POTRAIT VIEW\n }\n else if ([\"portrait-secondary\", \"portrait-primary\"].includes(orientationType)) {\n // TO BE CONFIRM IF NEED CLOSE DIALOG\n }\n });\n }\n // Driven by the card-detection loop. \n onFramedChange(framed) {\n this.isFramed = framed;\n if (this.hasAutoCaptured)\n return;\n if (framed)\n this.setHint((0, translations_1.$t)(\"doc_hold_steady\"));\n this.ensureHoldLoop();\n }\n // Single rAF loop: progress climbs while framed, decays while not. Captures when it\n // hits 1 (and the card is still framed); idles itself once it drains back to 0.\n ensureHoldLoop() {\n if (this.holdRafId !== null || this.hasAutoCaptured)\n return;\n this.lastHoldTick = performance.now();\n const tick = (now) => {\n const dt = now - this.lastHoldTick;\n this.lastHoldTick = now;\n this.holdProgress += this.isFramed\n ? dt / CardCamera.AUTO_CAPTURE_HOLD_MS\n : -dt / CardCamera.AUTO_CAPTURE_DECAY_MS;\n this.holdProgress = Math.max(0, Math.min(1, this.holdProgress));\n this.setRingProgress(this.holdProgress, this.isFramed);\n if (this.holdProgress >= 1 && this.isFramed) {\n this.holdRafId = null;\n this.triggerAutoCapture();\n return;\n }\n if (this.holdProgress <= 0 && !this.isFramed) {\n this.holdRafId = null;\n this.setHint((0, translations_1.$t)(\"doc_align_card\"));\n return;\n }\n this.holdRafId = requestAnimationFrame(tick);\n };\n this.holdRafId = requestAnimationFrame(tick);\n }\n cancelHold() {\n if (this.holdRafId !== null) {\n cancelAnimationFrame(this.holdRafId);\n this.holdRafId = null;\n }\n this.isFramed = false;\n this.holdProgress = 0;\n this.setRingProgress(0);\n this.hideCaptureTick();\n if (!this.hasAutoCaptured)\n this.setHint((0, translations_1.$t)(\"doc_align_card\"));\n }\n triggerAutoCapture() {\n var _a;\n return __awaiter(this, void 0, void 0, function* () {\n if (this.hasAutoCaptured)\n return;\n this.hasAutoCaptured = true;\n // Stop inference immediately — no point running the model (and flickering the\n // border over a frozen frame) in the window before pause/flip kicks in.\n (_a = this.cardDetectionSdk) === null || _a === void 0 ? void 0 : _a.stopDetectionLoop();\n this.setRingProgress(1);\n const mainBody = document.getElementById(this.cameraId);\n const image = yield this.captureImage();\n // Frame is secured — now confirm success to the user.\n this.showCaptureTick();\n const event = new CustomEvent(\"captureImage\", {\n detail: {\n image,\n type: this.cardType,\n },\n });\n yield new Promise((resolve) => setTimeout(resolve, CardCamera.CAPTURE_TICK_MS));\n mainBody === null || mainBody === void 0 ? void 0 : mainBody.dispatchEvent(event);\n });\n }\n // progress 0 -> empty ring, 1 -> full ring\n setRingProgress(progress, filling = true) {\n const ring = document.getElementById(\"takePicProgress\");\n if (!ring)\n return;\n ring.setAttribute(\"stroke\", filling ? CardCamera.RING_FILL_COLOR : CardCamera.RING_FAIL_COLOR);\n const offset = CardCamera.RING_CIRCUMFERENCE * (1 - progress);\n ring.setAttribute(\"stroke-dashoffset\", `${offset}`);\n }\n // Briefly flash a green checkmark inside the capture button to confirm a\n // successful capture, then clear it so the button is clean for the next side.\n showCaptureTick() {\n var _a, _b;\n if (document.getElementById(\"takePicTick\"))\n return;\n const container = (_a = document.getElementById(\"takePicInner\")) === null || _a === void 0 ? void 0 : _a.parentElement;\n if (!container)\n return;\n // White backing pops once the tick lands.\n (_b = document\n .getElementById(\"takePicInner\")) === null || _b === void 0 ? void 0 : _b.classList.replace(\"pw-opacity-60\", \"pw-opacity-100\");\n const svgNS = \"http://www.w3.org/2000/svg\";\n const tick = document.createElementNS(svgNS, \"svg\");\n tick.id = \"takePicTick\";\n tick.setAttribute(\"viewBox\", \"0 0 24 24\");\n tick.setAttribute(\"width\", \"34\");\n tick.setAttribute(\"height\", \"34\");\n tick.classList.add(\"pw-absolute\", \"pw-top-1/2\", \"-pw-translate-y-1/2\", \"pw-left-1/2\", \"-pw-translate-x-1/2\");\n const path = document.createElementNS(svgNS, \"path\");\n path.setAttribute(\"d\", \"M5 13l4 4L19 7\");\n path.setAttribute(\"fill\", \"none\");\n path.setAttribute(\"stroke\", CardCamera.RING_FILL_COLOR);\n path.setAttribute(\"stroke-width\", \"3\");\n path.setAttribute(\"stroke-linecap\", \"round\");\n path.setAttribute(\"stroke-linejoin\", \"round\");\n tick.appendChild(path);\n container.appendChild(tick);\n if (this.tickTimer)\n clearTimeout(this.tickTimer);\n this.tickTimer = setTimeout(() => this.hideCaptureTick(), CardCamera.CAPTURE_TICK_MS);\n }\n hideCaptureTick() {\n var _a, _b;\n if (this.tickTimer) {\n clearTimeout(this.tickTimer);\n this.tickTimer = null;\n }\n (_a = document.getElementById(\"takePicTick\")) === null || _a === void 0 ? void 0 : _a.remove();\n (_b = document\n .getElementById(\"takePicInner\")) === null || _b === void 0 ? void 0 : _b.classList.replace(\"pw-opacity-100\", \"pw-opacity-60\");\n }\n setHint(text) {\n const hintEl = document.getElementById(\"pw-doc-hint\");\n if (hintEl)\n hintEl.innerHTML = text;\n }\n resizeElement() {\n const videoContainer = document.getElementById(camera_1.ELEMENT_IDS.VIDEO_CONTAINER);\n const videoElement = this.videoElement;\n if (videoElement && videoElement.videoWidth && videoElement.videoHeight) {\n const vpW = window.innerWidth;\n const vpH = window.innerHeight;\n const videoAspect = videoElement.videoWidth / videoElement.videoHeight;\n // Fit the container to the camera's native aspect ratio within the viewport\n let containerW;\n let containerH;\n if (vpW / vpH > videoAspect) {\n containerH = vpH; // viewport wider than video — constrain by height\n containerW = vpH * videoAspect;\n }\n else {\n containerW = vpW; // viewport taller than video — constrain by width\n containerH = vpW / videoAspect;\n }\n if (videoContainer) {\n videoContainer.style.width = `${containerW}px`;\n videoContainer.style.height = `${containerH}px`;\n }\n videoElement.style.width = \"100%\";\n videoElement.style.height = \"100%\";\n videoElement.style.minWidth = \"\";\n videoElement.style.minHeight = \"\";\n }\n this.cardDetectionSdk.resizeElement();\n }\n}\nCardCamera.AUTO_CAPTURE_HOLD_MS = 1500; // framed time to fill the ring\nCardCamera.AUTO_CAPTURE_DECAY_MS = 1200; // unframed time to drain it\nCardCamera.RING_RADIUS = 29;\nCardCamera.RING_CIRCUMFERENCE = 2 * Math.PI * CardCamera.RING_RADIUS;\nCardCamera.RING_FILL_COLOR = \"#16A34A\";\nCardCamera.RING_FAIL_COLOR = \"#D21C3E\";\nCardCamera.CAPTURE_TICK_MS = 800;\nexports[\"default\"] = CardCamera;\n\n\n//# sourceURL=webpack://PWUISDK/./src/plugins/card-camera.ts?");
4003
+ eval("\nvar __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n};\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nconst card_detection_1 = __importDefault(__webpack_require__(/*! ./card-detection */ \"./src/plugins/card-detection.ts\"));\nconst translations_1 = __webpack_require__(/*! ../translations */ \"./src/translations/index.ts\");\nconst deviceDetection_1 = __webpack_require__(/*! ../helper/deviceDetection */ \"./src/helper/deviceDetection.ts\");\nconst camera_1 = __webpack_require__(/*! ../models/camera */ \"./src/models/camera.ts\");\nconst camera_instruction_1 = __webpack_require__(/*! ../constant/camera-instruction */ \"./src/constant/camera-instruction.ts\");\nconst baseDialog_1 = __importDefault(__webpack_require__(/*! ../components/baseDialog */ \"./src/components/baseDialog.ts\"));\nconst components_1 = __webpack_require__(/*! ../components/components */ \"./src/components/components.ts\");\nconst navigationBar_1 = __importDefault(__webpack_require__(/*! ../components/navigationBar */ \"./src/components/navigationBar.ts\"));\nconst cameraViewport_1 = __importDefault(__webpack_require__(/*! ../components/cameraViewport */ \"./src/components/cameraViewport.ts\"));\nconst footer_1 = __importDefault(__webpack_require__(/*! ../components/footer */ \"./src/components/footer.ts\"));\nconst MainPage_1 = __webpack_require__(/*! ../classes/MainPage */ \"./src/classes/MainPage.ts\");\nconst imagesHelper_1 = __webpack_require__(/*! ../helper/imagesHelper */ \"./src/helper/imagesHelper.ts\");\nclass CardCamera {\n constructor(cameraId, cardType, mainScreenId = \"\") {\n this.cameraTimeLimit = 90000;\n this.holdRafId = null;\n this.hasAutoCaptured = false;\n this.isFramed = false;\n this.holdProgress = 0;\n this.lastHoldTick = 0;\n this.pendingCapture = null;\n this.tickTimer = null;\n this._onWindowResize = () => { this.resizeElement(); };\n this._onBeforeUnload = () => { this._cleanup(); };\n this.videoElement = null;\n this.canvasElement = null;\n this.overlayElement = null;\n this.titleElement = null;\n this.footerElement = null;\n this.cameraId = cameraId;\n this.cardType = cardType;\n this.mainScreenId = mainScreenId;\n }\n isMobile() {\n // Use extracted helper function\n return (0, deviceDetection_1.isMobileDevice)();\n }\n captureImage() {\n var _a, _b, _c, _d, _e, _f, _g;\n return __awaiter(this, void 0, void 0, function* () {\n const bitmap = this.videoElement;\n // 1. full image\n let canvas = document.createElement(\"canvas\");\n let context = canvas.getContext(\"2d\");\n canvas.width = bitmap.videoWidth;\n canvas.height = bitmap.videoHeight;\n context.drawImage(bitmap, 0, 0, bitmap.videoWidth, bitmap.videoHeight);\n // 2. cropped image — region corresponds exactly to what the overlay shows\n let cropCanvas = document.createElement(\"canvas\");\n let cropContext = cropCanvas.getContext(\"2d\");\n cropCanvas.width = bitmap.videoWidth;\n cropCanvas.height = bitmap.videoHeight;\n cropContext.drawImage(bitmap, 0, 0, bitmap.videoWidth, bitmap.videoHeight);\n // Scale factor: stream pixels per display pixel\n const scaleX = bitmap.videoWidth / ((_b = (_a = this.videoElement) === null || _a === void 0 ? void 0 : _a.clientWidth) !== null && _b !== void 0 ? _b : bitmap.videoWidth);\n const scaleY = bitmap.videoHeight / ((_d = (_c = this.videoElement) === null || _c === void 0 ? void 0 : _c.clientHeight) !== null && _d !== void 0 ? _d : bitmap.videoHeight);\n // Use overlay's actual on-screen position as the crop region\n const overlayRect = (_e = this.overlayElement) === null || _e === void 0 ? void 0 : _e.getBoundingClientRect();\n const videoRect = (_f = this.videoElement) === null || _f === void 0 ? void 0 : _f.getBoundingClientRect();\n let cropX, cropY, cropW, cropH;\n if (overlayRect && videoRect) {\n cropX = Math.round((overlayRect.left - videoRect.left) * scaleX);\n cropY = Math.round((overlayRect.top - videoRect.top) * scaleY);\n cropW = Math.round(overlayRect.width * scaleX);\n cropH = Math.round(overlayRect.height * scaleY);\n }\n else {\n // Fallback: derive from calculateHeightNWidth + manual scale\n const { width, height } = this.cardDetectionSdk.calculateHeightNWidth(bitmap.videoWidth, bitmap.videoHeight);\n cropW = Math.round(width * scaleX);\n cropH = Math.round(height * scaleY);\n cropX = Math.round((bitmap.videoWidth - cropW) / 2);\n cropY = Math.round((bitmap.videoHeight - cropH) / 2);\n }\n // Clamp to stream bounds\n cropX = Math.max(0, cropX);\n cropY = Math.max(0, cropY);\n cropW = Math.min(cropW, bitmap.videoWidth - cropX);\n cropH = Math.min(cropH, bitmap.videoHeight - cropY);\n // crop the selected part image (viewport frame region)\n const imgData = cropContext.getImageData(cropX, cropY, cropW, cropH);\n cropCanvas.width = cropW;\n cropCanvas.height = cropH;\n cropContext.putImageData(imgData, 0, 0);\n // Determine card type prefix for file naming\n const cardTypePrefix = this.cardType === camera_1.CARD_TYPES.ID ? 'id-front' :\n this.cardType === camera_1.CARD_TYPES.ID_BACK ? 'id-back' :\n this.cardType === camera_1.CARD_TYPES.LICENSE ? 'license-front' :\n this.cardType === camera_1.CARD_TYPES.LICENSE_BACK ? 'license-back' :\n this.cardType === camera_1.CARD_TYPES.PASSPORT ? 'passport' :\n this.cardType === camera_1.CARD_TYPES.CC ? 'cc-front' : 'cc-back';\n // TEMP: Download BEFORE resize for comparison (only if debug enabled)\n if (MainPage_1.enableImageDebug) {\n const beforeResizeFullCanvas = canvas.cloneNode();\n const beforeResizeFullCtx = beforeResizeFullCanvas.getContext(\"2d\");\n beforeResizeFullCanvas.width = canvas.width;\n beforeResizeFullCanvas.height = canvas.height;\n beforeResizeFullCtx.drawImage(canvas, 0, 0);\n const beforeResizeCropCanvas = cropCanvas.cloneNode();\n const beforeResizeCropCtx = beforeResizeCropCanvas.getContext(\"2d\");\n beforeResizeCropCanvas.width = cropCanvas.width;\n beforeResizeCropCanvas.height = cropCanvas.height;\n beforeResizeCropCtx.drawImage(cropCanvas, 0, 0);\n console.log(`[DEBUG] ${cardTypePrefix} BEFORE resize - Full: ${beforeResizeFullCanvas.width}x${beforeResizeFullCanvas.height}, Cropped: ${beforeResizeCropCanvas.width}x${beforeResizeCropCanvas.height}`);\n this.downloadImage(beforeResizeFullCanvas.toDataURL(\"image/jpeg\"), `${cardTypePrefix}-full-BEFORE-${beforeResizeFullCanvas.width}x${beforeResizeFullCanvas.height}.jpg`);\n this.downloadImage(beforeResizeCropCanvas.toDataURL(\"image/jpeg\"), `${cardTypePrefix}-cropped-BEFORE-${beforeResizeCropCanvas.width}x${beforeResizeCropCanvas.height}.jpg`);\n }\n // 3. resize the canvas\n try {\n canvas = (0, imagesHelper_1.resizeCanvas)(canvas, imagesHelper_1.maxFullImage.width, imagesHelper_1.maxFullImage.height);\n cropCanvas = (0, imagesHelper_1.resizeCanvas)(cropCanvas, imagesHelper_1.maxCroppedImage.width, imagesHelper_1.maxCroppedImage.height);\n }\n catch (err) {\n console.error(\"doc resize image error\", err);\n }\n // 4. construct payload - convert to desired file format\n const { width: displayWidth = \"\", height: displayHeight = \"\" } = ((_g = this.overlayElement) === null || _g === void 0 ? void 0 : _g.style) || {};\n const payload = {\n fullImage: canvas.toDataURL(\"image/jpeg\"),\n croppedImage: cropCanvas.toDataURL(\"image/jpeg\"),\n displaySize: {\n width: Number(displayWidth.replace(\"px\", \"\")),\n height: Number(displayHeight.replace(\"px\", \"\")),\n },\n };\n // TEMP: Download AFTER resize for comparison (only if debug enabled)\n if (MainPage_1.enableImageDebug) {\n console.log(`[DEBUG] ${cardTypePrefix} AFTER resize - Full: ${canvas.width}x${canvas.height}, Cropped: ${cropCanvas.width}x${cropCanvas.height}`);\n if (payload.fullImage)\n this.downloadImage(payload.fullImage, `${cardTypePrefix}-full-AFTER-${canvas.width}x${canvas.height}.jpg`);\n if (payload.croppedImage)\n this.downloadImage(payload.croppedImage, `${cardTypePrefix}-cropped-AFTER-${cropCanvas.width}x${cropCanvas.height}.jpg`);\n }\n return payload;\n });\n }\n // TEMP: Helper method to download image\n downloadImage(dataUrl, filename) {\n const link = document.createElement(\"a\");\n link.href = dataUrl;\n link.download = filename;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n }\n init() {\n try {\n // show loading spinner in full pg\n (0, components_1.createFullLoading)(this.cameraId, \"Initializing Camera...\");\n // 1. Draw the camera display\n this.drawCameraDisplay();\n // 2. Setup related SDK\n const { videoElement, canvasElement, overlayElement, titleElement } = this;\n this.cardDetectionSdk = new card_detection_1.default({\n videoElement,\n canvasElement,\n overlayElement,\n titleElement,\n });\n // 3. Get the camera permission and trigger the card detection\n this.cameraInit();\n // 4. Start timer to close camera\n this.timer = setTimeout(() => {\n this.stopVideoStream();\n // open idle dialog\n const idleDialog = new baseDialog_1.default();\n idleDialog.render(this.mainScreenId, (0, translations_1.$t)(\"idle_message\"), (0, translations_1.$t)(\"btn_okay\"));\n }, this.cameraTimeLimit);\n }\n finally {\n // hide loading spinner in full pg\n (0, components_1.hideFullLoading)(this.cameraId);\n }\n }\n drawCameraDisplay() {\n // 1. header section\n const header = document.createElement(\"div\");\n header.id = camera_1.ELEMENT_IDS.HEADER;\n // header.classList.add(\"camera-header-area\");\n header.classList.add(\"pw-absolute\", \"pw-z-[2]\", \"pw-w-full\", \"pw-text-white\");\n const navigationDiv = new navigationBar_1.default().render(() => {\n this.stopVideoStream();\n });\n header.appendChild(navigationDiv);\n const body = document.createElement(\"div\");\n body.id = camera_1.ELEMENT_IDS.BODY;\n body.classList.add(\"camera-body-area\");\n const videoContainer = document.createElement(\"div\");\n videoContainer.id = camera_1.ELEMENT_IDS.VIDEO_CONTAINER;\n videoContainer.classList.add(\"video-container\");\n const overlayContainer = document.createElement(\"div\");\n overlayContainer.id = camera_1.ELEMENT_IDS.OVERLAY_CONTAINER;\n overlayContainer.classList.add(\"overlay-container\");\n // Use CameraViewport component for consistent viewport design\n const cameraViewport = new cameraViewport_1.default(this.cardType);\n const overlayElement = cameraViewport.render();\n overlayElement.classList.add(\"pw-relative\");\n // Container for title and description - positioned above the overlay\n const headerContainer = document.createElement(\"div\");\n headerContainer.classList.add(\"pw-absolute\");\n headerContainer.style.zIndex = \"1\";\n headerContainer.style.left = \"50%\";\n headerContainer.style.bottom = \"100%\";\n headerContainer.style.transform = \"translate(-50%, -20px)\";\n headerContainer.style.width = \"calc(100vw - 48px)\";\n headerContainer.style.maxWidth = \"1400px\";\n // Set Title\n const titleDiv = document.createElement(\"div\");\n titleDiv.id = \"pw-camera-title\";\n titleDiv.classList.add(\"pw-text-2xl\", \"pw-text-center\", \"pw-text-white\", \"pw-mb-4\");\n titleDiv.innerHTML = camera_instruction_1.CAMERA_INSTRUCTION[this.cardType].title;\n this.titleElement = titleDiv;\n headerContainer.appendChild(titleDiv);\n // Set Description\n const descriptionDiv = document.createElement(\"div\");\n descriptionDiv.classList.add(\"pw-text-center\", \"pw-px-5\", \"pw-text-xl\", \"pw-text-white\");\n descriptionDiv.innerHTML = camera_instruction_1.CAMERA_INSTRUCTION[this.cardType].description;\n headerContainer.appendChild(descriptionDiv);\n overlayElement.appendChild(headerContainer);\n const canvasElement = document.createElement(\"canvas\");\n canvasElement.id = camera_1.ELEMENT_IDS.CANVAS;\n canvasElement.classList.add(\"video-element\");\n canvasElement.classList.add(\"canvas-element\");\n // Mirror canvas to match video when not on mobile device (desktop/laptop)\n if (!(0, deviceDetection_1.isMobileDevice)()) {\n canvasElement.style.transform = \"scaleX(-1)\";\n }\n const videoElement = document.createElement(\"video\");\n videoElement.id = camera_1.ELEMENT_IDS.VIDEO;\n videoElement.autoplay = true;\n videoElement.playsInline = true;\n videoElement.preload = \"auto\";\n videoElement.loop = true;\n videoElement.classList.add(\"video-element\");\n // Mirror horizontally when not on mobile device (desktop/laptop front camera UX)\n if (!(0, deviceDetection_1.isMobileDevice)()) {\n videoElement.style.transform = \"scaleX(-1)\";\n }\n // 2.1\n this.videoElement = videoElement;\n this.overlayElement = overlayElement;\n this.canvasElement = canvasElement;\n overlayContainer.appendChild(overlayElement);\n videoContainer.appendChild(overlayContainer);\n videoContainer.appendChild(canvasElement);\n videoContainer.appendChild(videoElement);\n body.appendChild(videoContainer);\n // 3. append child\n const mainBody = document.getElementById(this.cameraId);\n if (!mainBody)\n return;\n mainBody.classList.add(\"camera-bg\", \"pw-overflow-y-hidden\");\n mainBody.appendChild(header);\n mainBody.appendChild(body);\n }\n listenToCardCapture(cb) {\n const cameraBody = document.getElementById(this.cameraId);\n cameraBody === null || cameraBody === void 0 ? void 0 : cameraBody.addEventListener(\"captureImage\", (e) => {\n cb(e);\n });\n }\n // here also is where the take picture button resides\n renderCameraBottomUi() {\n const footer = document.createElement(\"div\");\n footer.id = camera_1.ELEMENT_IDS.FOOTER;\n footer.classList.add(\"pw-absolute\", \"pw-bottom-[10%]\", \"pw-z-[2]\", \"pw-w-full\", \"pw-text-white\", \"pw-relative\");\n const hint = document.createElement(\"div\");\n hint.id = \"pw-doc-hint\";\n hint.classList.add(\"pw-absolute\", \"pw-bottom-[9.5rem]\", \"pw-w-full\", \"pw-text-center\", \"pw-text-lg\", \"pw-text-white\");\n hint.innerHTML = (0, translations_1.$t)(\"doc_align_card\");\n footer.appendChild(hint);\n const takePicButton = document.createElement(\"div\");\n takePicButton.id = \"takePicButton\";\n takePicButton.classList.add(\"pw-box-border\", \"pw-mx-auto\", \"pw-absolute\", \"pw-bottom-16\", \"pw-relative\", \"pw-rounded-full\", \"pw-h-16\", \"pw-w-16\", \"pw-border-solid\", \"pw-border-white\", \"pw-border-4\", \"pw-pointer-events-none\");\n // SVG progress ring sitting on top of the white border circle. \n // The progress arc is animated from empty -> full over AUTO_CAPTURE_HOLD_MS.\n const svgNS = \"http://www.w3.org/2000/svg\";\n const ringSvg = document.createElementNS(svgNS, \"svg\");\n ringSvg.setAttribute(\"viewBox\", \"0 0 64 64\");\n ringSvg.classList.add(\"pw-absolute\", \"pw-inset-0\", \"pw-w-full\", \"pw-h-full\");\n ringSvg.style.transform = \"rotate(-90deg)\";\n const progressCircle = document.createElementNS(svgNS, \"circle\");\n progressCircle.id = \"takePicProgress\";\n progressCircle.setAttribute(\"cx\", \"32\");\n progressCircle.setAttribute(\"cy\", \"32\");\n progressCircle.setAttribute(\"r\", `${CardCamera.RING_RADIUS}`);\n progressCircle.setAttribute(\"fill\", \"none\");\n progressCircle.setAttribute(\"stroke\", \"#ffffff\");\n progressCircle.setAttribute(\"stroke-width\", \"6\");\n progressCircle.setAttribute(\"stroke-linecap\", \"round\");\n progressCircle.setAttribute(\"stroke-dasharray\", `${CardCamera.RING_CIRCUMFERENCE}`);\n progressCircle.setAttribute(\"stroke-dashoffset\", `${CardCamera.RING_CIRCUMFERENCE}`);\n progressCircle.style.transition = \"stroke-dashoffset 80ms linear, stroke 150ms linear\";\n ringSvg.appendChild(progressCircle);\n takePicButton.appendChild(ringSvg);\n const smallCircleContainer = document.createElement(\"div\");\n smallCircleContainer.classList.add(\"pw-absolute\", \"pw-top-1/2\", \"-pw-translate-y-1/2\", \"pw-left-1/2\", \"-pw-translate-x-1/2\");\n takePicButton.appendChild(smallCircleContainer);\n const smallCircle = document.createElement(\"div\");\n smallCircle.id = \"takePicInner\";\n smallCircle.classList.add(\"pw-w-11\", \"pw-h-11\", \"pw-bg-white\", \"pw-rounded-full\", \"pw-opacity-60\");\n smallCircleContainer.appendChild(smallCircle);\n footer.appendChild(takePicButton);\n const mainBody = document.getElementById(this.cameraId);\n this.footerElement = footer;\n mainBody === null || mainBody === void 0 ? void 0 : mainBody.appendChild(footer);\n // Powered by pipwave at bottom of viewport\n const poweredByWrapper = document.createElement(\"div\");\n poweredByWrapper.classList.add(\"pw-absolute\", \"pw-bottom-0\", \"pw-left-0\", \"pw-right-0\", \"pw-pb-4\", \"pw-z-[2]\", \"pw-flex\", \"pw-justify-center\", \"pw-items-center\", \"pw-text-white\");\n const poweredByFooter = new footer_1.default().render(true);\n poweredByWrapper.appendChild(poweredByFooter);\n mainBody === null || mainBody === void 0 ? void 0 : mainBody.appendChild(poweredByWrapper);\n }\n pauseVideoStream() {\n var _a, _b;\n return __awaiter(this, void 0, void 0, function* () {\n (_a = this.cardDetectionSdk) === null || _a === void 0 ? void 0 : _a.stopDetectionLoop();\n this.cancelHold();\n // stop the video\n (_b = this.videoElement) === null || _b === void 0 ? void 0 : _b.pause();\n // stop idle timeout\n if (this.timer) {\n clearTimeout(this.timer);\n }\n });\n }\n resumeVideoStream(cardType) {\n var _a;\n return __awaiter(this, void 0, void 0, function* () {\n // resume the video first so detection grabs live frames\n (_a = this.videoElement) === null || _a === void 0 ? void 0 : _a.play();\n // updateType re-arms auto-capture and (re)starts the detection loop for this side\n this.updateType(cardType);\n // start idle timeout\n this.timer = setTimeout(() => {\n this.stopVideoStream();\n // open idle dialog\n const idleDialog = new baseDialog_1.default();\n idleDialog.render(this.mainScreenId, (0, translations_1.$t)(\"idle_message\"), (0, translations_1.$t)(\"btn_okay\"));\n }, this.cameraTimeLimit);\n (0, components_1.hideFullLoading)(this.cameraId);\n });\n }\n stopVideoStream() {\n this._cleanup();\n const cameraDiv = document.getElementById(this.cameraId);\n cameraDiv === null || cameraDiv === void 0 ? void 0 : cameraDiv.remove();\n (0, components_1.hideFullLoading)(this.cameraId);\n }\n _cleanup() {\n var _a;\n (_a = this.cardDetectionSdk) === null || _a === void 0 ? void 0 : _a.dispose();\n if (this.videoElement && this.videoElement.srcObject) {\n const stream = this.videoElement.srcObject;\n stream.getTracks().forEach(t => t.stop());\n this.videoElement.srcObject = null;\n }\n if (this.timer)\n clearTimeout(this.timer);\n window.removeEventListener(\"resize\", this._onWindowResize);\n window.removeEventListener(\"beforeunload\", this._onBeforeUnload);\n }\n updateType(cardType) {\n this.cardType = cardType;\n // New side (card flipped to back) or resumed session — re-arm auto-capture.\n this.hasAutoCaptured = false;\n this.cancelHold();\n // update title\n const title = document.getElementById(\"pw-camera-title\");\n if (title)\n title.innerHTML = camera_instruction_1.CAMERA_INSTRUCTION[this.cardType].title;\n // triggerAutoCapture stops the loop on every capture, so (re)start it for this\n // side. The flip flow (VerifyId) only calls updateType — not resumeVideoStream —\n // so this is what brings detection back for the back of the card.\n this.startDetection();\n }\n startDetection() {\n var _a;\n (_a = this.cardDetectionSdk) === null || _a === void 0 ? void 0 : _a.startDetectionLoop((framed) => this.onFramedChange(framed));\n }\n loadVideoStream(videoElement) {\n const initialConstraints = {\n facingMode: \"environment\",\n width: {\n ideal: 1280, // 1280, 1024, 640\n },\n height: {\n ideal: 960, // 960, 768, 480\n },\n video: true,\n };\n // Mirror video when not on mobile device (desktop/laptop front camera UX)\n if (!(0, deviceDetection_1.isMobileDevice)()) {\n videoElement.style.transform = \"scaleX(-1)\";\n }\n // Load the video stream\n const nav = window.navigator.mediaDevices\n ? window.navigator.mediaDevices\n : window.navigator;\n if (!nav)\n return;\n nav\n .getUserMedia({ video: initialConstraints })\n .then((stream) => {\n videoElement.srcObject = stream;\n })\n .catch((err) => {\n console.error(\"Error accessing the camera:\", err);\n });\n }\n cameraInit() {\n var _a;\n if (!this.videoElement)\n return;\n this.loadVideoStream(this.videoElement);\n // Fire model load concurrently — typically ready by the time video starts.\n this.cardDetectionSdk.loadModel();\n // video event listeners\n this.videoElement.addEventListener(\"loadedmetadata\", () => {\n var _a;\n this.cardDetectionSdk.init(this.cardType);\n (_a = this.overlayElement) === null || _a === void 0 ? void 0 : _a.classList.remove(\"pw-hidden\");\n // Set initial video dimensions based on orientation\n this.resizeElement();\n this.startDetection();\n });\n this.videoElement.addEventListener(\"resize\", () => {\n const isFooterExist = document.getElementById(camera_1.ELEMENT_IDS.FOOTER);\n if (!isFooterExist) {\n this.renderCameraBottomUi();\n }\n this.resizeElement();\n });\n window.addEventListener(\"resize\", this._onWindowResize);\n window.addEventListener(\"beforeunload\", this._onBeforeUnload);\n (_a = screen.orientation) === null || _a === void 0 ? void 0 : _a.addEventListener(\"change\", () => {\n const orientationType = screen.orientation.type;\n if (!orientationType || !this.isMobile)\n return;\n if ([\"landscape-primary\", \"landscape-secondary\"].includes(orientationType)) {\n // TO ADD DIALOG ASK USER TO USE POTRAIT VIEW\n }\n else if ([\"portrait-secondary\", \"portrait-primary\"].includes(orientationType)) {\n // TO BE CONFIRM IF NEED CLOSE DIALOG\n }\n });\n }\n // Driven by the card-detection loop. \n onFramedChange(framed) {\n this.isFramed = framed;\n if (this.hasAutoCaptured)\n return;\n if (framed) {\n this.setHint((0, translations_1.$t)(\"doc_hold_steady\"));\n this.ensureHoldLoop();\n }\n else {\n // Lost framing: empty the ring instantly and restart — no slow drain.\n this.cancelHold();\n }\n }\n // Single rAF loop: progress climbs while framed. Secures the frame at the midpoint\n // and captures once it hits 1. Draining is handled instantly by onFramedChange.\n ensureHoldLoop() {\n if (this.holdRafId !== null || this.hasAutoCaptured)\n return;\n this.lastHoldTick = performance.now();\n const tick = (now) => {\n const dt = now - this.lastHoldTick;\n this.lastHoldTick = now;\n if (!this.isFramed) {\n this.holdRafId = null;\n return;\n }\n this.holdProgress = Math.min(1, this.holdProgress + dt / CardCamera.AUTO_CAPTURE_HOLD_MS);\n this.setRingProgress(this.holdProgress);\n if (this.holdProgress >= CardCamera.AUTO_CAPTURE_GRAB_AT && this.pendingCapture === null) {\n this.pendingCapture = this.captureImage();\n if (MainPage_1.enableImageDebug)\n console.log(`[DEBUG] ${this.cardType} frame secured at midpoint`);\n }\n if (this.holdProgress >= 1) {\n this.holdRafId = null;\n this.triggerAutoCapture();\n return;\n }\n this.holdRafId = requestAnimationFrame(tick);\n };\n this.holdRafId = requestAnimationFrame(tick);\n }\n cancelHold() {\n if (this.holdRafId !== null) {\n cancelAnimationFrame(this.holdRafId);\n this.holdRafId = null;\n }\n this.isFramed = false;\n this.holdProgress = 0;\n this.pendingCapture = null;\n this.setRingProgress(0);\n this.hideCaptureTick();\n if (!this.hasAutoCaptured)\n this.setHint((0, translations_1.$t)(\"doc_align_card\"));\n }\n triggerAutoCapture() {\n var _a;\n return __awaiter(this, void 0, void 0, function* () {\n if (this.hasAutoCaptured || this.pendingCapture === null)\n return;\n this.hasAutoCaptured = true;\n // Commit only the frame secured at the midpoint — never a fresh (possibly blurry) grab.\n const capture = this.pendingCapture;\n this.pendingCapture = null;\n // Stop inference immediately — no point running the model (and flickering the\n // border over a frozen frame) in the window before pause/flip kicks in.\n (_a = this.cardDetectionSdk) === null || _a === void 0 ? void 0 : _a.stopDetectionLoop();\n this.setRingProgress(1);\n const mainBody = document.getElementById(this.cameraId);\n const image = yield capture;\n if (MainPage_1.enableImageDebug)\n console.log(`[DEBUG] ${this.cardType} image captured (committed at success)`);\n // Frame is secured — now confirm success to the user.\n this.showCaptureTick();\n const event = new CustomEvent(\"captureImage\", {\n detail: {\n image,\n type: this.cardType,\n },\n });\n yield new Promise((resolve) => setTimeout(resolve, CardCamera.CAPTURE_TICK_MS));\n mainBody === null || mainBody === void 0 ? void 0 : mainBody.dispatchEvent(event);\n });\n }\n // progress 0 -> empty ring, 1 -> full ring\n setRingProgress(progress) {\n const ring = document.getElementById(\"takePicProgress\");\n if (!ring)\n return;\n ring.setAttribute(\"stroke\", CardCamera.RING_FILL_COLOR);\n const offset = CardCamera.RING_CIRCUMFERENCE * (1 - progress);\n ring.setAttribute(\"stroke-dashoffset\", `${offset}`);\n }\n // Briefly flash a green checkmark inside the capture button to confirm a\n // successful capture, then clear it so the button is clean for the next side.\n showCaptureTick() {\n var _a, _b;\n if (document.getElementById(\"takePicTick\"))\n return;\n const container = (_a = document.getElementById(\"takePicInner\")) === null || _a === void 0 ? void 0 : _a.parentElement;\n if (!container)\n return;\n // White backing pops once the tick lands.\n (_b = document\n .getElementById(\"takePicInner\")) === null || _b === void 0 ? void 0 : _b.classList.replace(\"pw-opacity-60\", \"pw-opacity-100\");\n const svgNS = \"http://www.w3.org/2000/svg\";\n const tick = document.createElementNS(svgNS, \"svg\");\n tick.id = \"takePicTick\";\n tick.setAttribute(\"viewBox\", \"0 0 24 24\");\n tick.setAttribute(\"width\", \"34\");\n tick.setAttribute(\"height\", \"34\");\n tick.classList.add(\"pw-absolute\", \"pw-top-1/2\", \"-pw-translate-y-1/2\", \"pw-left-1/2\", \"-pw-translate-x-1/2\");\n const path = document.createElementNS(svgNS, \"path\");\n path.setAttribute(\"d\", \"M5 13l4 4L19 7\");\n path.setAttribute(\"fill\", \"none\");\n path.setAttribute(\"stroke\", CardCamera.RING_FILL_COLOR);\n path.setAttribute(\"stroke-width\", \"3\");\n path.setAttribute(\"stroke-linecap\", \"round\");\n path.setAttribute(\"stroke-linejoin\", \"round\");\n tick.appendChild(path);\n container.appendChild(tick);\n if (this.tickTimer)\n clearTimeout(this.tickTimer);\n this.tickTimer = setTimeout(() => this.hideCaptureTick(), CardCamera.CAPTURE_TICK_MS);\n }\n hideCaptureTick() {\n var _a, _b;\n if (this.tickTimer) {\n clearTimeout(this.tickTimer);\n this.tickTimer = null;\n }\n (_a = document.getElementById(\"takePicTick\")) === null || _a === void 0 ? void 0 : _a.remove();\n (_b = document\n .getElementById(\"takePicInner\")) === null || _b === void 0 ? void 0 : _b.classList.replace(\"pw-opacity-100\", \"pw-opacity-60\");\n }\n setHint(text) {\n const hintEl = document.getElementById(\"pw-doc-hint\");\n if (hintEl)\n hintEl.innerHTML = text;\n }\n resizeElement() {\n const videoContainer = document.getElementById(camera_1.ELEMENT_IDS.VIDEO_CONTAINER);\n const videoElement = this.videoElement;\n if (videoElement && videoElement.videoWidth && videoElement.videoHeight) {\n const vpW = window.innerWidth;\n const vpH = window.innerHeight;\n const videoAspect = videoElement.videoWidth / videoElement.videoHeight;\n // Fit the container to the camera's native aspect ratio within the viewport\n let containerW;\n let containerH;\n if (vpW / vpH > videoAspect) {\n containerH = vpH; // viewport wider than video — constrain by height\n containerW = vpH * videoAspect;\n }\n else {\n containerW = vpW; // viewport taller than video — constrain by width\n containerH = vpW / videoAspect;\n }\n if (videoContainer) {\n videoContainer.style.width = `${containerW}px`;\n videoContainer.style.height = `${containerH}px`;\n }\n videoElement.style.width = \"100%\";\n videoElement.style.height = \"100%\";\n videoElement.style.minWidth = \"\";\n videoElement.style.minHeight = \"\";\n }\n this.cardDetectionSdk.resizeElement();\n }\n}\nCardCamera.AUTO_CAPTURE_HOLD_MS = 1500; // framed time to fill the ring\nCardCamera.AUTO_CAPTURE_GRAB_AT = 0.5; // fill ratio at which we secure the frame\nCardCamera.RING_RADIUS = 29;\nCardCamera.RING_CIRCUMFERENCE = 2 * Math.PI * CardCamera.RING_RADIUS;\nCardCamera.RING_FILL_COLOR = \"#16A34A\";\nCardCamera.CAPTURE_TICK_MS = 800;\nexports[\"default\"] = CardCamera;\n\n\n//# sourceURL=webpack://PWUISDK/./src/plugins/card-camera.ts?");
4004
4004
 
4005
4005
  /***/ }),
4006
4006
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pipwave-ekyc-uikit",
3
- "version": "3.1.0",
3
+ "version": "3.1.1",
4
4
  "description": "",
5
5
  "main": "dist/pw-bundle.js",
6
6
  "scripts": {
@@ -1,7 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(git branch *)"
5
- ]
6
- }
7
- }