From a92d771ed8563c7ac1907fbdb3d3ea470b72a3d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sans=C3=A3o=20Araldi?= Date: Thu, 23 Jul 2026 17:28:59 -0300 Subject: [PATCH 1/4] feat: move face methods for useAziface hook --- apps/nextapp/app/home.tsx | 58 ++++++++--------- packages/aziface/package.json | 3 + packages/aziface/src/hooks/useAziface.ts | 40 ++++++++++++ packages/aziface/src/index.ts | 7 +- packages/aziface/src/module/aziface.ts | 82 +++++++++++++++++------- packages/aziface/src/types/aziface.ts | 5 ++ 6 files changed, 135 insertions(+), 60 deletions(-) create mode 100644 packages/aziface/src/hooks/useAziface.ts diff --git a/apps/nextapp/app/home.tsx b/apps/nextapp/app/home.tsx index 9291cbd..94e5580 100644 --- a/apps/nextapp/app/home.tsx +++ b/apps/nextapp/app/home.tsx @@ -1,22 +1,17 @@ 'use client'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import toast, { Toaster } from 'react-hot-toast'; import { useBiometricConfigs } from '@/services'; import { useUser } from '@/hooks'; import { - authenticate, dispose, - enroll, initialize, - liveness, - photoMatch, - photoScan, setLocale, - SessionError, InitializeParams, InitializeHeaders, Locale, + useAziface, } from '@azify/aziface-web'; import { FaceType } from '@/types/services.types'; import { LOCALES } from '@/constants'; @@ -28,6 +23,8 @@ export function Home() { const { data: configs } = useBiometricConfigs(); const { tokenBiometric, logout } = useUser(); + const { error, authenticate, enroll, liveness, photoMatch, photoScan } = + useAziface(); const onInitialize = (): void => { const params: InitializeParams = { @@ -72,33 +69,32 @@ export function Home() { }; const onFaceScan = async (type: FaceType): Promise => { - try { - switch (type) { - case 'enroll': - await enroll(); - break; - case 'authenticate': - await authenticate(); - break; - case 'liveness': - await liveness(); - break; - case 'photoMatch': - await photoMatch(); - break; - case 'photoScan': - await photoScan(); - break; - default: - toast.error(`Invalid face scan type: ${type}`); - break; - } - } catch (error) { - const sessionError = error as SessionError; - toast.error(sessionError.message); + switch (type) { + case 'enroll': + await enroll(); + break; + case 'authenticate': + await authenticate(); + break; + case 'liveness': + await liveness(); + break; + case 'photoMatch': + await photoMatch(); + break; + case 'photoScan': + await photoScan(); + break; + default: + toast.error(`Invalid face scan type: ${type}`); + break; } }; + useEffect(() => { + if (error) toast.error(error.message); + }, [error]); + return (
diff --git a/packages/aziface/package.json b/packages/aziface/package.json index 2f1f1c9..f673855 100644 --- a/packages/aziface/package.json +++ b/packages/aziface/package.json @@ -21,5 +21,8 @@ "publishConfig": { "registry": "https://registry.npmjs.org/", "access": "public" + }, + "peerDependencies": { + "react": "*" } } diff --git a/packages/aziface/src/hooks/useAziface.ts b/packages/aziface/src/hooks/useAziface.ts new file mode 100644 index 0000000..8f6ba78 --- /dev/null +++ b/packages/aziface/src/hooks/useAziface.ts @@ -0,0 +1,40 @@ +import { useSyncExternalStore } from 'react'; +import { AzifaceController, controller } from '../module/aziface'; +import { SnapshotProps } from '../types/aziface'; + +export function useAziface() { + const state = useSyncExternalStore( + AzifaceController.subscribe, + AzifaceController.getSnapshot, + AzifaceController.getSnapshot, + ); + + async function enroll(): Promise { + return await controller.enroll(); + } + + async function authenticate(): Promise { + return await controller.authenticate(); + } + + async function liveness(): Promise { + return await controller.liveness(); + } + + async function photoMatch(): Promise { + return await controller.photoMatch(); + } + + async function photoScan(): Promise { + return await controller.photoScan(); + } + + return { + enroll, + authenticate, + liveness, + photoMatch, + photoScan, + ...state, + }; +} diff --git a/packages/aziface/src/index.ts b/packages/aziface/src/index.ts index 00bc12f..02c4990 100644 --- a/packages/aziface/src/index.ts +++ b/packages/aziface/src/index.ts @@ -1,16 +1,13 @@ export { - authenticate, dispose, - enroll, initialize, - liveness, - photoMatch, - photoScan, resetTheme, withTheme, setLocale, } from './module/aziface'; +export { useAziface } from './hooks/useAziface'; + export type { CancelLocation, DisposeCallback, diff --git a/packages/aziface/src/module/aziface.ts b/packages/aziface/src/module/aziface.ts index 7ad1c1c..e75bd1c 100644 --- a/packages/aziface/src/module/aziface.ts +++ b/packages/aziface/src/module/aziface.ts @@ -19,6 +19,7 @@ import { InitializeHeaders, Locale, MethodError, + SnapshotProps, Style, } from '../types/aziface'; import { i18n } from '../i18n'; @@ -29,12 +30,33 @@ export class AzifaceController implements Controller { public static isInitialized: boolean = false; public static isDevelopment: boolean = false; private static latestExternalDatabaseRefID: string = ''; + private static listeners: Set = new Set(); + private static currentSnapshot: SnapshotProps = { + data: undefined, + error: undefined, + }; + private static oldSnapshot: SnapshotProps = { + data: undefined, + error: undefined, + }; public static deviceKeyIdentifier: string = ''; public static baseUrl: string = ''; public static headers: InitializeHeaders = {} as InitializeHeaders; private faceTecSDKInstance: FaceTecSDKInstance | null = null; private internalID: number | undefined = undefined; + public static subscribe(listener: VoidFunction): VoidFunction { + AzifaceController.listeners.add(listener); + + return () => { + AzifaceController.listeners.delete(listener); + }; + } + + public static getSnapshot(): SnapshotProps { + return AzifaceController.currentSnapshot; + } + public initialize = ( init: Initialize, callback: InitializeCallback, @@ -195,6 +217,12 @@ export class AzifaceController implements Controller { AzifaceController.baseUrl = init?.params?.baseUrl || ''; AzifaceController.isDevelopment = init?.params?.isDevelopment || false; AzifaceController.headers = init?.headers; + AzifaceController.currentSnapshot = { + data: undefined, + error: undefined, + }; + + this.onStateChange(); }; private cleanup = (): void => { @@ -204,11 +232,16 @@ export class AzifaceController implements Controller { AzifaceController.deviceKeyIdentifier = ''; AzifaceController.baseUrl = ''; AzifaceController.headers = {} as InitializeHeaders; + AzifaceController.currentSnapshot = { + data: undefined, + error: undefined, + }; window.removeEventListener('click', applyResponsiveStyles); window.clearInterval(this.internalID); this.withTheme(); + this.onStateChange(); }; private generateExternalDatabaseRefID = (): string => @@ -229,9 +262,30 @@ export class AzifaceController implements Controller { const isError = faceTecSessionStatus !== FaceTecSDK.FaceTecSessionStatus.SessionCompleted; - if (isError) { - throw new SessionError(faceTecSessionStatus); - } + + AzifaceController.currentSnapshot = { + data: isError ? undefined : faceTecSessionStatus, + error: isError ? new SessionError(faceTecSessionStatus) : undefined, + }; + + this.onStateChange(); + }; + + private onStateChange = (): void => { + const didChange = + AzifaceController.currentSnapshot.data !== + AzifaceController.oldSnapshot.data || + AzifaceController.currentSnapshot.error !== + AzifaceController.oldSnapshot.error; + + if (!didChange) return; + + AzifaceController.oldSnapshot = { + data: AzifaceController.currentSnapshot.data, + error: AzifaceController.currentSnapshot.error, + }; + + AzifaceController.listeners.forEach(listener => listener()); }; private onAttach = () => { @@ -259,7 +313,7 @@ export class AzifaceController implements Controller { }; } -const controller = new AzifaceController(); +export const controller = new AzifaceController(); export function initialize( init: Initialize, @@ -272,26 +326,6 @@ export function dispose(callback: DisposeCallback): void { controller.dispose(callback); } -export async function enroll(): Promise { - return await controller.enroll(); -} - -export async function authenticate(): Promise { - return await controller.authenticate(); -} - -export async function liveness(): Promise { - return await controller.liveness(); -} - -export async function photoMatch(): Promise { - return await controller.photoMatch(); -} - -export async function photoScan(): Promise { - return await controller.photoScan(); -} - export function withTheme(overrides?: Style): void { controller.withTheme(overrides); } diff --git a/packages/aziface/src/types/aziface.ts b/packages/aziface/src/types/aziface.ts index 51fb9f4..a7713ec 100644 --- a/packages/aziface/src/types/aziface.ts +++ b/packages/aziface/src/types/aziface.ts @@ -345,6 +345,11 @@ export type Locale = | 'vi' | 'zh'; +export interface SnapshotProps { + data?: SessionCode; + error?: Error; +} + export interface Controller { initialize: (init: Initialize, callback: InitializeCallback) => void; dispose: (callback: DisposeCallback) => void; From edf55e75fe8633c988114017e25ebb05a973bc73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sans=C3=A3o=20Araldi?= Date: Thu, 23 Jul 2026 17:29:13 -0300 Subject: [PATCH 2/4] docs: update documentation --- README.md | 44 +++++----- packages/aziface/README.md | 159 +++++++++++++++++++++---------------- 2 files changed, 114 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index 740072d..95ee340 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,11 @@ Monorepo for the **Aziface Web SDK** (`@azify/aziface-web`) and reference demo a ## Packages & apps -| Path | Description | -| --- | --- | +| Path | Description | +| ---------------------------------------- | ------------------------------------------------------------------------------------------- | | [`packages/aziface`](./packages/aziface) | Publishable SDK — biometric flows for React (enroll, authenticate, liveness, document scan) | -| [`apps/nextapp`](./apps/nextapp) | Next.js demo app | -| [`apps/viteapp`](./apps/viteapp) | Vite + React demo app | +| [`apps/nextapp`](./apps/nextapp) | Next.js demo app | +| [`apps/viteapp`](./apps/viteapp) | Vite + React demo app | **SDK documentation (installation, API, types):** [packages/aziface/README.md](./packages/aziface/README.md) @@ -28,15 +28,15 @@ npm ci ### Development -| Command | Description | -| --- | --- | -| `npm run dev` | Start all workspaces that expose a `dev` script | -| `npm run next:dev` | Next.js demo at `http://localhost:3000` | -| `npm run vite:dev` | Vite demo at `http://localhost:5173` | -| `npm run build` | Build all workspaces | -| `npm run next:build` | Build Next.js demo only | -| `npm run vite:build` | Build Vite demo only | -| `npm run clean` | Remove build artifacts | +| Command | Description | +| -------------------- | ----------------------------------------------- | +| `npm run dev` | Start all workspaces that expose a `dev` script | +| `npm run next:dev` | Next.js demo at `http://localhost:3000` | +| `npm run vite:dev` | Vite demo at `http://localhost:5173` | +| `npm run build` | Build all workspaces | +| `npm run next:build` | Build Next.js demo only | +| `npm run vite:build` | Build Vite demo only | +| `npm run clean` | Remove build artifacts | ### SDK package only @@ -69,10 +69,10 @@ aziface-web/ **Commit → release mapping** (configured in `.releaserc.js`): -| Commit type | Version bump | -| --- | --- | -| `feat`, `chore` | minor | -| `fix`, `perf`, `style` | patch | +| Commit type | Version bump | +| ------------------------- | --------------------------------- | +| `feat`, `chore` | minor | +| `fix`, `perf`, `style` | patch | | `BREAKING CHANGE` in body | major (default analyzer behavior) | ### CI @@ -104,11 +104,11 @@ Requires `GH_TOKEN` and `NPM_TOKEN` with publish access to the `@azify` scope. ### GitHub Actions secrets -| Secret | Purpose | -| --- | --- | -| `GH_TOKEN_V2` | Push release commits, create GitHub releases | -| `NPM_TOKEN_V2` | Publish `@azify/aziface-web` to npm (Automation token with **Read and Write** on `@azify`) | -| `AZIFACE_ASSETS_URL` | FaceTec assets (CI/internal use) | +| Secret | Purpose | +| -------------------- | ------------------------------------------------------------------------------------------ | +| `GH_TOKEN_V2` | Push release commits, create GitHub releases | +| `NPM_TOKEN_V2` | Publish `@azify/aziface-web` to npm (Automation token with **Read and Write** on `@azify`) | +| `AZIFACE_ASSETS_URL` | FaceTec assets (CI/internal use) | ## License diff --git a/packages/aziface/README.md b/packages/aziface/README.md index 0bc92f0..3ff087c 100644 --- a/packages/aziface/README.md +++ b/packages/aziface/README.md @@ -18,11 +18,6 @@ Web SDK adapter for React — face enrollment, authentication, liveness, and doc - [`Properties`](#properties) - [`dispose`](#dispose) - [`Properties`](#properties-1) - - [`enroll`](#enroll) - - [`authenticate`](#authenticate) - - [`liveness`](#liveness) - - [`photoScan`](#photoscan) - - [`photoMatch`](#photomatch) - [`withTheme`](#withtheme) - [`Properties`](#properties-2) - [`Custom images`](#custom-images) @@ -30,6 +25,14 @@ Web SDK adapter for React — face enrollment, authentication, liveness, and doc - [`resetTheme`](#resettheme) - [`setLocale`](#setlocale) - [`Properties`](#properties-3) +- [Hooks] + - [`useAziface`](#useaziface) + - [`Properties`](#properties-4) + - [`enroll`](#enroll) + - [`authenticate`](#authenticate) + - [`liveness`](#liveness) + - [`photoScan`](#photoscan) + - [`photoMatch`](#photomatch) - [Styles](#styles) - [Types](#types) - [`Initialize`](#initialize-1) @@ -57,21 +60,25 @@ Web SDK adapter for React — face enrollment, authentication, liveness, and doc - **FaceTec static assets** hosted by your application (not bundled in the npm package) - Valid Aziface credentials: `deviceKeyIdentifier`, `baseUrl`, and `x-token-bearer` +
+ ## Installation ```bash npm i @azify/aziface-web ``` +
+ ## Static assets The SDK expects FaceTec resources to be available at runtime. Host the following paths in your app's public directory: -| Path | Purpose | -| --- | --- | -| `/core/facetec/FaceTecSDK.js` | FaceTec browser SDK (loaded via `