diff --git a/docs/rfc/000-TypedMessage-binary-format.md b/docs/rfc/000-TypedMessage-binary-format.md index 1e5597753156..c1808057d7d7 100644 --- a/docs/rfc/000-TypedMessage-binary-format.md +++ b/docs/rfc/000-TypedMessage-binary-format.md @@ -62,7 +62,7 @@ type TypedMessageBase = [ type: TypedMessageTypeEnum | String, version: Integer, metadata: Map | Nil, - ...rest: Array + ...rest: Array, ] enum TypedMessageTypeEnum { Tuple = 0, diff --git a/package.json b/package.json index 4d19558d2d2b..45489dd935e6 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "yarn": ">=999.0.0", "npm": ">=999.0.0" }, - "version": "2.5.0", + "version": "2.6.0", "private": true, "license": "AGPL-3.0-or-later", "scripts": { diff --git a/packages/dashboard/src/pages/Personas/components/PersonaCard/Row.tsx b/packages/dashboard/src/pages/Personas/components/PersonaCard/Row.tsx index a1d3248978e4..341f9d3f5f2e 100644 --- a/packages/dashboard/src/pages/Personas/components/PersonaCard/Row.tsx +++ b/packages/dashboard/src/pages/Personas/components/PersonaCard/Row.tsx @@ -88,7 +88,12 @@ export interface PersonaRowCardUIProps { profiles: ProfileInformation[] definedSocialNetworks: SocialNetwork[] publicKey: string - onConnect: (identifier: PersonaIdentifier, networkIdentifier: string, type?: 'local' | 'nextID') => void + onConnect: ( + identifier: PersonaIdentifier, + networkIdentifier: string, + type?: 'local' | 'nextID', + profile?: ProfileIdentifier, + ) => void onDisconnect: (identifier: ProfileIdentifier) => void onRename: (identifier: PersonaIdentifier, target: string, callback?: () => void) => Promise onDeleteBound: ( @@ -200,7 +205,9 @@ export const PersonaRowCardUI = memo((props) => { disableAdd={currentNetworkProfiles.length >= 5} isHideOperations={false} key={networkIdentifier} - onConnect={(type) => onConnect(identifier, networkIdentifier, type)} + onConnect={(type, profile) => + onConnect(identifier, networkIdentifier, type, profile) + } onDisconnect={onDisconnect} onDeleteBound={(profile: ProfileIdentifier) => { onDeleteBound(identifier, profile, networkIdentifier, NextIDAction.Delete) diff --git a/packages/dashboard/src/pages/Personas/components/PersonaCard/index.tsx b/packages/dashboard/src/pages/Personas/components/PersonaCard/index.tsx index f8fa8aef15a9..ad1badae8cb2 100644 --- a/packages/dashboard/src/pages/Personas/components/PersonaCard/index.tsx +++ b/packages/dashboard/src/pages/Personas/components/PersonaCard/index.tsx @@ -79,7 +79,12 @@ export const PersonaCard = memo((props) => { export interface PersonaCardUIProps extends PersonaCardProps { definedSocialNetworks: SocialNetwork[] - onConnect: (identifier: PersonaIdentifier, networkIdentifier: string, type?: 'local' | 'nextID') => void + onConnect: ( + identifier: PersonaIdentifier, + networkIdentifier: string, + type?: 'local' | 'nextID', + profile?: ProfileIdentifier, + ) => void onDisconnect: (identifier: ProfileIdentifier) => void verification?: NextIDPersonaBindings } @@ -121,7 +126,9 @@ export const PersonaCardUI = memo((props) => { proof={proof} isHideOperations key={networkIdentifier} - onConnect={(type) => onConnect(identifier, networkIdentifier, type)} + onConnect={(type, profile) => + onConnect(identifier, networkIdentifier, type, profile) + } onDisconnect={onDisconnect} profileIdentifiers={currentNetworkProfiles.map((x) => x.identifier)} networkIdentifier={networkIdentifier} diff --git a/packages/dashboard/src/pages/Personas/components/PersonaLine/index.tsx b/packages/dashboard/src/pages/Personas/components/PersonaLine/index.tsx index 2dd27e7fde5f..89ab6d457c13 100644 --- a/packages/dashboard/src/pages/Personas/components/PersonaLine/index.tsx +++ b/packages/dashboard/src/pages/Personas/components/PersonaLine/index.tsx @@ -77,7 +77,7 @@ export const UnconnectedPersonaLine = memo(({ onCon export interface ConnectedPersonaLineProps { isHideOperations: boolean - onConnect: (type: 'nextID' | 'local') => void + onConnect: (type: 'nextID' | 'local', profile?: ProfileIdentifier) => void onDisconnect: (identifier: ProfileIdentifier) => void onDeleteBound?: (profile: ProfileIdentifier) => void profileIdentifiers: ProfileIdentifier[] @@ -108,10 +108,10 @@ export const ConnectedPersonaLine = memo( const handleUserIdClick = async (network: string, userId: string) => { await openProfilePage(network, userId) } - const handleProofIconClick = (e: MouseEvent, proof: BindingProof | undefined) => { + const handleProofIconClick = (e: MouseEvent, proof: BindingProof | undefined, profile: ProfileIdentifier) => { e.stopPropagation() if (!proof || !proof.is_valid) { - onConnect('nextID') + onConnect('nextID', profile) } } @@ -138,7 +138,7 @@ export const ConnectedPersonaLine = memo( {profile.network === EnhanceableSite.Twitter && ( handleProofIconClick(e, isProved)}> + onClick={(e: MouseEvent) => handleProofIconClick(e, isProved, profile)}> {proof.loading ? ( ) : isProved?.is_valid ? ( diff --git a/packages/injected-script/main/EventListenerPatch/capture.ts b/packages/injected-script/main/EventListenerPatch/capture.ts index 796d1e8f9341..9ddec65f8fc2 100644 --- a/packages/injected-script/main/EventListenerPatch/capture.ts +++ b/packages/injected-script/main/EventListenerPatch/capture.ts @@ -81,7 +81,7 @@ export function dispatchEventRaw( overwrites: Partial = {}, ) { let currentTarget: null | Node | Document = target - const event = getMockedEvent(eventBase, () => currentTarget!, overwrites) + const event = getMockedEvent(eventBase, () => (isTwitter() ? target! : currentTarget!), overwrites) // Note: in firefox, "event" is "Opaque". Displayed as an empty object. const type = eventBase.type if (!CapturingEvents.has(type)) return warn("[@masknet/injected-script] Trying to send event didn't captured.") diff --git a/packages/mask/.webpack/config.ts b/packages/mask/.webpack/config.ts index fdbace324f36..e5a143627592 100644 --- a/packages/mask/.webpack/config.ts +++ b/packages/mask/.webpack/config.ts @@ -70,6 +70,7 @@ export function createConfiguration(rawFlags: BuildFlags): Configuration { '@masknet/gun-utils': join(__dirname, '../../gun-utils/src/'), '@masknet/shared': join(__dirname, '../../shared/src/'), '@masknet/shared-base': join(__dirname, '../../shared-base/src/'), + '@masknet/shared-base-ui': join(__dirname, '../../shared-base-ui/src/'), '@masknet/theme': join(__dirname, '../../theme/src/'), '@masknet/icons': join(__dirname, '../../icons/index.ts'), '@masknet/web3-providers': join(__dirname, '../../web3-providers/src'), diff --git a/packages/mask/background/services/crypto/decryption.ts b/packages/mask/background/services/crypto/decryption.ts index 3ef71f695f44..046c4daa8d44 100644 --- a/packages/mask/background/services/crypto/decryption.ts +++ b/packages/mask/background/services/crypto/decryption.ts @@ -16,6 +16,8 @@ import { } from '@masknet/encryption' import { AESCryptoKey, + ECKeyIdentifierFromJsonWebKey, + EC_JsonWebKey, EC_Public_JsonWebKey, IdentifierMap, PostIVIdentifier, @@ -23,6 +25,7 @@ import { } from '@masknet/shared-base' import type { TypedMessage } from '@masknet/typed-message' import { noop } from 'lodash-unified' +import { queryProfileDB, queryPersonaDB } from '../../database/persona/db' import { createProfileWithPersona, decryptByLocalKey, @@ -225,6 +228,16 @@ async function storeAuthorPublicKey( if (pub.algr !== EC_KeyCurveEnum.secp256k1) { throw new Error('TODO: support other curves') } + + // if privateKey, we should possibly not recreate it + const profile = await queryProfileDB(payloadAuthor) + const persona = profile?.linkedPersona ? await queryPersonaDB(profile.linkedPersona) : undefined + if (persona?.privateKey) return + + const key = (await crypto.subtle.exportKey('jwk', pub.key)) as EC_JsonWebKey + const otherPersona = await queryPersonaDB(ECKeyIdentifierFromJsonWebKey(key)) + if (otherPersona?.privateKey) return + return createProfileWithPersona( payloadAuthor, { connectionConfirmState: 'confirmed' }, diff --git a/packages/mask/background/services/identity/index.ts b/packages/mask/background/services/identity/index.ts index f7e4ace2daf0..0196cfdcbe81 100644 --- a/packages/mask/background/services/identity/index.ts +++ b/packages/mask/background/services/identity/index.ts @@ -1,3 +1,3 @@ export { createPersonaByPrivateKey } from './persona/create' -export { signWithPersona, type SignRequest, type SignRequestResult } from './persona/sign' +export { signWithPersona, type SignRequest, type SignRequestResult, generateSignResult } from './persona/sign' export { exportPersonaMnemonicWords, exportPersonaPrivateKey } from './persona/backup' diff --git a/packages/mask/background/services/identity/persona/sign.ts b/packages/mask/background/services/identity/persona/sign.ts index 035752da099d..0e148ffac86e 100644 --- a/packages/mask/background/services/identity/persona/sign.ts +++ b/packages/mask/background/services/identity/persona/sign.ts @@ -9,7 +9,7 @@ import { ECDSASignature, } from 'ethereumjs-util' import { MaskMessages } from '../../../../shared' -import { PersonaIdentifier, fromBase64URL, PopupRoutes } from '@masknet/shared-base' +import { PersonaIdentifier, fromBase64URL, PopupRoutes, ECKeyIdentifier } from '@masknet/shared-base' import { queryPersonasWithPrivateKey } from '../../../../background/database/persona/db' import { openPopupWindow } from '../../../../background/services/helper' import { delay } from '@dimensiondev/kit' @@ -49,6 +49,10 @@ export async function signWithPersona({ message, method, identifier }: SignReque }) }) const signer = await waitForApprove + return generateSignResult(signer, message) +} + +export async function generateSignResult(signer: ECKeyIdentifier, message: string) { const persona = (await queryPersonasWithPrivateKey()).find((x) => x.identifier.equals(signer)) if (!persona) throw new Error('Persona not found') diff --git a/packages/mask/shared-ui/locales/en-US.json b/packages/mask/shared-ui/locales/en-US.json index 9aac8c916855..b8342b4b448a 100644 --- a/packages/mask/shared-ui/locales/en-US.json +++ b/packages/mask/shared-ui/locales/en-US.json @@ -83,6 +83,7 @@ "setup_guide_connect_auto": "Connect", "setup_guide_connect_failed": "Re-Connect", "setup_guide_verify": "Verfiy", + "setup_guide_verify_should_change_profile": "Inconsistent Account", "setup_guide_verify_dismiss": "Don't show again.", "setup_guide_verify_checking": "Checking", "setup_guide_verify_post_not_found": "No verification post found", diff --git a/packages/mask/src/components/CompositionDialog/Composition.tsx b/packages/mask/src/components/CompositionDialog/Composition.tsx index 07561d218b9b..cd5a11b5b22c 100644 --- a/packages/mask/src/components/CompositionDialog/Composition.tsx +++ b/packages/mask/src/components/CompositionDialog/Composition.tsx @@ -46,7 +46,12 @@ export function Composition({ type = 'timeline', requireClipboardPermission }: P useEffect(() => { return MaskMessages.events.requestComposition.on(({ reason, open, content, options }) => { - if (reason !== 'reply' && (reason !== type || globalUIState.profiles.value.length <= 0)) return + if ( + (reason !== 'reply' && reason !== type) || + (reason === 'reply' && type === 'popup') || + globalUIState.profiles.value.length <= 0 + ) + return setOpen(open) setReason(reason) if (content) UI.current?.setMessage(content) diff --git a/packages/mask/src/components/CompositionDialog/CompositionUI.tsx b/packages/mask/src/components/CompositionDialog/CompositionUI.tsx index 18bb65e04ecd..7da4567a6222 100644 --- a/packages/mask/src/components/CompositionDialog/CompositionUI.tsx +++ b/packages/mask/src/components/CompositionDialog/CompositionUI.tsx @@ -106,8 +106,7 @@ export const CompositionDialogUI = forwardRef( }) }, []) - useImperativeHandle( - ref, + const refItem = useMemo( (): CompositionRef => ({ setMessage: (msg) => { if (Editor.current) Editor.current.value = msg @@ -119,6 +118,8 @@ export const CompositionDialogUI = forwardRef( [reset], ) + useImperativeHandle(ref, () => refItem, [refItem]) + const context = useMemo( (): CompositionContext => ({ attachMetadata: (meta, data) => Editor.current?.attachMetadata(meta, data), diff --git a/packages/mask/src/components/CompositionDialog/PluginEntryRender.tsx b/packages/mask/src/components/CompositionDialog/PluginEntryRender.tsx index 5d9e55775a7a..5db488b0ef98 100644 --- a/packages/mask/src/components/CompositionDialog/PluginEntryRender.tsx +++ b/packages/mask/src/components/CompositionDialog/PluginEntryRender.tsx @@ -12,7 +12,7 @@ import { RedPacketPluginID } from '../../plugins/RedPacket/constants' import { ITO_PluginID } from '../../plugins/ITO/constants' import { ClickableChip } from '../shared/SelectRecipients/ClickableChip' import { makeStyles } from '@masknet/theme' -import { useCallback, useState, useRef, forwardRef, memo, useImperativeHandle } from 'react' +import { useCallback, useState, useRef, forwardRef, memo, useImperativeHandle, useMemo } from 'react' import { useChainId } from '@masknet/web3-shared-evm' import { Trans } from 'react-i18next' const useStyles = makeStyles()({ @@ -60,8 +60,7 @@ export const PluginEntryRender = memo( function useSetPluginEntryRenderRef(ref: React.ForwardedRef) { const pluginRefs = useRef>({}) - useImperativeHandle( - ref, + const refItem: PluginEntryRenderRef = useMemo( () => ({ openPlugin: function openPlugin(id: string, tryTimes = 4) { const ref = pluginRefs.current[id] @@ -74,13 +73,15 @@ function useSetPluginEntryRenderRef(ref: React.ForwardedRef refItem, [refItem]) const trackPluginRef = (pluginID: string) => (ref: PluginRef | null) => { pluginRefs.current = { ...pluginRefs.current, [pluginID]: ref } } return [trackPluginRef] } function useSetPluginRef(ref: React.ForwardedRef, onClick: () => void) { - useImperativeHandle(ref, () => ({ open: onClick }), [onClick]) + const refItem = useMemo(() => ({ open: onClick }), [onClick]) + useImperativeHandle(ref, () => refItem, [refItem]) } type PluginRef = { open(): void } diff --git a/packages/mask/src/components/CompositionDialog/TypedMessageEditor.tsx b/packages/mask/src/components/CompositionDialog/TypedMessageEditor.tsx index d8c2b2541e77..0e1f0d9da9f5 100644 --- a/packages/mask/src/components/CompositionDialog/TypedMessageEditor.tsx +++ b/packages/mask/src/components/CompositionDialog/TypedMessageEditor.tsx @@ -8,7 +8,7 @@ import { } from '@masknet/typed-message' import { makeStyles } from '@masknet/theme' import { InputBase, Alert, Button } from '@mui/material' -import { useCallback, useImperativeHandle, useState, useRef, forwardRef, memo } from 'react' +import { useCallback, useImperativeHandle, useState, useRef, forwardRef, memo, useMemo } from 'react' import { useI18N } from '../../utils' import { BadgeRenderer } from './BadgeRenderer' @@ -81,30 +81,27 @@ export const TypedMessageEditor = memo( }, [setMessage], ) - useImperativeHandle( - ref, - (): TypedMessageEditorRef => { - return { - get estimatedLength() { - // TODO: we should count metadata into the estimated size - if (isTypedMessageText(currentValue.current)) return currentValue.current.content.length - return 0 - }, - get value() { - return currentValue.current - }, - set value(val) { - setMessage(val) - }, - reset: () => setMessage(emptyMessage), - attachMetadata(meta, data) { - setMessage(editTypedMessageMeta(currentValue.current, (map) => map.set(meta, data))) - }, - dropMetadata: deleteMetaID, - } - }, - [setMessage, deleteMetaID], - ) + const refItem = useMemo((): TypedMessageEditorRef => { + return { + get estimatedLength() { + // TODO: we should count metadata into the estimated size + if (isTypedMessageText(currentValue.current)) return currentValue.current.content.length + return 0 + }, + get value() { + return currentValue.current + }, + set value(val) { + setMessage(val) + }, + reset: () => setMessage(emptyMessage), + attachMetadata(meta, data) { + setMessage(editTypedMessageMeta(currentValue.current, (map) => map.set(meta, data))) + }, + dropMetadata: deleteMetaID, + } + }, [setMessage, deleteMetaID]) + useImperativeHandle(ref, () => refItem, [refItem]) if (!isTypedMessageText(value)) { const reset = () => setAsText('') diff --git a/packages/mask/src/components/DataSource/useNextID.ts b/packages/mask/src/components/DataSource/useNextID.ts index 568cc85c39d4..9537838e36b9 100644 --- a/packages/mask/src/components/DataSource/useNextID.ts +++ b/packages/mask/src/components/DataSource/useNextID.ts @@ -22,11 +22,12 @@ export const usePersonaBoundPlatform = (personaPublicKey: string) => { let isOpenedVerifyDialog = false let isOpenedFromButton = false -const verifyPersona = (personaIdentifier?: PersonaIdentifier) => async () => { +const verifyPersona = (personaIdentifier?: PersonaIdentifier, username?: string) => async () => { if (!personaIdentifier) return currentSetupGuideStatus[activatedSocialNetworkUI.networkIdentifier].value = stringify({ status: SetupGuideStep.VerifyOnNextID, persona: personaIdentifier.toText(), + username, }) } @@ -107,7 +108,7 @@ export function useNextIDConnectStatus() { isOpenedVerifyDialog = true isOpenedFromButton = false return NextIDVerificationStatus.WaitingVerify - }, [username, enableNextID, lastStateRef.value, isOpenedVerifyDialog, currentPersonaIdentifier.value]) + }, [username, enableNextID, isOpenedVerifyDialog, currentPersonaIdentifier.value]) return { isVerified: VerificationStatus === NextIDVerificationStatus.Verified, @@ -119,7 +120,7 @@ export function useNextIDConnectStatus() { }, action: VerificationStatus === NextIDVerificationStatus.WaitingVerify - ? verifyPersona(personaConnectStatus.currentConnectedPersona?.identifier) + ? verifyPersona(personaConnectStatus.currentConnectedPersona?.identifier, lastState.username) : null, } } diff --git a/packages/mask/src/components/InjectedComponents/SetupGuide.tsx b/packages/mask/src/components/InjectedComponents/SetupGuide.tsx index b9d04a813f25..4122decacfec 100644 --- a/packages/mask/src/components/InjectedComponents/SetupGuide.tsx +++ b/packages/mask/src/components/InjectedComponents/SetupGuide.tsx @@ -71,6 +71,11 @@ function SetupGuideUI(props: SetupGuideUIProps) { lastState.username || (lastRecognized.identifier.isUnknown ? '' : lastRecognized.identifier.userId) const [username, setUsername] = useState(getUsername) + const disableVerify = + lastRecognized.identifier.isUnknown || !lastState.username + ? false + : lastRecognized.identifier.userId !== lastState.username + useEffect(() => { const handler = (val: SocialNetworkUI.CollectingCapabilities.IdentityResolved) => { if (username === '' && !val.identifier.isUnknown) setUsername(val.identifier.userId) @@ -259,6 +264,7 @@ function SetupGuideUI(props: SetupGuideUIProps) { onVerify={onVerify} onDone={onVerifyDone} onClose={onClose} + disableVerify={disableVerify} /> ) case SetupGuideStep.PinExtension: diff --git a/packages/mask/src/components/InjectedComponents/SetupGuide/VerifyNextID.tsx b/packages/mask/src/components/InjectedComponents/SetupGuide/VerifyNextID.tsx index 4e14d30fc99e..bb6b4cf019f4 100644 --- a/packages/mask/src/components/InjectedComponents/SetupGuide/VerifyNextID.tsx +++ b/packages/mask/src/components/InjectedComponents/SetupGuide/VerifyNextID.tsx @@ -17,6 +17,7 @@ interface VerifyNextIDProps extends Partial { personaIdentifier?: PersonaIdentifier network: string avatar?: string + disableVerify: boolean onUsernameChange?: (username: string) => void onVerify: () => Promise onDone?: () => void @@ -31,6 +32,7 @@ export const VerifyNextID = ({ onDone, onClose, network, + disableVerify, }: VerifyNextIDProps) => { const { t } = useI18N() @@ -86,13 +88,13 @@ export const VerifyNextID = ({ ({ overflow: 'hidden', }, button: { - width: 150, + minWidth: 150, height: 40, minHeight: 40, marginLeft: 0, diff --git a/packages/mask/src/components/shared/ApplicationBoard.tsx b/packages/mask/src/components/shared/ApplicationBoard.tsx index d1bcd990e35b..6df629f5fb51 100644 --- a/packages/mask/src/components/shared/ApplicationBoard.tsx +++ b/packages/mask/src/components/shared/ApplicationBoard.tsx @@ -234,7 +234,7 @@ export function ApplicationBoard({ secondEntries, secondEntryChainTabs }: MaskAp // Todo: remove this after refactor applicationBoard const isITOSupportedChain = - ITO_Definition.enableRequirement.web3![NetworkPluginID.PLUGIN_EVM]?.supportedChainIds?.includes(currentChainId) + ITO_Definition.enableRequirement.web3![currentPluginId]?.supportedChainIds?.includes(currentChainId) const firstLevelEntries: MaskAppEntry[] = [ createEntry( diff --git a/packages/mask/src/components/shared/Image.tsx b/packages/mask/src/components/shared/Image.tsx index 00da7a77d999..38e79eb2aedf 100644 --- a/packages/mask/src/components/shared/Image.tsx +++ b/packages/mask/src/components/shared/Image.tsx @@ -1,4 +1,4 @@ -import { useRef, useEffect, forwardRef, useImperativeHandle, useState } from 'react' +import { useRef, useEffect, forwardRef, useImperativeHandle, useState, useMemo } from 'react' import { useAsync } from 'react-use' import Services from '../../extension/service' import { Skeleton, SkeletonProps } from '@mui/material' @@ -78,7 +78,8 @@ export const Image = forwardRef(function Image(props, outg const url: string | undefined = blobURL || (typeof src === 'string' ? src : undefined) useEffect(() => void (url && onURL?.(url)), [onURL, url]) - useImperativeHandle(outgoingRef, () => ({ canvas: canvasRef.current, img: imgRef.current }), []) + const outgoingRefItem = useMemo(() => ({ canvas: canvasRef.current, img: imgRef.current }), []) + useImperativeHandle(outgoingRef, () => outgoingRefItem, [outgoingRefItem]) // TODO: handle image loading error const { loading, error, value } = useAsync( diff --git a/packages/mask/src/extension/background-script/IdentityService.ts b/packages/mask/src/extension/background-script/IdentityService.ts index b8a22610b1b1..9668da327efb 100644 --- a/packages/mask/src/extension/background-script/IdentityService.ts +++ b/packages/mask/src/extension/background-script/IdentityService.ts @@ -23,6 +23,7 @@ import { ProfileInformation, PostIVIdentifier, RelationFavor, + NextIDAction, } from '@masknet/shared-base' import type { Persona, Profile } from '../../database/Persona/types' import { @@ -57,6 +58,7 @@ import { getCurrentPersonaIdentifier } from './SettingsService' import { MaskMessages } from '../../utils' import { first, orderBy } from 'lodash-unified' import { recover_ECDH_256k1_KeyPair_ByMnemonicWord } from '../../utils/mnemonic-code' +import { bindProof } from '@masknet/web3-providers' assertEnvironment(Environment.ManifestBackground) @@ -391,3 +393,21 @@ export async function queryPersonaByPrivateKey(privateKeyString: string) { return null } // #endregion + +export async function detachProfileWithNextID( + uuid: string, + personaPublicKey: string, + platform: string, + identity: string, + createdAt: string, + options?: { + walletSignature?: string + signature?: string + proofLocation?: string + }, +) { + await bindProof(uuid, personaPublicKey, NextIDAction.Delete, platform, identity, createdAt, { + signature: options?.signature, + }) + MaskMessages.events.ownProofChanged.sendToAll(undefined) +} diff --git a/packages/mask/src/extension/background-script/SocialNetworkService.ts b/packages/mask/src/extension/background-script/SocialNetworkService.ts index 46b9a57e2633..bbdd64706fb2 100644 --- a/packages/mask/src/extension/background-script/SocialNetworkService.ts +++ b/packages/mask/src/extension/background-script/SocialNetworkService.ts @@ -5,7 +5,7 @@ import { requestSNSAdaptorPermission } from '../../social-network/utils/permissi import { currentSetupGuideStatus } from '../../settings/settings' import stringify from 'json-stable-stringify' import { SetupGuideStep } from '../../components/InjectedComponents/SetupGuide/types' -import type { PersonaIdentifier } from '@masknet/shared-base' +import type { PersonaIdentifier, ProfileIdentifier } from '@masknet/shared-base' import { delay } from '@dimensiondev/kit' export async function getDefinedSocialNetworkUIs() { @@ -15,7 +15,12 @@ export async function getDefinedSocialNetworkUIs() { } }) } -export async function connectSocialNetwork(identifier: PersonaIdentifier, network: string, type?: 'local' | 'nextID') { +export async function connectSocialNetwork( + identifier: PersonaIdentifier, + network: string, + type?: 'local' | 'nextID', + profile?: ProfileIdentifier, +) { const ui = await loadSocialNetworkUI(network) const home = ui.utils.getHomePage?.() if (!Flags.no_web_extension_dynamic_permission_request) { @@ -24,6 +29,7 @@ export async function connectSocialNetwork(identifier: PersonaIdentifier, networ currentSetupGuideStatus[network].value = stringify({ status: type === 'nextID' ? SetupGuideStep.VerifyOnNextID : SetupGuideStep.FindUsername, persona: identifier.toText(), + username: profile?.userId, }) await delay(100) home && browser.tabs.create({ active: true, url: home }) diff --git a/packages/mask/src/extension/popups/pages/Personas/PersonaSignRequest/index.tsx b/packages/mask/src/extension/popups/pages/Personas/PersonaSignRequest/index.tsx index b55204d6ac2b..e2300aec7244 100644 --- a/packages/mask/src/extension/popups/pages/Personas/PersonaSignRequest/index.tsx +++ b/packages/mask/src/extension/popups/pages/Personas/PersonaSignRequest/index.tsx @@ -6,6 +6,10 @@ import { useNavigate, useLocation } from 'react-router-dom' import { ECKeyIdentifier, Identifier, PopupRoutes } from '@masknet/shared-base' import { useMyPersonas } from '../../../../../components/DataSource/useMyPersonas' import type { Persona } from '../../../../../database' +import { PersonaContext } from '../hooks/usePersonaContext' +import { MethodAfterPersonaSign } from '../../Wallet/type' +import { useAsyncFn } from 'react-use' +import Services from '../../../../service' const useStyles = makeStyles()(() => ({ container: { @@ -85,7 +89,7 @@ const PersonaSignRequest = memo(() => { const [message, setMessage] = useState() const [selected, setSelected] = useState() const personas = useMyPersonas() - + const { currentPersona } = PersonaContext.useContainer() useEffect(() => { if (!personas.length) return const url = new URLSearchParams(location.search) @@ -103,22 +107,72 @@ const PersonaSignRequest = memo(() => { } }, [personas, location.search]) - const onSign = () => { + const [, onSign] = useAsyncFn(async () => { + const url = new URLSearchParams(location.search) if (!requestID || !selected) return + const selectedPersona = Identifier.fromString( + selected.identifier.toText(), + ECKeyIdentifier, + ).unwrap() MaskMessages.events.personaSignRequest.sendToBackgroundPage({ requestID, - selectedPersona: Identifier.fromString( - selected.identifier.toText(), - ECKeyIdentifier, - ).unwrap(), + selectedPersona, }) - window.close() - } - const onCancel = () => { + const method = url.get('method') as MethodAfterPersonaSign | undefined + + if (!method) { + window.close() + return + } + + // sign request from popup + switch (method) { + case MethodAfterPersonaSign.DISCONNECT_NEXT_ID: + if (!message) break + const signatureResult = await Services.Identity.generateSignResult(selectedPersona, message) + + const profileIdentifier = url.get('profileIdentifier') + const platform = url.get('platform') + const identity = url.get('identity') + const createdAt = url.get('createdAt') + const uuid = url.get('uuid') + + if ( + !signatureResult || + !profileIdentifier || + !platform || + !identity || + !createdAt || + !uuid || + !currentPersona?.publicHexKey + ) + break + await Services.Identity.detachProfileWithNextID( + uuid, + currentPersona.publicHexKey, + platform, + identity, + createdAt, + { + signature: signatureResult.signature.signature, + }, + ) + const profile = currentPersona.linkedProfiles.find((x) => x.identifier.toText() === profileIdentifier) + if (!profile) break + await Services.Identity.detachProfile(profile.identifier) + break + } + navigate(-1) + }, [location, selected, requestID, message, currentPersona]) + + const onCancel = async () => { if (!requestID) return + const url = new URLSearchParams(location.search) MaskMessages.events.personaSignRequest.sendToBackgroundPage({ requestID }) - window.close() + const method = url.get('method') + if (!method) window.close() + navigate(-1) } 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 ff2d7edfb23f..e06e71a181e2 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 @@ -8,18 +8,21 @@ import { EnhanceableSite, NextIDAction, NextIDPlatform, + PopupRoutes, } from '@masknet/shared-base' import { compact } from 'lodash-unified' import { makeStyles } from '@masknet/theme' -import { useI18N, MaskMessages } from '../../../../../../utils' +import { useI18N } from '../../../../../../utils' import { PersonaContext } from '../../hooks/usePersonaContext' import { useAsyncFn, useAsyncRetry } from 'react-use' import Services from '../../../../../service' import { GrayMasks } from '@masknet/icons' import { DisconnectDialog } from '../DisconnectDialog' -import { bindProof, createPersonaPayload, queryExistedBindingByPersona } from '@masknet/web3-providers' -import { delay } from '@dimensiondev/kit' +import { createPersonaPayload, queryExistedBindingByPersona } from '@masknet/web3-providers' import classNames from 'classnames' +import { useNavigate } from 'react-router-dom' +import urlcat from 'urlcat' +import { MethodAfterPersonaSign } from '../../../Wallet/type' const useStyles = makeStyles()((theme) => ({ list: { @@ -97,6 +100,7 @@ export interface ProfileListProps {} export const ProfileList = memo(() => { const { currentPersona } = PersonaContext.useContainer() + const navigate = useNavigate() const [unbind, setUnbind] = useState<{ identifier: ProfileIdentifier identity?: string @@ -111,9 +115,14 @@ export const ProfileList = memo(() => { ) const [, onConnect] = useAsyncFn( - async (networkIdentifier: string) => { + async (networkIdentifier: string, type?: 'local' | 'nextID', profile?: ProfileIdentifier) => { if (currentPersona) { - await Services.SocialNetwork.connectSocialNetwork(currentPersona.identifier, networkIdentifier) + await Services.SocialNetwork.connectSocialNetwork( + currentPersona.identifier, + networkIdentifier, + type, + profile, + ) } }, [currentPersona], @@ -175,35 +184,21 @@ export const ProfileList = memo(() => { 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( - result.uuid, - publicHexKey, - NextIDAction.Delete, - unbind.platform, - unbind.identity, - result.createdAt, - { - signature: signatureResult.signature.signature, - }, + navigate( + urlcat(PopupRoutes.PersonaSignRequest, { + requestID: Math.random().toString().slice(3), + message: result.signPayload, + identifier: currentPersona.identifier.toText(), + method: MethodAfterPersonaSign.DISCONNECT_NEXT_ID, + profileIdentifier: unbind.identifier.toText(), + platform: unbind.platform, + identity: unbind.identity, + createdAt: result.createdAt, + uuid: result.uuid, + }), ) - - await Services.Identity.detachProfile(unbind.identifier) - - await delay(2000) - setUnbind(null) - refreshProfileList() } catch { console.log('Disconnect failed') - } finally { - MaskMessages.events.ownProofChanged.sendToAll(undefined) } }, [unbind, currentPersona?.identifier, refreshProfileList]) @@ -234,7 +229,7 @@ interface MergedProfileInformation extends ProfileInformation { } export interface ProfileListUIProps { - onConnect: (networkIdentifier: string) => void + onConnect: (networkIdentifier: string, type?: 'local' | 'nextID', profile?: ProfileIdentifier) => void onDisconnect: ( identifier: ProfileIdentifier, is_valid?: boolean, @@ -296,7 +291,13 @@ export const ProfileListUI = memo( @{identifier.userId} {!is_valid && identifier.network === 'twitter.com' ? ( - + { + onConnect(identifier.network, 'nextID', identifier) + e.stopPropagation() + }}> {t('popups_persona_to_be_verified')} ) : null} diff --git a/packages/mask/src/extension/popups/pages/Wallet/type.ts b/packages/mask/src/extension/popups/pages/Wallet/type.ts index 43f356aba4ee..0f0674e1b2c9 100644 --- a/packages/mask/src/extension/popups/pages/Wallet/type.ts +++ b/packages/mask/src/extension/popups/pages/Wallet/type.ts @@ -9,3 +9,7 @@ export enum TransferAddressError { RESOLVE_FAILED = 'RESOLVE_FAILED', NETWORK_NOT_SUPPORT = 'NETWORK_NOT_SUPPORT', } + +export enum MethodAfterPersonaSign { + DISCONNECT_NEXT_ID = 'DISCONNECT_NEXT_ID', +} diff --git a/packages/mask/src/manifest.json b/packages/mask/src/manifest.json index 4e4379f5f14f..f436056eeecf 100644 --- a/packages/mask/src/manifest.json +++ b/packages/mask/src/manifest.json @@ -1,6 +1,6 @@ { "name": "Mask Network", - "version": "2.5.0", + "version": "2.6.0", "manifest_version": 2, "permissions": ["storage", "downloads", "webNavigation", "activeTab"], "optional_permissions": ["", "notifications", "clipboardRead"], diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/ArticleTab.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/ArticleTab.tsx index e6386237d1dc..de18a192d279 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/ArticleTab.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/ArticleTab.tsx @@ -1,7 +1,8 @@ import { makeStyles } from '@masknet/theme' import { CollectibleTab } from './CollectibleTab' import { CollectibleState } from '../hooks/useCollectibleState' -import { AssetPlayer } from '@masknet/shared' +import { NFTCardStyledAssetPlayer } from '@masknet/shared' +import { hasNativeAPI } from '../../../../shared/native-rpc' import { useMemo } from 'react' const useStyles = makeStyles()((theme) => ({ @@ -43,6 +44,9 @@ const useStyles = makeStyles()((theme) => ({ minWidth: 300, minHeight: 300, }, + imgWrapper: { + maxWidth: 300, + }, })) export interface ArticleTabProps {} @@ -53,18 +57,14 @@ export function ArticleTab(props: ArticleTabProps) { return useMemo(() => { if (!asset.value) return null - const resourceUrl = asset.value.animation_url || asset.value.image_url + const resourceUrl = hasNativeAPI + ? asset.value.image_url || asset.value.animation_url + : asset.value.animation_url || asset.value.image_url return (
- + {/* Todo: Migrate `hasNativeAPI` to `@masknet/shared` to use it in directly. */} +
) diff --git a/packages/mask/src/plugins/EVM/UI/Web3State/getAssetsFn.ts b/packages/mask/src/plugins/EVM/UI/Web3State/getAssetsFn.ts index 79e99824cf38..69f40729cae1 100644 --- a/packages/mask/src/plugins/EVM/UI/Web3State/getAssetsFn.ts +++ b/packages/mask/src/plugins/EVM/UI/Web3State/getAssetsFn.ts @@ -38,7 +38,6 @@ export const getFungibleAssetsFn = async (address: string, providerType: string, network: Web3Plugin.NetworkDescriptor, pagination?: Pagination) => { const chainId = context.chainId.getCurrentValue() const wallet = context.wallets.getCurrentValue().find((x) => isSameAddress(x.address, address)) - const socket = await context.providerSocket const networks = PLUGIN_NETWORKS const trustedTokens = uniqBy( context.erc20Tokens @@ -51,19 +50,7 @@ export const getFungibleAssetsFn = createExternalProvider(context.request, context.getSendOverrides, context.getRequestOptions), ) const { BALANCE_CHECKER_ADDRESS } = getEthereumConstants(chainId) - const socketId = `mask.fetchFungibleTokenAsset_${address}` - let dataFromProvider = await socket.sendAsync>({ - id: socketId, - method: 'mask.fetchFungibleTokenAsset', - params: { - address: address, - pageSize: 10000, - }, - }) - if (!dataFromProvider.length) { - // @ts-ignore getAssetList Asset[] - dataFromProvider = await context.getAssetsList(address, FungibleAssetProvider.DEBANK) - } + const dataFromProvider = await context.getAssetsList(address, FungibleAssetProvider.DEBANK) const assetsFromProvider: Web3Plugin.Asset[] = dataFromProvider.map((x) => ({ id: x.token.address, chainId: x.token.chainId, @@ -108,7 +95,7 @@ export const getFungibleAssetsFn = const assetFromChain = balanceList.map( (balance, idx): Web3Plugin.Asset => ({ id: trustedTokens[idx].address, - chainId: chainId, + chainId, token: { ...trustedTokens[idx], id: trustedTokens[idx].address, diff --git a/packages/mask/src/plugins/EVM/hooks/useAsset.ts b/packages/mask/src/plugins/EVM/hooks/useAsset.ts index d0f179ff29d3..fbca7176d93e 100644 --- a/packages/mask/src/plugins/EVM/hooks/useAsset.ts +++ b/packages/mask/src/plugins/EVM/hooks/useAsset.ts @@ -6,7 +6,7 @@ import { useAccount, useChainId, useTokenConstants, - resolveIPFSLink, + resolveIPFSLinkFromURL, } from '@masknet/web3-shared-evm' import { EVM_RPC } from '../messages' import { resolveAvatarLinkOnCurrentProvider } from '../../Collectible/pipes' @@ -20,9 +20,7 @@ export function useAsset(address: string, tokenId: string, provider: NonFungible const asset = await EVM_RPC.getAsset({ address, tokenId, chainId, provider }) return { ...asset, - image_url: asset?.image_url?.startsWith('ipfs://') - ? resolveIPFSLink(asset.image_url.replace('ipfs://', '')) - : asset?.image_url, + image_url: resolveIPFSLinkFromURL(asset?.image_url ?? ''), isOrderWeth: isSameAddress(asset?.desktopOrder?.payment_token ?? '', WNATIVE_ADDRESS) ?? false, isCollectionWeth: asset?.collection?.payment_tokens?.some(currySameAddress(WNATIVE_ADDRESS)) ?? false, isOwner: asset?.top_ownerships.some((item) => isSameAddress(item.owner.address, account)) ?? false, diff --git a/packages/mask/src/plugins/ITO/SNSAdaptor/ITO.tsx b/packages/mask/src/plugins/ITO/SNSAdaptor/ITO.tsx index b63a868c8f53..d35d84c6d41a 100644 --- a/packages/mask/src/plugins/ITO/SNSAdaptor/ITO.tsx +++ b/packages/mask/src/plugins/ITO/SNSAdaptor/ITO.tsx @@ -786,7 +786,7 @@ export function ITO(props: ITO_Props) { status={claimDialogStatus} total_remaining={total_remaining} payload={{ ...payload, qualification_address: qualificationAddress }} - shareSuccessLink={successShareText} + shareSuccessText={successShareText} isBuyer={isBuyer} exchangeTokens={exchange_tokens} open={openClaimDialog} diff --git a/packages/mask/src/plugins/ITO/SNSAdaptor/RegionSelect.tsx b/packages/mask/src/plugins/ITO/SNSAdaptor/RegionSelect.tsx index b53d0080d8f9..780fe7f27453 100644 --- a/packages/mask/src/plugins/ITO/SNSAdaptor/RegionSelect.tsx +++ b/packages/mask/src/plugins/ITO/SNSAdaptor/RegionSelect.tsx @@ -1,4 +1,4 @@ -import { useState, useRef, useCallback, forwardRef, useImperativeHandle } from 'react' +import { useState, useRef, useCallback, forwardRef, useImperativeHandle, useMemo } from 'react' import type { InputBaseComponentProps } from '@mui/material' import { makeStyles } from '@masknet/theme' import { useDebounce } from 'react-use' @@ -80,11 +80,15 @@ export const RegionSelect = forwardRef(({ value = [], onRegionChange, ...props } }, []) const [minPopoverWidth, setMinPopoverWidth] = useState(0) - useImperativeHandle(ref, () => ({ - focus: () => { - displayRef.current?.focus() - }, - })) + const refItem = useMemo( + () => ({ + focus: () => { + displayRef.current?.focus() + }, + }), + [], + ) + useImperativeHandle(ref, () => refItem, [refItem]) const [filterText, setFilterText] = useState('') const [filteredRegions, setFilteredRegions] = useState(allRegions) diff --git a/packages/mask/src/plugins/ITO/SNSAdaptor/ShareDialog.tsx b/packages/mask/src/plugins/ITO/SNSAdaptor/ShareDialog.tsx index ec3c7ddf214e..432658ea1c99 100644 --- a/packages/mask/src/plugins/ITO/SNSAdaptor/ShareDialog.tsx +++ b/packages/mask/src/plugins/ITO/SNSAdaptor/ShareDialog.tsx @@ -1,11 +1,12 @@ -import { useCallback } from 'react' -import { Box, Typography } from '@mui/material' import { makeStyles, useStylesExtends } from '@masknet/theme' -import { getAssetAsBlobURL, useI18N } from '../../../utils' -import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton' -import { formatBalance, FungibleTokenDetailed } from '@masknet/web3-shared-evm' import { isZero } from '@masknet/web3-shared-base' +import { formatBalance, FungibleTokenDetailed } from '@masknet/web3-shared-evm' +import { Box, Typography } from '@mui/material' import type { BigNumber } from 'bignumber.js' +import { useCallback } from 'react' +import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton' +import { activatedSocialNetworkUI } from '../../../social-network' +import { getAssetAsBlobURL, useI18N } from '../../../utils' const useStyles = makeStyles()((theme) => ({ shareWrapper: { @@ -53,7 +54,7 @@ const useStyles = makeStyles()((theme) => ({ })) export interface ShareDialogProps extends withClasses<'root'> { - shareSuccessLink: string | undefined + shareSuccessText: string | undefined token: FungibleTokenDetailed actualSwapAmount: BigNumber.Value poolName: string @@ -64,13 +65,14 @@ export function ShareDialog(props: ShareDialogProps) { const ShareBackground = getAssetAsBlobURL(new URL('../assets/share-background.jpg', import.meta.url)) const { t } = useI18N() const classes = useStylesExtends(useStyles(), {}) - const { token, actualSwapAmount, shareSuccessLink, onClose } = props + const { token, actualSwapAmount, shareSuccessText, onClose } = props const amount = formatBalance(actualSwapAmount, token.decimals) const onShareSuccess = useCallback(async () => { onClose() - window.open(shareSuccessLink, '_blank', 'noopener noreferrer') - }, [shareSuccessLink, onClose]) + if (!shareSuccessText) return + activatedSocialNetworkUI.utils.share?.(shareSuccessText) + }, [shareSuccessText, onClose]) return ( @@ -86,7 +88,7 @@ export function ShareDialog(props: ShareDialogProps) { {isZero(actualSwapAmount) ? t('plugin_ito_out_of_stock_hit') : t('plugin_ito_congratulations')} - {shareSuccessLink ? ( + {shareSuccessText ? ( , Omit { status: SwapStatus - shareSuccessLink: string | undefined + shareSuccessText: string | undefined total_remaining: BigNumber isBuyer: boolean retryPayload: () => void @@ -49,7 +49,7 @@ export function SwapGuide(props: SwapGuideProps) { isBuyer, open, retryPayload, - shareSuccessLink, + shareSuccessText, total_remaining, onUpdate, onClose, @@ -127,7 +127,7 @@ export function SwapGuide(props: SwapGuideProps) { case SwapStatus.Share: return ( { title: string @@ -137,7 +143,15 @@ export const MaskPostExtraPluginWrapper: PluginWrapperComponent(false) const [title, setTitle] = useState(undefined) - useImperativeHandle(ref, () => ({ setWidth, setWrap: setOpen, setWrapperName: setTitle }), []) + const refItem = useMemo((): PluginWrapperMethods => { + return { + setWidth, + setWrap: setOpen, + setWrapperName: setTitle, + } + }, []) + + useImperativeHandle(ref, () => refItem, [refItem]) if (!open) return <>{props.children} return ( diff --git a/packages/mask/src/plugins/Pets/constants.ts b/packages/mask/src/plugins/Pets/constants.ts index 103364138493..cd0a547c4653 100644 --- a/packages/mask/src/plugins/Pets/constants.ts +++ b/packages/mask/src/plugins/Pets/constants.ts @@ -38,9 +38,7 @@ export const Punk3D = { } export const Share_Twitter = 'https://twitter.com/realMaskNetwork/status/1486648872558424064' -export const Share_Twitter_TXT = encodeURIComponent( - `I just set up my Non-Fungible Friend with @realMaskNetwork (powered by @MintTeamNFT). Visit my profile to check it out! Install Mask Network extension from mask.io and set yours.\n #mask_io #nonfungiblefriends\n${Share_Twitter}`, -) +export const Share_Twitter_TXT = `I just set up my Non-Fungible Friend with @realMaskNetwork (powered by @MintTeamNFT). Visit my profile to check it out! Install Mask Network extension from mask.io and set yours.\n #mask_io #nonfungiblefriends\n${Share_Twitter}` export const GLB3DIcon = new URL('./assets/glb3D.png', import.meta.url).toString() export const CloseIcon = new URL('./assets/close.png', import.meta.url).toString() diff --git a/packages/mask/src/plugins/Wallet/apis/debank.ts b/packages/mask/src/plugins/Wallet/apis/debank.ts index 47144aed6f31..e72fdc67de63 100644 --- a/packages/mask/src/plugins/Wallet/apis/debank.ts +++ b/packages/mask/src/plugins/Wallet/apis/debank.ts @@ -17,7 +17,12 @@ export async function getAssetsList(address: string) { `${DEBANK_OPEN_API}/v1/user/token_list?is_all=true&has_balance=true&id=${address.toLowerCase()}`, ) try { - return ((await response.json()) ?? []) as WalletTokenRecord[] + const records = ((await response.json()) ?? []) as WalletTokenRecord[] + return records.map((x) => ({ + ...x, + id: x.id === 'bsc' ? 'bnb' : x.id, + chain: x.chain === 'bsc' ? 'bnb' : x.chain, + })) } catch { return [] } diff --git a/packages/mask/src/social-network-adaptor/twitter.com/injection/PostDialogHint.tsx b/packages/mask/src/social-network-adaptor/twitter.com/injection/PostDialogHint.tsx index 37b1836affa3..2eb6d896327d 100644 --- a/packages/mask/src/social-network-adaptor/twitter.com/injection/PostDialogHint.tsx +++ b/packages/mask/src/social-network-adaptor/twitter.com/injection/PostDialogHint.tsx @@ -4,7 +4,6 @@ import { isReplyPageSelector, postEditorInPopupSelector, searchReplyToolbarSelec import { createReactRootShadowed } from '../../../utils/shadow-root/renderInShadowRoot' import { PostDialogHint } from '../../../components/InjectedComponents/PostDialogHint' import { MaskMessages } from '../../../utils/messages' -import { hasEditor, isCompose } from '../utils/postBox' import { startWatch } from '../../../utils/watcher' import { makeStyles, MaskColorVar } from '@masknet/theme' import { alpha } from '@mui/material' @@ -12,6 +11,7 @@ import { twitterBase } from '../base' import { sayHelloShowed } from '../../../settings/settings' import { makeTypedMessageText } from '@masknet/typed-message' import { useI18N } from '../../../utils' +import { hasEditor, isCompose } from '../utils/postBox' const useStyles = makeStyles()((theme) => ({ iconButton: { diff --git a/packages/mask/src/social-network-adaptor/twitter.com/utils/selector.ts b/packages/mask/src/social-network-adaptor/twitter.com/utils/selector.ts index 7382fbb7032f..398d94890015 100644 --- a/packages/mask/src/social-network-adaptor/twitter.com/utils/selector.ts +++ b/packages/mask/src/social-network-adaptor/twitter.com/utils/selector.ts @@ -110,9 +110,7 @@ export const postEditorContentInPopupSelector: () => LiveSelector = () '[aria-labelledby="modal-header"] > div:first-child > div:first-child > div:first-child > div:nth-child(3)', ) export const postEditorInPopupSelector: () => LiveSelector = () => - querySelector( - '[aria-labelledby="modal-header"] > div:first-child > div:first-child > div:first-child > div:nth-child(3) > div:first-child [role="button"][aria-label]:nth-child(6)', - ) + querySelector('[aria-labelledby="modal-header"] div[data-testid="toolBar"] div[data-testid="geoButton"]') export const toolBoxInSideBarSelector: () => LiveSelector = () => querySelector('[role="banner"] [role="navigation"] > div') export const sideBarProfileSelector: () => LiveSelector = () => @@ -157,7 +155,7 @@ export const bioPageUserNickNameSelector = () => .map((x) => x.parentElement?.parentElement?.previousElementSibling) .querySelector('div[dir]') export const bioPageUserIDSelector = (selector: () => LiveSelector) => - selector().map((x) => (x.parentElement?.nextElementSibling as HTMLElement).innerText.replace('@', '')) + selector().map((x) => (x.parentElement?.nextElementSibling as HTMLElement)?.innerText?.replace('@', '')) export const floatingBioCardSelector = () => querySelector( '[style~="left:"] a[role=link] > div:first-child > div:first-child > div:first-child[dir="auto"]', @@ -267,7 +265,7 @@ export const searchProfileSetAvatarSelector = () => ? searchProfessionalButtonSelector() .closest(4) .querySelector('div > div:nth-child(2) >div > div:nth-child(2)') - : querySelector('[data-testid="ProfileBirthdate"]') + : querySelector('[data-testid^="ProfileBirthdate"]') .closest(5) .querySelector('div > div:nth-child(2) > div:nth-child(2)') // #endregion diff --git a/packages/plugin-infra/src/utils/createInjectHooksRenderer.tsx b/packages/plugin-infra/src/utils/createInjectHooksRenderer.tsx index 5264eb6cfa97..2da5547361b3 100644 --- a/packages/plugin-infra/src/utils/createInjectHooksRenderer.tsx +++ b/packages/plugin-infra/src/utils/createInjectHooksRenderer.tsx @@ -3,7 +3,7 @@ import { ErrorBoundary } from '@masknet/shared-base-ui' import { ShadowRootIsolation } from '@masknet/theme' import type { Plugin } from '../types' import { usePluginI18NField, PluginWrapperComponent, PluginWrapperMethods } from '../hooks' -import { emptyPluginWrapperMethods, PluginWrapperMethodsContext } from '../hooks/usePluginWrapper' +import { PluginWrapperMethodsContext } from '../hooks/usePluginWrapper' type Inject = Plugin.InjectUI type Raw = Plugin.InjectUIRaw @@ -18,10 +18,12 @@ export function createInjectHooksRenderer(null) if (PluginWrapperComponent) { return ( - (ref === r ? void 0 : setRef(ref))}> - - {element} - + + {ref ? ( + + {element} + + ) : null} ) } diff --git a/packages/plugins/CyberConnect/src/base.tsx b/packages/plugins/CyberConnect/src/base.tsx index f299ed33c3aa..4d9b8121ffd9 100644 --- a/packages/plugins/CyberConnect/src/base.tsx +++ b/packages/plugins/CyberConnect/src/base.tsx @@ -2,6 +2,7 @@ import { Plugin, NetworkPluginID } from '@masknet/plugin-infra' import { ChainId } from '@masknet/web3-shared-evm' import { CYBERCONNECT_PLUGIN_ID } from './constants' import { CyberConnectIcon } from './assets/CyberConnectIcon' + export const base: Plugin.Shared.Definition = { ID: CYBERCONNECT_PLUGIN_ID, icon: , diff --git a/packages/scripts/src/extension/normal.ts b/packages/scripts/src/extension/normal.ts index 22c2ccdb8e22..2af02b00bdda 100644 --- a/packages/scripts/src/extension/normal.ts +++ b/packages/scripts/src/extension/normal.ts @@ -13,7 +13,10 @@ const presets = ['chromium', 'firefox', 'android', 'iOS', 'base'] as const const otherFlags = ['beta', 'insider', 'reproducible', 'profile', 'mv3', 'readonlyCache', 'progress'] as const export async function extension(f?: Function | ExtensionBuildArgs) { - await Promise.all([buildPolyfill(), buildInjectedScript(), buildMaskSDK(), buildGun()]) + await buildPolyfill() + await buildInjectedScript() + await buildGun() + await buildMaskSDK() if (typeof f === 'function') return awaitChildProcess(webpack('build')) return awaitChildProcess(webpack('build', f)) } diff --git a/packages/shared/src/UI/components/NFTCardStyledAssetPlayer/index.tsx b/packages/shared/src/UI/components/NFTCardStyledAssetPlayer/index.tsx index 7a4a6b1d65fe..2aeb6adc0757 100644 --- a/packages/shared/src/UI/components/NFTCardStyledAssetPlayer/index.tsx +++ b/packages/shared/src/UI/components/NFTCardStyledAssetPlayer/index.tsx @@ -41,6 +41,7 @@ interface Props extends withClasses<'loadingFailImage' | 'iframe' | 'wrapper' | fallbackImage?: URL fallbackResourceLoader?: JSX.Element renderOrder?: number + isNative?: boolean setERC721TokenName?: (name: string) => void setSourceType?: (type: string) => void } @@ -49,6 +50,7 @@ export function NFTCardStyledAssetPlayer(props: Props) { chainId = ChainId.Mainnet, contractAddress = '', tokenId = '', + isNative = false, fallbackImage, fallbackResourceLoader, url, @@ -69,7 +71,7 @@ export function NFTCardStyledAssetPlayer(props: Props) { ? new URL('./nft_token_fallback_dark.png', import.meta.url) : new URL('./nft_token_fallback.png', import.meta.url) - return isImageToken ? ( + return isImageToken || isNative ? (
) { - const links = parseLink(string).flatMap((x, index) => { + const links = parseLink(string).flatMap((x) => { if (x.type === 'text') { - return x.content.split(/(\n)/g).map((x) => (x === '\n' && index !== 0 ?
: )) + return sliceString(x.content).map((x) => (x === '\n' ?
: )) } if (x.category === 'normal' && !x.content.match(/^https?:\/\//gi)) x.content = 'http://' + x.content return }) return links } + +function sliceString(x: string): string[] { + const result: string[] = [] + + let pos = 0 + let index = x.indexOf('\n') + + if (index === -1) return [x] + while (index !== -1) { + result.push(x.slice(pos, index), '\n') + pos = index + 1 + index = x.indexOf('\n', pos) + } + result.push(x.slice(pos)) + return result.filter(Boolean) +} diff --git a/packages/web3-providers/src/NFTScan/index.ts b/packages/web3-providers/src/NFTScan/index.ts index e9306f1eb700..e1dc4b639600 100644 --- a/packages/web3-providers/src/NFTScan/index.ts +++ b/packages/web3-providers/src/NFTScan/index.ts @@ -3,7 +3,7 @@ import { createERC721ContractDetailed, createERC721Token, ERC721TokenDetailed, - resolveResourceLink, + resolveIPFSLinkFromURL, ERC721ContractDetailed, } from '@masknet/web3-shared-evm' import addSeconds from 'date-fns/addSeconds' @@ -66,7 +66,9 @@ function createERC721TokenAsset(asset: NFTScanAsset) { { name: payload?.name ?? asset.nft_name ?? asset.nft_platform_name ?? '', description: payload?.description ?? '', - mediaUrl: resolveResourceLink(asset.nft_cover ?? asset.nft_content_uri ?? payload.image ?? ''), + mediaUrl: resolveIPFSLinkFromURL( + JSON.parse(asset.nft_json ?? '{}').image ?? asset.nft_content_uri ?? payload.image ?? '', + ), owner: asset.nft_holder ?? '', }, asset.token_id, @@ -155,7 +157,7 @@ export class NFTScanAPI implements NonFungibleTokenAPI.Provider { info: { name: t.nft_name, description: t.nft_detail, - mediaUrl: resolveResourceLink(t.nft_cover ?? t.nft_content_uri ?? ''), + mediaUrl: resolveIPFSLinkFromURL(JSON.parse(t.nft_json ?? '{}').image ?? t.nft_content_uri ?? ''), tokenURI: t.nft_token_uri, }, })) diff --git a/packages/web3-providers/src/debank/index.ts b/packages/web3-providers/src/debank/index.ts index aec25c572722..80c860c77f17 100644 --- a/packages/web3-providers/src/debank/index.ts +++ b/packages/web3-providers/src/debank/index.ts @@ -14,7 +14,13 @@ export async function getAssetListFromDebank(address: string) { ) try { const result = ((await response.json()) ?? []) as WalletTokenRecord[] - return formatAssets(result) + return formatAssets( + result.map((x) => ({ + ...x, + id: x.id === 'bsc' ? 'bnb' : x.id, + chain: x.chain === 'bsc' ? 'bnb' : x.chain, + })), + ) } catch { return [] } diff --git a/packages/web3-providers/src/kv/index.ts b/packages/web3-providers/src/kv/index.ts index f19115e85d02..612a9542ae41 100644 --- a/packages/web3-providers/src/kv/index.ts +++ b/packages/web3-providers/src/kv/index.ts @@ -14,7 +14,6 @@ export class JSON_Storage implements StorageAPI.Storage { }), { method: 'GET', - mode: 'no-cors', headers: { 'Content-Type': 'application/json', }, diff --git a/packages/web3-providers/src/rarible/index.ts b/packages/web3-providers/src/rarible/index.ts index 8e2195e5cae1..efeeb09258a5 100644 --- a/packages/web3-providers/src/rarible/index.ts +++ b/packages/web3-providers/src/rarible/index.ts @@ -6,7 +6,7 @@ import { ERC721TokenDetailed, EthereumTokenType, FungibleTokenDetailed, - resolveResourceLink, + resolveIPFSLinkFromURL, } from '@masknet/web3-shared-evm' import { Ownership, @@ -51,7 +51,7 @@ function createERC721TokenFromAsset( tokenId: string, asset?: RaribleNFTItemMapResponse, ): ERC721TokenDetailed { - const imageURL = resolveResourceLink(asset?.meta?.image?.url.ORIGINAL ?? asset?.meta?.image?.url.PREVIEW ?? '') + const imageURL = resolveIPFSLinkFromURL(asset?.meta?.image?.url.ORIGINAL ?? asset?.meta?.image?.url.PREVIEW ?? '') return { contractDetailed: { type: EthereumTokenType.ERC721, @@ -64,7 +64,7 @@ function createERC721TokenFromAsset( name: asset?.meta?.name ?? '', description: asset?.meta?.description ?? '', mediaUrl: - resolveResourceLink( + resolveIPFSLinkFromURL( asset?.meta?.animation?.url.ORIGINAL ?? asset?.meta?.animation?.url.PREVIEW ?? '', ) || imageURL, imageURL, @@ -81,7 +81,7 @@ function createNFTAsset(asset: RaribleNFTItemMapResponse, chainId: ChainId): Non is_verified: false, is_auction: false, token_address: asset.contract, - image_url: resolveResourceLink(asset?.meta?.image?.url.ORIGINAL ?? ''), + image_url: resolveIPFSLinkFromURL(asset?.meta?.image?.url.ORIGINAL ?? ''), asset_contract: null, owner: owner ? { @@ -214,7 +214,7 @@ export class RaribleAPI implements NonFungibleTokenAPI.Provider { maker_account: { user: { username: ownerInfo?.name ?? '' }, address: ownerInfo?.id ?? '', - profile_img_url: resolveResourceLink(ownerInfo?.image ?? ''), + profile_img_url: resolveIPFSLinkFromURL(ownerInfo?.image ?? ''), link: `${resolveRaribleUserNetwork(chainId as number)}${ownerInfo?.id ?? ''}`, }, } @@ -245,7 +245,7 @@ export class RaribleAPI implements NonFungibleTokenAPI.Provider { maker_account: { user: { username: ownerInfo?.name ?? '' }, address: ownerInfo?.id ?? '', - profile_img_url: resolveResourceLink(ownerInfo?.image ?? ''), + profile_img_url: resolveIPFSLinkFromURL(ownerInfo?.image ?? ''), link: `${resolveRaribleUserNetwork(chainId as number)}${ownerInfo?.id ?? ''}`, }, } diff --git a/packages/web3-providers/src/zora/index.ts b/packages/web3-providers/src/zora/index.ts index 00f041ef36cd..53f908fddbb0 100644 --- a/packages/web3-providers/src/zora/index.ts +++ b/packages/web3-providers/src/zora/index.ts @@ -6,6 +6,7 @@ import { FungibleTokenDetailed, ERC721TokenDetailed, EthereumTokenType, + resolveIPFSLinkFromURL, } from '@masknet/web3-shared-evm' import { NonFungibleTokenAPI } from '..' @@ -14,11 +15,13 @@ import { getAssetQuery, getTokenHistoryQuery, getBidsQuery, getAsksQuery } from import { ZORA_MAINNET_GRAPHQL_URL } from './constants' function createNFTAsset(asset: ZoraToken): NonFungibleTokenAPI.Asset { + const image_url = + asset.metadata.json.image ?? asset.metadata.json.animation_url ?? asset.metadata.json.image_url ?? '' + const animation_url = asset.metadata.json.image ?? asset.metadata.json.animation_url ?? '' return { is_verified: false, is_auction: asset.currentAuction !== null, - image_url: - asset.metadata.json.animation_url ?? asset.metadata.json.image_url ?? asset.metadata.json.image ?? '', + image_url: resolveIPFSLinkFromURL(image_url), asset_contract: { name: asset.tokenContract.name, description: '', @@ -49,7 +52,7 @@ function createNFTAsset(asset: ZoraToken): NonFungibleTokenAPI.Asset { description: asset.metadata.json.description, name: asset.name ?? asset.metadata.json.name, collection_name: '', - animation_url: asset.metadata.json.animation_url, + animation_url: resolveIPFSLinkFromURL(animation_url), end_time: asset.currentAuction ? new Date(asset.currentAuction.expiresAt) : null, order_payment_tokens: [] as FungibleTokenDetailed[], offer_payment_tokens: [] as FungibleTokenDetailed[], diff --git a/packages/web3-shared/base/src/proxy/index.ts b/packages/web3-shared/base/src/proxy/index.ts index 74b4ded352f8..85ff04bdecf9 100644 --- a/packages/web3-shared/base/src/proxy/index.ts +++ b/packages/web3-shared/base/src/proxy/index.ts @@ -160,7 +160,7 @@ export class ProviderProxy { const SOCKET_POINT = // workaround, should create a stage env for QA testing process.env.NODE_ENV === 'production' && process.env.channel === 'stable' - ? 'wss://hyper-proxy.r2d2.to' + ? 'wss://hyper-proxy-production.mask-reverse-proxy.workers.dev' : 'wss://hyper-proxy-development.mask-reverse-proxy.workers.dev' enum SocketState { diff --git a/packages/web3-shared/evm/assets/chains.json b/packages/web3-shared/evm/assets/chains.json index 4f3e7530cc3a..7e8b9f9c89d7 100644 --- a/packages/web3-shared/evm/assets/chains.json +++ b/packages/web3-shared/evm/assets/chains.json @@ -151,7 +151,7 @@ "chainId": 97, "fullName": "Binance", "shortName": "bnbt", - "chain": "BSC", + "chain": "BNB Chain", "network": "chapel", "networkId": 97, "nativeCurrency": { diff --git a/packages/web3-shared/evm/pipes/index.ts b/packages/web3-shared/evm/pipes/index.ts index 4125068846c5..9e247a3caf95 100644 --- a/packages/web3-shared/evm/pipes/index.ts +++ b/packages/web3-shared/evm/pipes/index.ts @@ -174,16 +174,14 @@ export function resolveBlockLinkOnExplorer(chainId: ChainId, block: string): str return urlcat(resolveLinkOnExplorer(chainId), '/block/:block', { block }) } +// TODO check ipfs inside before resolving export function resolveIPFSLink(ipfs: string): string { return urlcat('https://coldcdn.com/api/cdn/mipfsygtms/ipfs/:ipfs', { ipfs }) } -export function resolveResourceLink(originLink: string): string { - if (!originLink) return '' - if (originLink.startsWith('http') || originLink.startsWith('data')) return originLink - if (originLink.startsWith('ipfs://ipfs/')) return resolveIPFSLink(originLink.replace(/^ipfs:\/\/ipfs\//, '')) - if (originLink.startsWith('ipfs://')) return resolveIPFSLink(decodeURIComponent(originLink).replace('ipfs://', '')) - return resolveIPFSLink(originLink) +export function resolveIPFSLinkFromURL(url: string): string { + if (!url.startsWith('ipfs://')) return url + return resolveIPFSLink(url.replace(/^ipfs:\/\/(ipfs\/)?/, '')) } export function resolveDomainLink(domain?: string) {