* Add URL secret reading logic * Add private url share dialog * Salt password hash with roomId * Don't allow incorrect password to be entered Co-authored-by: Jeremy Kahn <jeremyckahn@gmail.com>
This commit is contained in:
parent
d5aa4d7f0b
commit
fcec242194
@ -40,7 +40,7 @@ export function useRoom(
|
|||||||
{ password, ...roomConfig }: BaseRoomConfig & TorrentRoomConfig,
|
{ password, ...roomConfig }: BaseRoomConfig & TorrentRoomConfig,
|
||||||
{ roomId, userId, getUuid = uuid }: UseRoomConfig
|
{ roomId, userId, getUuid = uuid }: UseRoomConfig
|
||||||
) {
|
) {
|
||||||
const isPublicRoom = !password
|
const isPrivate = password !== undefined
|
||||||
|
|
||||||
const [peerRoom] = useState(
|
const [peerRoom] = useState(
|
||||||
() => new PeerRoom({ password: password ?? roomId, ...roomConfig }, roomId)
|
() => new PeerRoom({ password: password ?? roomId, ...roomConfig }, roomId)
|
||||||
@ -54,8 +54,11 @@ export function useRoom(
|
|||||||
setPeerList,
|
setPeerList,
|
||||||
tabHasFocus,
|
tabHasFocus,
|
||||||
showAlert,
|
showAlert,
|
||||||
|
setRoomId,
|
||||||
|
setPassword,
|
||||||
setIsPeerListOpen,
|
setIsPeerListOpen,
|
||||||
} = useContext(ShellContext)
|
} = useContext(ShellContext)
|
||||||
|
|
||||||
const settingsContext = useContext(SettingsContext)
|
const settingsContext = useContext(SettingsContext)
|
||||||
const [isMessageSending, setIsMessageSending] = useState(false)
|
const [isMessageSending, setIsMessageSending] = useState(false)
|
||||||
const [messageLog, _setMessageLog] = useState<Array<Message | InlineMedia>>(
|
const [messageLog, _setMessageLog] = useState<Array<Message | InlineMedia>>(
|
||||||
@ -105,6 +108,7 @@ export function useRoom(
|
|||||||
|
|
||||||
const roomContextValue = useMemo(
|
const roomContextValue = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
|
isPrivate,
|
||||||
isMessageSending,
|
isMessageSending,
|
||||||
selfVideoStream,
|
selfVideoStream,
|
||||||
setSelfVideoStream,
|
setSelfVideoStream,
|
||||||
@ -118,6 +122,7 @@ export function useRoom(
|
|||||||
setPeerOfferedFileMetadata,
|
setPeerOfferedFileMetadata,
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
|
isPrivate,
|
||||||
isMessageSending,
|
isMessageSending,
|
||||||
selfVideoStream,
|
selfVideoStream,
|
||||||
setSelfVideoStream,
|
setSelfVideoStream,
|
||||||
@ -139,6 +144,22 @@ export function useRoom(
|
|||||||
}
|
}
|
||||||
}, [peerRoom, setIsPeerListOpen])
|
}, [peerRoom, setIsPeerListOpen])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPassword(password)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
setPassword(undefined)
|
||||||
|
}
|
||||||
|
}, [password, setPassword])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setRoomId(roomId)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
setRoomId(undefined)
|
||||||
|
}
|
||||||
|
}, [roomId, setRoomId])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setDoShowPeers(true)
|
setDoShowPeers(true)
|
||||||
|
|
||||||
@ -239,7 +260,7 @@ export function useRoom(
|
|||||||
try {
|
try {
|
||||||
const promises: Promise<any>[] = [sendPeerId(userId, peerId)]
|
const promises: Promise<any>[] = [sendPeerId(userId, peerId)]
|
||||||
|
|
||||||
if (isPublicRoom) {
|
if (!isPrivate) {
|
||||||
promises.push(
|
promises.push(
|
||||||
sendMessageTranscript(messageLog.filter(isMessageReceived), peerId)
|
sendMessageTranscript(messageLog.filter(isMessageReceived), peerId)
|
||||||
)
|
)
|
||||||
@ -322,6 +343,7 @@ export function useRoom(
|
|||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
isPrivate,
|
||||||
handleInlineMediaUpload,
|
handleInlineMediaUpload,
|
||||||
isMessageSending,
|
isMessageSending,
|
||||||
messageLog,
|
messageLog,
|
||||||
|
190
src/components/Shell/RoomShareDialog.tsx
Normal file
190
src/components/Shell/RoomShareDialog.tsx
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
import {
|
||||||
|
Alert,
|
||||||
|
AlertColor,
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogContentText,
|
||||||
|
DialogTitle,
|
||||||
|
IconButton,
|
||||||
|
FormControlLabel,
|
||||||
|
TextField,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mui/material'
|
||||||
|
import CloseIcon from '@mui/icons-material/Close'
|
||||||
|
|
||||||
|
import { AlertOptions } from 'models/shell'
|
||||||
|
import { useEffect, useState, SyntheticEvent } from 'react'
|
||||||
|
import { encodePassword, sleep } from 'utils'
|
||||||
|
|
||||||
|
export interface RoomShareDialogProps {
|
||||||
|
isOpen: boolean
|
||||||
|
handleClose: () => void
|
||||||
|
roomId: string
|
||||||
|
password: string
|
||||||
|
showAlert: (message: string, options?: AlertOptions) => void
|
||||||
|
copyToClipboard: (
|
||||||
|
content: string,
|
||||||
|
alert: string,
|
||||||
|
severity: AlertColor
|
||||||
|
) => Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RoomShareDialog(props: RoomShareDialogProps) {
|
||||||
|
const [isAdvanced, setIsAdvanced] = useState(false)
|
||||||
|
const [isUnderstood, setIsUnderstood] = useState(false)
|
||||||
|
const [password, setPassword] = useState('')
|
||||||
|
const [passThrottled, setPassThrottled] = useState(false)
|
||||||
|
const handleClose = () => {
|
||||||
|
props.handleClose()
|
||||||
|
setPassword('')
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isAdvanced) setIsUnderstood(false)
|
||||||
|
}, [isAdvanced])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isUnderstood) setPassword('')
|
||||||
|
}, [isUnderstood])
|
||||||
|
|
||||||
|
const url = window.location.href.split('#')[0]
|
||||||
|
|
||||||
|
const copyWithPass = async () => {
|
||||||
|
const encoded = await encodePassword(props.roomId, password)
|
||||||
|
if (encoded === props.password) {
|
||||||
|
const params = new URLSearchParams()
|
||||||
|
params.set('secret', props.password)
|
||||||
|
await props.copyToClipboard(
|
||||||
|
`${url}#${params}`,
|
||||||
|
'Private room URL with password copied to clipboard',
|
||||||
|
'warning'
|
||||||
|
)
|
||||||
|
handleClose()
|
||||||
|
} else {
|
||||||
|
setPassThrottled(true)
|
||||||
|
props.showAlert('Incorrect password entered. Please wait 2s to retry.', {
|
||||||
|
severity: 'error',
|
||||||
|
})
|
||||||
|
await sleep(2000)
|
||||||
|
setPassThrottled(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const copyWithoutPass = async () => {
|
||||||
|
await props.copyToClipboard(
|
||||||
|
url,
|
||||||
|
isAdvanced
|
||||||
|
? 'Private room URL without password copied to clipboard'
|
||||||
|
: 'Current URL copied to clipboard',
|
||||||
|
'success'
|
||||||
|
)
|
||||||
|
handleClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFormSubmit = (event: SyntheticEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault()
|
||||||
|
if (!passThrottled) copyWithPass()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
open={props.isOpen}
|
||||||
|
onClose={handleClose}
|
||||||
|
aria-labelledby="alert-dialog-title"
|
||||||
|
aria-describedby="alert-dialog-description"
|
||||||
|
>
|
||||||
|
<form onSubmit={handleFormSubmit}>
|
||||||
|
{isAdvanced && (
|
||||||
|
<DialogTitle id="alert-dialog-title">
|
||||||
|
Copy URL with password
|
||||||
|
<Button onClick={() => setIsAdvanced(false)}>Simple</Button>
|
||||||
|
<IconButton
|
||||||
|
aria-label="close"
|
||||||
|
onClick={handleClose}
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
right: 8,
|
||||||
|
top: 8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
</DialogTitle>
|
||||||
|
)}
|
||||||
|
{isAdvanced && (
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText sx={{ mb: 2 }}>
|
||||||
|
Copy URL to this private room containing an indecipherable hash of
|
||||||
|
the password. When using this URL, users will not need to enter
|
||||||
|
the password themselves.
|
||||||
|
</DialogContentText>
|
||||||
|
<Alert severity="error" sx={{ mb: 2 }}>
|
||||||
|
Be careful where and how this URL is shared. Anybody who obtains
|
||||||
|
it can enter the room. The sharing medium must be trusted, as well
|
||||||
|
as all potential recipients of the URL, just as if you were
|
||||||
|
sharing the password itself.
|
||||||
|
</Alert>
|
||||||
|
<Alert severity="warning">
|
||||||
|
By design, the password hash does not leave the web browser when
|
||||||
|
this URL is used to access the room. However, web browsers can
|
||||||
|
still independently record the full URL in the address history,
|
||||||
|
and may even store the history in the cloud if configured to do
|
||||||
|
so.
|
||||||
|
</Alert>
|
||||||
|
<FormControlLabel
|
||||||
|
label="I understand the risks"
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={isUnderstood}
|
||||||
|
onChange={e => setIsUnderstood(e.target.checked)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
autoFocus
|
||||||
|
margin="none"
|
||||||
|
id="password"
|
||||||
|
label="Password"
|
||||||
|
type="password"
|
||||||
|
fullWidth
|
||||||
|
variant="standard"
|
||||||
|
value={password}
|
||||||
|
disabled={!isUnderstood}
|
||||||
|
onChange={e => setPassword(e.target.value)}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
)}
|
||||||
|
<DialogActions>
|
||||||
|
{isAdvanced ? (
|
||||||
|
<Tooltip title="Copy room URL with password. No password entry required to access room.">
|
||||||
|
<span>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
onClick={copyWithPass}
|
||||||
|
color="error"
|
||||||
|
disabled={
|
||||||
|
password.length === 0 || !isUnderstood || passThrottled
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Copy URL with password
|
||||||
|
</Button>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<Button onClick={() => setIsAdvanced(true)} color="error">
|
||||||
|
Advanced
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Tooltip title="Copy room URL. Password required to access room.">
|
||||||
|
<Button onClick={copyWithoutPass} color="success" autoFocus>
|
||||||
|
Copy URL
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</DialogActions>
|
||||||
|
</form>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
@ -25,6 +25,7 @@ import { NotificationArea } from './NotificationArea'
|
|||||||
import { RouteContent } from './RouteContent'
|
import { RouteContent } from './RouteContent'
|
||||||
import { PeerList } from './PeerList'
|
import { PeerList } from './PeerList'
|
||||||
import { QRCodeDialog } from './QRCodeDialog'
|
import { QRCodeDialog } from './QRCodeDialog'
|
||||||
|
import { RoomShareDialog } from './RoomShareDialog'
|
||||||
|
|
||||||
export interface ShellProps extends PropsWithChildren {
|
export interface ShellProps extends PropsWithChildren {
|
||||||
userPeerId: string
|
userPeerId: string
|
||||||
@ -36,11 +37,14 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
|
|||||||
const [isAlertShowing, setIsAlertShowing] = useState(false)
|
const [isAlertShowing, setIsAlertShowing] = useState(false)
|
||||||
const [isDrawerOpen, setIsDrawerOpen] = useState(false)
|
const [isDrawerOpen, setIsDrawerOpen] = useState(false)
|
||||||
const [isQRCodeDialogOpen, setIsQRCodeDialogOpen] = useState(false)
|
const [isQRCodeDialogOpen, setIsQRCodeDialogOpen] = useState(false)
|
||||||
|
const [isRoomShareDialogOpen, setIsRoomShareDialogOpen] = useState(false)
|
||||||
const [doShowPeers, setDoShowPeers] = useState(false)
|
const [doShowPeers, setDoShowPeers] = useState(false)
|
||||||
const [alertSeverity, setAlertSeverity] = useState<AlertColor>('info')
|
const [alertSeverity, setAlertSeverity] = useState<AlertColor>('info')
|
||||||
const [title, setTitle] = useState('')
|
const [title, setTitle] = useState('')
|
||||||
const [alertText, setAlertText] = useState('')
|
const [alertText, setAlertText] = useState('')
|
||||||
const [numberOfPeers, setNumberOfPeers] = useState(1)
|
const [numberOfPeers, setNumberOfPeers] = useState(1)
|
||||||
|
const [roomId, setRoomId] = useState<string | undefined>(undefined)
|
||||||
|
const [password, setPassword] = useState<string | undefined>(undefined)
|
||||||
const [isPeerListOpen, setIsPeerListOpen] = useState(false)
|
const [isPeerListOpen, setIsPeerListOpen] = useState(false)
|
||||||
const [peerList, setPeerList] = useState<Peer[]>([]) // except you
|
const [peerList, setPeerList] = useState<Peer[]>([]) // except you
|
||||||
const [tabHasFocus, setTabHasFocus] = useState(true)
|
const [tabHasFocus, setTabHasFocus] = useState(true)
|
||||||
@ -67,6 +71,10 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
|
|||||||
showAlert,
|
showAlert,
|
||||||
isPeerListOpen,
|
isPeerListOpen,
|
||||||
setIsQRCodeDialogOpen,
|
setIsQRCodeDialogOpen,
|
||||||
|
roomId,
|
||||||
|
setRoomId,
|
||||||
|
password,
|
||||||
|
setPassword,
|
||||||
setIsPeerListOpen,
|
setIsPeerListOpen,
|
||||||
peerList,
|
peerList,
|
||||||
setPeerList,
|
setPeerList,
|
||||||
@ -80,6 +88,10 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
|
|||||||
[
|
[
|
||||||
isPeerListOpen,
|
isPeerListOpen,
|
||||||
setIsQRCodeDialogOpen,
|
setIsQRCodeDialogOpen,
|
||||||
|
roomId,
|
||||||
|
setRoomId,
|
||||||
|
password,
|
||||||
|
setPassword,
|
||||||
numberOfPeers,
|
numberOfPeers,
|
||||||
peerList,
|
peerList,
|
||||||
tabHasFocus,
|
tabHasFocus,
|
||||||
@ -146,12 +158,21 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
|
|||||||
setIsPeerListOpen(!isPeerListOpen)
|
setIsPeerListOpen(!isPeerListOpen)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleLinkButtonClick = async () => {
|
const copyToClipboard = async (
|
||||||
await navigator.clipboard.writeText(window.location.href)
|
content: string,
|
||||||
|
alert: string,
|
||||||
|
severity: AlertColor = 'success'
|
||||||
|
) => {
|
||||||
|
await navigator.clipboard.writeText(content)
|
||||||
|
shellContextValue.showAlert(alert, { severity })
|
||||||
|
}
|
||||||
|
|
||||||
shellContextValue.showAlert('Current URL copied to clipboard', {
|
const handleLinkButtonClick = async () => {
|
||||||
severity: 'success',
|
if (roomId !== undefined && password !== undefined) {
|
||||||
})
|
setIsRoomShareDialogOpen(true)
|
||||||
|
} else {
|
||||||
|
copyToClipboard(window.location.href, 'Current URL copied to clipboard')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDrawerClose = () => {
|
const handleDrawerClose = () => {
|
||||||
@ -178,6 +199,10 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
|
|||||||
setIsQRCodeDialogOpen(false)
|
setIsQRCodeDialogOpen(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleRoomShareDialogClose = () => {
|
||||||
|
setIsRoomShareDialogOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ShellContext.Provider value={shellContextValue}>
|
<ShellContext.Provider value={shellContextValue}>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
@ -234,6 +259,14 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
|
|||||||
isOpen={isQRCodeDialogOpen}
|
isOpen={isQRCodeDialogOpen}
|
||||||
handleClose={handleQRCodeDialogClose}
|
handleClose={handleQRCodeDialogClose}
|
||||||
/>
|
/>
|
||||||
|
<RoomShareDialog
|
||||||
|
isOpen={isRoomShareDialogOpen}
|
||||||
|
handleClose={handleRoomShareDialogClose}
|
||||||
|
roomId={roomId ?? ''}
|
||||||
|
password={password ?? ''}
|
||||||
|
showAlert={showAlert}
|
||||||
|
copyToClipboard={copyToClipboard}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</ShellContext.Provider>
|
</ShellContext.Provider>
|
||||||
|
@ -2,6 +2,7 @@ import { createContext, Dispatch, SetStateAction } from 'react'
|
|||||||
import { FileOfferMetadata } from 'models/chat'
|
import { FileOfferMetadata } from 'models/chat'
|
||||||
|
|
||||||
interface RoomContextProps {
|
interface RoomContextProps {
|
||||||
|
isPrivate: boolean
|
||||||
isMessageSending: boolean
|
isMessageSending: boolean
|
||||||
selfVideoStream: MediaStream | null
|
selfVideoStream: MediaStream | null
|
||||||
setSelfVideoStream: Dispatch<SetStateAction<MediaStream | null>>
|
setSelfVideoStream: Dispatch<SetStateAction<MediaStream | null>>
|
||||||
@ -18,6 +19,7 @@ interface RoomContextProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const RoomContext = createContext<RoomContextProps>({
|
export const RoomContext = createContext<RoomContextProps>({
|
||||||
|
isPrivate: false,
|
||||||
isMessageSending: false,
|
isMessageSending: false,
|
||||||
selfVideoStream: null,
|
selfVideoStream: null,
|
||||||
setSelfVideoStream: () => {},
|
setSelfVideoStream: () => {},
|
||||||
|
@ -10,6 +10,10 @@ interface ShellContextProps {
|
|||||||
setNumberOfPeers: Dispatch<SetStateAction<number>>
|
setNumberOfPeers: Dispatch<SetStateAction<number>>
|
||||||
setTitle: Dispatch<SetStateAction<string>>
|
setTitle: Dispatch<SetStateAction<string>>
|
||||||
showAlert: (message: string, options?: AlertOptions) => void
|
showAlert: (message: string, options?: AlertOptions) => void
|
||||||
|
roomId?: string
|
||||||
|
setRoomId: Dispatch<SetStateAction<string | undefined>>
|
||||||
|
password?: string
|
||||||
|
setPassword: Dispatch<SetStateAction<string | undefined>>
|
||||||
isPeerListOpen: boolean
|
isPeerListOpen: boolean
|
||||||
setIsPeerListOpen: Dispatch<SetStateAction<boolean>>
|
setIsPeerListOpen: Dispatch<SetStateAction<boolean>>
|
||||||
peerList: Peer[]
|
peerList: Peer[]
|
||||||
@ -29,6 +33,10 @@ export const ShellContext = createContext<ShellContextProps>({
|
|||||||
setNumberOfPeers: () => {},
|
setNumberOfPeers: () => {},
|
||||||
setTitle: () => {},
|
setTitle: () => {},
|
||||||
showAlert: () => {},
|
showAlert: () => {},
|
||||||
|
roomId: undefined,
|
||||||
|
setRoomId: () => {},
|
||||||
|
password: undefined,
|
||||||
|
setPassword: () => {},
|
||||||
isPeerListOpen: false,
|
isPeerListOpen: false,
|
||||||
setIsPeerListOpen: () => {},
|
setIsPeerListOpen: () => {},
|
||||||
peerList: [],
|
peerList: [],
|
||||||
|
@ -5,6 +5,7 @@ import { useParams } from 'react-router-dom'
|
|||||||
import { ShellContext } from 'contexts/ShellContext'
|
import { ShellContext } from 'contexts/ShellContext'
|
||||||
import { NotificationService } from 'services/Notification'
|
import { NotificationService } from 'services/Notification'
|
||||||
import { PasswordPrompt } from 'components/PasswordPrompt/PasswordPrompt'
|
import { PasswordPrompt } from 'components/PasswordPrompt/PasswordPrompt'
|
||||||
|
import { encodePassword } from 'utils'
|
||||||
|
|
||||||
interface PublicRoomProps {
|
interface PublicRoomProps {
|
||||||
userId: string
|
userId: string
|
||||||
@ -13,7 +14,12 @@ interface PublicRoomProps {
|
|||||||
export function PrivateRoom({ userId }: PublicRoomProps) {
|
export function PrivateRoom({ userId }: PublicRoomProps) {
|
||||||
const { roomId = '' } = useParams()
|
const { roomId = '' } = useParams()
|
||||||
const { setTitle } = useContext(ShellContext)
|
const { setTitle } = useContext(ShellContext)
|
||||||
const [password, setPassword] = useState('')
|
|
||||||
|
const urlParams = new URLSearchParams(window.location.hash.substring(1))
|
||||||
|
// Clear secret from address bar
|
||||||
|
if (window.location.hash.length > 0)
|
||||||
|
window.history.replaceState(window.history.state, '', '#')
|
||||||
|
const [secret, setSecret] = useState(urlParams.get('secret') ?? '')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
NotificationService.requestPermission()
|
NotificationService.requestPermission()
|
||||||
@ -23,18 +29,21 @@ export function PrivateRoom({ userId }: PublicRoomProps) {
|
|||||||
setTitle(`Room: ${roomId}`)
|
setTitle(`Room: ${roomId}`)
|
||||||
}, [roomId, setTitle])
|
}, [roomId, setTitle])
|
||||||
|
|
||||||
const handlePasswordEntered = (password: string) => {
|
const handlePasswordEntered = async (password: string) => {
|
||||||
setPassword(password)
|
if (password.length !== 0) setSecret(await encodePassword(roomId, password))
|
||||||
}
|
}
|
||||||
|
|
||||||
const isPasswordEntered = password.length === 0
|
if (urlParams.has('pwd') && !urlParams.has('secret'))
|
||||||
|
handlePasswordEntered(urlParams.get('pwd') ?? '')
|
||||||
|
|
||||||
return isPasswordEntered ? (
|
const awaitingSecret = secret.length === 0
|
||||||
|
|
||||||
|
return awaitingSecret ? (
|
||||||
<PasswordPrompt
|
<PasswordPrompt
|
||||||
isOpen={isPasswordEntered}
|
isOpen={awaitingSecret}
|
||||||
onPasswordEntered={handlePasswordEntered}
|
onPasswordEntered={handlePasswordEntered}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Room userId={userId} roomId={roomId} password={password} />
|
<Room userId={userId} roomId={roomId} password={secret} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -14,3 +14,10 @@ export const isRecord = (variable: any): variable is Record<string, any> => {
|
|||||||
export const isError = (e: any): e is Error => {
|
export const isError = (e: any): e is Error => {
|
||||||
return e instanceof Error
|
return e instanceof Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const encodePassword = async (roomId: string, password: string) => {
|
||||||
|
const data = new TextEncoder().encode(`${roomId}_${password}`)
|
||||||
|
const digest = await window.crypto.subtle.digest('SHA-256', data)
|
||||||
|
const bytes = new Uint8Array(digest)
|
||||||
|
return window.btoa(String.fromCharCode(...bytes))
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user