From 531dfeceb180f61ac0753583006c0877104dfc00 Mon Sep 17 00:00:00 2001 From: nuanyang233 <528944303@qq.com> Date: Wed, 2 Mar 2022 15:18:04 +0800 Subject: [PATCH 1/6] feat: query binding data by persona --- .../popups/pages/Personas/Home/index.tsx | 1 - .../Personas/components/ProfileList/index.tsx | 22 ++++++++++++++++++- packages/shared-base/src/NextID/type.ts | 1 + 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/mask/src/extension/popups/pages/Personas/Home/index.tsx b/packages/mask/src/extension/popups/pages/Personas/Home/index.tsx index 03bdb8a180bb..ade45d09331d 100644 --- a/packages/mask/src/extension/popups/pages/Personas/Home/index.tsx +++ b/packages/mask/src/extension/popups/pages/Personas/Home/index.tsx @@ -97,7 +97,6 @@ const PersonaHome = memo(() => { const { currentPersona, setDeletingPersona, personas } = PersonaContext.useContainer() const history = useHistory() - console.log(personas) return ( <>
diff --git a/packages/mask/src/extension/popups/pages/Personas/components/ProfileList/index.tsx b/packages/mask/src/extension/popups/pages/Personas/components/ProfileList/index.tsx index 5c3c50d9f984..da7b20e6bcdf 100644 --- a/packages/mask/src/extension/popups/pages/Personas/components/ProfileList/index.tsx +++ b/packages/mask/src/extension/popups/pages/Personas/components/ProfileList/index.tsx @@ -7,9 +7,10 @@ import { compact } from 'lodash-unified' import { makeStyles } from '@masknet/theme' import { useI18N } from '../../../../../../utils' import { PersonaContext } from '../../hooks/usePersonaContext' -import { useAsyncFn } from 'react-use' +import { useAsync, useAsyncFn } from 'react-use' import Services from '../../../../../service' import { GrayMasks } from '@masknet/icons' +import { queryExistedBindingByPersona } from '@masknet/web3-providers' const useStyles = makeStyles()((theme) => ({ list: { @@ -92,6 +93,25 @@ export const ProfileList = memo(() => { Services.Identity.detachProfile(identifier), ) + const { value: verifiedProfile } = useAsync(async () => { + if (!currentPersona) return + const persona = await Services.Identity.queryPersona(currentPersona.identifier) + if (!persona.publicHexKey) return + const response = await queryExistedBindingByPersona(persona.publicHexKey) + if (!response) return + + return currentPersona?.linkedProfiles.map((profile) => { + const target = response.proofs.find( + (x) => profile.identifier.userId.toLowerCase() === x.identity.toLowerCase(), + ) + + return { + ...profile, + is_valid: target?.is_valid, + } + }) + }, [currentPersona]) + return ( Date: Fri, 4 Mar 2022 18:52:20 +0800 Subject: [PATCH 2/6] feat: support next id on popups --- .../mask/background/services/helper/index.ts | 2 +- .../mask/background/services/helper/nextID.ts | 2 +- packages/mask/shared-ui/locales/en-US.json | 5 + .../InjectedComponents/SetupGuide.tsx | 11 +- .../components/DisconnectDialog/index.tsx | 94 ++++++++++ .../Personas/components/ProfileList/index.tsx | 160 +++++++++++++++--- .../components/SwitchSNSDialog/index.tsx | 49 ++++++ packages/shared-base/src/NextID/type.ts | 2 +- packages/web3-providers/src/NextID/index.ts | 14 +- 9 files changed, 299 insertions(+), 40 deletions(-) create mode 100644 packages/mask/src/extension/popups/pages/Personas/components/DisconnectDialog/index.tsx create mode 100644 packages/mask/src/extension/popups/pages/Personas/components/SwitchSNSDialog/index.tsx diff --git a/packages/mask/background/services/helper/index.ts b/packages/mask/background/services/helper/index.ts index 6f4810cfab58..f9d6d1327ca6 100644 --- a/packages/mask/background/services/helper/index.ts +++ b/packages/mask/background/services/helper/index.ts @@ -3,4 +3,4 @@ export { resolveTCOLink } from './short-link-resolver' export { openPopupWindow, removePopupWindow } from './popup-opener' export { __deprecated__getStorage, __deprecated__setStorage } from './deprecated-storage' export { queryExtensionPermission, requestExtensionPermission } from './request-permission' -export { createPersonaPayload, queryExistedBinding, bindProof } from './nextID' +export { createPersonaPayload, queryExistedBinding, bindProof, queryPersonaHexPublicKey } from './nextID' diff --git a/packages/mask/background/services/helper/nextID.ts b/packages/mask/background/services/helper/nextID.ts index c9dba1b0f509..11eb94379d9f 100644 --- a/packages/mask/background/services/helper/nextID.ts +++ b/packages/mask/background/services/helper/nextID.ts @@ -72,7 +72,7 @@ export async function bindProof( }) } -async function queryPersonaHexPublicKey(persona: PersonaIdentifier) { +export async function queryPersonaHexPublicKey(persona: PersonaIdentifier) { const key256 = decompressSecp256k1Key(persona.compressedPoint.replace(/\|/g, '/')) if (!key256.x || !key256.y) return null const arr = compressSecp256k1Point(key256.x, key256.y) diff --git a/packages/mask/shared-ui/locales/en-US.json b/packages/mask/shared-ui/locales/en-US.json index 3064c07e26be..7bdcff909a1f 100644 --- a/packages/mask/shared-ui/locales/en-US.json +++ b/packages/mask/shared-ui/locales/en-US.json @@ -994,7 +994,12 @@ "popups_wallet_no_transactions": "You have no transactions", "popups_missing_parameter_caption": "Please close this page.", "popups_persona_connect_to": "Connect to {{type}}", + "popups_persona_to_be_verified": "To be verified", "popups_persona_disconnect": "Disconnect", + "popups_persona_disconnect_confirmation": "Disconnect confirmation?", + "popups_persona_disconnect_confirmation_description": "This persona verification record will no longer show up in your verification profile page. Your\n Mask friends can no longer send encrypted message to you by this persona or check your Web 3\n products", + "popups_persona": "Persona", + "popups_twitter_id": "Twitter ID", "popups_persona_logout": "Log out", "popups_persona_disconnect_tip": "After logging out, your associated social accounts can no longer decrypt past encrypted messages. If you need to reuse your account, you can recover your account with your identity, private key, local or cloud backup.", "popups_persona_persona_name_exists": "The persona name already exists", diff --git a/packages/mask/src/components/InjectedComponents/SetupGuide.tsx b/packages/mask/src/components/InjectedComponents/SetupGuide.tsx index 8af270978eff..ba5944d1c4ee 100644 --- a/packages/mask/src/components/InjectedComponents/SetupGuide.tsx +++ b/packages/mask/src/components/InjectedComponents/SetupGuide.tsx @@ -140,15 +140,10 @@ function SetupGuideUI(props: SetupGuideUIProps) { const post = collectVerificationPost?.(postContent) if (post && persona_.publicHexKey) { clearInterval(verifyPostCollectTimer.current!) - await bindProof( - persona_.publicHexKey, - NextIDAction.Create, - platform, - username, - undefined, + await bindProof(persona_.publicHexKey, NextIDAction.Create, platform, username, { signature, - post.postId, - ) + proofLocation: post.postId, + }) resolve() } }, 1000) diff --git a/packages/mask/src/extension/popups/pages/Personas/components/DisconnectDialog/index.tsx b/packages/mask/src/extension/popups/pages/Personas/components/DisconnectDialog/index.tsx new file mode 100644 index 000000000000..f9079f0ed09e --- /dev/null +++ b/packages/mask/src/extension/popups/pages/Personas/components/DisconnectDialog/index.tsx @@ -0,0 +1,94 @@ +import { memo } from 'react' +import { Button, Dialog, DialogActions, DialogContent, Typography } from '@mui/material' +import { makeStyles } from '@masknet/theme' +import classNames from 'classnames' +import type { ProfileIdentifier } from '@masknet/shared-base' +import { formatFingerprint } from '@masknet/shared' +import { PersonaContext } from '../../hooks/usePersonaContext' +import { LoadingButton } from '@mui/lab' +import { useI18N } from '../../../../../../utils' + +const useStyles = makeStyles()(() => ({ + title: { + fontSize: 16, + lineHeight: '22px', + color: '#0F1419', + }, + content: { + marginTop: 24, + fontSize: 14, + lineHeight: '20px', + color: '#536471', + }, + actions: { + display: 'flex', + flexDirection: 'column', + gap: 16, + '& > *': { + marginLeft: '0px !important', + }, + }, + button: { + padding: '8px 0', + width: '100%', + borderRadius: 9999, + fontSize: 14, + fontWeight: 600, + lineHeight: '20px', + }, + confirmButton: { + backgroundColor: '#F4212E', + color: '#ffffff', + '&:hover': { + backgroundColor: '#dc1e2a', + }, + }, + cancelButton: { + color: '#111418', + border: '1px solid #CFD9DE', + }, +})) + +interface DisconnectDialogProps { + open: boolean + unbundledIdentity?: ProfileIdentifier + onClose: () => void + onConfirmDisconnect: () => void + confirmLoading: boolean +} + +export const DisconnectDialog = memo( + ({ open, onClose, unbundledIdentity, onConfirmDisconnect, confirmLoading }) => { + const { classes } = useStyles() + const { t } = useI18N() + const { currentPersona } = PersonaContext.useContainer() + if (!unbundledIdentity) return null + + return ( + + + {t('popups_persona_disconnect_confirmation')} + + {t('popups_persona_disconnect_confirmation_description')} + + + {t('popups_persona')}: {formatFingerprint(currentPersona?.identifier.compressedPoint ?? '', 10)} +
+ {t('popups_twitter_id')}: @{unbundledIdentity.userId} +
+
+ + + {t('confirm')} + + + +
+ ) + }, +) diff --git a/packages/mask/src/extension/popups/pages/Personas/components/ProfileList/index.tsx b/packages/mask/src/extension/popups/pages/Personas/components/ProfileList/index.tsx index da7b20e6bcdf..abc47fbda80a 100644 --- a/packages/mask/src/extension/popups/pages/Personas/components/ProfileList/index.tsx +++ b/packages/mask/src/extension/popups/pages/Personas/components/ProfileList/index.tsx @@ -1,4 +1,4 @@ -import { memo } from 'react' +import { memo, useCallback, useState } from 'react' import { Avatar, Link, List, ListItem, ListItemText, Typography } from '@mui/material' import { definedSocialNetworkUIs } from '../../../../../../social-network' import { SOCIAL_MEDIA_ICON_MAPPING } from '@masknet/shared' @@ -7,10 +7,14 @@ import { compact } from 'lodash-unified' import { makeStyles } from '@masknet/theme' import { useI18N } from '../../../../../../utils' import { PersonaContext } from '../../hooks/usePersonaContext' -import { useAsync, useAsyncFn } from 'react-use' +import { useAsyncFn, useAsyncRetry } from 'react-use' import Services from '../../../../../service' import { GrayMasks } from '@masknet/icons' -import { queryExistedBindingByPersona } from '@masknet/web3-providers' +import { DisconnectDialog } from '../DisconnectDialog' +import { bindProof, createPersonaPayload, queryExistedBindingByPersona } from '@masknet/web3-providers' +import { NextIDAction, NextIDPlatform } from '@masknet/shared-base' +import { delay } from '@dimensiondev/kit' +import classNames from 'classnames' const useStyles = makeStyles()((theme) => ({ list: { @@ -30,6 +34,10 @@ const useStyles = makeStyles()((theme) => ({ text: { fontWeight: 600, margin: 0, + '& > span': { + display: 'flex', + alignItems: 'center', + }, }, link: { cursor: 'pointer', @@ -43,6 +51,13 @@ const useStyles = makeStyles()((theme) => ({ avatar: { width: 20, height: 20, + borderRadius: '50%', + }, + verified_avatar: { + border: '1px solid #60DFAB', + }, + unverified_avatar: { + border: '1px solid #FFB915', }, circle: { backgroundColor: '#ffffff', @@ -61,6 +76,15 @@ const useStyles = makeStyles()((theme) => ({ height: 9, }, }, + tag: { + background: 'linear-gradient(0deg, rgba(255, 177, 0, 0.2), rgba(255, 177, 0, 0.2)), #FFFFFF', + padding: 4, + color: '#FFB100', + fontSize: 10, + lineHeight: 1, + borderRadius: 4, + marginLeft: 6, + }, })) export interface ProfileListProps {} @@ -68,6 +92,12 @@ export interface ProfileListProps {} export const ProfileList = memo(() => { const { currentPersona } = PersonaContext.useContainer() + const [unbind, setUnbind] = useState<{ + identifier: ProfileIdentifier + identity?: string + platform?: NextIDPlatform + } | null>(null) + const definedSocialNetworks = compact( [...definedSocialNetworkUIs.values()].map(({ networkIdentifier }) => { if (networkIdentifier === 'localhost') return null @@ -89,45 +119,113 @@ export const ProfileList = memo(() => { [], ) - const [, onDisconnect] = useAsyncFn(async (identifier: ProfileIdentifier) => - Services.Identity.detachProfile(identifier), + const onDisconnect = useCallback( + (identifier: ProfileIdentifier, is_valid?: boolean, platform?: NextIDPlatform, identity?: string) => { + if (is_valid) { + setUnbind({ + identifier, + platform, + identity, + }) + return + } + Services.Identity.detachProfile(identifier) + }, + [], ) - const { value: verifiedProfile } = useAsync(async () => { + const { value: mergedProfiles, retry: refreshProfileList } = useAsyncRetry(async () => { if (!currentPersona) return - const persona = await Services.Identity.queryPersona(currentPersona.identifier) - if (!persona.publicHexKey) return - const response = await queryExistedBindingByPersona(persona.publicHexKey) + const publicHexKey = await Services.Helper.queryPersonaHexPublicKey(currentPersona.identifier) + if (!publicHexKey) return + const response = await queryExistedBindingByPersona(publicHexKey) if (!response) return return currentPersona?.linkedProfiles.map((profile) => { const target = response.proofs.find( - (x) => profile.identifier.userId.toLowerCase() === x.identity.toLowerCase(), + (x) => + profile.identifier.userId.toLowerCase() === x.identity.toLowerCase() && + profile.identifier.network.replace('.com', '') === x.platform, ) return { ...profile, + platform: target?.platform, + identity: target?.identity, is_valid: target?.is_valid, } }) }, [currentPersona]) + const [confirmState, onConfirmDisconnect] = useAsyncFn(async () => { + // fetch signature payload + try { + if (!currentPersona) return + const publicHexKey = await Services.Helper.queryPersonaHexPublicKey(currentPersona.identifier) + if (!publicHexKey || !unbind || !unbind.identity || !unbind.platform) return + const result = await createPersonaPayload( + publicHexKey, + NextIDAction.Delete, + unbind.identity, + unbind.platform, + ) + if (!result) return + const signatureResult = await Services.Identity.signWithPersona({ + method: 'eth', + message: result.signPayload, + identifier: currentPersona.identifier.toText(), + }) + + if (!signatureResult) return + + await bindProof(publicHexKey, NextIDAction.Delete, unbind.platform, unbind.identity, { + signature: signatureResult.signature.signature, + }) + + await delay(2000) + setUnbind(null) + refreshProfileList() + } catch { + console.log('error') + } + }, [unbind, currentPersona?.identifier, refreshProfileList]) + return ( - + <> + + setUnbind(null)} + onConfirmDisconnect={onConfirmDisconnect} + confirmLoading={confirmState.loading} + /> + ) }) +interface MergedProfileInformation extends ProfileInformation { + is_valid?: boolean + identity?: string + platform?: NextIDPlatform +} + export interface ProfileListUIProps { onConnect: (networkIdentifier: string) => void - onDisconnect: (identifier: ProfileIdentifier) => void + onDisconnect: ( + identifier: ProfileIdentifier, + is_valid?: boolean, + platform?: NextIDPlatform, + identity?: string, + ) => void openProfilePage: (network: string, userId: string) => void - profiles: ProfileInformation[] + profiles: MergedProfileInformation[] networks: string[] } @@ -138,7 +236,7 @@ export const ProfileListUI = memo( return ( - {profiles.map(({ nickname, identifier, avatar }) => { + {profiles.map(({ identifier, avatar, is_valid, platform, identity }) => { return ( ( onDisconnect(identifier)}> + onClick={() => onDisconnect(identifier, is_valid, platform, identity)}> {t('popups_persona_disconnect')} }>
{avatar ? ( - + ) : (
- +
)}
{SOCIAL_MEDIA_ICON_MAPPING[identifier.network]}
@@ -169,6 +278,11 @@ export const ProfileListUI = memo( @{identifier.userId} + {!is_valid ? ( + + {t('popups_persona_to_be_verified')} + + ) : null} ) diff --git a/packages/mask/src/extension/popups/pages/Personas/components/SwitchSNSDialog/index.tsx b/packages/mask/src/extension/popups/pages/Personas/components/SwitchSNSDialog/index.tsx new file mode 100644 index 000000000000..2201dbf12b2c --- /dev/null +++ b/packages/mask/src/extension/popups/pages/Personas/components/SwitchSNSDialog/index.tsx @@ -0,0 +1,49 @@ +import { memo } from 'react' +import { Button, Dialog, DialogActions, DialogContent, Typography } from '@mui/material' +import { makeStyles } from '@masknet/theme' +import { useI18N } from '../../../../../../utils' + +const useStyles = makeStyles()(() => ({ + title: { + fontSize: 16, + lineHeight: '22px', + color: '#0F1419', + }, + content: { + marginTop: 24, + fontSize: 14, + lineHeight: '20px', + color: '#536471', + }, + button: { + padding: '8px 0', + width: '100%', + borderRadius: 9999, + fontSize: 14, + fontWeight: 600, + lineHeight: '20px', + backgroundColor: '#1C68F3', + color: '#ffffff', + '&:hover': { + backgroundColor: '#1a5edd', + }, + }, +})) + +export const SwitchSNSDialog = memo(() => { + const { t } = useI18N() + const { classes } = useStyles() + return ( + + + Switch Twitter Account + + You are not connected to @Vitalik.eth, please log in and try again. + + + + + + + ) +}) diff --git a/packages/shared-base/src/NextID/type.ts b/packages/shared-base/src/NextID/type.ts index 44069ec1abb3..9454675a4c51 100644 --- a/packages/shared-base/src/NextID/type.ts +++ b/packages/shared-base/src/NextID/type.ts @@ -19,7 +19,7 @@ export interface NextIDPayload { export interface NextIDPersonaBindings { persona: string proofs: { - platform: string + platform: NextIDPlatform identity: string is_valid: boolean }[] diff --git a/packages/web3-providers/src/NextID/index.ts b/packages/web3-providers/src/NextID/index.ts index 34a890ca439d..ae1365ca5030 100644 --- a/packages/web3-providers/src/NextID/index.ts +++ b/packages/web3-providers/src/NextID/index.ts @@ -19,19 +19,21 @@ export async function bindProof( action: NextIDAction, platform: string, identity: string, - walletSignature?: string, - signature?: string, - proofLocation?: string, + options?: { + walletSignature?: string + signature?: string + proofLocation?: string + }, ) { const requestBody = { action, platform, identity, public_key: personaPublicKey, - ...(proofLocation ? { proof_location: proofLocation } : {}), + proof_location: options?.proofLocation, extra: { - ...(walletSignature ? { wallet_signature: toBase64(fromHex(walletSignature)) } : {}), - ...(signature ? { signature: toBase64(fromHex(signature)) } : {}), + wallet_signature: options?.walletSignature ? toBase64(fromHex(options.walletSignature)) : undefined, + signature: options?.signature ? toBase64(fromHex(options.signature)) : undefined, }, } From 9aef3c79896d2b2e8120e88a97f7a33bbd51dfe5 Mon Sep 17 00:00:00 2001 From: nuanyang233 <528944303@qq.com> Date: Mon, 7 Mar 2022 12:51:24 +0800 Subject: [PATCH 3/6] fix: bugfix --- .../src/extension/popups/PersonaSignRequest/index.tsx | 5 ++++- .../pages/Personas/components/ProfileList/index.tsx | 8 ++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/mask/src/extension/popups/PersonaSignRequest/index.tsx b/packages/mask/src/extension/popups/PersonaSignRequest/index.tsx index ff65b55f6b84..9db1b6f1b9ad 100644 --- a/packages/mask/src/extension/popups/PersonaSignRequest/index.tsx +++ b/packages/mask/src/extension/popups/PersonaSignRequest/index.tsx @@ -135,7 +135,10 @@ const PersonaSignRequest = memo(() => { {message}
-