Move StandaloneRegistration to React
This commit is contained in:
parent
67b17ec317
commit
7c1ce3366d
20 changed files with 452 additions and 1358 deletions
|
@ -1759,6 +1759,10 @@ app.on('will-finish-launching', () => {
|
||||||
if (isCaptchaHref(incomingHref, getLogger())) {
|
if (isCaptchaHref(incomingHref, getLogger())) {
|
||||||
const { captcha } = parseCaptchaHref(incomingHref, getLogger());
|
const { captcha } = parseCaptchaHref(incomingHref, getLogger());
|
||||||
challengeHandler.handleCaptcha(captcha);
|
challengeHandler.handleCaptcha(captcha);
|
||||||
|
|
||||||
|
// Show window after handling captcha
|
||||||
|
showWindow();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,14 +85,6 @@
|
||||||
<button class='finish' tabIndex='1'><span class='icon'></span></button>
|
<button class='finish' tabIndex='1'><span class='icon'></span></button>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/x-tmpl-mustache" id="phone-number">
|
|
||||||
<div class='phone-input-form'>
|
|
||||||
<div class='number-container'>
|
|
||||||
<input type='tel' class='number' placeholder="Phone Number" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script type="text/x-tmpl-mustache" id="group-member-list">
|
<script type="text/x-tmpl-mustache" id="group-member-list">
|
||||||
<div class='container' tabindex='0'>
|
<div class='container' tabindex='0'>
|
||||||
{{ #summary }} <div class='summary'>{{ summary }}</div>{{ /summary }}
|
{{ #summary }} <div class='summary'>{{ summary }}</div>{{ /summary }}
|
||||||
|
@ -198,36 +190,6 @@
|
||||||
{{/isError}}
|
{{/isError}}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/x-tmpl-mustache" id="standalone">
|
|
||||||
<div class='module-title-bar-drag-area'></div>
|
|
||||||
|
|
||||||
<div class='step'>
|
|
||||||
<div class='inner'>
|
|
||||||
<div class='step-body'>
|
|
||||||
<div class="banner-image module-splash-screen__logo module-img--128"></div>
|
|
||||||
<div class='header'>Create your Signal Account</div>
|
|
||||||
<div id='phone-number-input'>
|
|
||||||
<div class='phone-input-form'>
|
|
||||||
<div id='number-container' class='number-container'>
|
|
||||||
<input type='tel' class='number' placeholder='Phone Number' />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class='clearfix'>
|
|
||||||
<a class='button' id='request-sms'>Send SMS</a>
|
|
||||||
<a class='link' id='request-voice' tabindex='-1'>Call</a>
|
|
||||||
</div>
|
|
||||||
<input class='form-control' type='text' pattern='\s*[0-9]{3}-?[0-9]{3}\s*' title='Enter your 6-digit verification code. If you did not receive a code, click Call or Send SMS to request a new one' id='code' placeholder='Verification Code' autocomplete='off'>
|
|
||||||
<div id='error' class='collapse'></div>
|
|
||||||
<div id='status'></div>
|
|
||||||
</div>
|
|
||||||
<div class='nav'>
|
|
||||||
<a class='button' id='verifyCode' data-loading-text='Please wait...'>Register</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script type="text/javascript" src="js/components.js"></script>
|
<script type="text/javascript" src="js/components.js"></script>
|
||||||
<script type="text/javascript" src="ts/set_os_class.js"></script>
|
<script type="text/javascript" src="ts/set_os_class.js"></script>
|
||||||
<script
|
<script
|
||||||
|
|
|
@ -36,7 +36,6 @@
|
||||||
"node_modules/mustache/mustache.js",
|
"node_modules/mustache/mustache.js",
|
||||||
"node_modules/underscore/underscore.js",
|
"node_modules/underscore/underscore.js",
|
||||||
"components/qrcode/**/*.js",
|
"components/qrcode/**/*.js",
|
||||||
"node_modules/intl-tel-input/build/js/intlTelInput.js",
|
|
||||||
"components/autosize/**/*.js",
|
"components/autosize/**/*.js",
|
||||||
"components/webaudiorecorder/lib/WebAudioRecorder.js"
|
"components/webaudiorecorder/lib/WebAudioRecorder.js"
|
||||||
]
|
]
|
||||||
|
|
|
@ -107,7 +107,7 @@
|
||||||
"heic-convert": "^1.2.4",
|
"heic-convert": "^1.2.4",
|
||||||
"history": "4.9.0",
|
"history": "4.9.0",
|
||||||
"humanize-duration": "3.26.0",
|
"humanize-duration": "3.26.0",
|
||||||
"intl-tel-input": "12.1.15",
|
"intl-tel-input": "17.0.13",
|
||||||
"jquery": "3.5.0",
|
"jquery": "3.5.0",
|
||||||
"js-yaml": "3.13.1",
|
"js-yaml": "3.13.1",
|
||||||
"linkify-it": "2.2.0",
|
"linkify-it": "2.2.0",
|
||||||
|
@ -195,6 +195,7 @@
|
||||||
"@types/google-libphonenumber": "7.4.14",
|
"@types/google-libphonenumber": "7.4.14",
|
||||||
"@types/history": "4.7.2",
|
"@types/history": "4.7.2",
|
||||||
"@types/humanize-duration": "^3.18.1",
|
"@types/humanize-duration": "^3.18.1",
|
||||||
|
"@types/intl-tel-input": "17.0.4",
|
||||||
"@types/jquery": "3.5.6",
|
"@types/jquery": "3.5.6",
|
||||||
"@types/js-yaml": "3.12.0",
|
"@types/js-yaml": "3.12.0",
|
||||||
"@types/linkify-it": "2.1.0",
|
"@types/linkify-it": "2.1.0",
|
||||||
|
|
|
@ -457,7 +457,6 @@ try {
|
||||||
require('./ts/views/conversation_view');
|
require('./ts/views/conversation_view');
|
||||||
require('./ts/views/inbox_view');
|
require('./ts/views/inbox_view');
|
||||||
require('./ts/views/install_view');
|
require('./ts/views/install_view');
|
||||||
require('./ts/views/standalone_registration_view');
|
|
||||||
require('./ts/SignalProtocolStore');
|
require('./ts/SignalProtocolStore');
|
||||||
require('./ts/background');
|
require('./ts/background');
|
||||||
|
|
||||||
|
|
|
@ -570,7 +570,28 @@ $loading-height: 16px;
|
||||||
@media (min-height: 750px) and (min-width: 700px) {
|
@media (min-height: 750px) and (min-width: 700px) {
|
||||||
font-size: 20pt;
|
font-size: 20pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
background-color: $color-gray-20;
|
||||||
|
cursor: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.link {
|
||||||
|
@include button-reset;
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
margin: 0.5em auto;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: underline;
|
||||||
|
color: $color-ultramarine;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
color: $color-gray-20;
|
||||||
|
cursor: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
a.link {
|
a.link {
|
||||||
display: block;
|
display: block;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
|
@ -5,9 +5,16 @@
|
||||||
@import 'variables';
|
@import 'variables';
|
||||||
@import '../node_modules/intl-tel-input/build/css/intlTelInput.css';
|
@import '../node_modules/intl-tel-input/build/css/intlTelInput.css';
|
||||||
@import 'progress';
|
@import 'progress';
|
||||||
.iti-flag {
|
|
||||||
|
.iti__flag {
|
||||||
// override intlTelInput's flags image location
|
// override intlTelInput's flags image location
|
||||||
background: url('../node_modules/intl-tel-input/build/img/flags.png');
|
background-image: url('../node_modules/intl-tel-input/build/img/flags.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
|
||||||
|
.iti__flag {
|
||||||
|
background-image: url('../node_modules/intl-tel-input/build/img/flags@2x.png');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.intl-tel-input .country-list {
|
.intl-tel-input .country-list {
|
||||||
|
|
|
@ -54,14 +54,6 @@
|
||||||
<button class='close'><span class='icon'></span></button>
|
<button class='close'><span class='icon'></span></button>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/x-tmpl-mustache" id="phone-number">
|
|
||||||
<div class='phone-input-form'>
|
|
||||||
<div class='number-container'>
|
|
||||||
<input type='tel' class='number' placeholder="Phone Number" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script type="text/x-tmpl-mustache" id="file-size-modal">
|
<script type="text/x-tmpl-mustache" id="file-size-modal">
|
||||||
{{ file-size-warning }}
|
{{ file-size-warning }}
|
||||||
({{ limit }}{{ units }})
|
({{ limit }}{{ units }})
|
||||||
|
@ -169,36 +161,6 @@
|
||||||
{{/isError}}
|
{{/isError}}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/x-tmpl-mustache" id="standalone">
|
|
||||||
<div class='module-title-bar-drag-area'></div>
|
|
||||||
|
|
||||||
<div class='step'>
|
|
||||||
<div class='inner'>
|
|
||||||
<div class='step-body'>
|
|
||||||
<div class="banner-image module-splash-screen__logo module-img--128"></div>
|
|
||||||
<div class='header'>Create your Signal Account</div>
|
|
||||||
<div id='phone-number-input'>
|
|
||||||
<div class='phone-input-form'>
|
|
||||||
<div id='number-container' class='number-container'>
|
|
||||||
<input type='tel' class='number' placeholder='Phone Number' />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class='clearfix'>
|
|
||||||
<a class='button' id='request-sms'>Send SMS</a>
|
|
||||||
<a class='link' id='request-voice' tabindex='-1'>Call</a>
|
|
||||||
</div>
|
|
||||||
<input class='form-control' type='text' pattern='\s*[0-9]{3}-?[0-9]{3}\s*' title='Enter your 6-digit verification code. If you did not receive a code, click Call or Send SMS to request a new one' id='code' placeholder='Verification Code' autocomplete='off'>
|
|
||||||
<div id='error' class='collapse'></div>
|
|
||||||
<div id='status'></div>
|
|
||||||
</div>
|
|
||||||
<div class='nav'>
|
|
||||||
<a class='button' id='verifyCode' data-loading-text='Please wait...'>Register</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script
|
<script
|
||||||
type="text/javascript"
|
type="text/javascript"
|
||||||
src="../libtextsecure/test/fake_web_api.js"
|
src="../libtextsecure/test/fake_web_api.js"
|
||||||
|
|
|
@ -2009,6 +2009,14 @@ export async function startApp(): Promise<void> {
|
||||||
|
|
||||||
window.textsecure.messaging = new window.textsecure.MessageSender(server);
|
window.textsecure.messaging = new window.textsecure.MessageSender(server);
|
||||||
|
|
||||||
|
// Update our profile key in the conversation if we just got linked.
|
||||||
|
const profileKey = await ourProfileKeyService.get();
|
||||||
|
if (firstRun && profileKey) {
|
||||||
|
const me = window.ConversationController.getOurConversation();
|
||||||
|
strictAssert(me !== undefined, "Didn't find newly created ourselves");
|
||||||
|
await me.setProfileKey(Bytes.toBase64(profileKey));
|
||||||
|
}
|
||||||
|
|
||||||
if (connectCount === 0) {
|
if (connectCount === 0) {
|
||||||
try {
|
try {
|
||||||
// Force a re-fetch before we process our queue. We may want to turn on
|
// Force a re-fetch before we process our queue. We may want to turn on
|
||||||
|
|
|
@ -312,6 +312,19 @@ export class ChallengeHandler {
|
||||||
await this.persist();
|
await this.persist();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async requestCaptcha(token = ''): Promise<string> {
|
||||||
|
const request: IPCRequest = { seq: this.seq };
|
||||||
|
this.seq += 1;
|
||||||
|
|
||||||
|
this.options.requestChallenge(request);
|
||||||
|
|
||||||
|
const response = await new Promise<ChallengeResponse>((resolve, reject) => {
|
||||||
|
this.responseHandlers.set(request.seq, { token, resolve, reject });
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.captcha;
|
||||||
|
}
|
||||||
|
|
||||||
private async persist(): Promise<void> {
|
private async persist(): Promise<void> {
|
||||||
assert(
|
assert(
|
||||||
this.isLoaded,
|
this.isLoaded,
|
||||||
|
@ -407,16 +420,10 @@ export class ChallengeHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async solve(token: string): Promise<void> {
|
private async solve(token: string): Promise<void> {
|
||||||
const request: IPCRequest = { seq: this.seq };
|
|
||||||
this.seq += 1;
|
|
||||||
|
|
||||||
this.options.setChallengeStatus('required');
|
this.options.setChallengeStatus('required');
|
||||||
this.options.requestChallenge(request);
|
this.challengeToken = token;
|
||||||
|
|
||||||
this.challengeToken = token || '';
|
const captcha = await this.requestCaptcha(token);
|
||||||
const response = await new Promise<ChallengeResponse>((resolve, reject) => {
|
|
||||||
this.responseHandlers.set(request.seq, { token, resolve, reject });
|
|
||||||
});
|
|
||||||
|
|
||||||
// Another `.solve()` has completed earlier than us
|
// Another `.solve()` has completed earlier than us
|
||||||
if (this.challengeToken === undefined) {
|
if (this.challengeToken === undefined) {
|
||||||
|
@ -434,7 +441,7 @@ export class ChallengeHandler {
|
||||||
await this.sendChallengeResponse({
|
await this.sendChallengeResponse({
|
||||||
type: 'recaptcha',
|
type: 'recaptcha',
|
||||||
token: lastToken,
|
token: lastToken,
|
||||||
captcha: response.captcha,
|
captcha,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`challenge: challenge failure, error: ${error && error.stack}`);
|
log.error(`challenge: challenge failure, error: ${error && error.stack}`);
|
||||||
|
|
|
@ -18,6 +18,13 @@ type PropsType = {
|
||||||
appView: AppViewType;
|
appView: AppViewType;
|
||||||
renderCallManager: () => JSX.Element;
|
renderCallManager: () => JSX.Element;
|
||||||
renderGlobalModalContainer: () => JSX.Element;
|
renderGlobalModalContainer: () => JSX.Element;
|
||||||
|
openInbox: () => void;
|
||||||
|
requestVerification: (
|
||||||
|
type: 'sms' | 'voice',
|
||||||
|
number: string,
|
||||||
|
token: string
|
||||||
|
) => Promise<void>;
|
||||||
|
registerSingleDevice: (number: string, code: string) => Promise<void>;
|
||||||
theme: ThemeType;
|
theme: ThemeType;
|
||||||
} & ComponentProps<typeof Inbox>;
|
} & ComponentProps<typeof Inbox>;
|
||||||
|
|
||||||
|
@ -34,6 +41,9 @@ export const App = ({
|
||||||
renderCustomizingPreferredReactionsModal,
|
renderCustomizingPreferredReactionsModal,
|
||||||
renderGlobalModalContainer,
|
renderGlobalModalContainer,
|
||||||
renderSafetyNumber,
|
renderSafetyNumber,
|
||||||
|
openInbox,
|
||||||
|
requestVerification,
|
||||||
|
registerSingleDevice,
|
||||||
theme,
|
theme,
|
||||||
verifyConversationsStoppingMessageSend,
|
verifyConversationsStoppingMessageSend,
|
||||||
}: PropsType): JSX.Element => {
|
}: PropsType): JSX.Element => {
|
||||||
|
@ -42,7 +52,17 @@ export const App = ({
|
||||||
if (appView === AppViewType.Installer) {
|
if (appView === AppViewType.Installer) {
|
||||||
contents = <Install />;
|
contents = <Install />;
|
||||||
} else if (appView === AppViewType.Standalone) {
|
} else if (appView === AppViewType.Standalone) {
|
||||||
contents = <StandaloneRegistration />;
|
const onComplete = () => {
|
||||||
|
window.removeSetupMenuItems();
|
||||||
|
openInbox();
|
||||||
|
};
|
||||||
|
contents = (
|
||||||
|
<StandaloneRegistration
|
||||||
|
onComplete={onComplete}
|
||||||
|
requestVerification={requestVerification}
|
||||||
|
registerSingleDevice={registerSingleDevice}
|
||||||
|
/>
|
||||||
|
);
|
||||||
} else if (appView === AppViewType.Inbox) {
|
} else if (appView === AppViewType.Inbox) {
|
||||||
contents = (
|
contents = (
|
||||||
<Inbox
|
<Inbox
|
||||||
|
|
|
@ -1,14 +1,268 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React from 'react';
|
import type { ChangeEvent } from 'react';
|
||||||
import { BackboneHost } from './BackboneHost';
|
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<Plugin | undefined>();
|
||||||
|
const elemRef = useRef<HTMLInputElement | null>(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<HTMLInputElement>) => {
|
||||||
|
if (elemRef.current) {
|
||||||
|
validateNumber(elemRef.current.value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[validateNumber]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onKeyDown = useCallback(
|
||||||
|
(event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
// Pacify TypeScript and handle events bubbling up
|
||||||
|
if (event.target instanceof HTMLInputElement) {
|
||||||
|
validateNumber(event.target.value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[validateNumber]
|
||||||
|
);
|
||||||
|
|
||||||
export const StandaloneRegistration = (): JSX.Element => {
|
|
||||||
return (
|
return (
|
||||||
<BackboneHost
|
<div className="phone-input">
|
||||||
className="full-screen-flow"
|
<div className="phone-input-form">
|
||||||
View={window.Whisper.StandaloneRegistrationView}
|
<div className={`number-container ${isValid ? 'valid' : 'invalid'}`}>
|
||||||
/>
|
<input
|
||||||
|
className="number"
|
||||||
|
type="tel"
|
||||||
|
ref={onRef}
|
||||||
|
onChange={onChange}
|
||||||
|
onKeyDown={onKeyDown}
|
||||||
|
placeholder="Phone Number"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const StandaloneRegistration = ({
|
||||||
|
onComplete,
|
||||||
|
requestVerification,
|
||||||
|
registerSingleDevice,
|
||||||
|
}: {
|
||||||
|
onComplete: () => void;
|
||||||
|
requestVerification: (
|
||||||
|
type: 'sms' | 'voice',
|
||||||
|
number: string,
|
||||||
|
token: string
|
||||||
|
) => Promise<void>;
|
||||||
|
registerSingleDevice: (number: string, code: string) => Promise<void>;
|
||||||
|
}): JSX.Element => {
|
||||||
|
useEffect(() => {
|
||||||
|
window.readyForUpdates();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [isValidNumber, setIsValidNumber] = useState(false);
|
||||||
|
const [isValidCode, setIsValidCode] = useState(false);
|
||||||
|
const [number, setNumber] = useState<string | undefined>(undefined);
|
||||||
|
const [code, setCode] = useState('');
|
||||||
|
const [error, setError] = useState<string | undefined>(undefined);
|
||||||
|
const [status, setStatus] = useState<string | undefined>(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<HTMLButtonElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
onRequestCode('sms');
|
||||||
|
},
|
||||||
|
[onRequestCode]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onVoiceClick = useCallback(
|
||||||
|
(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
onRequestCode('voice');
|
||||||
|
},
|
||||||
|
[onRequestCode]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onChangeCode = useCallback(
|
||||||
|
(event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const { value } = event.target;
|
||||||
|
|
||||||
|
setIsValidCode(value.length === 6);
|
||||||
|
setCode(value);
|
||||||
|
},
|
||||||
|
[setIsValidCode, setCode]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onVerifyCode = useCallback(
|
||||||
|
async (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
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 (
|
||||||
|
<div className="full-screen-flow">
|
||||||
|
<div className="module-title-bar-drag-area" />
|
||||||
|
|
||||||
|
<div className="step">
|
||||||
|
<div className="inner">
|
||||||
|
<div className="step-body">
|
||||||
|
<div className="banner-image module-splash-screen__logo module-img--128" />
|
||||||
|
<div className="header">Create your Signal Account</div>
|
||||||
|
<div>
|
||||||
|
<div className="phone-input-form">
|
||||||
|
<PhoneInput
|
||||||
|
onValidation={setIsValidNumber}
|
||||||
|
onNumberChange={setNumber}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="clearfix">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="button"
|
||||||
|
disabled={!isValidNumber}
|
||||||
|
onClick={onSMSClick}
|
||||||
|
>
|
||||||
|
Send SMS
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="link"
|
||||||
|
tabIndex={-1}
|
||||||
|
disabled={!isValidNumber}
|
||||||
|
onClick={onVoiceClick}
|
||||||
|
>
|
||||||
|
Call
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
className={`form-control ${isValidCode ? 'valid' : 'invalid'}`}
|
||||||
|
type="text"
|
||||||
|
pattern="\s*[0-9]{3}-?[0-9]{3}\s*"
|
||||||
|
title="Enter your 6-digit verification code. If you did not receive a code, click Call or Send SMS to request a new one"
|
||||||
|
placeholder="Verification Code"
|
||||||
|
autoComplete="off"
|
||||||
|
value={code}
|
||||||
|
onChange={onChangeCode}
|
||||||
|
/>
|
||||||
|
<div>{error}</div>
|
||||||
|
<div>{status}</div>
|
||||||
|
</div>
|
||||||
|
<div className="nav">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="button"
|
||||||
|
disabled={!isValidNumber || !isValidCode}
|
||||||
|
onClick={onVerifyCode}
|
||||||
|
>
|
||||||
|
Register
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -38,6 +38,22 @@ const mapStateToProps = (state: StateType) => {
|
||||||
renderSafetyNumber: (props: SafetyNumberProps) => (
|
renderSafetyNumber: (props: SafetyNumberProps) => (
|
||||||
<SmartSafetyNumberViewer {...props} />
|
<SmartSafetyNumberViewer {...props} />
|
||||||
),
|
),
|
||||||
|
requestVerification: (
|
||||||
|
type: 'sms' | 'voice',
|
||||||
|
number: string,
|
||||||
|
token: string
|
||||||
|
): Promise<void> => {
|
||||||
|
const accountManager = window.getAccountManager();
|
||||||
|
|
||||||
|
if (type === 'sms') {
|
||||||
|
return accountManager.requestSMSVerification(number, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
return accountManager.requestVoiceVerification(number, token);
|
||||||
|
},
|
||||||
|
registerSingleDevice: (number: string, code: string): Promise<void> => {
|
||||||
|
return window.getAccountManager().registerSingleDevice(number, code);
|
||||||
|
},
|
||||||
theme: getTheme(state),
|
theme: getTheme(state),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -83,12 +83,12 @@ export default class AccountManager extends EventTarget {
|
||||||
this.pending = Promise.resolve();
|
this.pending = Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
async requestVoiceVerification(number: string) {
|
async requestVoiceVerification(number: string, token: string) {
|
||||||
return this.server.requestVerificationVoice(number);
|
return this.server.requestVerificationVoice(number, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
async requestSMSVerification(number: string) {
|
async requestSMSVerification(number: string, token: string) {
|
||||||
return this.server.requestVerificationSMS(number);
|
return this.server.requestVerificationSMS(number, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptDeviceName(name: string, identityKey: KeyPairType) {
|
encryptDeviceName(name: string, identityKey: KeyPairType) {
|
||||||
|
|
|
@ -869,8 +869,8 @@ export type WebAPIType = {
|
||||||
registerKeys: (genKeys: KeysType) => Promise<void>;
|
registerKeys: (genKeys: KeysType) => Promise<void>;
|
||||||
registerSupportForUnauthenticatedDelivery: () => Promise<void>;
|
registerSupportForUnauthenticatedDelivery: () => Promise<void>;
|
||||||
reportMessage: (senderE164: string, serverGuid: string) => Promise<void>;
|
reportMessage: (senderE164: string, serverGuid: string) => Promise<void>;
|
||||||
requestVerificationSMS: (number: string) => Promise<void>;
|
requestVerificationSMS: (number: string, token: string) => Promise<void>;
|
||||||
requestVerificationVoice: (number: string) => Promise<void>;
|
requestVerificationVoice: (number: string, token: string) => Promise<void>;
|
||||||
sendMessages: (
|
sendMessages: (
|
||||||
destination: string,
|
destination: string,
|
||||||
messageArray: ReadonlyArray<MessageType>,
|
messageArray: ReadonlyArray<MessageType>,
|
||||||
|
@ -1557,19 +1557,19 @@ export function initialize({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function requestVerificationSMS(number: string) {
|
async function requestVerificationSMS(number: string, token: string) {
|
||||||
await _ajax({
|
await _ajax({
|
||||||
call: 'accounts',
|
call: 'accounts',
|
||||||
httpType: 'GET',
|
httpType: 'GET',
|
||||||
urlParameters: `/sms/code/${number}`,
|
urlParameters: `/sms/code/${number}?captcha=${token}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function requestVerificationVoice(number: string) {
|
async function requestVerificationVoice(number: string, token: string) {
|
||||||
await _ajax({
|
await _ajax({
|
||||||
call: 'accounts',
|
call: 'accounts',
|
||||||
httpType: 'GET',
|
httpType: 'GET',
|
||||||
urlParameters: `/voice/code/${number}`,
|
urlParameters: `/voice/code/${number}?captcha=${token}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,38 +0,0 @@
|
||||||
// Copyright 2015-2021 Signal Messenger, LLC
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
window.Whisper = window.Whisper || {};
|
|
||||||
|
|
||||||
export const PhoneInputView = window.Whisper.View.extend({
|
|
||||||
tagName: 'div',
|
|
||||||
className: 'phone-input',
|
|
||||||
template: () => $('#phone-number').html(),
|
|
||||||
initialize() {
|
|
||||||
this.$('input.number').intlTelInput();
|
|
||||||
},
|
|
||||||
events: {
|
|
||||||
change: 'validateNumber',
|
|
||||||
keydown: 'validateNumber',
|
|
||||||
},
|
|
||||||
validateNumber() {
|
|
||||||
const input = this.$('input.number');
|
|
||||||
const regionCode = this.$('li.active')
|
|
||||||
.attr('data-country-code')
|
|
||||||
.toUpperCase();
|
|
||||||
const number = input.val();
|
|
||||||
|
|
||||||
const parsedNumber = window.libphonenumber.util.parseNumber(
|
|
||||||
number,
|
|
||||||
regionCode
|
|
||||||
);
|
|
||||||
if (parsedNumber.isValidNumber) {
|
|
||||||
this.$('.number-container').removeClass('invalid');
|
|
||||||
this.$('.number-container').addClass('valid');
|
|
||||||
} else {
|
|
||||||
this.$('.number-container').removeClass('valid');
|
|
||||||
}
|
|
||||||
input.trigger('validation');
|
|
||||||
|
|
||||||
return parsedNumber.isValidNumber ? parsedNumber.e164 : undefined;
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,117 +0,0 @@
|
||||||
// Copyright 2017-2021 Signal Messenger, LLC
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
import * as log from '../logging/log';
|
|
||||||
import { PhoneInputView } from './phone_input_view';
|
|
||||||
|
|
||||||
window.Whisper = window.Whisper || {};
|
|
||||||
|
|
||||||
const { Whisper } = window;
|
|
||||||
|
|
||||||
export const StandaloneRegistrationView = Whisper.View.extend({
|
|
||||||
template: () => $('#standalone').html(),
|
|
||||||
className: 'full-screen-flow',
|
|
||||||
initialize() {
|
|
||||||
window.readyForUpdates();
|
|
||||||
|
|
||||||
this.accountManager = window.getAccountManager();
|
|
||||||
|
|
||||||
this.render();
|
|
||||||
|
|
||||||
const number = window.textsecure.storage.user.getNumber();
|
|
||||||
if (number) {
|
|
||||||
this.$('input.number').val(number);
|
|
||||||
}
|
|
||||||
this.phoneView = new PhoneInputView({
|
|
||||||
el: this.$('#phone-number-input'),
|
|
||||||
});
|
|
||||||
this.$('#error').hide();
|
|
||||||
},
|
|
||||||
events: {
|
|
||||||
'validation input.number': 'onValidation',
|
|
||||||
'click #request-voice': 'requestVoice',
|
|
||||||
'click #request-sms': 'requestSMSVerification',
|
|
||||||
'change #code': 'onChangeCode',
|
|
||||||
'click #verifyCode': 'verifyCode',
|
|
||||||
},
|
|
||||||
getVerificationCode() {
|
|
||||||
const codeHTML = $('#code').val();
|
|
||||||
if (!codeHTML) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return String(codeHTML).replace(/\D+/g, '');
|
|
||||||
},
|
|
||||||
async verifyCode() {
|
|
||||||
const number = this.phoneView.validateNumber();
|
|
||||||
const verificationCode = this.getVerificationCode();
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.accountManager.registerSingleDevice(number, verificationCode);
|
|
||||||
this.$el.trigger('openInbox');
|
|
||||||
} catch (err) {
|
|
||||||
this.log(err);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
log(s: Error) {
|
|
||||||
log.info(s);
|
|
||||||
this.$('#status').text(s);
|
|
||||||
},
|
|
||||||
validateCode() {
|
|
||||||
const verificationCode = this.getVerificationCode();
|
|
||||||
|
|
||||||
if (verificationCode.length === 6) {
|
|
||||||
return verificationCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
displayError(error: Error) {
|
|
||||||
this.$('#error').hide().text(error).addClass('in').fadeIn();
|
|
||||||
},
|
|
||||||
onValidation() {
|
|
||||||
if (this.$('#number-container').hasClass('valid')) {
|
|
||||||
this.$('#request-sms, #request-voice').removeAttr('disabled');
|
|
||||||
} else {
|
|
||||||
this.$('#request-sms, #request-voice').prop('disabled', 'disabled');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onChangeCode() {
|
|
||||||
if (!this.validateCode()) {
|
|
||||||
this.$('#code').addClass('invalid');
|
|
||||||
} else {
|
|
||||||
this.$('#code').removeClass('invalid');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async requestVoice() {
|
|
||||||
window.removeSetupMenuItems();
|
|
||||||
this.$('#error').hide();
|
|
||||||
const number = this.phoneView.validateNumber();
|
|
||||||
if (number) {
|
|
||||||
this.$('#step2').addClass('in').fadeIn();
|
|
||||||
try {
|
|
||||||
await this.accountManager.requestVoiceVerification(number);
|
|
||||||
} catch (err) {
|
|
||||||
this.displayError(err);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.$('#number-container').addClass('invalid');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async requestSMSVerification() {
|
|
||||||
window.removeSetupMenuItems();
|
|
||||||
$('#error').hide();
|
|
||||||
const number = this.phoneView.validateNumber();
|
|
||||||
if (number) {
|
|
||||||
this.$('#step2').addClass('in').fadeIn();
|
|
||||||
try {
|
|
||||||
await this.accountManager.requestSMSVerification(number);
|
|
||||||
} catch (err) {
|
|
||||||
this.displayError(err);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.$('#number-container').addClass('invalid');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
Whisper.StandaloneRegistrationView = StandaloneRegistrationView;
|
|
1
ts/window.d.ts
vendored
1
ts/window.d.ts
vendored
|
@ -591,6 +591,5 @@ export type WhisperType = {
|
||||||
KeyVerificationPanelView: typeof AnyViewClass;
|
KeyVerificationPanelView: typeof AnyViewClass;
|
||||||
ReactWrapperView: typeof BasicReactWrapperViewClass;
|
ReactWrapperView: typeof BasicReactWrapperViewClass;
|
||||||
SafetyNumberChangeDialogView: typeof AnyViewClass;
|
SafetyNumberChangeDialogView: typeof AnyViewClass;
|
||||||
StandaloneRegistrationView: typeof AnyViewClass;
|
|
||||||
View: typeof AnyViewClass;
|
View: typeof AnyViewClass;
|
||||||
};
|
};
|
||||||
|
|
15
yarn.lock
15
yarn.lock
|
@ -2567,6 +2567,13 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/humanize-duration/-/humanize-duration-3.18.1.tgz#10090d596053703e7de0ac43a37b96cd9fc78309"
|
resolved "https://registry.yarnpkg.com/@types/humanize-duration/-/humanize-duration-3.18.1.tgz#10090d596053703e7de0ac43a37b96cd9fc78309"
|
||||||
integrity sha512-MUgbY3CF7hg/a/jogixmAufLjJBQT7WEf8Q+kYJkOc47ytngg1IuZobCngdTjAgY83JWEogippge5O5fplaQlw==
|
integrity sha512-MUgbY3CF7hg/a/jogixmAufLjJBQT7WEf8Q+kYJkOc47ytngg1IuZobCngdTjAgY83JWEogippge5O5fplaQlw==
|
||||||
|
|
||||||
|
"@types/intl-tel-input@17.0.4":
|
||||||
|
version "17.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/intl-tel-input/-/intl-tel-input-17.0.4.tgz#a7be083bd1989830e7e02f6d552a40428fd5ed7c"
|
||||||
|
integrity sha512-AIX0Azxs7fAkeI2wjAsMNE4Lfoa+/NqazWDPM4k4Vm8lhcId0BNMjzC/BACEGE0nAScc71+pznlKnTxBfW5HHA==
|
||||||
|
dependencies:
|
||||||
|
"@types/jquery" "*"
|
||||||
|
|
||||||
"@types/jquery@*":
|
"@types/jquery@*":
|
||||||
version "3.5.0"
|
version "3.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.5.0.tgz#ccb7dfd317d02d4227dd3803c75297d0c10dad68"
|
resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.5.0.tgz#ccb7dfd317d02d4227dd3803c75297d0c10dad68"
|
||||||
|
@ -10064,10 +10071,10 @@ interpret@~1.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"
|
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"
|
||||||
integrity sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=
|
integrity sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=
|
||||||
|
|
||||||
intl-tel-input@12.1.15:
|
intl-tel-input@17.0.13:
|
||||||
version "12.1.15"
|
version "17.0.13"
|
||||||
resolved "https://registry.yarnpkg.com/intl-tel-input/-/intl-tel-input-12.1.15.tgz#7393e6b77572731bbc65ca4585782e8ba3d74de4"
|
resolved "https://registry.yarnpkg.com/intl-tel-input/-/intl-tel-input-17.0.13.tgz#74a51db3b44f47ae8264df7d101a0810e53c77a6"
|
||||||
integrity sha512-9TN9x6aGdO1eL6iGFpobuLU4UymZqjSnS9UnsOSi//LU3A8nkLOcokSYBYjak18Uu8OM59HsGYDd1jKmwRsskw==
|
integrity sha512-+jPgPKUcgbhSSRN0BZLJOdntTaMv8tqowLrVQ3CGCNGpMKb4B9PRyzOZQpwo1FeA+LTOzvErOlBhbCuQog7R3g==
|
||||||
|
|
||||||
invariant@2.2.4, invariant@^2.2.3, invariant@^2.2.4:
|
invariant@2.2.4, invariant@^2.2.3, invariant@^2.2.4:
|
||||||
version "2.2.4"
|
version "2.2.4"
|
||||||
|
|
Loading…
Add table
Reference in a new issue