Skip to content

Commit 98985e3

Browse files
committed
feat(mask): fw-6740 centralize extension permissions & add request UI
move permission constants to shared defs, use them in oauth helper and firefly wallet flow, and add a runtime permission dialog to request origins.
1 parent 3a0154d commit 98985e3

File tree

19 files changed

+238
-38
lines changed

19 files changed

+238
-38
lines changed

packages/mask/background/services/helper/oauth-x.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { timeout } from '@masknet/kit'
22
import { requestExtensionPermissionFromContentScript } from './request-permission.js'
3+
import { XOAuthRequestOrigins } from '../../../shared/definitions/extension.js'
34

45
/** Modified from https://github.com/ddo/oauth-1.0a/blob/master/oauth-1.0a.js */
56
class OAuth {
@@ -184,15 +185,7 @@ const client = new OAuth(process.env.FIREFLY_X_CLIENT_ID, process.env.FIREFLY_X_
184185
let pendingOAuth: PromiseWithResolvers<{ oauth_verifier: string; oauth_token: string }> | undefined
185186
export async function requestXOAuthToken() {
186187
await requestExtensionPermissionFromContentScript({
187-
origins: [
188-
// In order to send API request without CORS limit
189-
'https://api.twitter.com/*',
190-
// In order to run content script on it
191-
'https://firefly.social/api/mask/delegate-x-token',
192-
'https://firefly.social/api/auth/callback/twitter',
193-
'https://canary.firefly.social/api/mask/delegate-x-token',
194-
'https://canary.firefly.social/api/auth/callback/twitter',
195-
],
188+
origins: XOAuthRequestOrigins,
196189
})
197190
await Promise.all([
198191
fetch('https://firefly.social/api/mask/delegate-x-token'),

packages/mask/dashboard/pages/CreateMaskWallet/FireflyWallet/index.tsx

Lines changed: 103 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import Services from '#services'
22
import { Trans } from '@lingui/react/macro'
33
import { Icons } from '@masknet/icons'
4+
import { PopupRoutes } from '@masknet/shared-base'
45
import { makeStyles } from '@masknet/theme'
5-
import { Typography } from '@mui/material'
6-
import { memo } from 'react'
6+
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Typography } from '@mui/material'
7+
import { memo, useState } from 'react'
8+
import { useAsyncFn, useAsyncRetry } from 'react-use'
79
import { PrimaryButton } from '../../../components/PrimaryButton/index.js'
810
import { SetupFrameController } from '../../../components/SetupFrame/index.js'
9-
import { useAsyncFn } from 'react-use'
10-
import { PopupRoutes } from '@masknet/shared-base'
11+
import { XOAuthRequestOrigins } from '../../../../shared/definitions/extension.js'
1112

1213
const useStyles = makeStyles()((theme) => ({
1314
container: {
@@ -63,20 +64,79 @@ const useStyles = makeStyles()((theme) => ({
6364
listStyle: 'disc',
6465
},
6566
},
67+
dialog: {
68+
width: 600,
69+
boxSizing: 'border-box',
70+
padding: 24,
71+
borderRadius: 12,
72+
display: 'flex',
73+
flexDirection: 'column',
74+
gap: 24,
75+
},
76+
dialogTitle: {
77+
color: theme.palette.maskColor.main,
78+
fontSize: 18,
79+
fontWeight: 700,
80+
lineHeight: '22px',
81+
padding: 0,
82+
},
83+
dialogContent: {
84+
display: 'flex',
85+
flexDirection: 'column',
86+
gap: 12,
87+
padding: 0,
88+
},
89+
permissions: {
90+
backgroundColor: theme.palette.maskColor.bg,
91+
padding: 12,
92+
borderRadius: 8,
93+
height: 212,
94+
boxSizing: 'border-box',
95+
minHeight: 0,
96+
flexGrow: 1,
97+
overflow: 'auto',
98+
},
99+
dialogActions: {
100+
display: 'flex',
101+
justifyContent: 'center',
102+
gap: 12,
103+
padding: 0,
104+
},
105+
actionButton: {
106+
minWidth: 110,
107+
'&&': {
108+
marginLeft: 0,
109+
},
110+
},
66111
}))
67112

68113
export const Component = memo(function CreateWalletForm() {
69114
const { classes, cx } = useStyles()
115+
const [open, setOpen] = useState(false)
116+
117+
const { retry, value: hasPermission } = useAsyncRetry(() => {
118+
const hasPermission = browser.permissions.contains({
119+
origins: XOAuthRequestOrigins,
120+
})
121+
setOpen(!hasPermission)
122+
return hasPermission
123+
}, [])
124+
70125
const [{ loading }, request] = useAsyncFn(async () => {
126+
if (!hasPermission) {
127+
setOpen(true)
128+
return
129+
}
71130
try {
72131
const data = await Services.Helper.loginFireflyViaTwitter()
73-
console.log('login data', data)
74132
if (!data) return
75-
await Services.Helper.openPopupWindow(PopupRoutes.CreateWallet, undefined)
133+
await Services.Helper.openPopupWindow(PopupRoutes.CreateWallet, {
134+
creatingFireflyWallet: true,
135+
})
76136
} catch (err) {
77-
console.log('login error', err)
137+
console.error('Failed to login firefly', err)
78138
}
79-
}, [])
139+
}, [hasPermission])
80140

81141
return (
82142
<div className={classes.container}>
@@ -121,6 +181,41 @@ export const Component = memo(function CreateWalletForm() {
121181
<Trans>Continue</Trans>
122182
</PrimaryButton>
123183
</SetupFrameController>
184+
<Dialog
185+
open={open}
186+
PaperProps={{
187+
elevation: 0,
188+
className: classes.dialog,
189+
}}>
190+
<DialogTitle className={classes.dialogTitle}>
191+
<Trans>Mask needs the following permissions</Trans>
192+
</DialogTitle>
193+
<DialogContent className={classes.dialogContent}>
194+
<Typography>
195+
<Trans>Sites</Trans>
196+
</Typography>
197+
<div className={classes.permissions} data-hide-scrollbar>
198+
{XOAuthRequestOrigins.map((origin) => (
199+
<Typography key={origin} lineHeight="18px">
200+
{origin}
201+
</Typography>
202+
))}
203+
</div>
204+
</DialogContent>
205+
<DialogActions className={classes.dialogActions}>
206+
<Button onClick={() => setOpen(false)} variant="outlined" className={classes.actionButton}>
207+
<Trans>Cancel</Trans>
208+
</Button>
209+
<Button
210+
onClick={() => {
211+
browser.permissions.request({ origins: XOAuthRequestOrigins }).finally(retry)
212+
}}
213+
variant="contained"
214+
className={classes.actionButton}>
215+
<Trans>Approve</Trans>
216+
</Button>
217+
</DialogActions>
218+
</Dialog>
124219
</div>
125220
)
126221
})

packages/mask/dashboard/pages/routes.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ const routes: RouteObject[] = [
1010
{ path: DashboardRoutes.Setup, element: <PersonaFrame />, children: personaRoutes },
1111
{ path: DashboardRoutes.SignUp, element: <SignUpFrame />, children: signUpRoutes },
1212
{ path: DashboardRoutes.CreateMaskWallet, element: <WalletFrame />, children: walletRoutes },
13-
{ path: DashboardRoutes.CreateFireflyWallet, element: <WalletFrame />, children: walletRoutes },
1413
]
1514
const rootElement = (
1615
<>

packages/mask/popups/pages/RequestPermission/index.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,10 @@ import { Box } from '@mui/material'
22
import { useEffect } from 'react'
33
import { useLocation } from 'react-router-dom'
44
import { useAsyncRetry } from 'react-use'
5-
import { RequestPermission } from './RequestPermission.js'
65
import type { Manifest } from 'webextension-polyfill'
6+
import { CanRequestDynamically } from '../../../shared/definitions/extension.js'
7+
import { RequestPermission } from './RequestPermission.js'
78

8-
const CanRequestDynamically: readonly Manifest.OptionalPermission[] = [
9-
'clipboardRead',
10-
'clipboardWrite',
11-
'notifications',
12-
'webRequestBlocking',
13-
]
149
function canRequestDynamically(x: string): x is Manifest.OptionalPermission {
1510
return (CanRequestDynamically as string[]).includes(x)
1611
}

packages/mask/popups/pages/Wallet/WalletGuard/index.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { memo } from 'react'
2-
import { Navigate, Outlet, useLocation, useSearchParams } from 'react-router-dom'
2+
import { Navigate, Outlet, useLocation, useMatch, useSearchParams } from 'react-router-dom'
33
import { PopupRoutes } from '@masknet/shared-base'
44
import { ChainContextProvider, useWallets } from '@masknet/web3-hooks-base'
55
import Unlock from '../Unlock/index.js'
@@ -19,8 +19,9 @@ export const WalletGuard = memo(function WalletGuard() {
1919

2020
const hitPaymentPasswordGuard = usePaymentPasswordGuard()
2121
const hitMessageGuard = useMessageGuard()
22+
const matchCreateWallet = useMatch(PopupRoutes.CreateWallet)
2223

23-
if (!wallets.length) {
24+
if (!wallets.length || matchCreateWallet) {
2425
return <WalletStartUp />
2526
}
2627

packages/mask/popups/pages/Wallet/components/ImportCreateWallet/index.tsx

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
import Services from '#services'
22
import { Trans } from '@lingui/react/macro'
33
import { Icons } from '@masknet/icons'
4+
import { useWallets } from '@privy-io/react-auth'
45
import { timeout } from '@masknet/kit'
56
import { LoadingStatus } from '@masknet/shared'
6-
import { DashboardRoutes } from '@masknet/shared-base'
7+
import { DashboardRoutes, type NetworkPluginID, PopupRoutes } from '@masknet/shared-base'
78
import { ActionButton, makeStyles } from '@masknet/theme'
89
import { alpha, Box, Typography, type BoxProps } from '@mui/material'
9-
import { memo, useEffect } from 'react'
10-
import { useSearchParams } from 'react-router-dom'
11-
import { useAsyncFn } from 'react-use'
10+
import { memo, useCallback } from 'react'
11+
import { useNavigate, useSearchParams } from 'react-router-dom'
12+
import { useAsync, useAsyncFn } from 'react-use'
1213
import urlcat from 'urlcat'
14+
import { useChainContext } from '@masknet/web3-hooks-base'
15+
import { EVMWeb3 } from '@masknet/web3-providers'
16+
import { ProviderType } from '@masknet/web3-shared-evm'
1317

1418
const useStyles = makeStyles()((theme) => {
1519
return {
@@ -76,7 +80,9 @@ async function loginFirefly() {
7680
}
7781
export const ImportCreateWallet = memo<Props>(function ImportCreateWallet({ onChoose, ...props }) {
7882
const { classes, cx, theme } = useStyles()
79-
const [params, setParams] = useSearchParams()
83+
const [params] = useSearchParams()
84+
const { chainId } = useChainContext<NetworkPluginID.PLUGIN_EVM>()
85+
const navigate = useNavigate()
8086
const [, handleChoose] = useAsyncFn(
8187
async (route: DashboardRoutes) => {
8288
const hasPassword = await Services.Wallet.hasPassword()
@@ -97,12 +103,28 @@ export const ImportCreateWallet = memo<Props>(function ImportCreateWallet({ onCh
97103
return timeout(loginFirefly(), 3 * 60 * 1000, timeoutMessage)
98104
}, [])
99105

100-
const isCreatingFireflyWallet = !!params.get('isCreating')
101-
useEffect(() => {
106+
const isCreatingFireflyWallet = !!params.get('creatingFireflyWallet')
107+
const { wallets } = useWallets()
108+
const selectPrivyWallet = useCallback(async () => {
109+
if (!wallets.length) return
110+
if (wallets.length > 1) {
111+
navigate(PopupRoutes.SelectWallet)
112+
return
113+
}
114+
await EVMWeb3.connect({
115+
account: wallets[0].address,
116+
chainId,
117+
providerType: ProviderType.MaskWallet,
118+
})
119+
navigate(PopupRoutes.Wallet)
120+
}, [wallets, chainId])
121+
122+
useAsync(async () => {
102123
if (!isCreatingFireflyWallet) return
103-
createPrivyWallet()
104-
setParams({}, { replace: true })
105-
}, [isCreatingFireflyWallet])
124+
await createPrivyWallet()
125+
await selectPrivyWallet()
126+
}, [isCreatingFireflyWallet, selectPrivyWallet])
127+
106128
const oauthTimeout = error?.message === timeoutMessage
107129

108130
return (

packages/mask/popups/pages/Wallet/components/StartUp/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ const useStyles = makeStyles()((theme) => ({
4747
export const WalletStartUp = memo(function WalletStartUp() {
4848
const { classes } = useStyles()
4949
const matchResetWallet = useMatch(PopupRoutes.ResetWallet)
50+
const matchCreateWallet = useMatch(PopupRoutes.CreateWallet)
5051

5152
const [, onEnterCreateWallet] = useAsyncFn(async () => {
5253
if (Sniffings.is_firefox) {
@@ -58,7 +59,7 @@ export const WalletStartUp = memo(function WalletStartUp() {
5859

5960
return (
6061
<Box className={classes.container} data-hide-scrollbar>
61-
<WalletSetupHeaderUI showBack={!!matchResetWallet}>
62+
<WalletSetupHeaderUI showBack={!!matchResetWallet || !!matchCreateWallet}>
6263
<Box className={classes.titleWrapper}>
6364
<Typography className={classes.title}>
6465
<Trans>Add Wallet</Trans>

packages/mask/shared-ui/locale/en-US.json

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/mask/shared-ui/locale/en-US.po

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/mask/shared-ui/locale/ja-JP.json

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)