import NativeMyidSdk from './NativeMyidSdk'; import type { MyIdAppearanceInput, MyIdConfigInput, MyIdOrganizationDetailsInput, } from './NativeMyidSdk'; import { MyIdError, MyIdErrorCode } from './myid-error'; import type { MyIdResult, StartMyIdOptions } from './myid-types'; /** * Drops keys whose value is `undefined`. * * This matters: the native layer only applies a setting when its key is * present, so an absent key preserves the SDK's own default. Forwarding * `undefined` would instead push a null through the bridge and could override a * default with an empty value. */ function stripUndefined(value: T): T { const entries = Object.entries(value).filter( ([, fieldValue]) => fieldValue !== undefined ); return Object.fromEntries(entries) as T; } /** * Reads the `code` a native rejection carries. React Native surfaces both * platforms' reject(code, message) as an Error with a `code` property, but it * arrives as a string on one platform and can be a number on the other. */ function readErrorCode(error: unknown): string { if (typeof error === 'object' && error !== null && 'code' in error) { const { code } = error as { code: unknown }; if (typeof code === 'string' && code.length > 0) { return code; } if (typeof code === 'number') { return String(code); } } return MyIdErrorCode.FAILURE; } function readErrorMessage(error: unknown): string { if (error instanceof Error && error.message) { return error.message; } return 'MyID verification failed'; } /** * Launches the MyID verification flow and resolves once the user completes it. * * Rejects with a {@link MyIdError} for every failure path — including the user * simply backing out, which arrives as code `101`. * * @example * ```ts * try { * const result = await start({ * config: { sessionId, clientHash, clientHashId }, * }); * console.log(result.code, result.base64); * } catch (error) { * if (isMyIdError(error) && error.code === MyIdErrorCode.USER_CANCELLED) { * // user backed out — usually not worth surfacing * } * } * ``` */ export async function start(options: StartMyIdOptions): Promise { const { config, iosAppearance } = options; // An organizationDetails that strips to {} must not be forwarded: native would // build an empty details object and overwrite the SDK's own default, which // carries MyID's call-centre number. // // The ternary also neutralises an explicit null. That matters more than it // looks: the generated iOS accessor only checks for nil, not NSNull, so a null // here would construct the param struct around NSNull and crash on first // access. const organizationDetails = config.organizationDetails ? stripUndefined(config.organizationDetails) : undefined; const payload = stripUndefined({ ...config, organizationDetails: organizationDetails && Object.keys(organizationDetails).length > 0 ? organizationDetails : undefined, }); const appearance: MyIdAppearanceInput = iosAppearance ? stripUndefined(iosAppearance) : {}; try { const result = await NativeMyidSdk.start(payload, appearance); return { code: result.code, base64: result.base64 }; } catch (error) { throw new MyIdError(readErrorCode(error), readErrorMessage(error)); } }