Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for SINPE Movil #163

Open
wants to merge 46 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
2c53721
feat: set country selector top left in buy modal
onmax May 30, 2024
da0d25b
feat: add better text defaults for wrapping texts
onmax May 30, 2024
8d9379a
feat: added RouteName enum
onmax May 31, 2024
b8d3daa
feat: added SinpeMovilSellInfoModal
onmax May 31, 2024
c4c8203
feat: Updated logic in sidebar to toggle sell feature
onmax May 31, 2024
0f9b6c8
chore: fix linter issues
onmax Jun 12, 2024
7a109ee
chore: complete RouteName
onmax Jun 12, 2024
663f583
chore: added lint:fix command
onmax Jun 12, 2024
8404f5e
chore: added basic store for sinpe
onmax Jun 12, 2024
fb1a813
chore: added support for CRC and LN_BTC in the current logic
onmax Jun 12, 2024
5279da5
chore: added simple store
onmax Jun 12, 2024
fbd7bdf
feat: added sell sinpe info and phone/otp input modals
onmax Jun 12, 2024
bc45ee9
chore: removed console.log
onmax Jun 12, 2024
2dc5d9f
chore: created initial template for buy/sell
onmax Jun 12, 2024
11be158
chore: lint issues
onmax Jun 12, 2024
619eb20
chore: AssetTransfer UI with data from composables
onmax Jun 13, 2024
dbca53e
chore: add amount components to display values
onmax Jun 13, 2024
117f08d
chore: setMax value and fixed compilation errors
onmax Jun 14, 2024
46e884c
Refactor, cleanup, renaming, TS errors
sisou Jun 14, 2024
c84dd4b
Fix linter errors
sisou Jun 14, 2024
1a7d29d
chore: asset transfer tooltips
onmax Jun 14, 2024
3cc6685
feat: added animation component to sinpe swap
onmax Jun 14, 2024
c35bfd5
chore: check limits of input in DualCurrencyInput instead
onmax Jun 14, 2024
bf9e2a6
chore: simplify ui logic
onmax Jun 14, 2024
c9bb676
chore: remove trailing dot if no decimals are allowed in AmountInput
onmax Jun 17, 2024
8b4ee2a
chore: fiat store updateExchange accept fiat currency as input
onmax Jun 17, 2024
d35054a
chore(swap crc): implemented swap logic
onmax Jun 28, 2024
7ad5b83
progress
onmax Aug 6, 2024
4c2a95e
chore(swap crc): implemented swap logic
onmax Aug 13, 2024
7c52e74
Use different curried functions for the different OASIS instances
sisou Aug 13, 2024
98dbd3c
Use @nimiq/hub-api from Github to pass CI
sisou Aug 15, 2024
ac1fa33
fix: setMax
onmax Aug 18, 2024
736dd37
fix: refactor invalid reason for swap
onmax Aug 18, 2024
b835380
chore: refactor mobile verification
onmax Aug 18, 2024
e35b686
feat: capture esc key when selector is opened
onmax Aug 18, 2024
bdf8a6c
fix: small ui improvments
onmax Aug 18, 2024
6d24011
fix: display 2 decimals in sinpe swap
onmax Aug 18, 2024
bc22a42
chore: Enable sinpe if crc is active or user is in costa rica
onmax Aug 19, 2024
3cb3150
chore: refactor getColor in SwapAnimation
onmax Aug 19, 2024
1180618
chore: refactor logic to detect if route is swap
onmax Aug 19, 2024
697effc
chore: better errors messages and fixes
onmax Aug 19, 2024
4156b1d
chore: check only for eur in the buy and sell crypto modal in the swa…
onmax Aug 20, 2024
47de604
fix: typo
onmax Aug 21, 2024
f4e9a84
chore: add options to capDecimals fn
onmax Aug 21, 2024
f63aacd
chore: improve types
onmax Aug 21, 2024
660a2e7
chore: revert text-wrap changes globally
onmax Aug 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
chore: refactor mobile verification
  • Loading branch information
onmax authored and sisou committed Aug 29, 2024
commit b835380e4e2156c9d2f1b5a3b77833cb8c55d1ca
111 changes: 80 additions & 31 deletions src/components/modals/SinpeMovilMobileVerificationModal.vue
Original file line number Diff line number Diff line change
@@ -14,6 +14,11 @@

<form @submit.prevent="sendSms">
<PageBody>
<!--
When the https://github.com/nimiq/vue3-components/pull/5 is merged,
we can add this and remove the watcher at the end of <script>
-->
<!-- :tabindex="scrollerIndex === 0 ? 0 : -1" -->
<LabelInput
:placeholder="$t('Enter phone number')"
v-model="phoneNumber"
@@ -27,17 +32,17 @@
:disabled="
sinpaMovilDisabled ||
!validPhoneNumber ||
state !== SinpeMovilFlowState.Idle
(state !== SinpeMovilFlowState.Idle && state !== SinpeMovilFlowState.AllowResendSms)
"
>
<template v-if="state === SinpeMovilFlowState.SendingSms">
{{ $t("Sending SMS...") }}
</template>
<template v-else>
{{ $t("Confirm Number") }}
<template v-if="sendSmsTimeleft > 0"
<template v-if="sendSmsTimeLeft > 0"
>{{
$tc("({secondsLeft}s)", Math.ceil(sendSmsTimeleft / 1000))
$tc("({secondsLeft}s)", Math.ceil(sendSmsTimeLeft / 1000))
}}
</template>
</template>
@@ -68,20 +73,22 @@
maxlength="6"
pattern="\d{6}"
@input="onOtpCodeInput()"
:tabindex="scrollerIndex === 1 ? 0 : -1"
/>
<p class="nq-notice error">{{ errorMessage }}</p>
</PageBody>
<PageFooter>
<button
class="nq-button-s"
@click="sendSms"
:disabled="sendSmsTimeleft > 0"
:disabled="sendSmsTimeLeft > 0"
:tabindex="scrollerIndex === 1 ? 0 : -1"
>
<template v-if="sendSmsTimeleft > 0">
<template v-if="sendSmsTimeLeft > 0">
{{
$tc(
"Resend ({secondsLeft}s)",
Math.ceil(sendSmsTimeleft / 1000)
Math.ceil(sendSmsTimeLeft / 1000)
)
}}
</template>
@@ -122,13 +129,34 @@ import { SINPE_MOVIL_PAIRS } from '@/lib/Constants';
import { isFiatAsset } from '@/stores/Swaps';
import Modal from './Modal.vue';

/**
ErrorSendingSms ErrorVerifyingSms
▲ ▲
│ │
Idle──►SendingSms─►WaitingForOtp─►VerifyingSms──►PhoneVerified
▲ │ │
│ │ ▼
AllowResend◄───┴────WaitingForTimeout
*/
export enum SinpeMovilFlowState {
// Initial state
Idle = 'idle',
// User sends phone number to server and server sends SMS
SendingSms = 'sending-sms',
// Error sending SMS
ErrorSendingSms = 'error-sending-sms',
// User entered phone number and we are waiting for the OTP BUT he cannot send another SMS yet
WaitingForOtp = 'waiting-for-otp',
// User entered phone number and we are waiting for the OTP AND he can send another SMS
AllowResendSms = 'allow-resend-sms',
// User went back to the phone number input but he cannot send another SMS yet
WaitingForTimeout = 'waiting-for-timeout',
// User input the OTP and we are verifying it
VerifyingSms = 'verifying-sms',
// Error verifying the OTP
ÈrrorVerifyingSms = 'error-verifying-sms',
// User input the OTP and it was correct
PhoneVerified = 'phone-verified',
Error = 'error',
}

const headers = { Authorization: `Basic ${btoa('nimiq:run8.deadest')}` };
@@ -215,28 +243,32 @@ export default defineComponent({
);
onUnmounted(() => clearInterval(stopIntervalNow));

const sendSmsTimeout = 60 * 1000; // 1 minute
const sendSmsTimeout = 5 * 1000; // 1 minute
// Time left for the user to be able to send another SMS
const sendSmsTimeleft = computed(() => {
const sendSmsTimeLeft = computed(() => {
if (!sendSmsTs.value) return 0;
const timeleft = sendSmsTimeout - (now.value - sendSmsTs.value);
return timeleft > 0 ? timeleft : 0;
});
watch(sendSmsTimeLeft, (timeleft) => {
if (timeleft > 0) return;
state.value = SinpeMovilFlowState.AllowResendSms;
});

/**
* How to do verify the phone number:
* 1. User enters phone number and we send it to the server.
* 2. Server sends an SMS with a code to the phone number.
* 3. Immediately after sending the SMS, we get a response with an HMAC and a timestamp.
* 4. User enters the code they received in the SMS.
* 5. We send the phone number + the code + the HMAC + timestamp to the server to verify
* 6. We get a new HMAC and timestamp if the verification was successful. This new hmac
* will be used in the /settle request in the swap.
*/
* How to do verify the phone number:
* 1. User enters phone number and we send it to the server.
* 2. Server sends an SMS with a code to the phone number.
* 3. Immediately after sending the SMS, we get a response with an HMAC and a timestamp.
* 4. User enters the code they received in the SMS.
* 5. We send the phone number + the code + the HMAC + timestamp to the server to verify
* 6. We get a new HMAC and timestamp if the verification was successful. This new hmac
* will be used in the /settle request in the swap.
*/

async function sendSms() {
if (sinpaMovilDisabled.value || !validPhoneNumber.value) return;
if (sendSmsTs.value && sendSmsTimeleft.value > 0) return;
if (sendSmsTs.value && sendSmsTimeLeft.value > 0) return;
state.value = SinpeMovilFlowState.SendingSms;
errorMessage.value = '';

@@ -247,14 +279,14 @@ export default defineComponent({
headers,
});
if (!res.ok) {
state.value = SinpeMovilFlowState.Error;
state.value = SinpeMovilFlowState.ErrorSendingSms;
errorMessage.value = (await res.text())
|| 'We could not send the SMS. Please try again later.';
return;
}
const json = (await res.json()) as HmacVerificationResponse;
if (!json.hmac || !json.timestamp) {
state.value = SinpeMovilFlowState.Error;
state.value = SinpeMovilFlowState.ErrorSendingSms;
errorMessage.value = 'There was an error with the SMS response. Please try again later.';
return;
}
@@ -264,15 +296,10 @@ export default defineComponent({
slideNext();
detectOtp();
} catch (error: unknown) {
state.value = SinpeMovilFlowState.Error;
state.value = SinpeMovilFlowState.ErrorSendingSms;
errorMessage.value = 'There was an error with the phone verification system. Please try again later.';
if (config.reportToSentry) captureException(error);
else console.error(error); // eslint-disable-line no-console

// TODO Remove
// state.value = SinpeMovilFlowState.WaitingForOtp;
// sendSmsResponse.value = { hmac: '123456', timestamp: Date.now() };
// slideNext();
}
}

@@ -345,7 +372,7 @@ export default defineComponent({
})
.then(async (res) => {
if (!res.ok) {
state.value = SinpeMovilFlowState.Error;
state.value = SinpeMovilFlowState.ÈrrorVerifyingSms;
errorMessage.value = res.status === 412
? (context.root.$t('The code you entered is incorrect. Please try again.') as string)
: (context.root.$t(
@@ -355,7 +382,7 @@ export default defineComponent({
}
const json = (await res.json()) as { token: string, timestamp: number };
if (!json || !json.token) {
state.value = SinpeMovilFlowState.Error;
state.value = SinpeMovilFlowState.ÈrrorVerifyingSms;
errorMessage.value = context.root.$t(
'There was an error with the phone verification system. Please try again later.') as string;
return;
@@ -373,24 +400,33 @@ export default defineComponent({
},
query: context.root.$router.currentRoute.query,
});
setTimeout(() => {
reset();
}, 1000);
})
.catch((error) => {
errorMessage.value = error.message
? error.message
: JSON.stringify(error);
if (config.reportToSentry) captureException(error);
else console.error(error); // eslint-disable-line no-console
state.value = SinpeMovilFlowState.Error;
state.value = SinpeMovilFlowState.ÈrrorVerifyingSms;
});
}

function goBack() {
if (scrollerIndex.value === 0) {
setTimeout(() => {
reset();
}, 400);
context.root.$router.push({
name: RouteName.SinpeMovilInfo,
params: { pair: JSON.stringify(props.pair) },
});
} else {
state.value = sendSmsTimeLeft.value > 0
? SinpeMovilFlowState.WaitingForTimeout
: SinpeMovilFlowState.AllowResendSms;
slidePrev();
}
}
@@ -416,6 +452,19 @@ export default defineComponent({
}
}

// Workaround until this PR is merged:
// https://github.com/nimiq/vue3-components/pull/5
watch(scrollerIndex, () => {
const lis = scrollerEl.value?.querySelectorAll('li');
lis?.forEach((li, liIndex) => {
const liInputs = li.querySelectorAll('input, button');
liInputs.forEach((input) => {
// Set tabindex to 0 if the li is currently shown, otherwise set it to -1
(input as HTMLInputElement | HTMLButtonElement).tabIndex = liIndex === scrollerIndex.value ? 0 : -1;
});
});
});

return {
activeAddressInfo,
addressListOpened,
@@ -431,7 +480,7 @@ export default defineComponent({
errorMessage,

sendSms,
sendSmsTimeleft,
sendSmsTimeLeft,

otpCode,
otpCode$,