typescript
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
const HEX_COLOR_PATTERN = /^#?([0-9a-fA-F]{6})$/;
|
||||
|
||||
const clamp01 = (value) => Math.min(1, Math.max(0, value));
|
||||
const clamp01 = (value: number): number => Math.min(1, Math.max(0, value));
|
||||
|
||||
const clampRange = (value, min, max) => Math.min(max, Math.max(min, value));
|
||||
const clampRange = (value: number, min: number, max: number): number => Math.min(max, Math.max(min, value));
|
||||
|
||||
const hslToHex = (h, s, l) => {
|
||||
const hslToHex = (h: number, s: number, l: number): string => {
|
||||
const normalizedH = ((h % 360) + 360) % 360;
|
||||
const sat = clamp01(s);
|
||||
const light = clamp01(l);
|
||||
@@ -48,7 +48,13 @@ const hslToHex = (h, s, l) => {
|
||||
return `#${((sr << 16) | (sg << 8) | sb).toString(16).padStart(6, '0')}`;
|
||||
};
|
||||
|
||||
const rgbToHsl = ({ r, g, b }) => {
|
||||
interface RgbColor {
|
||||
r: number;
|
||||
g: number;
|
||||
b: number;
|
||||
}
|
||||
|
||||
const rgbToHsl = ({ r, g, b }: RgbColor) => {
|
||||
const rn = r / 255;
|
||||
const gn = g / 255;
|
||||
const bn = b / 255;
|
||||
@@ -79,7 +85,7 @@ const rgbToHsl = ({ r, g, b }) => {
|
||||
return { h: hue, s: clamp01(saturation), l: clamp01(lightness) };
|
||||
};
|
||||
|
||||
export const hexToRgb = (input) => {
|
||||
export const hexToRgb = (input: string | null | undefined): (RgbColor & { hex: string }) | null => {
|
||||
if (!input) return null;
|
||||
const match = HEX_COLOR_PATTERN.exec(input.trim());
|
||||
if (!match) return null;
|
||||
@@ -92,7 +98,7 @@ export const hexToRgb = (input) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const relativeLuminance = ({ r, g, b }) => {
|
||||
export const relativeLuminance = ({ r, g, b }: RgbColor): number => {
|
||||
const toLinear = (channel) => {
|
||||
const normalized = channel / 255;
|
||||
return normalized <= 0.03928
|
||||
@@ -104,12 +110,12 @@ export const relativeLuminance = ({ r, g, b }) => {
|
||||
return 0.2126 * red + 0.7152 * green + 0.0722 * blue;
|
||||
};
|
||||
|
||||
const contrastRatio = (lumA, lumB) => {
|
||||
const contrastRatio = (lumA: number, lumB: number): number => {
|
||||
const [lighter, darker] = lumA >= lumB ? [lumA, lumB] : [lumB, lumA];
|
||||
return (lighter + 0.05) / (darker + 0.05);
|
||||
};
|
||||
|
||||
const parseCandidateColor = (candidate) => {
|
||||
const parseCandidateColor = (candidate: string) => {
|
||||
const rgb = hexToRgb(candidate);
|
||||
if (!rgb) return null;
|
||||
return {
|
||||
@@ -118,7 +124,7 @@ const parseCandidateColor = (candidate) => {
|
||||
};
|
||||
};
|
||||
|
||||
const contrastForPair = (backgroundHex, textHex) => {
|
||||
const contrastForPair = (backgroundHex: string, textHex: string): number => {
|
||||
const background = hexToRgb(backgroundHex);
|
||||
const text = hexToRgb(textHex);
|
||||
if (!background || !text) {
|
||||
@@ -128,9 +134,9 @@ const contrastForPair = (backgroundHex, textHex) => {
|
||||
};
|
||||
|
||||
export const getReadableTextColor = (
|
||||
hex,
|
||||
{ light = '#1f1f1f', dark = '#ffffff', fallback = '#1f1f1f' } = {},
|
||||
) => {
|
||||
hex: string,
|
||||
{ light = '#1f1f1f', dark = '#ffffff', fallback = '#1f1f1f' }: { light?: string; dark?: string; fallback?: string } = {},
|
||||
): string => {
|
||||
const background = hexToRgb(hex);
|
||||
if (!background) return fallback;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const ensureDate = (value) => {
|
||||
const ensureDate = (value: string | number | Date | null | undefined): Date | null => {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
@@ -6,7 +6,13 @@ const ensureDate = (value) => {
|
||||
return Number.isNaN(date.getTime()) ? null : date;
|
||||
};
|
||||
|
||||
export const formatDate = (value, { fallback = '—', locale, options } = {}) => {
|
||||
interface FormatOptions {
|
||||
fallback?: string;
|
||||
locale?: Intl.LocalesArgument;
|
||||
options?: Intl.DateTimeFormatOptions;
|
||||
}
|
||||
|
||||
export const formatDate = (value: string | number | Date | null | undefined, { fallback = '—', locale, options }: FormatOptions = {}): string => {
|
||||
const date = ensureDate(value);
|
||||
if (!date) {
|
||||
return fallback;
|
||||
@@ -14,7 +20,7 @@ export const formatDate = (value, { fallback = '—', locale, options } = {}) =>
|
||||
return date.toLocaleDateString(locale, options);
|
||||
};
|
||||
|
||||
export const formatDateTime = (value, { fallback = '—', locale, options } = {}) => {
|
||||
export const formatDateTime = (value: string | number | Date | null | undefined, { fallback = '—', locale, options }: FormatOptions = {}): string => {
|
||||
const date = ensureDate(value);
|
||||
if (!date) {
|
||||
return fallback;
|
||||
@@ -22,7 +28,7 @@ export const formatDateTime = (value, { fallback = '—', locale, options } = {}
|
||||
return date.toLocaleString(locale, options);
|
||||
};
|
||||
|
||||
export const toDateInputValue = (value) => {
|
||||
export const toDateInputValue = (value: string | number | Date | null | undefined): string => {
|
||||
const date = ensureDate(value);
|
||||
if (!date) {
|
||||
return '';
|
||||
@@ -32,7 +38,7 @@ export const toDateInputValue = (value) => {
|
||||
return localDate.toISOString().slice(0, 10);
|
||||
};
|
||||
|
||||
export const toIssuedTimestamp = (dateString, fallback) => {
|
||||
export const toIssuedTimestamp = (dateString: string | null | undefined, fallback: string | number | Date | null | undefined): string | null => {
|
||||
if (!dateString) {
|
||||
return null;
|
||||
}
|
||||
@@ -46,7 +52,7 @@ export const toIssuedTimestamp = (dateString, fallback) => {
|
||||
return Number.isNaN(candidate.getTime()) ? null : candidate.toISOString();
|
||||
};
|
||||
|
||||
export const parseDateValue = (value) => ensureDate(value);
|
||||
export const parseDateValue = (value: string | number | Date | null | undefined): Date | null => ensureDate(value);
|
||||
|
||||
export default {
|
||||
formatDate,
|
||||
@@ -1,4 +1,4 @@
|
||||
export const formatFileSize = (value) => {
|
||||
export const formatFileSize = (value: number | string): string => {
|
||||
const bytes = Number(value);
|
||||
if (!Number.isFinite(bytes) || bytes <= 0) {
|
||||
return '0 B';
|
||||
@@ -15,4 +15,3 @@ export const formatFileSize = (value) => {
|
||||
|
||||
return `${amount.toFixed(2)} ${units[index]}`;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const clamp = (value, min, max) => {
|
||||
export const clamp = (value: number, min: number, max: number): number => {
|
||||
if (value < min) {
|
||||
return min;
|
||||
}
|
||||
@@ -1,9 +1,34 @@
|
||||
import { createAssetView, resolveDocumentAssetUrl } from '../asset_manager';
|
||||
|
||||
const BASE_FETCH_OPTIONS = { start: 1, limit: 1 };
|
||||
const BASE_FETCH_OPTIONS = { start: 1, limit: 1 } as const;
|
||||
|
||||
const pickAsset = (doc, getDocumentAsset) => {
|
||||
if (!doc || typeof getDocumentAsset !== 'function') {
|
||||
interface DocumentLike {
|
||||
id?: string | number;
|
||||
current_version?: unknown;
|
||||
}
|
||||
|
||||
interface AssetLike {
|
||||
id?: string | number;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
type EnsurePreviewData = (id: string | number) => Promise<DocumentLike | null | undefined>;
|
||||
type GetDocumentAsset = (doc: DocumentLike, type: string) => AssetLike | null | undefined;
|
||||
type EnsureAssetUrl = (
|
||||
id: string | number,
|
||||
asset: AssetLike,
|
||||
options?: { start?: number; limit?: number; force?: boolean },
|
||||
) => Promise<AssetLike | null | undefined>;
|
||||
|
||||
interface ResolveOcrTextUrlOptions {
|
||||
document: DocumentLike | null;
|
||||
ensurePreviewData?: EnsurePreviewData;
|
||||
getDocumentAsset?: GetDocumentAsset;
|
||||
ensureAssetUrl?: EnsureAssetUrl;
|
||||
}
|
||||
|
||||
const pickAsset = (doc: DocumentLike | null | undefined, getDocumentAsset?: GetDocumentAsset): AssetLike | null => {
|
||||
if (!doc || !getDocumentAsset) {
|
||||
return null;
|
||||
}
|
||||
return getDocumentAsset(doc, 'ocr-text') || null;
|
||||
@@ -14,7 +39,7 @@ export async function resolveOcrTextUrl({
|
||||
ensurePreviewData,
|
||||
getDocumentAsset,
|
||||
ensureAssetUrl,
|
||||
}) {
|
||||
}: ResolveOcrTextUrlOptions): Promise<string | null> {
|
||||
if (!document?.id) {
|
||||
return null;
|
||||
}
|
||||
@@ -22,7 +47,7 @@ export async function resolveOcrTextUrl({
|
||||
let docRef = document;
|
||||
let asset = pickAsset(docRef, getDocumentAsset);
|
||||
|
||||
if (!asset && typeof ensurePreviewData === 'function') {
|
||||
if (!asset && ensurePreviewData) {
|
||||
const refreshed = await ensurePreviewData(docRef.id);
|
||||
if (refreshed) {
|
||||
docRef = refreshed;
|
||||
@@ -38,9 +63,9 @@ export async function resolveOcrTextUrl({
|
||||
const hasUrl = Boolean(baseView.getPrimaryUrl());
|
||||
|
||||
let entry = asset;
|
||||
if (typeof ensureAssetUrl === 'function') {
|
||||
if (ensureAssetUrl) {
|
||||
const ensureOptions = { ...BASE_FETCH_OPTIONS, force: !hasUrl };
|
||||
entry = (await ensureAssetUrl(docRef.id, asset, ensureOptions)) || asset;
|
||||
entry = (await ensureAssetUrl(docRef.id!, asset, ensureOptions)) || asset;
|
||||
}
|
||||
|
||||
const ensuredView = createAssetView(entry);
|
||||
@@ -59,7 +84,7 @@ export async function resolveOcrTextUrl({
|
||||
);
|
||||
}
|
||||
|
||||
export async function openOcrTextInNewTab(options) {
|
||||
export async function openOcrTextInNewTab(options: ResolveOcrTextUrlOptions): Promise<boolean> {
|
||||
const popup = window.open('', '_blank');
|
||||
const popupAvailable = Boolean(popup);
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
const base64urlToBase64 = (value = '') => {
|
||||
const normalized = value.replace(/-/g, '+').replace(/_/g, '/');
|
||||
const padding = normalized.length % 4;
|
||||
if (padding === 0) {
|
||||
return normalized;
|
||||
}
|
||||
const padLength = 4 - padding;
|
||||
return normalized + '='.repeat(padLength);
|
||||
};
|
||||
|
||||
const base64ToBase64url = (value = '') =>
|
||||
value.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
|
||||
|
||||
const decodeBase64 = (value) => {
|
||||
if (window.atob) {
|
||||
return window.atob(value);
|
||||
}
|
||||
const bufferCtor = globalThis?.Buffer;
|
||||
if (bufferCtor) {
|
||||
return bufferCtor.from(value, 'base64').toString('binary');
|
||||
}
|
||||
throw new Error('No base64 decoder available.');
|
||||
};
|
||||
|
||||
const encodeBase64 = (binary) => {
|
||||
if (window.btoa) {
|
||||
return window.btoa(binary);
|
||||
}
|
||||
const bufferCtor = globalThis?.Buffer;
|
||||
if (bufferCtor) {
|
||||
return bufferCtor.from(binary, 'binary').toString('base64');
|
||||
}
|
||||
throw new Error('No base64 encoder available.');
|
||||
};
|
||||
|
||||
export const base64urlToUint8Array = (value) => {
|
||||
const base64 = base64urlToBase64(value || '');
|
||||
const binary = decodeBase64(base64);
|
||||
const bytes = new Uint8Array(binary.length);
|
||||
for (let i = 0; i < binary.length; i += 1) {
|
||||
bytes[i] = binary.charCodeAt(i);
|
||||
}
|
||||
return bytes;
|
||||
};
|
||||
|
||||
export const arrayBufferToBase64url = (buffer) => {
|
||||
const bytes = new Uint8Array(buffer || []);
|
||||
let binary = '';
|
||||
for (let i = 0; i < bytes.length; i += 1) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
const base64 = encodeBase64(binary);
|
||||
return base64ToBase64url(base64);
|
||||
};
|
||||
|
||||
export const isWebAuthnAvailable = () =>
|
||||
Boolean(navigator?.credentials?.create && navigator.credentials.get);
|
||||
|
||||
export const preparePublicKeyCreationOptions = (challengeResponse) => {
|
||||
if (!challengeResponse || !challengeResponse.publicKey) {
|
||||
throw new Error('Missing publicKey challenge options.');
|
||||
}
|
||||
|
||||
const publicKey = { ...challengeResponse.publicKey };
|
||||
|
||||
if (publicKey.challenge) {
|
||||
publicKey.challenge = base64urlToUint8Array(publicKey.challenge);
|
||||
}
|
||||
|
||||
if (publicKey.user?.id) {
|
||||
publicKey.user = {
|
||||
...publicKey.user,
|
||||
id: base64urlToUint8Array(publicKey.user.id),
|
||||
};
|
||||
}
|
||||
|
||||
if (Array.isArray(publicKey.excludeCredentials)) {
|
||||
publicKey.excludeCredentials = publicKey.excludeCredentials.map((descriptor) => ({
|
||||
...descriptor,
|
||||
id: base64urlToUint8Array(descriptor.id),
|
||||
}));
|
||||
}
|
||||
|
||||
if (publicKey.authenticatorSelection?.residentKey === 'discouraged' && !publicKey.authenticatorSelection.requireResidentKey) {
|
||||
delete publicKey.authenticatorSelection.requireResidentKey;
|
||||
}
|
||||
|
||||
return publicKey;
|
||||
};
|
||||
|
||||
export const preparePublicKeyRequestOptions = (challengeResponse) => {
|
||||
if (!challengeResponse || !challengeResponse.publicKey) {
|
||||
throw new Error('Missing publicKey request options.');
|
||||
}
|
||||
|
||||
const publicKey = { ...challengeResponse.publicKey };
|
||||
|
||||
if (publicKey.challenge) {
|
||||
publicKey.challenge = base64urlToUint8Array(publicKey.challenge);
|
||||
}
|
||||
|
||||
if (Array.isArray(publicKey.allowCredentials)) {
|
||||
publicKey.allowCredentials = publicKey.allowCredentials.map((descriptor) => ({
|
||||
...descriptor,
|
||||
id: base64urlToUint8Array(descriptor.id),
|
||||
}));
|
||||
}
|
||||
|
||||
return publicKey;
|
||||
};
|
||||
|
||||
export const serializeRegistrationCredential = (credential) => {
|
||||
if (!credential) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const transports = credential?.response?.getTransports?.();
|
||||
|
||||
return {
|
||||
id: credential.id,
|
||||
type: credential.type,
|
||||
rawId: arrayBufferToBase64url(credential.rawId),
|
||||
response: {
|
||||
clientDataJSON: arrayBufferToBase64url(credential.response.clientDataJSON),
|
||||
attestationObject: arrayBufferToBase64url(credential.response.attestationObject),
|
||||
transports: transports && transports.length ? Array.from(transports) : undefined,
|
||||
},
|
||||
clientExtensionResults: credential.getClientExtensionResults?.() || {},
|
||||
};
|
||||
};
|
||||
|
||||
export const serializeAuthenticationCredential = (credential) => {
|
||||
if (!credential) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: credential.id,
|
||||
type: credential.type,
|
||||
rawId: arrayBufferToBase64url(credential.rawId),
|
||||
response: {
|
||||
clientDataJSON: arrayBufferToBase64url(credential.response.clientDataJSON),
|
||||
authenticatorData: arrayBufferToBase64url(credential.response.authenticatorData),
|
||||
signature: arrayBufferToBase64url(credential.response.signature),
|
||||
userHandle: credential.response.userHandle
|
||||
? arrayBufferToBase64url(credential.response.userHandle)
|
||||
: undefined,
|
||||
},
|
||||
clientExtensionResults: credential.getClientExtensionResults?.() || {},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,249 @@
|
||||
/* global PublicKeyCredentialCreationOptions, PublicKeyCredentialRequestOptions, BufferSource, PublicKeyCredentialUserEntity, PublicKeyCredentialDescriptor, AuthenticatorSelectionCriteria, AuthenticatorTransport, AuthenticationExtensionsClientOutputs */
|
||||
|
||||
type BufferCtor = {
|
||||
from: (input: string, encoding: string) => {
|
||||
toString: (encoding: string) => string;
|
||||
};
|
||||
};
|
||||
|
||||
type CreationChallengeResponse = {
|
||||
publicKey?: PublicKeyCredentialCreationOptions & {
|
||||
challenge?: string | BufferSource;
|
||||
user?: PublicKeyCredentialUserEntity & { id?: string | BufferSource };
|
||||
excludeCredentials?: Array<PublicKeyCredentialDescriptor & { id?: string | BufferSource }>;
|
||||
authenticatorSelection?: AuthenticatorSelectionCriteria & { requireResidentKey?: boolean };
|
||||
};
|
||||
};
|
||||
|
||||
type RequestChallengeResponse = {
|
||||
publicKey?: PublicKeyCredentialRequestOptions & {
|
||||
challenge?: string | BufferSource;
|
||||
allowCredentials?: Array<PublicKeyCredentialDescriptor & { id?: string | BufferSource }>;
|
||||
};
|
||||
};
|
||||
|
||||
type RegistrationCredential = PublicKeyCredential & {
|
||||
response: AuthenticatorAttestationResponse & {
|
||||
getTransports?: () => AuthenticatorTransport[];
|
||||
};
|
||||
};
|
||||
|
||||
type AuthenticationCredential = PublicKeyCredential & {
|
||||
response: AuthenticatorAssertionResponse;
|
||||
};
|
||||
|
||||
const base64urlToBase64 = (value: string = ''): string => {
|
||||
const normalized = value.replace(/-/g, '+').replace(/_/g, '/');
|
||||
const padding = normalized.length % 4;
|
||||
if (padding === 0) {
|
||||
return normalized;
|
||||
}
|
||||
const padLength = 4 - padding;
|
||||
return normalized + '='.repeat(padLength);
|
||||
};
|
||||
|
||||
const base64ToBase64url = (value: string = ''): string =>
|
||||
value.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
|
||||
|
||||
const getWindowObject = (): (Window & typeof globalThis) | null =>
|
||||
(typeof window !== 'undefined' ? window : null);
|
||||
|
||||
const decodeBase64 = (value: string): string => {
|
||||
const win = getWindowObject();
|
||||
if (win?.atob) {
|
||||
return win.atob(value);
|
||||
}
|
||||
const bufferCtor = (globalThis as typeof globalThis & { Buffer?: BufferCtor }).Buffer;
|
||||
if (bufferCtor) {
|
||||
return bufferCtor.from(value, 'base64').toString('binary');
|
||||
}
|
||||
throw new Error('No base64 decoder available.');
|
||||
};
|
||||
|
||||
const encodeBase64 = (binary: string): string => {
|
||||
const win = getWindowObject();
|
||||
if (win?.btoa) {
|
||||
return win.btoa(binary);
|
||||
}
|
||||
const bufferCtor = (globalThis as typeof globalThis & { Buffer?: BufferCtor }).Buffer;
|
||||
if (bufferCtor) {
|
||||
return bufferCtor.from(binary, 'binary').toString('base64');
|
||||
}
|
||||
throw new Error('No base64 encoder available.');
|
||||
};
|
||||
|
||||
export const base64urlToUint8Array = (value?: string | null): Uint8Array => {
|
||||
const base64 = base64urlToBase64(value || '');
|
||||
const binary = decodeBase64(base64);
|
||||
const bytes = new Uint8Array(binary.length);
|
||||
for (let i = 0; i < binary.length; i += 1) {
|
||||
bytes[i] = binary.charCodeAt(i);
|
||||
}
|
||||
return bytes;
|
||||
};
|
||||
|
||||
const toUint8Array = (input?: ArrayBuffer | ArrayBufferView | ArrayLike<number> | null): Uint8Array => {
|
||||
if (!input) {
|
||||
return new Uint8Array();
|
||||
}
|
||||
if (input instanceof ArrayBuffer) {
|
||||
return new Uint8Array(input as ArrayBuffer) as Uint8Array;
|
||||
}
|
||||
if (ArrayBuffer.isView(input)) {
|
||||
const buffer = (input.buffer as ArrayBuffer).slice(
|
||||
input.byteOffset,
|
||||
input.byteOffset + input.byteLength,
|
||||
);
|
||||
return new Uint8Array(buffer) as Uint8Array;
|
||||
}
|
||||
return Uint8Array.from(input as ArrayLike<number>) as Uint8Array;
|
||||
};
|
||||
|
||||
export const arrayBufferToBase64url = (
|
||||
buffer?: ArrayBuffer | ArrayBufferView | ArrayLike<number> | null,
|
||||
): string => {
|
||||
const bytes = toUint8Array(buffer);
|
||||
let binary = '';
|
||||
for (let i = 0; i < bytes.length; i += 1) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
const base64 = encodeBase64(binary);
|
||||
return base64ToBase64url(base64);
|
||||
};
|
||||
|
||||
export const isWebAuthnAvailable = (): boolean =>
|
||||
Boolean(typeof navigator !== 'undefined' && navigator?.credentials?.create && navigator.credentials.get);
|
||||
|
||||
export const preparePublicKeyCreationOptions = (
|
||||
challengeResponse: CreationChallengeResponse,
|
||||
): PublicKeyCredentialCreationOptions => {
|
||||
if (!challengeResponse || !challengeResponse.publicKey) {
|
||||
throw new Error('Missing publicKey challenge options.');
|
||||
}
|
||||
|
||||
const publicKey: PublicKeyCredentialCreationOptions = { ...challengeResponse.publicKey };
|
||||
|
||||
if (publicKey.challenge && typeof publicKey.challenge === 'string') {
|
||||
publicKey.challenge = base64urlToUint8Array(publicKey.challenge);
|
||||
}
|
||||
|
||||
if (publicKey.user?.id) {
|
||||
publicKey.user = {
|
||||
...publicKey.user,
|
||||
id: typeof publicKey.user.id === 'string'
|
||||
? base64urlToUint8Array(publicKey.user.id)
|
||||
: publicKey.user.id,
|
||||
};
|
||||
}
|
||||
|
||||
if (Array.isArray(publicKey.excludeCredentials)) {
|
||||
publicKey.excludeCredentials = publicKey.excludeCredentials.map((descriptor) => ({
|
||||
...descriptor,
|
||||
id: typeof descriptor.id === 'string'
|
||||
? base64urlToUint8Array(descriptor.id)
|
||||
: descriptor.id,
|
||||
}));
|
||||
}
|
||||
|
||||
if (publicKey.authenticatorSelection?.residentKey === 'discouraged' && !publicKey.authenticatorSelection.requireResidentKey) {
|
||||
delete publicKey.authenticatorSelection.requireResidentKey;
|
||||
}
|
||||
|
||||
return publicKey;
|
||||
};
|
||||
|
||||
export const preparePublicKeyRequestOptions = (
|
||||
challengeResponse: RequestChallengeResponse,
|
||||
): PublicKeyCredentialRequestOptions => {
|
||||
if (!challengeResponse || !challengeResponse.publicKey) {
|
||||
throw new Error('Missing publicKey request options.');
|
||||
}
|
||||
|
||||
const publicKey: PublicKeyCredentialRequestOptions = { ...challengeResponse.publicKey };
|
||||
|
||||
if (publicKey.challenge && typeof publicKey.challenge === 'string') {
|
||||
publicKey.challenge = base64urlToUint8Array(publicKey.challenge);
|
||||
}
|
||||
|
||||
if (Array.isArray(publicKey.allowCredentials)) {
|
||||
publicKey.allowCredentials = publicKey.allowCredentials.map((descriptor) => ({
|
||||
...descriptor,
|
||||
id: typeof descriptor.id === 'string'
|
||||
? base64urlToUint8Array(descriptor.id)
|
||||
: descriptor.id,
|
||||
}));
|
||||
}
|
||||
|
||||
return publicKey;
|
||||
};
|
||||
|
||||
export const serializeRegistrationCredential = (
|
||||
credential?: PublicKeyCredential | null,
|
||||
): {
|
||||
id: string;
|
||||
type: PublicKeyCredential['type'];
|
||||
rawId: string;
|
||||
response: {
|
||||
clientDataJSON: string;
|
||||
attestationObject: string;
|
||||
transports?: AuthenticatorTransport[];
|
||||
};
|
||||
clientExtensionResults: AuthenticationExtensionsClientOutputs;
|
||||
} | null => {
|
||||
if (!credential) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const response = credential.response as RegistrationCredential['response'];
|
||||
const transports = response?.getTransports?.();
|
||||
|
||||
return {
|
||||
id: credential.id,
|
||||
type: credential.type,
|
||||
rawId: arrayBufferToBase64url(credential.rawId),
|
||||
response: {
|
||||
clientDataJSON: arrayBufferToBase64url(response.clientDataJSON),
|
||||
attestationObject: arrayBufferToBase64url(response.attestationObject),
|
||||
transports: transports && transports.length
|
||||
? (Array.from(transports) as AuthenticatorTransport[])
|
||||
: undefined,
|
||||
},
|
||||
clientExtensionResults: credential.getClientExtensionResults?.() || {},
|
||||
};
|
||||
};
|
||||
|
||||
export const serializeAuthenticationCredential = (
|
||||
credential?: PublicKeyCredential | null,
|
||||
): {
|
||||
id: string;
|
||||
type: PublicKeyCredential['type'];
|
||||
rawId: string;
|
||||
response: {
|
||||
clientDataJSON: string;
|
||||
authenticatorData: string;
|
||||
signature: string;
|
||||
userHandle?: string;
|
||||
};
|
||||
clientExtensionResults: AuthenticationExtensionsClientOutputs;
|
||||
} | null => {
|
||||
if (!credential) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const response = credential.response as AuthenticationCredential['response'];
|
||||
|
||||
return {
|
||||
id: credential.id,
|
||||
type: credential.type,
|
||||
rawId: arrayBufferToBase64url(credential.rawId),
|
||||
response: {
|
||||
clientDataJSON: arrayBufferToBase64url(response.clientDataJSON),
|
||||
authenticatorData: arrayBufferToBase64url(response.authenticatorData),
|
||||
signature: arrayBufferToBase64url(response.signature),
|
||||
userHandle: response.userHandle
|
||||
? arrayBufferToBase64url(response.userHandle)
|
||||
: undefined,
|
||||
},
|
||||
clientExtensionResults: credential.getClientExtensionResults?.() || {},
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user