npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@vizualkei/sophid-client-sdk

v0.33.1

Published

SophID Web Client SDK - Biometric authentication SDK for webapp integration

Readme

SophID Client SDK

TypeScript SDK that bridges web applications to the SophID Mobile app for biometric authentication. Supports phone browsers (direct deep-linking) and desktop browsers (QR code flow). Returns signed Biometric Result Tokens (BRT, ES256 JWT) from the SophID server.

Installation

pnpm add @vizualkei/sophid-client-sdk

In Next.js, add to next.config.js:

module.exports = {
  transpilePackages: ['@vizualkei/sophid-client-sdk'],
};

Quick Start (Recommended)

Use the SophidClientHelper singleton for the simplest integration. It handles BST fetching, SDK initialization, and BRT submission automatically.

import { sophidClientHelper } from '@vizualkei/sophid-client-sdk';

// Initialize once at app startup
const sophidClient = sophidClientHelper.init({
  biometricSessionUrl: '/api/biometric-session',
  biometricResultUrl: '/api/biometric-result',
  fetcher: (input, init) => fetch(input, init),
  onQrCode: (qrData) => showMyQrModal(qrData.deepLinkUrl, qrData.onCancel),
  onAppLaunchFailed: (context) => showInstallModal(context),
});

// Enroll a new user
const brt = await sophidClient.enrollUser({ userName: 'Jane Roe' });

// Authenticate
const authBrt = await sophidClient.authenticateUser();

Architecture

End-to-End Flow

PWA Server                  Web SDK                   Mobile App             SophID Server
    │                          │                          │                        │
    │◄── POST /biometric-session ──│                      │                        │
    │── { bst } ──────────────►│                          │                        │
    │                          │── deep-link (bst) ──────►│                        │
    │                          │                          │── POST /api/{op} ─────►│
    │                          │── long-poll ─────────────────────────────────────►│
    │                          │◄─────────────────────────────────── { brt } ──────│
    │◄── POST /biometric-result ──│                       │                        │
    │── validate BRT ──────────│                          │                        │

Security Tokens

| Token | Format | Purpose | |-------|--------|---------| | BST | JSON string ({"sid","ts","ebuid","sig"}) | Partner-issued session token, HMAC-signed | | BRT | ES256 JWT | Server-signed operation result | | ebuid | AES-256-GCM encrypted string | Binds session to a specific user |


SophidClientHelper (Recommended)

The SophidClientHelper is the recommended integration surface. It wraps the low-level SDK and automates the full BST→operation→BRT flow in a single method call.

Initialization

import { sophidClientHelper } from '@vizualkei/sophid-client-sdk';

const sophidClient = sophidClientHelper.init(config);

SophidClientHelperConfig

| Field | Type | Required | Description | |-------|------|----------|-------------| | biometricSessionUrl | string | Yes | Server endpoint to POST for BST (e.g., /api/biometric-session) | | biometricResultUrl | string | Yes | Server endpoint to POST BRT after operation (e.g., /api/biometric-result) | | fetcher | (input, init?) => Promise<Response> | Yes | Fetch function (inject auth headers here) | | biometricService | string | No | SophID server as host, host:port, or https://host[:port] (default: https://api.sophid.xyz:443) | | onQrCode | (qrData: QrCodeData) => QrCodeCleanupFn \| void | No | Callback for desktop browser QR flow | | onAppLaunchFailed | (context: AppLaunchFailureContext) => void | No | Called when a phone-browser deep-link likely failed to open the native app | | sdkOptions | SophIDMobileOptions | No | Additional SDK options (timeout, overlay) | | resultPayloadBuilder | (params) => Record<string, unknown> | No | Custom BRT submission payload builder |

Methods

enrollUser(user, enrollOptions?): Promise<string>

Enroll a new user. Fetches BST, launches mobile enrollment, submits BRT to server.

const brt = await sophidClient.enrollUser(
  { userName: 'Jane Roe', email: '[email protected]', phone: '+886900000000' },
  { email: '[email protected]', phoneNo: '+886900000000' }  // optional, included in BRT submission
);

| Parameter | Type | Description | |-----------|------|-------------| | user | UserDescriptor | { userName (required), email?, phone? } | | enrollOptions | EnrollUserOptions | { email?, phoneNo? } — extra fields sent with BRT |

restoreUser(): Promise<string>

Restore an existing user to a new device.

const brt = await sophidClient.restoreUser();

authenticateUser(): Promise<string>

Authenticate the enrolled user. Fetches BST, runs authentication, submits BRT.

const brt = await sophidClient.authenticateUser();

authenticateUserDirect(): Promise<string>

Authenticate without submitting the BRT to the server. Use this when a business route validates the BRT itself (e.g., biometric-gated password update).

const brt = await sophidClient.authenticateUserDirect();
// Pass brt to your own endpoint for validation
await fetch('/api/update-password', {
  method: 'POST',
  body: JSON.stringify({ brt, newPassword }),
});

unenrollUser(): Promise<string>

Remove the user's enrollment on the server.

const brt = await sophidClient.unenrollUser();

retrieveKey(): Promise<null>

Offline key retrieval (phone browsers only). Returns null; the user must manually paste the result from the mobile app.

await sophidClient.retrieveKey();
// Later, when user pastes JSON from mobile app:
const keyResult = sophidClient.parseRetrieveKeyResult(pastedJson);

clearUser(): Promise<void>

Clear local enrollment data on the device (phone browsers only, offline).

await sophidClient.clearUser();

parseRetrieveKeyResult(jsonString): KeyRetrievalResult

Parse the JSON string pasted from the mobile app after retrieveKey().

const result = sophidClient.parseRetrieveKeyResult('{"key":"...","userId":"..."}');
// result.key, result.userId, result.userDescriptor

Integration Examples

Example: No-Auth Demo Server

import { sophidClientHelper } from '@vizualkei/sophid-client-sdk';
import { showBiometricQrModal } from './utils/BiometricQrModal';

const sophidClient = sophidClientHelper.init({
  biometricSessionUrl: '/api/biometric-session',
  biometricResultUrl: '/api/biometric-results',
  fetcher: (input, init) => fetch(input, init),
  resultPayloadBuilder: ({ brt, enrollOptions }) => ({
    brt,
    email: enrollOptions?.email ?? null,
    phoneNo: enrollOptions?.phoneNo ?? null,
  }),
  onAppLaunchFailed: ({ installUrl, message }) => {
    showInstallModal({ installUrl, message });
  },
  onQrCode: ({ deepLinkUrl, onCancel }) =>
    showBiometricQrModal(deepLinkUrl, onCancel),
});

Example: Authenticated Server (Bearer Token)

import { sophidClientHelper } from '@vizualkei/sophid-client-sdk';
import { showBiometricQrModal } from '@/utils/biometricQrModal';
import { getAccessToken } from '@/lib/auth';

const fetchWithAuth = async (input: RequestInfo, init: RequestInit = {}) => {
  const token = await getAccessToken();
  return fetch(input, {
    ...init,
    headers: { ...(init.headers || {}), Authorization: `Bearer ${token}` },
  });
};

const sophidClient = sophidClientHelper.init({
  biometricSessionUrl: '/api/biometric-session',
  biometricResultUrl: '/api/biometric-result',
  fetcher: fetchWithAuth,
  onQrCode: ({ deepLinkUrl, onCancel }) =>
    showBiometricQrModal(deepLinkUrl, onCancel),
});

Singleton Access

After calling sophidClientHelper.init(), you can access the instance from anywhere:

import { sophidClientHelper } from '@vizualkei/sophid-client-sdk';

// Direct singleton methods (delegates to the initialized instance)
const brt = await sophidClientHelper.authenticateUser();

// Or get the instance explicitly
const client = sophidClientHelper.get();

Low-Level API: SophIDMobile

For advanced use cases, you can use the factory directly. You are responsible for BST fetching and BRT submission.

Create a Client

import { SophIDMobileFactory } from '@vizualkei/sophid-client-sdk';

const client = SophIDMobileFactory.create();

Initialize

client.initialize({
  timeoutMs: 90000,
  overlay: { appName: 'SophID Mobile' },
  biometricSessionToken: bst,  // from your server
  onQrCode: (qrData) => { /* show QR modal */ },
});

SophIDMobileOptions

| Field | Type | Default | Description | |-------|------|---------|-------------| | timeoutMs | number | 90000 | Polling timeout in milliseconds | | overlay | { appName? } | { appName: 'SophID Mobile' } | App naming config used in onAppLaunchFailed guidance | | biometricService | string | https://api.sophid.xyz:443 | SophID server as host, host:port, or https://host[:port] | | biometricSessionToken | string | — | BST from your server | | onQrCode | (qrData) => cleanup \| void | — | Desktop QR flow callback | | onAppLaunchFailed | (context) => void | — | Phone-browser fallback callback when the app likely failed to open |

Operations

// All operations return BRT (JWT string) except retrieveKey and clearUser
const enrollBrt = await client.enrollUser({ userName: 'Jane', email: '[email protected]' });
const restoreBrt = await client.restoreUser();
const authBrt = await client.authenticateUser();
const unenrollBrt = await client.unenrollUser();

// Offline operations (phone browser only)
await client.retrieveKey();  // returns null
const key = client.parseRetrieveKeyResult(pastedJson);
await client.clearUser();

// Utility
const version = await client.getVersion();
const enrolled = await client.getEnrolledUser();

Manual BST Flow

// 1. Get BST from your server
const { bst } = await fetch('/api/biometric-session', { method: 'POST' }).then(r => r.json());

// 2. Initialize with BST
client.initialize({ biometricSessionToken: bst });

// 3. Run operation
const brt = await client.authenticateUser();

// 4. Submit BRT to your server
await fetch('/api/biometric-result', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ brt }),
});

Desktop QR Flow

When running on a desktop browser, the SDK generates an HTTPS URL for QR code display instead of direct deep-linking.

How It Works

  1. SDK detects desktop browser via user-agent analysis.
  2. Generates a short HTTPS URL: https://{host}/f/{opCode}/{callbackId}?b={bioServer}&w={webServer}&u={userName}&...
  3. Calls onQrCode(qrData) — your app displays the QR code.
  4. User scans QR with phone camera → phone browser opens → redirects to mobile app.
  5. Mobile app completes biometric operation → SophID server signs and caches result.
  6. SDK receives result via long-polling → calls cleanup function → returns BRT.

QrCodeData

interface QrCodeData {
  deepLinkUrl: string;    // HTTPS URL to encode in QR code
  callbackId: string;     // Polling callback ID
  onCancel: () => void;   // Call when user cancels the QR modal
}

QR Callback Example

client.initialize({
  onQrCode: (qrData) => {
    const modal = showQrModal(qrData.deepLinkUrl, qrData.onCancel);
    return () => modal.dismiss();  // cleanup function called when operation completes
  },
});

Restrictions

  • retrieveKey() is not supported on desktop browsers (requires same-device clipboard). Throws: "This operation is supported only on mobile browsers".
  • clearUser() is not supported on desktop browsers.

App Launch Failure Detection

On phone browsers, the SDK cannot directly prove whether the native app is installed. Instead it:

  1. Attempts to open the deep link.
  2. Waits briefly for the page to go hidden.
  3. If the page stays visible, it infers that the app likely failed to open.

When that heuristic triggers, the SDK calls onAppLaunchFailed(context). Client apps should use that callback to show install/open guidance UI. On Android, the context includes the direct Google Play URL for SophID Mobile.


Phone vs Desktop Comparison

| Aspect | Phone Browser | Desktop Browser | |--------|---------------|-----------------| | Invocation | Direct deep-link (sophdplk://...) | QR code scan → HTTPS URL → redirect | | User Flow | Tap → app opens → complete biometric | Tap → scan QR → app opens → complete biometric | | Polling | Same server long-polling | Same server long-polling | | retrieveKey | Supported | Not supported | | clearUser | Supported | Not supported | | Fallback UI | Client-owned onAppLaunchFailed guidance | QR modal with cancel button |


Types

UserDescriptor

interface UserDescriptor {
  readonly userName: string;
  readonly email?: string;
  readonly phone?: string;
}

EnrollmentResult

interface EnrollmentResult {
  readonly userId: string;
  readonly enrollmentId: string;
  readonly enrolledAt: Date;
  readonly userDescriptor: UserDescriptor;
}

AuthenticationResult

interface AuthenticationResult {
  readonly userId: string;
  readonly userDescriptor: UserDescriptor;
  readonly authenticatedAt: Date;
}

KeyRetrievalResult

interface KeyRetrievalResult {
  readonly key: string;
  readonly userId: string;
  readonly userDescriptor: UserDescriptor;
}

PackageVersion

interface PackageVersion {
  readonly major: number;
  readonly minor: number;
  readonly patch: number;
  readonly build: number;
}

EnrollUserOptions

type EnrollUserOptions = {
  email?: string | null;
  phoneNo?: string | null;
};

SophIDError

class SophIDError extends Error {
  readonly code:
    | 'USER_EXISTS'
    | 'AUTH_FAILED'
    | 'USER_NOT_FOUND'
    | 'INVALID_BIOMETRIC'
    | 'USER_CANCELLED'
    | 'PARSE_ERROR';
}

Error Handling

All biometric operations throw SophIDError on failure or cancellation.

import { SophIDError } from '@vizualkei/sophid-client-sdk';

try {
  const brt = await sophidClient.authenticateUser();
} catch (err) {
  if (err instanceof SophIDError) {
    switch (err.code) {
      case 'USER_CANCELLED': /* user dismissed the operation */ break;
      case 'AUTH_FAILED': /* biometric mismatch */ break;
      case 'USER_NOT_FOUND': /* no enrollment found */ break;
      case 'USER_EXISTS': /* duplicate enrollment */ break;
      case 'INVALID_BIOMETRIC': /* biometric quality issue */ break;
      case 'PARSE_ERROR': /* malformed response */ break;
    }
  }
}

BST Format

BST is a JSON string issued by your server, not a JWT.

{"sid":"<uuid>","ts":1700000000000,"ebuid":"<base64url>","sig":"<base64url>"}

| Field | Type | Description | |-------|------|-------------| | sid | string | Session UUID | | ts | number | Timestamp (ms since epoch) | | ebuid | string | Encrypted biometric user ID (empty for enrollment) | | sig | string | HMAC-SHA256 of sid.ts.ebuid using partner API key |

TTL: 5 minutes. Single-use (consumed on validation).


BRT Claims

BRT is an ES256 JWT signed by the SophID server.

Common Claims

| Claim | Type | Description | |-------|------|-------------| | success | boolean | Operation outcome | | op | string | enroll \| authenticate \| unenroll \| checkin \| restore \| update-pin | | callbackId | string | Polling callback ID | | bst | string | Original BST (for partner validation) | | iss | string | JWT issuer | | aud | string | JWT audience | | jti | string | Unique token ID | | iat | number | Issued at (unix seconds) | | exp | number | Expiration (unix seconds) |

Success Claims by Operation

| Operation | Additional Claims | |-----------|-------------------| | enroll | userId, enrollmentId, userName, enrolledAt | | authenticate | userId, enrollmentId, userName, authenticatedAt, message | | restore | userId, enrollmentId, userName, enrolledAt | | unenroll | userId, enrollmentId, userName | | checkin | userId, eventId, ts | | update-pin | userId, enrollmentId, message |

Failure Claims

| Claim | Type | Description | |-------|------|-------------| | error | string | Error message | | reason | string | Failure reason | | code | string | Error code | | cancelled | boolean | true if user cancelled |