refactor(services): Standardize services and lib organization (#226)

* refactor(Notification): use instance methods
* refactor(Audio): move to lib layer
* refactor(EncryptionService): rename instance to encryption
* refactor(ConnectionTest): move to lib
* refactor(FileTransfer): move to lib
* refactor(PeerRoom): move to lib
* refactor(sleep): move to lib
* refactor(type-guards): move to lib
* refactor(SerializationService): use standard instance name
* refactor(SettingsService): use standard instance name
This commit is contained in:
Jeremy Kahn 2024-01-28 20:46:59 -06:00 committed by GitHub
parent 4d6d1482f2
commit 94a4b2fb2e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
46 changed files with 129 additions and 151 deletions

View File

@ -3,7 +3,7 @@ import localforage from 'localforage'
import { PersistedStorageKeys } from 'models/storage' import { PersistedStorageKeys } from 'models/storage'
import { import {
mockSerializationService, mockSerialization,
mockSerializedPrivateKey, mockSerializedPrivateKey,
mockSerializedPublicKey, mockSerializedPublicKey,
} from 'test-utils/mocks/mockSerializationService' } from 'test-utils/mocks/mockSerializationService'
@ -34,7 +34,7 @@ const renderBootstrap = async (overrides: Partial<BootstrapProps> = {}) => {
<Bootstrap <Bootstrap
persistedStorage={mockPersistedStorage as any as typeof localforage} persistedStorage={mockPersistedStorage as any as typeof localforage}
initialUserSettings={userSettingsStub} initialUserSettings={userSettingsStub}
serializationService={mockSerializationService} serializationService={mockSerialization}
{...overrides} {...overrides}
/> />
) )

View File

@ -27,15 +27,12 @@ import {
PostMessageEvent, PostMessageEvent,
PostMessageEventName, PostMessageEventName,
} from 'models/sdk' } from 'models/sdk'
import { import { serialization, SerializedUserSettings } from 'services/Serialization'
serializationService as serializationServiceInstance,
SerializedUserSettings,
} from 'services/Serialization'
export interface BootstrapProps { export interface BootstrapProps {
persistedStorage?: typeof localforage persistedStorage?: typeof localforage
initialUserSettings: UserSettings initialUserSettings: UserSettings
serializationService?: typeof serializationServiceInstance serializationService?: typeof serialization
} }
const configListenerTimeout = 3000 const configListenerTimeout = 3000
@ -82,7 +79,7 @@ const Bootstrap = ({
description: 'Persisted settings data for chitchatter', description: 'Persisted settings data for chitchatter',
}), }),
initialUserSettings, initialUserSettings,
serializationService = serializationServiceInstance, serializationService = serialization,
}: BootstrapProps) => { }: BootstrapProps) => {
const queryParams = useMemo( const queryParams = useMemo(
() => new URLSearchParams(window.location.search), () => new URLSearchParams(window.location.search),

View File

@ -3,7 +3,7 @@ import Box from '@mui/material/Box'
import Typography from '@mui/material/Typography' import Typography from '@mui/material/Typography'
import { v4 as uuid } from 'uuid' import { v4 as uuid } from 'uuid'
import { encryptionService } from 'services/Encryption' import { encryption } from 'services/Encryption'
import { import {
EnvironmentUnsupportedDialog, EnvironmentUnsupportedDialog,
isEnvironmentSupported, isEnvironmentSupported,
@ -32,8 +32,7 @@ const Init = ({ getUuid = uuid, ...props }: InitProps) => {
if (userSettings !== null) return if (userSettings !== null) return
try { try {
const { publicKey, privateKey } = const { publicKey, privateKey } = await encryption.generateKeyPair()
await encryptionService.generateKeyPair()
setUserSettings({ setUserSettings({
userId: getUuid(), userId: getUuid(),

View File

@ -3,7 +3,7 @@ import { TorrentFile } from 'webtorrent'
import CircularProgress from '@mui/material/CircularProgress' import CircularProgress from '@mui/material/CircularProgress'
import Typography from '@mui/material/Typography' import Typography from '@mui/material/Typography'
import { fileTransfer } from 'services/FileTransfer' import { fileTransfer } from 'lib/FileTransfer'
import { ShellContext } from 'contexts/ShellContext' import { ShellContext } from 'contexts/ShellContext'
type TorrentFiles = Awaited<ReturnType<typeof fileTransfer.download>> type TorrentFiles = Awaited<ReturnType<typeof fileTransfer.download>>

View File

@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'
import { materialDark } from 'react-syntax-highlighter/dist/esm/styles/prism' import { materialDark } from 'react-syntax-highlighter/dist/esm/styles/prism'
import { PrismAsyncLight as SyntaxHighlighter } from 'react-syntax-highlighter' import { PrismAsyncLight as SyntaxHighlighter } from 'react-syntax-highlighter'
import { CopyableBlock } from 'components/CopyableBlock/CopyableBlock' import { CopyableBlock } from 'components/CopyableBlock/CopyableBlock'
import { encryptionService } from 'services/Encryption/Encryption' import { encryption } from 'services/Encryption/Encryption'
interface PeerPublicKeyProps { interface PeerPublicKeyProps {
publicKey: CryptoKey publicKey: CryptoKey
@ -13,7 +13,7 @@ export const PublicKey = ({ publicKey }: PeerPublicKeyProps) => {
useEffect(() => { useEffect(() => {
;(async () => { ;(async () => {
setPublicKeyString(await encryptionService.stringifyCryptoKey(publicKey)) setPublicKeyString(await encryption.stringifyCryptoKey(publicKey))
})() })()
}, [publicKey]) }, [publicKey])

View File

@ -12,7 +12,7 @@ import { RoomContext } from 'contexts/RoomContext'
import { ShellContext } from 'contexts/ShellContext' import { ShellContext } from 'contexts/ShellContext'
import { MessageForm } from 'components/MessageForm' import { MessageForm } from 'components/MessageForm'
import { ChatTranscript } from 'components/ChatTranscript' import { ChatTranscript } from 'components/ChatTranscript'
import { encryptionService as encryptionServiceInstance } from 'services/Encryption' import { encryption } from 'services/Encryption'
import { SettingsContext } from 'contexts/SettingsContext' import { SettingsContext } from 'contexts/SettingsContext'
import { useRoom } from './useRoom' import { useRoom } from './useRoom'
@ -30,13 +30,13 @@ export interface RoomProps {
password?: string password?: string
roomId: string roomId: string
userId: string userId: string
encryptionService?: typeof encryptionServiceInstance encryptionService?: typeof encryption
} }
export function Room({ export function Room({
appId = `${encodeURI(window.location.origin)}_${process.env.REACT_APP_NAME}`, appId = `${encodeURI(window.location.origin)}_${process.env.REACT_APP_NAME}`,
getUuid = uuid, getUuid = uuid,
encryptionService = encryptionServiceInstance, encryptionService = encryption,
roomId, roomId,
password, password,
userId, userId,

View File

@ -9,7 +9,7 @@ import Menu from '@mui/material/Menu'
import MenuItem from '@mui/material/MenuItem' import MenuItem from '@mui/material/MenuItem'
import Tooltip from '@mui/material/Tooltip' import Tooltip from '@mui/material/Tooltip'
import { PeerRoom } from 'services/PeerRoom/PeerRoom' import { PeerRoom } from 'lib/PeerRoom'
import { useRoomAudio } from './useRoomAudio' import { useRoomAudio } from './useRoomAudio'
import { MediaButton } from './MediaButton' import { MediaButton } from './MediaButton'

View File

@ -6,7 +6,7 @@ import Tooltip from '@mui/material/Tooltip'
import CircularProgress from '@mui/material/CircularProgress' import CircularProgress from '@mui/material/CircularProgress'
import { RoomContext } from 'contexts/RoomContext' import { RoomContext } from 'contexts/RoomContext'
import { PeerRoom } from 'services/PeerRoom/PeerRoom' import { PeerRoom } from 'lib/PeerRoom'
import { useRoomFileShare } from './useRoomFileShare' import { useRoomFileShare } from './useRoomFileShare'
import { MediaButton } from './MediaButton' import { MediaButton } from './MediaButton'

View File

@ -3,7 +3,7 @@ import ScreenShare from '@mui/icons-material/ScreenShare'
import StopScreenShare from '@mui/icons-material/StopScreenShare' import StopScreenShare from '@mui/icons-material/StopScreenShare'
import Tooltip from '@mui/material/Tooltip' import Tooltip from '@mui/material/Tooltip'
import { PeerRoom } from 'services/PeerRoom/PeerRoom' import { PeerRoom } from 'lib/PeerRoom'
import { useRoomScreenShare } from './useRoomScreenShare' import { useRoomScreenShare } from './useRoomScreenShare'
import { MediaButton } from './MediaButton' import { MediaButton } from './MediaButton'

View File

@ -9,7 +9,7 @@ import Menu from '@mui/material/Menu'
import MenuItem from '@mui/material/MenuItem' import MenuItem from '@mui/material/MenuItem'
import Tooltip from '@mui/material/Tooltip' import Tooltip from '@mui/material/Tooltip'
import { PeerRoom } from 'services/PeerRoom/PeerRoom' import { PeerRoom } from 'lib/PeerRoom'
import { useRoomVideo } from './useRoomVideo' import { useRoomVideo } from './useRoomVideo'
import { MediaButton } from './MediaButton' import { MediaButton } from './MediaButton'

View File

@ -1,8 +1,8 @@
import { useCallback, useContext, useEffect, useState } from 'react' import { useCallback, useContext, useEffect, useState } from 'react'
import { ShellContext } from 'contexts/ShellContext' import { ShellContext } from 'contexts/ShellContext'
import { Peer, PeerVerificationState } from 'models/chat' import { Peer, PeerVerificationState } from 'models/chat'
import { encryptionService as encryptionServiceInstance } from 'services/Encryption' import { encryption } from 'services/Encryption'
import { PeerRoom } from 'services/PeerRoom' import { PeerRoom } from 'lib/PeerRoom'
import { PeerActions } from 'models/network' import { PeerActions } from 'models/network'
import { verificationTimeout } from 'config/messaging' import { verificationTimeout } from 'config/messaging'
import { usePeerNameDisplay } from 'components/PeerNameDisplay' import { usePeerNameDisplay } from 'components/PeerNameDisplay'
@ -10,13 +10,13 @@ import { usePeerNameDisplay } from 'components/PeerNameDisplay'
interface UserPeerVerificationProps { interface UserPeerVerificationProps {
peerRoom: PeerRoom peerRoom: PeerRoom
privateKey: CryptoKey privateKey: CryptoKey
encryptionService?: typeof encryptionServiceInstance encryptionService?: typeof encryption
} }
export const usePeerVerification = ({ export const usePeerVerification = ({
peerRoom, peerRoom,
privateKey, privateKey,
encryptionService = encryptionServiceInstance, encryptionService = encryption,
}: UserPeerVerificationProps) => { }: UserPeerVerificationProps) => {
const { updatePeer, peerList, showAlert } = useContext(ShellContext) const { updatePeer, peerList, showAlert } = useContext(ShellContext)

View File

@ -25,14 +25,11 @@ import {
PeerVerificationState, PeerVerificationState,
} from 'models/chat' } from 'models/chat'
import { getPeerName, usePeerNameDisplay } from 'components/PeerNameDisplay' import { getPeerName, usePeerNameDisplay } from 'components/PeerNameDisplay'
import { NotificationService } from 'services/Notification' import { Audio } from 'lib/Audio'
import { Audio as AudioService } from 'services/Audio' import { notification } from 'services/Notification'
import { PeerRoom, PeerHookType } from 'services/PeerRoom' import { PeerRoom, PeerHookType } from 'lib/PeerRoom'
import { fileTransfer } from 'services/FileTransfer' import { fileTransfer } from 'lib/FileTransfer'
import { import { AllowedKeyType, encryption } from 'services/Encryption'
AllowedKeyType,
encryptionService as encryptionServiceInstance,
} from 'services/Encryption'
import { messageTranscriptSizeLimit } from 'config/messaging' import { messageTranscriptSizeLimit } from 'config/messaging'
@ -43,7 +40,7 @@ interface UseRoomConfig {
userId: string userId: string
publicKey: CryptoKey publicKey: CryptoKey
getUuid?: typeof uuid getUuid?: typeof uuid
encryptionService?: typeof encryptionServiceInstance encryptionService?: typeof encryption
} }
interface UserMetadata { interface UserMetadata {
@ -59,7 +56,7 @@ export function useRoom(
userId, userId,
publicKey, publicKey,
getUuid = uuid, getUuid = uuid,
encryptionService = encryptionServiceInstance, encryptionService = encryption,
}: UseRoomConfig }: UseRoomConfig
) { ) {
const isPrivate = password !== undefined const isPrivate = password !== undefined
@ -87,7 +84,7 @@ export function useRoom(
[] []
) )
const [newMessageAudio] = useState( const [newMessageAudio] = useState(
() => new AudioService(process.env.PUBLIC_URL + '/sounds/new-message.aac') () => new Audio(process.env.PUBLIC_URL + '/sounds/new-message.aac')
) )
const { getDisplayUsername } = usePeerNameDisplay() const { getDisplayUsername } = usePeerNameDisplay()
@ -323,9 +320,7 @@ export function useRoom(
if (userSettings.showNotificationOnNewMessage) { if (userSettings.showNotificationOnNewMessage) {
const displayUsername = getDisplayUsername(message.authorId) const displayUsername = getDisplayUsername(message.authorId)
NotificationService.showNotification( notification.showNotification(`${displayUsername}: ${message.text}`)
`${displayUsername}: ${message.text}`
)
} }
} }
@ -433,7 +428,7 @@ export function useRoom(
} }
if (userSettings.showNotificationOnNewMessage) { if (userSettings.showNotificationOnNewMessage) {
NotificationService.showNotification( notification.showNotification(
`${getDisplayUsername(inlineMedia.authorId)} shared media` `${getDisplayUsername(inlineMedia.authorId)} shared media`
) )
} }

View File

@ -3,7 +3,7 @@ import { useContext, useEffect, useCallback, useState } from 'react'
import { ShellContext } from 'contexts/ShellContext' import { ShellContext } from 'contexts/ShellContext'
import { PeerActions } from 'models/network' import { PeerActions } from 'models/network'
import { AudioState, Peer } from 'models/chat' import { AudioState, Peer } from 'models/chat'
import { PeerRoom, PeerHookType, PeerStreamType } from 'services/PeerRoom' import { PeerRoom, PeerHookType, PeerStreamType } from 'lib/PeerRoom'
interface UseRoomAudioConfig { interface UseRoomAudioConfig {
peerRoom: PeerRoom peerRoom: PeerRoom

View File

@ -1,13 +1,12 @@
import { useContext, useEffect, useState } from 'react' import { useContext, useEffect, useState } from 'react'
import { sleep } from 'utils' import { sleep } from 'lib/sleep'
import { RoomContext } from 'contexts/RoomContext' import { RoomContext } from 'contexts/RoomContext'
import { ShellContext } from 'contexts/ShellContext' import { ShellContext } from 'contexts/ShellContext'
import { PeerActions } from 'models/network' import { PeerActions } from 'models/network'
import { FileOfferMetadata, Peer } from 'models/chat' import { FileOfferMetadata, Peer } from 'models/chat'
import { PeerRoom, PeerHookType } from 'services/PeerRoom' import { PeerRoom, PeerHookType } from 'lib/PeerRoom'
import { fileTransfer } from 'lib/FileTransfer'
import { fileTransfer } from 'services/FileTransfer/index'
interface UseRoomFileShareConfig { interface UseRoomFileShareConfig {
onInlineMediaUpload: (files: File[]) => void onInlineMediaUpload: (files: File[]) => void

View File

@ -1,11 +1,11 @@
import { useContext, useEffect, useCallback, useState } from 'react' import { useContext, useEffect, useCallback, useState } from 'react'
import { isRecord } from 'utils' import { isRecord } from 'lib/type-guards'
import { RoomContext } from 'contexts/RoomContext' import { RoomContext } from 'contexts/RoomContext'
import { ShellContext } from 'contexts/ShellContext' import { ShellContext } from 'contexts/ShellContext'
import { PeerActions } from 'models/network' import { PeerActions } from 'models/network'
import { ScreenShareState, Peer, VideoStreamType } from 'models/chat' import { ScreenShareState, Peer, VideoStreamType } from 'models/chat'
import { PeerRoom, PeerHookType, PeerStreamType } from 'services/PeerRoom' import { PeerRoom, PeerHookType, PeerStreamType } from 'lib/PeerRoom'
interface UseRoomScreenShareConfig { interface UseRoomScreenShareConfig {
peerRoom: PeerRoom peerRoom: PeerRoom

View File

@ -4,9 +4,8 @@ import { RoomContext } from 'contexts/RoomContext'
import { ShellContext } from 'contexts/ShellContext' import { ShellContext } from 'contexts/ShellContext'
import { PeerActions } from 'models/network' import { PeerActions } from 'models/network'
import { VideoState, Peer, VideoStreamType } from 'models/chat' import { VideoState, Peer, VideoStreamType } from 'models/chat'
import { PeerRoom, PeerHookType, PeerStreamType } from 'services/PeerRoom' import { PeerRoom, PeerHookType, PeerStreamType } from 'lib/PeerRoom'
import { isRecord } from 'lib/type-guards'
import { isRecord } from 'utils'
interface UseRoomVideoConfig { interface UseRoomVideoConfig {
peerRoom: PeerRoom peerRoom: PeerRoom

View File

@ -6,7 +6,7 @@ import Circle from '@mui/icons-material/FiberManualRecord'
import { Box } from '@mui/system' import { Box } from '@mui/system'
import ReportIcon from '@mui/icons-material/Report' import ReportIcon from '@mui/icons-material/Report'
import { TrackerConnection } from 'services/ConnectionTest/ConnectionTest' import { TrackerConnection } from 'lib/ConnectionTest'
import { ShellContext } from 'contexts/ShellContext' import { ShellContext } from 'contexts/ShellContext'
import { ConnectionTestResults as IConnectionTestResults } from './useConnectionTest' import { ConnectionTestResults as IConnectionTestResults } from './useConnectionTest'

View File

@ -5,8 +5,8 @@ import Tooltip from '@mui/material/Tooltip'
import Download from '@mui/icons-material/Download' import Download from '@mui/icons-material/Download'
import CircularProgress from '@mui/material/CircularProgress' import CircularProgress from '@mui/material/CircularProgress'
import { isError } from 'utils' import { isError } from 'lib/type-guards'
import { fileTransfer } from 'services/FileTransfer/index' import { fileTransfer } from 'lib/FileTransfer'
import { Peer } from 'models/chat' import { Peer } from 'models/chat'
import { ShellContext } from 'contexts/ShellContext' import { ShellContext } from 'contexts/ShellContext'

View File

@ -10,8 +10,8 @@ import CircularProgress from '@mui/material/CircularProgress'
import { UserInfo } from 'components/UserInfo' import { UserInfo } from 'components/UserInfo'
import { AudioState, Peer } from 'models/chat' import { AudioState, Peer } from 'models/chat'
import { PeerConnectionType } from 'services/PeerRoom' import { PeerConnectionType } from 'lib/PeerRoom'
import { TrackerConnection } from 'services/ConnectionTest' import { TrackerConnection } from 'lib/ConnectionTest'
import { PeerListHeader } from './PeerListHeader' import { PeerListHeader } from './PeerListHeader'
import { PeerListItem } from './PeerListItem' import { PeerListItem } from './PeerListItem'

View File

@ -19,7 +19,7 @@ import { AudioVolume } from 'components/AudioVolume'
import { PeerNameDisplay } from 'components/PeerNameDisplay' import { PeerNameDisplay } from 'components/PeerNameDisplay'
import { PublicKey } from 'components/PublicKey' import { PublicKey } from 'components/PublicKey'
import { Peer, PeerVerificationState } from 'models/chat' import { Peer, PeerVerificationState } from 'models/chat'
import { PeerConnectionType } from 'services/PeerRoom/PeerRoom' import { PeerConnectionType } from 'lib/PeerRoom'
import { PeerDownloadFileButton } from './PeerDownloadFileButton' import { PeerDownloadFileButton } from './PeerDownloadFileButton'

View File

@ -14,8 +14,8 @@ import CloseIcon from '@mui/icons-material/Close'
import { AlertOptions } from 'models/shell' import { AlertOptions } from 'models/shell'
import { useEffect, useState, SyntheticEvent } from 'react' import { useEffect, useState, SyntheticEvent } from 'react'
import { sleep } from 'utils' import { sleep } from 'lib/sleep'
import { encryptionService } from 'services/Encryption' import { encryption } from 'services/Encryption'
export interface RoomShareDialogProps { export interface RoomShareDialogProps {
isOpen: boolean isOpen: boolean
@ -51,10 +51,7 @@ export function RoomShareDialog(props: RoomShareDialogProps) {
const url = window.location.href.split('#')[0] const url = window.location.href.split('#')[0]
const copyWithPass = async () => { const copyWithPass = async () => {
const encoded = await encryptionService.encodePassword( const encoded = await encryption.encodePassword(props.roomId, password)
props.roomId,
password
)
if (encoded === props.password) { if (encoded === props.password) {
const params = new URLSearchParams() const params = new URLSearchParams()

View File

@ -21,8 +21,7 @@ import { SettingsContext } from 'contexts/SettingsContext'
import { AlertOptions, QueryParamKeys } from 'models/shell' import { AlertOptions, QueryParamKeys } from 'models/shell'
import { AudioState, ScreenShareState, VideoState, Peer } from 'models/chat' import { AudioState, ScreenShareState, VideoState, Peer } from 'models/chat'
import { ErrorBoundary } from 'components/ErrorBoundary' import { ErrorBoundary } from 'components/ErrorBoundary'
import { PeerConnectionType } from 'lib/PeerRoom'
import { PeerConnectionType } from 'services/PeerRoom/PeerRoom'
import { Drawer } from './Drawer' import { Drawer } from './Drawer'
import { UpgradeDialog } from './UpgradeDialog' import { UpgradeDialog } from './UpgradeDialog'

View File

@ -1,11 +1,12 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { sleep } from 'utils'
import { sleep } from 'lib/sleep'
import { import {
ConnectionTest, ConnectionTest,
ConnectionTestEvent, ConnectionTestEvent,
ConnectionTestEvents, ConnectionTestEvents,
TrackerConnection, TrackerConnection,
} from 'services/ConnectionTest/ConnectionTest' } from 'lib/ConnectionTest'
export interface ConnectionTestResults { export interface ConnectionTestResults {
hasHost: boolean hasHost: boolean

View File

@ -1,7 +1,7 @@
import { createContext } from 'react' import { createContext } from 'react'
import { ColorMode, UserSettings } from 'models/settings' import { ColorMode, UserSettings } from 'models/settings'
import { encryptionService } from 'services/Encryption' import { encryption } from 'services/Encryption'
export interface SettingsContextProps { export interface SettingsContextProps {
updateUserSettings: (settings: Partial<UserSettings>) => Promise<void> updateUserSettings: (settings: Partial<UserSettings>) => Promise<void>
@ -17,7 +17,7 @@ export const SettingsContext = createContext<SettingsContextProps>({
playSoundOnNewMessage: true, playSoundOnNewMessage: true,
showNotificationOnNewMessage: true, showNotificationOnNewMessage: true,
showActiveTypingStatus: true, showActiveTypingStatus: true,
publicKey: encryptionService.cryptoKeyStub, publicKey: encryption.cryptoKeyStub,
privateKey: encryptionService.cryptoKeyStub, privateKey: encryption.cryptoKeyStub,
}), }),
}) })

View File

@ -2,9 +2,9 @@ import { createContext, Dispatch, SetStateAction } from 'react'
import { AlertOptions } from 'models/shell' import { AlertOptions } from 'models/shell'
import { AudioState, ScreenShareState, VideoState, Peer } from 'models/chat' import { AudioState, ScreenShareState, VideoState, Peer } from 'models/chat'
import { PeerConnectionType } from 'services/PeerRoom/PeerRoom' import { PeerConnectionType } from 'lib/PeerRoom'
import { ConnectionTestResults } from 'components/Shell/useConnectionTest' import { ConnectionTestResults } from 'components/Shell/useConnectionTest'
import { TrackerConnection } from 'services/ConnectionTest/ConnectionTest' import { TrackerConnection } from 'lib/ConnectionTest'
interface ShellContextProps { interface ShellContextProps {
isEmbedded: boolean isEmbedded: boolean

View File

@ -1,7 +1,7 @@
import { joinRoom, Room, BaseRoomConfig } from 'trystero' import { joinRoom, Room, BaseRoomConfig } from 'trystero'
import { TorrentRoomConfig } from 'trystero/torrent' import { TorrentRoomConfig } from 'trystero/torrent'
import { sleep } from 'utils' import { sleep } from 'lib/sleep'
export enum PeerHookType { export enum PeerHookType {
NEW_PEER = 'NEW_PEER', NEW_PEER = 'NEW_PEER',
@ -48,6 +48,18 @@ export class PeerRoom {
private isProcessingPendingStreams = false private isProcessingPendingStreams = false
private processPendingStreams = async () => {
if (this.isProcessingPendingStreams) return
this.isProcessingPendingStreams = true
while (this.streamQueue.length > 0) {
await this.streamQueue.shift()?.()
}
this.isProcessingPendingStreams = false
}
constructor(config: TorrentRoomConfig & BaseRoomConfig, roomId: string) { constructor(config: TorrentRoomConfig & BaseRoomConfig, roomId: string) {
this.roomConfig = config this.roomConfig = config
this.room = joinRoom(this.roomConfig, roomId) this.room = joinRoom(this.roomConfig, roomId)
@ -171,18 +183,6 @@ export class PeerRoom {
this.processPendingStreams() this.processPendingStreams()
} }
private processPendingStreams = async () => {
if (this.isProcessingPendingStreams) return
this.isProcessingPendingStreams = true
while (this.streamQueue.length > 0) {
await this.streamQueue.shift()?.()
}
this.isProcessingPendingStreams = false
}
removeStream: Room['removeStream'] = (stream, targetPeers) => { removeStream: Room['removeStream'] = (stream, targetPeers) => {
return this.room.removeStream(stream, targetPeers) return this.room.removeStream(stream, targetPeers)
} }

4
src/lib/sleep.ts Normal file
View File

@ -0,0 +1,4 @@
export const sleep = (milliseconds: number): Promise<void> =>
new Promise<void>(res => {
setTimeout(res, milliseconds)
})

View File

@ -1,8 +1,3 @@
export const sleep = (milliseconds: number): Promise<void> =>
new Promise<void>(res => {
setTimeout(res, milliseconds)
})
export const isRecord = (variable: any): variable is Record<string, any> => { export const isRecord = (variable: any): variable is Record<string, any> => {
return ( return (
typeof variable === 'object' && typeof variable === 'object' &&

View File

@ -3,9 +3,9 @@ import { Room } from 'components/Room'
import { useParams } from 'react-router-dom' import { useParams } from 'react-router-dom'
import { ShellContext } from 'contexts/ShellContext' import { ShellContext } from 'contexts/ShellContext'
import { NotificationService } from 'services/Notification' import { notification } from 'services/Notification'
import { PasswordPrompt } from 'components/PasswordPrompt' import { PasswordPrompt } from 'components/PasswordPrompt'
import { encryptionService } from 'services/Encryption' import { encryption } from 'services/Encryption'
interface PublicRoomProps { interface PublicRoomProps {
userId: string userId: string
@ -22,7 +22,7 @@ export function PrivateRoom({ userId }: PublicRoomProps) {
const [secret, setSecret] = useState(urlParams.get('secret') ?? '') const [secret, setSecret] = useState(urlParams.get('secret') ?? '')
useEffect(() => { useEffect(() => {
NotificationService.requestPermission() notification.requestPermission()
}, []) }, [])
useEffect(() => { useEffect(() => {
@ -31,7 +31,7 @@ export function PrivateRoom({ userId }: PublicRoomProps) {
const handlePasswordEntered = async (password: string) => { const handlePasswordEntered = async (password: string) => {
if (password.length !== 0) if (password.length !== 0)
setSecret(await encryptionService.encodePassword(roomId, password)) setSecret(await encryption.encodePassword(roomId, password))
} }
if (urlParams.has('pwd') && !urlParams.has('secret')) if (urlParams.has('pwd') && !urlParams.has('secret'))

View File

@ -3,7 +3,7 @@ import { Room } from 'components/Room'
import { useParams } from 'react-router-dom' import { useParams } from 'react-router-dom'
import { ShellContext } from 'contexts/ShellContext' import { ShellContext } from 'contexts/ShellContext'
import { NotificationService } from 'services/Notification' import { notification } from 'services/Notification'
interface PublicRoomProps { interface PublicRoomProps {
userId: string userId: string
@ -14,7 +14,7 @@ export function PublicRoom({ userId }: PublicRoomProps) {
const { setTitle } = useContext(ShellContext) const { setTitle } = useContext(ShellContext)
useEffect(() => { useEffect(() => {
NotificationService.requestPermission() notification.requestPermission()
}, []) }, [])
useEffect(() => { useEffect(() => {

View File

@ -10,15 +10,15 @@ import FormControlLabel from '@mui/material/FormControlLabel'
import Paper from '@mui/material/Paper' import Paper from '@mui/material/Paper'
import useTheme from '@mui/material/styles/useTheme' import useTheme from '@mui/material/styles/useTheme'
import { settingsService } from 'services/Settings' import { settings } from 'services/Settings'
import { NotificationService } from 'services/Notification' import { notification } from 'services/Notification'
import { ShellContext } from 'contexts/ShellContext' import { ShellContext } from 'contexts/ShellContext'
import { StorageContext } from 'contexts/StorageContext' import { StorageContext } from 'contexts/StorageContext'
import { SettingsContext } from 'contexts/SettingsContext' import { SettingsContext } from 'contexts/SettingsContext'
import { PeerNameDisplay } from 'components/PeerNameDisplay' import { PeerNameDisplay } from 'components/PeerNameDisplay'
import { ConfirmDialog } from 'components/ConfirmDialog' import { ConfirmDialog } from 'components/ConfirmDialog'
import { isErrorWithMessage } from '../../utils' import { isErrorWithMessage } from '../../lib/type-guards'
interface SettingsProps { interface SettingsProps {
userId: string userId: string
@ -45,7 +45,7 @@ export const Settings = ({ userId }: SettingsProps) => {
useEffect(() => { useEffect(() => {
;(async () => { ;(async () => {
await NotificationService.requestPermission() await notification.requestPermission()
// This state needs to be set to cause a rerender so that // This state needs to be set to cause a rerender so that
// areNotificationsAvailable is up-to-date. // areNotificationsAvailable is up-to-date.
@ -93,7 +93,7 @@ export const Settings = ({ userId }: SettingsProps) => {
const handleExportSettingsClick = async () => { const handleExportSettingsClick = async () => {
try { try {
await settingsService.exportSettings(getUserSettings()) await settings.exportSettings(getUserSettings())
} catch (e) { } catch (e) {
if (isErrorWithMessage(e)) { if (isErrorWithMessage(e)) {
showAlert(e.message, { severity: 'error' }) showAlert(e.message, { severity: 'error' })
@ -103,7 +103,7 @@ export const Settings = ({ userId }: SettingsProps) => {
const handleImportSettingsClick = async ([[, file]]: Result[]) => { const handleImportSettingsClick = async ([[, file]]: Result[]) => {
try { try {
const userSettings = await settingsService.importSettings(file) const userSettings = await settings.importSettings(file)
updateUserSettings(userSettings) updateUserSettings(userSettings)
@ -115,7 +115,7 @@ export const Settings = ({ userId }: SettingsProps) => {
} }
} }
const areNotificationsAvailable = NotificationService.permission === 'granted' const areNotificationsAvailable = notification.permission === 'granted'
return ( return (
<Box className="max-w-3xl mx-auto p-4"> <Box className="max-w-3xl mx-auto p-4">

View File

@ -110,4 +110,4 @@ export class EncryptionService {
} }
} }
export const encryptionService = new EncryptionService() export const encryption = new EncryptionService()

View File

@ -1,18 +1,17 @@
export class NotificationService { export class NotificationService {
static permission: NotificationPermission permission: NotificationPermission = 'default'
static requestPermission = async () => { requestPermission = async () => {
if (NotificationService.permission === 'granted') return if (this.permission === 'granted') return
NotificationService.permission = await Notification.requestPermission() this.permission = await Notification.requestPermission()
} }
static showNotification = ( showNotification = (message: string, options?: NotificationOptions) => {
message: string, if (this.permission !== 'granted') return
options?: NotificationOptions
) => {
if (NotificationService.permission !== 'granted') return
new Notification(message, options) new Notification(message, options)
} }
} }
export const notification = new NotificationService()

View File

@ -1,5 +1,5 @@
import { ColorMode, UserSettings } from 'models/settings' import { ColorMode, UserSettings } from 'models/settings'
import { AllowedKeyType, encryptionService } from 'services/Encryption' import { AllowedKeyType, encryption } from 'services/Encryption'
export interface SerializedUserSettings export interface SerializedUserSettings
extends Omit<UserSettings, 'publicKey' | 'privateKey'> { extends Omit<UserSettings, 'publicKey' | 'privateKey'> {
@ -42,13 +42,9 @@ export class SerializationService {
...userSettingsRest ...userSettingsRest
} = userSettings } = userSettings
const publicKey = await encryptionService.stringifyCryptoKey( const publicKey = await encryption.stringifyCryptoKey(publicCryptoKey)
publicCryptoKey
)
const privateKey = await encryptionService.stringifyCryptoKey( const privateKey = await encryption.stringifyCryptoKey(privateCryptoKey)
privateCryptoKey
)
return { return {
...userSettingsRest, ...userSettingsRest,
@ -66,11 +62,11 @@ export class SerializationService {
...userSettingsForIndexedDbRest ...userSettingsForIndexedDbRest
} = serializedUserSettings } = serializedUserSettings
const publicKey = await encryptionService.parseCryptoKeyString( const publicKey = await encryption.parseCryptoKeyString(
publicCryptoKeyString, publicCryptoKeyString,
AllowedKeyType.PUBLIC AllowedKeyType.PUBLIC
) )
const privateKey = await encryptionService.parseCryptoKeyString( const privateKey = await encryption.parseCryptoKeyString(
privateCryptoKeyString, privateCryptoKeyString,
AllowedKeyType.PRIVATE AllowedKeyType.PRIVATE
) )
@ -83,4 +79,4 @@ export class SerializationService {
} }
} }
export const serializationService = new SerializationService() export const serialization = new SerializationService()

View File

@ -1,10 +1,10 @@
import { saveAs } from 'file-saver' import { saveAs } from 'file-saver'
import { UserSettings } from 'models/settings' import { UserSettings } from 'models/settings'
import { encryptionService } from 'services/Encryption' import { encryption } from 'services/Encryption'
import { import {
isSerializedUserSettings, isSerializedUserSettings,
serializationService, serialization,
} from 'services/Serialization/Serialization' } from 'services/Serialization/Serialization'
class InvalidFileError extends Error { class InvalidFileError extends Error {
@ -15,8 +15,9 @@ const encryptionTestTarget = 'chitchatter'
export class SettingsService { export class SettingsService {
exportSettings = async (userSettings: UserSettings) => { exportSettings = async (userSettings: UserSettings) => {
const serializedUserSettings = const serializedUserSettings = await serialization.serializeUserSettings(
await serializationService.serializeUserSettings(userSettings) userSettings
)
const blob = new Blob([JSON.stringify(serializedUserSettings)], { const blob = new Blob([JSON.stringify(serializedUserSettings)], {
type: 'application/json;charset=utf-8', type: 'application/json;charset=utf-8',
@ -44,14 +45,14 @@ export class SettingsService {
} }
const deserializedUserSettings = const deserializedUserSettings =
await serializationService.deserializeUserSettings(parsedFileResult) await serialization.deserializeUserSettings(parsedFileResult)
const encryptedString = await encryptionService.encryptString( const encryptedString = await encryption.encryptString(
deserializedUserSettings.publicKey, deserializedUserSettings.publicKey,
encryptionTestTarget encryptionTestTarget
) )
const decryptedString = await encryptionService.decryptString( const decryptedString = await encryption.decryptString(
deserializedUserSettings.privateKey, deserializedUserSettings.privateKey,
encryptedString encryptedString
) )
@ -77,4 +78,4 @@ export class SettingsService {
} }
} }
export const settingsService = new SettingsService() export const settings = new SettingsService()

View File

@ -1,10 +1,10 @@
import { encryptionService } from 'services/Encryption' import { encryption } from 'services/Encryption'
export const mockEncryptionService = encryptionService export const mockEncryptionService = encryption
mockEncryptionService.generateKeyPair = jest.fn(async () => ({ mockEncryptionService.generateKeyPair = jest.fn(async () => ({
publicKey: encryptionService.cryptoKeyStub, publicKey: encryption.cryptoKeyStub,
privateKey: encryptionService.cryptoKeyStub, privateKey: encryption.cryptoKeyStub,
})) }))
mockEncryptionService.encodePassword = async () => '' mockEncryptionService.encodePassword = async () => ''
@ -12,4 +12,4 @@ mockEncryptionService.encodePassword = async () => ''
mockEncryptionService.stringifyCryptoKey = async () => '' mockEncryptionService.stringifyCryptoKey = async () => ''
mockEncryptionService.parseCryptoKeyString = async () => mockEncryptionService.parseCryptoKeyString = async () =>
encryptionService.cryptoKeyStub encryption.cryptoKeyStub

View File

@ -1,16 +1,13 @@
import { UserSettings } from 'models/settings' import { UserSettings } from 'models/settings'
import { encryptionService } from 'services/Encryption' import { encryption } from 'services/Encryption'
import { import { serialization, SerializedUserSettings } from 'services/Serialization'
serializationService,
SerializedUserSettings,
} from 'services/Serialization'
export const mockSerializedPublicKey = 'public key' export const mockSerializedPublicKey = 'public key'
export const mockSerializedPrivateKey = 'private key' export const mockSerializedPrivateKey = 'private key'
export const mockSerializationService = serializationService export const mockSerialization = serialization
mockSerializationService.serializeUserSettings = async ( mockSerialization.serializeUserSettings = async (
userSettings: UserSettings userSettings: UserSettings
) => { ) => {
const { publicKey, privateKey, ...userSettingsRest } = userSettings const { publicKey, privateKey, ...userSettingsRest } = userSettings
@ -22,14 +19,14 @@ mockSerializationService.serializeUserSettings = async (
} }
} }
mockSerializationService.deserializeUserSettings = async ( mockSerialization.deserializeUserSettings = async (
serializedUserSettings: SerializedUserSettings serializedUserSettings: SerializedUserSettings
) => { ) => {
const { publicKey, privateKey, ...userSettingsRest } = serializedUserSettings const { publicKey, privateKey, ...userSettingsRest } = serializedUserSettings
return { return {
publicKey: encryptionService.cryptoKeyStub, publicKey: encryption.cryptoKeyStub,
privateKey: encryptionService.cryptoKeyStub, privateKey: encryption.cryptoKeyStub,
...userSettingsRest, ...userSettingsRest,
} }
} }

View File

@ -1,6 +1,6 @@
import { SettingsContextProps } from 'contexts/SettingsContext' import { SettingsContextProps } from 'contexts/SettingsContext'
import { ColorMode, UserSettings } from 'models/settings' import { ColorMode, UserSettings } from 'models/settings'
import { encryptionService } from 'services/Encryption' import { encryption } from 'services/Encryption'
export const userSettingsContextStubFactory = ( export const userSettingsContextStubFactory = (
userSettingsOverrides: Partial<UserSettings> = {} userSettingsOverrides: Partial<UserSettings> = {}
@ -14,8 +14,8 @@ export const userSettingsContextStubFactory = (
playSoundOnNewMessage: true, playSoundOnNewMessage: true,
showNotificationOnNewMessage: true, showNotificationOnNewMessage: true,
showActiveTypingStatus: true, showActiveTypingStatus: true,
publicKey: encryptionService.cryptoKeyStub, publicKey: encryption.cryptoKeyStub,
privateKey: encryptionService.cryptoKeyStub, privateKey: encryption.cryptoKeyStub,
...userSettingsOverrides, ...userSettingsOverrides,
}), }),
} }

View File

@ -1,5 +1,5 @@
import { ColorMode, UserSettings } from 'models/settings' import { ColorMode, UserSettings } from 'models/settings'
import { encryptionService } from 'services/Encryption' import { encryption } from 'services/Encryption'
export const userSettingsStubFactory = ( export const userSettingsStubFactory = (
overrides: Partial<UserSettings> = {} overrides: Partial<UserSettings> = {}
@ -11,8 +11,8 @@ export const userSettingsStubFactory = (
playSoundOnNewMessage: true, playSoundOnNewMessage: true,
showNotificationOnNewMessage: true, showNotificationOnNewMessage: true,
showActiveTypingStatus: true, showActiveTypingStatus: true,
publicKey: encryptionService.cryptoKeyStub, publicKey: encryption.cryptoKeyStub,
privateKey: encryptionService.cryptoKeyStub, privateKey: encryption.cryptoKeyStub,
...overrides, ...overrides,
} }
} }