diff --git a/packages/neuron-ui/src/components/CameraScanDialog/index.tsx b/packages/neuron-ui/src/components/CameraScanDialog/index.tsx index 810dbc2ee7..a846db44eb 100644 --- a/packages/neuron-ui/src/components/CameraScanDialog/index.tsx +++ b/packages/neuron-ui/src/components/CameraScanDialog/index.tsx @@ -1,6 +1,9 @@ import React, { useEffect, useRef, useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' +import { askForCameraAccess } from 'services/remote' import Dialog from 'widgets/Dialog' +import AlertDialog from 'widgets/AlertDialog' +import { isSuccessResponse } from 'utils' import jsQR from 'jsqr' import styles from './cameraScanDialog.module.scss' @@ -17,7 +20,7 @@ const CameraScanDialog = ({ close, onConfirm }: { close: () => void; onConfirm: const videoRef = useRef() const canvasRef = useRef(null) const canvas2dRef = useRef() - const [loading, setLoading] = useState(true) + const [dialogType, setDialogType] = useState<'' | 'no-camera' | 'access-fail' | 'scan'>('') const drawLine = (begin: Point, end: Point) => { if (!canvas2dRef.current) return @@ -31,7 +34,7 @@ const CameraScanDialog = ({ close, onConfirm }: { close: () => void; onConfirm: const scan = useCallback(() => { if (videoRef.current?.readyState === HTMLMediaElement.HAVE_ENOUGH_DATA) { - setLoading(false) + setDialogType('scan') const canvas2d = canvasRef.current?.getContext('2d') if (canvas2d) { canvas2d.drawImage(videoRef.current, 0, 0, IMAGE_SIZE, IMAGE_SIZE) @@ -50,24 +53,31 @@ const CameraScanDialog = ({ close, onConfirm }: { close: () => void; onConfirm: } } requestAnimationFrame(scan) - }, []) + }, [setDialogType]) useEffect(() => { let mediaStream: MediaStream - navigator.mediaDevices - .getUserMedia({ - audio: false, - video: { width: IMAGE_SIZE, height: IMAGE_SIZE }, - }) - .then(res => { - if (res) { - videoRef.current = document.createElement('video') - videoRef.current.srcObject = res - videoRef.current.play() - mediaStream = res - requestAnimationFrame(scan) - } - }) + + askForCameraAccess().then(sccessRes => { + if (isSuccessResponse(sccessRes)) { + navigator.mediaDevices + .getUserMedia({ + audio: false, + video: { width: IMAGE_SIZE, height: IMAGE_SIZE }, + }) + .then(res => { + if (res) { + videoRef.current = document.createElement('video') + videoRef.current.srcObject = res + videoRef.current.play() + mediaStream = res + requestAnimationFrame(scan) + } + }) + } else { + setDialogType('access-fail') + } + }) return () => { if (mediaStream) { @@ -79,24 +89,32 @@ const CameraScanDialog = ({ close, onConfirm }: { close: () => void; onConfirm: }, []) return ( - -
-
- {loading ? ( -
{t('wallet-connect.waiting-camera')}
- ) : ( + <> + +
+
- )} +
-
-
+ + + { + close() + }} + /> + ) } diff --git a/packages/neuron-ui/src/components/ScreenScanDialog/index.tsx b/packages/neuron-ui/src/components/ScreenScanDialog/index.tsx index 3ab55c5b79..81193904eb 100644 --- a/packages/neuron-ui/src/components/ScreenScanDialog/index.tsx +++ b/packages/neuron-ui/src/components/ScreenScanDialog/index.tsx @@ -4,6 +4,7 @@ import Button from 'widgets/Button' import { isSuccessResponse } from 'utils' import { useTranslation } from 'react-i18next' import Dialog from 'widgets/Dialog' +import AlertDialog from 'widgets/AlertDialog' import jsQR from 'jsqr' import styles from './screenScanDialog.module.scss' @@ -20,6 +21,7 @@ const ScreenScanDialog = ({ close, onConfirm }: { close: () => void; onConfirm: const canvas2dRef = useRef() const [sources, setSources] = useState([]) const [selectId, setSelectId] = useState('') + const [dialogType, setDialogType] = useState<'' | 'access-fail' | 'scan'>('') const [uri, setUri] = useState('') const drawLine = (begin: Point, end: Point) => { @@ -65,11 +67,14 @@ const ScreenScanDialog = ({ close, onConfirm }: { close: () => void; onConfirm: useEffect(() => { captureScreen().then(res => { if (isSuccessResponse(res)) { + setDialogType('scan') const result = res.result as Controller.CaptureScreenSource[] setSources(result) if (result.length) { setSelectId(result[0].id) } + } else { + setDialogType('access-fail') } }) }, []) @@ -87,40 +92,48 @@ const ScreenScanDialog = ({ close, onConfirm }: { close: () => void; onConfirm: } return ( - -
-
- {sources.map(({ dataUrl, id }) => ( - - ))} + <> + +
+
+ {sources.map(({ dataUrl, id }) => ( + + ))} +
+
+ + {source ? : null} +
-
- - {source ? : null} -
-
-
+ + + { + close() + }} + /> + ) } ScreenScanDialog.displayName = 'ScreenScanDialog' export default ScreenScanDialog - -// {loading ? ( -//
{t('wallet-connect.waiting-camera')}
-// ) diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json index c58c70491f..ad2303558b 100644 --- a/packages/neuron-ui/src/locales/en.json +++ b/packages/neuron-ui/src/locales/en.json @@ -1145,6 +1145,10 @@ "scan-with-camera": "Scanning with the camera", "scan-qrcode": "Scan the QR code in the screen", "no-camera-tip": "Don't have a camera? Use the uri to connect", + "camera-fail": "Unable to acquire camera data", + "camera-msg": "Please check that the settings allow Neuron to access the camera", + "screen-fail": "Unable to get screen data", + "screen-msg": "Please check that the settings allow Neuron to access the screen", "session-request": "Connection Requests", "no-session": "No connection requests", "reject": "Reject", diff --git a/packages/neuron-ui/src/locales/zh-tw.json b/packages/neuron-ui/src/locales/zh-tw.json index 08f43b4da0..a0732edbb3 100644 --- a/packages/neuron-ui/src/locales/zh-tw.json +++ b/packages/neuron-ui/src/locales/zh-tw.json @@ -1116,6 +1116,10 @@ "scan-with-camera": "使用攝像頭掃描", "scan-qrcode": "掃描屏幕二維碼", "no-camera-tip": "沒有攝像頭?使用連結碼連結", + "camera-fail": "無法獲取攝像頭數據", + "camera-msg": "請檢查設置是否允許Neuron訪問攝像頭", + "screen-fail": "無法獲取屏幕數據", + "screen-msg": "請檢查設置是否允許Neuron訪問屏幕", "session-request": "連結請求", "no-session": "沒有連結請求", "reject": "拒絕", diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json index 927fdabcd5..aa6142bb97 100644 --- a/packages/neuron-ui/src/locales/zh.json +++ b/packages/neuron-ui/src/locales/zh.json @@ -1136,8 +1136,11 @@ "add-title": "新增WalletConnect连接", "scan-with-camera": "使用摄像头扫描", "scan-qrcode": "扫描屏幕二维码", - "waiting-camera": "等待摄像头响应...", "no-camera-tip": "没有摄像头?使用连接码连接", + "camera-fail": "无法获取摄像头数据", + "camera-msg": "请检查设置是否允许Neuron访问摄像头", + "screen-fail": "无法获取屏幕数据", + "screen-msg": "请检查设置是否允许Neuron访问屏幕", "session-request": "连接请求", "no-session": "没有连接请求", "reject": "拒绝", diff --git a/packages/neuron-ui/src/services/remote/hardware.ts b/packages/neuron-ui/src/services/remote/hardware.ts index f614eedae9..db8f0c41e2 100644 --- a/packages/neuron-ui/src/services/remote/hardware.ts +++ b/packages/neuron-ui/src/services/remote/hardware.ts @@ -39,4 +39,5 @@ export const connectDevice = remoteApi('connect-device') export const createHardwareWallet = remoteApi( 'create-hardware-wallet' ) +export const askForCameraAccess = remoteApi('ask-camera-access') export const captureScreen = remoteApi('capture-screen') diff --git a/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts b/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts index 1f0ca84a79..725ca7bfe8 100644 --- a/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts +++ b/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts @@ -158,6 +158,7 @@ type Action = | 'wc-reject-session' | 'wc-approve-request' | 'wc-reject-request' + | 'ask-camera-access' | 'capture-screen' export const remoteApi = diff --git a/packages/neuron-wallet/src/controllers/api.ts b/packages/neuron-wallet/src/controllers/api.ts index 733c9571bf..e2a26df689 100644 --- a/packages/neuron-wallet/src/controllers/api.ts +++ b/packages/neuron-wallet/src/controllers/api.ts @@ -733,6 +733,10 @@ export default class ApiController { return this.#hardwareController.getPublicKey() }) + handle('ask-camera-access', async () => { + return this.#hardwareController.askForCameraAccess() + }) + handle('capture-screen', async () => { return this.#hardwareController.captureScreen() }) diff --git a/packages/neuron-wallet/src/controllers/hardware.ts b/packages/neuron-wallet/src/controllers/hardware.ts index e209652511..7a743594bf 100644 --- a/packages/neuron-wallet/src/controllers/hardware.ts +++ b/packages/neuron-wallet/src/controllers/hardware.ts @@ -1,9 +1,9 @@ -import { desktopCapturer, screen, BrowserWindow } from 'electron' +import { desktopCapturer, screen, BrowserWindow, systemPreferences } from 'electron' import logger from '../utils/logger' import { DeviceInfo, ExtendedPublicKey, PublicKey } from '../services/hardware/common' import { ResponseCode } from '../utils/const' import HardwareWalletService from '../services/hardware' -import { connectDeviceFailed } from '../exceptions' +import { connectDeviceFailed, AskAccessFailed } from '../exceptions' import { AccountExtendedPublicKey } from '../models/keys/key' export default class HardwareController { @@ -71,8 +71,30 @@ export default class HardwareController { } } + public async askForCameraAccess() { + const status = await systemPreferences.getMediaAccessStatus('camera') + if (status === 'granted') { + return { + status: ResponseCode.Success, + } + } + + const canAccess = await systemPreferences.askForMediaAccess('camera') + if (canAccess) { + return { + status: ResponseCode.Success, + } + } + + throw new AskAccessFailed() + } + public async captureScreen() { - // TODO: 权限提示 + const status = await systemPreferences.getMediaAccessStatus('screen') + if (status === 'denied') { + throw new AskAccessFailed() + } + const currentWindow = BrowserWindow.getFocusedWindow() currentWindow?.hide() const display = screen.getPrimaryDisplay() diff --git a/packages/neuron-wallet/src/exceptions/hardware.ts b/packages/neuron-wallet/src/exceptions/hardware.ts index eeb3b88c28..18a5ca7525 100644 --- a/packages/neuron-wallet/src/exceptions/hardware.ts +++ b/packages/neuron-wallet/src/exceptions/hardware.ts @@ -19,3 +19,7 @@ export class UnsupportedManufacturer extends Error { super(t('messages.unsupported-manufacturer', { manufacturer })) } } + +export class AskAccessFailed extends Error { + public code = 408 +}