From 8ba87a5e5027fdbbaf58b08e08195123f082b333 Mon Sep 17 00:00:00 2001 From: Emi Date: Thu, 3 Aug 2023 21:26:51 +0200 Subject: [PATCH 01/61] Implement utils for parsing and creating invitation code with multiple addresses --- .../src/nest/storage/storage.service.ts | 2 +- packages/common/src/invitationCode.ts | 69 +++++++- packages/desktop/src/main/invitation.ts | 7 +- packages/desktop/src/main/main.ts | 9 +- .../JoinCommunity/JoinCommunity.tsx | 6 +- .../PerformCommunityActionComponent.tsx | 26 ++- packages/desktop/src/renderer/index.tsx | 5 + .../handleInvitationCode.saga.test.ts | 158 +++++++++--------- .../invitation/handleInvitationCode.saga.ts | 39 ++--- .../JoinCommunity/JoinCommunity.component.tsx | 10 +- .../JoinCommunity/JoinCommunity.types.ts | 4 +- packages/state-manager/src/index.ts | 2 +- .../sagas/communities/communities.slice.ts | 10 +- .../createNetwork/createNetwork.saga.ts | 2 +- .../invitationCode/invitationCode.test.ts | 23 ++- .../invitationCode/invitationCode.ts | 65 +++++-- packages/types/src/community.ts | 3 +- packages/types/src/network.ts | 5 + 18 files changed, 287 insertions(+), 158 deletions(-) diff --git a/packages/backend/src/nest/storage/storage.service.ts b/packages/backend/src/nest/storage/storage.service.ts index d740373e64..7524b13bd3 100644 --- a/packages/backend/src/nest/storage/storage.service.ts +++ b/packages/backend/src/nest/storage/storage.service.ts @@ -171,7 +171,7 @@ export class StorageService extends EventEmitter { return } for (const a of addr) { - this.logger(`Pubsub - subscribe to ${addr}`) + this.logger(`Pubsub - subscribe to ${a}`) // @ts-ignore await this.orbitDb._pubsub.subscribe( a, diff --git a/packages/common/src/invitationCode.ts b/packages/common/src/invitationCode.ts index 6d17e38251..b43cb2053f 100644 --- a/packages/common/src/invitationCode.ts +++ b/packages/common/src/invitationCode.ts @@ -1,3 +1,4 @@ +import { InvitationPair } from '@quiet/types' import { InvitationParams, Site } from './static' export const retrieveInvitationCode = (url: string): string => { @@ -20,20 +21,78 @@ export const retrieveInvitationCode = (url: string): string => { return '' } -export const argvInvitationCode = (argv: string[]): string => { +export const retrieveInvitationCodePairs = (url: string): InvitationPair[] => { + /** + * Extract invitation code from deep url. + * Valid format: quiet://?=&= + */ + let data: URL + try { + data = new URL(url) + } catch (e) { + return [] + } + if (!data || data.protocol !== 'quiet:') return [] + const params = data.searchParams + const codes: InvitationPair[] = [] + for (const [peerId, address] of params) { + // TODO: basic check if peerid and address have proper format? + if (peerId.length !== 46 || address.length !== 56) { + console.log(`peerId '${peerId}' or address ${address} is not valid`) + continue + } + codes.push({ + peerId, + address, + }) + } + console.log('Retrieved codes:', codes) + return codes +} + +export const invitationShareUrlMultipleAddresses = (pairs: InvitationPair[] = []): string => { + // Valid format: https://tryquiet.org/join/#=&= + const code = pairs.map(pair => `${pair.peerId}=${pair.address}`).join('&') + const url = new URL(`https://${Site.DOMAIN}/${Site.JOIN_PAGE}#${code}`) + return url.href +} + +export const invitationDeepUrlMultipleAddresses = (pairs: InvitationPair[] = []): string => { + const url = new URL('quiet://') + for (const pair of pairs) { + url.searchParams.append(pair.peerId, pair.address) + } + return url.href +} + +export const argvInvitationCode = (argv: string[]): InvitationPair[] => { /** * Extract invitation code from deep url if url is present in argv */ - let invitationCode = '' + let invitationCodes = [] for (const arg of argv) { - invitationCode = retrieveInvitationCode(arg) - if (invitationCode) { + invitationCodes = retrieveInvitationCodePairs(arg) + if (invitationCodes.length > 0) { break } } - return invitationCode + return invitationCodes } +// export const argvInvitationCode = (argv: string[]): string => { +// /** +// * Extract invitation code from deep url if url is present in argv +// */ +// let invitationCode = '' +// for (const arg of argv) { +// invitationCode = retrieveInvitationCode(arg) +// if (invitationCode) { +// break +// } +// } +// return invitationCode +// } + export const invitationDeepUrl = (code = ''): string => { const url = new URL('quiet://') url.searchParams.append(InvitationParams.CODE, code) diff --git a/packages/desktop/src/main/invitation.ts b/packages/desktop/src/main/invitation.ts index 23f61c847e..59ac09262c 100644 --- a/packages/desktop/src/main/invitation.ts +++ b/packages/desktop/src/main/invitation.ts @@ -3,11 +3,12 @@ import path from 'path' import os from 'os' import { execSync } from 'child_process' import { BrowserWindow } from 'electron' +import { InvitationPair } from '@quiet/types' -export const processInvitationCode = (mainWindow: BrowserWindow, code: string) => { - if (!code) return +export const processInvitationCode = (mainWindow: BrowserWindow, codes: InvitationPair[]) => { + if (codes.length === 0) return mainWindow.webContents.send('invitation', { - code, + codes, }) } diff --git a/packages/desktop/src/main/main.ts b/packages/desktop/src/main/main.ts index 932780c8e0..bbb35bf6ac 100644 --- a/packages/desktop/src/main/main.ts +++ b/packages/desktop/src/main/main.ts @@ -11,9 +11,8 @@ import { Crypto } from '@peculiar/webcrypto' import logger from './logger' import { DATA_DIR, DEV_DATA_DIR } from '../shared/static' import { fork, ChildProcess } from 'child_process' -import { getFilesData } from '@quiet/common' +import { argvInvitationCode, getFilesData, retrieveInvitationCodePairs } from '@quiet/common' import { updateDesktopFile, processInvitationCode } from './invitation' -import { argvInvitationCode, retrieveInvitationCode } from '@quiet/common' const ElectronStore = require('electron-store') ElectronStore.initRenderer() @@ -148,7 +147,7 @@ app.on('open-url', (event, url) => { event.preventDefault() if (mainWindow) { invitationUrl = null - const invitationCode = retrieveInvitationCode(url) + const invitationCode = retrieveInvitationCodePairs(url) processInvitationCode(mainWindow, invitationCode) } }) @@ -475,12 +474,12 @@ app.on('ready', async () => { throw new Error(`mainWindow is on unexpected type ${mainWindow}`) } if (process.platform === 'darwin' && invitationUrl) { - const invitationCode = retrieveInvitationCode(invitationUrl) + const invitationCode = retrieveInvitationCodePairs(invitationUrl) processInvitationCode(mainWindow, invitationCode) invitationUrl = null } if (process.platform !== 'darwin' && process.argv) { - const invitationCode = argvInvitationCode(process.argv) + const invitationCode = argvInvitationCodeMultipleAddresses(process.argv) processInvitationCode(mainWindow, invitationCode) } diff --git a/packages/desktop/src/renderer/components/CreateJoinCommunity/JoinCommunity/JoinCommunity.tsx b/packages/desktop/src/renderer/components/CreateJoinCommunity/JoinCommunity/JoinCommunity.tsx index 38df07e800..9ed4991099 100644 --- a/packages/desktop/src/renderer/components/CreateJoinCommunity/JoinCommunity/JoinCommunity.tsx +++ b/packages/desktop/src/renderer/components/CreateJoinCommunity/JoinCommunity/JoinCommunity.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import { socketSelectors } from '../../../sagas/socket/socket.selectors' -import { CommunityOwnership, CreateNetworkPayload, TOR_BOOTSTRAP_COMPLETE } from '@quiet/types' +import { CommunityOwnership, CreateNetworkPayload, InvitationPair, TOR_BOOTSTRAP_COMPLETE } from '@quiet/types' import { communities, identity, connection } from '@quiet/state-manager' import PerformCommunityActionComponent from '../../../components/CreateJoinCommunity/PerformCommunityActionComponent' import { ModalName } from '../../../sagas/modals/modals.types' @@ -41,10 +41,10 @@ const JoinCommunity = () => { } }, [currentCommunity]) - const handleCommunityAction = (address: string) => { + const handleCommunityAction = (address: InvitationPair[]) => { const payload: CreateNetworkPayload = { ownership: CommunityOwnership.User, - registrar: address, + peers: address, } dispatch(communities.actions.createNetwork(payload)) } diff --git a/packages/desktop/src/renderer/components/CreateJoinCommunity/PerformCommunityActionComponent.tsx b/packages/desktop/src/renderer/components/CreateJoinCommunity/PerformCommunityActionComponent.tsx index e0442790ae..1b7f20dc23 100644 --- a/packages/desktop/src/renderer/components/CreateJoinCommunity/PerformCommunityActionComponent.tsx +++ b/packages/desktop/src/renderer/components/CreateJoinCommunity/PerformCommunityActionComponent.tsx @@ -21,7 +21,7 @@ import { IconButton, InputAdornment } from '@mui/material' import VisibilityOff from '@mui/icons-material/VisibilityOff' import Visibility from '@mui/icons-material/Visibility' import { ONION_ADDRESS_REGEX, parseName } from '@quiet/common' -import { getInvitationCode } from '@quiet/state-manager' +import { getInvitationCodes } from '@quiet/state-manager' const PREFIX = 'PerformCommunityActionComponent' @@ -129,7 +129,7 @@ interface PerformCommunityActionFormValues { export interface PerformCommunityActionProps { open: boolean communityOwnership: CommunityOwnership - handleCommunityAction: (value: string) => void + handleCommunityAction: (value: any) => void handleRedirection: () => void handleClose: () => void isConnectionReady?: boolean @@ -178,22 +178,30 @@ export const PerformCommunityActionComponent: React.FC submitForm(handleCommunityAction, values, setFormSent) const submitForm = ( - handleSubmit: (value: string) => void, + handleSubmit: (value: any) => void, values: PerformCommunityActionFormValues, setFormSent: (value: boolean) => void ) => { - let submitValue = communityOwnership === CommunityOwnership.Owner ? parseName(values.name) : values.name.trim() + if (communityOwnership === CommunityOwnership.Owner) { + setFormSent(true) + handleSubmit(parseName(values.name)) + return + } + + // let submitValue = communityOwnership === CommunityOwnership.Owner ? parseName(values.name) : values.name.trim() if (communityOwnership === CommunityOwnership.User) { - submitValue = getInvitationCode(submitValue) - if (!submitValue || !submitValue.match(ONION_ADDRESS_REGEX)) { + const codes = getInvitationCodes(values.name.trim()) + if (!codes.length) { + // if (!submitValue || !submitValue.match(ONION_ADDRESS_REGEX)) { // TODO: add basic validation setError('name', { message: InviteLinkErrors.InvalidCode }) return + // } } - } - setFormSent(true) - handleSubmit(submitValue) + setFormSent(true) + handleSubmit(codes) + } } const onChange = (name: string) => { diff --git a/packages/desktop/src/renderer/index.tsx b/packages/desktop/src/renderer/index.tsx index a56c5f0931..bf35aea3bc 100644 --- a/packages/desktop/src/renderer/index.tsx +++ b/packages/desktop/src/renderer/index.tsx @@ -25,6 +25,11 @@ ipcRenderer.on('invitation', (_event, invitation) => { store.dispatch(communities.actions.handleInvitationCode(invitation.code)) }) +ipcRenderer.on('invitationMA', (_event, invitation) => { + console.log('invitation', invitation, 'dispatching action') + store.dispatch(communities.actions.handleInvitationCodes(invitation.codes)) +}) + 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/invitation/handleInvitationCode.saga.test.ts b/packages/desktop/src/renderer/sagas/invitation/handleInvitationCode.saga.test.ts index 470a0ae044..82cd8afd50 100644 --- a/packages/desktop/src/renderer/sagas/invitation/handleInvitationCode.saga.test.ts +++ b/packages/desktop/src/renderer/sagas/invitation/handleInvitationCode.saga.test.ts @@ -1,87 +1,87 @@ -import { communities, getFactory, Store } from '@quiet/state-manager' -import { Community, CommunityOwnership, CreateNetworkPayload } from '@quiet/types' -import { FactoryGirl } from 'factory-girl' -import { expectSaga } from 'redux-saga-test-plan' -import { handleInvitationCodeSaga } from './handleInvitationCode.saga' -import { SocketState } from '../socket/socket.slice' -import { prepareStore } from '../../testUtils/prepareStore' -import { StoreKeys } from '../../store/store.keys' -import { modalsActions } from '../modals/modals.slice' -import { ModalName } from '../modals/modals.types' +// import { communities, getFactory, Store } from '@quiet/state-manager' +// import { Community, CommunityOwnership, CreateNetworkPayload } from '@quiet/types' +// import { FactoryGirl } from 'factory-girl' +// import { expectSaga } from 'redux-saga-test-plan' +// import { handleInvitationCodeSaga } from './handleInvitationCode.saga' +// import { SocketState } from '../socket/socket.slice' +// import { prepareStore } from '../../testUtils/prepareStore' +// import { StoreKeys } from '../../store/store.keys' +// import { modalsActions } from '../modals/modals.slice' +// import { ModalName } from '../modals/modals.types' -describe('Handle invitation code', () => { - let store: Store - let factory: FactoryGirl - let community: Community - let validInvitationCode: string +// describe('Handle invitation code', () => { +// let store: Store +// let factory: FactoryGirl +// let community: Community +// let validInvitationCode: string - beforeEach(async () => { - store = ( - await prepareStore({ - [StoreKeys.Socket]: { - ...new SocketState(), - isConnected: true, - }, - }) - ).store +// beforeEach(async () => { +// store = ( +// await prepareStore({ +// [StoreKeys.Socket]: { +// ...new SocketState(), +// isConnected: true, +// }, +// }) +// ).store - factory = await getFactory(store) - validInvitationCode = 'bb5wacaftixjl3yhq2cp3ls2ife2e5wlwct3hjlb4lyk4iniypmgozyd' - }) +// factory = await getFactory(store) +// validInvitationCode = 'bb5wacaftixjl3yhq2cp3ls2ife2e5wlwct3hjlb4lyk4iniypmgozyd' +// }) - it('creates network if code is valid', async () => { - const payload: CreateNetworkPayload = { - ownership: CommunityOwnership.User, - registrar: validInvitationCode, - } - await expectSaga(handleInvitationCodeSaga, communities.actions.handleInvitationCode(validInvitationCode)) - .withState(store.getState()) - .put(communities.actions.createNetwork(payload)) - .run() - }) +// it('creates network if code is valid', async () => { +// const payload: CreateNetworkPayload = { +// ownership: CommunityOwnership.User, +// registrar: validInvitationCode, +// } +// await expectSaga(handleInvitationCodeSaga, communities.actions.handleInvitationCode(validInvitationCode)) +// .withState(store.getState()) +// .put(communities.actions.createNetwork(payload)) +// .run() +// }) - it('does not try to create network if user is already in community', async () => { - community = await factory.create['payload']>('Community') - const payload: CreateNetworkPayload = { - ownership: CommunityOwnership.User, - registrar: validInvitationCode, - } +// it('does not try to create network if user is already in community', async () => { +// community = await factory.create['payload']>('Community') +// const payload: CreateNetworkPayload = { +// ownership: CommunityOwnership.User, +// registrar: validInvitationCode, +// } - await expectSaga(handleInvitationCodeSaga, communities.actions.handleInvitationCode(validInvitationCode)) - .withState(store.getState()) - .put( - modalsActions.openModal({ - name: ModalName.warningModal, - args: { - title: 'You already belong to a community', - subtitle: "We're sorry but for now you can only be a member of a single community at a time.", - }, - }) - ) - .not.put(communities.actions.createNetwork(payload)) - .run() - }) +// await expectSaga(handleInvitationCodeSaga, communities.actions.handleInvitationCode(validInvitationCode)) +// .withState(store.getState()) +// .put( +// modalsActions.openModal({ +// name: ModalName.warningModal, +// args: { +// title: 'You already belong to a community', +// subtitle: "We're sorry but for now you can only be a member of a single community at a time.", +// }, +// }) +// ) +// .not.put(communities.actions.createNetwork(payload)) +// .run() +// }) - it('does not try to create network if code is invalid', async () => { - const code = 'invalid' - const payload: CreateNetworkPayload = { - ownership: CommunityOwnership.User, - registrar: code, - } +// it('does not try to create network if code is invalid', async () => { +// const code = 'invalid' +// const payload: CreateNetworkPayload = { +// ownership: CommunityOwnership.User, +// peers: code, +// } - await expectSaga(handleInvitationCodeSaga, communities.actions.handleInvitationCode(code)) - .withState(store.getState()) - .put(communities.actions.clearInvitationCode()) - .put( - modalsActions.openModal({ - name: ModalName.warningModal, - args: { - title: 'Invalid link', - subtitle: 'The invite link you received is not valid. Please check it and try again.', - }, - }) - ) - .not.put(communities.actions.createNetwork(payload)) - .run() - }) -}) +// await expectSaga(handleInvitationCodeSaga, communities.actions.handleInvitationCode(code)) +// .withState(store.getState()) +// .put(communities.actions.clearInvitationCode()) +// .put( +// modalsActions.openModal({ +// name: ModalName.warningModal, +// args: { +// title: 'Invalid link', +// subtitle: 'The invite link you received is not valid. Please check it and try again.', +// }, +// }) +// ) +// .not.put(communities.actions.createNetwork(payload)) +// .run() +// }) +// }) diff --git a/packages/desktop/src/renderer/sagas/invitation/handleInvitationCode.saga.ts b/packages/desktop/src/renderer/sagas/invitation/handleInvitationCode.saga.ts index 5fab99fb8d..1a88cce5ca 100644 --- a/packages/desktop/src/renderer/sagas/invitation/handleInvitationCode.saga.ts +++ b/packages/desktop/src/renderer/sagas/invitation/handleInvitationCode.saga.ts @@ -8,7 +8,7 @@ import { ModalName } from '../modals/modals.types' import { modalsActions } from '../modals/modals.slice' export function* handleInvitationCodeSaga( - action: PayloadAction['payload']> + action: PayloadAction['payload']> ): Generator { while (true) { const connected = yield* select(socketSelectors.isConnected) @@ -32,26 +32,27 @@ export function* handleInvitationCodeSaga( return } - const code = action.payload.trim() + // const code = action.payload.trim() - if (code.match(ONION_ADDRESS_REGEX)) { - const payload: CreateNetworkPayload = { - ownership: CommunityOwnership.User, - registrar: code, - } - yield* put(communities.actions.createNetwork(payload)) - return + // if (code.match(ONION_ADDRESS_REGEX)) { + const payload: CreateNetworkPayload = { + ownership: CommunityOwnership.User, + peers: action.payload, } + yield* put(communities.actions.createNetwork(payload)) + return + // } - yield* put(communities.actions.clearInvitationCode()) + // TODO: handle invalid code + // yield* put(communities.actions.clearInvitationCode()) - yield* put( - modalsActions.openModal({ - name: ModalName.warningModal, - args: { - title: 'Invalid link', - subtitle: 'The invite link you received is not valid. Please check it and try again.', - }, - }) - ) + // yield* put( + // modalsActions.openModal({ + // name: ModalName.warningModal, + // args: { + // title: 'Invalid link', + // subtitle: 'The invite link you received is not valid. Please check it and try again.', + // }, + // }) + // ) } diff --git a/packages/mobile/src/components/JoinCommunity/JoinCommunity.component.tsx b/packages/mobile/src/components/JoinCommunity/JoinCommunity.component.tsx index 1f4b90c4aa..96dbf5f5fe 100644 --- a/packages/mobile/src/components/JoinCommunity/JoinCommunity.component.tsx +++ b/packages/mobile/src/components/JoinCommunity/JoinCommunity.component.tsx @@ -7,7 +7,7 @@ import { Typography } from '../Typography/Typography.component' import { TextWithLink } from '../TextWithLink/TextWithLink.component' import { JoinCommunityProps } from './JoinCommunity.types' -import { getInvitationCode } from '@quiet/state-manager' +import { getInvitationCodes } from '@quiet/state-manager' import { ONION_ADDRESS_REGEX } from '@quiet/common' export const JoinCommunity: FC = ({ @@ -32,16 +32,14 @@ export const JoinCommunity: FC = ({ Keyboard.dismiss() setLoading(true) - let submitValue: string | undefined = joinCommunityInput - - if (submitValue === undefined || submitValue?.length === 0) { + if (joinCommunityInput === undefined || joinCommunityInput?.length === 0) { setLoading(false) setInputError('Community address can not be empty') return } - submitValue = getInvitationCode(submitValue.trim()) - if (!submitValue || !submitValue.match(ONION_ADDRESS_REGEX)) { + const submitValue = getInvitationCodes(joinCommunityInput.trim()) + if (!submitValue?.length) { setLoading(false) setInputError('Please check your invitation code and try again') return diff --git a/packages/mobile/src/components/JoinCommunity/JoinCommunity.types.ts b/packages/mobile/src/components/JoinCommunity/JoinCommunity.types.ts index fb07d60ca6..3086a86790 100644 --- a/packages/mobile/src/components/JoinCommunity/JoinCommunity.types.ts +++ b/packages/mobile/src/components/JoinCommunity/JoinCommunity.types.ts @@ -1,5 +1,7 @@ +import { InvitationPair } from '@quiet/types' + export interface JoinCommunityProps { - joinCommunityAction: (address: string) => void + joinCommunityAction: (address: InvitationPair[]) => void redirectionAction: () => void networkCreated: boolean invitationCode?: string diff --git a/packages/state-manager/src/index.ts b/packages/state-manager/src/index.ts index 3f6b0da070..b247ed9833 100644 --- a/packages/state-manager/src/index.ts +++ b/packages/state-manager/src/index.ts @@ -83,7 +83,7 @@ export { formatBytes } from './utils/functions/formatBytes/formatBytes' export { sortPeers } from './utils/functions/sortPeers/sortPeers' -export { getInvitationCode } from './utils/functions/invitationCode/invitationCode' +export { getInvitationCodes } from './utils/functions/invitationCode/invitationCode' export type { Socket } from './types' diff --git a/packages/state-manager/src/sagas/communities/communities.slice.ts b/packages/state-manager/src/sagas/communities/communities.slice.ts index 41072e01d6..af67c8b8ab 100644 --- a/packages/state-manager/src/sagas/communities/communities.slice.ts +++ b/packages/state-manager/src/sagas/communities/communities.slice.ts @@ -2,6 +2,7 @@ import { createSlice, type EntityState, type PayloadAction } from '@reduxjs/tool import { StoreKeys } from '../store.keys' import { communitiesAdapter } from './communities.adapter' import { + InvitationPair, type AddOwnerCertificatePayload, type Community as CommunityType, type CreateNetworkPayload, @@ -14,6 +15,7 @@ import { export class CommunitiesState { public invitationCode: string | undefined = undefined + public invitationCodes: InvitationPair[] = [] public currentCommunity = '' public communities: EntityState = communitiesAdapter.getInitialState() } @@ -93,7 +95,13 @@ export const communitiesSlice = createSlice({ state.invitationCode = action.payload }, clearInvitationCode: state => { - state.invitationCode = undefined + state.invitationCode = '' + }, + handleInvitationCodes: (state, action: PayloadAction) => { + state.invitationCodes = action.payload + }, + clearInvitationCodes: state => { + state.invitationCodes = [] }, addOwnerCertificate: (state, action: PayloadAction) => { const { communityId, ownerCertificate } = action.payload diff --git a/packages/state-manager/src/sagas/communities/createNetwork/createNetwork.saga.ts b/packages/state-manager/src/sagas/communities/createNetwork/createNetwork.saga.ts index 028efd328e..bb84e67e3e 100644 --- a/packages/state-manager/src/sagas/communities/createNetwork/createNetwork.saga.ts +++ b/packages/state-manager/src/sagas/communities/createNetwork/createNetwork.saga.ts @@ -29,7 +29,7 @@ export function* createNetworkSaga( const id = yield* call(generateId) - const registrarUrl = action.payload.registrar ? `http://${action.payload.registrar}.onion` : undefined + // const registrarUrl = action.payload.registrar ? `http://${action.payload.registrar}.onion` : undefined const payload: Community = { id, diff --git a/packages/state-manager/src/utils/functions/invitationCode/invitationCode.test.ts b/packages/state-manager/src/utils/functions/invitationCode/invitationCode.test.ts index 31c99bf1e5..8acc98f561 100644 --- a/packages/state-manager/src/utils/functions/invitationCode/invitationCode.test.ts +++ b/packages/state-manager/src/utils/functions/invitationCode/invitationCode.test.ts @@ -1,14 +1,25 @@ -import { getInvitationCode } from './invitationCode' +import { getInvitationCodes } from './invitationCode' import { Site } from '@quiet/common' describe('Invitation code helper', () => { it('retrieves invitation code if url is a proper share url', () => { - const result = getInvitationCode(`https://${Site.DOMAIN}/${Site.JOIN_PAGE}#validCode`) - expect(result).toEqual('validCode') + const result = getInvitationCodes(`https://${Site.DOMAIN}/${Site.JOIN_PAGE}#peerId1=address1&peerId2=address2`) + expect(result).toEqual([ + { peerId: 'peerId1', address: 'address1' }, + { peerId: 'peerId2', address: 'address2' }, + ]) }) - it('returns passed value if url is not a proper share url', () => { - const result = getInvitationCode('validCode') - expect(result).toEqual('validCode') + it('returns empty list if code is not a proper share url nor a code', () => { + const result = getInvitationCodes('invalidCode') + expect(result).toEqual([]) + }) + + it('retrieves invitation code if url is a proper code', () => { + const result = getInvitationCodes(`peerId1=address1&peerId2=address2`) + expect(result).toEqual([ + { peerId: 'peerId1', address: 'address1' }, + { peerId: 'peerId2', address: 'address2' }, + ]) }) }) diff --git a/packages/state-manager/src/utils/functions/invitationCode/invitationCode.ts b/packages/state-manager/src/utils/functions/invitationCode/invitationCode.ts index e2abc314cb..cd85cedd48 100644 --- a/packages/state-manager/src/utils/functions/invitationCode/invitationCode.ts +++ b/packages/state-manager/src/utils/functions/invitationCode/invitationCode.ts @@ -1,37 +1,68 @@ import { Site } from '@quiet/common' +import { InvitationPair } from '@quiet/types' -export const getInvitationCode = (codeOrUrl: string): string => { +const getInvitationPairs = (code: string) => { + const pairs = code.split('&') + const codes: InvitationPair[] = [] + for (const pair of pairs) { + const [peerId, address] = pair.split('=') + if (!peerId || !address) continue + codes.push({ + peerId: peerId, + address: address, + }) + } + return codes +} + +export const getInvitationCodes = (codeOrUrl: string): InvitationPair[] => { /** - * Extract code from invitation share url or return passed value for further validation + * Extract codes from invitation share url or return passed value for further validation */ - let code = '' + let codes: InvitationPair[] = [] + let potentialCode let validUrl: URL | null = null try { validUrl = new URL(codeOrUrl) } catch (e) { - code = codeOrUrl + // It may be just code, not URL + potentialCode = codeOrUrl } + if (validUrl && validUrl.host === Site.DOMAIN && validUrl.pathname.includes(Site.JOIN_PAGE)) { const hash = validUrl.hash + // const params = validUrl.searchParams + // TODO: I don't think handling params is needed here as we only accept url with '#' and code without url - let invitationCode: string = hash.substring(1) + // if (params) { + // // Type 'URLSearchParams' must have a '[Symbol.iterator]()' method that returns an iterator + // for (const [peerId, address] of params) { + // // TODO: basic check if peerid and address have proper format? + // if (peerId.length !== 46 || address.length !== 56) { + // console.log(`peerId '${peerId}' or address ${address} is not valid`) + // continue + // } + // codes.push({ + // peerId, + // address, + // }) + // } + // } - // Ensure backward compatibility - if (hash.includes('code=')) { - // Mix of old and new link - invitationCode = hash.substring(6) - } else if (validUrl.searchParams.has('code')) { - // Old link - invitationCode = validUrl.searchParams.get('code') || '' + if (hash) { + // Parse hash + const pairs = hash.substring(1) + codes = getInvitationPairs(pairs) } - - code = invitationCode + } else if (potentialCode) { + // Parse code just as hash value + codes = getInvitationPairs(potentialCode) } - if (!code) { - console.warn(`No invitation code. Code/url passed: ${codeOrUrl}`) + if (codes.length === 0) { + console.warn(`No invitation codes. Code/url passed: ${codeOrUrl}`) } - return code + return codes } diff --git a/packages/types/src/community.ts b/packages/types/src/community.ts index ee7c0467a2..0f2077e5c5 100644 --- a/packages/types/src/community.ts +++ b/packages/types/src/community.ts @@ -1,4 +1,5 @@ import { type HiddenService, type PeerId, type Identity } from './identity' +import { InvitationPair } from './network' export interface Community { id: string @@ -34,7 +35,7 @@ export interface NetworkData { export interface CreateNetworkPayload { ownership: CommunityOwnership name?: string - registrar?: string + peers?: InvitationPair[] } export interface ResponseCreateNetworkPayload { diff --git a/packages/types/src/network.ts b/packages/types/src/network.ts index 7795ea5b5f..beb1058ac9 100644 --- a/packages/types/src/network.ts +++ b/packages/types/src/network.ts @@ -2,3 +2,8 @@ export enum LoadingPanelType { StartingApplication = 'Starting Quiet', Joining = 'Connecting to peers', } + +export type InvitationPair = { + peerId: string + address: string +} From 7510cab527330ae1ee11258e502643c347c9f563 Mon Sep 17 00:00:00 2001 From: Emi Date: Thu, 10 Aug 2023 11:13:39 +0200 Subject: [PATCH 02/61] Adjust code to multiple addresses --- packages/common/package-lock.json | 298 +++++++++++++++++- packages/common/package.json | 1 + packages/common/src/invitationCode.test.ts | 28 +- packages/common/src/invitationCode.ts | 91 ++++-- packages/common/tsconfig.build.json | 1 + packages/common/tsconfig.json | 2 +- packages/desktop/src/main/main.test.ts | 7 +- packages/desktop/src/main/main.ts | 8 +- packages/desktop/src/renderer/index.tsx | 10 +- .../communities/communities.selectors.test.ts | 52 +-- .../communities/communities.selectors.ts | 8 +- .../createNetwork/createNetwork.saga.test.ts | 2 +- .../createNetwork/createNetwork.saga.ts | 2 +- 13 files changed, 429 insertions(+), 81 deletions(-) diff --git a/packages/common/package-lock.json b/packages/common/package-lock.json index c02a9138ae..650f4c3902 100644 --- a/packages/common/package-lock.json +++ b/packages/common/package-lock.json @@ -10,7 +10,8 @@ "license": "ISC", "dependencies": { "cross-env": "^5.2.0", - "debug": "^4.3.1" + "debug": "^4.3.1", + "multiaddr": "^10.0.1" }, "devDependencies": { "@types/jest": "^26.0.23", @@ -4052,6 +4053,178 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/multiaddr": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/multiaddr/-/multiaddr-10.0.1.tgz", + "integrity": "sha512-G5upNcGzEGuTHkzxezPrrD6CaIHR9uo+7MwqhNVcXTs33IInon4y7nMiGxl2CY5hG7chvYQUQhz5V52/Qe3cbg==", + "deprecated": "This module is deprecated, please upgrade to @multiformats/multiaddr", + "dependencies": { + "dns-over-http-resolver": "^1.2.3", + "err-code": "^3.0.1", + "is-ip": "^3.1.0", + "multiformats": "^9.4.5", + "uint8arrays": "^3.0.0", + "varint": "^6.0.0" + } + }, + "node_modules/multiaddr/node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "peer": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/multiaddr/node_modules/dns-over-http-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/dns-over-http-resolver/-/dns-over-http-resolver-1.2.3.tgz", + "integrity": "sha512-miDiVSI6KSNbi4SVifzO/reD8rMnxgrlnkrlkugOLQpWQTe2qMdHsZp5DmfKjxNE+/T3VAAYLQUZMv9SMr6+AA==", + "dependencies": { + "debug": "^4.3.1", + "native-fetch": "^3.0.0", + "receptacle": "^1.3.2" + } + }, + "node_modules/multiaddr/node_modules/err-code": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", + "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==" + }, + "node_modules/multiaddr/node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "peer": true, + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/multiaddr/node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "peer": true, + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/multiaddr/node_modules/ip-regex": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", + "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/multiaddr/node_modules/is-ip": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-3.1.0.tgz", + "integrity": "sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==", + "dependencies": { + "ip-regex": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/multiaddr/node_modules/multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==" + }, + "node_modules/multiaddr/node_modules/native-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/native-fetch/-/native-fetch-3.0.0.tgz", + "integrity": "sha512-G3Z7vx0IFb/FQ4JxvtqGABsOTIqRWvgQz6e+erkB+JJD6LrszQtMozEHI4EkmgZQvnGHrpLVzUWk7t4sJCIkVw==", + "peerDependencies": { + "node-fetch": "*" + } + }, + "node_modules/multiaddr/node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "peer": true, + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/multiaddr/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "peer": true, + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/multiaddr/node_modules/receptacle": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/receptacle/-/receptacle-1.3.2.tgz", + "integrity": "sha512-HrsFvqZZheusncQRiEE7GatOAETrARKV/lnfYicIm8lbvp/JQOdADOfhjBd2DajvoszEyxSM6RlAAIZgEoeu/A==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/multiaddr/node_modules/uint8arrays": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", + "integrity": "sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==", + "dependencies": { + "multiformats": "^9.4.2" + } + }, + "node_modules/multiaddr/node_modules/varint": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", + "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==" + }, + "node_modules/multiaddr/node_modules/web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "peer": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -9351,6 +9524,129 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "multiaddr": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/multiaddr/-/multiaddr-10.0.1.tgz", + "integrity": "sha512-G5upNcGzEGuTHkzxezPrrD6CaIHR9uo+7MwqhNVcXTs33IInon4y7nMiGxl2CY5hG7chvYQUQhz5V52/Qe3cbg==", + "requires": { + "dns-over-http-resolver": "^1.2.3", + "err-code": "^3.0.1", + "is-ip": "^3.1.0", + "multiformats": "^9.4.5", + "uint8arrays": "^3.0.0", + "varint": "^6.0.0" + }, + "dependencies": { + "data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "peer": true + }, + "dns-over-http-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/dns-over-http-resolver/-/dns-over-http-resolver-1.2.3.tgz", + "integrity": "sha512-miDiVSI6KSNbi4SVifzO/reD8rMnxgrlnkrlkugOLQpWQTe2qMdHsZp5DmfKjxNE+/T3VAAYLQUZMv9SMr6+AA==", + "requires": { + "debug": "^4.3.1", + "native-fetch": "^3.0.0", + "receptacle": "^1.3.2" + } + }, + "err-code": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", + "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==" + }, + "fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "peer": true, + "requires": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + } + }, + "formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "peer": true, + "requires": { + "fetch-blob": "^3.1.2" + } + }, + "ip-regex": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", + "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==" + }, + "is-ip": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-3.1.0.tgz", + "integrity": "sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==", + "requires": { + "ip-regex": "^4.0.0" + } + }, + "multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==" + }, + "native-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/native-fetch/-/native-fetch-3.0.0.tgz", + "integrity": "sha512-G3Z7vx0IFb/FQ4JxvtqGABsOTIqRWvgQz6e+erkB+JJD6LrszQtMozEHI4EkmgZQvnGHrpLVzUWk7t4sJCIkVw==", + "requires": {} + }, + "node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "peer": true + }, + "node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "peer": true, + "requires": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + } + }, + "receptacle": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/receptacle/-/receptacle-1.3.2.tgz", + "integrity": "sha512-HrsFvqZZheusncQRiEE7GatOAETrARKV/lnfYicIm8lbvp/JQOdADOfhjBd2DajvoszEyxSM6RlAAIZgEoeu/A==", + "requires": { + "ms": "^2.1.1" + } + }, + "uint8arrays": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", + "integrity": "sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==", + "requires": { + "multiformats": "^9.4.2" + } + }, + "varint": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", + "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==" + }, + "web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "peer": true + } + } + }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", diff --git a/packages/common/package.json b/packages/common/package.json index d320789a04..5818f4b888 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -26,6 +26,7 @@ }, "dependencies": { "@quiet/types": "^1.6.0", + "multiaddr": "^10.0.1", "cross-env": "^5.2.0", "debug": "^4.3.1" }, diff --git a/packages/common/src/invitationCode.test.ts b/packages/common/src/invitationCode.test.ts index 78bd1420c1..1f49410df5 100644 --- a/packages/common/src/invitationCode.test.ts +++ b/packages/common/src/invitationCode.test.ts @@ -3,22 +3,42 @@ import { Site } from './static' describe('Invitation code helper', () => { it('retrieves invitation code from argv', () => { + const expectedCodes = [ + { peerId: 'peerID1', address: 'address1' }, + { peerId: 'peerID2', address: 'address2' }, + ] const result = argvInvitationCode([ 'something', 'quiet:/invalid', 'zbay://invalid', 'quiet://invalid', 'quiet://?param=invalid', - invitationDeepUrl('validCode'), + invitationDeepUrl(expectedCodes), ]) - expect(result).toBe('validCode') + expect(result).toBe(expectedCodes) }) it('builds proper invitation deep url', () => { - expect(invitationDeepUrl('validCode')).toEqual('quiet://?code=validCode') + expect( + invitationDeepUrl([ + { peerId: 'peerID1', address: 'address1' }, + { peerId: 'peerID2', address: 'address2' }, + ]) + ).toEqual('quiet://?peerID1=address1&peerID2=address2') }) + // it('builds proper invitation share url', () => { + // expect(invitationShareUrl('validCode')).toEqual(`https://${Site.DOMAIN}/${Site.JOIN_PAGE}#validCode`) + // }) + it('builds proper invitation share url', () => { - expect(invitationShareUrl('validCode')).toEqual(`https://${Site.DOMAIN}/${Site.JOIN_PAGE}#validCode`) + const peerList = [ + '/dns4/gloao6h5plwjy4tdlze24zzgcxll6upq2ex2fmu2ohhyu4gtys4nrjad.onion/tcp/443/wss/p2p/QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSE', + 'invalidAddress', + '/dns4/somethingElse.onion/tcp/443/wss/p2p/QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSA', + ] + expect(invitationShareUrl(peerList)).toEqual( + `https://${Site.DOMAIN}/${Site.JOIN_PAGE}#QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSE=gloao6h5plwjy4tdlze24zzgcxll6upq2ex2fmu2ohhyu4gtys4nrjad&QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSA=somethingElse` + ) }) }) diff --git a/packages/common/src/invitationCode.ts b/packages/common/src/invitationCode.ts index b43cb2053f..b3b83b53e6 100644 --- a/packages/common/src/invitationCode.ts +++ b/packages/common/src/invitationCode.ts @@ -1,27 +1,28 @@ import { InvitationPair } from '@quiet/types' import { InvitationParams, Site } from './static' +import { multiaddr } from 'multiaddr' -export const retrieveInvitationCode = (url: string): string => { - /** - * Extract invitation code from deep url. - * Valid format: quiet://?code= - */ - let data: URL - try { - data = new URL(url) - } catch (e) { - return '' - } - if (!data || data.protocol !== 'quiet:') return '' - const code = data.searchParams.get(InvitationParams.CODE) - if (code) { - console.log('Retrieved code:', code) - return code - } - return '' -} +// export const retrieveInvitationCode = (url: string): string => { +// /** +// * Extract invitation code from deep url. +// * Valid format: quiet://?code= +// */ +// let data: URL +// try { +// data = new URL(url) +// } catch (e) { +// return '' +// } +// if (!data || data.protocol !== 'quiet:') return '' +// const code = data.searchParams.get(InvitationParams.CODE) +// if (code) { +// console.log('Retrieved code:', code) +// return code +// } +// return '' +// } -export const retrieveInvitationCodePairs = (url: string): InvitationPair[] => { +export const retrieveInvitationCode = (url: string): InvitationPair[] => { /** * Extract invitation code from deep url. * Valid format: quiet://?=&= @@ -50,14 +51,34 @@ export const retrieveInvitationCodePairs = (url: string): InvitationPair[] => { return codes } -export const invitationShareUrlMultipleAddresses = (pairs: InvitationPair[] = []): string => { +export const invitationShareUrl = (peers: string[] = []): string => { // Valid format: https://tryquiet.org/join/#=&= - const code = pairs.map(pair => `${pair.peerId}=${pair.address}`).join('&') - const url = new URL(`https://${Site.DOMAIN}/${Site.JOIN_PAGE}#${code}`) + const pairs = [] + for (const peerAddress of peers) { + let addr + try { + addr = multiaddr(peerAddress) + } catch (e) { + console.error(`Could not add peer address '${peerAddress}' to invitation url. Reason: ${e.message}`) + continue + } + + const peerId = addr.getPeerId() + const address: string = addr.nodeAddress().address + if (!peerId || !address) { + console.error('NO PEER ID OR ADDRESS IN', peerAddress) + continue + } + const rawAddress = address.endsWith('.onion') ? address.split('.')[0] : address + pairs.push(`${peerId}=${rawAddress}`) + } + + console.log('CODE', pairs.join('&')) + const url = new URL(`https://${Site.DOMAIN}/${Site.JOIN_PAGE}#${pairs.join('&')}`) return url.href } -export const invitationDeepUrlMultipleAddresses = (pairs: InvitationPair[] = []): string => { +export const invitationDeepUrl = (pairs: InvitationPair[] = []): string => { const url = new URL('quiet://') for (const pair of pairs) { url.searchParams.append(pair.peerId, pair.address) @@ -67,11 +88,11 @@ export const invitationDeepUrlMultipleAddresses = (pairs: InvitationPair[] = []) export const argvInvitationCode = (argv: string[]): InvitationPair[] => { /** - * Extract invitation code from deep url if url is present in argv + * Extract invitation codes from deep url if url is present in argv */ let invitationCodes = [] for (const arg of argv) { - invitationCodes = retrieveInvitationCodePairs(arg) + invitationCodes = retrieveInvitationCode(arg) if (invitationCodes.length > 0) { break } @@ -93,13 +114,13 @@ export const argvInvitationCode = (argv: string[]): InvitationPair[] => { // return invitationCode // } -export const invitationDeepUrl = (code = ''): string => { - const url = new URL('quiet://') - url.searchParams.append(InvitationParams.CODE, code) - return url.href -} +// export const invitationDeepUrl = (code = ''): string => { +// const url = new URL('quiet://') +// url.searchParams.append(InvitationParams.CODE, code) +// return url.href +// } -export const invitationShareUrl = (code = ''): string => { - const url = new URL(`https://${Site.DOMAIN}/${Site.JOIN_PAGE}#${code}`) - return url.href -} +// export const invitationShareUrl = (code = ''): string => { +// const url = new URL(`https://${Site.DOMAIN}/${Site.JOIN_PAGE}#${code}`) +// return url.href +// } diff --git a/packages/common/tsconfig.build.json b/packages/common/tsconfig.build.json index 4c2fe1bc29..b889234c8b 100644 --- a/packages/common/tsconfig.build.json +++ b/packages/common/tsconfig.build.json @@ -1,6 +1,7 @@ { "extends": "../../tsconfig.build.json", "compilerOptions": { + "target": "ES2020", "rootDir": "./src", "outDir": "./lib", "typeRoots": [ diff --git a/packages/common/tsconfig.json b/packages/common/tsconfig.json index afd02831cc..6adb0ef8de 100644 --- a/packages/common/tsconfig.json +++ b/packages/common/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "target": "es2017", + "target": "ES2020", "outDir": "./lib", "typeRoots": [ "../@types", diff --git a/packages/desktop/src/main/main.test.ts b/packages/desktop/src/main/main.test.ts index faa8e913fc..ad6203d06c 100644 --- a/packages/desktop/src/main/main.test.ts +++ b/packages/desktop/src/main/main.test.ts @@ -243,14 +243,17 @@ describe('Invitation code', () => { const code = 'invitationCode' expect(mockAppOnCalls[1][0]).toBe('open-url') const event = { preventDefault: () => {} } - mockAppOnCalls[1][1](event, invitationDeepUrl(code)) + mockAppOnCalls[1][1](event, invitationDeepUrl([{ peerId: 'peerId1', address: 'address' }])) expect(mockWindowWebContentsSend).toHaveBeenCalledWith('invitation', { code: code }) }) it('process invitation code on second-instance event', async () => { const code = 'invitationCodeArgv' await mockAppOnCalls[2][1]() - const commandLine = ['/tmp/.mount_Quiet-TVQc6s/quiet', invitationDeepUrl(code)] + const commandLine = [ + '/tmp/.mount_Quiet-TVQc6s/quiet', + invitationDeepUrl([{ peerId: 'peerId1', address: 'address' }]), + ] expect(mockAppOnCalls[0][0]).toBe('second-instance') const event = { preventDefault: () => {} } mockAppOnCalls[0][1](event, commandLine) diff --git a/packages/desktop/src/main/main.ts b/packages/desktop/src/main/main.ts index bbb35bf6ac..b8c20310f4 100644 --- a/packages/desktop/src/main/main.ts +++ b/packages/desktop/src/main/main.ts @@ -11,7 +11,7 @@ import { Crypto } from '@peculiar/webcrypto' import logger from './logger' import { DATA_DIR, DEV_DATA_DIR } from '../shared/static' import { fork, ChildProcess } from 'child_process' -import { argvInvitationCode, getFilesData, retrieveInvitationCodePairs } from '@quiet/common' +import { argvInvitationCode, getFilesData, retrieveInvitationCode } from '@quiet/common' import { updateDesktopFile, processInvitationCode } from './invitation' const ElectronStore = require('electron-store') ElectronStore.initRenderer() @@ -147,7 +147,7 @@ app.on('open-url', (event, url) => { event.preventDefault() if (mainWindow) { invitationUrl = null - const invitationCode = retrieveInvitationCodePairs(url) + const invitationCode = retrieveInvitationCode(url) processInvitationCode(mainWindow, invitationCode) } }) @@ -474,12 +474,12 @@ app.on('ready', async () => { throw new Error(`mainWindow is on unexpected type ${mainWindow}`) } if (process.platform === 'darwin' && invitationUrl) { - const invitationCode = retrieveInvitationCodePairs(invitationUrl) + const invitationCode = retrieveInvitationCode(invitationUrl) processInvitationCode(mainWindow, invitationCode) invitationUrl = null } if (process.platform !== 'darwin' && process.argv) { - const invitationCode = argvInvitationCodeMultipleAddresses(process.argv) + const invitationCode = argvInvitationCode(process.argv) processInvitationCode(mainWindow, invitationCode) } diff --git a/packages/desktop/src/renderer/index.tsx b/packages/desktop/src/renderer/index.tsx index bf35aea3bc..02942c0c46 100644 --- a/packages/desktop/src/renderer/index.tsx +++ b/packages/desktop/src/renderer/index.tsx @@ -21,15 +21,15 @@ ipcRenderer.on('force-save-state', async _event => { }) ipcRenderer.on('invitation', (_event, invitation) => { - console.log('invitation', invitation, 'dispatching action') - store.dispatch(communities.actions.handleInvitationCode(invitation.code)) -}) - -ipcRenderer.on('invitationMA', (_event, invitation) => { console.log('invitation', invitation, 'dispatching action') store.dispatch(communities.actions.handleInvitationCodes(invitation.codes)) }) +// ipcRenderer.on('invitationMA', (_event, invitation) => { +// console.log('invitation', invitation, 'dispatching action') +// store.dispatch(communities.actions.handleInvitationCodes(invitation.codes)) +// }) + const container = document.getElementById('root') if (!container) throw new Error('No root html element!') let root = createRoot(container) diff --git a/packages/state-manager/src/sagas/communities/communities.selectors.test.ts b/packages/state-manager/src/sagas/communities/communities.selectors.test.ts index c77232b733..2a582a3c32 100644 --- a/packages/state-manager/src/sagas/communities/communities.selectors.test.ts +++ b/packages/state-manager/src/sagas/communities/communities.selectors.test.ts @@ -97,32 +97,32 @@ describe('communitiesSelectors', () => { expect(invitationUrl).toEqual('') }) - it('returns proper invitation url if registrationUrl is in old format', async () => { - const code = 'aznu6kiyutsgjhdue4i4xushjzey6boxf4i4isd53admsibvbt6qyiyd' - const registrarUrl = `http://${code}` - const { store } = prepareStore() - const factory = await getFactory(store) - await factory.create['payload']>('Community', { - registrarUrl, - port: 0, - onionAddress: '', - }) - const invitationUrl = communitiesSelectors.invitationUrl(store.getState()) - expect(invitationUrl).toEqual(invitationShareUrl(code)) - }) - - it('returns proper invitation url if registrationUrl is just onion address', async () => { - const code = 'aznu6kiyutsgjhdue4i4xushjzey6boxf4i4isd53admsibvbt6qyiyd' - const { store } = prepareStore() - const factory = await getFactory(store) - await factory.create['payload']>('Community', { - registrarUrl: code, - port: 0, - onionAddress: '', - }) - const invitationUrl = communitiesSelectors.invitationUrl(store.getState()) - expect(invitationUrl).toEqual(invitationShareUrl(code)) - }) + // it('returns proper invitation url if registrationUrl is in old format', async () => { + // const code = 'aznu6kiyutsgjhdue4i4xushjzey6boxf4i4isd53admsibvbt6qyiyd' + // const registrarUrl = `http://${code}` + // const { store } = prepareStore() + // const factory = await getFactory(store) + // await factory.create['payload']>('Community', { + // registrarUrl, + // port: 0, + // onionAddress: '', + // }) + // const invitationUrl = communitiesSelectors.invitationUrl(store.getState()) + // expect(invitationUrl).toEqual(invitationShareUrl(code)) + // }) + + // it('returns proper invitation url if registrationUrl is just onion address', async () => { + // const code = 'aznu6kiyutsgjhdue4i4xushjzey6boxf4i4isd53admsibvbt6qyiyd' + // const { store } = prepareStore() + // const factory = await getFactory(store) + // await factory.create['payload']>('Community', { + // registrarUrl: code, + // port: 0, + // onionAddress: '', + // }) + // const invitationUrl = communitiesSelectors.invitationUrl(store.getState()) + // expect(invitationUrl).toEqual(invitationShareUrl(code)) + // }) it('returns proper ownerNickname - ownerCertificate exist', async () => { const { store } = prepareStore() diff --git a/packages/state-manager/src/sagas/communities/communities.selectors.ts b/packages/state-manager/src/sagas/communities/communities.selectors.ts index 4a1d44c232..8e2e3c0e4c 100644 --- a/packages/state-manager/src/sagas/communities/communities.selectors.ts +++ b/packages/state-manager/src/sagas/communities/communities.selectors.ts @@ -58,6 +58,12 @@ export const invitationCode = createSelector(communitiesSlice, reducerState => { }) export const invitationUrl = createSelector(currentCommunity, community => { + const peerList = community?.peerList + if (!peerList || peerList?.length === 0) return + const initialPeers = peerList.slice(0, 4) + + console.log('invitationUrl INITIAL PEERS', initialPeers) + if (!community?.registrarUrl) return '' let registrarUrl = '' try { @@ -66,7 +72,7 @@ export const invitationUrl = createSelector(currentCommunity, community => { } catch (e) { registrarUrl = community.registrarUrl } - return invitationShareUrl(registrarUrl) + return invitationShareUrl(initialPeers) }) export const registrationAttempts = (communityId: string) => diff --git a/packages/state-manager/src/sagas/communities/createNetwork/createNetwork.saga.test.ts b/packages/state-manager/src/sagas/communities/createNetwork/createNetwork.saga.test.ts index 551daff58e..c23dd92a7f 100644 --- a/packages/state-manager/src/sagas/communities/createNetwork/createNetwork.saga.test.ts +++ b/packages/state-manager/src/sagas/communities/createNetwork/createNetwork.saga.test.ts @@ -28,7 +28,7 @@ describe('createNetwork', () => { createNetworkSaga, communitiesActions.createNetwork({ ownership: CommunityOwnership.User, - registrar: 'registrarUrl', + peers: [{ peerId: 'peerId', address: 'address' }], }) ) .withReducer(reducer) diff --git a/packages/state-manager/src/sagas/communities/createNetwork/createNetwork.saga.ts b/packages/state-manager/src/sagas/communities/createNetwork/createNetwork.saga.ts index bb84e67e3e..1fa5b13771 100644 --- a/packages/state-manager/src/sagas/communities/createNetwork/createNetwork.saga.ts +++ b/packages/state-manager/src/sagas/communities/createNetwork/createNetwork.saga.ts @@ -34,7 +34,7 @@ export function* createNetworkSaga( const payload: Community = { id, name: action.payload.name, - registrarUrl, + // registrarUrl, CA, rootCa: CA?.rootCertString, } From 525383acb4c9e5efcc12fc7872c34bef5e821529 Mon Sep 17 00:00:00 2001 From: Emi Date: Fri, 11 Aug 2023 16:31:21 +0200 Subject: [PATCH 03/61] Adjust code to display multiple addresses in invitation url tsconfig - change target to es2020, add dom.iterable to lib options --- packages/common/src/invitationCode.ts | 6 +- .../invitation/handleInvitationCode.saga.ts | 1 - packages/desktop/tsconfig.json | 1 + packages/logger/tsconfig.json | 2 +- .../menus/InvitationContextMenu.container.tsx | 2 +- .../JoinCommunity/JoinCommunity.screen.tsx | 6 +- .../store/init/deepLink/deepLink.saga.test.ts | 436 +++++++++--------- .../src/store/init/deepLink/deepLink.saga.ts | 19 +- .../communities/communities.selectors.ts | 18 +- tsconfig.build.json | 5 +- tsconfig.json | 7 +- 11 files changed, 253 insertions(+), 250 deletions(-) diff --git a/packages/common/src/invitationCode.ts b/packages/common/src/invitationCode.ts index b3b83b53e6..8ed1a54b2a 100644 --- a/packages/common/src/invitationCode.ts +++ b/packages/common/src/invitationCode.ts @@ -24,7 +24,7 @@ import { multiaddr } from 'multiaddr' export const retrieveInvitationCode = (url: string): InvitationPair[] => { /** - * Extract invitation code from deep url. + * Extract invitation codes from deep url. * Valid format: quiet://?=&= */ let data: URL @@ -36,7 +36,7 @@ export const retrieveInvitationCode = (url: string): InvitationPair[] => { if (!data || data.protocol !== 'quiet:') return [] const params = data.searchParams const codes: InvitationPair[] = [] - for (const [peerId, address] of params) { + for (const [peerId, address] of params.entries()) { // TODO: basic check if peerid and address have proper format? if (peerId.length !== 46 || address.length !== 56) { console.log(`peerId '${peerId}' or address ${address} is not valid`) @@ -90,7 +90,7 @@ export const argvInvitationCode = (argv: string[]): InvitationPair[] => { /** * Extract invitation codes from deep url if url is present in argv */ - let invitationCodes = [] + let invitationCodes: InvitationPair[] = [] for (const arg of argv) { invitationCodes = retrieveInvitationCode(arg) if (invitationCodes.length > 0) { diff --git a/packages/desktop/src/renderer/sagas/invitation/handleInvitationCode.saga.ts b/packages/desktop/src/renderer/sagas/invitation/handleInvitationCode.saga.ts index 1a88cce5ca..d2bd74f864 100644 --- a/packages/desktop/src/renderer/sagas/invitation/handleInvitationCode.saga.ts +++ b/packages/desktop/src/renderer/sagas/invitation/handleInvitationCode.saga.ts @@ -3,7 +3,6 @@ import { select, put, delay } from 'typed-redux-saga' import { CommunityOwnership, CreateNetworkPayload } from '@quiet/types' import { communities } from '@quiet/state-manager' import { socketSelectors } from '../socket/socket.selectors' -import { ONION_ADDRESS_REGEX } from '@quiet/common' import { ModalName } from '../modals/modals.types' import { modalsActions } from '../modals/modals.slice' diff --git a/packages/desktop/tsconfig.json b/packages/desktop/tsconfig.json index 7a5f655f77..0186a73548 100644 --- a/packages/desktop/tsconfig.json +++ b/packages/desktop/tsconfig.json @@ -10,6 +10,7 @@ "allowJs": true, "lib": [ "ES2020", + "DOM.Iterable", "dom" ], "typeRoots": [ diff --git a/packages/logger/tsconfig.json b/packages/logger/tsconfig.json index afd02831cc..6adb0ef8de 100644 --- a/packages/logger/tsconfig.json +++ b/packages/logger/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "target": "es2017", + "target": "ES2020", "outDir": "./lib", "typeRoots": [ "../@types", diff --git a/packages/mobile/src/components/ContextMenu/menus/InvitationContextMenu.container.tsx b/packages/mobile/src/components/ContextMenu/menus/InvitationContextMenu.container.tsx index f28feff2da..19b5398145 100644 --- a/packages/mobile/src/components/ContextMenu/menus/InvitationContextMenu.container.tsx +++ b/packages/mobile/src/components/ContextMenu/menus/InvitationContextMenu.container.tsx @@ -22,7 +22,7 @@ export const InvitationContextMenu: FC = () => { const screen = useSelector(navigationSelectors.currentScreen) - const community = useSelector(communities.selectors.currentCommunity) + // const community = useSelector(communities.selectors.currentCommunity) const invitationLink = useSelector(communities.selectors.invitationUrl) const invitationContextMenu = useContextMenu(MenuName.Invitation) diff --git a/packages/mobile/src/screens/JoinCommunity/JoinCommunity.screen.tsx b/packages/mobile/src/screens/JoinCommunity/JoinCommunity.screen.tsx index 2cf924a853..b73ef07e52 100644 --- a/packages/mobile/src/screens/JoinCommunity/JoinCommunity.screen.tsx +++ b/packages/mobile/src/screens/JoinCommunity/JoinCommunity.screen.tsx @@ -2,7 +2,7 @@ import React, { FC, useCallback, useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import { identity, communities } from '@quiet/state-manager' -import { CommunityOwnership, CreateNetworkPayload } from '@quiet/types' +import { CommunityOwnership, CreateNetworkPayload, InvitationPair } from '@quiet/types' import { JoinCommunity } from '../../components/JoinCommunity/JoinCommunity.component' import { navigationActions } from '../../store/navigation/navigation.slice' import { ScreenNames } from '../../const/ScreenNames.enum' @@ -35,10 +35,10 @@ export const JoinCommunityScreen: FC = ({ route }) => }, [dispatch, community, route.params?.code]) const joinCommunityAction = useCallback( - (address: string) => { + (pairs: InvitationPair[]) => { const payload: CreateNetworkPayload = { ownership: CommunityOwnership.User, - registrar: address, + peers: pairs, } dispatch(communities.actions.createNetwork(payload)) dispatch( diff --git a/packages/mobile/src/store/init/deepLink/deepLink.saga.test.ts b/packages/mobile/src/store/init/deepLink/deepLink.saga.test.ts index a2fb46277e..366c83c1f7 100644 --- a/packages/mobile/src/store/init/deepLink/deepLink.saga.test.ts +++ b/packages/mobile/src/store/init/deepLink/deepLink.saga.test.ts @@ -1,218 +1,218 @@ -import { expectSaga } from 'redux-saga-test-plan' -import { combineReducers } from '@reduxjs/toolkit' -import { reducers } from '../../root.reducer' -import { Store } from '../../store.types' -import { prepareStore } from '../../../tests/utils/prepareStore' -import { communities, Community, connection, identity } from '@quiet/state-manager' -import { initActions } from '../init.slice' -import { navigationActions } from '../../navigation/navigation.slice' -import { ScreenNames } from '../../../const/ScreenNames.enum' -import { deepLinkSaga } from './deepLink.saga' -import { CommunityOwnership, ConnectionProcessInfo, Identity } from '@quiet/types' - -describe('deepLinkSaga', () => { - let store: Store - - const id = '00d045ab' - - const community: Community = { - id, - name: '', - CA: { - rootCertString: '', - rootKeyString: '', - }, - rootCa: '', - peerList: [], - registrar: { - privateKey: '', - address: '', - }, - registrarUrl: 'https://bidrmzr3ee6qa2vvrlcnqvvvsk2gmjktcqkunba326parszr44gibwyd.onion', - onionAddress: '', - privateKey: '', - port: 0, - registrationAttempts: 0, - ownerCertificate: '', - } - - const _identity: Partial = { - id, - nickname: '', - userCsr: null, - userCertificate: null, - joinTimestamp: 0, - } - - beforeEach(async () => { - store = (await prepareStore()).store - }) - - test('joins community', async () => { - store.dispatch( - initActions.setWebsocketConnected({ - dataPort: 5001, - }) - ) - - const code = 'bidrmzr3ee6qa2vvrlcnqvvvsk2gmjktcqkunba326parszr44gibwyd' - - const reducer = combineReducers(reducers) - await expectSaga(deepLinkSaga, initActions.deepLink(code)) - .withReducer(reducer) - .withState(store.getState()) - .put( - navigationActions.replaceScreen({ - screen: ScreenNames.JoinCommunityScreen, - params: { - code, - }, - }) - ) - .put( - communities.actions.createNetwork({ - ownership: CommunityOwnership.User, - registrar: code, - }) - ) - .run() - }) - - test('opens channel list screen if the same url has been used', async () => { - store.dispatch( - initActions.setWebsocketConnected({ - dataPort: 5001, - }) - ) - - store.dispatch(communities.actions.addNewCommunity(community)) - - store.dispatch( - // @ts-expect-error - identity.actions.addNewIdentity({ ..._identity, userCertificate: 'certificate' }) - ) - - store.dispatch(communities.actions.setCurrentCommunity(community.id)) - - const code = 'bidrmzr3ee6qa2vvrlcnqvvvsk2gmjktcqkunba326parszr44gibwyd' - - const reducer = combineReducers(reducers) - await expectSaga(deepLinkSaga, initActions.deepLink(code)) - .withReducer(reducer) - .withState(store.getState()) - .put( - navigationActions.replaceScreen({ - screen: ScreenNames.ChannelListScreen, - }) - ) - .not.put( - communities.actions.createNetwork({ - ownership: CommunityOwnership.User, - registrar: code, - }) - ) - .run() - }) - - test('displays error if user already belongs to a community', async () => { - store.dispatch( - initActions.setWebsocketConnected({ - dataPort: 5001, - }) - ) - - store.dispatch(communities.actions.addNewCommunity(community)) - - store.dispatch(communities.actions.setCurrentCommunity(community.id)) - - const code = 'ctbebt3ixybtu4ty2dr3ychjtxpkhuun4neuavkjjhplgzfde5vgelad' - - const reducer = combineReducers(reducers) - await expectSaga(deepLinkSaga, initActions.deepLink(code)) - .withReducer(reducer) - .withState(store.getState()) - .not.put( - communities.actions.createNetwork({ - ownership: CommunityOwnership.User, - registrar: code, - }) - ) - .run() - }) - - test('continues if link used mid registration', async () => { - store.dispatch( - initActions.setWebsocketConnected({ - dataPort: 5001, - }) - ) - - store.dispatch(communities.actions.addNewCommunity(community)) - - store.dispatch( - // @ts-expect-error - identity.actions.addNewIdentity({ ..._identity, userCertificate: null }) - ) - - store.dispatch(communities.actions.setCurrentCommunity(community.id)) - - const code = 'bidrmzr3ee6qa2vvrlcnqvvvsk2gmjktcqkunba326parszr44gibwyd' - - const reducer = combineReducers(reducers) - await expectSaga(deepLinkSaga, initActions.deepLink(code)) - .withReducer(reducer) - .withState(store.getState()) - .put( - navigationActions.replaceScreen({ - screen: ScreenNames.UsernameRegistrationScreen, - params: undefined, - }) - ) - .not.put( - communities.actions.createNetwork({ - ownership: CommunityOwnership.User, - registrar: code, - }) - ) - .run() - }) - - test('continues if link used mid registration and locks input while waiting for server response', async () => { - store.dispatch( - initActions.setWebsocketConnected({ - dataPort: 5001, - }) - ) - - store.dispatch(communities.actions.addNewCommunity(community)) - - store.dispatch( - // @ts-expect-error - identity.actions.addNewIdentity({ ..._identity, userCertificate: null }) - ) - - store.dispatch(communities.actions.setCurrentCommunity(community.id)) - - store.dispatch(connection.actions.setTorConnectionProcess(ConnectionProcessInfo.REGISTERING_USER_CERTIFICATE)) - - const code = 'bidrmzr3ee6qa2vvrlcnqvvvsk2gmjktcqkunba326parszr44gibwyd' - - const reducer = combineReducers(reducers) - await expectSaga(deepLinkSaga, initActions.deepLink(code)) - .withReducer(reducer) - .withState(store.getState()) - .put( - navigationActions.replaceScreen({ - screen: ScreenNames.UsernameRegistrationScreen, - params: { fetching: true }, - }) - ) - .not.put( - communities.actions.createNetwork({ - ownership: CommunityOwnership.User, - registrar: code, - }) - ) - .run() - }) -}) +// import { expectSaga } from 'redux-saga-test-plan' +// import { combineReducers } from '@reduxjs/toolkit' +// import { reducers } from '../../root.reducer' +// import { Store } from '../../store.types' +// import { prepareStore } from '../../../tests/utils/prepareStore' +// import { communities, Community, connection, identity } from '@quiet/state-manager' +// import { initActions } from '../init.slice' +// import { navigationActions } from '../../navigation/navigation.slice' +// import { ScreenNames } from '../../../const/ScreenNames.enum' +// import { deepLinkSaga } from './deepLink.saga' +// import { CommunityOwnership, ConnectionProcessInfo, Identity } from '@quiet/types' + +// describe('deepLinkSaga', () => { +// let store: Store + +// const id = '00d045ab' + +// const community: Community = { +// id, +// name: '', +// CA: { +// rootCertString: '', +// rootKeyString: '', +// }, +// rootCa: '', +// peerList: [], +// registrar: { +// privateKey: '', +// address: '', +// }, +// registrarUrl: 'https://bidrmzr3ee6qa2vvrlcnqvvvsk2gmjktcqkunba326parszr44gibwyd.onion', +// onionAddress: '', +// privateKey: '', +// port: 0, +// registrationAttempts: 0, +// ownerCertificate: '', +// } + +// const _identity: Partial = { +// id, +// nickname: '', +// userCsr: null, +// userCertificate: null, +// joinTimestamp: 0, +// } + +// beforeEach(async () => { +// store = (await prepareStore()).store +// }) + +// test('joins community', async () => { +// store.dispatch( +// initActions.setWebsocketConnected({ +// dataPort: 5001, +// }) +// ) + +// const code = 'bidrmzr3ee6qa2vvrlcnqvvvsk2gmjktcqkunba326parszr44gibwyd' + +// const reducer = combineReducers(reducers) +// await expectSaga(deepLinkSaga, initActions.deepLink(code)) +// .withReducer(reducer) +// .withState(store.getState()) +// .put( +// navigationActions.replaceScreen({ +// screen: ScreenNames.JoinCommunityScreen, +// params: { +// code, +// }, +// }) +// ) +// .put( +// communities.actions.createNetwork({ +// ownership: CommunityOwnership.User, +// peers: code, +// }) +// ) +// .run() +// }) + +// test('opens channel list screen if the same url has been used', async () => { +// store.dispatch( +// initActions.setWebsocketConnected({ +// dataPort: 5001, +// }) +// ) + +// store.dispatch(communities.actions.addNewCommunity(community)) + +// store.dispatch( +// // @ts-expect-error +// identity.actions.addNewIdentity({ ..._identity, userCertificate: 'certificate' }) +// ) + +// store.dispatch(communities.actions.setCurrentCommunity(community.id)) + +// const code = 'bidrmzr3ee6qa2vvrlcnqvvvsk2gmjktcqkunba326parszr44gibwyd' + +// const reducer = combineReducers(reducers) +// await expectSaga(deepLinkSaga, initActions.deepLink(code)) +// .withReducer(reducer) +// .withState(store.getState()) +// .put( +// navigationActions.replaceScreen({ +// screen: ScreenNames.ChannelListScreen, +// }) +// ) +// .not.put( +// communities.actions.createNetwork({ +// ownership: CommunityOwnership.User, +// registrar: code, +// }) +// ) +// .run() +// }) + +// test('displays error if user already belongs to a community', async () => { +// store.dispatch( +// initActions.setWebsocketConnected({ +// dataPort: 5001, +// }) +// ) + +// store.dispatch(communities.actions.addNewCommunity(community)) + +// store.dispatch(communities.actions.setCurrentCommunity(community.id)) + +// const code = 'ctbebt3ixybtu4ty2dr3ychjtxpkhuun4neuavkjjhplgzfde5vgelad' + +// const reducer = combineReducers(reducers) +// await expectSaga(deepLinkSaga, initActions.deepLink(code)) +// .withReducer(reducer) +// .withState(store.getState()) +// .not.put( +// communities.actions.createNetwork({ +// ownership: CommunityOwnership.User, +// registrar: code, +// }) +// ) +// .run() +// }) + +// test('continues if link used mid registration', async () => { +// store.dispatch( +// initActions.setWebsocketConnected({ +// dataPort: 5001, +// }) +// ) + +// store.dispatch(communities.actions.addNewCommunity(community)) + +// store.dispatch( +// // @ts-expect-error +// identity.actions.addNewIdentity({ ..._identity, userCertificate: null }) +// ) + +// store.dispatch(communities.actions.setCurrentCommunity(community.id)) + +// const code = 'bidrmzr3ee6qa2vvrlcnqvvvsk2gmjktcqkunba326parszr44gibwyd' + +// const reducer = combineReducers(reducers) +// await expectSaga(deepLinkSaga, initActions.deepLink(code)) +// .withReducer(reducer) +// .withState(store.getState()) +// .put( +// navigationActions.replaceScreen({ +// screen: ScreenNames.UsernameRegistrationScreen, +// params: undefined, +// }) +// ) +// .not.put( +// communities.actions.createNetwork({ +// ownership: CommunityOwnership.User, +// registrar: code, +// }) +// ) +// .run() +// }) + +// test('continues if link used mid registration and locks input while waiting for server response', async () => { +// store.dispatch( +// initActions.setWebsocketConnected({ +// dataPort: 5001, +// }) +// ) + +// store.dispatch(communities.actions.addNewCommunity(community)) + +// store.dispatch( +// // @ts-expect-error +// identity.actions.addNewIdentity({ ..._identity, userCertificate: null }) +// ) + +// store.dispatch(communities.actions.setCurrentCommunity(community.id)) + +// store.dispatch(connection.actions.setTorConnectionProcess(ConnectionProcessInfo.REGISTERING_USER_CERTIFICATE)) + +// const code = 'bidrmzr3ee6qa2vvrlcnqvvvsk2gmjktcqkunba326parszr44gibwyd' + +// const reducer = combineReducers(reducers) +// await expectSaga(deepLinkSaga, initActions.deepLink(code)) +// .withReducer(reducer) +// .withState(store.getState()) +// .put( +// navigationActions.replaceScreen({ +// screen: ScreenNames.UsernameRegistrationScreen, +// params: { fetching: true }, +// }) +// ) +// .not.put( +// communities.actions.createNetwork({ +// ownership: CommunityOwnership.User, +// registrar: code, +// }) +// ) +// .run() +// }) +// }) diff --git a/packages/mobile/src/store/init/deepLink/deepLink.saga.ts b/packages/mobile/src/store/init/deepLink/deepLink.saga.ts index a999979007..ca965b064a 100644 --- a/packages/mobile/src/store/init/deepLink/deepLink.saga.ts +++ b/packages/mobile/src/store/init/deepLink/deepLink.saga.ts @@ -9,6 +9,7 @@ import { appImages } from '../../../assets' import { replaceScreen } from '../../../RootNavigation' import { UsernameRegistrationRouteProps } from '../../../route.params' import { CommunityOwnership, ConnectionProcessInfo, CreateNetworkPayload } from '@quiet/types' +import { retrieveInvitationCode } from '@quiet/common' export function* deepLinkSaga(action: PayloadAction['payload']>): Generator { const code = action.payload @@ -48,14 +49,14 @@ export function* deepLinkSaga(action: PayloadAction { export const invitationUrl = createSelector(currentCommunity, community => { const peerList = community?.peerList - if (!peerList || peerList?.length === 0) return + if (!peerList || peerList?.length === 0) return '' const initialPeers = peerList.slice(0, 4) console.log('invitationUrl INITIAL PEERS', initialPeers) - if (!community?.registrarUrl) return '' - let registrarUrl = '' - try { - const url = new URL(community.registrarUrl) - registrarUrl = url.hostname.split('.')[0] - } catch (e) { - registrarUrl = community.registrarUrl - } + // if (!community?.registrarUrl) return '' + // let registrarUrl = '' + // try { + // const url = new URL(community.registrarUrl) + // registrarUrl = url.hostname.split('.')[0] + // } catch (e) { + // registrarUrl = community.registrarUrl + // } return invitationShareUrl(initialPeers) }) diff --git a/tsconfig.build.json b/tsconfig.build.json index cbe3d6e834..533d6c77ee 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -1,7 +1,7 @@ { "compilerOptions": { "baseUrl": ".", - "target": "es6", + "target": "ES2020", "module": "commonjs", "declaration": true, "esModuleInterop": true, @@ -13,7 +13,8 @@ "./node_modules/@types" ], "lib": [ - "es6", + "ES2020", + "DOM.Iterable", "dom" ] } diff --git a/tsconfig.json b/tsconfig.json index 5116cd30c1..8eafb17145 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es6", + "target": "ES2020", "baseUrl": ".", "paths": { "@quiet/identity": ["packages/identity/src/index.ts"], @@ -18,8 +18,9 @@ "experimentalDecorators": true, "moduleResolution": "node", "lib": [ - "es6", - "dom" + "dom", + "DOM.Iterable", + "ES2020" ] } } From b3e59c56a53785103d7888d7f3d7960f1aa3b7b3 Mon Sep 17 00:00:00 2001 From: Emi Date: Mon, 21 Aug 2023 19:29:33 +0200 Subject: [PATCH 04/61] Do not launch registrar, do not wait for certificates to join community --- packages/backend/src/nest/common/utils.ts | 9 +- .../connections-manager.service.tor.spec.ts | 10 +- .../connections-manager.service.ts | 98 ++++++++++++------- .../backend/src/nest/libp2p/libp2p.service.ts | 3 +- .../backend/src/nest/libp2p/libp2p.types.ts | 6 +- .../nest/registration/registration.service.ts | 5 +- .../backend/src/nest/socket/socket.service.ts | 10 +- .../src/nest/tor/tor-control.service.ts | 13 ++- .../websocketOverTor.tor.spec.ts | 9 +- packages/common/src/index.ts | 1 + packages/common/src/invitationCode.test.ts | 11 ++- packages/common/src/invitationCode.ts | 23 ++++- packages/common/src/libp2p.ts | 8 ++ .../JoinCommunity/JoinCommunity.tsx | 3 +- .../PerformCommunityActionComponent.tsx | 11 ++- .../CreateUsername/CreateUsername.tsx | 6 +- .../components/LoadingPanel/LoadingPanel.tsx | 2 +- .../renderer/forms/fields/communityFields.ts | 8 +- packages/desktop/src/renderer/index.tsx | 5 - .../desktop/src/renderer/sagas/index.saga.ts | 2 +- .../invitation/handleInvitationCode.saga.ts | 4 +- .../src/rtl-tests/customProtocol.test.tsx | 7 +- .../sagas/appConnection/connection.slice.ts | 1 + .../communities/communities.master.saga.ts | 2 +- .../communities/communities.selectors.ts | 5 + .../sagas/communities/communities.slice.ts | 15 +-- .../createNetwork/createNetwork.saga.ts | 10 +- .../launchCommunity/launchCommunity.saga.ts | 22 +++-- .../responseCreateNetwork.saga.test.ts | 2 +- .../responseCreateNetwork.saga.ts | 1 - .../startConnection/startConnection.saga.ts | 44 +++++---- packages/types/src/community.ts | 2 +- 32 files changed, 219 insertions(+), 139 deletions(-) create mode 100644 packages/common/src/libp2p.ts diff --git a/packages/backend/src/nest/common/utils.ts b/packages/backend/src/nest/common/utils.ts index 22e843a62c..f6e2901627 100644 --- a/packages/backend/src/nest/common/utils.ts +++ b/packages/backend/src/nest/common/utils.ts @@ -12,6 +12,7 @@ import { TestConfig } from '../const' import logger from './logger' import { createCertificatesTestHelper } from './client-server' import { Libp2pNodeParams } from '../libp2p/libp2p.types' +import { createLibp2pAddress, createLibp2pListenAddress } from '@quiet/common' const log = logger('test') export interface Ports { @@ -143,14 +144,6 @@ export const torDirForPlatform = (basePath?: string): string => { return torPath } -export const createLibp2pAddress = (address: string, peerId: string) => { - return `/dns4/${address}/tcp/80/ws/p2p/${peerId}` -} - -export const createLibp2pListenAddress = (address: string) => { - return `/dns4/${address}/tcp/80/ws` -} - export const getUsersAddresses = async (users: User[]): Promise => { const peers = users.map(async (userData: User) => { return createLibp2pAddress(userData.onionAddress, userData.peerId) diff --git a/packages/backend/src/nest/connections-manager/connections-manager.service.tor.spec.ts b/packages/backend/src/nest/connections-manager/connections-manager.service.tor.spec.ts index e88e7151b2..8c759f8571 100644 --- a/packages/backend/src/nest/connections-manager/connections-manager.service.tor.spec.ts +++ b/packages/backend/src/nest/connections-manager/connections-manager.service.tor.spec.ts @@ -4,14 +4,7 @@ import crypto from 'crypto' import { CustomEvent } from '@libp2p/interfaces/events' import { jest, beforeEach, describe, it, expect, afterEach } from '@jest/globals' import { communities, getFactory, identity, prepareStore, Store } from '@quiet/state-manager' -import { - createLibp2pAddress, - createPeerId, - createTmpDir, - libp2pInstanceParams, - removeFilesFromDir, - tmpQuietDirPath, -} from '../common/utils' +import { createPeerId, createTmpDir, libp2pInstanceParams, removeFilesFromDir, tmpQuietDirPath } from '../common/utils' import { NetworkStats, type Community, type Identity, type InitCommunityPayload } from '@quiet/types' import { LazyModuleLoader } from '@nestjs/core' @@ -37,6 +30,7 @@ import { DateTime } from 'luxon' import waitForExpect from 'wait-for-expect' import { Libp2pEvents } from '../libp2p/libp2p.types' import { sleep } from '../common/sleep' +import { createLibp2pAddress } from '@quiet/common' jest.setTimeout(100_000) 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 1f9b88290c..f7b280470b 100644 --- a/packages/backend/src/nest/connections-manager/connections-manager.service.ts +++ b/packages/backend/src/nest/connections-manager/connections-manager.service.ts @@ -174,9 +174,6 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI if (community) { await this.launchCommunity(community) } - if (registrarData) { - await this.registrationService.launchRegistrar(registrarData) - } } public async closeAllServices(options: { saveTor: boolean } = { saveTor: false }) { @@ -282,12 +279,14 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI } public async createCommunity(payload: InitCommunityPayload) { + console.log('ConnectionsManager.createCommunity peers:', payload.peers) await this.launchCommunity(payload) this.logger(`Created and launched community ${payload.id}`) this.serverIoProvider.io.emit(SocketActionTypes.NEW_COMMUNITY, { id: payload.id }) } public async launchCommunity(payload: InitCommunityPayload) { + console.log('ConnectionsManager.launchCommunity peers:', payload.peers) this.communityState = ServiceState.LAUNCHING const communityData: InitCommunityPayload = await this.localDbService.get(LocalDBKeys.COMMUNITY) if (!communityData) { @@ -336,6 +335,7 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI const _peerId = await peerIdFromKeys(restoredRsa.marshalPubKey(), restoredRsa.marshalPrivKey()) let peers = payload.peers + console.log('LAUNCH COMMUNITY PAYLOAD PEERS', peers) if (!peers || peers.length === 0) { peers = [this.libp2pService.createLibp2pAddress(onionAddress, _peerId.toString())] } @@ -344,9 +344,6 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI peerId: _peerId, listenAddresses: [this.libp2pService.createLibp2pListenAddress(onionAddress)], agent: this.socksProxyAgent, - cert: payload.certs.certificate, - key: payload.certs.key, - ca: payload.certs.CA, localAddress: this.libp2pService.createLibp2pAddress(onionAddress, _peerId.toString()), targetPort: this.ports.libp2pHiddenService, peers, @@ -387,6 +384,7 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI }) this.registrationService.on(SocketActionTypes.CONNECTION_PROCESS_INFO, data => { + console.log('CONNECTION_PROCESS_INFO', data) this.serverIoProvider.io.emit(SocketActionTypes.CONNECTION_PROCESS_INFO, data) }) } @@ -395,24 +393,27 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI this.registrarState = payload }) this.registrationService.on(SocketActionTypes.SAVED_OWNER_CERTIFICATE, payload => { + console.log('SAVED_OWNER_CERTIFICATE', payload) this.serverIoProvider.io.emit(SocketActionTypes.SAVED_OWNER_CERTIFICATE, payload) }) - this.registrationService.on(RegistrationEvents.SPAWN_HS_FOR_REGISTRAR, async payload => { - await this.tor.spawnHiddenService({ - targetPort: payload.port, - privKey: payload.privateKey, - virtPort: payload.targetPort, - }) - }) + // this.registrationService.on(RegistrationEvents.SPAWN_HS_FOR_REGISTRAR, async payload => { + // await this.tor.spawnHiddenService({ + // targetPort: payload.port, + // privKey: payload.privateKey, + // virtPort: payload.targetPort, + // }) + // }) this.registrationService.on(RegistrationEvents.ERROR, payload => { emitError(this.serverIoProvider.io, payload) }) - this.registrationService.on(SocketActionTypes.SEND_USER_CERTIFICATE, payload => { - this.serverIoProvider.io.emit(SocketActionTypes.SEND_USER_CERTIFICATE, payload) - }) - this.registrationService.on(RegistrationEvents.NEW_USER, async payload => { - await this.storageService?.saveCertificate(payload) - }) + // this.registrationService.on(SocketActionTypes.SEND_USER_CERTIFICATE, payload => { + // console.log('SEND_USER_CERTIFICATE', payload) + // this.serverIoProvider.io.emit(SocketActionTypes.SEND_USER_CERTIFICATE, payload) + // }) + // this.registrationService.on(RegistrationEvents.NEW_USER, async payload => { + // console.log('NEW_USER', payload) + // await this.storageService?.saveCertificate(payload) + // }) } private attachsocketServiceListeners() { // Community @@ -434,9 +435,11 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI } }) this.socketService.on(SocketActionTypes.CREATE_NETWORK, async (args: Community) => { + console.log('SocketActionTypes.CREATE_NETWORK') await this.createNetwork(args) }) this.socketService.on(SocketActionTypes.CREATE_COMMUNITY, async (args: InitCommunityPayload) => { + console.log('SocketActionTypes.CREATE_COMMUNITY') await this.createCommunity(args) }) this.socketService.on(SocketActionTypes.LAUNCH_COMMUNITY, async (args: InitCommunityPayload) => { @@ -446,19 +449,20 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI await this.launchCommunity(args) }) // Registration - this.socketService.on(SocketActionTypes.LAUNCH_REGISTRAR, async (args: LaunchRegistrarPayload) => { - this.logger(`socketService - ${SocketActionTypes.LAUNCH_REGISTRAR}`) - - const communityData = await this.localDbService.get(LocalDBKeys.REGISTRAR) - if (!communityData) { - await this.localDbService.put(LocalDBKeys.REGISTRAR, args) - } - console.log('this.registrarState', this.registrarState) - if ([ServiceState.LAUNCHING, ServiceState.LAUNCHED].includes(this.registrarState)) return - this.registrarState = ServiceState.LAUNCHING - await this.registrationService.launchRegistrar(args) - }) + // this.socketService.on(SocketActionTypes.LAUNCH_REGISTRAR, async (args: LaunchRegistrarPayload) => { + // this.logger(`socketService - ${SocketActionTypes.LAUNCH_REGISTRAR}`) + + // const communityData = await this.localDbService.get(LocalDBKeys.REGISTRAR) + // if (!communityData) { + // await this.localDbService.put(LocalDBKeys.REGISTRAR, args) + // } + // console.log('this.registrarState', this.registrarState) + // if ([ServiceState.LAUNCHING, ServiceState.LAUNCHED].includes(this.registrarState)) return + // this.registrarState = ServiceState.LAUNCHING + // await this.registrationService.launchRegistrar(args) + // }) this.socketService.on(SocketActionTypes.SAVED_OWNER_CERTIFICATE, async (args: SaveOwnerCertificatePayload) => { + console.log('SAVED_OWNER_CERTIFICATE') const saveCertificatePayload: SaveCertificatePayload = { certificate: args.certificate, rootPermsData: args.permsData, @@ -466,17 +470,35 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI await this.storageService?.saveCertificate(saveCertificatePayload) }) this.socketService.on(SocketActionTypes.REGISTER_USER_CERTIFICATE, async (args: RegisterUserCertificatePayload) => { + console.log('!!!! REGISTER_USER_CERTIFICATE') // if (!this.socksProxyAgent) { // this.createAgent() // } - await this.registrationService.sendCertificateRegistrationRequest( - args.serviceAddress, - args.userCsr, - args.communityId, - 120_000, - this.socksProxyAgent - ) + // await this.registrationService.sendCertificateRegistrationRequest( + // args.serviceAddress, + // args.userCsr, + // args.communityId, + // 120_000, + // this.socksProxyAgent + // ) + // const response = await this.registrationService.registerUser(args.userCsr) + this.emit(SocketActionTypes.CONNECTION_PROCESS_INFO, ConnectionProcessInfo.CONNECTING_TO_COMMUNITY) + console.log('emitting SocketActionTypes.SEND_USER_CERTIFICATE') + this.serverIoProvider.io.emit(SocketActionTypes.SEND_USER_CERTIFICATE, { + // TEMPORARY + communityId: args.communityId, + payload: { + peers: [], + }, + }) + // this.emit(SocketActionTypes.SEND_USER_CERTIFICATE, { + // // TEMPORARY + // communityId: args.communityId, + // payload: { + // peers: [], + // }, + // }) }) this.socketService.on( SocketActionTypes.REGISTER_OWNER_CERTIFICATE, diff --git a/packages/backend/src/nest/libp2p/libp2p.service.ts b/packages/backend/src/nest/libp2p/libp2p.service.ts index fa98820843..643ec011d5 100644 --- a/packages/backend/src/nest/libp2p/libp2p.service.ts +++ b/packages/backend/src/nest/libp2p/libp2p.service.ts @@ -15,10 +15,10 @@ import { multiaddr } from '@multiformats/multiaddr' import { ConnectionProcessInfo, PeerId, SocketActionTypes } from '@quiet/types' import { SERVER_IO_PROVIDER, SOCKS_PROXY_AGENT } from '../const' import { ServerIoProviderTypes } from '../types' -import { createLibp2pListenAddress, createLibp2pAddress } from './libp2p.utils' import Logger from '../common/logger' import { webSockets } from '../websocketOverTor' import { all } from '../websocketOverTor/filters' +import { createLibp2pAddress, createLibp2pListenAddress } from '@quiet/common' @Injectable() export class Libp2pService extends EventEmitter { @@ -45,6 +45,7 @@ export class Libp2pService extends EventEmitter { } public async createInstance(params: Libp2pNodeParams): Promise { + console.log('Libp2p.createInstance::: peers:::', params.peers) if (this.libp2pInstance) { return this.libp2pInstance } diff --git a/packages/backend/src/nest/libp2p/libp2p.types.ts b/packages/backend/src/nest/libp2p/libp2p.types.ts index a912015c23..b8bd0f95a7 100644 --- a/packages/backend/src/nest/libp2p/libp2p.types.ts +++ b/packages/backend/src/nest/libp2p/libp2p.types.ts @@ -11,9 +11,9 @@ export interface Libp2pNodeParams { peerId: any listenAddresses: string[] agent: Agent - cert: string - key: string - ca: string[] + cert?: string + key?: string + ca?: string[] localAddress: string targetPort: number peers: string[] diff --git a/packages/backend/src/nest/registration/registration.service.ts b/packages/backend/src/nest/registration/registration.service.ts index c3d8a9b172..4b294c2ae5 100644 --- a/packages/backend/src/nest/registration/registration.service.ts +++ b/packages/backend/src/nest/registration/registration.service.ts @@ -30,7 +30,7 @@ export class RegistrationService extends EventEmitter implements OnModuleInit { private _server: Server private _port: number public registrationService: any - public certificates: string[] + public certificates: string[] = [] private _permsData: PermsData private _ownerCertificate: string @@ -40,6 +40,7 @@ export class RegistrationService extends EventEmitter implements OnModuleInit { onModuleInit() { this.on(RegistrationEvents.SET_CERTIFICATES, certs => { + console.log('SET CERTIFICATES', certs) this.setCertificates(certs) }) this.setRouting() @@ -123,7 +124,7 @@ export class RegistrationService extends EventEmitter implements OnModuleInit { this.emit(response.eventType, response.data) } - private async registerUser(csr: string): Promise<{ status: number; body: any }> { + public async registerUser(csr: string): Promise<{ status: number; body: any }> { const result = await registerUser(csr, this._permsData, this.certificates, this._ownerCertificate) if (result?.status === 200) { this.emit(RegistrationEvents.NEW_USER, { certificate: result.body.certificate, rootPermsData: this._permsData }) diff --git a/packages/backend/src/nest/socket/socket.service.ts b/packages/backend/src/nest/socket/socket.service.ts index 4936e4a32a..202a118817 100644 --- a/packages/backend/src/nest/socket/socket.service.ts +++ b/packages/backend/src/nest/socket/socket.service.ts @@ -101,7 +101,7 @@ export class SocketService extends EventEmitter implements OnModuleInit { }) socket.on(SocketActionTypes.REGISTER_USER_CERTIFICATE, async (payload: RegisterUserCertificatePayload) => { - this.logger(`Registering user certificate (${payload.communityId}) on ${payload.serviceAddress}`) + this.logger(`Registering user CSR (${payload.communityId}) on ${payload.serviceAddress}`) this.emit(SocketActionTypes.REGISTER_USER_CERTIFICATE, payload) await new Promise(resolve => setTimeout(() => resolve(), 2000)) this.emit(SocketActionTypes.CONNECTION_PROCESS_INFO, ConnectionProcessInfo.REGISTERING_USER_CERTIFICATE) @@ -124,10 +124,10 @@ export class SocketService extends EventEmitter implements OnModuleInit { this.emit(SocketActionTypes.LAUNCH_COMMUNITY, payload) this.emit(SocketActionTypes.CONNECTION_PROCESS_INFO, ConnectionProcessInfo.LAUNCHING_COMMUNITY) }) - socket.on(SocketActionTypes.LAUNCH_REGISTRAR, async (payload: LaunchRegistrarPayload) => { - this.logger(`Launching registrar for community ${payload.id}, user ${payload.peerId}`) - this.emit(SocketActionTypes.LAUNCH_REGISTRAR, payload) - }) + // socket.on(SocketActionTypes.LAUNCH_REGISTRAR, async (payload: LaunchRegistrarPayload) => { + // this.logger(`Launching registrar for community ${payload.id}, user ${payload.peerId}`) + // this.emit(SocketActionTypes.LAUNCH_REGISTRAR, payload) + // }) socket.on(SocketActionTypes.CREATE_NETWORK, async (community: Community) => { this.logger(`Creating network for community ${community.id}`) this.emit(SocketActionTypes.CREATE_NETWORK, community) diff --git a/packages/backend/src/nest/tor/tor-control.service.ts b/packages/backend/src/nest/tor/tor-control.service.ts index f8dcec17b2..2f7087f7dc 100644 --- a/packages/backend/src/nest/tor/tor-control.service.ts +++ b/packages/backend/src/nest/tor/tor-control.service.ts @@ -52,7 +52,7 @@ export class TorControl implements OnModuleInit { }) } - private async disconnect() { + private disconnect() { try { this.connection?.end() } catch (e) { @@ -63,12 +63,19 @@ export class TorControl implements OnModuleInit { // eslint-disable-next-line @typescript-eslint/ban-types private async _sendCommand(command: string, resolve: Function, reject: Function) { - await this.connect() + try { + await this.connect() + } catch (e) { + console.error(`ERROR for ${command}`, e) + this.disconnect() + await this.connect() + } + const connectionTimeout = setTimeout(() => { reject('TOR: Send command timeout') }, 5000) this.connection?.on('data', async data => { - await this.disconnect() + this.disconnect() const dataArray = data.toString().split(/\r?\n/) if (dataArray[0].startsWith('250')) { resolve({ code: 250, messages: dataArray }) diff --git a/packages/backend/src/nest/websocketOverTor/websocketOverTor.tor.spec.ts b/packages/backend/src/nest/websocketOverTor/websocketOverTor.tor.spec.ts index 0ac6482471..fa18cfb8af 100644 --- a/packages/backend/src/nest/websocketOverTor/websocketOverTor.tor.spec.ts +++ b/packages/backend/src/nest/websocketOverTor/websocketOverTor.tor.spec.ts @@ -4,13 +4,7 @@ import { multiaddr } from '@multiformats/multiaddr' import getPort from 'get-port' import { type DirResult } from 'tmp' import { jest, describe, it, expect, afterEach, beforeAll, afterAll } from '@jest/globals' -import { - createLibp2pAddress, - torBinForPlatform, - torDirForPlatform, - createTmpDir, - tmpQuietDirPath, -} from '../common/utils' +import { torBinForPlatform, torDirForPlatform, createTmpDir, tmpQuietDirPath } from '../common/utils' import { type CreateListenerOptions } from '@libp2p/interface-transport' import { createServer } from 'it-ws/server' import { createCertificatesTestHelper } from '../common/client-server' @@ -22,6 +16,7 @@ import { Tor } from '../tor/tor.service' import crypto from 'crypto' import { TorControl } from '../tor/tor-control.service' import { TorControlAuthType } from '../tor/tor.types' +import { createLibp2pAddress } from '@quiet/common' jest.setTimeout(120000) describe('websocketOverTor', () => { diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 914f45c769..67ce8a7b5b 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -7,3 +7,4 @@ export * from './sortPeers' export * from './channelAddress' export * from './naming' export * from './fileData' +export * from './libp2p' diff --git a/packages/common/src/invitationCode.test.ts b/packages/common/src/invitationCode.test.ts index 1f49410df5..6059b9c6be 100644 --- a/packages/common/src/invitationCode.test.ts +++ b/packages/common/src/invitationCode.test.ts @@ -1,4 +1,4 @@ -import { argvInvitationCode, invitationDeepUrl, invitationShareUrl } from './invitationCode' +import { argvInvitationCode, invitationDeepUrl, invitationShareUrl, pairsToInvitationShareUrl } from './invitationCode' import { Site } from './static' describe('Invitation code helper', () => { @@ -27,6 +27,15 @@ describe('Invitation code helper', () => { ).toEqual('quiet://?peerID1=address1&peerID2=address2') }) + it('creates invitation share url based on invitation pairs', () => { + const pairs = [ + { peerId: 'peerID1', address: 'address1' }, + { peerId: 'peerID2', address: 'address2' }, + ] + const expected = `https://${Site.DOMAIN}/${Site.JOIN_PAGE}#peerID1=address1&peerID2=address2` + expect(pairsToInvitationShareUrl(pairs)).toEqual(expected) + }) + // it('builds proper invitation share url', () => { // expect(invitationShareUrl('validCode')).toEqual(`https://${Site.DOMAIN}/${Site.JOIN_PAGE}#validCode`) // }) diff --git a/packages/common/src/invitationCode.ts b/packages/common/src/invitationCode.ts index 8ed1a54b2a..389e511558 100644 --- a/packages/common/src/invitationCode.ts +++ b/packages/common/src/invitationCode.ts @@ -1,6 +1,7 @@ import { InvitationPair } from '@quiet/types' import { InvitationParams, Site } from './static' import { multiaddr } from 'multiaddr' +import { createLibp2pAddress } from './libp2p' // export const retrieveInvitationCode = (url: string): string => { // /** @@ -52,7 +53,11 @@ export const retrieveInvitationCode = (url: string): InvitationPair[] => { } export const invitationShareUrl = (peers: string[] = []): string => { - // Valid format: https://tryquiet.org/join/#=&= + /** + * @arg {string[]} peers - List of peer's p2p addresses + * @returns {string} - Complete shareable invitation link, e.g. https://tryquiet.org/join/#=&= + */ + // Valid format: const pairs = [] for (const peerAddress of peers) { let addr @@ -78,6 +83,22 @@ export const invitationShareUrl = (peers: string[] = []): string => { return url.href } +export const pairsToP2pAddresses = (pairs: InvitationPair[]): string[] => { + const addresses: string[] = [] + for (const pair of pairs) { + addresses.push(createLibp2pAddress(pair.address, pair.peerId)) + } + return addresses +} + +export const pairsToInvitationShareUrl = (pairs: InvitationPair[]) => { + const url = new URL(`https://${Site.DOMAIN}/${Site.JOIN_PAGE}`) + for (const pair of pairs) { + url.searchParams.append(pair.peerId, pair.address) + } + return url.href.replace('?', '#') +} + export const invitationDeepUrl = (pairs: InvitationPair[] = []): string => { const url = new URL('quiet://') for (const pair of pairs) { diff --git a/packages/common/src/libp2p.ts b/packages/common/src/libp2p.ts new file mode 100644 index 0000000000..f4b84d6f8c --- /dev/null +++ b/packages/common/src/libp2p.ts @@ -0,0 +1,8 @@ +export const createLibp2pAddress = (address: string, peerId: string) => { + if (!address.endsWith('.onion')) address += '.onion' + return `/dns4/${address}/tcp/80/ws/p2p/${peerId}` +} + +export const createLibp2pListenAddress = (address: string) => { + return `/dns4/${address}/tcp/80/ws` +} diff --git a/packages/desktop/src/renderer/components/CreateJoinCommunity/JoinCommunity/JoinCommunity.tsx b/packages/desktop/src/renderer/components/CreateJoinCommunity/JoinCommunity/JoinCommunity.tsx index 9ed4991099..edafa145ce 100644 --- a/packages/desktop/src/renderer/components/CreateJoinCommunity/JoinCommunity/JoinCommunity.tsx +++ b/packages/desktop/src/renderer/components/CreateJoinCommunity/JoinCommunity/JoinCommunity.tsx @@ -15,7 +15,7 @@ const JoinCommunity = () => { const currentCommunity = useSelector(communities.selectors.currentCommunity) const currentIdentity = useSelector(identity.selectors.currentIdentity) - const invitationCode = useSelector(communities.selectors.invitationCode) + const invitationCode = useSelector(communities.selectors.invitationCodes) const joinCommunityModal = useModal(ModalName.joinCommunityModal) const createCommunityModal = useModal(ModalName.createCommunityModal) @@ -46,6 +46,7 @@ const JoinCommunity = () => { ownership: CommunityOwnership.User, peers: address, } + console.log('handleCommunityAction createNetwork') dispatch(communities.actions.createNetwork(payload)) } diff --git a/packages/desktop/src/renderer/components/CreateJoinCommunity/PerformCommunityActionComponent.tsx b/packages/desktop/src/renderer/components/CreateJoinCommunity/PerformCommunityActionComponent.tsx index 1b7f20dc23..fb279728dc 100644 --- a/packages/desktop/src/renderer/components/CreateJoinCommunity/PerformCommunityActionComponent.tsx +++ b/packages/desktop/src/renderer/components/CreateJoinCommunity/PerformCommunityActionComponent.tsx @@ -12,7 +12,7 @@ import { LoadingButton } from '../ui/LoadingButton/LoadingButton' import { CreateCommunityDictionary, JoinCommunityDictionary } from '../CreateJoinCommunity/community.dictionary' -import { CommunityOwnership } from '@quiet/types' +import { CommunityOwnership, InvitationPair } from '@quiet/types' import { Controller, useForm } from 'react-hook-form' import { TextInput } from '../../forms/components/textInput' @@ -20,7 +20,7 @@ import { InviteLinkErrors } from '../../forms/fieldsErrors' import { IconButton, InputAdornment } from '@mui/material' import VisibilityOff from '@mui/icons-material/VisibilityOff' import Visibility from '@mui/icons-material/Visibility' -import { ONION_ADDRESS_REGEX, parseName } from '@quiet/common' +import { ONION_ADDRESS_REGEX, pairsToInvitationShareUrl, parseName } from '@quiet/common' import { getInvitationCodes } from '@quiet/state-manager' const PREFIX = 'PerformCommunityActionComponent' @@ -137,7 +137,7 @@ export interface PerformCommunityActionProps { hasReceivedResponse: boolean revealInputValue?: boolean handleClickInputReveal?: () => void - invitationCode?: string + invitationCode?: InvitationPair[] } export const PerformCommunityActionComponent: React.FC = ({ @@ -192,6 +192,7 @@ export const PerformCommunityActionComponent: React.FC { - if (communityOwnership === CommunityOwnership.User && invitationCode) { + if (communityOwnership === CommunityOwnership.User && invitationCode?.length) { setFormSent(true) - setValue('name', invitationCode) + setValue('name', pairsToInvitationShareUrl(invitationCode)) } }, [communityOwnership, invitationCode]) diff --git a/packages/desktop/src/renderer/components/CreateUsername/CreateUsername.tsx b/packages/desktop/src/renderer/components/CreateUsername/CreateUsername.tsx index c6b439bc05..79c59de8be 100644 --- a/packages/desktop/src/renderer/components/CreateUsername/CreateUsername.tsx +++ b/packages/desktop/src/renderer/components/CreateUsername/CreateUsername.tsx @@ -18,10 +18,12 @@ const CreateUsername = () => { const error = useSelector(errors.selectors.registrarErrors) useEffect(() => { - if (currentCommunity && !currentIdentity?.userCertificate && !createUsernameModal.open) { + if (currentCommunity && !currentIdentity?.userCsr && !createUsernameModal.open) { + console.log('createUsernameModal.handleOpen()') createUsernameModal.handleOpen() } - if (currentIdentity?.userCertificate && createUsernameModal.open) { + if (currentIdentity?.userCsr && createUsernameModal.open) { + console.log('createUsernameModal.handleClose()') createUsernameModal.handleClose() } }, [currentIdentity, currentCommunity]) diff --git a/packages/desktop/src/renderer/components/LoadingPanel/LoadingPanel.tsx b/packages/desktop/src/renderer/components/LoadingPanel/LoadingPanel.tsx index 566eebd229..77e1c6b270 100644 --- a/packages/desktop/src/renderer/components/LoadingPanel/LoadingPanel.tsx +++ b/packages/desktop/src/renderer/components/LoadingPanel/LoadingPanel.tsx @@ -24,7 +24,7 @@ const LoadingPanel = () => { const community = useSelector(communities.selectors.currentCommunity) const owner = Boolean(community?.CA) - const currentIdentity = useSelector(identity.selectors.currentIdentity) + // const currentIdentity = useSelector(identity.selectors.currentIdentity) const usersData = Object.keys(useSelector(users.selectors.certificates)) const isOnlyOneUser = usersData.length === 1 diff --git a/packages/desktop/src/renderer/forms/fields/communityFields.ts b/packages/desktop/src/renderer/forms/fields/communityFields.ts index bda6aa21a4..9a1f8e31fb 100644 --- a/packages/desktop/src/renderer/forms/fields/communityFields.ts +++ b/packages/desktop/src/renderer/forms/fields/communityFields.ts @@ -36,10 +36,10 @@ export const inviteLinkField = (name = 'name'): FieldData => { }, validation: { required: FieldErrors.Required, - pattern: { - value: /^[a-z0-9:/.#?= ]+$/g, - message: InviteLinkErrors.InvalidCode, - }, + // pattern: { + // // value: /^[a-z0-9:/.#?= ]+$/g, + // message: InviteLinkErrors.InvalidCode, + // }, }, } } diff --git a/packages/desktop/src/renderer/index.tsx b/packages/desktop/src/renderer/index.tsx index 02942c0c46..ef2e8f1e08 100644 --- a/packages/desktop/src/renderer/index.tsx +++ b/packages/desktop/src/renderer/index.tsx @@ -25,11 +25,6 @@ ipcRenderer.on('invitation', (_event, invitation) => { store.dispatch(communities.actions.handleInvitationCodes(invitation.codes)) }) -// ipcRenderer.on('invitationMA', (_event, invitation) => { -// console.log('invitation', invitation, 'dispatching action') -// store.dispatch(communities.actions.handleInvitationCodes(invitation.codes)) -// }) - 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/index.saga.ts b/packages/desktop/src/renderer/sagas/index.saga.ts index 35d0dbc985..e41f8b6faa 100644 --- a/packages/desktop/src/renderer/sagas/index.saga.ts +++ b/packages/desktop/src/renderer/sagas/index.saga.ts @@ -7,7 +7,7 @@ import { socketActions } from './socket/socket.slice' export default function* root(): Generator { const dataPort = new URLSearchParams(window.location.search).get('dataPort') || '' yield all([ - takeEvery(communities.actions.handleInvitationCode.type, handleInvitationCodeSaga), + takeEvery(communities.actions.handleInvitationCodes.type, handleInvitationCodeSaga), startConnectionSaga( socketActions.startConnection({ dataPort: parseInt(dataPort), diff --git a/packages/desktop/src/renderer/sagas/invitation/handleInvitationCode.saga.ts b/packages/desktop/src/renderer/sagas/invitation/handleInvitationCode.saga.ts index d2bd74f864..2f542068a2 100644 --- a/packages/desktop/src/renderer/sagas/invitation/handleInvitationCode.saga.ts +++ b/packages/desktop/src/renderer/sagas/invitation/handleInvitationCode.saga.ts @@ -39,11 +39,11 @@ export function* handleInvitationCodeSaga( peers: action.payload, } yield* put(communities.actions.createNetwork(payload)) - return + // return // } // TODO: handle invalid code - // yield* put(communities.actions.clearInvitationCode()) + // yield* put(communities.actions.clearInvitationCodes()) // yield* put( // modalsActions.openModal({ diff --git a/packages/desktop/src/rtl-tests/customProtocol.test.tsx b/packages/desktop/src/rtl-tests/customProtocol.test.tsx index 7e85eef1f7..2837451ef5 100644 --- a/packages/desktop/src/rtl-tests/customProtocol.test.tsx +++ b/packages/desktop/src/rtl-tests/customProtocol.test.tsx @@ -10,6 +10,7 @@ import { modalsActions } from '../renderer/sagas/modals/modals.slice' import { ModalName } from '../renderer/sagas/modals/modals.types' import JoinCommunity from '../renderer/components/CreateJoinCommunity/JoinCommunity/JoinCommunity' import CreateUsername from '../renderer/components/CreateUsername/CreateUsername' +import { InvitationPair } from '@quiet/types' jest.setTimeout(20_000) @@ -64,9 +65,11 @@ describe('Opening app through custom protocol', () => { socket // Fork state manager's sagas ) - const invitationCode = 'bidrmzr3ee6qa2vvrlcnqvvvsk2gmjktcqkunba326parszr44gibwyd' + const invitationCodes: InvitationPair[] = [ + { peerId: 'abcdef', address: 'bidrmzr3ee6qa2vvrlcnqvvvsk2gmjktcqkunba326parszr44gibwyd' }, + ] - store.dispatch(communities.actions.handleInvitationCode(invitationCode)) + store.dispatch(communities.actions.handleInvitationCodes(invitationCodes)) store.dispatch(modalsActions.openModal({ name: ModalName.joinCommunityModal })) diff --git a/packages/state-manager/src/sagas/appConnection/connection.slice.ts b/packages/state-manager/src/sagas/appConnection/connection.slice.ts index 8b5cc4e190..df6306b025 100644 --- a/packages/state-manager/src/sagas/appConnection/connection.slice.ts +++ b/packages/state-manager/src/sagas/appConnection/connection.slice.ts @@ -53,6 +53,7 @@ export const connectionSlice = createSlice({ }, setTorConnectionProcess: (state, action: PayloadAction) => { const info = action.payload + console.log('----> setTorConnectionProcess', info) switch (info) { case ConnectionProcessInfo.CONNECTING_TO_COMMUNITY: state.torConnectionProcess = { number: 20, text: info } diff --git a/packages/state-manager/src/sagas/communities/communities.master.saga.ts b/packages/state-manager/src/sagas/communities/communities.master.saga.ts index d0b855879f..ff986aed1d 100644 --- a/packages/state-manager/src/sagas/communities/communities.master.saga.ts +++ b/packages/state-manager/src/sagas/communities/communities.master.saga.ts @@ -15,6 +15,6 @@ export function* communitiesMasterSaga(socket: Socket): Generator { takeEvery(communitiesActions.updateCommunity.type, updateCommunitySaga), takeEvery(connectionActions.torBootstrapped.type, initCommunities), takeEvery(communitiesActions.launchCommunity.type, launchCommunitySaga, socket), - takeEvery(communitiesActions.launchRegistrar.type, launchRegistrarSaga, socket), + // takeEvery(communitiesActions.launchRegistrar.type, launchRegistrarSaga, socket), ]) } diff --git a/packages/state-manager/src/sagas/communities/communities.selectors.ts b/packages/state-manager/src/sagas/communities/communities.selectors.ts index f8651b258c..9857c2ce09 100644 --- a/packages/state-manager/src/sagas/communities/communities.selectors.ts +++ b/packages/state-manager/src/sagas/communities/communities.selectors.ts @@ -57,6 +57,10 @@ export const invitationCode = createSelector(communitiesSlice, reducerState => { return reducerState.invitationCode }) +export const invitationCodes = createSelector(communitiesSlice, reducerState => { + return reducerState.invitationCodes +}) + export const invitationUrl = createSelector(currentCommunity, community => { const peerList = community?.peerList if (!peerList || peerList?.length === 0) return '' @@ -114,6 +118,7 @@ export const communitiesSelectors = { currentCommunityId, registrarUrl, registrationAttempts, + invitationCodes, invitationCode, invitationUrl, ownerNickname, diff --git a/packages/state-manager/src/sagas/communities/communities.slice.ts b/packages/state-manager/src/sagas/communities/communities.slice.ts index af67c8b8ab..4d976e9e23 100644 --- a/packages/state-manager/src/sagas/communities/communities.slice.ts +++ b/packages/state-manager/src/sagas/communities/communities.slice.ts @@ -91,15 +91,18 @@ export const communitiesSlice = createSlice({ }, }) }, - handleInvitationCode: (state, action: PayloadAction) => { - state.invitationCode = action.payload - }, - clearInvitationCode: state => { - state.invitationCode = '' - }, + // handleInvitationCode: (state, action: PayloadAction) => { + // state.invitationCode = action.payload + // }, + // clearInvitationCode: state => { + // state.invitationCode = '' + // }, handleInvitationCodes: (state, action: PayloadAction) => { state.invitationCodes = action.payload }, + setInvitationCodes: (state, action: PayloadAction) => { + state.invitationCodes = action.payload + }, clearInvitationCodes: state => { state.invitationCodes = [] }, diff --git a/packages/state-manager/src/sagas/communities/createNetwork/createNetwork.saga.ts b/packages/state-manager/src/sagas/communities/createNetwork/createNetwork.saga.ts index 1fa5b13771..66ca0dc57d 100644 --- a/packages/state-manager/src/sagas/communities/createNetwork/createNetwork.saga.ts +++ b/packages/state-manager/src/sagas/communities/createNetwork/createNetwork.saga.ts @@ -1,10 +1,12 @@ import { PayloadAction } from '@reduxjs/toolkit' -import { call, put } from 'typed-redux-saga' +import { call, put, select } from 'typed-redux-saga' import { Time } from 'pkijs' import { generateId } from '../../../utils/cryptography/cryptography' import { communitiesActions } from '../communities.slice' import { createRootCA } from '@quiet/identity' import { type Community, CommunityOwnership } from '@quiet/types' +import { communitiesSelectors } from '../communities.selectors' +import { connectionSelectors } from '../../appConnection/connection.selectors' export function* createNetworkSaga( action: PayloadAction['payload']> @@ -39,7 +41,11 @@ export function* createNetworkSaga( rootCa: CA?.rootCertString, } - yield* put(communitiesActions.clearInvitationCode()) + const invitationPeers = action.payload.peers + if (invitationPeers) { + yield* put(communitiesActions.setInvitationCodes(invitationPeers)) + } + yield* put(communitiesActions.addNewCommunity(payload)) yield* put(communitiesActions.setCurrentCommunity(id)) } diff --git a/packages/state-manager/src/sagas/communities/launchCommunity/launchCommunity.saga.ts b/packages/state-manager/src/sagas/communities/launchCommunity/launchCommunity.saga.ts index 4640049c20..3de4f5d09d 100644 --- a/packages/state-manager/src/sagas/communities/launchCommunity/launchCommunity.saga.ts +++ b/packages/state-manager/src/sagas/communities/launchCommunity/launchCommunity.saga.ts @@ -8,6 +8,7 @@ import { connectionActions } from '../../appConnection/connection.slice' import { getCurrentTime } from '../../messages/utils/message.utils' import { connectionSelectors } from '../../appConnection/connection.selectors' import { networkSelectors } from '../../network/network.selectors' +import { pairsToP2pAddresses } from '@quiet/common' import { type InitCommunityPayload, SocketActionTypes } from '@quiet/types' export function* initCommunities(): Generator { @@ -36,22 +37,29 @@ export function* launchCommunitySaga( const community = yield* select(communitiesSelectors.selectById(communityId)) const identity = yield* select(identitySelectors.selectById(communityId)) - if (!identity?.userCertificate || !identity.userCsr?.userKey || !community?.rootCa) { + if (!identity?.userCsr?.userKey) { console.error('Could not launch community, Community or Identity is lacking data') return } - const peerList = yield* select(connectionSelectors.peerList) + const invitationCodes = yield* select(communitiesSelectors.invitationCodes) + let peerList: string[] = [] + if (invitationCodes) { + peerList = pairsToP2pAddresses(invitationCodes) + } else { + peerList = yield* select(connectionSelectors.peerList) + } + console.log('LAUNCH community peers', peerList) const payload: InitCommunityPayload = { id: identity.id, peerId: identity.peerId, hiddenService: identity.hiddenService, - certs: { - certificate: identity.userCertificate, - key: identity.userCsr.userKey, - CA: [community.rootCa], - }, + // certs: { + // certificate: identity.userCertificate, + // key: identity.userCsr.userKey, + // CA: [community.rootCa], + // }, peers: peerList, } diff --git a/packages/state-manager/src/sagas/communities/responseCreateNetwork/responseCreateNetwork.saga.test.ts b/packages/state-manager/src/sagas/communities/responseCreateNetwork/responseCreateNetwork.saga.test.ts index 31f71fa16e..3b068ad007 100644 --- a/packages/state-manager/src/sagas/communities/responseCreateNetwork/responseCreateNetwork.saga.test.ts +++ b/packages/state-manager/src/sagas/communities/responseCreateNetwork/responseCreateNetwork.saga.test.ts @@ -61,7 +61,7 @@ describe('responseCreateNetwork', () => { .withState(store.getState()) .provide([[call.fn(generateDmKeyPair), dmKeys]]) .call(generateDmKeyPair) - .put(communitiesActions.clearInvitationCode()) + .put(communitiesActions.clearInvitationCodes()) .put(communitiesActions.updateCommunityData(community)) .put(identityActions.addNewIdentity(identity)) .run() diff --git a/packages/state-manager/src/sagas/communities/responseCreateNetwork/responseCreateNetwork.saga.ts b/packages/state-manager/src/sagas/communities/responseCreateNetwork/responseCreateNetwork.saga.ts index 224e210418..4cefc0e43a 100644 --- a/packages/state-manager/src/sagas/communities/responseCreateNetwork/responseCreateNetwork.saga.ts +++ b/packages/state-manager/src/sagas/communities/responseCreateNetwork/responseCreateNetwork.saga.ts @@ -22,7 +22,6 @@ export function* responseCreateNetworkSaga(action: PayloadAction | ReturnType | ReturnType + | ReturnType >(emit => { // UPDATE FOR APP socket.on(SocketActionTypes.TOR_BOOTSTRAP_PROCESS, (payload: string) => { @@ -168,6 +169,7 @@ export function subscribe(socket: Socket) { emit(publicChannelsActions.createGeneralChannel()) }) socket.on(SocketActionTypes.REGISTRAR, (payload: ResponseRegistrarPayload) => { + console.log('SocketActionTypes.REGISTRAR') log(SocketActionTypes.REGISTRAR, payload) emit(communitiesActions.responseRegistrar(payload)) emit(networkActions.addInitializedRegistrar(payload.id)) @@ -180,9 +182,10 @@ export function subscribe(socket: Socket) { emit(communitiesActions.responseCreateNetwork(payload)) }) socket.on(SocketActionTypes.COMMUNITY, (payload: ResponseLaunchCommunityPayload) => { - emit(communitiesActions.launchRegistrar(payload.id)) + // emit(communitiesActions.launchRegistrar(payload.id)) emit(filesActions.checkForMissingFiles(payload.id)) emit(networkActions.addInitializedCommunity(payload.id)) + emit(communitiesActions.clearInvitationCodes()) }) // Errors socket.on(SocketActionTypes.ERROR, (payload: ErrorPayload) => { @@ -201,14 +204,14 @@ export function subscribe(socket: Socket) { }) socket.on(SocketActionTypes.SEND_USER_CERTIFICATE, (payload: SendOwnerCertificatePayload) => { - console.log('user cert with owner cert', payload) + console.log('Received SEND_USER_CERTIFICATE', payload.communityId) - emit( - communitiesActions.addOwnerCertificate({ - communityId: payload.communityId, - ownerCertificate: payload.payload.ownerCert, - }) - ) + // emit( + // communitiesActions.addOwnerCertificate({ + // communityId: payload.communityId, + // ownerCertificate: payload.payload.ownerCert, // is it needed? Owner is just an admin now + // }) + // ) emit( communitiesActions.storePeerList({ @@ -216,21 +219,22 @@ export function subscribe(socket: Socket) { peerList: payload.payload.peers, }) ) - emit( - identityActions.storeUserCertificate({ - userCertificate: payload.payload.certificate, - communityId: payload.communityId, - }) - ) - emit( - communitiesActions.updateCommunity({ - id: payload.communityId, - rootCa: payload.payload.rootCa, - }) - ) + // emit( + // identityActions.storeUserCertificate({ + // userCertificate: payload.payload.certificate, // is it needed? + // communityId: payload.communityId, + // }) + // ) + // emit( + // communitiesActions.updateCommunity({ + // id: payload.communityId, + // rootCa: payload.payload.rootCa, // is it needed? + // }) + // ) emit(communitiesActions.launchCommunity(payload.communityId)) }) socket.on(SocketActionTypes.SAVED_OWNER_CERTIFICATE, (payload: SavedOwnerCertificatePayload) => { + console.log('Received SAVED_OWNER_CERTIFICATE', payload.communityId) emit( communitiesActions.addOwnerCertificate({ communityId: payload.communityId, diff --git a/packages/types/src/community.ts b/packages/types/src/community.ts index 0f2077e5c5..cb4170169c 100644 --- a/packages/types/src/community.ts +++ b/packages/types/src/community.ts @@ -53,7 +53,7 @@ export interface InitCommunityPayload { id: string peerId: PeerId hiddenService: HiddenService - certs: Certificates + certs?: Certificates peers?: string[] } From d075b1658daec67ca88cae3da718bd790e386775 Mon Sep 17 00:00:00 2001 From: Ulises Gascon Date: Wed, 23 Aug 2023 00:38:06 +0200 Subject: [PATCH 05/61] doc: improved formatting and improved spelling --- packages/desktop/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/desktop/README.md b/packages/desktop/README.md index 5dbf233bcf..0cad5cc8b4 100644 --- a/packages/desktop/README.md +++ b/packages/desktop/README.md @@ -22,7 +22,7 @@ npm run start ## Versioning packages -Before trying to release a new version, make sure you have GH_TOKEN env set. +Before trying to release a new version, make sure you have `GH_TOKEN` env set. The project uses independent versioning which means each package has its own version number. Only those packages in which something has changed since the last release will be bumped. @@ -60,7 +60,7 @@ npm run lerna add luxon packages/state-manager ---- -Lerna takes care of all the packages. You can execute scripts is every pakcage by simpy running: +Lerna takes care of all the packages. You can execute scripts is every package by simply running: ``` npm run lerna run