diff --git a/.github/actions/setup-env/action.yml b/.github/actions/setup-env/action.yml index 56af1bd3d7..41b4c86715 100644 --- a/.github/actions/setup-env/action.yml +++ b/.github/actions/setup-env/action.yml @@ -15,6 +15,21 @@ runs: - uses: actions/setup-node@master with: node-version: 18.12.1 + + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + if: runner.os == 'macOS' + with: + python-version: 3.12 + + - name: Print python version + run: which python3 + if: runner.os == 'macOS' + shell: bash + + - name: Install setuptools + run: python3 -m pip install setuptools + shell: bash - name: Print OS name run: echo ${{ runner.os }} diff --git a/.github/workflows/e2e-mac.yml b/.github/workflows/e2e-mac.yml index ff9002b06b..b8749fc7a2 100644 --- a/.github/workflows/e2e-mac.yml +++ b/.github/workflows/e2e-mac.yml @@ -35,7 +35,7 @@ jobs: - name: FILE_NAME env working-directory: ./packages/desktop/dist - run: echo "FILE_NAME="Quiet-$VERSION.dmg"" >> $GITHUB_ENV + run: echo "FILE_NAME="Quiet-$VERSION-arm64.dmg"" >> $GITHUB_ENV - name: List dist dir content working-directory: ./packages/desktop/dist @@ -50,7 +50,7 @@ jobs: run: hdiutil mount $FILE_NAME - name: Add App file to applications - run: cd ~ && cp -R "/Volumes/Quiet $VERSION/Quiet.app" /Applications + run: cd ~ && cp -R "/Volumes/Quiet $VERSION-arm64/Quiet.app" /Applications - name: Run invitation link test - Includes 2 separate application clients uses: nick-fields/retry@14672906e672a08bd6eeb15720e9ed3ce869cdd4 # v2.9.0 diff --git a/packages/backend/src/nest/connections-manager/connections-manager.service.ts b/packages/backend/src/nest/connections-manager/connections-manager.service.ts index b3d36347bb..1b41f8a611 100644 --- a/packages/backend/src/nest/connections-manager/connections-manager.service.ts +++ b/packages/backend/src/nest/connections-manager/connections-manager.service.ts @@ -12,7 +12,7 @@ import { getLibp2pAddressesFromCsrs, removeFilesFromDir } from '../common/utils' import { LazyModuleLoader } from '@nestjs/core' import { createLibp2pAddress, isPSKcodeValid } from '@quiet/common' -import { CertFieldsTypes, getCertFieldValue, loadCertificate } from '@quiet/identity' +import { CertFieldsTypes, createRootCA, getCertFieldValue, loadCertificate } from '@quiet/identity' import { ChannelMessageIdsResponse, ChannelSubscribedPayload, @@ -63,6 +63,7 @@ import { ServerStoredCommunityMetadata } from '../storageServiceClient/storageSe import { Tor } from '../tor/tor.service' import { ConfigOptions, GetPorts, ServerIoProviderTypes } from '../types' import { ServiceState, TorInitState } from './connections-manager.types' +import { DateTime } from 'luxon' @Injectable() export class ConnectionsManagerService extends EventEmitter implements OnModuleInit { @@ -223,7 +224,10 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI public async closeAllServices(options: { saveTor: boolean } = { saveTor: false }) { if (this.tor && !options.saveTor) { + this.logger('Killing tor') await this.tor.kill() + } else if (options.saveTor) { + this.logger('Saving tor') } if (this.storageService) { this.logger('Stopping orbitdb') @@ -260,8 +264,20 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI this.logger('Resuming!') this.logger('Reopening socket!') await this.openSocket() - this.logger('Dialing peers with info: ', this.peerInfo) - await this.libp2pService?.redialPeers(this.peerInfo) + this.logger('Attempting to redial peers!') + if (this.peerInfo && (this.peerInfo?.connected.length !== 0 || this.peerInfo?.dialed.length !== 0)) { + this.logger('Dialing peers with info from pause: ', this.peerInfo) + await this.libp2pService?.redialPeers([...this.peerInfo.connected, ...this.peerInfo.dialed]) + } else { + this.logger('Dialing peers from stored community (if exists)') + const community = await this.localDbService.getCurrentCommunity() + if (!community) { + this.logger(`No community launched, can't redial`) + return + } + const sortedPeers = await this.localDbService.getSortedPeers(community.peerList ?? []) + await this.libp2pService?.redialPeers(sortedPeers) + } } // This method is only used on iOS through rn-bridge for reacting on lifecycle changes @@ -269,15 +285,34 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI await this.socketService.init() } - public async leaveCommunity() { + public async leaveCommunity(): Promise { + this.logger('Running leaveCommunity') + + this.logger('Resetting tor') this.tor.resetHiddenServices() + + this.logger('Closing the socket') this.closeSocket() + + this.logger('Purging local DB') await this.localDbService.purge() + + this.logger('Closing services') await this.closeAllServices({ saveTor: true }) + + this.logger('Purging data') await this.purgeData() + + this.logger('Resetting state') await this.resetState() + + this.logger('Reopening local DB') await this.localDbService.open() - await this.socketService.init() + + this.logger('Restarting socket') + await this.openSocket() + + return true } async resetState() { @@ -295,16 +330,24 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI i.startsWith('Ipfs') || i.startsWith('OrbitDB') || i.startsWith('backendDB') || i.startsWith('Local Storage') ) for (const dir of dirsToRemove) { - removeFilesFromDir(path.join(this.quietDir, dir)) + const dirPath = path.join(this.quietDir, dir) + this.logger(`Removing dir: ${dirPath}`) + removeFilesFromDir(dirPath) } } public async getNetwork(): Promise { + this.logger('Getting network information') + + this.logger('Creating hidden service') const hiddenService = await this.tor.createNewHiddenService({ targetPort: this.ports.libp2pHiddenService }) + + this.logger('Destroying the hidden service we created') await this.tor.destroyHiddenService(hiddenService.onionAddress.split('.')[0]) // TODO: Do we want to create the PeerId here? It doesn't necessarily have // anything to do with Tor. + this.logger('Getting peer ID') const peerId: PeerId = await PeerId.create() const peerIdJson = peerId.toJSON() this.logger(`Created network for peer ${peerId.toString()}. Address: ${hiddenService.onionAddress}`) @@ -583,8 +626,19 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI await this.libp2pService.createInstance(params) // Libp2p event listeners - this.libp2pService.on(Libp2pEvents.PEER_CONNECTED, (payload: { peers: string[] }) => { + this.libp2pService.on(Libp2pEvents.PEER_CONNECTED, async (payload: { peers: string[] }) => { this.serverIoProvider.io.emit(SocketActionTypes.PEER_CONNECTED, payload) + for (const peer of payload.peers) { + const peerStats: NetworkStats = { + peerId: peer, + connectionTime: 0, + lastSeen: DateTime.utc().toSeconds(), + } + + await this.localDbService.update(LocalDBKeys.PEERS, { + [peer]: peerStats, + }) + } }) this.libp2pService.on(Libp2pEvents.PEER_DISCONNECTED, async (payload: NetworkDataPayload) => { @@ -633,6 +687,11 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI this.tor.on(SocketActionTypes.CONNECTION_PROCESS_INFO, data => { this.serverIoProvider.io.emit(SocketActionTypes.CONNECTION_PROCESS_INFO, data) }) + this.tor.on(SocketActionTypes.REDIAL_PEERS, async data => { + this.logger(`Socket - ${SocketActionTypes.REDIAL_PEERS}`) + const peerInfo = this.libp2pService?.getCurrentPeerInfo() + await this.libp2pService?.redialPeers([...peerInfo.connected, ...peerInfo.dialed]) + }) this.socketService.on(SocketActionTypes.CONNECTION_PROCESS_INFO, data => { this.serverIoProvider.io.emit(SocketActionTypes.CONNECTION_PROCESS_INFO, data) }) @@ -644,9 +703,9 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI // Update Frontend with Initialized Communities if (this.communityId) { this.serverIoProvider.io.emit(SocketActionTypes.COMMUNITY_LAUNCHED, { id: this.communityId }) - console.log('this.libp2pService.dialedPeers', this.libp2pService.dialedPeers) - console.log('this.libp2pService.connectedPeers', this.libp2pService.connectedPeers) - console.log('this.libp2pservice', this.libp2pService) + this.logger('this.libp2pService.connectedPeers', this.libp2pService.connectedPeers) + this.logger('this.libp2pservice', this.libp2pService) + this.logger('this.libp2pService.dialedPeers', this.libp2pService.dialedPeers) this.serverIoProvider.io.emit( SocketActionTypes.CONNECTED_PEERS, Array.from(this.libp2pService.connectedPeers.keys()) @@ -679,8 +738,10 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI callback(await this.joinCommunity(args)) } ) - this.socketService.on(SocketActionTypes.LEAVE_COMMUNITY, async () => { - await this.leaveCommunity() + + this.socketService.on(SocketActionTypes.LEAVE_COMMUNITY, async (callback: (closed: boolean) => void) => { + this.logger(`socketService - ${SocketActionTypes.LEAVE_COMMUNITY}`) + callback(await this.leaveCommunity()) }) // Username registration diff --git a/packages/backend/src/nest/libp2p/libp2p.service.ts b/packages/backend/src/nest/libp2p/libp2p.service.ts index 00a3763f8a..dcec245cd4 100644 --- a/packages/backend/src/nest/libp2p/libp2p.service.ts +++ b/packages/backend/src/nest/libp2p/libp2p.service.ts @@ -22,6 +22,7 @@ import { webSockets } from '../websocketOverTor' import { all } from '../websocketOverTor/filters' import { Libp2pConnectedPeer, Libp2pEvents, Libp2pNodeParams, Libp2pPeerInfo } from './libp2p.types' import { ProcessInChunksService } from './process-in-chunks.service' +import { peerIdFromString } from '@libp2p/peer-id' const KEY_LENGTH = 32 export const LIBP2P_PSK_METADATA = '/key/swarm/psk/1.0.0/\n/base16/\n' @@ -42,6 +43,7 @@ export class Libp2pService extends EventEmitter { private dialPeer = async (peerAddress: string) => { if (this.dialedPeers.has(peerAddress)) { + this.logger(`Skipping dial of ${peerAddress} because its already been dialed`) return } this.dialedPeers.add(peerAddress) @@ -94,13 +96,27 @@ export class Libp2pService extends EventEmitter { for (const peer of peers) { await this.hangUpPeer(peer) } + this.logger('All peers hung up') } public async hangUpPeer(peerAddress: string) { this.logger('Hanging up on peer', peerAddress) - await this.libp2pInstance?.hangUp(multiaddr(peerAddress)) + try { + const ma = multiaddr(peerAddress) + const peerId = peerIdFromString(ma.getPeerId()!) + + this.logger('Hanging up connection on libp2p') + await this.libp2pInstance?.hangUp(ma) + + this.logger('Removing peer from peer store') + await this.libp2pInstance?.peerStore.delete(peerId as any) + } catch (e) { + this.logger.error(e) + } + this.logger('Clearing local data') this.dialedPeers.delete(peerAddress) this.connectedPeers.delete(peerAddress) + this.logger('Done hanging up') } /** @@ -108,11 +124,9 @@ export class Libp2pService extends EventEmitter { * iOS where Tor receives a new port when the app resumes from background and * we want to close/re-open connections. */ - public async redialPeers(peerInfo?: Libp2pPeerInfo) { - const dialed = peerInfo ? peerInfo.dialed : Array.from(this.dialedPeers) - const toDial = peerInfo - ? [...peerInfo.connected, ...peerInfo.dialed] - : [...this.connectedPeers.keys(), ...this.dialedPeers] + public async redialPeers(peersToDial?: string[]) { + const dialed = peersToDial ?? Array.from(this.dialedPeers) + const toDial = peersToDial ?? [...this.connectedPeers.keys(), ...this.dialedPeers] if (dialed.length === 0) { this.logger('No peers to redial!') @@ -122,9 +136,7 @@ export class Libp2pService extends EventEmitter { this.logger(`Re-dialing ${dialed.length} peers`) // TODO: Sort peers - for (const peerAddress of dialed) { - await this.hangUpPeer(peerAddress) - } + await this.hangUpPeers(dialed) this.processInChunksService.updateData(toDial) await this.processInChunksService.process() @@ -142,7 +154,7 @@ export class Libp2pService extends EventEmitter { start: false, connectionManager: { minConnections: 3, // TODO: increase? - maxConnections: 8, // TODO: increase? + maxConnections: 20, // TODO: increase? dialTimeout: 120_000, maxParallelDials: 10, autoDial: true, // It's a default but let's set it to have explicit information diff --git a/packages/backend/src/nest/socket/socket.service.ts b/packages/backend/src/nest/socket/socket.service.ts index e0df314509..d1dec83f71 100644 --- a/packages/backend/src/nest/socket/socket.service.ts +++ b/packages/backend/src/nest/socket/socket.service.ts @@ -175,9 +175,9 @@ export class SocketService extends EventEmitter implements OnModuleInit { } ) - socket.on(SocketActionTypes.LEAVE_COMMUNITY, async () => { + socket.on(SocketActionTypes.LEAVE_COMMUNITY, async (callback: (closed: boolean) => void) => { this.logger('Leaving community') - this.emit(SocketActionTypes.LEAVE_COMMUNITY) + this.emit(SocketActionTypes.LEAVE_COMMUNITY, callback) }) socket.on(SocketActionTypes.LIBP2P_PSK_STORED, payload => { diff --git a/packages/backend/src/nest/storage/certificates/certificates.store.ts b/packages/backend/src/nest/storage/certificates/certificates.store.ts index 6341d51d0e..c262e96a07 100644 --- a/packages/backend/src/nest/storage/certificates/certificates.store.ts +++ b/packages/backend/src/nest/storage/certificates/certificates.store.ts @@ -41,6 +41,7 @@ export class CertificatesStore extends EventEmitter { write: ['*'], }, }) + await this.store.load() this.store.events.on('ready', async () => { this.logger('Loaded certificates to memory') @@ -58,8 +59,8 @@ export class CertificatesStore extends EventEmitter { await this.loadedCertificates() }) - // @ts-expect-error - OrbitDB's type declaration of `load` lacks 'options' - await this.store.load({ fetchEntryTimeout: 15000 }) + // // @ts-expect-error - OrbitDB's type declaration of `load` lacks 'options' + // await this.store.load({ fetchEntryTimeout: 15000 }) this.logger('Initialized') } @@ -147,7 +148,9 @@ export class CertificatesStore extends EventEmitter { * https://github.com/TryQuiet/quiet/issues/1899 */ public async getCertificates(): Promise { + this.logger('Getting certificates') if (!this.store) { + this.logger('No store found!') return [] } diff --git a/packages/backend/src/nest/tor/tor-control.service.ts b/packages/backend/src/nest/tor/tor-control.service.ts index b83040c7a4..9051994853 100644 --- a/packages/backend/src/nest/tor/tor-control.service.ts +++ b/packages/backend/src/nest/tor/tor-control.service.ts @@ -65,8 +65,7 @@ export class TorControl { this.logger('Tor connected') return } catch (e) { - this.logger(e) - this.logger('Retrying...') + this.logger.error('Retrying due to error...', e) await new Promise(r => setTimeout(r, 500)) } } @@ -76,7 +75,7 @@ export class TorControl { try { this.connection?.end() } catch (e) { - this.logger.error('Disconnect failed:', e.message) + this.logger.error('Disconnect failed:', e) } this.connection = null } @@ -94,6 +93,7 @@ export class TorControl { resolve({ code: 250, messages: dataArray }) } else { clearTimeout(connectionTimeout) + console.error(`TOR CONNECTION ERROR: ${JSON.stringify(dataArray, null, 2)}`) reject(`${dataArray[0]}`) } clearTimeout(connectionTimeout) @@ -104,6 +104,7 @@ export class TorControl { } public async sendCommand(command: string): Promise<{ code: number; messages: string[] }> { + this.logger(`Sending tor command: ${command}`) // Only send one command at a time. if (this.isSending) { this.logger('Tor connection already established, waiting...') @@ -111,7 +112,9 @@ export class TorControl { // Wait for existing command to finish. while (this.isSending) { - await new Promise(r => setTimeout(r, 750)) + const timeout = 750 + this.logger(`Waiting for ${timeout}ms to retry command...`) + await new Promise(r => setTimeout(r, timeout)) } this.isSending = true diff --git a/packages/backend/src/nest/tor/tor.service.ts b/packages/backend/src/nest/tor/tor.service.ts index 030925eb9c..6b739f0572 100644 --- a/packages/backend/src/nest/tor/tor.service.ts +++ b/packages/backend/src/nest/tor/tor.service.ts @@ -21,7 +21,7 @@ export class Tor extends EventEmitter implements OnModuleInit { extraTorProcessParams: TorParams controlPort: number | undefined interval: any - timeout: any + initTimeout: any private readonly logger = Logger(Tor.name) private hiddenServices: Map = new Map() private initializedHiddenServices: Map = new Map() @@ -59,12 +59,23 @@ export class Tor extends EventEmitter implements OnModuleInit { return Array.from(Object.entries(this.extraTorProcessParams)).flat() } + private async isBootstrappingFinished(): Promise { + this.logger('Checking bootstrap status') + const output = await this.torControl.sendCommand('GETINFO status/bootstrap-phase') + if (output.messages[0] === '250-status/bootstrap-phase=NOTICE BOOTSTRAP PROGRESS=100 TAG=done SUMMARY="Done"') { + this.logger('Bootstrapping finished!') + return true + } + return false + } + public async init(timeout = 120_000): Promise { if (!this.socksPort) this.socksPort = await getPort() this.logger('Initializing tor...') return await new Promise((resolve, reject) => { if (!fs.existsSync(this.quietDir)) { + this.logger("Quiet dir doesn't exist, creating it now") fs.mkdirSync(this.quietDir) } @@ -77,9 +88,10 @@ export class Tor extends EventEmitter implements OnModuleInit { this.logger(`${this.torPidPath} exists. Old tor pid: ${oldTorPid}`) } - this.timeout = setTimeout(async () => { - const log = await this.torControl.sendCommand('GETINFO status/bootstrap-phase') - if (log.messages[0] !== '250-status/bootstrap-phase=NOTICE BOOTSTRAP PROGRESS=100 TAG=done SUMMARY="Done"') { + this.initTimeout = setTimeout(async () => { + this.logger('Checking init timeout') + const bootstrapDone = await this.isBootstrappingFinished() + if (!bootstrapDone) { this.initializedHiddenServices = new Map() clearInterval(this.interval) await this.init() @@ -104,12 +116,14 @@ export class Tor extends EventEmitter implements OnModuleInit { await this.spawnTor() this.interval = setInterval(async () => { - const log = await this.torControl.sendCommand('GETINFO status/bootstrap-phase') - if ( - log.messages[0] === '250-status/bootstrap-phase=NOTICE BOOTSTRAP PROGRESS=100 TAG=done SUMMARY="Done"' - ) { + this.logger('Checking bootstrap interval') + const bootstrapDone = await this.isBootstrappingFinished() + if (bootstrapDone) { + this.logger(`Sending ${SocketActionTypes.TOR_INITIALIZED}`) this.serverIoProvider.io.emit(SocketActionTypes.TOR_INITIALIZED) - + // TODO: Figure out how to get redialing (or, ideally, initial dialing) on tor initialization working + // this.logger('Attempting to redial peers (if possible)') + // this.emit(SocketActionTypes.REDIAL_PEERS) clearInterval(this.interval) } }, 2500) @@ -265,8 +279,10 @@ export class Tor extends EventEmitter implements OnModuleInit { this.process.stdout.on('data', (data: any) => { this.logger(data.toString()) - const regexp = /Bootstrapped 0/ - if (regexp.test(data.toString())) { + const bootstrappedRegexp = /Bootstrapped 0/ + // TODO: Figure out if there's a way to get this working in tests + // const bootstrappedRegexp = /Loaded enough directory info to build circuits/ + if (bootstrappedRegexp.test(data.toString())) { this.spawnHiddenServices() resolve() } @@ -279,6 +295,7 @@ export class Tor extends EventEmitter implements OnModuleInit { } public async spawnHiddenServices() { + this.logger(`Spawning hidden service(s) (count: ${this.hiddenServices.size})`) for (const el of this.hiddenServices.values()) { await this.spawnHiddenService(el) } @@ -293,6 +310,7 @@ export class Tor extends EventEmitter implements OnModuleInit { privKey: string virtPort?: number }): Promise { + this.logger(`Spawning Tor hidden service`) const initializedHiddenService = this.initializedHiddenServices.get(privKey) if (initializedHiddenService) { this.logger(`Hidden service already initialized for ${initializedHiddenService.onionAddress}`) @@ -302,6 +320,7 @@ export class Tor extends EventEmitter implements OnModuleInit { `ADD_ONION ${privKey} Flags=Detach Port=${virtPort},127.0.0.1:${targetPort}` ) const onionAddress = status.messages[0].replace('250-ServiceID=', '') + this.logger(`Spawned hidden service with onion address ${onionAddress}`) const hiddenService: HiddenServiceData = { targetPort, privKey, virtPort, onionAddress } this.hiddenServices.set(privKey, hiddenService) @@ -369,7 +388,7 @@ export class Tor extends EventEmitter implements OnModuleInit { resolve() return } - if (this.timeout) clearTimeout(this.timeout) + if (this.initTimeout) clearTimeout(this.initTimeout) if (this.interval) clearInterval(this.interval) this.process?.on('close', () => { this.process = null diff --git a/packages/backend/src/nest/websocketOverTor/index.ts b/packages/backend/src/nest/websocketOverTor/index.ts index e714970e8b..c0b1160214 100644 --- a/packages/backend/src/nest/websocketOverTor/index.ts +++ b/packages/backend/src/nest/websocketOverTor/index.ts @@ -84,14 +84,14 @@ export class WebSockets extends EventEmitter { signal: options.signal, }) } catch (e) { - log.error('error connecting to %s. Details: %s', ma, e.message) + log.error('error connecting to %s. Details: %s', ma, e) throw e } try { maConn = socketToMaConn(socket, ma, { signal: options.signal }) log('new outbound connection %s', maConn.remoteAddr) } catch (e) { - log.error('error creating new outbound connection %s. Details: %s', ma, e.message) + log.error('error creating new outbound connection %s. Details: %s', ma, e) throw e } @@ -100,7 +100,7 @@ export class WebSockets extends EventEmitter { log('outbound connection %s upgraded', maConn.remoteAddr) return conn } catch (e) { - log.error('error upgrading outbound connection %s. Details: %s', maConn.remoteAddr, e.message) + log.error('error upgrading outbound connection %s. Details: %s', maConn.remoteAddr, e) throw e } } @@ -114,7 +114,7 @@ export class WebSockets extends EventEmitter { const errorPromise = pDefer() const errfn = (event: ErrorEvent) => { - log.error(`connection error: ${event.message}`) + log.error(`connection error`, event) errorPromise.reject(event) } @@ -229,7 +229,10 @@ export class WebSockets extends EventEmitter { listener.emit('connection', conn) }) .on('listening', () => listener.emit('listening')) - .on('error', err => listener.emit('error', err)) + .on('error', err => { + log.error(`Websocket error`, err) + listener.emit('error', err) + }) .on('close', () => listener.emit('close')) // Keep track of open connections to destroy in case of timeout diff --git a/packages/mobile/src/components/Input/Input.styles.ts b/packages/mobile/src/components/Input/Input.styles.ts index 08ad3f9b62..8adaeea25d 100644 --- a/packages/mobile/src/components/Input/Input.styles.ts +++ b/packages/mobile/src/components/Input/Input.styles.ts @@ -8,7 +8,7 @@ export const StyledTextInput = styled(TextInput)<{ }>` ${({ height, multiline }) => css` text-align-vertical: center; - height: ${Math.max(40, height)}; + height: ${Math.max(40, height)}px; ${Platform.select({ ios: { paddingTop: 12, diff --git a/packages/mobile/src/store/init/startConnection/startConnection.saga.ts b/packages/mobile/src/store/init/startConnection/startConnection.saga.ts index 4cc55f53f9..44a48cfbc6 100644 --- a/packages/mobile/src/store/init/startConnection/startConnection.saga.ts +++ b/packages/mobile/src/store/init/startConnection/startConnection.saga.ts @@ -1,5 +1,5 @@ import { io } from 'socket.io-client' -import { select, put, call, cancel, fork, takeEvery, FixedTask, delay, apply } from 'typed-redux-saga' +import { select, put, call, cancel, fork, takeEvery, FixedTask, delay, apply, putResolve } from 'typed-redux-saga' import { PayloadAction } from '@reduxjs/toolkit' import { socket as stateManager, Socket } from '@quiet/state-manager' import { encodeSecret } from '@quiet/common' @@ -91,5 +91,5 @@ function subscribeSocketLifecycle(socket: Socket, socketIOData: WebsocketConnect function* cancelRootTaskSaga(task: FixedTask): Generator { console.warn('Canceling root task', task.error()) yield* cancel(task) - yield* put(initActions.canceledRootTask()) + yield* putResolve(initActions.canceledRootTask()) } diff --git a/packages/mobile/src/store/nativeServices/leaveCommunity/leaveCommunity.saga.ts b/packages/mobile/src/store/nativeServices/leaveCommunity/leaveCommunity.saga.ts index 33df360946..56ec0c77f7 100644 --- a/packages/mobile/src/store/nativeServices/leaveCommunity/leaveCommunity.saga.ts +++ b/packages/mobile/src/store/nativeServices/leaveCommunity/leaveCommunity.saga.ts @@ -1,4 +1,4 @@ -import { select, call, put, takeLeading } from 'typed-redux-saga' +import { select, call, takeLeading, putResolve } from 'typed-redux-saga' import { app } from '@quiet/state-manager' import { persistor } from '../../store' import { nativeServicesActions } from '../nativeServices.slice' @@ -11,7 +11,7 @@ export function* leaveCommunitySaga(): Generator { console.log('Leaving community') // Restart backend - yield* put(app.actions.closeServices()) + yield* putResolve(app.actions.closeServices()) yield takeLeading(initActions.canceledRootTask.type, clearReduxStore) } @@ -23,18 +23,25 @@ export function* clearReduxStore(): Generator { console.info('Clearing redux store') // Stop persistor + console.info('Pausing persistor') yield* call(persistor.pause) + console.info('Flushing persistor') yield* call(persistor.flush) + console.info('Purging persistor') yield* call(persistor.purge) // Clear redux store - yield* put(nativeServicesActions.resetApp()) + console.info('Resetting app') + yield* putResolve(nativeServicesActions.resetApp()) // Resume persistor + console.info('Resuming persistor') yield* call(persistor.persist) // Restarting persistor doesn't mark store as ready automatically - yield* put(initActions.setStoreReady()) + console.info('Set store ready') + yield* putResolve(initActions.setStoreReady()) - yield* put(navigationActions.replaceScreen({ screen: ScreenNames.JoinCommunityScreen })) + console.info('Opening join community screen') + yield* putResolve(navigationActions.replaceScreen({ screen: ScreenNames.JoinCommunityScreen })) } diff --git a/packages/state-manager/src/sagas/app/closeServices.saga.ts b/packages/state-manager/src/sagas/app/closeServices.saga.ts index aed87f384d..0557a247b9 100644 --- a/packages/state-manager/src/sagas/app/closeServices.saga.ts +++ b/packages/state-manager/src/sagas/app/closeServices.saga.ts @@ -8,5 +8,5 @@ export function* closeServicesSaga( socket: Socket, _action: PayloadAction['payload']> ): Generator { - yield* apply(socket, socket.emit, [SocketActionTypes.LEAVE_COMMUNITY]) + yield* apply(socket, socket.emitWithAck, [SocketActionTypes.LEAVE_COMMUNITY]) } diff --git a/packages/state-manager/src/sagas/communities/createCommunity/createCommunity.saga.ts b/packages/state-manager/src/sagas/communities/createCommunity/createCommunity.saga.ts index 5b9fc25fff..072a4d24b1 100644 --- a/packages/state-manager/src/sagas/communities/createCommunity/createCommunity.saga.ts +++ b/packages/state-manager/src/sagas/communities/createCommunity/createCommunity.saga.ts @@ -1,5 +1,5 @@ import { type Socket, applyEmitParams } from '../../../types' -import { select, apply, put } from 'typed-redux-saga' +import { select, apply, putResolve } from 'typed-redux-saga' import { type PayloadAction } from '@reduxjs/toolkit' import { identityActions } from '../../identity/identity.slice' import { communitiesSelectors } from '../communities.selectors' @@ -53,21 +53,21 @@ export function* createCommunitySaga( return } - yield* put(communitiesActions.updateCommunityData(createdCommunity)) + yield* putResolve(communitiesActions.updateCommunityData(createdCommunity)) - yield* put( + yield* putResolve( identityActions.storeUserCertificate({ communityId: createdCommunity.id, userCertificate: createdCommunity.ownerCertificate, }) ) - yield* put(publicChannelsActions.createGeneralChannel()) + yield* putResolve(publicChannelsActions.createGeneralChannel()) // TODO: We can likely refactor this a bit. Currently, we issue the owner's // certificate before creating the community, but then we add the owner's CSR // to the OrbitDB store after creating the community (in the following saga). // We can likely add the owner's CSR when creating the community or decouple // community creation from CSR/certificate creation and create the community // first and then add the owner's CSR and issue their certificate. - yield* put(identityActions.saveUserCsr()) + yield* putResolve(identityActions.saveUserCsr()) } diff --git a/packages/state-manager/src/sagas/publicChannels/channelsReplicated/channelsReplicated.saga.test.ts b/packages/state-manager/src/sagas/publicChannels/channelsReplicated/channelsReplicated.saga.test.ts index a28df7e920..548d6600e0 100644 --- a/packages/state-manager/src/sagas/publicChannels/channelsReplicated/channelsReplicated.saga.test.ts +++ b/packages/state-manager/src/sagas/publicChannels/channelsReplicated/channelsReplicated.saga.test.ts @@ -87,7 +87,7 @@ describe('channelsReplicatedSaga', () => { ) .withReducer(reducer) .withState(store.getState()) - .put( + .putResolve( publicChannelsActions.addChannel({ channel: sailingChannel, }) @@ -108,12 +108,12 @@ describe('channelsReplicatedSaga', () => { ) .withReducer(reducer) .withState(store.getState()) - .not.put( + .not.putResolve( publicChannelsActions.addChannel({ channel: generalChannel, }) ) - .put( + .putResolve( publicChannelsActions.addChannel({ channel: sailingChannel, }) @@ -134,12 +134,12 @@ describe('channelsReplicatedSaga', () => { ) .withReducer(reducer) .withState(store.getState()) - .put( + .putResolve( publicChannelsActions.addChannel({ channel: sailingChannel, }) ) - .put( + .putResolve( messagesActions.addPublicChannelsMessagesBase({ channelId: sailingChannel.id, }) @@ -160,22 +160,22 @@ describe('channelsReplicatedSaga', () => { ) .withReducer(reducer) .withState(store.getState()) - .put( + .putResolve( publicChannelsActions.addChannel({ channel: sailingChannel, }) ) - .put( + .putResolve( messagesActions.addPublicChannelsMessagesBase({ channelId: sailingChannel.id, }) ) - .not.put( + .not.putResolve( publicChannelsActions.addChannel({ channel: generalChannel, }) ) - .not.put( + .not.putResolve( messagesActions.addPublicChannelsMessagesBase({ channelId: generalChannel.id, }) @@ -208,7 +208,7 @@ describe('channelsReplicatedSaga', () => { ) .withReducer(reducer) .withState(store.getState()) - .put(messages.actions.resetCurrentPublicChannelCache()) + .putResolve(messages.actions.resetCurrentPublicChannelCache()) .run() }) @@ -230,7 +230,7 @@ describe('channelsReplicatedSaga', () => { ) .withReducer(reducer) .withState(store.getState()) - .not.put(messages.actions.resetCurrentPublicChannelCache()) + .not.putResolve(messages.actions.resetCurrentPublicChannelCache()) .run() }) @@ -249,9 +249,9 @@ describe('channelsReplicatedSaga', () => { ) .withReducer(reducer) .withState(store.getState()) - .put(publicChannelsActions.deleteChannel({ channelId: photoChannel.id })) + .putResolve(publicChannelsActions.deleteChannel({ channelId: photoChannel.id })) .dispatch(publicChannelsActions.completeChannelDeletion({})) - .put( + .putResolve( publicChannelsActions.addChannel({ channel: sailingChannel, }) @@ -269,7 +269,7 @@ describe('channelsReplicatedSaga', () => { ) .withReducer(reducer) .withState(store.getState()) - .not.put(publicChannelsActions.deleteChannel({ channelId: generalChannel.id })) + .not.putResolve(publicChannelsActions.deleteChannel({ channelId: generalChannel.id })) .run() }) }) diff --git a/packages/state-manager/src/sagas/publicChannels/channelsReplicated/channelsReplicated.saga.ts b/packages/state-manager/src/sagas/publicChannels/channelsReplicated/channelsReplicated.saga.ts index ebf15f8f16..7bdd971b07 100644 --- a/packages/state-manager/src/sagas/publicChannels/channelsReplicated/channelsReplicated.saga.ts +++ b/packages/state-manager/src/sagas/publicChannels/channelsReplicated/channelsReplicated.saga.ts @@ -1,5 +1,5 @@ import { type PayloadAction } from '@reduxjs/toolkit' -import { select, put, take } from 'typed-redux-saga' +import { select, put, take, putResolve } from 'typed-redux-saga' import { publicChannelsSelectors } from '../publicChannels.selectors' import { publicChannelsActions } from '../publicChannels.slice' import { messagesSelectors } from '../../messages/messages.selectors' @@ -30,12 +30,12 @@ export function* channelsReplicatedSaga( if (!locallyStoredChannels.includes(channel.id)) { // TODO: Refactor to use QuietLogger log(`Adding #${channel.name} to store`) - yield* put( + yield* putResolve( publicChannelsActions.addChannel({ channel, }) ) - yield* put( + yield* putResolve( messagesActions.addPublicChannelsMessagesBase({ channelId: channel.id, }) @@ -49,7 +49,7 @@ export function* channelsReplicatedSaga( if (!databaseStoredChannelsIds.includes(channelId)) { // TODO: Refactor to use QuietLogger log(`Removing #${channelId} from store`) - yield* put(publicChannelsActions.deleteChannel({ channelId })) + yield* putResolve(publicChannelsActions.deleteChannel({ channelId })) yield* take(publicChannelsActions.completeChannelDeletion) } } @@ -60,12 +60,12 @@ export function* channelsReplicatedSaga( // (On collecting data from persist) Populating displayable data if (currentChannelCache.length < 1 && currentChannelRepository.length > 0) { - yield* put(messagesActions.resetCurrentPublicChannelCache()) + yield* putResolve(messagesActions.resetCurrentPublicChannelCache()) } const community = yield* select(communitiesSelectors.currentCommunity) if (!community?.CA && databaseStoredChannels.find(channel => channel.name === 'general')) { - yield* put(publicChannelsActions.sendIntroductionMessage()) + yield* putResolve(publicChannelsActions.sendIntroductionMessage()) } } diff --git a/packages/types/src/socket.ts b/packages/types/src/socket.ts index c74ba5dd14..4fcb44cfdb 100644 --- a/packages/types/src/socket.ts +++ b/packages/types/src/socket.ts @@ -71,6 +71,7 @@ export enum SocketActionTypes { PEER_CONNECTED = 'peerConnected', PEER_DISCONNECTED = 'peerDisconnected', TOR_INITIALIZED = 'torInitialized', + REDIAL_PEERS = 'redialPeers', // ====== Misc ======