diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 84e6c79ab5..13bdf03218 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -30,7 +30,7 @@ jobs: env: TEST_MODE: ${{ github.event.action == 'prereleased' }} - S3_BUCKET: ${{ github.event.action == 'released' && 'quiet' || 'test.quiet' }} + S3_BUCKET: ${{ github.event.action == 'released' && 'quiet.2.x' || 'test.quiet' }} CHECKSUM_PATH: ${{ github.event.action == 'released' && 'packages/desktop/dist/latest-linux.yml' || 'packages/desktop/dist/alpha-linux.yml' }} steps: @@ -102,7 +102,7 @@ jobs: env: TEST_MODE: ${{ github.event.action == 'prereleased' }} - S3_BUCKET: ${{ github.event.action == 'released' && 'quiet' || 'test.quiet' }} + S3_BUCKET: ${{ github.event.action == 'released' && 'quiet.2.x' || 'test.quiet' }} steps: - uses: actions/checkout@v4 @@ -138,8 +138,9 @@ jobs: GH_TOKEN: ${{ secrets.GH_TOKEN }} CSC_KEY_PASSWORD: ${{ secrets.MAC_CSC_KEY_PASSWORD }} CSC_LINK: ${{ secrets.MAC_CSC_LINK }} - APPLEID: ${{ secrets.APPLE_ID }} - APPLEIDPASS: ${{ secrets.APPLE_ID_PASS }} + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_ID_PASS: ${{ secrets.APPLE_ID_PASS }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} USE_HARD_LINKS: false @@ -175,7 +176,7 @@ jobs: env: TEST_MODE: ${{ github.event.action == 'prereleased' }} - S3_BUCKET: ${{ github.event.action == 'released' && 'quiet' || 'test.quiet' }} + S3_BUCKET: ${{ github.event.action == 'released' && 'quiet.2.x' || 'test.quiet' }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/e2e-linux.yml b/.github/workflows/e2e-linux.yml index 2ed3a4995e..45dada8bbc 100644 --- a/.github/workflows/e2e-linux.yml +++ b/.github/workflows/e2e-linux.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: - os: [ubuntu-latest-m] + os: [ubuntu-20.04, ubuntu-22.04] timeout-minutes: 180 diff --git a/.github/workflows/e2e-win.yml b/.github/workflows/e2e-win.yml index 9f614ca00f..ddff1a275a 100644 --- a/.github/workflows/e2e-win.yml +++ b/.github/workflows/e2e-win.yml @@ -3,7 +3,7 @@ name: E2E Windows on: [workflow_call] jobs: windows: - runs-on: windows-latest-l + runs-on: windows-2019 timeout-minutes: 180 @@ -66,7 +66,7 @@ jobs: run: Start-Process "Quiet Setup ${{ steps.extract_version.outputs.version }}.exe" -Wait working-directory: ./packages/desktop/dist shell: powershell - + - name: Check if Quiet installed properly run: Get-ChildItem -Path C:\Users\runneradmin\AppData\Local\Programs\@quietdesktop shell: powershell diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fab8d401e..2b3209af66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +[unreleased] + +* Move csrs to separate store. + +* Fix saveUserCsr saga to trigger only if user csr is absent in user slice. + +* Send an info message immediately after a user joins the community + +* Feature: add functionality to export chat to text document in desktop version + +[2.0.3-alpha.6] + +* Fix: filter out invalid peer addresses in peer list. Update peer list in localdb. + +* Fix: dial new peers on CSRs replication + [2.0.3-alpha.5] * Fix network data proceeding when using custom protocol multiple times #1847 diff --git a/README.md b/README.md index d0c76e8899..f3962e55ad 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Encrypted p2p team chat with no servers, just Tor.
- Downloads | + Downloads | How it Works | Features | Threat Model | @@ -52,7 +52,7 @@ See our [FAQ](https://github.com/TryQuiet/monorepo/wiki/Quiet-FAQ) for answers t ## Getting started -To try Quiet, download the [latest release](https://github.com/TryQuiet/quiet/releases/tag/quiet%401.9.6) for your platform (.dmg for macOS, .exe for Windows, etc.) and install it in the normal way. Then create a community and open the community's settings to invite members. +To try Quiet, download the [latest release](https://github.com/TryQuiet/quiet/releases/tag/quiet%401.9.7) for your platform (.dmg for macOS, .exe for Windows, etc.) and install it in the normal way. Then create a community and open the community's settings to invite members. If you'd like to help develop Quiet, see [Contributing to Quiet](#contributing-to-quiet). diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index 850cd66787..8318c9d408 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -193,6 +193,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline # [1.10.0-alpha.0](https://github.com/TryQuiet/backend/compare/@quiet/backend@1.9.0...@quiet/backend@1.10.0-alpha.0) (2023-08-29) +## [1.9.5](https://github.com/TryQuiet/backend/compare/@quiet/backend@1.9.4...@quiet/backend@1.9.5) (2023-11-09) **Note:** Version bump only for package @quiet/backend diff --git a/packages/backend/src/backendManager.ts b/packages/backend/src/backendManager.ts index f7e80b5e4e..b0abe6066e 100644 --- a/packages/backend/src/backendManager.ts +++ b/packages/backend/src/backendManager.ts @@ -8,8 +8,10 @@ import { ConnectionsManagerService } from './nest/connections-manager/connection import { TorControl } from './nest/tor/tor-control.service' import { torBinForPlatform, torDirForPlatform } from './nest/common/utils' import initRnBridge from './rn-bridge' - +import { INestApplicationContext } from '@nestjs/common' import logger from './nest/common/logger' +import { OpenServices, validateOptions } from './options' + const log = logger('backendManager') const program = new Command() @@ -25,21 +27,13 @@ program .option('-a, --appDataPath ', 'Path of application data directory') .option('-d, --socketIOPort ', 'Socket io data server port') .option('-r, --resourcesPath ', 'Application resources path') + .option('-scrt, --socketIOSecret ', 'socketIO secret') program.parse(process.argv) const options = program.opts() console.log('options', options) -interface OpenServices { - torControlPort?: any - socketIOPort?: any - httpTunnelPort?: any - authCookie?: any -} - -import { INestApplicationContext } from '@nestjs/common' - export const runBackendDesktop = async () => { const isDev = process.env.NODE_ENV === 'development' @@ -48,11 +42,14 @@ export const runBackendDesktop = async () => { // @ts-ignore global.crypto = webcrypto + validateOptions(options) + const resourcesPath = isDev ? null : options.resourcesPath.trim() const app = await NestFactory.createApplicationContext( AppModule.forOptions({ socketIOPort: options.socketIOPort, + socketIOSecret: options.socketIOSecret, torBinaryPath: torBinForPlatform(resourcesPath), torResourcesPath: torDirForPlatform(resourcesPath), torControlPort: await getPort(), @@ -87,7 +84,7 @@ export const runBackendDesktop = async () => { }) } -export const runBackendMobile = async (): Promise => { +export const runBackendMobile = async () => { // Enable triggering push notifications process.env['BACKEND'] = 'mobile' process.env['CONNECTION_TIME'] = (new Date().getTime() / 1000).toString() // Get time in seconds @@ -97,6 +94,7 @@ export const runBackendMobile = async (): Promise => { const app: INestApplicationContext = await NestFactory.createApplicationContext( AppModule.forOptions({ socketIOPort: options.dataPort, + socketIOSecret: options.socketIOSecret, httpTunnelPort: options.httpTunnelPort ? options.httpTunnelPort : null, torAuthCookie: options.authCookie ? options.authCookie : null, torControlPort: options.controlPort ? options.controlPort : await getPort(), diff --git a/packages/backend/src/nest/app.module.ts b/packages/backend/src/nest/app.module.ts index bbc422bbbb..cf2fdc655d 100644 --- a/packages/backend/src/nest/app.module.ts +++ b/packages/backend/src/nest/app.module.ts @@ -32,7 +32,7 @@ import { Server as SocketIO } from 'socket.io' import { StorageModule } from './storage/storage.module' import { IpfsModule } from './ipfs/ipfs.module' import { Level } from 'level' -import { getCors } from './common/utils' +import { verifyToken } from '@quiet/common' @Global() @Module({ @@ -94,10 +94,40 @@ export class AppModule { _app.use(cors()) const server = createServer(_app) const io = new SocketIO(server, { - cors: getCors(), + cors: { + origin: '127.0.0.1', + allowedHeaders: ['authorization'], + credentials: true, + }, pingInterval: 1000_000, pingTimeout: 1000_000, }) + io.engine.use((req, res, next) => { + const authHeader = req.headers['authorization'] + if (!authHeader) { + console.error('No authorization header') + res.writeHead(401, 'No authorization header') + res.end() + return + } + + const token = authHeader && authHeader.split(' ')[1] + if (!token) { + console.error('No auth token') + res.writeHead(401, 'No authorization token') + res.end() + return + } + + if (verifyToken(options.socketIOSecret, token)) { + next() + } else { + console.error('Wrong basic token') + res.writeHead(401, 'Unauthorized') + res.end() + } + }) + return { server, io } }, inject: [EXPRESS_PROVIDER], @@ -122,7 +152,7 @@ export class AppModule { }, { provide: LEVEL_DB, - useFactory: (dbPath: string) => new Level(dbPath, { valueEncoding: 'json' }), + useFactory: (dbPath: string) => new Level(dbPath, { valueEncoding: 'json' }), inject: [DB_PATH], }, ], diff --git a/packages/backend/src/nest/common/utils.ts b/packages/backend/src/nest/common/utils.ts index eeff2ad29c..6b1b445486 100644 --- a/packages/backend/src/nest/common/utils.ts +++ b/packages/backend/src/nest/common/utils.ts @@ -11,8 +11,9 @@ import { type PermsData } from '@quiet/types' import { TestConfig } from '../const' import logger from './logger' import { Libp2pNodeParams } from '../libp2p/libp2p.types' -import { createLibp2pAddress, createLibp2pListenAddress } from '@quiet/common' +import { createLibp2pAddress, createLibp2pListenAddress, isDefined } from '@quiet/common' import { Libp2pService } from '../libp2p/libp2p.service' +import { CertFieldsTypes, getReqFieldValue, loadCSR } from '@quiet/identity' const log = logger('test') @@ -153,6 +154,20 @@ export const getUsersAddresses = async (users: UserData[]): Promise => return await Promise.all(peers) } +export const getLibp2pAddressesFromCsrs = async (csrs: string[]): Promise => { + const addresses = await Promise.all( + csrs.map(async csr => { + const parsedCsr = await loadCSR(csr) + const peerId = getReqFieldValue(parsedCsr, CertFieldsTypes.peerId) + const onionAddress = getReqFieldValue(parsedCsr, CertFieldsTypes.commonName) + if (!peerId || !onionAddress) return + + return createLibp2pAddress(onionAddress, peerId) + }) + ) + return addresses.filter(isDefined) +} + /** * Compares given numbers * diff --git a/packages/backend/src/nest/connections-manager/connections-manager.service.spec.ts b/packages/backend/src/nest/connections-manager/connections-manager.service.spec.ts index 4952b669a6..0736326698 100644 --- a/packages/backend/src/nest/connections-manager/connections-manager.service.spec.ts +++ b/packages/backend/src/nest/connections-manager/connections-manager.service.spec.ts @@ -18,6 +18,7 @@ import { RegistrationService } from '../registration/registration.service' import { SocketModule } from '../socket/socket.module' import { ConnectionsManagerModule } from './connections-manager.module' import { ConnectionsManagerService } from './connections-manager.service' +import { createLibp2pAddress } from '@quiet/common' describe('ConnectionsManagerService', () => { let module: TestingModule @@ -101,7 +102,12 @@ describe('ConnectionsManagerService', () => { key: userIdentity.userCsr?.userKey, CA: [communityRootCa], }, - peers: community.peerList, + peers: [ + createLibp2pAddress( + 'y7yczmugl2tekami7sbdz5pfaemvx7bahwthrdvcbzw5vex2crsr26qd', + 'QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSE' + ), + ], } await localDbService.put(LocalDBKeys.COMMUNITY, launchCommunityPayload) @@ -113,41 +119,6 @@ describe('ConnectionsManagerService', () => { expect(launchCommunitySpy).toHaveBeenCalledWith(launchCommunityPayload) }) - it('launches community on init if their data exists in local db', async () => { - const launchCommunityPayload: InitCommunityPayload = { - id: community.id, - peerId: userIdentity.peerId, - hiddenService: userIdentity.hiddenService, - certs: { - // @ts-expect-error - certificate: userIdentity.userCertificate, - // @ts-expect-error - key: userIdentity.userCsr?.userKey, - CA: [communityRootCa], - }, - peers: community.peerList, - } - - await localDbService.put(LocalDBKeys.COMMUNITY, launchCommunityPayload) - - const peerAddress = '/dns4/test.onion/tcp/80/ws/p2p/peerid' - await localDbService.put(LocalDBKeys.PEERS, { - [peerAddress]: { - peerId: 'QmaEvCkpUG7GxhgvMkk8wxurfi1ehjHhSUNRksWTmXN2ix', - connectionTime: 50, - lastSeen: 1000, - }, - }) - - await connectionsManagerService.closeAllServices() - - const launchCommunitySpy = jest.spyOn(connectionsManagerService, 'launchCommunity').mockResolvedValue() - - await connectionsManagerService.init() - - expect(launchCommunitySpy).toHaveBeenCalledWith(Object.assign(launchCommunityPayload, { peers: [peerAddress] })) - }) - it('does not launch community on init if its data does not exist in local db', async () => { await connectionsManagerService.closeAllServices() await connectionsManagerService.init() @@ -200,10 +171,10 @@ describe('ConnectionsManagerService', () => { // await connectionsManager.init() await localDbService.put(LocalDBKeys.COMMUNITY, launchCommunityPayload) - const peerAddress = '/dns4/test.onion/tcp/80/ws/p2p/peerid' + const peerid = 'QmaEvCkpUG7GxhgvMkk8wxurfi1ehjHhSUNRksWTmXN2ix' await localDbService.put(LocalDBKeys.PEERS, { - [peerAddress]: { - peerId: 'QmaEvCkpUG7GxhgvMkk8wxurfi1ehjHhSUNRksWTmXN2ix', + [peerid]: { + peerId: peerid, connectionTime: 50, lastSeen: 1000, }, 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 0c64741b26..5784f38fc3 100644 --- a/packages/backend/src/nest/connections-manager/connections-manager.service.ts +++ b/packages/backend/src/nest/connections-manager/connections-manager.service.ts @@ -8,7 +8,7 @@ import { setEngine, CryptoEngine } from 'pkijs' import { EventEmitter } from 'events' import getPort from 'get-port' import PeerId from 'peer-id' -import { removeFilesFromDir } from '../common/utils' +import { getLibp2pAddressesFromCsrs, removeFilesFromDir } from '../common/utils' import { AskForMessagesPayload, @@ -157,9 +157,10 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI this.logger('launchCommunityFromStorage') const community: InitCommunityPayload = await this.localDbService.get(LocalDBKeys.COMMUNITY) - this.logger('launchCommunityFromStorage - community:', community?.id) + this.logger('launchCommunityFromStorage - community peers', community?.peers) if (community) { const sortedPeers = await this.localDbService.getSortedPeers(community.peers) + this.logger('launchCommunityFromStorage - sorted peers', sortedPeers) if (sortedPeers.length > 0) { community.peers = sortedPeers } @@ -191,9 +192,9 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI this.logger('Closing local storage') await this.localDbService.close() } - if (this.libp2pService?.libp2pInstance) { + if (this.libp2pService) { this.logger('Stopping libp2p') - await this.libp2pService.libp2pInstance.stop() + await this.libp2pService.close() } } @@ -208,22 +209,24 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI public async leaveCommunity() { this.tor.resetHiddenServices() - this.serverIoProvider.io.close() + this.closeSocket() await this.localDbService.purge() await this.closeAllServices({ saveTor: true }) await this.purgeData() + await this.resetState() + await this.localDbService.open() + await this.socketService.init() + } + + async resetState() { this.communityId = '' this.ports = { ...this.ports, libp2pHiddenService: await getPort() } - this.libp2pService.libp2pInstance = null - this.libp2pService.connectedPeers = new Map() this.communityState = ServiceState.DEFAULT this.registrarState = ServiceState.DEFAULT - await this.localDbService.open() - await this.socketService.init() } public async purgeData() { - console.log('removing data') + this.logger('Purging community data') const dirsToRemove = fs .readdirSync(this.quietDir) .filter( @@ -601,7 +604,8 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI this.storageService.on( StorageEvents.REPLICATED_CSR, async (payload: { csrs: string[]; certificates: string[]; id: string }) => { - console.log(`Storage - ${StorageEvents.REPLICATED_CSR}`) + this.logger(`Storage - ${StorageEvents.REPLICATED_CSR}`) + this.libp2pService.emit(Libp2pEvents.DIAL_PEERS, await getLibp2pAddressesFromCsrs(payload.csrs)) this.serverIoProvider.io.emit(SocketActionTypes.RESPONSE_GET_CSRS, { csrs: payload.csrs }) this.registrationService.emit(RegistrationEvents.REGISTER_USER_CERTIFICATE, payload) } diff --git a/packages/backend/src/nest/const.ts b/packages/backend/src/nest/const.ts index f4c86c3a35..80cd62656c 100644 --- a/packages/backend/src/nest/const.ts +++ b/packages/backend/src/nest/const.ts @@ -28,6 +28,8 @@ export const IPFS_REPO_PATCH = 'ipfsRepoPath' export const CONFIG_OPTIONS = 'configOptions' export const SERVER_IO_PROVIDER = 'serverIoProvider' +export const PROCESS_IN_CHUNKS_PROVIDER = 'processInChunksProvider' + export const EXPRESS_PROVIDER = 'expressProvider' export const LEVEL_DB = 'levelDb' diff --git a/packages/backend/src/nest/libp2p/libp2p.module.ts b/packages/backend/src/nest/libp2p/libp2p.module.ts index bdc20e965c..d75edc799e 100644 --- a/packages/backend/src/nest/libp2p/libp2p.module.ts +++ b/packages/backend/src/nest/libp2p/libp2p.module.ts @@ -1,10 +1,11 @@ import { Module } from '@nestjs/common' import { SocketModule } from '../socket/socket.module' import { Libp2pService } from './libp2p.service' +import { ProcessInChunksService } from './process-in-chunks.service' @Module({ imports: [SocketModule], - providers: [Libp2pService], + providers: [Libp2pService, ProcessInChunksService], exports: [Libp2pService], }) export class Libp2pModule {} diff --git a/packages/backend/src/nest/libp2p/libp2p.service.spec.ts b/packages/backend/src/nest/libp2p/libp2p.service.spec.ts index 5fc4e370aa..13ebb5f1ed 100644 --- a/packages/backend/src/nest/libp2p/libp2p.service.spec.ts +++ b/packages/backend/src/nest/libp2p/libp2p.service.spec.ts @@ -1,16 +1,20 @@ +import { jest } from '@jest/globals' import { Test, TestingModule } from '@nestjs/testing' import { TestModule } from '../common/test.module' -import { libp2pInstanceParams } from '../common/utils' +import { createPeerId, libp2pInstanceParams } from '../common/utils' import { Libp2pModule } from './libp2p.module' import { LIBP2P_PSK_METADATA, Libp2pService } from './libp2p.service' -import { Libp2pNodeParams } from './libp2p.types' +import { Libp2pEvents, Libp2pNodeParams } from './libp2p.types' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import validator from 'validator' +import waitForExpect from 'wait-for-expect' +import { ProcessInChunksService } from './process-in-chunks.service' describe('Libp2pService', () => { let module: TestingModule let libp2pService: Libp2pService let params: Libp2pNodeParams + let processInChunks: ProcessInChunksService beforeAll(async () => { module = await Test.createTestingModule({ @@ -18,6 +22,7 @@ describe('Libp2pService', () => { }).compile() libp2pService = await module.resolve(Libp2pService) + processInChunks = await module.resolve(ProcessInChunksService) params = await libp2pInstanceParams() }) @@ -32,13 +37,13 @@ describe('Libp2pService', () => { expect(libp2pService?.libp2pInstance?.peerId).toBe(params.peerId) }) - it('destory instance libp2p', async () => { + it('close libp2p service', async () => { await libp2pService.createInstance(params) - await libp2pService.destroyInstance() + await libp2pService.close() expect(libp2pService.libp2pInstance).toBeNull() }) - it('creates libp2p address with proper ws type (%s)', async () => { + it('creates libp2p address', async () => { const libp2pAddress = libp2pService.createLibp2pAddress(params.localAddress, params.peerId.toString()) expect(libp2pAddress).toStrictEqual(`/dns4/${params.localAddress}.onion/tcp/80/ws/p2p/${params.peerId.toString()}`) }) @@ -58,4 +63,40 @@ describe('Libp2pService', () => { const expectedFullKeyString = LIBP2P_PSK_METADATA + uint8ArrayToString(generatedPskBuffer, 'base16') expect(uint8ArrayToString(generatedKey.fullKey)).toEqual(expectedFullKeyString) }) + + it(`Starts dialing peers on '${Libp2pEvents.DIAL_PEERS}' event`, async () => { + const peerId1 = await createPeerId() + const peerId2 = await createPeerId() + const addresses = [ + libp2pService.createLibp2pAddress('onionAddress1.onion', peerId1.toString()), + libp2pService.createLibp2pAddress('onionAddress2.onion', peerId2.toString()), + ] + await libp2pService.createInstance(params) + // @ts-expect-error processItem is private + const spyOnProcessItem = jest.spyOn(processInChunks, 'processItem') + expect(libp2pService.libp2pInstance).not.toBeNull() + libp2pService.emit(Libp2pEvents.DIAL_PEERS, addresses) + await waitForExpect(async () => { + expect(spyOnProcessItem).toBeCalledTimes(addresses.length) + }) + }) + + it(`Do not dial peer on '${Libp2pEvents.DIAL_PEERS}' event if peer was already dialed`, async () => { + const peerId1 = await createPeerId() + const peerId2 = await createPeerId() + const alreadyDialedAddress = libp2pService.createLibp2pAddress('onionAddress1.onion', peerId1.toString()) + libp2pService.dialedPeers.add(alreadyDialedAddress) + const addresses = [ + alreadyDialedAddress, + libp2pService.createLibp2pAddress('onionAddress2.onion', peerId2.toString()), + ] + await libp2pService.createInstance(params) + expect(libp2pService.libp2pInstance).not.toBeNull() + // @ts-expect-error processItem is private + const dialPeerSpy = jest.spyOn(processInChunks, 'processItem') + libp2pService.emit(Libp2pEvents.DIAL_PEERS, addresses) + await waitForExpect(async () => { + expect(dialPeerSpy).toBeCalledTimes(1) + }) + }) }) diff --git a/packages/backend/src/nest/libp2p/libp2p.service.ts b/packages/backend/src/nest/libp2p/libp2p.service.ts index 68a4fd6c7c..d427a1d2b6 100644 --- a/packages/backend/src/nest/libp2p/libp2p.service.ts +++ b/packages/backend/src/nest/libp2p/libp2p.service.ts @@ -1,27 +1,27 @@ -import { Inject, Injectable } from '@nestjs/common' -import { Agent } from 'https' -import { createLibp2p, Libp2p } from 'libp2p' -import { noise } from '@chainsafe/libp2p-noise' import { gossipsub } from '@chainsafe/libp2p-gossipsub' -import { mplex } from '@libp2p/mplex' +import { noise } from '@chainsafe/libp2p-noise' import { kadDHT } from '@libp2p/kad-dht' -import { createServer } from 'it-ws' -import { DateTime } from 'luxon' -import { EventEmitter } from 'events' -import { Libp2pEvents, Libp2pNodeParams } from './libp2p.types' -import { ProcessInChunks } from './process-in-chunks' +import { mplex } from '@libp2p/mplex' import { multiaddr } from '@multiformats/multiaddr' +import { Inject, Injectable } from '@nestjs/common' +import { createLibp2pAddress, createLibp2pListenAddress } from '@quiet/common' import { ConnectionProcessInfo, PeerId, SocketActionTypes } from '@quiet/types' +import crypto from 'crypto' +import { EventEmitter } from 'events' +import { Agent } from 'https' +import { createServer } from 'it-ws' +import { Libp2p, createLibp2p } from 'libp2p' +import { preSharedKey } from 'libp2p/pnet' +import { DateTime } from 'luxon' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import Logger from '../common/logger' import { SERVER_IO_PROVIDER, SOCKS_PROXY_AGENT } from '../const' import { ServerIoProviderTypes } from '../types' -import Logger from '../common/logger' import { webSockets } from '../websocketOverTor' import { all } from '../websocketOverTor/filters' -import { createLibp2pAddress, createLibp2pListenAddress } from '@quiet/common' -import { preSharedKey } from 'libp2p/pnet' -import { toString as uint8ArrayToString } from 'uint8arrays/to-string' -import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' -import crypto from 'crypto' +import { Libp2pEvents, Libp2pNodeParams } from './libp2p.types' +import { ProcessInChunksService } from './process-in-chunks.service' const KEY_LENGTH = 32 export const LIBP2P_PSK_METADATA = '/key/swarm/psk/1.0.0/\n/base16/\n' @@ -30,15 +30,21 @@ export const LIBP2P_PSK_METADATA = '/key/swarm/psk/1.0.0/\n/base16/\n' export class Libp2pService extends EventEmitter { public libp2pInstance: Libp2p | null public connectedPeers: Map = new Map() + public dialedPeers: Set = new Set() private readonly logger = Logger(Libp2pService.name) constructor( @Inject(SERVER_IO_PROVIDER) public readonly serverIoProvider: ServerIoProviderTypes, - @Inject(SOCKS_PROXY_AGENT) public readonly socksProxyAgent: Agent + @Inject(SOCKS_PROXY_AGENT) public readonly socksProxyAgent: Agent, + private readonly processInChunksService: ProcessInChunksService ) { super() } private dialPeer = async (peerAddress: string) => { + if (this.dialedPeers.has(peerAddress)) { + return + } + this.dialedPeers.add(peerAddress) await this.libp2pInstance?.dial(multiaddr(peerAddress)) } @@ -79,10 +85,11 @@ export class Libp2pService extends EventEmitter { libp2p = await createLibp2p({ start: false, connectionManager: { - minConnections: 3, - maxConnections: 8, + minConnections: 3, // TODO: increase? + maxConnections: 8, // TODO: increase? dialTimeout: 120_000, maxParallelDials: 10, + autoDial: true, // It's a default but let's set it to have explicit information }, peerId: params.peerId, addresses: { @@ -129,9 +136,17 @@ export class Libp2pService extends EventEmitter { throw new Error('libp2pInstance was not created') } + this.logger(`Local peerId: ${peerId.toString()}`) + this.on(Libp2pEvents.DIAL_PEERS, async (addresses: string[]) => { + const nonDialedAddresses = addresses.filter(peerAddress => !this.dialedPeers.has(peerAddress)) + this.logger('Dialing', nonDialedAddresses.length, 'addresses') + this.processInChunksService.updateData(nonDialedAddresses) + await this.processInChunksService.process() + }) + this.logger(`Initializing libp2p for ${peerId.toString()}, bootstrapping with ${peers.length} peers`) this.serverIoProvider.io.emit(SocketActionTypes.CONNECTION_PROCESS_INFO, ConnectionProcessInfo.INITIALIZING_LIBP2P) - const dialInChunks = new ProcessInChunks(peers, this.dialPeer) + this.processInChunksService.init(peers, this.dialPeer) this.libp2pInstance.addEventListener('peer:discovery', peer => { this.logger(`${peerId.toString()} discovered ${peer.detail.id}`) @@ -139,13 +154,12 @@ export class Libp2pService extends EventEmitter { this.libp2pInstance.addEventListener('peer:connect', async peer => { const remotePeerId = peer.detail.remotePeer.toString() - this.logger(`${peerId.toString()} connected to ${remotePeerId}`) - - // Stop dialing as soon as we connect to a peer - dialInChunks.stop() + const localPeerId = peerId.toString() + this.logger(`${localPeerId} connected to ${remotePeerId}`) this.connectedPeers.set(remotePeerId, DateTime.utc().valueOf()) - this.logger(`${this.connectedPeers.size} connected peers`) + this.logger(`${localPeerId} is connected to ${this.connectedPeers.size} peers`) + this.logger(`${localPeerId} has ${this.libp2pInstance?.getConnections().length} open connections`) this.emit(Libp2pEvents.PEER_CONNECTED, { peers: [remotePeerId], @@ -154,12 +168,13 @@ export class Libp2pService extends EventEmitter { this.libp2pInstance.addEventListener('peer:disconnect', async peer => { const remotePeerId = peer.detail.remotePeer.toString() - this.logger(`${peerId.toString()} disconnected from ${remotePeerId}`) + const localPeerId = peerId.toString() + this.logger(`${localPeerId} disconnected from ${remotePeerId}`) if (!this.libp2pInstance) { this.logger.error('libp2pInstance was not created') throw new Error('libp2pInstance was not created') } - this.logger(`${this.libp2pInstance.getConnections().length} open connections`) + this.logger(`${localPeerId} has ${this.libp2pInstance.getConnections().length} open connections`) const connectionStartTime = this.connectedPeers.get(remotePeerId) if (!connectionStartTime) { @@ -172,7 +187,7 @@ export class Libp2pService extends EventEmitter { const connectionDuration: number = connectionEndTime - connectionStartTime this.connectedPeers.delete(remotePeerId) - this.logger(`${this.connectedPeers.size} connected peers`) + this.logger(`${localPeerId} is connected to ${this.connectedPeers.size} peers`) this.emit(Libp2pEvents.PEER_DISCONNECTED, { peer: remotePeerId, @@ -181,21 +196,16 @@ export class Libp2pService extends EventEmitter { }) }) - await dialInChunks.process() + await this.processInChunksService.process() this.logger(`Initialized libp2p for peer ${peerId.toString()}`) } - public async destroyInstance(): Promise { - this.libp2pInstance?.removeEventListener('peer:discovery') - this.libp2pInstance?.removeEventListener('peer:connect') - this.libp2pInstance?.removeEventListener('peer:disconnect') - try { - await this.libp2pInstance?.stop() - } catch (error) { - this.logger.error(error) - } - + public async close(): Promise { + this.logger('Closing libp2p service') + await this.libp2pInstance?.stop() this.libp2pInstance = null + this.connectedPeers = new Map() + this.dialedPeers = new Set() } } diff --git a/packages/backend/src/nest/libp2p/libp2p.types.ts b/packages/backend/src/nest/libp2p/libp2p.types.ts index 8b80845033..1f9d8c9fb7 100644 --- a/packages/backend/src/nest/libp2p/libp2p.types.ts +++ b/packages/backend/src/nest/libp2p/libp2p.types.ts @@ -5,6 +5,7 @@ export enum Libp2pEvents { PEER_CONNECTED = 'peerConnected', PEER_DISCONNECTED = 'peerDisconnected', NETWORK_STATS = 'networkStats', + DIAL_PEERS = 'dialPeers', } export interface Libp2pNodeParams { diff --git a/packages/backend/src/nest/libp2p/process-in-chunks.ts b/packages/backend/src/nest/libp2p/process-in-chunks.service.ts similarity index 68% rename from packages/backend/src/nest/libp2p/process-in-chunks.ts rename to packages/backend/src/nest/libp2p/process-in-chunks.service.ts index b3e11caf61..2e7b30588b 100644 --- a/packages/backend/src/nest/libp2p/process-in-chunks.ts +++ b/packages/backend/src/nest/libp2p/process-in-chunks.service.ts @@ -1,22 +1,30 @@ +import { EventEmitter } from 'events' import Logger from '../common/logger' const DEFAULT_CHUNK_SIZE = 10 -export class ProcessInChunks { +export class ProcessInChunksService extends EventEmitter { private isActive: boolean private data: T[] private chunkSize: number private processItem: (arg: T) => Promise - private readonly logger = Logger(ProcessInChunks.name) - constructor(data: T[], processItem: (arg: T) => Promise, chunkSize: number = DEFAULT_CHUNK_SIZE) { + private readonly logger = Logger(ProcessInChunksService.name) + constructor() { + super() + } + + public init(data: T[], processItem: (arg: T) => Promise, chunkSize: number = DEFAULT_CHUNK_SIZE) { this.data = data this.processItem = processItem this.chunkSize = chunkSize - this.isActive = true + } + + updateData(items: T[]) { + this.logger(`Updating data with ${items.length} items`) + this.data = [...new Set(this.data.concat(items))] } public async processOneItem() { - if (!this.isActive) return const toProcess = this.data.shift() if (toProcess) { try { @@ -32,7 +40,7 @@ export class ProcessInChunks { } public async process() { - this.logger(`Processing ${Math.min(this.chunkSize, this.data.length)} items`) + this.logger(`Processing ${this.data.length} items`) for (let i = 0; i < this.chunkSize; i++) { // Do not wait for this promise as items should be processed simultineously void this.processOneItem() diff --git a/packages/backend/src/nest/libp2p/process-in-chunks.spec.ts b/packages/backend/src/nest/libp2p/process-in-chunks.spec.ts index abd1770f67..38979c1cf9 100644 --- a/packages/backend/src/nest/libp2p/process-in-chunks.spec.ts +++ b/packages/backend/src/nest/libp2p/process-in-chunks.spec.ts @@ -1,22 +1,73 @@ import { jest, describe, it, expect } from '@jest/globals' -import { ProcessInChunks } from './process-in-chunks' - +import { ProcessInChunksService } from './process-in-chunks.service' +import waitForExpect from 'wait-for-expect' +import { TestModule } from '../common/test.module' +import { Test, TestingModule } from '@nestjs/testing' describe('ProcessInChunks', () => { + let module: TestingModule + let processInChunks: ProcessInChunksService + + beforeEach(async () => { + module = await Test.createTestingModule({ + imports: [TestModule, ProcessInChunksService], + }).compile() + + processInChunks = await module.resolve(ProcessInChunksService) + }) + it('processes data', async () => { const mockProcessItem = jest - .fn(async () => {}) + .fn(async a => { + console.log('processing', a) + }) + .mockResolvedValueOnce() + .mockRejectedValueOnce(new Error('Rejected 1')) + .mockResolvedValueOnce() + .mockRejectedValueOnce(new Error('Rejected 2')) + processInChunks.init(['a', 'b', 'c', 'd'], mockProcessItem) + await processInChunks.process() + await waitForExpect(() => { + expect(mockProcessItem).toBeCalledTimes(4) + }) + }) + + it('processes new data', async () => { + const mockProcessItem = jest + .fn(async a => { + console.log('processing', a) + }) + .mockResolvedValueOnce() + .mockRejectedValueOnce(new Error('Rejected 1')) + processInChunks.init(['a', 'b'], mockProcessItem) + await processInChunks.process() + processInChunks.updateData(['e', 'f']) + await processInChunks.process() + await waitForExpect(() => { + expect(mockProcessItem).toBeCalledTimes(4) + }) + }) + + it('processes data in chunks', async () => { + const mockProcessItem = jest + .fn(async a => { + console.log('processing', a) + }) .mockResolvedValueOnce() .mockRejectedValueOnce(new Error('Rejected 1')) .mockResolvedValueOnce() .mockRejectedValueOnce(new Error('Rejected 2')) - const processInChunks = new ProcessInChunks(['a', 'b', 'c', 'd'], mockProcessItem) + const chunkSize = 2 + processInChunks.init(['a', 'b', 'c', 'd'], mockProcessItem, chunkSize) await processInChunks.process() - expect(mockProcessItem).toBeCalledTimes(4) + await waitForExpect(() => { + expect(mockProcessItem).toBeCalledTimes(4) + }) }) - it('does not process more data if stopped', async () => { + it.skip('does not process more data if stopped', async () => { const mockProcessItem = jest.fn(async () => {}) - const processInChunks = new ProcessInChunks(['a', 'b', 'c', 'd'], mockProcessItem) + const processInChunks = new ProcessInChunksService() + processInChunks.init(['a', 'b', 'c', 'd'], mockProcessItem) processInChunks.stop() await processInChunks.process() expect(mockProcessItem).not.toBeCalled() diff --git a/packages/backend/src/nest/local-db/local-db.service.spec.ts b/packages/backend/src/nest/local-db/local-db.service.spec.ts index 0cf4af79cf..ee9a42b8c1 100644 --- a/packages/backend/src/nest/local-db/local-db.service.spec.ts +++ b/packages/backend/src/nest/local-db/local-db.service.spec.ts @@ -4,15 +4,15 @@ import { TestModule } from '../common/test.module' import { LocalDbModule } from './local-db.module' import { LocalDbService } from './local-db.service' import { LocalDBKeys } from './local-db.types' +import { createLibp2pAddress } from '@quiet/common' describe('LocalDbService', () => { let module: TestingModule let localDbService: LocalDbService - - let peer1Address: string let peer1Stats: Record = {} - let peer2Address: string + let peer1ID: string let peer2Stats: Record = {} + let peer2ID: string beforeAll(async () => { module = await Test.createTestingModule({ @@ -20,21 +20,18 @@ describe('LocalDbService', () => { }).compile() localDbService = await module.resolve(LocalDbService) - - peer1Address = - '/dns4/mxtsfs4kzxzuisrw4tumdmycbyerqwakx37kj6om6azcjdaasifxmoqd.onion/tcp/443/wss/p2p/QmaEvCkpUG7GxhgvMkk8wxurfi1ehjHhSUNRksWTmXN2ix' + peer1ID = 'QmaEvCkpUG7GxhgvMkk8wxurfi1ehjHhSUNRksWTmXN2ix' peer1Stats = { - [peer1Address]: { - peerId: 'QmaEvCkpUG7GxhgvMkk8wxurfi1ehjHhSUNRksWTmXN2ix', + [peer1ID]: { + peerId: peer1ID, connectionTime: 50, lastSeen: 1000, }, } - peer2Address = - '/dns4/hxr74a76b4lerhov75a6ha6yprruvow3wfu4qmmeoc6ajs7m7323lyid.onion/tcp/443/wss/p2p/QmZB6pVafcvAQfy5R5LxvDXvB8xcDifD39Lp3XGDM9XDuQ' + peer2ID = 'QmZB6pVafcvAQfy5R5LxvDXvB8xcDifD39Lp3XGDM9XDuQ' peer2Stats = { - [peer2Address]: { - peerId: 'QmZB6pVafcvAQfy5R5LxvDXvB8xcDifD39Lp3XGDM9XDuQ', + [peer2ID]: { + peerId: peer2ID, connectionTime: 500, lastSeen: 500, }, @@ -71,16 +68,28 @@ describe('LocalDbService', () => { expect(localDbService.getStatus()).toEqual('closed') }) + it('get sorted peers returns peers list if no stats in db', async () => { + const peers = [ + createLibp2pAddress( + 'zl37gnntp64dhnisddftypxbt5cqx6cum65vdv6oeaffrbqmemwc52ad.onion', + 'QmPGdGDUV1PXaJky4V53KSvFszdqEcM7KCoDpF2uFPf5w6' + ), + ] + const sortedPeers = await localDbService.getSortedPeers(peers) + expect(sortedPeers).toEqual(peers) + }) + it('get sorted peers', async () => { - const extraPeers = [ - '/dns4/zl37gnntp64dhnisddftypxbt5cqx6cum65vdv6oeaffrbqmemwc52ad.onion/tcp/443/ws/p2p/QmPGdGDUV1PXaJky4V53KSvFszdqEcM7KCoDpF2uFPf5w6', + const peers = [ + createLibp2pAddress('nqnw4kc4c77fb47lk52m5l57h4tcxceo7ymxekfn7yh5m66t4jv2olad.onion', peer2ID), + createLibp2pAddress('zl37gnntp64dhnisddftypxbt5cqx6cum65vdv6oeaffrbqmemwc52ad.onion', peer1ID), ] await localDbService.put(LocalDBKeys.PEERS, { ...peer1Stats, ...peer2Stats, }) - const sortedPeers = await localDbService.getSortedPeers(extraPeers) - expect(sortedPeers).toEqual([peer1Address, peer2Address, extraPeers[0]]) + const sortedPeers = await localDbService.getSortedPeers(peers.reverse()) + expect(sortedPeers).toEqual(peers) }) it('updates nested object', async () => { @@ -94,19 +103,19 @@ describe('LocalDbService', () => { }) const peer2StatsUpdated: NetworkStats = { - peerId: 'QmR7Qgd4tg2XrGD3kW647ZnYyazTwHQF3cqRBmSduhhusA', + peerId: peer2ID, connectionTime: 777, lastSeen: 678, } await localDbService.update(LocalDBKeys.PEERS, { - [peer2Address]: peer2StatsUpdated, + [peer2StatsUpdated.peerId]: peer2StatsUpdated, }) const updatedPeersDBdata = await localDbService.get(LocalDBKeys.PEERS) expect(updatedPeersDBdata).toEqual({ ...peer1Stats, - [peer2Address]: peer2StatsUpdated, + [peer2StatsUpdated.peerId]: peer2StatsUpdated, }) }) }) diff --git a/packages/backend/src/nest/local-db/local-db.service.ts b/packages/backend/src/nest/local-db/local-db.service.ts index 96e6402f81..f5275ff77f 100644 --- a/packages/backend/src/nest/local-db/local-db.service.ts +++ b/packages/backend/src/nest/local-db/local-db.service.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common' import { Level } from 'level' import { NetworkStats } from '@quiet/types' -import { sortPeers } from '@quiet/common' +import { filterAndSortPeers } from '@quiet/common' import { LEVEL_DB } from '../const' import { LocalDBKeys, LocalDbStatus } from './local-db.types' import Logger from '../common/logger' @@ -73,9 +73,7 @@ export class LocalDbService { public async getSortedPeers(peers: string[] = []): Promise { const peersStats = (await this.get(LocalDBKeys.PEERS)) || {} - const peersAddresses: string[] = [...new Set(Object.keys(peersStats).concat(peers))] const stats: NetworkStats[] = Object.values(peersStats) - const sortedPeers = sortPeers(peersAddresses, stats) - return sortedPeers + return filterAndSortPeers(peers, stats) } } diff --git a/packages/backend/src/nest/storage/certificatesRequestsStore.spec.ts b/packages/backend/src/nest/storage/certificatesRequestsStore.spec.ts new file mode 100644 index 0000000000..ac504cbbca --- /dev/null +++ b/packages/backend/src/nest/storage/certificatesRequestsStore.spec.ts @@ -0,0 +1,173 @@ +import OrbitDB from 'orbit-db' +import fs from 'fs' +import { jest } from '@jest/globals' +import { create, IPFS } from 'ipfs-core' +import { CertificatesRequestsStore } from './certificatesRequestsStore' +import { EventEmitter } from 'events' +import { StorageEvents } from './storage.types' +import { ORBIT_DB_DIR } from '../const' + +// Futher improvement is creating 2 orbitdb instances, connect them and create 'real-life' tests. +const createOrbitDbInstance = async () => { + const ipfs: IPFS = await create() + // @ts-ignore + const orbitdb = await OrbitDB.createInstance(ipfs, { + directory: ORBIT_DB_DIR, + }) + + return { orbitdb, ipfs } +} + +const replicatedEvent = async (store: any) => { + store.events.emit('replicated') + await new Promise(resolve => setTimeout(() => resolve(), 2000)) +} + +describe('CertificatesRequestsStore', () => { + let ipfs: IPFS + let orbitdb: OrbitDB + let store: CertificatesRequestsStore + let emitter: EventEmitter + + beforeEach(async () => { + ; ({ orbitdb, ipfs } = await createOrbitDbInstance()) + store = new CertificatesRequestsStore(orbitdb) + emitter = new EventEmitter() + await store.init(emitter) + }) + + afterEach(async () => { + await store.close() + await orbitdb.stop() + await ipfs.stop() + if (fs.existsSync(ORBIT_DB_DIR)) { + fs.rmSync(ORBIT_DB_DIR, { recursive: true, force: true }) + } + }) + + it('validateUserCs, correct csr', async () => { + const cert = + 'MIIDITCCAsYCAQAwSTFHMEUGA1UEAxM+anR3c3hxMnZ1dWthY3JodWhvdnAzd2JxbzRxNXc0d2s3Nm1qbWJ3cXk3eGNma2FsdmRxb3hhYWQub25pb24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQE2q6iS+WCmIVCSFI2AjHrW6ujUdrceD5T2xkcTJBTn0y50WphcupUajCRgkXaTBkTsGNJ3qWRZAKX7CiuehBJoIICGTAuBgkqhkiG9w0BCQ4xITAfMB0GA1UdDgQWBBQuE5JgPY/BYBpgG5pnjMkEEIkrGjCCAUcGCSqGSIb3DQEJDDGCATgEggE0BDlx84glBl72q82F2a+y8iTVKM8IMiXYYrmNyhFPj6XsfVQpvLhNviZ5zHdMBWbFj44vTSUIasNP9I9eCWSEAaEJqjngEh18WCRS/XbvQxI/8qB5pzcfghvM8BCgSLbSEjK2GMYVhCXmRH1YGHIZu0+Ii9pe5nwG154JlPUsmIRgu6ruY6PQk65Aoo4OyhPn5CCUFInptHcz1JpAiCRe0Z6wuQHud03VY50fx4ETdmUNJBEIPOyd/Xn6lMOi6SaWGHbCWiufeJRm+mRdoHJAEt6kPLhGIYGyduNT/8cGoe2xKyQDvNoTr4dqqRZ2HgZ18nicsTHswpGqAlUnZXaA3V85Qu1cvaMAqEoPOUlGP9AriIVwtIZM0hdWHqKHgBCZrKfHb5oLxt6ourQ3+q19tvx+u6UwFAYKKwYBBAGDjBsCATEGEwRlbGxvMD0GCSsGAQIBDwMBATEwEy5RbVVvNXN0NXNqR3RFMUtQeXhOVW5pTWhnQXduV0JVNXk3TnpoMlpRRkdacVdiMEcGA1UdETFAEz5qdHdzeHEydnV1a2Fjcmh1aG92cDN3YnFvNHE1dzR3azc2bWptYndxeTd4Y2ZrYWx2ZHFveGFhZC5vbmlvbjAKBggqhkjOPQQDAgNJADBGAiEAt9udw7B7vnjJyWvkkRLb7DImFXwsrSxirqbmhIH+1rUCIQD86GWyfAE2d8gCNAn4h1t9B+mAx33ZdPLgFssHl1i3pA==' + const result = await CertificatesRequestsStore.validateUserCsr(cert) + expect(result).toBe(true) + }) + + it('validateUserCsr, malformed string', async () => { + const cert = 'notACsr' + const result = await CertificatesRequestsStore.validateUserCsr(cert) + expect(result).toBe(false) + }) + + it('getCsrs - remove old csrs and replace with new for each pubkey', async () => { + const store = new CertificatesRequestsStore(orbitdb) + const emitter = new EventEmitter() + await store.init(emitter) + + const allCsrs = [ + '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', + ] + + for (const csr of allCsrs) { + await store.addUserCsr(csr) + // This should not be there, there's bug in orbitdb, it breaks if we add entries without artificial sleep, tho it's awaited. + // https://github.com/TryQuiet/quiet/issues/2121 + await new Promise(resolve => setTimeout(() => resolve(), 500)) + } + + await store.getCsrs() + + const filteredCsrs = await store.getCsrs() + + expect(filteredCsrs.length).toEqual(allCsrs.length - 1) + expect(filteredCsrs).toEqual([ + 'MIIDITCCAsYCAQAwSTFHMEUGA1UEAxM+anR3c3hxMnZ1dWthY3JodWhvdnAzd2JxbzRxNXc0d2s3Nm1qbWJ3cXk3eGNma2FsdmRxb3hhYWQub25pb24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQE2q6iS+WCmIVCSFI2AjHrW6ujUdrceD5T2xkcTJBTn0y50WphcupUajCRgkXaTBkTsGNJ3qWRZAKX7CiuehBJoIICGTAuBgkqhkiG9w0BCQ4xITAfMB0GA1UdDgQWBBQuE5JgPY/BYBpgG5pnjMkEEIkrGjCCAUcGCSqGSIb3DQEJDDGCATgEggE0BDlx84glBl72q82F2a+y8iTVKM8IMiXYYrmNyhFPj6XsfVQpvLhNviZ5zHdMBWbFj44vTSUIasNP9I9eCWSEAaEJqjngEh18WCRS/XbvQxI/8qB5pzcfghvM8BCgSLbSEjK2GMYVhCXmRH1YGHIZu0+Ii9pe5nwG154JlPUsmIRgu6ruY6PQk65Aoo4OyhPn5CCUFInptHcz1JpAiCRe0Z6wuQHud03VY50fx4ETdmUNJBEIPOyd/Xn6lMOi6SaWGHbCWiufeJRm+mRdoHJAEt6kPLhGIYGyduNT/8cGoe2xKyQDvNoTr4dqqRZ2HgZ18nicsTHswpGqAlUnZXaA3V85Qu1cvaMAqEoPOUlGP9AriIVwtIZM0hdWHqKHgBCZrKfHb5oLxt6ourQ3+q19tvx+u6UwFAYKKwYBBAGDjBsCATEGEwRlbGxvMD0GCSsGAQIBDwMBATEwEy5RbVVvNXN0NXNqR3RFMUtQeXhOVW5pTWhnQXduV0JVNXk3TnpoMlpRRkdacVdiMEcGA1UdETFAEz5qdHdzeHEydnV1a2Fjcmh1aG92cDN3YnFvNHE1dzR3azc2bWptYndxeTd4Y2ZrYWx2ZHFveGFhZC5vbmlvbjAKBggqhkjOPQQDAgNJADBGAiEAt9udw7B7vnjJyWvkkRLb7DImFXwsrSxirqbmhIH+1rUCIQD86GWyfAE2d8gCNAn4h1t9B+mAx33ZdPLgFssHl1i3pA==', + + 'MIIDITCCAscCAQAwSTFHMEUGA1UEAxM+Z3hscHR5ZWs0eG12NGl2cTRxZGkzNXRzbDJwaXkzc2Ruc256dGN5dHA2NWZ3Nm93djdjc252aWQub25pb24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQpn3SvkJv5Py+q+PVQcHpMEI4r6WGmUELj6PSv9HlNzup6AbgTF+fkGJ5Ei75XSiF9hNsL2RqjzD6cvqANAhvSoIICGjAuBgkqhkiG9w0BCQ4xITAfMB0GA1UdDgQWBBQyCApwbL2Z81491MXLwgSLAb8yVTCCAUcGCSqGSIb3DQEJDDGCATgEggE0IlA9j/+4ffYhOyzM2DKwBPjqB5MD1mVLtOkYYmOaI16f+DEIZRU8+SjzbaXqBoG9EvkzCP8I7afJTG37/zEcE5SbLVRXGZalqzFb7NCOrXsZViUlaCOoikRkbiGj4j6o3af/STSQUCfeBiTSNfmEJX/pBoaBNsqqjfm0OACvLsAVg/Hka+/97DPYgk1pHgErt1NL5I6nFltHJxKlYxxMkvVTJSJLfZcGf+/73Oz+MoyxcyRJq3u8d23rxqRXhl3CvtH7GafzM2T7fNIgpbjMI9nYHCJvqbvCArua4dviKi4X9j54m4rYA4wwPPWYgV55NoN4AfJN5p7NTLhcyrzkcXIm3CNgh3NzzyvE8B+pJ67oVo/eGFecGtQE7tfgx9DjpLd+NfF9dnR7vx9WioJgCTnXvF0wFQYKKwYBBAGDjBsCATEHEwVvd25lcjA9BgkrBgECAQ8DAQExMBMuUW1ZaUN3bTNRV3NHMlpEQnBCeGNvaEVtWFpVZXo3d213b3lQNFdnTHhiUEYzSDBHBgNVHRExQBM+Z3hscHR5ZWs0eG12NGl2cTRxZGkzNXRzbDJwaXkzc2Ruc256dGN5dHA2NWZ3Nm93djdjc252aWQub25pb24wCgYIKoZIzj0EAwIDSAAwRQIhANGQ+7FGYrUksCVQOYa56FUy6nhJCmOn01r+mZ1CiXKiAiBSBjDGPueE9jzP1b8GHCYRDo7y31XQLoPb5PgvWbCfhA==', + + 'MIIDIjCCAskCAQAwSTFHMEUGA1UEAxM+ZDczejJmemt6Nm9zZ3Q0aGxiYXVoY2dlejVtcm9uNXp1djVvc242aDJteXZxb2NjeWQ2MjNnaWQub25pb24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAReJrJSBfMmV2t3LPzI3CzPaCaczslnE5LgdptV8HcWhwTzaE+z9bUqA28xc9SaWNWvZ5v9xURKMKc6aMv0tySJoIICHDAuBgkqhkiG9w0BCQ4xITAfMB0GA1UdDgQWBBR6gB8ZoO1xPEX+bej0/a0fffXDajCCAUcGCSqGSIb3DQEJDDGCATgEggE0IfNRueluz1lwKCPyiU8i/d2uyVgC351lK7LHr9n/1u1Ln00g7HKCDSZl2vinu1YaxhBdjlgDl8NjST3+5NTBZAn5liQM53WImqzY8yUJgm1+hms96qb30pK73owxkHHeS1fmbz/gTlH4KvDGLQLQl2QuHuXJ9PJDg4B07/EcM61UE+mMp1B4zkuXBTihrLLT2PQNfeaFzK0FX8tkvTJ8ym53xfb30YfeQnEOkxREJksWxMtxBKki7pCOzzTyUCcsSVNBic59sKpwkiQ4aeQMtJF2eKQUqnlkyP4r0e6KV9EivxB7FLNrHNb/2slgeLRFLbGUf0csZiaFgFt1Ps2ZW3wakpl5Fe+ZQh+89hZfi1flSne/mLr/J9TF4IN+XXiNtGJp18f6xXLv54Cg8cde432U3iQwFwYKKwYBBAGDjBsCATEJEwdrYWNwZXIyMD0GCSsGAQIBDwMBATEwEy5RbVdYMXJOVlV4Q2hkOGN6Nmh3VDRqdjdXS3JodGl1dkdCNzJZN3pZWEFTVGJRMEcGA1UdETFAEz5kNzN6MmZ6a3o2b3NndDRobGJhdWhjZ2V6NW1yb241enV2NW9zbjZoMm15dnFvY2N5ZDYyM2dpZC5vbmlvbjAKBggqhkjOPQQDAgNHADBEAiBjivWf9a+YwInRNQ5W0zm7VmsjZLOlQXhf922JzP3XEgIgAYW6vm0PNfXMxPss24gbe3UK9/uPjSDEb26lu2bvgzY=', + + 'MIIDIjCCAsgCAQAwSTFHMEUGA1UEAxM+dTdua2gyNHBvbXRsNzVvYWFibXd5dGt0dTNjNmx4aW9uZ2IzYm1jamtuZXBmZWF3Mmk2ZHdkYWQub25pb24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASKexp/LUMwIEJElHaKzlAjXGvLl/vFiOugGa7pUACVYc/xINEPnbQTy0kHjb47vBPl0NXryCx/ncGxqnEBZat+oIICGzAuBgkqhkiG9w0BCQ4xITAfMB0GA1UdDgQWBBTrrHm/uv3ViamQqQsImfE+Nd0R1jCCAUcGCSqGSIb3DQEJDDGCATgEggE0EEQQfvnnjicwQYHZLzsPiRaoQtS8rP4q4cqjLBA6zJcibd88zpWKFH5oNkUaVaZi64iiX0bCCEmJFX+nQWJdtuhMd4/ut+6vW5cj/DWMAak5q3fi7gQ2lSsDfd702Ter0uNJToSbm7X1NlYm/WXCtLeUEsXOV1G0kOcv2uthpaV7NSlWd4jtRDHidLrd/X/iJWHMsmi4KyLM/p7dCGEqk24aobLfJA9cYN540Q0Sp93tJAXw3Y3Gh5CUwItNolhMk/rVpS3niKIpxjMk2OtLrV0epBKhMVV7jDqKsxZX9I0gDMNTRdixIEXbKHacVY4dSP9iNY+9T26yxGKBM6ah0KHxTY5rODLV29+ll/+wftIGsixYNJoo5HUEmZnWRSPVKri50scOJAI4C6l9HJfNgEBoNFEwFgYKKwYBBAGDjBsCATEIEwZrYWNwZXIwPQYJKwYBAgEPAwEBMTATLlFtY01LdlpWNWJZcEZ4eW9TRFlUcE1RY2VBdnRDa2taZnQ2TlRDVVB0VVAyTXkwRwYDVR0RMUATPnU3bmtoMjRwb210bDc1b2FhYm13eXRrdHUzYzZseGlvbmdiM2JtY2prbmVwZmVhdzJpNmR3ZGFkLm9uaW9uMAoGCCqGSM49BAMCA0gAMEUCIFsTfZsGWX3g44QnEksCh0naujBG60DuNNh83YHcl12FAiEAm9qALhC6ctx9JvakesWQhtDT4WFAGyEkuIB5Xtw68eg=', + ]) + }) + it('replicated event', async () => { + const store = new CertificatesRequestsStore(orbitdb) + const emitter = new EventEmitter() + await store.init(emitter) + + const spy = jest.fn() + + emitter.on(StorageEvents.LOADED_USER_CSRS, spy) + await replicatedEvent(store.store) + + expect(spy).toBeCalledTimes(1) + }) + it('2 replicated events - first not resolved ', async () => { + const store = new CertificatesRequestsStore(orbitdb) + const emitter = new EventEmitter() + await store.init(emitter) + + const spy = jest.fn() + + emitter.on(StorageEvents.LOADED_USER_CSRS, spy) + await replicatedEvent(store.store) + await replicatedEvent(store.store) + + expect(spy).toBeCalledTimes(1) + }) + it('2 replicated events - first resolved ', async () => { + const store = new CertificatesRequestsStore(orbitdb) + const emitter = new EventEmitter() + await store.init(emitter) + + const spy = jest.fn() + + emitter.on(StorageEvents.LOADED_USER_CSRS, spy) + await replicatedEvent(store.store) + await replicatedEvent(store.store) + + store.resolveCsrReplicatedPromise(1) + await new Promise(resolve => setTimeout(() => resolve(), 500)) + + expect(spy).toBeCalledTimes(2) + }) + it('3 replicated events - no resolved promises', async () => { + const store = new CertificatesRequestsStore(orbitdb) + const emitter = new EventEmitter() + await store.init(emitter) + + const spy = jest.fn() + + emitter.on(StorageEvents.LOADED_USER_CSRS, spy) + await replicatedEvent(store.store) + await replicatedEvent(store.store) + await replicatedEvent(store.store) + + expect(spy).toBeCalledTimes(1) + }) + it('3 replicated events - two resolved promises ', async () => { + const store = new CertificatesRequestsStore(orbitdb) + const emitter = new EventEmitter() + await store.init(emitter) + + const spy = jest.fn() + emitter.on(StorageEvents.LOADED_USER_CSRS, spy) + + await replicatedEvent(store.store) + await replicatedEvent(store.store) + store.resolveCsrReplicatedPromise(1) + await new Promise(resolve => setTimeout(() => resolve(), 500)) + await replicatedEvent(store.store) + store.resolveCsrReplicatedPromise(2) + await new Promise(resolve => setTimeout(() => resolve(), 500)) + + expect(spy).toBeCalledTimes(3) + }) +}) diff --git a/packages/backend/src/nest/storage/certificatesRequestsStore.ts b/packages/backend/src/nest/storage/certificatesRequestsStore.ts index 9eedadcfbe..fc01ebaea8 100644 --- a/packages/backend/src/nest/storage/certificatesRequestsStore.ts +++ b/packages/backend/src/nest/storage/certificatesRequestsStore.ts @@ -48,8 +48,6 @@ export class CertificatesRequestsStore { this.store.events.on('replicated', async () => { logger('Replicated CSRS') - // @ts-expect-error - OrbitDB's type declaration of `load` lacks 'options' - await this.store.load({ fetchEntryTimeout: 15000 }) this.csrReplicatedPromiseId++ const filteredCsrs = await this.getCsrs() @@ -70,8 +68,6 @@ export class CertificatesRequestsStore { }) }) - // @ts-expect-error - OrbitDB's type declaration of `load` lacks 'options' - await this.store.load({ fetchEntryTimeout: 15000 }) emitter.emit(StorageEvents.LOADED_USER_CSRS, { csrs: await this.getCsrs(), id: this.csrReplicatedPromiseId, @@ -141,17 +137,19 @@ export class CertificatesRequestsStore { return validationErrors } - protected async getCsrs() { + public async getCsrs() { const filteredCsrsMap: Map = new Map() - - const allCsrs = this.store + await this.store.load() + const allEntries = this.store .iterator({ limit: -1 }) .collect() - .map(e => e.payload.value) + .map(e => { + return e.payload.value + }) + const allCsrsUnique = [...new Set(allEntries)] await Promise.all( - allCsrs + allCsrsUnique .filter(async csr => { - // const parsedCsr = await loadCSR(csr) const validation = await CertificatesRequestsStore.validateUserCsr(csr) if (validation) return true return false diff --git a/packages/backend/src/nest/storage/storage.service.ts b/packages/backend/src/nest/storage/storage.service.ts index 579f72d4a1..1ace36913d 100644 --- a/packages/backend/src/nest/storage/storage.service.ts +++ b/packages/backend/src/nest/storage/storage.service.ts @@ -25,6 +25,7 @@ import { ConnectionProcessInfo, DeleteFilesFromChannelSocketPayload, FileMetadata, + InitCommunityPayload, NoCryptoEngineError, PublicChannel, PushNotificationPayload, diff --git a/packages/backend/src/nest/types.ts b/packages/backend/src/nest/types.ts index fa2c4e3943..ec6302de1a 100644 --- a/packages/backend/src/nest/types.ts +++ b/packages/backend/src/nest/types.ts @@ -5,6 +5,7 @@ import { Server as SocketIO } from 'socket.io' export class ConnectionsManagerTypes { options: Partial socketIOPort: number + socketIOSecret: string httpTunnelPort?: number torAuthCookie?: string torControlPort?: number diff --git a/packages/backend/src/options.ts b/packages/backend/src/options.ts new file mode 100644 index 0000000000..a19d0342c4 --- /dev/null +++ b/packages/backend/src/options.ts @@ -0,0 +1,32 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import commander from 'commander' + +export interface OpenServices { + torControlPort?: any + socketIOPort?: any + socketIOSecret?: any + httpTunnelPort?: any + authCookie?: any +} + +interface Options { + platform?: any + dataPath?: any + dataPort?: any + torBinary?: any + authCookie?: any + controlPort?: any + httpTunnelPort?: any + appDataPath?: string + socketIOPort?: number + resourcesPath?: string + socketIOSecret: string +} + +// concept +export const validateOptions = (_options: commander.OptionValues) => { + const options = _options as Options + if (!options.socketIOSecret) { + throw new Error('socketIOSecret is missing in options') + } +} diff --git a/packages/common/CHANGELOG.md b/packages/common/CHANGELOG.md index 58019f1a5d..8e887a1e7b 100644 --- a/packages/common/CHANGELOG.md +++ b/packages/common/CHANGELOG.md @@ -5,10 +5,6 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline ## [2.0.2-alpha.1](https://github.com/TryQuiet/quiet/compare/@quiet/common@2.0.2-alpha.0...@quiet/common@2.0.2-alpha.1) (2023-11-14) -**Note:** Version bump only for package @quiet/common - - - ## [2.0.2-alpha.0](https://github.com/TryQuiet/quiet/compare/@quiet/common@2.0.1-alpha.4...@quiet/common@2.0.2-alpha.0) (2023-10-26) @@ -107,9 +103,17 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline +# [1.9.0-alpha.0](/compare/@quiet/common@1.8.0...@quiet/common@1.9.0-alpha.0) (2023-08-29) + + +## [1.8.2](https://github.com/TryQuiet/quiet/compare/@quiet/common@1.8.1...@quiet/common@1.8.2) (2023-11-09) + +**Note:** Version bump only for package @quiet/common + + + -# [1.9.0-alpha.0](/compare/@quiet/common@1.8.0...@quiet/common@1.9.0-alpha.0) (2023-08-29) ## [1.8.1](https://github.com/TryQuiet/quiet/compare/@quiet/common@1.8.0...@quiet/common@1.8.1) (2023-09-15) **Note:** Version bump only for package @quiet/common diff --git a/packages/common/package-lock.json b/packages/common/package-lock.json index 8e6302db19..f59a8c3632 100644 --- a/packages/common/package-lock.json +++ b/packages/common/package-lock.json @@ -983,6 +983,88 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "node_modules/@peculiar/webcrypto": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.4.3.tgz", + "integrity": "sha512-VtaY4spKTdN5LjJ04im/d/joXuvLbQdgy5Z4DXF4MFZhQ+MTrejbNMkfZBp1Bs3O5+bFqnJgyGdPuZQflvIa5A==", + "dependencies": { + "@peculiar/asn1-schema": "^2.3.6", + "@peculiar/json-schema": "^1.1.12", + "pvtsutils": "^1.3.2", + "tslib": "^2.5.0", + "webcrypto-core": "^1.7.7" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@peculiar/webcrypto/node_modules/@peculiar/asn1-schema": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.8.tgz", + "integrity": "sha512-ULB1XqHKx1WBU/tTFIA+uARuRoBVZ4pNdOA878RDrRbBfBGcSzi5HBkdScC6ZbHn8z7L8gmKCgPC1LHRrP46tA==", + "dependencies": { + "asn1js": "^3.0.5", + "pvtsutils": "^1.3.5", + "tslib": "^2.6.2" + } + }, + "node_modules/@peculiar/webcrypto/node_modules/@peculiar/json-schema": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", + "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@peculiar/webcrypto/node_modules/asn1js": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.5.tgz", + "integrity": "sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==", + "dependencies": { + "pvtsutils": "^1.3.2", + "pvutils": "^1.1.3", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@peculiar/webcrypto/node_modules/pvtsutils": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.5.tgz", + "integrity": "sha512-ARvb14YB9Nm2Xi6nBq1ZX6dAM0FsJnuk+31aUp4TrcZEdKUlSqOqsxJHUPJDNE3qiIp+iUPEIeR6Je/tgV7zsA==", + "dependencies": { + "tslib": "^2.6.1" + } + }, + "node_modules/@peculiar/webcrypto/node_modules/pvutils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz", + "integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@peculiar/webcrypto/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@peculiar/webcrypto/node_modules/webcrypto-core": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.7.7.tgz", + "integrity": "sha512-7FjigXNsBfopEj+5DV2nhNpfic2vumtjjgPmeDKk45z+MJwXKKfhPB7118Pfzrmh4jqOMST6Ch37iPAHoImg5g==", + "dependencies": { + "@peculiar/asn1-schema": "^2.3.6", + "@peculiar/json-schema": "^1.1.12", + "asn1js": "^3.0.1", + "pvtsutils": "^1.3.2", + "tslib": "^2.4.0" + } + }, "node_modules/@sinonjs/commons": { "version": "1.8.6", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", @@ -6982,6 +7064,78 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "@peculiar/webcrypto": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.4.3.tgz", + "integrity": "sha512-VtaY4spKTdN5LjJ04im/d/joXuvLbQdgy5Z4DXF4MFZhQ+MTrejbNMkfZBp1Bs3O5+bFqnJgyGdPuZQflvIa5A==", + "requires": { + "@peculiar/asn1-schema": "^2.3.6", + "@peculiar/json-schema": "^1.1.12", + "pvtsutils": "^1.3.2", + "tslib": "^2.5.0", + "webcrypto-core": "^1.7.7" + }, + "dependencies": { + "@peculiar/asn1-schema": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.8.tgz", + "integrity": "sha512-ULB1XqHKx1WBU/tTFIA+uARuRoBVZ4pNdOA878RDrRbBfBGcSzi5HBkdScC6ZbHn8z7L8gmKCgPC1LHRrP46tA==", + "requires": { + "asn1js": "^3.0.5", + "pvtsutils": "^1.3.5", + "tslib": "^2.6.2" + } + }, + "@peculiar/json-schema": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", + "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", + "requires": { + "tslib": "^2.0.0" + } + }, + "asn1js": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.5.tgz", + "integrity": "sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==", + "requires": { + "pvtsutils": "^1.3.2", + "pvutils": "^1.1.3", + "tslib": "^2.4.0" + } + }, + "pvtsutils": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.5.tgz", + "integrity": "sha512-ARvb14YB9Nm2Xi6nBq1ZX6dAM0FsJnuk+31aUp4TrcZEdKUlSqOqsxJHUPJDNE3qiIp+iUPEIeR6Je/tgV7zsA==", + "requires": { + "tslib": "^2.6.1" + } + }, + "pvutils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz", + "integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==" + }, + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "webcrypto-core": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.7.7.tgz", + "integrity": "sha512-7FjigXNsBfopEj+5DV2nhNpfic2vumtjjgPmeDKk45z+MJwXKKfhPB7118Pfzrmh4jqOMST6Ch37iPAHoImg5g==", + "requires": { + "@peculiar/asn1-schema": "^2.3.6", + "@peculiar/json-schema": "^1.1.12", + "asn1js": "^3.0.1", + "pvtsutils": "^1.3.2", + "tslib": "^2.4.0" + } + } + } + }, "@sinonjs/commons": { "version": "1.8.6", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", diff --git a/packages/common/src/auth.test.ts b/packages/common/src/auth.test.ts new file mode 100644 index 0000000000..3d00770d7b --- /dev/null +++ b/packages/common/src/auth.test.ts @@ -0,0 +1,19 @@ +import { encodeSecret, verifyToken } from './auth' + +describe('Auth', () => { + it('correctly create secret, encode and decode', () => { + const secret = 'secret' + const token = encodeSecret(secret) + const decodedSecret = verifyToken(secret, token) + + expect(decodedSecret).toBeTruthy() + }) + + it('create token with wrong secret', () => { + const secret = 'secret' + const token = encodeSecret('test') + const decodedSecret = verifyToken(secret, token) + + expect(decodedSecret).toBeFalsy() + }) +}) diff --git a/packages/common/src/auth.ts b/packages/common/src/auth.ts new file mode 100644 index 0000000000..41bd90449e --- /dev/null +++ b/packages/common/src/auth.ts @@ -0,0 +1,6 @@ +export const encodeSecret = (secret: string) => Buffer.from(secret).toString('base64') + +export const verifyToken = (secret: string, token: string): boolean => { + const decoded = Buffer.from(token, 'base64').toString('ascii') + return decoded === secret +} diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index bd92483eb7..f15cd34520 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -9,3 +9,5 @@ export * from './naming' export * from './fileData' export * from './libp2p' export * from './tests' +export * from './auth' +export * from './messages' diff --git a/packages/common/src/libp2p.test.ts b/packages/common/src/libp2p.test.ts new file mode 100644 index 0000000000..5a623aadb7 --- /dev/null +++ b/packages/common/src/libp2p.test.ts @@ -0,0 +1,20 @@ +import { filterAndSortPeers } from './sortPeers' + +describe('filterValidAddresses', () => { + it('filters out invalid addresses', () => { + const valid = [ + '/dns4/gloao6h5plwjy4tdlze24zzgcxll6upq2ex2fmu2ohhyu4gtys4nrjad.onion/tcp/443/ws/p2p/QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSE', + '/dns4/gloao6h5plwjy4tdlze24zzgcxll6upq2ex2fmu2ohhyu4gtys4nrjad.onion/tcp/80/ws/p2p/QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSE', + ] + const addresses = [ + '/dns4/gloao6h5plwjy4tdlze24zzgcxll6upq2ex2fmu2ohhyu4gtys4nrjad.onion/tcp/443/wss/p2p/QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSE', + ...valid, + 'invalidAddress', + '/dns4/somethingElse.onion/tcp/443/wss/p2p/QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSA', + '/dns4/gloao6h5plwjy4tdlze24zzgcxll6upq2ex2fmu2ohhyu4gtys4nrjad.onion/tcp/443/ws/p2p/QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbK', + '/dns4/gloao6h5plwjy4tdlze24zzgcxll6upq2ex2fmu2ohhyu4gtys4nrj.onion/tcp/443/ws/p2p/QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSE', + 'QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbK', + ] + expect(filterAndSortPeers(addresses, [])).toEqual(valid) + }) +}) diff --git a/packages/common/src/libp2p.ts b/packages/common/src/libp2p.ts index b2fb70e83f..4ada2cb6b9 100644 --- a/packages/common/src/libp2p.ts +++ b/packages/common/src/libp2p.ts @@ -17,3 +17,7 @@ export const isPSKcodeValid = (psk: string): boolean => { const _psk = psk.trim() return validator.isBase64(_psk) && _psk.length === PSK_LENGTH } + +export const filterValidAddresses = (addresses: string[]) => { + return addresses.filter(add => add.match(/^\/dns4\/[a-z0-9]{56}.onion\/tcp\/(443|80)\/ws\/p2p\/[a-zA-Z0-9]{46}$/g)) +} diff --git a/packages/common/src/messages.test.ts b/packages/common/src/messages.test.ts new file mode 100644 index 0000000000..1b6dc825e1 --- /dev/null +++ b/packages/common/src/messages.test.ts @@ -0,0 +1,50 @@ +import { PublicChannelStorage } from '@quiet/types' +import { generateChannelId } from './channelAddress' +import { userCreatedChannelMessage, userJoinedMessage, verifyUserInfoMessage } from './messages' + +describe('messages helper', () => { + const username = 'johnny' + + const generalChannel: PublicChannelStorage = { + name: 'general', + description: 'Welcome to #general', + timestamp: 1, + owner: username, + id: generateChannelId('general'), + messages: { ids: [], entities: {} }, + } + + const sportChannel: PublicChannelStorage = { + name: 'sport', + description: 'Welcome to #sport', + timestamp: 1, + owner: username, + id: generateChannelId('sport'), + messages: { ids: [], entities: {} }, + } + it('userCreatedChannelMessage', () => { + const expectedMessage = '@johnny created #sport' + const message = userCreatedChannelMessage(username, sportChannel.name) + expect(message).toEqual(expectedMessage) + }) + + it('userJoinedMessage', () => { + const expectedMessage = + '**@johnny** has joined and will be registered soon. 🎉 [Learn more](https://github.com/TryQuiet/quiet/wiki/Quiet-FAQ#how-does-username-registration-work)' + const message = userJoinedMessage(username) + expect(message).toEqual(expectedMessage) + }) + + it('verifyUserInfoMessage - general channel', () => { + const expectedMessage = + '**@johnny** has joined and will be registered soon. 🎉 [Learn more](https://github.com/TryQuiet/quiet/wiki/Quiet-FAQ#how-does-username-registration-work)' + const message = verifyUserInfoMessage(username, generalChannel) + expect(message).toEqual(expectedMessage) + }) + + it('verifyUserInfoMessage - other channel', () => { + const expectedMessage = '@johnny created #sport' + const message = verifyUserInfoMessage(username, sportChannel) + expect(message).toEqual(expectedMessage) + }) +}) diff --git a/packages/common/src/messages.ts b/packages/common/src/messages.ts new file mode 100644 index 0000000000..83dace3273 --- /dev/null +++ b/packages/common/src/messages.ts @@ -0,0 +1,17 @@ +import { PublicChannelStorage } from '@quiet/types' + +export const userCreatedChannelMessage = (username: string, channelName: string) => + `@${username} created #${channelName}` + +export const generalChannelDeletionMessage = (username: string) => `@${username} deleted all messages in #general` + +export const userJoinedMessage = (username: string) => + `**@${username}** has joined and will be registered soon. 🎉 [Learn more](https://github.com/TryQuiet/quiet/wiki/Quiet-FAQ#how-does-username-registration-work)` + +export const verifyUserInfoMessage = (username: string, channel: PublicChannelStorage) => { + if (channel.name === 'general') { + return userJoinedMessage(username) + } else { + return userCreatedChannelMessage(username, channel.name) + } +} diff --git a/packages/common/src/sortPeers.ts b/packages/common/src/sortPeers.ts index 33d98c06c7..2b92cd1ea8 100644 --- a/packages/common/src/sortPeers.ts +++ b/packages/common/src/sortPeers.ts @@ -1,14 +1,17 @@ import { type NetworkStats } from '@quiet/types' import { isDefined } from './helpers' +import { filterValidAddresses } from './libp2p' /** This is the very simple algorithm for evaluating the most wanted peers. +0. Filters out invalid peers addresses 1. It takes the peers stats list that contains statistics for every peer our node was ever connected to. 2. Two sorted arrays are created - one sorted by last seen and other by most uptime shared. 3. Arrays are merged taking one element from list one and one element from the second list. Duplicates are ommited 4. We end up with mix of last seen and most uptime descending array of peers, the it is enchanced to libp2p address. */ -export const sortPeers = (peersAddresses: string[], stats: NetworkStats[]): string[] => { +export const filterAndSortPeers = (peersAddresses: string[], stats: NetworkStats[]): string[] => { + peersAddresses = filterValidAddresses(peersAddresses) const lastSeenSorted = [...stats].sort((a, b) => { return b.lastSeen - a.lastSeen }) diff --git a/packages/desktop/CHANGELOG.md b/packages/desktop/CHANGELOG.md index 78943be782..c906f7b6d0 100644 --- a/packages/desktop/CHANGELOG.md +++ b/packages/desktop/CHANGELOG.md @@ -3,7 +3,18 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. -## [2.0.3-alpha.5](https://github.com/TryQuiet/quiet/compare/@quiet/desktop@2.0.3-alpha.4...@quiet/desktop@2.0.3-alpha.5) (2023-11-14) +## [2.0.3-alpha.10](https://github.com/TryQuiet/quiet/compare/@quiet/desktop@2.0.3-alpha.9...@quiet/desktop@2.0.3-alpha.10) (2023-11-24) + + +### Features + +* add appPath to notarize ([bd9a0dc](https://github.com/TryQuiet/quiet/commit/bd9a0dc69f9da99405317d8210a14b82e6ac4910)) + + + + + +## [2.0.3-alpha.9](https://github.com/TryQuiet/quiet/compare/@quiet/desktop@2.0.3-alpha.8...@quiet/desktop@2.0.3-alpha.9) (2023-11-24) **Note:** Version bump only for package @quiet/desktop @@ -11,38 +22,65 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline -## [2.0.3-alpha.4](https://github.com/TryQuiet/quiet/compare/@quiet/desktop@2.0.3-alpha.3...@quiet/desktop@2.0.3-alpha.4) (2023-11-14) +## [2.0.3-alpha.8](https://github.com/TryQuiet/quiet/compare/@quiet/desktop@2.0.3-alpha.7...@quiet/desktop@2.0.3-alpha.8) (2023-11-22) ### Features -* trigger desktop ([713e1b8](https://github.com/TryQuiet/quiet/commit/713e1b822b266c218f71742ed68616b8b1056c75)) +* trigger lerna ([4ca8195](https://github.com/TryQuiet/quiet/commit/4ca81958c57e88f172e0d78f055e9008a5a4a90a)) -## [2.0.3-alpha.3](https://github.com/TryQuiet/quiet/compare/@quiet/desktop@2.0.3-alpha.2...@quiet/desktop@2.0.3-alpha.3) (2023-11-13) +## [2.0.3-alpha.7](https://github.com/TryQuiet/quiet/compare/@quiet/desktop@2.0.3-alpha.6...@quiet/desktop@2.0.3-alpha.7) (2023-11-22) + +**Note:** Version bump only for package @quiet/desktop + + + + + +## [2.0.3-alpha.6](https://github.com/TryQuiet/quiet/compare/@quiet/desktop@2.0.3-alpha.5...@quiet/desktop@2.0.3-alpha.6) (2023-11-21) + +**Note:** Version bump only for package @quiet/desktop + + + + + +## [2.0.3-alpha.5](https://github.com/TryQuiet/quiet/compare/@quiet/desktop@2.0.3-alpha.4...@quiet/desktop@2.0.3-alpha.5) (2023-11-14) + +**Note:** Version bump only for package @quiet/desktop + + + + + +## [2.0.3-alpha.4](https://github.com/TryQuiet/quiet/compare/@quiet/desktop@2.0.3-alpha.3...@quiet/desktop@2.0.3-alpha.4) (2023-11-14) ### Features -* add debug logs ([#2057](https://github.com/TryQuiet/quiet/issues/2057)) ([aa3e777](https://github.com/TryQuiet/quiet/commit/aa3e777778b0861d5f96e6116bfc70031ed67929)) +* trigger desktop ([713e1b8](https://github.com/TryQuiet/quiet/commit/713e1b822b266c218f71742ed68616b8b1056c75)) -## [2.0.3-alpha.2](https://github.com/TryQuiet/quiet/compare/@quiet/desktop@2.0.3-alpha.1...@quiet/desktop@2.0.3-alpha.2) (2023-11-09) +## [2.0.3-alpha.3](https://github.com/TryQuiet/quiet/compare/@quiet/desktop@2.0.3-alpha.2...@quiet/desktop@2.0.3-alpha.3) (2023-11-13) -### Bug Fixes +### Features + +* add debug logs ([#2057](https://github.com/TryQuiet/quiet/issues/2057)) ([aa3e777](https://github.com/TryQuiet/quiet/commit/aa3e777778b0861d5f96e6116bfc70031ed67929)) -* trigger desktop ([2898bee](https://github.com/TryQuiet/quiet/commit/2898bee80bbf2f16cbda67281a29e47716faa77c)) +## [2.0.3-alpha.2](https://github.com/TryQuiet/quiet/compare/@quiet/desktop@2.0.3-alpha.1...@quiet/desktop@2.0.3-alpha.2) (2023-11-09) + ## [2.0.3-alpha.1](https://github.com/TryQuiet/quiet/compare/@quiet/desktop@2.0.3-alpha.0...@quiet/desktop@2.0.3-alpha.1) (2023-11-08) @@ -132,6 +170,19 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline ## [2.0.1-alpha.1](https://github.com/TryQuiet/quiet/compare/quiet@2.0.1-alpha.0...quiet@2.0.1-alpha.1) (2023-09-25) + +## [1.9.7](https://github.com/TryQuiet/quiet/compare/quiet@1.9.6...quiet@1.9.7) (2023-11-17) + + +### Bug Fixes + +* trigger desktop ([2898bee](https://github.com/TryQuiet/quiet/commit/2898bee80bbf2f16cbda67281a29e47716faa77c)) +* pass team id for notarization ([ab86fc0](https://github.com/TryQuiet/quiet/commit/ab86fc0cefd5d8b3715712a4dd234bbe45f18cc2)) + + + +## [1.9.6](https://github.com/TryQuiet/quiet/compare/quiet@1.9.5...quiet@1.9.6) (2023-11-09) + **Note:** Version bump only for package quiet diff --git a/packages/desktop/package-lock.json b/packages/desktop/package-lock.json index 62347f7ab7..d623077d56 100644 --- a/packages/desktop/package-lock.json +++ b/packages/desktop/package-lock.json @@ -1,12 +1,12 @@ { "name": "@quiet/desktop", - "version": "2.0.3-alpha.5", + "version": "2.0.3-alpha.10", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@quiet/desktop", - "version": "2.0.3-alpha.5", + "version": "2.0.3-alpha.10", "license": "ISC", "dependencies": { "@electron/remote": "^2.0.8", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 42dd73f5ff..6d54258c66 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -80,7 +80,7 @@ }, "homepage": "https://github.com/TryQuiet", "@comment version": "To build new version for specific platform, just replace platform in version tag to one of following linux, mac, windows", - "version": "2.0.3-alpha.5", + "version": "2.0.3-alpha.10", "description": "Decentralized team chat", "main": "dist/main/main.js", "scripts": { @@ -111,7 +111,7 @@ "build:renderer:prod": "webpack --config webpack/webpack.config.renderer.prod.js", "postBuild": "node scripts/postBuild.js", "prestart": "npm run build:main", - "start": "cross-env DEBUG='backend*,quiet*,state-manager*,desktop*,utils*,libp2p:websockets:listener:backend' npm run start:renderer", + "start": "cross-env DEBUG='backend*,quiet*,state-manager*,desktop*,utils*,libp2p:websockets:listener:backend,libp2p:connection-manager:auto-dialler' npm run start:renderer", "start:main": "cross-env NODE_ENV=development electron .", "start:renderer": "cross-env NODE_ENV=development webpack-dev-server --config webpack/webpack.config.renderer.dev.js", "storybook": "export NODE_OPTIONS=--openssl-legacy-provider && start-storybook -p 6006", diff --git a/packages/desktop/scripts/notarize.js b/packages/desktop/scripts/notarize.js index f60c6b64e1..4e3b870bc8 100644 --- a/packages/desktop/scripts/notarize.js +++ b/packages/desktop/scripts/notarize.js @@ -3,23 +3,24 @@ const { notarize } = require('@electron/notarize') exports.default = async function notarizing(context) { const { electronPlatformName, appOutDir } = context + const appName = context.packager.appInfo.productFilename + if (electronPlatformName !== 'darwin' || process.env.IS_E2E) { console.log('skipping notarization') return } - const appName = context.packager.appInfo.productFilename - console.log('notarization start') - try { - const response = await notarize({ - appBundleId: 'com.yourcompany.yourAppId', - appPath: `${appOutDir}/${appName}.app`, - appleId: process.env.APPLEID, - appleIdPassword: process.env.APPLEIDPASS - }) - console.log('notarization done') - return response - } catch (e) { - console.error(e) - } + console.log('notarization started') + + const response = await notarize({ + tool: 'notarytool', + appPath: `${appOutDir}/${appName}.app`, + appleId: process.env.APPLE_ID, + appleIdPassword: process.env.APPLE_ID_PASS, + teamId: process.env.APPLE_TEAM_ID, + }) + + console.log('notarization done') + + return response } diff --git a/packages/desktop/src/main/main.ts b/packages/desktop/src/main/main.ts index c6c74db976..24174c5dd9 100644 --- a/packages/desktop/src/main/main.ts +++ b/packages/desktop/src/main/main.ts @@ -102,6 +102,8 @@ setEngine( }) ) +const SOCKET_IO_SECRET = webcrypto.getRandomValues(new Uint32Array(5)).join('') + export const isBrowserWindow = (window: BrowserWindow | null): window is BrowserWindow => { return window instanceof BrowserWindow } @@ -208,7 +210,7 @@ export const createWindow = async () => { mainWindow.loadURL( url.format({ pathname: path.join(__dirname, './index.html'), - search: `dataPort=${ports.dataServer}`, + search: `dataPort=${ports.dataServer}&socketIOSecret=${SOCKET_IO_SECRET}`, protocol: 'file:', slashes: true, hash: '/', @@ -333,6 +335,7 @@ app.on('ready', async () => { await createWindow() mainWindow?.webContents.on('did-finish-load', () => { + mainWindow?.webContents.send('socketIOSecret', SOCKET_IO_SECRET) if (splash && !splash.isDestroyed()) { const [width, height] = splash.getSize() mainWindow?.setSize(width, height) @@ -365,6 +368,8 @@ app.on('ready', async () => { `${process.resourcesPath}`, '-p', 'desktop', + '-scrt', + `${SOCKET_IO_SECRET}`, ] const backendBundlePath = path.normalize(require.resolve('backend-bundle')) diff --git a/packages/desktop/src/renderer/Root.tsx b/packages/desktop/src/renderer/Root.tsx index 608060d374..3cd16b9876 100644 --- a/packages/desktop/src/renderer/Root.tsx +++ b/packages/desktop/src/renderer/Root.tsx @@ -33,6 +33,8 @@ import UnregisteredModalContainer from './components/widgets/userLabel/unregiste import DuplicateModalContainer from './components/widgets/userLabel/duplicate/DuplicateModal.container' import UsernameTakenModalContainer from './components/widgets/usernameTakenModal/UsernameTakenModal.container' import PossibleImpersonationAttackModalContainer from './components/widgets/possibleImpersonationAttackModal/PossibleImpersonationAttackModal.container' +import BreakingChangesWarning from './containers/widgets/breakingChangesWarning/BreakingChangesWarning' +// Trigger lerna export const persistor = persistStore(store) export default () => { @@ -61,6 +63,7 @@ export default () => { + diff --git a/packages/desktop/src/renderer/components/ContextMenu/menus/ChannelContextMenu.container.tsx b/packages/desktop/src/renderer/components/ContextMenu/menus/ChannelContextMenu.container.tsx index d0ff7d555d..2ce53a7a48 100644 --- a/packages/desktop/src/renderer/components/ContextMenu/menus/ChannelContextMenu.container.tsx +++ b/packages/desktop/src/renderer/components/ContextMenu/menus/ChannelContextMenu.container.tsx @@ -10,10 +10,12 @@ import { ContextMenuItemProps } from '../ContextMenu.types' import { useModal } from '../../../containers/hooks' import { ModalName } from '../../../sagas/modals/modals.types' +import { exportChats } from '../../../../utils/functions/exportMessages' export const ChannelContextMenu: FC = () => { const community = useSelector(communities.selectors.currentCommunity) const channel = useSelector(publicChannels.selectors.currentChannel) + const channelMessages = useSelector(publicChannels.selectors.currentChannelMessagesMergedBySender) let title = '' if (channel) { @@ -36,6 +38,10 @@ export const ChannelContextMenu: FC = () => { deleteChannelModal.handleOpen() }, }, + { + title: 'Export messages', + action: () => channel && exportChats(channel?.name, channelMessages), + }, ] } diff --git a/packages/desktop/src/renderer/components/widgets/channels/BasicMessage.tsx b/packages/desktop/src/renderer/components/widgets/channels/BasicMessage.tsx index 460d577e7e..b4f7e19cdb 100644 --- a/packages/desktop/src/renderer/components/widgets/channels/BasicMessage.tsx +++ b/packages/desktop/src/renderer/components/widgets/channels/BasicMessage.tsx @@ -174,8 +174,8 @@ export const BasicMessageComponent: React.FC = args => { + return +} + +const args: UpdateModalProps = { + open: true, + handleClose: function (): void { + console.log('modal closed') + }, + buttons: [ + , + ], + title: 'Software update', + message: 'An update is available for Quiet.', +} + +export const Component = Template.bind({}) + +Component.args = args + +const component: ComponentMeta = { + title: 'Components/UpdateModalComponent', + decorators: [withTheme], + component: UpdateModal, +} + +export default component diff --git a/packages/desktop/src/renderer/components/widgets/update/UpdateModal.test.tsx b/packages/desktop/src/renderer/components/widgets/update/UpdateModalComponent.test.tsx similarity index 54% rename from packages/desktop/src/renderer/components/widgets/update/UpdateModal.test.tsx rename to packages/desktop/src/renderer/components/widgets/update/UpdateModalComponent.test.tsx index d29212be1e..1a968eca97 100644 --- a/packages/desktop/src/renderer/components/widgets/update/UpdateModal.test.tsx +++ b/packages/desktop/src/renderer/components/widgets/update/UpdateModalComponent.test.tsx @@ -1,11 +1,19 @@ import React from 'react' -import { UpdateModal } from './UpdateModal' import { renderComponent } from '../../../testUtils/renderComponent' +import UpdateModalComponent from './UpdateModalComponent' describe('UpdateModal', () => { it('renders component', () => { - const result = renderComponent() + const result = renderComponent( + + ) expect(result.baseElement).toMatchInlineSnapshot(` { style="width: 600px;" >
-
- -
+
-
-

- Software update -

-
+ Software update +
-
-

- A new update for Quiet is available and will be applied on your next restart. -

-
+ Update is available for Quiet. +

-
- -
-
-
- -
+ class="MuiGrid-root MuiGrid-container MuiGrid-spacing-xs-2 MuiGrid-direction-xs-column css-1bnhfwg-MuiGrid-root" + />
diff --git a/packages/desktop/src/renderer/components/widgets/update/UpdateModalComponent.tsx b/packages/desktop/src/renderer/components/widgets/update/UpdateModalComponent.tsx new file mode 100644 index 0000000000..0ffd86edbb --- /dev/null +++ b/packages/desktop/src/renderer/components/widgets/update/UpdateModalComponent.tsx @@ -0,0 +1,79 @@ +import React, { ReactElement } from 'react' + +import { styled } from '@mui/material/styles' + +import Typography from '@mui/material/Typography' +import Grid from '@mui/material/Grid' + +import Icon from '../../ui/Icon/Icon' +import updateIcon from '../../../static/images/updateIcon.svg' +import Modal from '../../ui/Modal/Modal' + +const PREFIX = 'UpdateModal' + +const classes = { + info: `${PREFIX}info`, + updateIcon: `${PREFIX}updateIcon`, + title: `${PREFIX}title`, + message: `${PREFIX}message`, +} + +const StyledModalContent = styled(Grid)(({ theme }) => ({ + backgroundColor: theme.palette.colors.white, + border: 'none', + + [`& .${classes.info}`]: { + marginTop: 38, + }, + + [`& .${classes.updateIcon}`]: { + width: 102, + height: 102, + }, + + [`& .${classes.title}`]: { + marginTop: 24, + marginBottom: 16, + textAlign: 'center', + }, + + [`& .${classes.message}`]: { + marginBottom: 32, + textAlign: 'center', + }, +})) + +export interface UpdateModalProps { + open: boolean + handleClose: () => void + buttons: ReactElement[] + title: string + message: string +} + +export const UpdateModalComponent: React.FC = ({ open, handleClose, buttons, title, message }) => { + return ( + + + + + + + {title} + + + {message} + + + {buttons.map((button, index) => ( + + {button} + + ))} + + + + ) +} + +export default UpdateModalComponent diff --git a/packages/desktop/src/renderer/containers/ui/QuitAppDialog.tsx b/packages/desktop/src/renderer/containers/ui/QuitAppDialog.tsx index ac5aeba210..5fbe6d6875 100644 --- a/packages/desktop/src/renderer/containers/ui/QuitAppDialog.tsx +++ b/packages/desktop/src/renderer/containers/ui/QuitAppDialog.tsx @@ -1,5 +1,4 @@ import React from 'react' - import { useModal } from '../hooks' import { ModalName } from '../../sagas/modals/modals.types' import QuitAppDialog from '../../components/ui/QuitApp/QuitAppDialog' diff --git a/packages/desktop/src/renderer/containers/widgets/breakingChangesWarning/BreakingChangesWarning.tsx b/packages/desktop/src/renderer/containers/widgets/breakingChangesWarning/BreakingChangesWarning.tsx new file mode 100644 index 0000000000..9ef8817dca --- /dev/null +++ b/packages/desktop/src/renderer/containers/widgets/breakingChangesWarning/BreakingChangesWarning.tsx @@ -0,0 +1,63 @@ +import React, { useCallback, useEffect } from 'react' +import { ModalName } from '../../../sagas/modals/modals.types' +import { useModal } from '../../hooks' +import UpdateModalComponent from '../../../components/widgets/update/UpdateModalComponent' + +import Button from '@mui/material/Button' +import theme from '../../../theme' + +import { shell } from 'electron' +import { Site } from '@quiet/common' + +const BreakingChangesWarning = () => { + const modal = useModal(ModalName.breakingChangesWarning) + + const title = 'Update available' + const message = + 'Quiet’s next release makes joining communities faster and more reliable by letting people join when the owner is offline! 🎉 However, these changes are not backwards compatible, so you must re-install Quiet from tryquiet.org and re-create or re-join your community. 😥 This version of Quiet will no longer receive any updates or security fixes, so please re-install soon. We apologize for the inconvenience.' + + const updateAction = useCallback(() => { + shell.openExternal(`${Site.MAIN_PAGE}#Downloads`) + }, []) + + const updateButton = ( + + ) + + const dismissButton = ( + + ) + + return +} + +export default BreakingChangesWarning diff --git a/packages/desktop/src/renderer/containers/widgets/update/UpdateModal.test.tsx b/packages/desktop/src/renderer/containers/widgets/update/UpdateModal.test.tsx new file mode 100644 index 0000000000..25bbb22ed1 --- /dev/null +++ b/packages/desktop/src/renderer/containers/widgets/update/UpdateModal.test.tsx @@ -0,0 +1,35 @@ +import React from 'react' +import { jest } from '@jest/globals' +import { fireEvent, screen } from '@testing-library/dom' +import { prepareStore, renderComponent } from '../../../testUtils' +import { StoreKeys } from '@quiet/state-manager' +import { ModalsInitialState } from '../../../sagas/modals/modals.slice' +import { ModalName } from '../../../sagas/modals/modals.types' +import * as UpdateModal from './UpdateModal' + +describe('Update Modal', () => { + test('triggers app update on button click', async () => { + const { store } = await prepareStore({ + [StoreKeys.Modals]: { + ...new ModalsInitialState(), + [ModalName.applicationUpdate]: { open: true, args: {} }, + }, + }) + + const update = jest.fn() + + const modal = + + // @ts-expect-error + jest.spyOn(UpdateModal, 'mapDispatchToProps').mockImplementation(() => ({ + handleUpdate: update, + })) + + renderComponent(modal, store) + + const button = screen.getByText('Update now') + fireEvent.click(button) + + expect(update).toHaveBeenCalled() + }) +}) diff --git a/packages/desktop/src/renderer/containers/widgets/update/UpdateModal.tsx b/packages/desktop/src/renderer/containers/widgets/update/UpdateModal.tsx index 30096a2c84..b2a54ad345 100644 --- a/packages/desktop/src/renderer/containers/widgets/update/UpdateModal.tsx +++ b/packages/desktop/src/renderer/containers/widgets/update/UpdateModal.tsx @@ -1,11 +1,16 @@ import React from 'react' import { AnyAction, Dispatch, bindActionCreators } from 'redux' import { useDispatch } from 'react-redux' -import UpdateModal from '../../../components/widgets/update/UpdateModal' import updateHandlers from '../../../store/handlers/update' + import { useModal } from '../../hooks' import { ModalName } from '../../../sagas/modals/modals.types' +import UpdateModalComponent from '../../../components/widgets/update/UpdateModalComponent' + +import Button from '@mui/material/Button' +import theme from '../../../theme' + export const mapDispatchToProps = (dispatch: Dispatch) => bindActionCreators( { @@ -17,8 +22,32 @@ export const mapDispatchToProps = (dispatch: Dispatch) => const ApplicationUpdateModal: React.FC = () => { const dispatch = useDispatch() + const actions = mapDispatchToProps(dispatch) const modal = useModal(ModalName.applicationUpdate) - return + + const title = 'Software update' + const message = 'An update is availale for Quiet.' + + const button = ( + + ) + + return } + export default ApplicationUpdateModal diff --git a/packages/desktop/src/renderer/index.tsx b/packages/desktop/src/renderer/index.tsx index 49a82e3f13..3c478402b2 100644 --- a/packages/desktop/src/renderer/index.tsx +++ b/packages/desktop/src/renderer/index.tsx @@ -1,11 +1,10 @@ import React from 'react' import { createRoot } from 'react-dom/client' import { ipcRenderer } from 'electron' - import Root, { persistor } from './Root' import store from './store' import updateHandlers from './store/handlers/update' -import { communities } from '@quiet/state-manager' +import { communities, connection } from '@quiet/state-manager' import { InvitationData } from '@quiet/types' if (window && process.env.DEBUG) { @@ -27,6 +26,10 @@ ipcRenderer.on('invitation', (_event, invitation: { data: InvitationData }) => { store.dispatch(communities.actions.customProtocol(invitation.data)) }) +ipcRenderer.on('socketIOSecret', (_event, socketIOSecret) => { + store.dispatch(connection.actions.setSocketIOSecret(socketIOSecret)) +}) + const container = document.getElementById('root') if (!container) throw new Error('No root html element!') let root = createRoot(container) diff --git a/packages/desktop/src/renderer/sagas/modals/modals.slice.ts b/packages/desktop/src/renderer/sagas/modals/modals.slice.ts index fee515645f..31b217a520 100644 --- a/packages/desktop/src/renderer/sagas/modals/modals.slice.ts +++ b/packages/desktop/src/renderer/sagas/modals/modals.slice.ts @@ -37,7 +37,8 @@ export class ModalsInitialState { [ModalName.unregisteredUsernameModal] = { open: false, args: {} }; [ModalName.duplicatedUsernameModal] = { open: false, args: {} }; [ModalName.usernameTakenModal] = { open: false, args: {} }; - [ModalName.possibleImpersonationAttackModal] = { open: false, args: {} } + [ModalName.possibleImpersonationAttackModal] = { open: false, args: {} }; + [ModalName.breakingChangesWarning] = { open: false, args: {} }; } export const modalsSlice = createSlice({ diff --git a/packages/desktop/src/renderer/sagas/modals/modals.types.ts b/packages/desktop/src/renderer/sagas/modals/modals.types.ts index d79e2518e1..ddc4b9e8b3 100644 --- a/packages/desktop/src/renderer/sagas/modals/modals.types.ts +++ b/packages/desktop/src/renderer/sagas/modals/modals.types.ts @@ -1,5 +1,6 @@ export enum ModalName { applicationUpdate = 'applicationUpdate', + breakingChangesWarning = 'breakingChangesWarning', createChannel = 'createChannel', deleteChannel = 'deleteChannel', accountSettingsModal = 'accountSettingsModal', diff --git a/packages/desktop/src/renderer/sagas/socket/socket.saga.test.ts b/packages/desktop/src/renderer/sagas/socket/socket.saga.test.ts new file mode 100644 index 0000000000..d6c199291f --- /dev/null +++ b/packages/desktop/src/renderer/sagas/socket/socket.saga.test.ts @@ -0,0 +1,41 @@ +import { connection, getFactory, Store } from '@quiet/state-manager' +import { FactoryGirl } from 'factory-girl' +import { expectSaga } from 'redux-saga-test-plan' +import { socketActions, WebsocketConnectionPayload } from '../socket/socket.slice' +import { prepareStore } from '../../testUtils/prepareStore' +import { startConnectionSaga } from './socket.saga' + +describe('Start Connection Saga', () => { + const dataPort = 1234 + let store: Store + let factory: FactoryGirl + + beforeEach(async () => { + store = (await prepareStore()).store + factory = await getFactory(store) + }) + + it('socketIOSecret is null - take setSocketIOSecret', async () => { + const payload: WebsocketConnectionPayload = { + dataPort, + } + + await expectSaga(startConnectionSaga, socketActions.startConnection(payload)) + .withState(store.getState()) + .take(connection.actions.setSocketIOSecret) + .run() + }) + + it('socketIOSecret already exist', async () => { + const payload: WebsocketConnectionPayload = { + dataPort, + } + + store.dispatch(connection.actions.setSocketIOSecret('secret')) + + await expectSaga(startConnectionSaga, socketActions.startConnection(payload)) + .withState(store.getState()) + .not.take(connection.actions.setSocketIOSecret) + .run() + }) +}) diff --git a/packages/desktop/src/renderer/sagas/socket/socket.saga.ts b/packages/desktop/src/renderer/sagas/socket/socket.saga.ts index 03646371b5..99037aed24 100644 --- a/packages/desktop/src/renderer/sagas/socket/socket.saga.ts +++ b/packages/desktop/src/renderer/sagas/socket/socket.saga.ts @@ -1,22 +1,39 @@ import { io, Socket } from 'socket.io-client' -import { all, fork, takeEvery, call, put, cancel, FixedTask } from 'typed-redux-saga' +import { all, fork, takeEvery, call, put, cancel, FixedTask, select, take } from 'typed-redux-saga' import { PayloadAction } from '@reduxjs/toolkit' -import { socket as stateManager, messages } from '@quiet/state-manager' +import { socket as stateManager, messages, connection } from '@quiet/state-manager' import { socketActions } from './socket.slice' import { eventChannel } from 'redux-saga' import { displayMessageNotificationSaga } from '../notifications/notifications.saga' - import logger from '../../logger' +import { encodeSecret } from '@quiet/common' + const log = logger('socket') export function* startConnectionSaga( action: PayloadAction['payload']> ): Generator { - const dataPort = action.payload.dataPort + const { dataPort } = action.payload if (!dataPort) { log.error('About to start connection but no dataPort found') } - const socket = yield* call(io, `http://127.0.0.1:${dataPort}`) + + let socketIOSecret = yield* select(connection.selectors.socketIOSecret) + + if (!socketIOSecret) { + yield* take(connection.actions.setSocketIOSecret) + socketIOSecret = yield* select(connection.selectors.socketIOSecret) + } + + if (!socketIOSecret) return + + const token = encodeSecret(socketIOSecret) + const socket = yield* call(io, `http://127.0.0.1:${dataPort}`, { + withCredentials: true, + extraHeaders: { + authorization: `Basic ${token}`, + }, + }) yield* fork(handleSocketLifecycleActions, socket) // Handle opening/restoring connection diff --git a/packages/desktop/src/renderer/testUtils/prepareStore.ts b/packages/desktop/src/renderer/testUtils/prepareStore.ts index 6a8f15d65c..aff3b9f127 100644 --- a/packages/desktop/src/renderer/testUtils/prepareStore.ts +++ b/packages/desktop/src/renderer/testUtils/prepareStore.ts @@ -119,5 +119,6 @@ function* mockSocketConnectionSaga(socket: MockedSocket): Generator { socket.socketClient.emit('connect') }) }) + yield* put(connection.actions.setSocketIOSecret('socketIOSecret')) yield* put(socketActions.startConnection({ dataPort: 4677 })) } diff --git a/packages/desktop/src/shared/static.ts b/packages/desktop/src/shared/static.ts index 007634973e..b0a95acfd7 100644 --- a/packages/desktop/src/shared/static.ts +++ b/packages/desktop/src/shared/static.ts @@ -1,7 +1,7 @@ import mirrorKey from 'keymirror' export const DEV_DATA_DIR = 'Quietdev' -export const DATA_DIR = 'Quiet' +export const DATA_DIR = 'Quiet2' export const actionTypes = mirrorKey({ SET_APP_VERSION: undefined, diff --git a/packages/desktop/src/utils/functions/__snapshots__/exportMessages.test.ts.snap b/packages/desktop/src/utils/functions/__snapshots__/exportMessages.test.ts.snap new file mode 100644 index 0000000000..55a04b311d --- /dev/null +++ b/packages/desktop/src/utils/functions/__snapshots__/exportMessages.test.ts.snap @@ -0,0 +1,23 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`channelMessagesToText should convert channel messages to text format: + "[JohnDoe Nov 22, 12:18 PM] + Hello, World! + + [JaneDoe Nov 22, 12:18 PM] + How are you? + + [Alice Nov 22, 1:14 PM] + Hi there! + 1`] = ` +"[JohnDoe Nov 22, 12:18 PM] +Hello, World! + +[JaneDoe Nov 22, 12:18 PM] +How are you? + +[Alice Nov 22, 1:14 PM] +Hi there! + +" +`; diff --git a/packages/desktop/src/utils/functions/exportMessages.test.ts b/packages/desktop/src/utils/functions/exportMessages.test.ts new file mode 100644 index 0000000000..ef7b31368a --- /dev/null +++ b/packages/desktop/src/utils/functions/exportMessages.test.ts @@ -0,0 +1,58 @@ +import { channelMessagesToText } from './exportMessages' +import { MessagesDailyGroups } from '@quiet/types' + +const messages: MessagesDailyGroups = { + '2023-11-22': [ + [ + { + id: '1', + type: 1, + message: 'Hello, World!', + createdAt: 1637547600, + date: 'Nov 22, 12:18 PM', + nickname: 'JohnDoe', + isRegistered: true, + isDuplicated: false, + pubKey: 'public_key', + }, + { + id: '2', + type: 1, + message: 'How are you?', + createdAt: 1637551200, + date: 'Nov 22, 12:18 PM', + nickname: 'JaneDoe', + isRegistered: true, + isDuplicated: false, + pubKey: 'public_key_2', + }, + { + id: '3', + type: 1, + message: 'Hi there!', + createdAt: 1637554800, + date: 'Nov 22, 1:14 PM', + nickname: 'Alice', + isRegistered: true, + isDuplicated: false, + pubKey: 'public_key_3', + }, + ], + ], +} + +describe('channelMessagesToText', () => { + it('should convert channel messages to text format', () => { + const result = channelMessagesToText(messages) + expect(result).toMatchSnapshot(` + "[JohnDoe Nov 22, 12:18 PM] + Hello, World! + + [JaneDoe Nov 22, 12:18 PM] + How are you? + + [Alice Nov 22, 1:14 PM] + Hi there! + `) + }) +}) diff --git a/packages/desktop/src/utils/functions/exportMessages.ts b/packages/desktop/src/utils/functions/exportMessages.ts new file mode 100644 index 0000000000..e238482f52 --- /dev/null +++ b/packages/desktop/src/utils/functions/exportMessages.ts @@ -0,0 +1,39 @@ +import { MessagesDailyGroups } from '@quiet/types' +import { dialog } from '@electron/remote' +import fs from 'fs' + +export const exportChats = async (channelName: string, channelMessages: MessagesDailyGroups) => { + dialog + .showSaveDialog({ + title: 'Save file', + defaultPath: `${channelName}.txt`, + buttonLabel: 'Save', + + filters: [ + { name: 'txt', extensions: ['txt'] }, + { name: 'All Files', extensions: ['*'] }, + ], + }) + .then(({ filePath }) => { + if (filePath) { + fs.writeFile(filePath, channelMessagesToText(channelMessages), err => { + if (err) { + console.log(err) + } + }) + } + }) +} + +// This function is exported just to test it +export const channelMessagesToText = (channelMessages: MessagesDailyGroups) => { + return Object.keys(channelMessages) + .map(day => + channelMessages[day] + .map(messages => + messages.map(message => `[${message.nickname} ${message.date}]\n${message.message}\n\n`).join('') + ) + .join('') + ) + .join('\n') +} diff --git a/packages/e2e-tests/CHANGELOG.md b/packages/e2e-tests/CHANGELOG.md index 7866fc693b..a40de9a683 100644 --- a/packages/e2e-tests/CHANGELOG.md +++ b/packages/e2e-tests/CHANGELOG.md @@ -127,6 +127,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline # [1.9.0-alpha.0](/compare/e2e-tests@1.8.0...e2e-tests@1.9.0-alpha.0) (2023-08-29) +## [1.8.3](https://github.com/TryQuiet/quiet/compare/e2e-tests@1.8.2...e2e-tests@1.8.3) (2023-11-09) **Note:** Version bump only for package e2e-tests diff --git a/packages/e2e-tests/src/tests/backwardsCompatibility.test.ts b/packages/e2e-tests/src/tests/backwardsCompatibility.test.ts index eb463ef500..72cbd8eab9 100644 --- a/packages/e2e-tests/src/tests/backwardsCompatibility.test.ts +++ b/packages/e2e-tests/src/tests/backwardsCompatibility.test.ts @@ -19,8 +19,8 @@ describe.skip('Backwards Compatibility', () => { let generalChannel: Channel let secondChannel: Channel let messagesToCompare: WebElement[] - let sidebar: Sidebar + const dataDir = `e2e_${(Math.random() * 10 ** 18).toString(36)}` const communityName = 'testcommunity' const ownerUsername = 'bob' @@ -28,6 +28,8 @@ describe.skip('Backwards Compatibility', () => { const loopMessages = 'abc'.split('') const newChannelName = 'mid-night-club' + const isAlpha = process.env.FILE_NAME?.toString().includes('alpha') + beforeAll(async () => { ownerAppOldVersion = new App({ dataDir, fileName: 'Quiet-1.2.0-copy.AppImage' }) }) @@ -141,7 +143,7 @@ describe.skip('Backwards Compatibility', () => { await ownerAppNewVersion.open() }) - if (process.env.TEST_MODE) { + if (process.env.TEST_MODE && isAlpha) { it('Close debug modal', async () => { console.log('New version', 2) const debugModal = new DebugModeModal(ownerAppNewVersion.driver) diff --git a/packages/e2e-tests/src/tests/invitationLink.test.ts b/packages/e2e-tests/src/tests/invitationLink.test.ts index 00e6932b85..8956672981 100644 --- a/packages/e2e-tests/src/tests/invitationLink.test.ts +++ b/packages/e2e-tests/src/tests/invitationLink.test.ts @@ -9,7 +9,7 @@ import { Sidebar, WarningModal, } from '../selectors' -import { capitalizeFirstLetter, composeInvitationDeepUrl, parseInvitationCode } from '@quiet/common' +import { capitalizeFirstLetter, composeInvitationDeepUrl, parseInvitationCode, userJoinedMessage } from '@quiet/common' import { execSync } from 'child_process' import { type SupportedPlatformDesktop } from '@quiet/types' @@ -184,10 +184,13 @@ describe('New user joins using invitation link while having app opened', () => { console.log('Invitation Link', 21) const generalChannel = new Channel(ownerApp.driver, 'general') await generalChannel.element.isDisplayed() - const userJoinedMessage = await generalChannel.getMessage( - `@${joiningUserUsername} has joined ${capitalizeFirstLetter(communityName)}!` + + const hasMessage = await generalChannel.waitForUserMessage( + joiningUserUsername, + userJoinedMessage(joiningUserUsername) ) - expect(await userJoinedMessage.isDisplayed()).toBeTruthy() + const isMessageDisplayed = await hasMessage?.isDisplayed() + expect(isMessageDisplayed).toBeTruthy() }) }) }) diff --git a/packages/e2e-tests/src/tests/multipleClients.test.ts b/packages/e2e-tests/src/tests/multipleClients.test.ts index 600525618d..093c783379 100644 --- a/packages/e2e-tests/src/tests/multipleClients.test.ts +++ b/packages/e2e-tests/src/tests/multipleClients.test.ts @@ -186,7 +186,7 @@ describe('Multiple Clients', () => { const messages2 = await generalChannelUser1.getUserMessages(users.user1.username) const messages1 = await generalChannelUser1.getUserMessages(users.owner.username) console.log({ messages1, messages2 }) - const text2 = await messages2[0].getText() + const text2 = await messages2[1].getText() expect(text2).toEqual(users.user1.messages[0]) }) it('First user opens the settings tab and copies updated invitation code', async () => { diff --git a/packages/integration-tests/CHANGELOG.md b/packages/integration-tests/CHANGELOG.md index 756e9508a6..d874888668 100644 --- a/packages/integration-tests/CHANGELOG.md +++ b/packages/integration-tests/CHANGELOG.md @@ -140,6 +140,12 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline # [1.10.0-alpha.0](/compare/integration-tests@1.9.0...integration-tests@1.10.0-alpha.0) (2023-08-29) + + +## [1.9.2](https://github.com/TryQuiet/quiet/compare/integration-tests@1.9.1...integration-tests@1.9.2) (2023-11-09) + +**Note:** Version bump only for package integration-tests + ## [1.9.1](https://github.com/TryQuiet/quiet/compare/integration-tests@1.9.0...integration-tests@1.9.1) (2023-09-15) **Note:** Version bump only for package integration-tests diff --git a/packages/mobile/.storybook/index.js b/packages/mobile/.storybook/index.js index 703004a968..2042c91936 100644 --- a/packages/mobile/.storybook/index.js +++ b/packages/mobile/.storybook/index.js @@ -27,6 +27,7 @@ configure(() => { require('../src/components/DeleteChannel/DeleteChannel.stories') require('../src/components/QRCode/QRCode.stories') require('../src/components/Message/Message.stories') + require('../src/components/Notifier/Notifier.stories') require('../src/components/Chat/Chat.stories') require('../src/components/TextWithLink/TextWithLink.stories') require('../src/components/Typography/Typography.stories') diff --git a/packages/mobile/CHANGELOG.md b/packages/mobile/CHANGELOG.md index e213a976cd..67495aea83 100644 --- a/packages/mobile/CHANGELOG.md +++ b/packages/mobile/CHANGELOG.md @@ -3,6 +3,49 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.0.3-alpha.11](https://github.com/TryQuiet/quiet/compare/@quiet/mobile@2.0.3-alpha.10...@quiet/mobile@2.0.3-alpha.11) (2023-11-24) + +**Note:** Version bump only for package @quiet/mobile + + + + + +## [2.0.3-alpha.10](https://github.com/TryQuiet/quiet/compare/@quiet/mobile@2.0.3-alpha.9...@quiet/mobile@2.0.3-alpha.10) (2023-11-24) + +**Note:** Version bump only for package @quiet/mobile + + + + + +## [2.0.3-alpha.9](https://github.com/TryQuiet/quiet/compare/@quiet/mobile@2.0.3-alpha.8...@quiet/mobile@2.0.3-alpha.9) (2023-11-22) + + +### Features + +* trigger lerna ([4ca8195](https://github.com/TryQuiet/quiet/commit/4ca81958c57e88f172e0d78f055e9008a5a4a90a)) + + + + + +## [2.0.3-alpha.8](https://github.com/TryQuiet/quiet/compare/@quiet/mobile@2.0.3-alpha.7...@quiet/mobile@2.0.3-alpha.8) (2023-11-22) + +**Note:** Version bump only for package @quiet/mobile + + + + + +## [2.0.3-alpha.7](https://github.com/TryQuiet/quiet/compare/@quiet/mobile@2.0.3-alpha.6...@quiet/mobile@2.0.3-alpha.7) (2023-11-21) + +**Note:** Version bump only for package @quiet/mobile + + + + + ## [2.0.3-alpha.6](https://github.com/TryQuiet/quiet/compare/@quiet/mobile@2.0.3-alpha.5...@quiet/mobile@2.0.3-alpha.6) (2023-11-14) **Note:** Version bump only for package @quiet/mobile @@ -23,11 +66,13 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline ## [2.0.3-alpha.4](https://github.com/TryQuiet/quiet/compare/@quiet/mobile@2.0.3-alpha.3...@quiet/mobile@2.0.3-alpha.4) (2023-11-13) +## [1.10.10](https://github.com/TryQuiet/quiet/compare/@quiet/mobile@1.10.9...@quiet/mobile@1.10.10) (2023-11-09) ### Features * bump versionCode ([104c656](https://github.com/TryQuiet/quiet/commit/104c6569805efecffcc23a801a8ba91a352966fe)) +* bump versionCode ([08af810](https://github.com/TryQuiet/quiet/commit/08af81032b533beaea800cf1fd53035616c9d5d8)) @@ -319,6 +364,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline # [1.11.0-alpha.0](/compare/@quiet/mobile@1.10.0...@quiet/mobile@1.11.0-alpha.0) (2023-08-29) +## [1.10.9](https://github.com/TryQuiet/quiet/compare/@quiet/mobile@1.10.8...@quiet/mobile@1.10.9) (2023-11-09) **Note:** Version bump only for package @quiet/mobile diff --git a/packages/mobile/android/app/build.gradle b/packages/mobile/android/app/build.gradle index 28e26ff703..aa33b2bc6b 100644 --- a/packages/mobile/android/app/build.gradle +++ b/packages/mobile/android/app/build.gradle @@ -172,8 +172,8 @@ android { applicationId "com.quietmobile" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 376 - versionName "2.0.3-alpha.6" + versionCode 381 + versionName "2.0.3-alpha.11" resValue "string", "build_config_package", "com.quietmobile" testBuildType System.getProperty('testBuildType', 'debug') testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' @@ -315,10 +315,8 @@ dependencies { implementation group: 'commons-io', name: 'commons-io', version: '2.6' - api 'info.guardianproject:jtorctl:0.4.5.7' - // implementation 'info.guardianproject:tor-android:0.4.5.7' - - implementation 'org.torproject:tor-android-binary:0.4.4.6' + implementation 'info.guardianproject:tor-android:0.4.5.7' + implementation 'info.guardianproject:jtorctl:0.4.5.7' // Websockets connection implementation ('io.socket:socket.io-client:2.0.0') { diff --git a/packages/mobile/android/app/src/main/java/com/quietmobile/Backend/BackendWorker.kt b/packages/mobile/android/app/src/main/java/com/quietmobile/Backend/BackendWorker.kt index 111310380a..5a23d35382 100644 --- a/packages/mobile/android/app/src/main/java/com/quietmobile/Backend/BackendWorker.kt +++ b/packages/mobile/android/app/src/main/java/com/quietmobile/Backend/BackendWorker.kt @@ -1,6 +1,7 @@ package com.quietmobile.Backend; import android.content.Context +import android.util.Base64 import android.util.Log import androidx.core.app.NotificationCompat import androidx.work.CoroutineWorker @@ -24,8 +25,8 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.json.JSONException import org.json.JSONObject -import org.torproject.android.binary.TorResourceInstaller import java.util.concurrent.ThreadLocalRandom +import kotlin.collections.ArrayList class BackendWorker(private val context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) { @@ -88,8 +89,10 @@ class BackendWorker(private val context: Context, workerParams: WorkerParameters setForeground(createForegroundInfo()) withContext(Dispatchers.IO) { + // Get and store data port for usage in methods across the app val dataPort = Utils.getOpenPort(11000) + val socketIOSecret = Utils.generateRandomString(20) // Init nodejs project launch { @@ -98,7 +101,7 @@ class BackendWorker(private val context: Context, workerParams: WorkerParameters launch { notificationHandler = NotificationHandler(context) - subscribePushNotifications(dataPort) + subscribePushNotifications(dataPort, socketIOSecret) } launch { @@ -112,17 +115,17 @@ class BackendWorker(private val context: Context, workerParams: WorkerParameters * In any case, websocket won't connect until data server starts listening */ delay(WEBSOCKET_CONNECTION_DELAY) - startWebsocketConnection(dataPort) + startWebsocketConnection(dataPort, socketIOSecret) } val dataPath = Utils.createDirectory(context) - val tor = TorResourceInstaller(context, context.filesDir).installResources() - val torBinary = tor.canonicalPath - + val appInfo = context.packageManager.getApplicationInfo(context.packageName, 0) + val torBinary = appInfo.nativeLibraryDir + "/libtor.so" + val platform = "mobile" - startNodeProjectWithArguments("bundle.cjs --torBinary $torBinary --dataPath $dataPath --dataPort $dataPort --platform $platform") + startNodeProjectWithArguments("bundle.cjs --torBinary $torBinary --dataPath $dataPath --dataPort $dataPort --platform $platform --socketIOSecret $socketIOSecret") } println("FINISHING BACKEND WORKER") @@ -167,8 +170,14 @@ class BackendWorker(private val context: Context, workerParams: WorkerParameters ) } - private fun subscribePushNotifications(port: Int) { - val webSocketClient = IO.socket("http://localhost:$port") + private fun subscribePushNotifications(port: Int, secret: String) { + val encodedSecret = Base64.encodeToString(secret.toByteArray(Charsets.UTF_8), Base64.NO_WRAP) + val options = IO.Options() + val headers = mutableMapOf>() + headers["Authorization"] = listOf("Basic $encodedSecret") + options.extraHeaders = headers + + val webSocketClient = IO.socket("http://127.0.0.1:$port", options) // Listen for events sent from nodejs webSocketClient.on("pushNotification", onPushNotification) // Client won't connect by itself (`connect()` method has to be called manually) @@ -190,10 +199,10 @@ class BackendWorker(private val context: Context, workerParams: WorkerParameters notificationHandler.notify(message, username) } - private fun startWebsocketConnection(port: Int) { + private fun startWebsocketConnection(port: Int, socketIOSecret: String) { Log.d("WEBSOCKET CONNECTION", "Starting on $port") // Proceed only if data port is defined - val websocketConnectionPayload = WebsocketConnectionPayload(port) + val websocketConnectionPayload = WebsocketConnectionPayload(port, socketIOSecret) CommunicationModule.handleIncomingEvents( CommunicationModule.WEBSOCKET_CONNECTION_CHANNEL, Gson().toJson(websocketConnectionPayload), diff --git a/packages/mobile/android/app/src/main/java/com/quietmobile/Communication/CommunicationModule.java b/packages/mobile/android/app/src/main/java/com/quietmobile/Communication/CommunicationModule.java index d4798c6222..e51ee1f125 100644 --- a/packages/mobile/android/app/src/main/java/com/quietmobile/Communication/CommunicationModule.java +++ b/packages/mobile/android/app/src/main/java/com/quietmobile/Communication/CommunicationModule.java @@ -92,7 +92,7 @@ private static void sendEvent(@Nullable WritableMap params) { private static void deleteBackendData() { Context context = reactContext.getApplicationContext(); try { - FileUtils.deleteDirectory(new File(context.getFilesDir(), "backend/files")); + FileUtils.deleteDirectory(new File(context.getFilesDir(), "backend/files2")); } catch (IOException e) { e.printStackTrace(); } diff --git a/packages/mobile/android/app/src/main/java/com/quietmobile/Scheme/WebsocketConnectionPayload.kt b/packages/mobile/android/app/src/main/java/com/quietmobile/Scheme/WebsocketConnectionPayload.kt index a5a490284a..ac8d89962c 100644 --- a/packages/mobile/android/app/src/main/java/com/quietmobile/Scheme/WebsocketConnectionPayload.kt +++ b/packages/mobile/android/app/src/main/java/com/quietmobile/Scheme/WebsocketConnectionPayload.kt @@ -1,5 +1,6 @@ package com.quietmobile.Scheme data class WebsocketConnectionPayload ( - val dataPort: Int + val dataPort: Int, + val socketIOSecret: String ) diff --git a/packages/mobile/android/app/src/main/java/com/quietmobile/Utils/Utils.kt b/packages/mobile/android/app/src/main/java/com/quietmobile/Utils/Utils.kt index 58da0628ba..29491191a0 100644 --- a/packages/mobile/android/app/src/main/java/com/quietmobile/Utils/Utils.kt +++ b/packages/mobile/android/app/src/main/java/com/quietmobile/Utils/Utils.kt @@ -7,6 +7,7 @@ import java.io.* import java.net.ConnectException import java.net.InetSocketAddress import java.net.Socket +import java.security.SecureRandom import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine @@ -23,12 +24,25 @@ object Utils { } fun createDirectory(context: Context): String { - val dataDirectory = File(context.filesDir, "backend/files") + val dataDirectory = File(context.filesDir, "backend/files2") dataDirectory.mkdirs() return dataDirectory.absolutePath } + fun generateRandomString(length: Int): String { + val CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + val secureRandom = SecureRandom() + val randomString = StringBuilder(length) + + repeat(length) { + val randomIndex = secureRandom.nextInt(CHARACTERS.length) + randomString.append(CHARACTERS[randomIndex]) + } + + return randomString.toString() + } + suspend fun getOpenPort(starting: Int) = suspendCoroutine { continuation -> val port = checkPort(starting) continuation.resume(port) diff --git a/packages/mobile/assets/icons/update_graphics.png b/packages/mobile/assets/icons/update_graphics.png new file mode 100644 index 0000000000..7f3c8e281b Binary files /dev/null and b/packages/mobile/assets/icons/update_graphics.png differ diff --git a/packages/mobile/ios/CommunicationModule.swift b/packages/mobile/ios/CommunicationModule.swift index 6b5fb1821a..75c132e14b 100644 --- a/packages/mobile/ios/CommunicationModule.swift +++ b/packages/mobile/ios/CommunicationModule.swift @@ -10,8 +10,8 @@ class CommunicationModule: RCTEventEmitter { static let WEBSOCKET_CONNECTION_CHANNEL = "_WEBSOCKET_CONNECTION_" @objc - func sendDataPort(port: UInt16) { - self.sendEvent(withName: CommunicationModule.BACKEND_EVENT_IDENTIFIER, body: ["channelName": CommunicationModule.WEBSOCKET_CONNECTION_CHANNEL, "payload": ["dataPort": port]]) + func sendDataPort(port: UInt16, socketIOSecret: String) { + self.sendEvent(withName: CommunicationModule.BACKEND_EVENT_IDENTIFIER, body: ["channelName": CommunicationModule.WEBSOCKET_CONNECTION_CHANNEL, "payload": ["dataPort": port, "socketIOSecret": socketIOSecret]]) } @objc diff --git a/packages/mobile/ios/DataDirectory.swift b/packages/mobile/ios/DataDirectory.swift index e03faf33ec..5afc8b73c6 100644 --- a/packages/mobile/ios/DataDirectory.swift +++ b/packages/mobile/ios/DataDirectory.swift @@ -6,7 +6,7 @@ class DataDirectory: NSObject { let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) let documentsDirectory = paths[0] let docURL = URL(string: documentsDirectory)! - let dataPath = docURL.appendingPathComponent("backend/files") + let dataPath = docURL.appendingPathComponent("backend/files2") if !FileManager.default.fileExists(atPath: dataPath.path) { do { try FileManager.default.createDirectory(atPath: dataPath.path, withIntermediateDirectories: true, attributes: nil) diff --git a/packages/mobile/ios/Quiet.xcodeproj/project.pbxproj b/packages/mobile/ios/Quiet.xcodeproj/project.pbxproj index b7a34ce203..be437d02b7 100644 --- a/packages/mobile/ios/Quiet.xcodeproj/project.pbxproj +++ b/packages/mobile/ios/Quiet.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 069DD1D7AEEE422CAA8DDBAD /* Rubik-Black.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 087527EFE5B6436EAB420BE2 /* Rubik-Black.ttf */; }; 0FBBD0C07F80428D9985FB09 /* Rubik-LightItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = F07611BD79814082A0C19928 /* Rubik-LightItalic.ttf */; }; 1389B75C9E334BCFB4A3E58B /* Rubik-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = F2CAE82B74814677AFF9779E /* Rubik-Regular.ttf */; }; + 180E120B2AEFB7F900804659 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 180E120A2AEFB7F900804659 /* Utils.swift */; }; 1827A9E229783D6E00245FD3 /* classic-level.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1827A9E129783D6E00245FD3 /* classic-level.framework */; platformFilter = ios; }; 1827A9E329783D7600245FD3 /* classic-level.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1827A9E129783D6E00245FD3 /* classic-level.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 1827A9E429783D7600245FD3 /* classic-level.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 1827A9E129783D6E00245FD3 /* classic-level.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -35,15 +36,15 @@ 18FD2A3E296F009E00A2B8C0 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 18FD2A37296F009E00A2B8C0 /* AppDelegate.m */; }; 18FD2A3F296F009E00A2B8C0 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 18FD2A38296F009E00A2B8C0 /* Images.xcassets */; }; 18FD2A40296F009E00A2B8C0 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 18FD2A39296F009E00A2B8C0 /* main.m */; }; - 3C78B58573E0D2A7A1622803 /* libPods-Quiet-QuietTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 865CA6E5185C29ABCD1914D9 /* libPods-Quiet-QuietTests.a */; }; 43B99DB1C98D429295E8CB91 /* Rubik-SemiBoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 2BB5F4B8D4F3462CB8AF6312 /* Rubik-SemiBoldItalic.ttf */; }; 5CBDF3C0D937401A886684F8 /* Rubik-Light.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 706E1601C7B649A8A40A7877 /* Rubik-Light.ttf */; }; 62541AEC8BEC401EBAAF6198 /* Rubik-ExtraBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 18ED84D0299E4E55AB68AA3E /* Rubik-ExtraBold.ttf */; }; - 6CF8B0FBA0941F3F55F1AEC5 /* libPods-Quiet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A741A825CDCBDA1C56FA789 /* libPods-Quiet.a */; }; 7F80A59D9EC1440186E5D5CF /* Rubik-SemiBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = BF73B04135634980BC656D6C /* Rubik-SemiBold.ttf */; }; 80CCB457674F4979A3C5DB06 /* Rubik-MediumItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = EBC2A7699E0A49059904DD8C /* Rubik-MediumItalic.ttf */; }; 8A009A60D84E4B08AB0E8152 /* Rubik-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E820B3E5514B49EE8C72DECB /* Rubik-Bold.ttf */; }; 9EC9E7C54868433990A479EC /* Rubik-ExtraBoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 182B8961416E4D4C8A29793D /* Rubik-ExtraBoldItalic.ttf */; }; + 9EFEA2C3C6AE0D6FA6A4C079 /* libPods-Quiet-QuietTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = ED4D442ACC913B8E48E2C569 /* libPods-Quiet-QuietTests.a */; }; + A43CD77BAC37717C692E6333 /* libPods-Quiet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 47D3FEF6693D5638AD9D0AFF /* libPods-Quiet.a */; }; B4D947D797C747ABAC4EDB69 /* Rubik-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 378A1FDC610441568B568410 /* Rubik-Medium.ttf */; }; DB3A77793E604ACE8E52C67D /* Rubik-BlackItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 23AE2E9AAC824E76A9C4B3D5 /* Rubik-BlackItalic.ttf */; }; DCA6BD5DCC514EEF87705ACB /* Rubik-BoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 84720E58493F44BE8C4784E3 /* Rubik-BoldItalic.ttf */; }; @@ -81,7 +82,7 @@ 00E356F21AD99517003FC87E /* QuietTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QuietTests.m; sourceTree = ""; }; 087527EFE5B6436EAB420BE2 /* Rubik-Black.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Rubik-Black.ttf"; path = "../assets/fonts/Rubik-Black.ttf"; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* Quiet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Quiet.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 149DA46FB928274C5FB2FB01 /* Pods-Quiet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Quiet.debug.xcconfig"; path = "Target Support Files/Pods-Quiet/Pods-Quiet.debug.xcconfig"; sourceTree = ""; }; + 180E120A2AEFB7F900804659 /* Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; }; 1827A9E129783D6E00245FD3 /* classic-level.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = "classic-level.framework"; sourceTree = ""; }; 182B8961416E4D4C8A29793D /* Rubik-ExtraBoldItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Rubik-ExtraBoldItalic.ttf"; path = "../assets/fonts/Rubik-ExtraBoldItalic.ttf"; sourceTree = ""; }; 183C484F296C7B6700BA2D8B /* v8-platform.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "v8-platform.h"; sourceTree = ""; }; @@ -596,21 +597,22 @@ 18FD2A39296F009E00A2B8C0 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Quiet/main.m; sourceTree = ""; }; 18FD2A3A296F009E00A2B8C0 /* Quiet.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; name = Quiet.entitlements; path = Quiet/Quiet.entitlements; sourceTree = ""; }; 18FD2A3B296F009E00A2B8C0 /* QuietDebug.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; name = QuietDebug.entitlements; path = Quiet/QuietDebug.entitlements; sourceTree = ""; }; + 20C39A27A55F5BFF2B63BC14 /* Pods-Quiet-QuietTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Quiet-QuietTests.debug.xcconfig"; path = "Target Support Files/Pods-Quiet-QuietTests/Pods-Quiet-QuietTests.debug.xcconfig"; sourceTree = ""; }; 2386DA39D8B240D9A80ECBD9 /* Rubik-Italic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Rubik-Italic.ttf"; path = "../assets/fonts/Rubik-Italic.ttf"; sourceTree = ""; }; 23AE2E9AAC824E76A9C4B3D5 /* Rubik-BlackItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Rubik-BlackItalic.ttf"; path = "../assets/fonts/Rubik-BlackItalic.ttf"; sourceTree = ""; }; - 262E5B8A9D4174F60C5940B1 /* Pods-Quiet-QuietTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Quiet-QuietTests.debug.xcconfig"; path = "Target Support Files/Pods-Quiet-QuietTests/Pods-Quiet-QuietTests.debug.xcconfig"; sourceTree = ""; }; 2BB5F4B8D4F3462CB8AF6312 /* Rubik-SemiBoldItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Rubik-SemiBoldItalic.ttf"; path = "../assets/fonts/Rubik-SemiBoldItalic.ttf"; sourceTree = ""; }; 378A1FDC610441568B568410 /* Rubik-Medium.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Rubik-Medium.ttf"; path = "../assets/fonts/Rubik-Medium.ttf"; sourceTree = ""; }; - 4A741A825CDCBDA1C56FA789 /* libPods-Quiet.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Quiet.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 56957883430CB6A0A09E6740 /* Pods-Quiet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Quiet.release.xcconfig"; path = "Target Support Files/Pods-Quiet/Pods-Quiet.release.xcconfig"; sourceTree = ""; }; + 47D3FEF6693D5638AD9D0AFF /* libPods-Quiet.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Quiet.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 599527D61D41D1899FC6F87B /* Pods-Quiet-QuietTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Quiet-QuietTests.release.xcconfig"; path = "Target Support Files/Pods-Quiet-QuietTests/Pods-Quiet-QuietTests.release.xcconfig"; sourceTree = ""; }; 706E1601C7B649A8A40A7877 /* Rubik-Light.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Rubik-Light.ttf"; path = "../assets/fonts/Rubik-Light.ttf"; sourceTree = ""; }; 84720E58493F44BE8C4784E3 /* Rubik-BoldItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Rubik-BoldItalic.ttf"; path = "../assets/fonts/Rubik-BoldItalic.ttf"; sourceTree = ""; }; - 865CA6E5185C29ABCD1914D9 /* libPods-Quiet-QuietTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Quiet-QuietTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 9CC7C58C143E70B860940210 /* Pods-Quiet-QuietTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Quiet-QuietTests.release.xcconfig"; path = "Target Support Files/Pods-Quiet-QuietTests/Pods-Quiet-QuietTests.release.xcconfig"; sourceTree = ""; }; BF73B04135634980BC656D6C /* Rubik-SemiBold.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Rubik-SemiBold.ttf"; path = "../assets/fonts/Rubik-SemiBold.ttf"; sourceTree = ""; }; + CD39A872C9F80A6BA04B8A4A /* Pods-Quiet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Quiet.release.xcconfig"; path = "Target Support Files/Pods-Quiet/Pods-Quiet.release.xcconfig"; sourceTree = ""; }; E820B3E5514B49EE8C72DECB /* Rubik-Bold.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Rubik-Bold.ttf"; path = "../assets/fonts/Rubik-Bold.ttf"; sourceTree = ""; }; EBC2A7699E0A49059904DD8C /* Rubik-MediumItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Rubik-MediumItalic.ttf"; path = "../assets/fonts/Rubik-MediumItalic.ttf"; sourceTree = ""; }; + ED4D442ACC913B8E48E2C569 /* libPods-Quiet-QuietTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Quiet-QuietTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; F07611BD79814082A0C19928 /* Rubik-LightItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Rubik-LightItalic.ttf"; path = "../assets/fonts/Rubik-LightItalic.ttf"; sourceTree = ""; }; + F2C59D3CAFDB714D809D1115 /* Pods-Quiet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Quiet.debug.xcconfig"; path = "Target Support Files/Pods-Quiet/Pods-Quiet.debug.xcconfig"; sourceTree = ""; }; F2CAE82B74814677AFF9779E /* Rubik-Regular.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Rubik-Regular.ttf"; path = "../assets/fonts/Rubik-Regular.ttf"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -622,7 +624,7 @@ 18C5DE1329782326008036F4 /* leveldown.framework in Frameworks */, 1842897C295466AA00CA5039 /* NodeMobile.framework in Frameworks */, 1827A9E229783D6E00245FD3 /* classic-level.framework in Frameworks */, - 3C78B58573E0D2A7A1622803 /* libPods-Quiet-QuietTests.a in Frameworks */, + 9EFEA2C3C6AE0D6FA6A4C079 /* libPods-Quiet-QuietTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -633,7 +635,7 @@ 18C5DE1829782334008036F4 /* leveldown.framework in Frameworks */, 1827A9E329783D7600245FD3 /* classic-level.framework in Frameworks */, 1842897D295466B500CA5039 /* NodeMobile.framework in Frameworks */, - 6CF8B0FBA0941F3F55F1AEC5 /* libPods-Quiet.a in Frameworks */, + A43CD77BAC37717C692E6333 /* libPods-Quiet.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -660,6 +662,7 @@ 13B07FAE1A68108700A75B9A /* Quiet */ = { isa = PBXGroup; children = ( + 180E120A2AEFB7F900804659 /* Utils.swift */, 18FD2A36296F009E00A2B8C0 /* AppDelegate.h */, 18FD2A37296F009E00A2B8C0 /* AppDelegate.m */, 18FD2A38296F009E00A2B8C0 /* Images.xcassets */, @@ -4688,10 +4691,10 @@ 1CEEDB4F07B9978C125775C5 /* Pods */ = { isa = PBXGroup; children = ( - 149DA46FB928274C5FB2FB01 /* Pods-Quiet.debug.xcconfig */, - 56957883430CB6A0A09E6740 /* Pods-Quiet.release.xcconfig */, - 262E5B8A9D4174F60C5940B1 /* Pods-Quiet-QuietTests.debug.xcconfig */, - 9CC7C58C143E70B860940210 /* Pods-Quiet-QuietTests.release.xcconfig */, + F2C59D3CAFDB714D809D1115 /* Pods-Quiet.debug.xcconfig */, + CD39A872C9F80A6BA04B8A4A /* Pods-Quiet.release.xcconfig */, + 20C39A27A55F5BFF2B63BC14 /* Pods-Quiet-QuietTests.debug.xcconfig */, + 599527D61D41D1899FC6F87B /* Pods-Quiet-QuietTests.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -4702,8 +4705,8 @@ 1827A9E129783D6E00245FD3 /* classic-level.framework */, 18C5DE1029782326008036F4 /* leveldown.framework */, 1842897B295466AA00CA5039 /* NodeMobile.framework */, - 4A741A825CDCBDA1C56FA789 /* libPods-Quiet.a */, - 865CA6E5185C29ABCD1914D9 /* libPods-Quiet-QuietTests.a */, + 47D3FEF6693D5638AD9D0AFF /* libPods-Quiet.a */, + ED4D442ACC913B8E48E2C569 /* libPods-Quiet-QuietTests.a */, ); name = Frameworks; sourceTree = ""; @@ -4769,12 +4772,12 @@ isa = PBXNativeTarget; buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "QuietTests" */; buildPhases = ( - 05887DFAA4618A6767326B2C /* [CP] Check Pods Manifest.lock */, + 3A0DBD8E832195F1C96149E4 /* [CP] Check Pods Manifest.lock */, 00E356EA1AD99517003FC87E /* Sources */, 00E356EB1AD99517003FC87E /* Frameworks */, 00E356EC1AD99517003FC87E /* Resources */, - BCB5CD734D9D7837D92F6F6A /* [CP] Embed Pods Frameworks */, - 96FE87B8CF60D56415A6D872 /* [CP] Copy Pods Resources */, + 906843AE2BDF77EFA0025899 /* [CP] Embed Pods Frameworks */, + 75398FB9BE83762F7A4A343B /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -4790,7 +4793,7 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Quiet" */; buildPhases = ( - 1A881985853DC9243FFCAD06 /* [CP] Check Pods Manifest.lock */, + A66CC1900B2FEB9695F131B5 /* [CP] Check Pods Manifest.lock */, FD10A7F022414F080027D42C /* Start Packager */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, @@ -4802,8 +4805,8 @@ 1827A9E0297837FE00245FD3 /* [CUSTOM NODEJS MOBILE] Remove prebuilds */, 1868C095292F8FE2001D6D5E /* Embed Frameworks */, 18D742A42A41DAD8007D4C4E /* Remove Simulator Strips */, - 33EE387FBE8BC810E57CE73A /* [CP] Embed Pods Frameworks */, - C3B19D7F8A37CA1DEC5A8545 /* [CP] Copy Pods Resources */, + 897206F7B8FF9B5DD7F5561D /* [CP] Embed Pods Frameworks */, + F76D7A280A8CDA06072C296A /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -4899,28 +4902,6 @@ shellPath = /bin/sh; shellScript = "set -e\n\nexport NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh\n"; }; - 05887DFAA4618A6767326B2C /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Quiet-QuietTests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; 1827A9DE297828AB00245FD3 /* [CUSTOM NODEJS MOBILE] Mock .node files */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -5011,7 +4992,7 @@ shellPath = /bin/sh; shellScript = "find \"$CODESIGNING_FOLDER_PATH/nodejs-project/node_modules/\" -name \"python3\" | xargs rm\n"; }; - 1A881985853DC9243FFCAD06 /* [CP] Check Pods Manifest.lock */ = { + 3A0DBD8E832195F1C96149E4 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -5026,48 +5007,48 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Quiet-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-Quiet-QuietTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 33EE387FBE8BC810E57CE73A /* [CP] Embed Pods Frameworks */ = { + 75398FB9BE83762F7A4A343B /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Quiet/Pods-Quiet-frameworks-${CONFIGURATION}-input-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-Quiet-QuietTests/Pods-Quiet-QuietTests-resources-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Embed Pods Frameworks"; + name = "[CP] Copy Pods Resources"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Quiet/Pods-Quiet-frameworks-${CONFIGURATION}-output-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-Quiet-QuietTests/Pods-Quiet-QuietTests-resources-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Quiet/Pods-Quiet-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Quiet-QuietTests/Pods-Quiet-QuietTests-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 96FE87B8CF60D56415A6D872 /* [CP] Copy Pods Resources */ = { + 897206F7B8FF9B5DD7F5561D /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Quiet-QuietTests/Pods-Quiet-QuietTests-resources-${CONFIGURATION}-input-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-Quiet/Pods-Quiet-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Copy Pods Resources"; + name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Quiet-QuietTests/Pods-Quiet-QuietTests-resources-${CONFIGURATION}-output-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-Quiet/Pods-Quiet-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Quiet-QuietTests/Pods-Quiet-QuietTests-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Quiet/Pods-Quiet-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - BCB5CD734D9D7837D92F6F6A /* [CP] Embed Pods Frameworks */ = { + 906843AE2BDF77EFA0025899 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -5084,7 +5065,29 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Quiet-QuietTests/Pods-Quiet-QuietTests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - C3B19D7F8A37CA1DEC5A8545 /* [CP] Copy Pods Resources */ = { + A66CC1900B2FEB9695F131B5 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Quiet-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + F76D7A280A8CDA06072C296A /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -5146,6 +5149,7 @@ 18FD2A3E296F009E00A2B8C0 /* AppDelegate.m in Sources */, 1868BCED292E9212001D6D5E /* NodeRunner.mm in Sources */, 1868C43C2930E255001D6D5E /* CommunicationModule.swift in Sources */, + 180E120B2AEFB7F900804659 /* Utils.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -5162,7 +5166,7 @@ /* Begin XCBuildConfiguration section */ 00E356F61AD99517003FC87E /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 262E5B8A9D4174F60C5940B1 /* Pods-Quiet-QuietTests.debug.xcconfig */; + baseConfigurationReference = 20C39A27A55F5BFF2B63BC14 /* Pods-Quiet-QuietTests.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; @@ -5195,7 +5199,7 @@ }; 00E356F71AD99517003FC87E /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9CC7C58C143E70B860940210 /* Pods-Quiet-QuietTests.release.xcconfig */; + baseConfigurationReference = 599527D61D41D1899FC6F87B /* Pods-Quiet-QuietTests.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; @@ -5225,7 +5229,7 @@ }; 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 149DA46FB928274C5FB2FB01 /* Pods-Quiet.debug.xcconfig */; + baseConfigurationReference = F2C59D3CAFDB714D809D1115 /* Pods-Quiet.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ARCHS = "$(ARCHS_STANDARD)"; @@ -5319,7 +5323,7 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 56957883430CB6A0A09E6740 /* Pods-Quiet.release.xcconfig */; + baseConfigurationReference = CD39A872C9F80A6BA04B8A4A /* Pods-Quiet.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ARCHS = "$(ARCHS_STANDARD)"; diff --git a/packages/mobile/ios/Quiet/AppDelegate.h b/packages/mobile/ios/Quiet/AppDelegate.h index 0f44aa8b5e..51fed2c14d 100644 --- a/packages/mobile/ios/Quiet/AppDelegate.h +++ b/packages/mobile/ios/Quiet/AppDelegate.h @@ -12,6 +12,8 @@ @property uint16_t dataPort; +@property NSString *socketIOSecret; + @property NSString *dataPath; @property RCTBridge *bridge; diff --git a/packages/mobile/ios/Quiet/AppDelegate.m b/packages/mobile/ios/Quiet/AppDelegate.m index 9f3984ca96..7706f61020 100644 --- a/packages/mobile/ios/Quiet/AppDelegate.m +++ b/packages/mobile/ios/Quiet/AppDelegate.m @@ -104,7 +104,7 @@ - (void) initWebsocketConnection { NSTimeInterval delayInSeconds = 5; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^(void) { - [[self.bridge moduleForName:@"CommunicationModule"] sendDataPortWithPort:self.dataPort]; + [[self.bridge moduleForName:@"CommunicationModule"] sendDataPortWithPort:self.dataPort socketIOSecret:self.socketIOSecret]; }); }); } @@ -114,9 +114,13 @@ - (void) spinupBackend:(BOOL)init { // (1/6) Find ports to use in tor and backend configuration FindFreePort *findFreePort = [FindFreePort new]; + Utils *utils = [Utils new]; - self.dataPort = [findFreePort getFirstStartingFromPort:11000]; + if (self.socketIOSecret == nil) { + self.socketIOSecret = [utils generateSecretWithLength:(20)]; + } + self.dataPort = [findFreePort getFirstStartingFromPort:11000]; uint16_t socksPort = [findFreePort getFirstStartingFromPort:12000]; uint16_t controlPort = [findFreePort getFirstStartingFromPort:14000]; uint16_t httpTunnelPort = [findFreePort getFirstStartingFromPort:16000]; @@ -196,16 +200,17 @@ - (NSData *) getAuthCookieData { - (void) launchBackend:(uint16_t)controlPort:(uint16_t)httpTunnelPort:(NSString *)authCookie { self.nodeJsMobile = [RNNodeJsMobile new]; - [self.nodeJsMobile callStartNodeProject:[NSString stringWithFormat:@"bundle.cjs --dataPort %hu --dataPath %@ --controlPort %hu --httpTunnelPort %hu --authCookie %@ --platform %@", self.dataPort, self.dataPath, controlPort, httpTunnelPort, authCookie, platform]]; + [self.nodeJsMobile callStartNodeProject:[NSString stringWithFormat:@"bundle.cjs --dataPort %hu --dataPath %@ --controlPort %hu --httpTunnelPort %hu --authCookie %@ --platform %@ --socketIOSecret %@", self.dataPort, self.dataPath, controlPort, httpTunnelPort, authCookie, platform, self.socketIOSecret]]; } - (void) reviweServices:(uint16_t)controlPort:(uint16_t)httpTunnelPort:(NSString *)authCookie { NSString * dataPortPayload = [NSString stringWithFormat:@"%@:%hu", @"socketIOPort", self.dataPort]; + NSString * socketIOSecretPayload = [NSString stringWithFormat:@"%@:%@", @"socketIOSecret", self.socketIOSecret]; NSString * controlPortPayload = [NSString stringWithFormat:@"%@:%hu", @"torControlPort", controlPort]; NSString * httpTunnelPortPayload = [NSString stringWithFormat:@"%@:%hu", @"httpTunnelPort", httpTunnelPort]; NSString * authCookiePayload = [NSString stringWithFormat:@"%@:%@", @"authCookie", authCookie]; - NSString * payload = [NSString stringWithFormat:@"%@|%@|%@|%@", dataPortPayload, controlPortPayload, httpTunnelPortPayload, authCookiePayload]; + NSString * payload = [NSString stringWithFormat:@"%@|%@|%@|%@|%@", dataPortPayload, socketIOSecretPayload, controlPortPayload, httpTunnelPortPayload, authCookiePayload]; [self.nodeJsMobile sendMessageToNode:@"open":payload]; } diff --git a/packages/mobile/ios/Quiet/Info.plist b/packages/mobile/ios/Quiet/Info.plist index 0e38c27799..7844106f33 100644 --- a/packages/mobile/ios/Quiet/Info.plist +++ b/packages/mobile/ios/Quiet/Info.plist @@ -34,7 +34,7 @@ CFBundleVersion - 333 + 338 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS diff --git a/packages/mobile/ios/QuietTests/Info.plist b/packages/mobile/ios/QuietTests/Info.plist index 7b9921d260..50d2cac43e 100644 --- a/packages/mobile/ios/QuietTests/Info.plist +++ b/packages/mobile/ios/QuietTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 333 + 338 diff --git a/packages/mobile/ios/Utils.swift b/packages/mobile/ios/Utils.swift new file mode 100644 index 0000000000..2c36113f99 --- /dev/null +++ b/packages/mobile/ios/Utils.swift @@ -0,0 +1,23 @@ +@objc(Utils) +class Utils: NSObject { + + @objc + func generateSecret(length: Int) -> String { + let characters = Array("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") + var randomString = "" + + for _ in 0.. - - - - - - + + - - @@ -112,6 +109,13 @@ function App(): JSX.Element { component={PossibleImpersonationAttackScreen} name={ScreenNames.PossibleImpersonationAttackScreen} /> + + + + + + + diff --git a/packages/mobile/src/assets.ts b/packages/mobile/src/assets.ts index b2d5be2dac..0970f3374f 100644 --- a/packages/mobile/src/assets.ts +++ b/packages/mobile/src/assets.ts @@ -1,38 +1,40 @@ -import quiet_icon from '../assets/icons/quiet_icon.png' -import quiet_icon_round from '../assets/icons/quiet_icon_round.png' -import icon_send from '../assets/icons/icon_send.png' -import icon_send_disabled from '../assets/icons/icon_send_disabled.png' -import icon_check_white from '../assets/icons/icon_check_white.png' -import check_circle_green from '../assets/icons/check_circle_green.png' -import check_circle_blank from '../assets/icons/check_circle_blank.png' -import username_registered from '../assets/icons/username_registered.png' import arrow_left from '../assets/icons/arrow_left.png' import arrow_right_short from '../assets/icons/arrow_right_short.png' -import icon_warning from '../assets/icons/icon_warning.png' -import icon_close from '../assets/icons/icon_close.png' -import file_document from '../assets/icons/file_document.png' +import check_circle_blank from '../assets/icons/check_circle_blank.png' +import check_circle_green from '../assets/icons/check_circle_green.png' import dots from '../assets/icons/dots.png' import paperclip_gray from '../assets/icons/paperclip_gray.png' +import file_document from '../assets/icons/file_document.png' +import icon_check_white from '../assets/icons/icon_check_white.png' +import icon_close from '../assets/icons/icon_close.png' +import icon_send from '../assets/icons/icon_send.png' +import icon_send_disabled from '../assets/icons/icon_send_disabled.png' +import icon_warning from '../assets/icons/icon_warning.png' +import quiet_icon from '../assets/icons/quiet_icon.png' +import quiet_icon_round from '../assets/icons/quiet_icon_round.png' +import update_graphics from '../assets/icons/update_graphics.png' +import username_registered from '../assets/icons/username_registered.png' /** * @description This assets are for the app. */ export const appImages = { - quiet_icon, - quiet_icon_round, - icon_send, - icon_send_disabled, - icon_check_white, - check_circle_green, - check_circle_blank, - username_registered, arrow_left, arrow_right_short, - icon_warning, - icon_close, - file_document, + check_circle_blank, + check_circle_green, dots, paperclip_gray, + file_document, + icon_check_white, + icon_close, + icon_send, + icon_send_disabled, + icon_warning, + quiet_icon, + quiet_icon_round, + update_graphics, + username_registered, } /** diff --git a/packages/mobile/src/components/Appbar/Appbar.stories.tsx b/packages/mobile/src/components/Appbar/Appbar.stories.tsx index cbf7d390ce..1ef71f558f 100644 --- a/packages/mobile/src/components/Appbar/Appbar.stories.tsx +++ b/packages/mobile/src/components/Appbar/Appbar.stories.tsx @@ -1,6 +1,5 @@ import React from 'react' import { storiesOf } from '@storybook/react-native' - import { useContextMenu } from '../../hooks/useContextMenu' import { Appbar } from './Appbar.component' diff --git a/packages/mobile/src/components/Button/Button.component.tsx b/packages/mobile/src/components/Button/Button.component.tsx index 9c9fc97999..0393c7469d 100644 --- a/packages/mobile/src/components/Button/Button.component.tsx +++ b/packages/mobile/src/components/Button/Button.component.tsx @@ -2,10 +2,11 @@ import React, { FC } from 'react' import { TouchableWithoutFeedback, View } from 'react-native' import { ButtonProps } from './Button.types' import * as Progress from 'react-native-progress' + import { Typography } from '../Typography/Typography.component' import { defaultTheme } from '../../styles/themes/default.theme' -export const Button: FC = ({ onPress, title, width, loading, negative, disabled }) => { +export const Button: FC = ({ onPress, title, width, loading, negative, disabled, newDesign }) => { return ( { @@ -22,12 +23,12 @@ export const Button: FC = ({ onPress, title, width, loading, negati borderRadius: 8, justifyContent: 'center', alignItems: 'center', - minHeight: 45, + minHeight: newDesign ? 50 : 45, width, }} > {!loading ? ( - + {title} ) : ( diff --git a/packages/mobile/src/components/Button/Button.stories.tsx b/packages/mobile/src/components/Button/Button.stories.tsx index 2c4cb72cb3..d5ce0a479d 100644 --- a/packages/mobile/src/components/Button/Button.stories.tsx +++ b/packages/mobile/src/components/Button/Button.stories.tsx @@ -1,9 +1,10 @@ -import { storiesOf } from '@storybook/react-native' import React from 'react' +import { storiesOf } from '@storybook/react-native' import { storybookLog } from '../../utils/functions/storybookLog/storybookLog.function' import { Button } from './Button.component' storiesOf('Button', module) .add('Default', () =>