Skip to content
Draft
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .node-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v23.6.0
v24.7.0
6 changes: 6 additions & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -246,12 +246,16 @@
"words": [
"arbitrum",
"boba",
"Bsky",
"cashtags",
"celo",
"deeplink",
"endregion",
"firelfy",
"linkedin",
"luma",
"muln",
"pathnames",
"reposted",
"reposts",
"sepolia",
Expand All @@ -260,6 +264,8 @@
"tweetnacl",
"txid",
"waitlist",
"WARPCAST",
"webm",
"youtube"
]
}
19 changes: 10 additions & 9 deletions packages/mask/background/database/persona/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,31 +180,32 @@ export async function createPersonaByJsonWebKey(options: {

export async function createProfileWithPersona(
profileID: ProfileIdentifier,
data: LinkedProfileDetails,
keys: {
linkMeta: LinkedProfileDetails,
persona: {
nickname?: string
publicKey: EC_Public_JsonWebKey
privateKey?: EC_Private_JsonWebKey
localKey?: AESJsonWebKey
mnemonic?: PersonaRecord['mnemonic']
},
token?: string | null,
): Promise<void> {
const ec_id = (await ECKeyIdentifier.fromJsonWebKey(keys.publicKey)).unwrap()
const ec_id = (await ECKeyIdentifier.fromJsonWebKey(persona.publicKey)).unwrap()
const rec: PersonaRecord = {
createdAt: new Date(),
updatedAt: new Date(),
identifier: ec_id,
linkedProfiles: new Map(),
nickname: keys.nickname,
publicKey: keys.publicKey,
privateKey: keys.privateKey,
localKey: keys.localKey,
mnemonic: keys.mnemonic,
nickname: persona.nickname,
publicKey: persona.publicKey,
privateKey: persona.privateKey,
localKey: persona.localKey,
mnemonic: persona.mnemonic,
hasLogout: false,
}
await consistentPersonaDBWriteAccess(async (t) => {
await createOrUpdatePersonaDB(rec, { explicitUndefinedField: 'ignore', linkedProfiles: 'merge' }, t)
await attachProfileDB(profileID, ec_id, data, t)
await attachProfileDB(profileID, ec_id, linkMeta, { token }, t)
})
}
// #endregion
Expand Down
1 change: 1 addition & 0 deletions packages/mask/background/database/persona/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export interface ProfileRecord {
nickname?: string
localKey?: AESJsonWebKey
linkedPersona?: PersonaIdentifier
token?: string
createdAt: Date
updatedAt: Date
}
Expand Down
7 changes: 7 additions & 0 deletions packages/mask/background/database/persona/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,7 @@ export async function attachProfileDB(
identifier: ProfileIdentifier,
attachTo: PersonaIdentifier,
data: LinkedProfileDetails,
profileExtra?: { token?: string | null },
t?: FullPersonaDBTransaction<'readwrite'>,
): Promise<void> {
t = t || createTransaction(await db(), 'readwrite')('personas', 'profiles', 'relations')
Expand All @@ -536,6 +537,12 @@ export async function attachProfileDB(
await detachProfileDB(identifier, t)
}

if (profileExtra?.token) {
profile.token = profileExtra.token
} else if (profileExtra && 'token' in profileExtra && !profileExtra.token) {
delete profile.token
}

profile.linkedPersona = attachTo
persona.linkedProfiles.set(identifier, data)

Expand Down
8 changes: 5 additions & 3 deletions packages/mask/background/services/identity/profile/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,22 +86,23 @@ export async function resolveUnknownLegacyIdentity(identifier: ProfileIdentifier
export async function attachProfile(
source: ProfileIdentifier,
target: ProfileIdentifier | PersonaIdentifier,
data: LinkedProfileDetails,
linkMeta: LinkedProfileDetails,
profileExtra?: { token?: string | null },
): Promise<void> {
if (target instanceof ProfileIdentifier) {
const profile = await queryProfileDB(target)
if (!profile?.linkedPersona) throw new Error('target not found')
target = profile.linkedPersona
}
return attachProfileDB(source, target, data)
return attachProfileDB(source, target, linkMeta, profileExtra)
}
export function detachProfile(identifier: ProfileIdentifier): Promise<void> {
return detachProfileDB(identifier)
}

/**
* Set NextID profile to profileDB
* */
*/

export async function attachNextIDPersonaToProfile(item: ProfileInformationFromNextID, whoAmI: ECKeyIdentifier) {
if (!item.linkedPersona) throw new Error('LinkedPersona Not Found')
Expand Down Expand Up @@ -137,6 +138,7 @@ export async function attachNextIDPersonaToProfile(item: ProfileInformationFromN
profileRecord.identifier,
item.linkedPersona!,
{ connectionConfirmState: 'confirmed' },
undefined,
t,
)
await createOrUpdateRelationDB(
Expand Down
6 changes: 3 additions & 3 deletions packages/mask/background/services/site-adaptors/connect.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { compact, first, sortBy } from 'lodash-es'
import stringify from 'json-stable-stringify'
import { delay } from '@masknet/kit'
import {
type PersonaIdentifier,
type ProfileIdentifier,
currentSetupGuideStatus,
SetupGuideStep,
} from '@masknet/shared-base'
import stringify from 'json-stable-stringify'
import { compact, first, sortBy } from 'lodash-es'
import type { Tabs } from 'webextension-polyfill'
import { definedSiteAdaptors } from '../../../shared/site-adaptors/definitions.js'
import type { SiteAdaptor } from '../../../shared/site-adaptors/types.js'
import type { Tabs } from 'webextension-polyfill'

async function hasPermission(origin: string): Promise<boolean> {
return browser.permissions.contains({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Trans } from '@lingui/react/macro'
import { Icons } from '@masknet/icons'
import { BindingDialog, LoadingStatus, SOCIAL_MEDIA_ROUND_ICON_MAPPING, type BindingDialogProps } from '@masknet/shared'
import { Sniffings, SOCIAL_MEDIA_NAME } from '@masknet/shared-base'
Expand All @@ -6,7 +7,6 @@ import { Box, Button, Typography } from '@mui/material'
import { memo } from 'react'
import { activatedSiteAdaptorUI } from '../../../site-adaptor-infra/ui.js'
import { SetupGuideContext } from './SetupGuideContext.js'
import { Trans } from '@lingui/react/macro'

const useStyles = makeStyles()((theme) => {
return {
Expand Down
4 changes: 3 additions & 1 deletion packages/mask/popups/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import { EnhanceableSite } from '@masknet/shared-base'
export const MATCH_PASSWORD_RE = /^(?=.{8,20}$)(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[^\dA-Za-z]).*/u
export const MAX_FILE_SIZE = 10 * 1024 * 1024

export const SOCIAL_MEDIA_ICON_FILTER_COLOR: Record<EnhanceableSite | string, string> = {
export const SOCIAL_MEDIA_ICON_FILTER_COLOR: Record<EnhanceableSite, string> = {
[EnhanceableSite.Twitter]: 'drop-shadow(0px 6px 12px rgba(29, 161, 242, 0.20))',
[EnhanceableSite.Facebook]: 'drop-shadow(0px 6px 12px rgba(60, 89, 155, 0.20))',
[EnhanceableSite.Minds]: 'drop-shadow(0px 6px 12px rgba(33, 37, 42, 0.20))',
[EnhanceableSite.Instagram]: 'drop-shadow(0px 6px 12px rgba(246, 100, 16, 0.20))',
[EnhanceableSite.OpenSea]: '',
[EnhanceableSite.Mirror]: '',
[EnhanceableSite.Localhost]: '',
[EnhanceableSite.Firefly]: '',
[EnhanceableSite.Farcaster]: '',
}
18 changes: 12 additions & 6 deletions packages/mask/popups/modals/ConnectSocialAccountModal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
import { memo, useCallback } from 'react'
import { EMPTY_LIST, type EnhanceableSite } from '@masknet/shared-base'
import Services from '#services'
import { Trans } from '@lingui/react/macro'
import { PersonaContext } from '@masknet/shared'
import { EMPTY_LIST, EnhanceableSite, PopupRoutes } from '@masknet/shared-base'
import { Telemetry } from '@masknet/web3-telemetry'
import { EventType } from '@masknet/web3-telemetry/types'
import { memo, useCallback } from 'react'
import { useNavigate } from 'react-router-dom'
import { requestPermissionFromExtensionPage } from '../../../shared-ui/index.js'
import { ActionModal, type ActionModalBaseProps } from '../../components/index.js'
import { EventMap } from '../../../shared/definitions/event.js'
import { ConnectSocialAccounts } from '../../components/ConnectSocialAccounts/index.js'
import { ActionModal, type ActionModalBaseProps } from '../../components/index.js'
import { useSupportSocialNetworks } from '../../hooks/index.js'
import Services from '#services'
import { EventMap } from '../../../shared/definitions/event.js'
import { Trans } from '@lingui/react/macro'

export const ConnectSocialAccountModal = memo<ActionModalBaseProps>(function ConnectSocialAccountModal(props) {
const { data: definedSocialNetworks = EMPTY_LIST } = useSupportSocialNetworks()

const { currentPersona } = PersonaContext.useContainer()
const navigate = useNavigate()

const handleConnect = useCallback(
async (networkIdentifier: EnhanceableSite) => {
if (networkIdentifier === EnhanceableSite.Farcaster) {
navigate(PopupRoutes.ConnectFirefly)
return
}
if (!currentPersona) return
if (!(await requestPermissionFromExtensionPage(networkIdentifier))) return
await Services.SiteAdaptor.connectSite(currentPersona.identifier, networkIdentifier, undefined)
Expand Down
12 changes: 5 additions & 7 deletions packages/mask/popups/modals/SupportedSitesModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,21 +75,19 @@ export const SupportedSitesModal = memo<ActionModalBaseProps>(function Supported
<List className={classes.list}>
{!isPending && data ?
data.map((x) => {
const Icon = SOCIAL_MEDIA_ROUND_ICON_MAPPING[x.networkIdentifier]

const networkIdentifier = x.networkIdentifier as EnhanceableSite
const Icon = SOCIAL_MEDIA_ROUND_ICON_MAPPING[networkIdentifier]
return (
<ListItemButton
key={x.networkIdentifier}
className={classes.item}
onClick={() =>
handleSwitch({ ...x, networkIdentifier: x.networkIdentifier as EnhanceableSite })
}>
onClick={() => handleSwitch({ ...x, networkIdentifier })}>
{Icon ?
<ListItemIcon className={classes.icon}>
<Icon
size={24}
style={{
filter: SOCIAL_MEDIA_ICON_FILTER_COLOR[x.networkIdentifier],
filter: SOCIAL_MEDIA_ICON_FILTER_COLOR[networkIdentifier],
backdropFilter: 'blur(8px)',
borderRadius: 99,
}}
Expand All @@ -98,7 +96,7 @@ export const SupportedSitesModal = memo<ActionModalBaseProps>(function Supported
: null}
<ListItemText
classes={{ primary: classes.name }}
primary={SOCIAL_MEDIA_NAME[x.networkIdentifier]}
primary={SOCIAL_MEDIA_NAME[networkIdentifier]}
/>
<Switch checked={!!x.hasPermission && !!x.allowInject} />
</ListItemButton>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {
FarcasterSession,
FIREFLY_ROOT_URL,
fireflySessionHolder,
patchFarcasterSessionRequired,
resolveFireflyResponseData,
} from '@masknet/web3-providers'
import { SessionType, type FireflyConfigAPI, type Session } from '@masknet/web3-providers/types'
import urlcat from 'urlcat'

async function bindFarcasterSessionToFirefly(session: FarcasterSession, signal?: AbortSignal) {
const isGrantByPermission = FarcasterSession.isGrantByPermission(session, true)
const isRelayService = FarcasterSession.isRelayService(session)

if (!isGrantByPermission && !isRelayService)
throw new Error(
'[bindFarcasterSessionToFirefly] Only grant-by-permission or relay service sessions are allowed.',
)

const response = await fireflySessionHolder.fetch<FireflyConfigAPI.BindResponse>(
urlcat(FIREFLY_ROOT_URL, '/v3/user/bindFarcaster'),
{
method: 'POST',
body: JSON.stringify({
token: isGrantByPermission ? session.signerRequestToken : undefined,
channelToken: isRelayService ? session.channelToken : undefined,
isForce: false,
}),
signal,
},
)

if (response.error?.some((x) => x.includes('Farcaster binding timed out'))) {
throw new Error('Bind Farcaster account to Firefly timeout.')
}

// If the farcaster is already bound to another account, throw an error.
if (
isRelayService &&
response.error?.some((x) => x.includes('This farcaster already bound to the other account'))
) {
throw new Error('This Farcaster account has already bound to another Firefly account.')
}

const data = resolveFireflyResponseData(response)
patchFarcasterSessionRequired(session, data.fid, data.farcaster_signer_private_key)
return data
}

/**
* Bind a lens or farcaster session to the currently logged-in Firefly session.
* @param session
* @param signal
* @returns
*/
export async function bindFireflySession(session: Session, signal?: AbortSignal) {
// Ensure that the Firefly session is resumed before calling this function.
fireflySessionHolder.assertSession()
if (session.type === SessionType.Farcaster) {
return bindFarcasterSessionToFirefly(session as FarcasterSession, signal)
} else if (session.type === SessionType.Firefly) {
throw new Error('Not allowed')
}
throw new Error(`Unknown session type: ${session.type}`)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { fireflySessionHolder } from '@masknet/web3-providers'
import type { Session } from '@masknet/web3-providers/types'
import { bindFireflySession } from './bindFireflySession'
import { restoreFireflySession } from './restoreFireflySession'

export async function bindOrRestoreFireflySession(session: Session, signal?: AbortSignal) {
try {
if (fireflySessionHolder.session) {
await bindFireflySession(session, signal)

// this will return the existing session
return fireflySessionHolder.assertSession(
'[bindOrRestoreFireflySession] Failed to bind farcaster session with firefly.',
)
} else {
throw new Error('[bindOrRestoreFireflySession] Firefly session is not available.')
}
} catch (error) {
// this will create a new session
return restoreFireflySession(session, signal)
}
}
Loading
Loading