From 556e56f913c06da814eda311036249b541ee567a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sans=C3=A3o=20Araldi?= Date: Thu, 2 Jul 2026 17:38:11 -0300 Subject: [PATCH 1/5] feat: add responsive design for enroll, authenticate and liveness check --- apps/nextapp/app/home.tsx | 3 +- packages/aziface/src/module/aziface.ts | 31 ++++++- packages/aziface/src/styles/aziface.css | 34 ++++++-- packages/aziface/src/utils/styles.ts | 102 ++++++++++++++++++++++++ 4 files changed, 160 insertions(+), 10 deletions(-) diff --git a/apps/nextapp/app/home.tsx b/apps/nextapp/app/home.tsx index 9291cbd..f1b86f1 100644 --- a/apps/nextapp/app/home.tsx +++ b/apps/nextapp/app/home.tsx @@ -41,7 +41,7 @@ export function Home() { 'x-only-raw-analysis': '1', }; - initialize({ params, headers }, initialized => { + initialize({ params, headers }, async initialized => { const error = initialized.error; setIsInitialized(initialized.isSuccess); @@ -49,6 +49,7 @@ export function Home() { toast.error(`(${error.code}) - ${error.cause}`); } else { setLocale(i18n); + await liveness(); } }); }; diff --git a/packages/aziface/src/module/aziface.ts b/packages/aziface/src/module/aziface.ts index f55f021..d007e89 100644 --- a/packages/aziface/src/module/aziface.ts +++ b/packages/aziface/src/module/aziface.ts @@ -7,7 +7,7 @@ import { FaceTecSDK as FaceTecSDKType } from '../types/FaceTecSDK'; import { SessionError } from '../errors/errors'; import { SessionRequestProcessor } from '../services/request-processor'; import { applyTheme, getBackgroundColor } from '../styles/theme'; -import { getInitializationErrorCauseByCode } from '../utils'; +import { getInitializationErrorCauseByCode, styleObserver } from '../utils'; import { Controller, DisposeCallback, @@ -30,6 +30,8 @@ export class AzifaceController implements Controller { public static baseUrl: string = ''; public static headers: InitializeHeaders = {} as InitializeHeaders; private faceTecSDKInstance: FaceTecSDKInstance | null = null; + private internalID: number | undefined = undefined; + public initialize = ( init: Initialize, callback: InitializeCallback, @@ -200,6 +202,9 @@ export class AzifaceController implements Controller { AzifaceController.baseUrl = ''; AzifaceController.headers = {} as InitializeHeaders; + window.removeEventListener('click', styleObserver); + window.clearInterval(this.internalID); + this.withTheme(); }; @@ -216,6 +221,9 @@ export class AzifaceController implements Controller { private onInitializationError = (): void => this.cleanup(); private onComplete = (faceTecSessionStatus: FaceTecSessionStatus): void => { + window.removeEventListener('click', styleObserver); + window.clearInterval(this.internalID); + const isError = faceTecSessionStatus !== FaceTecSDK.FaceTecSessionStatus.SessionCompleted; if (isError) { @@ -233,6 +241,27 @@ export class AzifaceController implements Controller { } else { throw new SessionError(MethodError.NotInitialized); } + + // DOM_FT_frameGetReadyOvalMask + // DOM_FT_frameOutsideOvalSVG + // const frameGetReadyOvalPath = document.getElementById( + // 'DOM_FT_frameGetReadyOvalPath', + // ) as SVGPathElement | null; + // const frameOvalPath = document.getElementById('DOM_FT_frameOvalPath'); + // const frameOutsideOvalMask = document.getElementById( + // 'DOM_FT_frameOutsideOvalMask', + // ); + window.addEventListener('click', styleObserver); + + this.internalID = window.setInterval(() => { + const windowClickEvent = new MouseEvent('click', { + bubbles: true, + cancelable: true, + view: window, + }); + + window.dispatchEvent(windowClickEvent); + }, 250); }; } diff --git a/packages/aziface/src/styles/aziface.css b/packages/aziface/src/styles/aziface.css index 4dc44ab..31ae617 100644 --- a/packages/aziface/src/styles/aziface.css +++ b/packages/aziface/src/styles/aziface.css @@ -1,13 +1,31 @@ -#DOM_FT_PRIMARY_TOPLEVEL_mainContainer - > #DOM_FT_overlayContainer - > #DOM_FT_idScanDesktopSubframeInterfaceContainer - > #DOM_FT_idScanButtonContainer { +#DOM_FT_idScanButtonContainer { border: none !important; } -#DOM_FT_PRIMARY_TOPLEVEL_mainContainer - > #DOM_FT_orientationContainer - > #DOM_FT_orientationContentContainer - > #DOM_FT_orientationImageContainer { +#DOM_FT_orientationImageContainer { margin: 0 auto !important; } + +#DOM_FT_mainInterfaceNonOverlayContainer { + @media (max-width: 768px) { + border: none !important; + } +} + +#DOM_FT_readyScreenHeaderElement { + @media (max-width: 768px) { + font-size: 1rem !important; + } +} + +#DOM_FT_readyScreenSubtextElement { + @media (max-width: 768px) { + font-size: 0.75rem !important; + } +} + +#DOM_FT_yourAppLogoContainer { + @media (max-width: 768px) { + height: calc(100% - 360px) !important; + } +} diff --git a/packages/aziface/src/utils/styles.ts b/packages/aziface/src/utils/styles.ts index e923e4a..bb0c427 100644 --- a/packages/aziface/src/utils/styles.ts +++ b/packages/aziface/src/utils/styles.ts @@ -1,12 +1,114 @@ import { CancelLocation } from '../types/aziface'; import type { FaceTecCancelButtonLocation } from '../types/FaceTecCustomization'; +const PREFIX = 'DOM_FT_'; const CANCEL_BUTTON_LOCATION = { Disabled: 0 as FaceTecCancelButtonLocation, TopLeft: 1 as FaceTecCancelButtonLocation, TopRight: 2 as FaceTecCancelButtonLocation, } as const; +function getElementById(id: string): HTMLElement | null { + return document.getElementById(id); +} + +function getStyle(id: string): string { + const element = getElementById(id); + return element ? element.getAttribute('style') || '' : ''; +} + +function setStyle(id: string, style: string): void { + const element = getElementById(id); + if (element) element.setAttribute('style', style); +} + +function isRequestingPermission(): boolean { + const ID = `${PREFIX}cameraPermissionsScreen`; + const currentStyle = getStyle(ID); + return currentStyle.includes('display: flex;'); +} + +function adjustFrameContainer(): void { + const { innerWidth } = window; + if (innerWidth > 768) return; + + const ID = `${PREFIX}frameContainer`; + if (!!getElementById(ID)) { + const currentStyle = getStyle(ID); + const newStyle = 'top: 0px !important; left: 0px !important;'; + const isRequesting = isRequestingPermission(); + const hasStyle = currentStyle.includes(newStyle); + + if (!hasStyle && !isRequesting) setStyle(ID, `${currentStyle} ${newStyle}`); + else if (hasStyle && isRequesting) { + const replatedStyle = currentStyle.replace( + newStyle, + 'top: -2px !important; left: -2px !important;', + ); + setStyle(ID, replatedStyle); + } + } +} + +function adjustCancelButtonElement(): void { + const { innerWidth } = window; + if (innerWidth > 768) return; + + const ID = `${PREFIX}cancelButtonElement`; + if (!!getElementById(ID)) { + if (isRequestingPermission()) { + const style = + 'left: 0px !important; height: 16px; width: 16px; padding: 15px; margin: 0px; opacity: 1; display: block; transition: opacity 500ms;'; + setStyle(ID, style); + } else { + const left = innerWidth > 400 ? '20%' : '22%'; + const newStyle = `left: ${left} !important;`; + const currentStyle = getStyle(ID); + if (!currentStyle.includes(newStyle)) { + setStyle(ID, `${currentStyle} ${newStyle}`); + } + } + } +} + +function generateDPath(): string | null { + const { innerWidth } = window; + if (innerWidth > 768) return null; + + const VIEW_BOX = 640; + const OVAL = 158; + const displacement = OVAL.toFixed(1); + const rx = (79).toFixed(1); + const ry = (116).toFixed(1); + const width = (VIEW_BOX / 2 - OVAL / 2).toFixed(1); + + return `m${width} 180 a${rx} ${ry} 0 1 0 ${displacement} 0 a${rx} ${ry} 0 1 0 -${displacement} 0`; +} + +function removeElementById(id: string): void { + if (window.innerWidth > 768) return; + + const element = getElementById(id); + if (element) element.remove(); +} + +function setDPath(id: string): void { + const element = getElementById(id); + const d = generateDPath(); + if (element && d) element.setAttribute('d', d); +} + +export function styleObserver(): void { + removeElementById(`${PREFIX}ovalSpinner1`); + removeElementById(`${PREFIX}ovalSpinner2`); + + adjustCancelButtonElement(); + adjustFrameContainer(); + + setDPath(`${PREFIX}frameGetReadyOvalPath`); + setDPath(`${PREFIX}frameOvalPath`); +} + export function resolveCancelLocation( location?: CancelLocation, ): FaceTecCancelButtonLocation { From d8e5d9e0494972dc9c49957ad638bf6e970ce782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sans=C3=A3o=20Araldi?= Date: Thu, 2 Jul 2026 17:42:10 -0300 Subject: [PATCH 2/5] refactor: remove unnecessary liveness in initialize callback --- apps/nextapp/app/home.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/nextapp/app/home.tsx b/apps/nextapp/app/home.tsx index f1b86f1..9291cbd 100644 --- a/apps/nextapp/app/home.tsx +++ b/apps/nextapp/app/home.tsx @@ -41,7 +41,7 @@ export function Home() { 'x-only-raw-analysis': '1', }; - initialize({ params, headers }, async initialized => { + initialize({ params, headers }, initialized => { const error = initialized.error; setIsInitialized(initialized.isSuccess); @@ -49,7 +49,6 @@ export function Home() { toast.error(`(${error.code}) - ${error.cause}`); } else { setLocale(i18n); - await liveness(); } }); }; From a3dbe40ce8e49a4f03222b0884753dfb195330f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sans=C3=A3o=20Araldi?= Date: Thu, 2 Jul 2026 17:43:15 -0300 Subject: [PATCH 3/5] refactor: remove unnecessary classes id --- packages/aziface/src/module/aziface.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packages/aziface/src/module/aziface.ts b/packages/aziface/src/module/aziface.ts index d007e89..b9cbe4c 100644 --- a/packages/aziface/src/module/aziface.ts +++ b/packages/aziface/src/module/aziface.ts @@ -242,15 +242,6 @@ export class AzifaceController implements Controller { throw new SessionError(MethodError.NotInitialized); } - // DOM_FT_frameGetReadyOvalMask - // DOM_FT_frameOutsideOvalSVG - // const frameGetReadyOvalPath = document.getElementById( - // 'DOM_FT_frameGetReadyOvalPath', - // ) as SVGPathElement | null; - // const frameOvalPath = document.getElementById('DOM_FT_frameOvalPath'); - // const frameOutsideOvalMask = document.getElementById( - // 'DOM_FT_frameOutsideOvalMask', - // ); window.addEventListener('click', styleObserver); this.internalID = window.setInterval(() => { From 6fe49a5a9ad070f2835c7ee1d3232caeb45994bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sans=C3=A3o=20Araldi?= Date: Thu, 2 Jul 2026 17:50:02 -0300 Subject: [PATCH 4/5] perf: remove `MouseEvent` inside of the `setInternal` --- packages/aziface/src/module/aziface.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/aziface/src/module/aziface.ts b/packages/aziface/src/module/aziface.ts index b9cbe4c..7742f16 100644 --- a/packages/aziface/src/module/aziface.ts +++ b/packages/aziface/src/module/aziface.ts @@ -244,13 +244,13 @@ export class AzifaceController implements Controller { window.addEventListener('click', styleObserver); - this.internalID = window.setInterval(() => { - const windowClickEvent = new MouseEvent('click', { - bubbles: true, - cancelable: true, - view: window, - }); + const windowClickEvent = new MouseEvent('click', { + bubbles: true, + cancelable: true, + view: window, + }); + this.internalID = window.setInterval(() => { window.dispatchEvent(windowClickEvent); }, 250); }; From b48f3a1cc5b8751473efe87c246f5d7ac9eea18d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sans=C3=A3o=20Araldi?= Date: Fri, 3 Jul 2026 11:34:03 -0300 Subject: [PATCH 5/5] feat: add responsive design for photo scan and photo match flow --- packages/aziface/src/module/aziface.ts | 11 ++- packages/aziface/src/utils/styles.ts | 113 ++++++++++++++++++------- 2 files changed, 91 insertions(+), 33 deletions(-) diff --git a/packages/aziface/src/module/aziface.ts b/packages/aziface/src/module/aziface.ts index 7742f16..7ad1c1c 100644 --- a/packages/aziface/src/module/aziface.ts +++ b/packages/aziface/src/module/aziface.ts @@ -7,7 +7,10 @@ import { FaceTecSDK as FaceTecSDKType } from '../types/FaceTecSDK'; import { SessionError } from '../errors/errors'; import { SessionRequestProcessor } from '../services/request-processor'; import { applyTheme, getBackgroundColor } from '../styles/theme'; -import { getInitializationErrorCauseByCode, styleObserver } from '../utils'; +import { + getInitializationErrorCauseByCode, + applyResponsiveStyles, +} from '../utils'; import { Controller, DisposeCallback, @@ -202,7 +205,7 @@ export class AzifaceController implements Controller { AzifaceController.baseUrl = ''; AzifaceController.headers = {} as InitializeHeaders; - window.removeEventListener('click', styleObserver); + window.removeEventListener('click', applyResponsiveStyles); window.clearInterval(this.internalID); this.withTheme(); @@ -221,7 +224,7 @@ export class AzifaceController implements Controller { private onInitializationError = (): void => this.cleanup(); private onComplete = (faceTecSessionStatus: FaceTecSessionStatus): void => { - window.removeEventListener('click', styleObserver); + window.removeEventListener('click', applyResponsiveStyles); window.clearInterval(this.internalID); const isError = @@ -242,7 +245,7 @@ export class AzifaceController implements Controller { throw new SessionError(MethodError.NotInitialized); } - window.addEventListener('click', styleObserver); + window.addEventListener('click', applyResponsiveStyles); const windowClickEvent = new MouseEvent('click', { bubbles: true, diff --git a/packages/aziface/src/utils/styles.ts b/packages/aziface/src/utils/styles.ts index bb0c427..29fa60b 100644 --- a/packages/aziface/src/utils/styles.ts +++ b/packages/aziface/src/utils/styles.ts @@ -2,6 +2,7 @@ import { CancelLocation } from '../types/aziface'; import type { FaceTecCancelButtonLocation } from '../types/FaceTecCustomization'; const PREFIX = 'DOM_FT_'; +const SAFE_MARGIN = 48; const CANCEL_BUTTON_LOCATION = { Disabled: 0 as FaceTecCancelButtonLocation, TopLeft: 1 as FaceTecCancelButtonLocation, @@ -22,58 +23,110 @@ function setStyle(id: string, style: string): void { if (element) element.setAttribute('style', style); } +function isMobile(): boolean { + return window.innerWidth <= 768; +} + function isRequestingPermission(): boolean { - const ID = `${PREFIX}cameraPermissionsScreen`; - const currentStyle = getStyle(ID); + const id = `${PREFIX}cameraPermissionsScreen`; + const currentStyle = getStyle(id); return currentStyle.includes('display: flex;'); } function adjustFrameContainer(): void { - const { innerWidth } = window; - if (innerWidth > 768) return; + if (!isMobile()) return; - const ID = `${PREFIX}frameContainer`; - if (!!getElementById(ID)) { - const currentStyle = getStyle(ID); + const id = `${PREFIX}frameContainer`; + if (!!getElementById(id)) { + const currentStyle = getStyle(id); const newStyle = 'top: 0px !important; left: 0px !important;'; const isRequesting = isRequestingPermission(); const hasStyle = currentStyle.includes(newStyle); - if (!hasStyle && !isRequesting) setStyle(ID, `${currentStyle} ${newStyle}`); - else if (hasStyle && isRequesting) { + if (!hasStyle && !isRequesting) { + setStyle(id, `${currentStyle} ${newStyle}`); + } else if (hasStyle && isRequesting) { const replatedStyle = currentStyle.replace( newStyle, 'top: -2px !important; left: -2px !important;', ); - setStyle(ID, replatedStyle); + setStyle(id, replatedStyle); + } + } +} + +function adjustCancelButtonElementBase(id: string, style: string): void { + if (!isMobile() || !getElementById(id)) return; + + if (isRequestingPermission()) { + setStyle(id, style); + } else { + const left = window.innerWidth > 400 ? '20%' : '22%'; + const newStyle = `left: ${left} !important;`; + const currentStyle = getStyle(id); + if (!currentStyle.includes(newStyle)) { + setStyle(id, `${currentStyle} ${newStyle}`); } } } function adjustCancelButtonElement(): void { - const { innerWidth } = window; - if (innerWidth > 768) return; - - const ID = `${PREFIX}cancelButtonElement`; - if (!!getElementById(ID)) { - if (isRequestingPermission()) { - const style = - 'left: 0px !important; height: 16px; width: 16px; padding: 15px; margin: 0px; opacity: 1; display: block; transition: opacity 500ms;'; - setStyle(ID, style); - } else { - const left = innerWidth > 400 ? '20%' : '22%'; - const newStyle = `left: ${left} !important;`; - const currentStyle = getStyle(ID); - if (!currentStyle.includes(newStyle)) { - setStyle(ID, `${currentStyle} ${newStyle}`); + adjustCancelButtonElementBase( + `${PREFIX}cancelButtonElement`, + 'left: 0px !important; height: 16px; width: 16px; padding: 15px; margin: 0px; opacity: 1; display: block; transition: opacity 500ms;', + ); + adjustCancelButtonElementBase( + `${PREFIX}idScanCancelButtonElement`, + 'height: 16px; width: 16px; padding: 8px; margin: 8px; top: 0px; left: 0px; opacity: 1; display: flex; transition: opacity 2000ms;', + ); +} + +function adjustIdScanMask(): void { + if (!isMobile()) return; + + const startY = '12px'; + const mask = getElementById(`${PREFIX}idScanCaptureFrameMask`); + if (mask) { + const [, rect] = mask.children; + rect.setAttribute('style', ''); + rect.setAttribute('y', startY); + } + + const rectId = `${PREFIX}idScanCaptureFrameRect`; + const rect = getElementById(rectId); + if (rect) { + setStyle(rectId, ''); + rect.setAttribute('y', startY); + } +} + +function adjustOCRFormContainer(): void { + if (!isMobile()) return; + + const id = `${PREFIX}ocrFormContainer`; + const element = getElementById(id); + if (element) { + const [form] = element.children; + + for (const child of form.children) { + if (child.tagName === 'SECTION') { + for (const sectionChild of child.children) { + if (sectionChild.tagName === 'DIV') { + const width = window.innerWidth - SAFE_MARGIN; + const currentStyle = sectionChild.getAttribute('style') || ''; + const newStyle = `margin: 0px auto; display: block; width: ${width}px;`; + if (!currentStyle.includes(newStyle)) { + sectionChild.setAttribute('style', newStyle); + } + } + } } } } } function generateDPath(): string | null { - const { innerWidth } = window; - if (innerWidth > 768) return null; + if (!isMobile()) return null; const VIEW_BOX = 640; const OVAL = 158; @@ -86,7 +139,7 @@ function generateDPath(): string | null { } function removeElementById(id: string): void { - if (window.innerWidth > 768) return; + if (!isMobile()) return; const element = getElementById(id); if (element) element.remove(); @@ -98,12 +151,14 @@ function setDPath(id: string): void { if (element && d) element.setAttribute('d', d); } -export function styleObserver(): void { +export function applyResponsiveStyles(): void { removeElementById(`${PREFIX}ovalSpinner1`); removeElementById(`${PREFIX}ovalSpinner2`); adjustCancelButtonElement(); adjustFrameContainer(); + adjustIdScanMask(); + adjustOCRFormContainer(); setDPath(`${PREFIX}frameGetReadyOvalPath`); setDPath(`${PREFIX}frameOvalPath`);