Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"yarn": ">=999.0.0",
"npm": ">=999.0.0"
},
"version": "2.35.1",
"version": "2.35.2",
"private": true,
"license": "AGPL-3.0-or-later",
"scripts": {
Expand Down
4 changes: 4 additions & 0 deletions packages/icons/general/QrcodeIcon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions packages/icons/icon-generated-as-jsx.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/icons/icon-generated-as-url.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions packages/mask/.webpack/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,6 @@ export async function createConfiguration(
NEXT_PUBLIC_FIREFLY_API_URL: process.env.NEXT_PUBLIC_FIREFLY_API_URL || '',
SOLANA_DEFAULT_RPC_URL: process.env.SOLANA_DEFAULT_RPC_URL || '',
MASK_ENABLE_EXCHANGE: process.env.MASK_ENABLE_EXCHANGE || '',
GOOGLE_CLIENT_ID: JSON.stringify(process.env.GOOGLE_CLIENT_ID) || '',
LOAD_KEY: process.env.LOAD_KEY || '',
FIREFLY_X_CLIENT_ID: process.env.FIREFLY_X_CLIENT_ID || '',
FIREFLY_X_CLIENT_SECRET: process.env.FIREFLY_X_CLIENT_SECRET || '',
PRIVY_APP_ID: process.env.PRIVY_APP_ID || '',
Expand Down
2 changes: 1 addition & 1 deletion packages/mask/.webpack/manifest/manifest-mv3.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0",
"manifest_version": 3,
"permissions": ["storage", "webNavigation", "activeTab", "scripting"],
"optional_permissions": ["notifications", "clipboardRead", "identity", "tabs"],
"optional_permissions": ["notifications", "clipboardRead", "identity", "tabs", "cookies"],
"optional_host_permissions": ["<all_urls>"],
"background": { "service_worker": "/manifest-v3.entry.js" },
"icons": {
Expand Down
2 changes: 1 addition & 1 deletion packages/mask/.webpack/manifest/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0",
"manifest_version": 2,
"permissions": ["storage", "webNavigation", "activeTab", "identity"],
"optional_permissions": ["<all_urls>", "notifications", "clipboardRead", "tabs"],
"optional_permissions": ["<all_urls>", "notifications", "clipboardRead", "tabs", "cookies"],
"background": { "page": "background.html" },
"icons": {
"16": "assets/16x16.png",
Expand Down
159 changes: 157 additions & 2 deletions packages/mask/background/services/helper/firefly.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,27 @@
import { PersistentStorages } from '@masknet/shared-base'
import { fromHex, toHex, PersistentStorages } from '@masknet/shared-base'

const FIREFLY_ROOT_URL = 'https://firefly.social'
const FIREFLY_API_URL = 'https://api.firefly.land'
const APP_LOGIN_ENCRYPT_IV = '0x4f05c37c16c801c2516b0338a8fd0cf9'

// Types for desktop sync
export interface SocialAccountTwitter {
type: 'x' // SourceInURL.X
user_id: string
handle: string
consumerKey: string
consumerKeySecret: string
accessToken: string
accessTokenSecret: string
cookie?: string
}

export interface TwitterOAuthData {
oauth_token: string
oauth_token_secret: string
user_id: string
screen_name: string
}

export async function loginFireflyViaTwitter() {
const data = await browser.storage.local.get('firefly_x_oauth')
Expand All @@ -18,7 +41,7 @@ export async function loginFireflyViaTwitter() {
const json = await res.json()
if (!json.success) throw new Error(json.message)

const res2 = await fetch('https://api.firefly.land/v3/auth/exchange/twitter', {
const res2 = await fetch(`${FIREFLY_API_URL}/v3/auth/exchange/twitter`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Expand All @@ -32,3 +55,135 @@ export async function loginFireflyViaTwitter() {
await PersistentStorages.Settings.storage.firefly_account.setValue(json2.data)
return json2.data
}

// Desktop sync functions
export async function encrypt(plainText: string, cryptoKey: string): Promise<string> {
const iv = fromHex(APP_LOGIN_ENCRYPT_IV)
const cryptoBytes = new TextEncoder().encode(cryptoKey)
const hashBuffer = await crypto.subtle.digest('SHA-256', cryptoBytes)
const aesKey = await crypto.subtle.importKey('raw', hashBuffer, { name: 'AES-CBC' }, false, ['encrypt'])

const plainBytes = new TextEncoder().encode(plainText)
const encryptedBuffer = await crypto.subtle.encrypt(
{
name: 'AES-CBC',
iv,
},
aesKey,
plainBytes,
)

const hex = toHex(new Uint8Array(encryptedBuffer))
return hex.startsWith('0x') ? hex.slice(2) : hex
}

export interface DesktopLinkInfoResponse {
link: string
session: string
expiresAt: string
}

export async function getDesktopSyncLinkInfo(accessToken: string): Promise<DesktopLinkInfoResponse> {
const url = `${FIREFLY_API_URL}/desktop/sync/linkInfo`
const response = await fetch(url, {
method: 'GET',
headers: {
Authorization: `Bearer ${accessToken}`,
},
})
if (!response.ok) throw new Error(`Failed to get desktop sync link info: ${response.statusText}`)
const json = await response.json()
if (json.code) throw new Error('Failed to get desktop sync link info, code: ' + json.code)
return json.data
}

export enum DesktopSyncChannelStatus {
Pending = 'pending',
Scanned = 'scanned',
Confirmed = 'confirmed',
DataReady = 'dataReady',
Cancel = 'cancel',
Expired = 'expired',
}

export interface SyncChannelStatusResponse {
status: DesktopSyncChannelStatus
}

export async function getSyncChannelStatus(session: string, accessToken: string): Promise<SyncChannelStatusResponse> {
const url = `${FIREFLY_API_URL}/desktop/sync/channelStatus`
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({ session }),
})
if (!response.ok) throw new Error(`Failed to get sync channel status: ${response.statusText}`)
const json = await response.json()
return json.data
}

export type ConfirmSyncChannelOperation = 'confirm' | 'cancel'

export async function confirmSyncChannel(
session: string,
operation: ConfirmSyncChannelOperation,
accessToken: string,
): Promise<{ status: string }> {
const url = `${FIREFLY_API_URL}/desktop/sync/confirm`
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({ session, op: operation }),
})
if (!response.ok) throw new Error(`Failed to confirm sync channel: ${response.statusText}`)
const json = await response.json()
return json.data
}

export interface TwitterCookiesPayload {
twitterAccounts: SocialAccountTwitter[]
fireflyAccountData: {
firefly_account_token: string
account_id: string
account_uid: string
display_name: string
avatar: string
}
}

// Helper function to get Twitter OAuth data from storage
export async function getTwitterOAuthData(): Promise<TwitterOAuthData | null> {
const data = await browser.storage.local.get('firefly_x_oauth')
if (!data?.firefly_x_oauth) return null
return data.firefly_x_oauth as TwitterOAuthData
}

export async function syncTwitterCookies(
session: string,
cryptoKey: string,
encryptedPayload: string,
accessToken: string,
): Promise<void> {
const url = `${FIREFLY_ROOT_URL}/api/firefly/desktop-sync/upload`
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({
session,
cryptoKey,
encryptedPayload,
}),
})
if (!response.ok) throw new Error(`Failed to sync twitter cookies: ${response.statusText}`)
const json = await response.json()
if (!json.success) throw new Error(json.message)
}
15 changes: 15 additions & 0 deletions packages/mask/background/services/helper/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,18 @@ export { fetchSandboxedPluginManifest } from './sandboxed.js'
export { getActiveTab } from './tabs.js'
export { requestXOAuthToken, resolveXOAuth, resetXOAuth } from './oauth-x.js'
export { loginFireflyViaTwitter } from './firefly.js'
export {
encrypt,
getDesktopSyncLinkInfo,
getSyncChannelStatus,
confirmSyncChannel,
syncTwitterCookies,
getTwitterOAuthData,
type DesktopLinkInfoResponse,
type SyncChannelStatusResponse,
type TwitterCookiesPayload,
type SocialAccountTwitter,
type TwitterOAuthData,
type ConfirmSyncChannelOperation,
} from './firefly.js'
export { getXOAuthToken } from './oauth-x.js'
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
browser.permissions.remove({ permissions: ['cookies'] }).catch(() => {})
1 change: 1 addition & 0 deletions packages/mask/background/tasks/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ import './setup.hmr.js'
// NotCancellable tasks here
import './NotCancellable/PrintBuildFlags.js'
import './NotCancellable/PendingTasks.js'
import './NotCancellable/DropPermission.js'
1 change: 1 addition & 0 deletions packages/mask/popups/Popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ const router = createHashRouter([
],
},
{ path: PopupRoutes.RequestPermission, lazy: () => import('./pages/RequestPermission/index.js') },
{ path: PopupRoutes.GetTwitterTokenByQR, lazy: () => import('./pages/GetTwitterTokenByQR/index.js') },
{ path: '*', element: <Navigate replace to={PopupRoutes.Personas} /> },
],
},
Expand Down
5 changes: 3 additions & 2 deletions packages/mask/popups/components/WalletAvatar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ const useStyles = makeStyles()((theme) => ({

interface Props extends HTMLProps<HTMLDivElement> {
size?: number
badgeSize?: number
address: string
}
export const WalletAvatar = memo<Props>(function WalletAvatar({ size = 30, address, ...rest }) {
export const WalletAvatar = memo<Props>(function WalletAvatar({ size = 30, address, badgeSize = 12, ...rest }) {
const { classes, cx } = useStyles()
const { wallets: fireflyWallets } = useWallets()

Expand All @@ -38,7 +39,7 @@ export const WalletAvatar = memo<Props>(function WalletAvatar({ size = 30, addre
return (
<div {...rest} className={cx(classes.container, rest.className)}>
<Image size={size} src={fireflyAccount.avatar} rounded />
<Icons.Firefly className={classes.badgeIcon} size={12} />
<Icons.Firefly className={classes.badgeIcon} size={badgeSize} />
</div>
)
return <Icons.MaskBlue size={size} className={rest.className} />
Expand Down
Loading