// Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import type { ChangeEvent } from 'react'; import React, { useState, useEffect, useCallback, useRef } from 'react'; import type { Plugin } from 'intl-tel-input'; import intlTelInput from 'intl-tel-input'; import { strictAssert } from '../util/assert'; import { getChallengeURL } from '../challenge'; const PhoneInput = ({ onValidation, onNumberChange, }: { onValidation: (isValid: boolean) => void; onNumberChange: (number?: string) => void; }): JSX.Element => { const [isValid, setIsValid] = useState(false); const pluginRef = useRef(); const elemRef = useRef(null); const onRef = useCallback((elem: HTMLInputElement | null) => { elemRef.current = elem; if (!elem) { return; } pluginRef.current?.destroy(); const plugin = intlTelInput(elem); pluginRef.current = plugin; }, []); const validateNumber = useCallback( (number: string) => { const { current: plugin } = pluginRef; if (!plugin) { return; } const regionCode = plugin.getSelectedCountryData().iso2; const parsedNumber = window.libphonenumber.util.parseNumber( number, regionCode ); setIsValid(parsedNumber.isValidNumber); onValidation(parsedNumber.isValidNumber); onNumberChange( parsedNumber.isValidNumber ? parsedNumber.e164 : undefined ); }, [setIsValid, onNumberChange, onValidation] ); const onChange = useCallback( (_: ChangeEvent) => { if (elemRef.current) { validateNumber(elemRef.current.value); } }, [validateNumber] ); const onKeyDown = useCallback( (event: React.KeyboardEvent) => { // Pacify TypeScript and handle events bubbling up if (event.target instanceof HTMLInputElement) { validateNumber(event.target.value); } }, [validateNumber] ); return (
); }; export const StandaloneRegistration = ({ onComplete, requestVerification, registerSingleDevice, }: { onComplete: () => void; requestVerification: ( type: 'sms' | 'voice', number: string, token: string ) => Promise; registerSingleDevice: (number: string, code: string) => Promise; }): JSX.Element => { useEffect(() => { window.readyForUpdates(); }, []); const [isValidNumber, setIsValidNumber] = useState(false); const [isValidCode, setIsValidCode] = useState(false); const [number, setNumber] = useState(undefined); const [code, setCode] = useState(''); const [error, setError] = useState(undefined); const [status, setStatus] = useState(undefined); const onRequestCode = useCallback( async (type: 'sms' | 'voice') => { if (!isValidNumber) { return; } if (!number) { setIsValidNumber(false); setError(undefined); return; } document.location.href = getChallengeURL(); const token = await window.Signal.challengeHandler.requestCaptcha(); try { requestVerification(type, number, token); setError(undefined); } catch (err) { setError(err.message); } }, [isValidNumber, setIsValidNumber, setError, requestVerification, number] ); const onSMSClick = useCallback( (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); onRequestCode('sms'); }, [onRequestCode] ); const onVoiceClick = useCallback( (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); onRequestCode('voice'); }, [onRequestCode] ); const onChangeCode = useCallback( (event: ChangeEvent) => { const { value } = event.target; setIsValidCode(value.length === 6); setCode(value); }, [setIsValidCode, setCode] ); const onVerifyCode = useCallback( async (event: React.MouseEvent) => { event.preventDefault(); event.stopPropagation(); if (!isValidNumber || !isValidCode) { return; } strictAssert(number && code, 'Missing number or code'); try { await registerSingleDevice(number, code); onComplete(); } catch (err) { setStatus(err.message); } }, [ registerSingleDevice, onComplete, number, code, setStatus, isValidNumber, isValidCode, ] ); return (
Create your Signal Account
{error}
{status}
); };