diff --git a/packages/dashboard/src/initialization/PluginHost.ts b/packages/dashboard/src/initialization/PluginHost.ts index 4e6e223cf984..272c30c82d69 100644 --- a/packages/dashboard/src/initialization/PluginHost.ts +++ b/packages/dashboard/src/initialization/PluginHost.ts @@ -3,7 +3,7 @@ import './plugins' import { Emitter } from '@servie/events' import { startPluginDashboard, Plugin } from '@masknet/plugin-infra/dashboard' import { Services, Messages } from '../API' -import { createI18NBundle, i18NextInstance } from '@masknet/shared-base' +import { createI18NBundle, createSubscriptionFromAsync, i18NextInstance } from '@masknet/shared-base' import { InMemoryStorages, PersistentStorages } from '../utils/kv-storage' const PluginHost: Plugin.__Host.Host = { @@ -17,6 +17,12 @@ const PluginHost: Plugin.__Host.Host = { createI18NBundle(plugin, resource)(i18NextInstance) }, createContext: (pluginID, signal) => { + const currentPersonaSub = createSubscriptionFromAsync( + Services.Settings.getCurrentPersonaIdentifier, + undefined, + Messages.events.currentPersonaIdentifier.on, + signal, + ) return { createKVStorage(type, defaultValues) { if (type === 'memory') return InMemoryStorages.Plugin.createSubScope(pluginID, defaultValues, signal) @@ -24,6 +30,7 @@ const PluginHost: Plugin.__Host.Host = { }, personaSign: Services.Identity.signWithPersona, walletSign: Services.Ethereum.personalSign, + currentPersona: currentPersonaSub, } }, } diff --git a/packages/mask/src/components/DataSource/useActivatedUI.ts b/packages/mask/src/components/DataSource/useActivatedUI.ts index c5cef6e522c4..955ece93934e 100644 --- a/packages/mask/src/components/DataSource/useActivatedUI.ts +++ b/packages/mask/src/components/DataSource/useActivatedUI.ts @@ -3,9 +3,9 @@ import { ValueRef } from '@dimensiondev/holoflows-kit' import { useValueRef } from '@masknet/shared-base-ui' import { EMPTY_LIST, ProfileIdentifier } from '@masknet/shared-base' import type { Profile } from '../../database' -import type { SocialNetworkUI } from '../../social-network' import { activatedSocialNetworkUI, globalUIState } from '../../social-network' import { Subscription, useSubscription } from 'use-subscription' +import type { IdentityResolved } from '@masknet/plugin-infra' export function useFriendsList() { const ref = useValueRef(globalUIState.friends) @@ -14,12 +14,10 @@ export function useFriendsList() { const default_ = new ValueRef({ identifier: ProfileIdentifier.unknown }) export function useLastRecognizedIdentity() { - return useValueRef( - activatedSocialNetworkUI.collecting.identityProvider?.recognized || default_, - ) + return useValueRef(activatedSocialNetworkUI.collecting.identityProvider?.recognized || default_) } export function useCurrentVisitingIdentity() { - return useValueRef( + return useValueRef( activatedSocialNetworkUI.collecting.currentVisitingIdentityProvider?.recognized || default_, ) } diff --git a/packages/mask/src/components/InjectedComponents/SetupGuide.tsx b/packages/mask/src/components/InjectedComponents/SetupGuide.tsx index d05d309b1703..5ceb0996d641 100644 --- a/packages/mask/src/components/InjectedComponents/SetupGuide.tsx +++ b/packages/mask/src/components/InjectedComponents/SetupGuide.tsx @@ -2,7 +2,7 @@ import { useMemo, useState, useEffect, useRef } from 'react' import { makeStyles } from '@masknet/theme' import { useValueRef } from '@masknet/shared-base-ui' import { useI18N, MaskMessages } from '../../utils' -import { activatedSocialNetworkUI, SocialNetworkUI } from '../../social-network' +import { activatedSocialNetworkUI } from '../../social-network' import { currentSetupGuideStatus, languageSettings, userGuideStatus, userPinExtension } from '../../settings/settings' import type { SetupGuideCrossContextStatus } from '../../settings/types' import { makeTypedMessageText } from '@masknet/typed-message' @@ -27,6 +27,7 @@ import { FindUsername } from './SetupGuide/FindUsername' import { VerifyNextID } from './SetupGuide/VerifyNextID' import { PinExtension } from './SetupGuide/PinExtension' import { NextIDProof } from '@masknet/web3-providers' +import type { IdentityResolved } from '@masknet/plugin-infra' // #region setup guide ui interface SetupGuideUIProps { @@ -77,7 +78,7 @@ function SetupGuideUI(props: SetupGuideUIProps) { : lastRecognized.identifier.userId !== lastState.username useEffect(() => { - const handler = (val: SocialNetworkUI.CollectingCapabilities.IdentityResolved) => { + const handler = (val: IdentityResolved) => { if (username === '' && !val.identifier.isUnknown) setUsername(val.identifier.userId) } ui.collecting.identityProvider?.recognized.addListener(handler) diff --git a/packages/mask/src/extension/background-script/Jobs/StartPluginWorker.ts b/packages/mask/src/extension/background-script/Jobs/StartPluginWorker.ts index d11eb4390500..8d68101ce6c3 100644 --- a/packages/mask/src/extension/background-script/Jobs/StartPluginWorker.ts +++ b/packages/mask/src/extension/background-script/Jobs/StartPluginWorker.ts @@ -1,5 +1,6 @@ import { startPluginWorker, Plugin } from '@masknet/plugin-infra/background-worker' -import { InMemoryStorages, PersistentStorages } from '../../../../shared' +import { createSubscriptionFromAsync } from '@masknet/shared-base' +import { InMemoryStorages, MaskMessages, PersistentStorages } from '../../../../shared' import { createPluginDatabase } from '../../../database/Plugin' import { createPluginHost } from '../../../plugin-infra/host' import { Services } from '../../service' @@ -9,6 +10,13 @@ export default function (signal: AbortSignal) { function createWorkerContext(pluginID: string, signal: AbortSignal): Plugin.Worker.WorkerContext { let storage: Plugin.Worker.DatabaseStorage = undefined! + + const currentPersonaSub = createSubscriptionFromAsync( + Services.Settings.getCurrentPersonaIdentifier, + undefined, + MaskMessages.events.currentPersonaIdentifier.on, + signal, + ) return { getDatabaseStorage() { return storage || (storage = createPluginDatabase(pluginID, signal)) @@ -19,5 +27,6 @@ function createWorkerContext(pluginID: string, signal: AbortSignal): Plugin.Work }, personaSign: Services.Identity.signWithPersona, walletSign: Services.Ethereum.personalSign, + currentPersona: currentPersonaSub, } } diff --git a/packages/mask/src/extension/dashboard/index.tsx b/packages/mask/src/extension/dashboard/index.tsx index bfbb0098cf32..79aaec76e3bb 100644 --- a/packages/mask/src/extension/dashboard/index.tsx +++ b/packages/mask/src/extension/dashboard/index.tsx @@ -12,6 +12,7 @@ import type { DashboardPluginMessages, DashboardPluginServices } from '@masknet/ import { createNormalReactRoot } from '../../utils/createNormalReactRoot' import { InMemoryStorages, PersistentStorages } from '../../../shared/kv-storage' import { status } from '../../setup.ui' +import { createSubscriptionFromAsync } from '@masknet/shared-base' const msg: DashboardPluginMessages = { Wallet: WalletMessages, @@ -33,6 +34,12 @@ setPluginServices(rpc) setPluginMessages(msg) startPluginDashboard( createPluginHost(undefined, (pluginID, signal) => { + const currentPersonaSub = createSubscriptionFromAsync( + Services.Settings.getCurrentPersonaIdentifier, + undefined, + MaskMessages.events.currentPersonaIdentifier.on, + signal, + ) return { createKVStorage(type, defaultValues) { if (type === 'memory') return InMemoryStorages.Plugin.createSubScope(pluginID, defaultValues, signal) @@ -40,6 +47,7 @@ startPluginDashboard( }, personaSign: Services.Identity.signWithPersona, walletSign: Services.Ethereum.personalSign, + currentPersona: currentPersonaSub, } }), ) diff --git a/packages/mask/src/extension/popups/render.tsx b/packages/mask/src/extension/popups/render.tsx index eab329694ff8..264c7fc796fc 100644 --- a/packages/mask/src/extension/popups/render.tsx +++ b/packages/mask/src/extension/popups/render.tsx @@ -1,16 +1,24 @@ import { startPluginDashboard } from '@masknet/plugin-infra/dashboard' -import { createNormalReactRoot } from '../../utils' +import { createNormalReactRoot, MaskMessages } from '../../utils' import { createPluginHost } from '../../plugin-infra/host' import { Services } from '../service' import { status } from '../../setup.ui' import Popups from './UI' import { InMemoryStorages, PersistentStorages } from '../../../shared/kv-storage' +import { createSubscriptionFromAsync } from '@masknet/shared-base' status.then(() => createNormalReactRoot()) // TODO: Should only load plugins when the page is plugin-aware. + startPluginDashboard( createPluginHost(undefined, (pluginID, signal) => { + const currentPersonaSub = createSubscriptionFromAsync( + Services.Settings.getCurrentPersonaIdentifier, + undefined, + MaskMessages.events.currentPersonaIdentifier.on, + signal, + ) return { createKVStorage(type, defaultValues) { if (type === 'memory') return InMemoryStorages.Plugin.createSubScope(pluginID, defaultValues, signal) @@ -18,6 +26,7 @@ startPluginDashboard( }, personaSign: Services.Identity.signWithPersona, walletSign: Services.Ethereum.personalSign, + currentPersona: currentPersonaSub, } }), ) diff --git a/packages/mask/src/social-network-adaptor/facebook.com/collecting/identity.ts b/packages/mask/src/social-network-adaptor/facebook.com/collecting/identity.ts index a04c503a6a07..27e4dce8ca84 100644 --- a/packages/mask/src/social-network-adaptor/facebook.com/collecting/identity.ts +++ b/packages/mask/src/social-network-adaptor/facebook.com/collecting/identity.ts @@ -6,6 +6,7 @@ import { ProfileIdentifier, EnhanceableSite } from '@masknet/shared-base' import { searchAvatarSelector, searchUserIdOnMobileSelector } from '../utils/selector' import { getAvatar, getBioDescription, getFacebookId, getNickName, getPersonalHomepage } from '../utils/user' import { delay } from '@dimensiondev/kit' +import type { IdentityResolved } from '@masknet/plugin-infra' export const IdentityProviderFacebook: SocialNetworkUI.CollectingCapabilities.IdentityResolveProvider = { hasDeprecatedPlaceholderName: true, @@ -15,7 +16,7 @@ export const IdentityProviderFacebook: SocialNetworkUI.CollectingCapabilities.Id }, } -function resolveLastRecognizedIdentityFacebookInner(ref: ValueRef, signal: AbortSignal) { +function resolveLastRecognizedIdentityFacebookInner(ref: ValueRef, signal: AbortSignal) { const self = (isMobileFacebook ? myUsernameLiveSelectorMobile : myUsernameLiveSelectorPC) .clone() .map((x) => getProfileIdentifierAtFacebook(x, false)) @@ -29,7 +30,7 @@ function resolveLastRecognizedIdentityFacebookInner(ref: ValueRef, signal characterData: true, }) signal.addEventListener('abort', () => watcher.stopWatch()) - function assign(i: Value) { + function assign(i: IdentityResolved) { if (!i.identifier.isUnknown) ref.value = i } fetch('/me', { method: 'HEAD', signal }) @@ -103,5 +104,4 @@ const myUsernameLiveSelectorMobile = new LiveSelector().querySelector div > ul > li:first-child a, #MComposer a', ) -type Value = SocialNetworkUI.CollectingCapabilities.IdentityResolved // #endregion diff --git a/packages/mask/src/social-network-adaptor/instagram.com/collecting/identity-provider.ts b/packages/mask/src/social-network-adaptor/instagram.com/collecting/identity-provider.ts index 0ad1cefd7b49..431ea2875591 100644 --- a/packages/mask/src/social-network-adaptor/instagram.com/collecting/identity-provider.ts +++ b/packages/mask/src/social-network-adaptor/instagram.com/collecting/identity-provider.ts @@ -2,6 +2,7 @@ import { SocialNetworkUI, creator } from '../../../social-network' import { ProfileIdentifier } from '@masknet/shared-base' import { instagramBase } from '../base' import { openDB } from 'idb/with-async-ittr' +import type { IdentityResolved } from '@masknet/plugin-infra' export const IdentityProviderInstagram: SocialNetworkUI.CollectingCapabilities.IdentityResolveProvider = { async start(signal) { const ref = this.recognized @@ -17,7 +18,7 @@ export const IdentityProviderInstagram: SocialNetworkUI.CollectingCapabilities.I recognized: creator.EmptyIdentityResolveProviderState(), } -async function query(): Promise { +async function query(): Promise { const db = await openDB('redux', 1, { upgrade: () => { db.createObjectStore('paths') diff --git a/packages/mask/src/social-network-adaptor/opensea.io/collecting/Identity.ts b/packages/mask/src/social-network-adaptor/opensea.io/collecting/Identity.ts index 1351f0d95f75..71c664819c7a 100644 --- a/packages/mask/src/social-network-adaptor/opensea.io/collecting/Identity.ts +++ b/packages/mask/src/social-network-adaptor/opensea.io/collecting/Identity.ts @@ -1,6 +1,7 @@ import { SocialNetworkUI, creator } from '../../../social-network' import { ProfileIdentifier } from '@masknet/shared-base' import { openseaBase } from '../base' +import type { IdentityResolved } from '@masknet/plugin-infra' export const IdentityProviderOpensea: SocialNetworkUI.CollectingCapabilities.IdentityResolveProvider = { async start(signal) { const ref = this.recognized @@ -16,7 +17,7 @@ export const IdentityProviderOpensea: SocialNetworkUI.CollectingCapabilities.Ide recognized: creator.EmptyIdentityResolveProviderState(), } -async function query(): Promise { +async function query(): Promise { function cookie2Json() { const res = decodeURIComponent(document.cookie) .split('; ') diff --git a/packages/mask/src/social-network/types.ts b/packages/mask/src/social-network/types.ts index 6e9dc95b4eee..55c646480efb 100644 --- a/packages/mask/src/social-network/types.ts +++ b/packages/mask/src/social-network/types.ts @@ -1,6 +1,6 @@ import type { ValueRef } from '@dimensiondev/holoflows-kit' import type { GrayscaleAlgorithm, SocialNetworkEnum } from '@masknet/encryption' -import type { PostInfo } from '@masknet/plugin-infra/content-script' +import type { IdentityResolved, PostInfo } from '@masknet/plugin-infra/content-script' import type { Identifier, ObservableWeakMap, @@ -227,7 +227,6 @@ export namespace SocialNetworkUI { getSearchedKeyword?(): string } export type ProfileUI = { bioContent: string } - export type IdentityResolved = Pick /** Resolve the information of who am I on the current network. */ export interface IdentityResolveProvider { diff --git a/packages/mask/src/social-network/ui.ts b/packages/mask/src/social-network/ui.ts index 8f54d8a0c768..17d93a7dc015 100644 --- a/packages/mask/src/social-network/ui.ts +++ b/packages/mask/src/social-network/ui.ts @@ -13,9 +13,10 @@ import { PersonaIdentifier, EnhanceableSite, i18NextInstance, + SubscriptionFromValueRef, } from '@masknet/shared-base' -import { Environment, assertNotEnvironment } from '@dimensiondev/holoflows-kit' -import { startPluginSNSAdaptor } from '@masknet/plugin-infra/content-script' +import { Environment, assertNotEnvironment, ValueRef } from '@dimensiondev/holoflows-kit' +import { IdentityResolved, startPluginSNSAdaptor } from '@masknet/plugin-infra/content-script' import { getCurrentSNSNetwork } from '../social-network-adaptor/utils' import { createPluginHost } from '../plugin-infra/host' import { definedSocialNetworkUIs } from './define' @@ -108,6 +109,21 @@ export async function activateSocialNetworkUIInner(ui_deferred: SocialNetworkUI. startPluginSNSAdaptor( getCurrentSNSNetwork(ui.networkIdentifier), createPluginHost(signal, (pluginID, signal) => { + const personaSub = createSubscriptionFromAsync( + Services.Settings.getCurrentPersonaIdentifier, + undefined as PersonaIdentifier | undefined, + MaskMessages.events.currentPersonaIdentifier.on, + signal, + ) + const empty = new ValueRef(undefined) + const lastRecognizedSub = SubscriptionFromValueRef( + ui.collecting.identityProvider?.recognized || empty, + signal, + ) + const currentVisitingSub = SubscriptionFromValueRef( + ui.collecting.currentVisitingIdentityProvider?.recognized || empty, + signal, + ) return { createKVStorage(type, defaultValues) { if (type === 'memory') @@ -116,11 +132,9 @@ export async function activateSocialNetworkUIInner(ui_deferred: SocialNetworkUI. }, personaSign: Services.Identity.signWithPersona, walletSign: Services.Ethereum.personalSign, - currentPersona: createSubscriptionFromAsync( - Services.Settings.getCurrentPersonaIdentifier, - undefined as PersonaIdentifier | undefined, - MaskMessages.events.currentPersonaIdentifier.on, - ), + currentPersona: personaSub, + lastRecognizedProfile: lastRecognizedSub, + currentVisitingProfile: currentVisitingSub, } }), ) diff --git a/packages/plugin-infra/src/types.ts b/packages/plugin-infra/src/types.ts index fe9e2dc24653..fe0af63f2f04 100644 --- a/packages/plugin-infra/src/types.ts +++ b/packages/plugin-infra/src/types.ts @@ -69,6 +69,7 @@ export namespace Plugin.Shared { personaSign(payload: PersonaSignRequest): Promise /** Sign a message with wallet */ walletSign(message: string, address: string): Promise + currentPersona: Subscription } export interface Definition { /** @@ -215,8 +216,8 @@ export namespace Plugin.Shared { /** This part runs in the SNSAdaptor */ export namespace Plugin.SNSAdaptor { export interface SNSAdaptorContext extends Shared.SharedContext { - /** Get current persona */ - currentPersona: Subscription + lastRecognizedProfile: Subscription + currentVisitingProfile: Subscription } export interface Definition extends Shared.DefinitionDeferred { /** This UI will be rendered for each post found. */ @@ -646,6 +647,14 @@ export enum CurrentSNSNetwork { Minds = 4, } +export interface IdentityResolved { + identifier: ProfileIdentifier + nickname?: string + avatar?: string + bio?: string + homepage?: string +} + /** * All integrated Plugin IDs */ diff --git a/packages/shared-base/src/utils/subscription.ts b/packages/shared-base/src/utils/subscription.ts index ce8c65929e31..ccc1de4392ac 100644 --- a/packages/shared-base/src/utils/subscription.ts +++ b/packages/shared-base/src/utils/subscription.ts @@ -13,6 +13,7 @@ export function createSubscriptionFromAsync( f: () => Promise, defaultValue: T, onChange: (callback: () => void) => () => void, + signal?: AbortSignal, ): Subscription { // 0 - idle, 1 - updating state, > 1 - waiting state let beats = 0 @@ -35,11 +36,13 @@ export function createSubscriptionFromAsync( return { getCurrentValue: () => state, subscribe: (sub) => { + if (signal?.aborted) return () => {} const a = subscribe(sub) const b = onChange(async () => { beats += 1 if (beats === 1) flush() }) + signal?.addEventListener('abort', () => [a(), b()], { once: true }) return () => void [a(), b()] }, } @@ -70,10 +73,15 @@ export function mapSubscription(sub: Subscription, mapper: (val: T) => } } -export function SubscriptionFromValueRef(ref: ValueRef): Subscription { +export function SubscriptionFromValueRef(ref: ValueRef, signal?: AbortSignal): Subscription { return SubscriptionDebug({ getCurrentValue: () => ref.value, - subscribe: (sub) => ref.addListener(sub), + subscribe: (sub) => { + if (signal?.aborted) return noop + const f = ref.addListener(sub) + signal?.addEventListener('abort', f, { once: true }) + return f + }, }) } export function SubscriptionDebug(x: Subscription): Subscription {