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

Remove Anonymous Login in Certain Chats #476

Merged
merged 11 commits into from
Nov 28, 2023
64 changes: 37 additions & 27 deletions src/components/auth/LoginModal/LoginModalContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import Toast from '@/components/Toast'
import useLoginAndRequestToken from '@/hooks/useLoginAndRequestToken'
import useSignMessageAndLinkEvmAddress from '@/hooks/useSignMessageAndLinkEvmAddress'
import useToastError from '@/hooks/useToastError'
import useWithoutAnonLoginOptions from '@/hooks/useWithoutAnonLoginOptions'
import { ApiRequestTokenResponse } from '@/pages/api/request-token'
import { useRequestToken } from '@/services/api/mutation'
import { useSendEvent } from '@/stores/analytics'
Expand Down Expand Up @@ -70,6 +71,7 @@ export const LoginContent = ({
}: ContentProps) => {
const [hasStartCaptcha, setHasStartCaptcha] = useState(false)
const sendEvent = useSendEvent()
const { withoutAnonLoginOptions } = useWithoutAnonLoginOptions()

const {
mutateAsync: loginAndRequestToken,
Expand All @@ -88,7 +90,12 @@ export const LoginContent = ({
<div>
<div className='flex w-full flex-col justify-center'>
<Logo className='mb-8 mt-4 text-5xl' />
<div className='flex flex-col gap-4'>
<div
className={cx(
'flex flex-col gap-4',
withoutAnonLoginOptions && 'pb-4'
)}
>
<Button
onClick={() => {
setCurrentState('connect-wallet')
Expand All @@ -111,32 +118,35 @@ export const LoginContent = ({
Enter Grill secret key
</div>
</Button>
<Button
type='button'
variant='primaryOutline'
size='lg'
className='w-full'
isLoading={isLoading}
onClick={async () => {
setHasStartCaptcha(true)
const token = await runCaptcha()
if (!token) return
setHasStartCaptcha(false)
const newAddress = await loginAndRequestToken({
captchaToken: token,
})
if (newAddress) {
setCurrentState('account-created')
}
}}
>
<div className='flex items-center justify-center gap-2'>
<IncognitoIcon className='text-text-muted' />
Continue anonymously
</div>
</Button>

{termsAndService('mt-4')}
{!withoutAnonLoginOptions && (
<>
<Button
type='button'
variant='primaryOutline'
size='lg'
className='w-full'
isLoading={isLoading}
onClick={async () => {
setHasStartCaptcha(true)
const token = await runCaptcha()
if (!token) return
setHasStartCaptcha(false)
const newAddress = await loginAndRequestToken({
captchaToken: token,
})
if (newAddress) {
setCurrentState('account-created')
}
}}
>
<div className='flex items-center justify-center gap-2'>
<IncognitoIcon className='text-text-muted' />
Continue anonymously
</div>
</Button>
{termsAndService('mt-4')}
</>
)}
</div>
</div>
</div>
Expand Down
9 changes: 6 additions & 3 deletions src/components/chats/ChatForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ERRORS } from '@/constants/error'
import useAutofocus from '@/hooks/useAutofocus'
import useRequestTokenAndSendMessage from '@/hooks/useRequestTokenAndSendMessage'
import { showErrorToast } from '@/hooks/useToastError'
import useWithoutAnonLoginOptions from '@/hooks/useWithoutAnonLoginOptions'
import { useConfigContext } from '@/providers/ConfigProvider'
import { getPostQuery } from '@/services/api/query'
import {
Expand Down Expand Up @@ -91,6 +92,7 @@ export default function ChatForm({
const messageToEdit = useMessageData((state) => state.messageToEdit)
const clearAction = useMessageData((state) => state.clearAction)
const setMessageBody = useMessageData((state) => state.setMessageBody)
const { withoutAnonLoginOptions } = useWithoutAnonLoginOptions()

const { data: editedMessage } = getPostQuery.useQuery(messageToEdit, {
enabled: !!messageToEdit,
Expand Down Expand Up @@ -188,6 +190,7 @@ export default function ChatForm({
clearAction?.()
}

const needCaptcha = !shouldSendMessage
const handleSubmit = async (captchaToken: string | null) => {
if (
shouldSendMessage &&
Expand Down Expand Up @@ -222,7 +225,8 @@ export default function ChatForm({
return
}

if (!hasSentMessageStorage.get()) {
const willOpenLoginModal = withoutAnonLoginOptions && !isLoggedIn
if (!hasSentMessageStorage.get() && !willOpenLoginModal) {
setTimeout(() => {
setIsOpenCtaModal(true)
}, 2000)
Expand All @@ -244,7 +248,6 @@ export default function ChatForm({
resetForm()
sendMessage(messageParams)
} else {
if (!captchaToken) return
resetForm()
requestTokenAndSendMessage({
captchaToken,
Expand Down Expand Up @@ -274,7 +277,7 @@ export default function ChatForm({
{(runCaptcha) => {
const submitForm = async (e?: SyntheticEvent) => {
e?.preventDefault()
if (shouldSendMessage) {
if (!needCaptcha) {
handleSubmit(null)
return
}
Expand Down
10 changes: 6 additions & 4 deletions src/components/navbar/Navbar/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import usePrevious from '@/hooks/usePrevious'
import { useConfigContext } from '@/providers/ConfigProvider'
import { getUnreadCountQuery } from '@/services/subsocial/datahub/posts/query'
import { useSendEvent } from '@/stores/analytics'
import { useLoginModal } from '@/stores/login-modal'
import { useMyAccount, useMyMainAddress } from '@/stores/my-account'
import { cx } from '@/utils/class-names'
import { getDatahubConfig } from '@/utils/env/client'
Expand Down Expand Up @@ -58,7 +59,8 @@ export default function Navbar({
const prevAddress = usePrevious(address)
const isLoggedIn = !!address

const [openLoginModal, setOpenLoginModal] = useState(false)
const isOpen = useLoginModal((state) => state.isOpen)
const setIsOpen = useLoginModal((state) => state.setIsOpen)
const [openPrivateKeyNotice, setOpenPrivateKeyNotice] = useState(false)
const isLoggingInWithKey = useRef(false)
const timeoutRef = useRef<any>()
Expand All @@ -79,7 +81,7 @@ export default function Navbar({
}, [address, isInitializedAddress, prevAddress])

const login = () => {
setOpenLoginModal(true)
setIsOpen(true)
}

const renderAuthComponent = () => {
Expand Down Expand Up @@ -154,8 +156,8 @@ export default function Navbar({
</Container>
</nav>
<LoginModal
isOpen={openLoginModal}
closeModal={() => setOpenLoginModal(false)}
isOpen={isOpen}
closeModal={() => setIsOpen(false)}
beforeLogin={() => (isLoggingInWithKey.current = true)}
afterLogin={() => (isLoggingInWithKey.current = false)}
/>
Expand Down
5 changes: 5 additions & 0 deletions src/constants/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,8 @@ const INCLUDED_CHAT_IDS_FOR_UNREAD_COUNT: string[] = ['754', '7465']
export function getIncludedChatIdsForUnreadCount() {
return INCLUDED_CHAT_IDS_FOR_UNREAD_COUNT
}

const CHAT_IDS_WITHOUT_ANON_LOGIN_OPTIONS: string[] = ['19361']
export function getChatIdsWithoutAnonLoginOptions() {
return CHAT_IDS_WITHOUT_ANON_LOGIN_OPTIONS
}
27 changes: 19 additions & 8 deletions src/hooks/useRequestTokenAndSendMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@ import {
} from '@/services/subsocial/commentIds'
import { useMyAccount, useMyMainAddress } from '@/stores/my-account'
import { useMutation, UseMutationOptions } from '@tanstack/react-query'
import useWithoutAnonLoginOptions from './useWithoutAnonLoginOptions'

type Params = SendMessageParams & {
captchaToken: string
captchaToken: string | null
}
export default function useRequestTokenAndSendMessage(
options?: UseMutationOptions<void, unknown, Params, unknown>
) {
const address = useMyMainAddress()
const { withoutAnonLoginOptions, promptUserForLogin } =
useWithoutAnonLoginOptions()

const { mutateAsync: requestToken } = useRequestToken()
const { mutateAsync: sendMessage } = useSendMessage()
Expand All @@ -22,15 +25,23 @@ export default function useRequestTokenAndSendMessage(
const { captchaToken, ...sendMessageParams } = params
let usedAddress: string = address ?? ''
if (!address) {
const address = await login()
if (!address) throw new Error('Failed to login')
usedAddress = address
if (withoutAnonLoginOptions) {
const loginAddress = await promptUserForLogin()
if (!loginAddress) return
usedAddress = loginAddress
} else {
const address = await login()
if (!address) throw new Error('Failed to login')
usedAddress = address
}
}

await Promise.all([
requestToken({ address: usedAddress, captchaToken }),
sendMessage(sendMessageParams),
])
const promises: Promise<any>[] = [sendMessage(sendMessageParams)]
if (captchaToken) {
promises.push(requestToken({ address: usedAddress, captchaToken }))
}

await Promise.all(promises)
}

return useMutation(requestTokenAndSendMessage, options)
Expand Down
37 changes: 37 additions & 0 deletions src/hooks/useWithoutAnonLoginOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { getChatIdsWithoutAnonLoginOptions } from '@/constants/chat'
import { useLoginModal } from '@/stores/login-modal'
import { useMyAccount } from '@/stores/my-account'
import { generateManuallyTriggeredPromise } from '@/utils/promise'
import { getIdFromSlug } from '@/utils/slug'
import { useRouter } from 'next/router'
import { useCallback, useEffect, useRef } from 'react'

export default function useWithoutAnonLoginOptions() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename to useLoginOptions

and return

isLoginRequired
promptUserForLogin

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For future it will be main option, so better call it more generic I guess

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated to isNonAnonLoginRequired
because isLoginRequired is pretty ambiguous to me

const { query } = useRouter()
const chatId =
typeof query.slug === 'string' ? getIdFromSlug(query.slug) : undefined
const withoutAnonLoginOptions = getChatIdsWithoutAnonLoginOptions().includes(
chatId ?? ''
)

const isOpen = useLoginModal((state) => state.isOpen)
const waitingLoginResolvers = useRef<VoidFunction[]>([])
useEffect(() => {
if (!isOpen) {
waitingLoginResolvers.current.forEach((resolver) => resolver())
waitingLoginResolvers.current = []
}
}, [isOpen])

const setIsOpen = useLoginModal((state) => state.setIsOpen)

const promptUserForLogin = useCallback(async () => {
setIsOpen(true)
const { getPromise, getResolver } = generateManuallyTriggeredPromise()
waitingLoginResolvers.current.push(getResolver())
await getPromise()
return useMyAccount.getState().address
}, [setIsOpen])

return { withoutAnonLoginOptions, promptUserForLogin }
}
32 changes: 32 additions & 0 deletions src/pages/api/request-token.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ESTIMATED_ENERGY_FOR_ONE_TX } from '@/constants/subsocial'
import { ApiResponse, handlerWrapper } from '@/server/common'
import { getSubsocialApi } from '@/subsocial-query/subsocial/connection'
import { validateAddress } from '@/utils/account'
Expand Down Expand Up @@ -46,6 +47,12 @@ export default handlerWrapper({
}
}

try {
const isNeedEnergy = getIsAddressNeedEnergy(data.address)
if (!isNeedEnergy)
return res.status(200).send({ success: true, message: 'OK', data: '' })
} catch {}

let hash: string
try {
hash = await sendToken(data.address)
Expand All @@ -72,6 +79,31 @@ export default handlerWrapper({
},
})

async function getIsAddressNeedEnergy(address: string) {
const { getSubsocialApi } = await import(
'@/subsocial-query/subsocial/connection'
)

const subsocialApi = await getSubsocialApi()
const substrateApi = await subsocialApi.substrateApi

return new Promise((resolve) => {
const unsub = substrateApi.query.energy.energyBalance(
address,
(energyAmount) => {
let parsedEnergy: unknown = energyAmount
if (typeof energyAmount.toPrimitive === 'function') {
parsedEnergy = energyAmount.toPrimitive()
}
const energy = parseFloat(parsedEnergy + '')
const isAddressNeedEnergy = energy < ESTIMATED_ENERGY_FOR_ONE_TX
resolve(isAddressNeedEnergy)
unsub.then((unsub) => unsub())
}
)
})
}

async function getServerAccount() {
const mnemonic = getServerMnemonic()
const keyring = new Keyring()
Expand Down
15 changes: 12 additions & 3 deletions src/services/subsocial/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import useWithoutAnonLoginOptions from '@/hooks/useWithoutAnonLoginOptions'
import { useRequestToken } from '@/services/api/mutation'
import { getHasEnoughEnergy, useMyAccount } from '@/stores/my-account'
import {
Expand Down Expand Up @@ -49,15 +50,23 @@ export default function useCommonTxSteps<Data, ReturnValue>(
const { mutateAsync: requestToken } = useRequestToken()
const login = useMyAccount((state) => state.login)

const { withoutAnonLoginOptions, promptUserForLogin } =
useWithoutAnonLoginOptions()
const needToRunCaptcha = !address || !hasEnoughEnergy

const workerFunc = async (params: { captchaToken?: string } & Data) => {
const { captchaToken } = params
let usedAddress: string = address ?? ''
if (!address) {
const address = await login()
if (!address) throw new Error('Failed to login')
usedAddress = address
if (withoutAnonLoginOptions) {
const address = await promptUserForLogin()
if (!address) return
usedAddress = address
} else {
const address = await login()
if (!address) throw new Error('Failed to login')
usedAddress = address
}
}

if (!hasEnoughEnergy && captchaToken) {
Expand Down
20 changes: 20 additions & 0 deletions src/stores/login-modal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { create } from './utils'

type State = {
isOpen: boolean
}

type Actions = {
setIsOpen: (isOpen: boolean) => void
}

const initialState: State = {
isOpen: false,
}

export const useLoginModal = create<State & Actions>()((set) => ({
...initialState,
setIsOpen: (isOpen) => {
set({ isOpen })
},
}))