remove dht-feature from backend modul

This commit is contained in:
Claus-Peter Hübner 2023-01-03 01:05:58 +01:00
parent 44f7863f89
commit 276ee66756
4 changed files with 0 additions and 999 deletions

View File

@ -1 +0,0 @@
declare module '@hyperswarm/dht'

View File

@ -1,798 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { startDHT } from './index'
import DHT from '@hyperswarm/dht'
import CONFIG from '@/config'
import { logger } from '@test/testSetup'
import { Community as DbCommunity } from '@entity/Community'
import { testEnvironment, cleanDB } from '@test/helpers'
CONFIG.FEDERATION_DHT_SEED = '64ebcb0e3ad547848fef4197c6e2332f'
jest.mock('@hyperswarm/dht')
const TEST_TOPIC = 'gradido_test_topic'
const keyPairMock = {
publicKey: Buffer.from('publicKey'),
secretKey: Buffer.from('secretKey'),
}
const serverListenSpy = jest.fn()
const serverEventMocks: { [key: string]: any } = {}
const serverOnMock = jest.fn().mockImplementation((key: string, callback) => {
serverEventMocks[key] = callback
})
const nodeCreateServerMock = jest.fn().mockImplementation(() => {
return {
on: serverOnMock,
listen: serverListenSpy,
}
})
const nodeAnnounceMock = jest.fn().mockImplementation(() => {
return {
finished: jest.fn(),
}
})
const lookupResultMock = {
token: Buffer.from(TEST_TOPIC),
from: {
id: Buffer.from('somone'),
host: '188.95.53.5',
port: 63561,
},
to: { id: null, host: '83.53.31.27', port: 55723 },
peers: [
{
publicKey: Buffer.from('some-public-key'),
relayAddresses: [],
},
],
}
const nodeLookupMock = jest.fn().mockResolvedValue([lookupResultMock])
const socketEventMocks: { [key: string]: any } = {}
const socketOnMock = jest.fn().mockImplementation((key: string, callback) => {
socketEventMocks[key] = callback
})
const socketWriteMock = jest.fn()
const nodeConnectMock = jest.fn().mockImplementation(() => {
return {
on: socketOnMock,
once: socketOnMock,
write: socketWriteMock,
}
})
DHT.hash.mockImplementation(() => {
return Buffer.from(TEST_TOPIC)
})
DHT.keyPair.mockImplementation(() => {
return keyPairMock
})
DHT.mockImplementation(() => {
return {
createServer: nodeCreateServerMock,
announce: nodeAnnounceMock,
lookup: nodeLookupMock,
connect: nodeConnectMock,
}
})
let con: any
let testEnv: any
beforeAll(async () => {
testEnv = await testEnvironment(logger)
con = testEnv.con
await cleanDB()
})
afterAll(async () => {
await cleanDB()
await con.close()
})
describe('federation', () => {
beforeAll(() => {
jest.useFakeTimers()
})
describe('call startDHT', () => {
const hashSpy = jest.spyOn(DHT, 'hash')
const keyPairSpy = jest.spyOn(DHT, 'keyPair')
beforeEach(async () => {
DHT.mockClear()
jest.clearAllMocks()
await startDHT(TEST_TOPIC)
})
it('calls DHT.hash', () => {
expect(hashSpy).toBeCalledWith(Buffer.from(TEST_TOPIC))
})
it('creates a key pair', () => {
expect(keyPairSpy).toBeCalledWith(expect.any(Buffer))
})
it('initializes a new DHT object', () => {
expect(DHT).toBeCalledWith({ keyPair: keyPairMock })
})
describe('DHT node', () => {
it('creates a server', () => {
expect(nodeCreateServerMock).toBeCalled()
})
it('listens on the server', () => {
expect(serverListenSpy).toBeCalled()
})
describe('timers', () => {
beforeEach(() => {
jest.runOnlyPendingTimers()
})
it('announces on topic', () => {
expect(nodeAnnounceMock).toBeCalledWith(Buffer.from(TEST_TOPIC), keyPairMock)
})
it('looks up on topic', () => {
expect(nodeLookupMock).toBeCalledWith(Buffer.from(TEST_TOPIC))
})
})
describe('server connection event', () => {
beforeEach(() => {
serverEventMocks.connection({
remotePublicKey: Buffer.from('another-public-key'),
on: socketOnMock,
})
})
it('can be triggered', () => {
expect(socketOnMock).toBeCalled()
})
describe('socket events', () => {
describe('on data', () => {
describe('with receiving simply a string', () => {
beforeEach(() => {
jest.clearAllMocks()
socketEventMocks.data(Buffer.from('no-json string'))
})
it('logs the received data', () => {
expect(logger.info).toBeCalledWith('data: no-json string')
})
it('logs an error of unexpected data format and structure', () => {
expect(logger.error).toBeCalledWith(
'Error on receiving data from socket:',
new SyntaxError('Unexpected token o in JSON at position 1'),
)
})
})
describe('with receiving array of strings', () => {
beforeEach(() => {
jest.clearAllMocks()
const strArray: string[] = ['invalid type test', 'api', 'url']
socketEventMocks.data(Buffer.from(strArray.toString()))
})
it('logs the received data', () => {
expect(logger.info).toBeCalledWith('data: invalid type test,api,url')
})
it('logs an error of unexpected data format and structure', () => {
expect(logger.error).toBeCalledWith(
'Error on receiving data from socket:',
new SyntaxError('Unexpected token i in JSON at position 0'),
)
})
})
describe('with receiving array of string-arrays', () => {
beforeEach(async () => {
jest.clearAllMocks()
const strArray: string[][] = [
[`api`, `url`, `invalid type in array test`],
[`wrong`, `api`, `url`],
]
await socketEventMocks.data(Buffer.from(strArray.toString()))
})
it('logs the received data', () => {
expect(logger.info).toBeCalledWith(
'data: api,url,invalid type in array test,wrong,api,url',
)
})
it('logs an error of unexpected data format and structure', () => {
expect(logger.error).toBeCalledWith(
'Error on receiving data from socket:',
new SyntaxError('Unexpected token a in JSON at position 0'),
)
})
})
describe('with receiving JSON-Array with too much entries', () => {
let jsonArray: { api: string; url: string }[]
beforeEach(async () => {
jest.clearAllMocks()
jsonArray = [
{ api: 'v1_0', url: 'too much versions at the same time test' },
{ api: 'v1_0', url: 'url2' },
{ api: 'v1_0', url: 'url3' },
{ api: 'v1_0', url: 'url4' },
{ api: 'v1_0', url: 'url5' },
]
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
})
it('logs the received data', () => {
expect(logger.info).toBeCalledWith(
'data: [{"api":"v1_0","url":"too much versions at the same time test"},{"api":"v1_0","url":"url2"},{"api":"v1_0","url":"url3"},{"api":"v1_0","url":"url4"},{"api":"v1_0","url":"url5"}]',
)
})
it('logs a warning of too much apiVersion-Definitions', () => {
expect(logger.warn).toBeCalledWith(
`received totaly wrong or too much apiVersions-Definition JSON-String: ${JSON.stringify(
jsonArray,
)}`,
)
})
})
describe('with receiving wrong but tolerated property data', () => {
let jsonArray: any[]
let result: DbCommunity[] = []
beforeAll(async () => {
jest.clearAllMocks()
jsonArray = [
{
wrong: 'wrong but tolerated property test',
api: 'v1_0',
url: 'url1',
},
{
api: 'v2_0',
url: 'url2',
wrong: 'wrong but tolerated property test',
},
]
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
result = await DbCommunity.find()
})
afterAll(async () => {
await cleanDB()
})
it('has two Communty entries in database', () => {
expect(result).toHaveLength(2)
})
it('has an entry for api version v1_0', () => {
expect(result).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
publicKey: expect.any(Buffer),
apiVersion: 'v1_0',
endPoint: 'url1',
lastAnnouncedAt: expect.any(Date),
createdAt: expect.any(Date),
updatedAt: null,
}),
]),
)
})
it('has an entry for api version v2_0', () => {
expect(result).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
publicKey: expect.any(Buffer),
apiVersion: 'v2_0',
endPoint: 'url2',
lastAnnouncedAt: expect.any(Date),
createdAt: expect.any(Date),
updatedAt: null,
}),
]),
)
})
})
describe('with receiving data but missing api property', () => {
let jsonArray: any[]
beforeEach(async () => {
jest.clearAllMocks()
jsonArray = [
{ test1: 'missing api proterty test', url: 'any url definition as string' },
{ api: 'some api', test2: 'missing url property test' },
]
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
})
it('logs the received data', () => {
expect(logger.info).toBeCalledWith(`data: ${JSON.stringify(jsonArray)}`)
})
it('logs a warning of invalid apiVersion-Definition', () => {
expect(logger.warn).toBeCalledWith(
`received invalid apiVersion-Definition: ${JSON.stringify(jsonArray[0])}`,
)
})
})
describe('with receiving data but missing url property', () => {
let jsonArray: any[]
beforeEach(async () => {
jest.clearAllMocks()
jsonArray = [
{ api: 'some api', test2: 'missing url property test' },
{ test1: 'missing api proterty test', url: 'any url definition as string' },
]
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
})
it('logs the received data', () => {
expect(logger.info).toBeCalledWith(`data: ${JSON.stringify(jsonArray)}`)
})
it('logs a warning of invalid apiVersion-Definition', () => {
expect(logger.warn).toBeCalledWith(
`received invalid apiVersion-Definition: ${JSON.stringify(jsonArray[0])}`,
)
})
})
describe('with receiving data but wrong type of api property', () => {
let jsonArray: any[]
beforeEach(async () => {
jest.clearAllMocks()
jsonArray = [
{ api: 1, url: 'wrong property type tests' },
{ api: 'urltyptest', url: 2 },
{ api: 1, url: 2 },
]
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
})
it('logs the received data', () => {
expect(logger.info).toBeCalledWith(`data: ${JSON.stringify(jsonArray)}`)
})
it('logs a warning of invalid apiVersion-Definition', () => {
expect(logger.warn).toBeCalledWith(
`received invalid apiVersion-Definition: ${JSON.stringify(jsonArray[0])}`,
)
})
})
describe('with receiving data but wrong type of url property', () => {
let jsonArray: any[]
beforeEach(async () => {
jest.clearAllMocks()
jsonArray = [
{ api: 'urltyptest', url: 2 },
{ api: 1, url: 'wrong property type tests' },
{ api: 1, url: 2 },
]
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
})
it('logs the received data', () => {
expect(logger.info).toBeCalledWith(`data: ${JSON.stringify(jsonArray)}`)
})
it('logs a warning of invalid apiVersion-Definition', () => {
expect(logger.warn).toBeCalledWith(
`received invalid apiVersion-Definition: ${JSON.stringify(jsonArray[0])}`,
)
})
})
describe('with receiving data but wrong type of both properties', () => {
let jsonArray: any[]
beforeEach(async () => {
jest.clearAllMocks()
jsonArray = [
{ api: 1, url: 2 },
{ api: 'urltyptest', url: 2 },
{ api: 1, url: 'wrong property type tests' },
]
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
})
it('logs the received data', () => {
expect(logger.info).toBeCalledWith(`data: ${JSON.stringify(jsonArray)}`)
})
it('logs a warning of invalid apiVersion-Definition', () => {
expect(logger.warn).toBeCalledWith(
`received invalid apiVersion-Definition: ${JSON.stringify(jsonArray[0])}`,
)
})
})
describe('with receiving data but too long api string', () => {
let jsonArray: any[]
beforeEach(async () => {
jest.clearAllMocks()
jsonArray = [
{ api: 'toolong api', url: 'some valid url' },
{
api: 'valid api',
url: 'this is a too long url definition with exact one character more than the allowed two hundert and fiftyfive characters. and here begins the fill characters with no sense of content menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmic',
},
{
api: 'toolong api',
url: 'this is a too long url definition with exact one character more than the allowed two hundert and fiftyfive characters. and here begins the fill characters with no sense of content menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmic',
},
]
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
})
it('logs the received data', () => {
expect(logger.info).toBeCalledWith(`data: ${JSON.stringify(jsonArray)}`)
})
it('logs a warning of invalid apiVersion-Definition', () => {
expect(logger.warn).toBeCalledWith(
`received apiVersion with content longer than max length: ${JSON.stringify(
jsonArray[0],
)}`,
)
})
})
describe('with receiving data but too long url string', () => {
let jsonArray: any[]
beforeEach(async () => {
jest.clearAllMocks()
jsonArray = [
{
api: 'api',
url: 'this is a too long url definition with exact one character more than the allowed two hundert and fiftyfive characters. and here begins the fill characters with no sense of content menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmic',
},
{ api: 'toolong api', url: 'some valid url' },
{
api: 'toolong api',
url: 'this is a too long url definition with exact one character more than the allowed two hundert and fiftyfive characters. and here begins the fill characters with no sense of content menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmic',
},
]
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
})
it('logs the received data', () => {
expect(logger.info).toBeCalledWith(`data: ${JSON.stringify(jsonArray)}`)
})
it('logs a warning of invalid apiVersion-Definition', () => {
expect(logger.warn).toBeCalledWith(
`received apiVersion with content longer than max length: ${JSON.stringify(
jsonArray[0],
)}`,
)
})
})
describe('with receiving data but both properties with too long strings', () => {
let jsonArray: any[]
beforeEach(async () => {
jest.clearAllMocks()
jsonArray = [
{
api: 'toolong api',
url: 'this is a too long url definition with exact one character more than the allowed two hundert and fiftyfive characters. and here begins the fill characters with no sense of content menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmic',
},
{
api: 'api',
url: 'this is a too long url definition with exact one character more than the allowed two hundert and fiftyfive characters. and here begins the fill characters with no sense of content menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmic',
},
{ api: 'toolong api', url: 'some valid url' },
]
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
})
it('logs the received data', () => {
expect(logger.info).toBeCalledWith(`data: ${JSON.stringify(jsonArray)}`)
})
})
describe('with receiving data of exact max allowed properties length', () => {
let jsonArray: any[]
let result: DbCommunity[] = []
beforeAll(async () => {
jest.clearAllMocks()
jsonArray = [
{
api: 'valid api',
url: 'this is a valid url definition with exact the max allowed length of two hundert and fiftyfive characters. and here begins the fill characters with no sense of content kuhwarmiga menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmich',
},
{
api: 'api',
url: 'this is a too long url definition with exact one character more than the allowed two hundert and fiftyfive characters. and here begins the fill characters with no sense of content menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmic',
},
{ api: 'toolong api', url: 'some valid url' },
]
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
result = await DbCommunity.find()
})
afterAll(async () => {
await cleanDB()
})
it('has one Communty entry in database', () => {
expect(result).toHaveLength(1)
})
it(`has an entry with max content length for api and url`, () => {
expect(result).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
publicKey: expect.any(Buffer),
apiVersion: 'valid api',
endPoint:
'this is a valid url definition with exact the max allowed length of two hundert and fiftyfive characters. and here begins the fill characters with no sense of content kuhwarmiga menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmich',
lastAnnouncedAt: expect.any(Date),
createdAt: expect.any(Date),
updatedAt: null,
}),
]),
)
})
})
describe('with receiving data of exact max allowed buffer length', () => {
let jsonArray: any[]
let result: DbCommunity[] = []
beforeAll(async () => {
jest.clearAllMocks()
jsonArray = [
{
api: 'valid api1',
url: 'this is a valid url definition with exact the max allowed length of two hundert and fiftyfive characters. and here begins the fill characters with no sense of content kuhwarmiga menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmich',
},
{
api: 'valid api2',
url: 'this is a valid url definition with exact the max allowed length of two hundert and fiftyfive characters. and here begins the fill characters with no sense of content kuhwarmiga menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmich',
},
{
api: 'valid api3',
url: 'this is a valid url definition with exact the max allowed length of two hundert and fiftyfive characters. and here begins the fill characters with no sense of content kuhwarmiga menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmich',
},
{
api: 'valid api4',
url: 'this is a valid url definition with exact the max allowed length of two hundert and fiftyfive characters. and here begins the fill characters with no sense of content kuhwarmiga menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmich',
},
]
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
result = await DbCommunity.find()
})
afterAll(async () => {
await cleanDB()
})
it('has five Communty entries in database', () => {
expect(result).toHaveLength(4)
})
it(`has an entry 'valid api1' with max content length for api and url`, () => {
expect(result).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
publicKey: expect.any(Buffer),
apiVersion: 'valid api1',
endPoint:
'this is a valid url definition with exact the max allowed length of two hundert and fiftyfive characters. and here begins the fill characters with no sense of content kuhwarmiga menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmich',
lastAnnouncedAt: expect.any(Date),
createdAt: expect.any(Date),
updatedAt: null,
}),
]),
)
})
it(`has an entry 'valid api2' with max content length for api and url`, () => {
expect(result).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
publicKey: expect.any(Buffer),
apiVersion: 'valid api2',
endPoint:
'this is a valid url definition with exact the max allowed length of two hundert and fiftyfive characters. and here begins the fill characters with no sense of content kuhwarmiga menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmich',
lastAnnouncedAt: expect.any(Date),
createdAt: expect.any(Date),
updatedAt: null,
}),
]),
)
})
it(`has an entry 'valid api3' with max content length for api and url`, () => {
expect(result).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
publicKey: expect.any(Buffer),
apiVersion: 'valid api3',
endPoint:
'this is a valid url definition with exact the max allowed length of two hundert and fiftyfive characters. and here begins the fill characters with no sense of content kuhwarmiga menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmich',
lastAnnouncedAt: expect.any(Date),
createdAt: expect.any(Date),
updatedAt: null,
}),
]),
)
})
it(`has an entry 'valid api4' with max content length for api and url`, () => {
expect(result).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
publicKey: expect.any(Buffer),
apiVersion: 'valid api4',
endPoint:
'this is a valid url definition with exact the max allowed length of two hundert and fiftyfive characters. and here begins the fill characters with no sense of content kuhwarmiga menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmich',
lastAnnouncedAt: expect.any(Date),
createdAt: expect.any(Date),
updatedAt: null,
}),
]),
)
})
})
describe('with receiving data longer than max allowed buffer length', () => {
let jsonArray: any[]
beforeEach(async () => {
jest.clearAllMocks()
jsonArray = [
{
api: 'Xvalid api1',
url: 'this is a valid url definition with exact the max allowed length of two hundert and fiftyfive characters. and here begins the fill characters with no sense of content kuhwarmiga menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmich',
},
{
api: 'valid api2',
url: 'this is a valid url definition with exact the max allowed length of two hundert and fiftyfive characters. and here begins the fill characters with no sense of content kuhwarmiga menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmich',
},
{
api: 'valid api3',
url: 'this is a valid url definition with exact the max allowed length of two hundert and fiftyfive characters. and here begins the fill characters with no sense of content kuhwarmiga menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmich',
},
{
api: 'valid api4',
url: 'this is a valid url definition with exact the max allowed length of two hundert and fiftyfive characters. and here begins the fill characters with no sense of content kuhwarmiga menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmich',
},
]
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
})
it('logs the received data', () => {
expect(logger.warn).toBeCalledWith(
`received more than max allowed length of data buffer: ${
JSON.stringify(jsonArray).length
} against 1141 max allowed`,
)
})
})
describe('with proper data', () => {
let result: DbCommunity[] = []
beforeAll(async () => {
jest.clearAllMocks()
await socketEventMocks.data(
Buffer.from(
JSON.stringify([
{
api: 'v1_0',
url: 'http://localhost:4000/api/v1_0',
},
{
api: 'v2_0',
url: 'http://localhost:4000/api/v2_0',
},
]),
),
)
result = await DbCommunity.find()
})
afterAll(async () => {
await cleanDB()
})
it('has two Communty entries in database', () => {
expect(result).toHaveLength(2)
})
it('has an entry for api version v1_0', () => {
expect(result).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
publicKey: expect.any(Buffer),
apiVersion: 'v1_0',
endPoint: 'http://localhost:4000/api/v1_0',
lastAnnouncedAt: expect.any(Date),
createdAt: expect.any(Date),
updatedAt: null,
}),
]),
)
})
it('has an entry for api version v2_0', () => {
expect(result).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
publicKey: expect.any(Buffer),
apiVersion: 'v2_0',
endPoint: 'http://localhost:4000/api/v2_0',
lastAnnouncedAt: expect.any(Date),
createdAt: expect.any(Date),
updatedAt: null,
}),
]),
)
})
})
})
describe('on open', () => {
beforeEach(() => {
socketEventMocks.open()
})
it.skip('calls socket write with own api versions', () => {
expect(socketWriteMock).toBeCalledWith(
Buffer.from(
JSON.stringify([
{
api: 'v1_0',
url: 'http://localhost:4000/api/v1_0',
},
{
api: 'v1_1',
url: 'http://localhost:4000/api/v1_1',
},
{
api: 'v2_0',
url: 'http://localhost:4000/api/v2_0',
},
]),
),
)
})
})
})
})
})
})
})

View File

@ -1,185 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import DHT from '@hyperswarm/dht'
import { backendLogger as logger } from '@/server/logger'
import CONFIG from '@/config'
import { Community as DbCommunity } from '@entity/Community'
const KEY_SECRET_SEEDBYTES = 32
const getSeed = (): Buffer | null =>
CONFIG.FEDERATION_DHT_SEED ? Buffer.alloc(KEY_SECRET_SEEDBYTES, CONFIG.FEDERATION_DHT_SEED) : null
const POLLTIME = 20000
const SUCCESSTIME = 120000
const ERRORTIME = 240000
const ANNOUNCETIME = 30000
enum ApiVersionType {
V1_0 = 'v1_0',
V1_1 = 'v1_1',
V2_0 = 'v2_0',
}
type CommunityApi = {
api: string
url: string
}
export const startDHT = async (topic: string): Promise<void> => {
try {
const TOPIC = DHT.hash(Buffer.from(topic))
const keyPair = DHT.keyPair(getSeed())
logger.info(`keyPairDHT: publicKey=${keyPair.publicKey.toString('hex')}`)
logger.debug(`keyPairDHT: secretKey=${keyPair.secretKey.toString('hex')}`)
const ownApiVersions = Object.values(ApiVersionType).map(function (apiEnum) {
const comApi: CommunityApi = {
api: apiEnum,
url: CONFIG.FEDERATION_COMMUNITY_URL + apiEnum,
}
return comApi
})
logger.debug(`ApiList: ${JSON.stringify(ownApiVersions)}`)
const node = new DHT({ keyPair })
const server = node.createServer()
server.on('connection', function (socket: any) {
logger.info(`server on... with Remote public key: ${socket.remotePublicKey.toString('hex')}`)
socket.on('data', async (data: Buffer) => {
try {
if (data.length > 1141) {
logger.warn(
`received more than max allowed length of data buffer: ${data.length} against 1141 max allowed`,
)
return
}
logger.info(`data: ${data.toString('ascii')}`)
const recApiVersions: CommunityApi[] = JSON.parse(data.toString('ascii'))
// TODO better to introduce the validation by https://github.com/typestack/class-validato
if (recApiVersions && Array.isArray(recApiVersions) && recApiVersions.length < 5) {
for (const recApiVersion of recApiVersions) {
if (
!recApiVersion.api ||
typeof recApiVersion.api !== 'string' ||
!recApiVersion.url ||
typeof recApiVersion.url !== 'string'
) {
logger.warn(
`received invalid apiVersion-Definition: ${JSON.stringify(recApiVersion)}`,
)
// in a forEach-loop use return instead of continue
return
}
// TODO better to introduce the validation on entity-Level by https://github.com/typestack/class-validator
if (recApiVersion.api.length > 10 || recApiVersion.url.length > 255) {
logger.warn(
`received apiVersion with content longer than max length: ${JSON.stringify(
recApiVersion,
)}`,
)
// in a forEach-loop use return instead of continue
return
}
const variables = {
apiVersion: recApiVersion.api,
endPoint: recApiVersion.url,
publicKey: socket.remotePublicKey.toString('hex'),
lastAnnouncedAt: new Date(),
}
logger.debug(`upsert with variables=${JSON.stringify(variables)}`)
// this will NOT update the updatedAt column, to distingue between a normal update and the last announcement
await DbCommunity.createQueryBuilder()
.insert()
.into(DbCommunity)
.values(variables)
.orUpdate({
conflict_target: ['id', 'publicKey', 'apiVersion'],
overwrite: ['end_point', 'last_announced_at'],
})
.execute()
logger.info(`federation community upserted successfully...`)
}
} else {
logger.warn(
`received totaly wrong or too much apiVersions-Definition JSON-String: ${JSON.stringify(
recApiVersions,
)}`,
)
}
} catch (e) {
logger.error('Error on receiving data from socket:', e)
}
})
})
await server.listen()
setInterval(async () => {
logger.info(`Announcing on topic: ${TOPIC.toString('hex')}`)
await node.announce(TOPIC, keyPair).finished()
}, ANNOUNCETIME)
let successfulRequests: string[] = []
let errorfulRequests: string[] = []
setInterval(async () => {
logger.info('Refreshing successful nodes')
successfulRequests = []
}, SUCCESSTIME)
setInterval(async () => {
logger.info('Refreshing errorful nodes')
errorfulRequests = []
}, ERRORTIME)
setInterval(async () => {
const result = await node.lookup(TOPIC)
const collectedPubKeys: string[] = []
for await (const data of result) {
data.peers.forEach((peer: any) => {
const pubKey = peer.publicKey.toString('hex')
if (
pubKey !== keyPair.publicKey.toString('hex') &&
!successfulRequests.includes(pubKey) &&
!errorfulRequests.includes(pubKey) &&
!collectedPubKeys.includes(pubKey)
) {
collectedPubKeys.push(peer.publicKey.toString('hex'))
}
})
}
logger.info(`Found new peers: ${collectedPubKeys}`)
collectedPubKeys.forEach((remotePubKey) => {
const socket = node.connect(Buffer.from(remotePubKey, 'hex'))
// socket.once("connect", function () {
// console.log("client side emitted connect");
// });
// socket.once("end", function () {
// console.log("client side ended");
// });
socket.once('error', (err: any) => {
errorfulRequests.push(remotePubKey)
logger.error(`error on peer ${remotePubKey}: ${err.message}`)
})
socket.on('open', function () {
socket.write(Buffer.from(JSON.stringify(ownApiVersions)))
successfulRequests.push(remotePubKey)
})
})
}, POLLTIME)
} catch (err) {
logger.error('DHT unexpected error:', err)
}
}

View File

@ -1,7 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import createServer from './server/createServer'
import { startDHT } from '@/federation/index'
// config
import CONFIG from './config'
@ -17,20 +16,6 @@ async function main() {
console.log(`GraphIQL available at http://localhost:${CONFIG.PORT}`)
}
})
// start DHT hyperswarm when DHT_TOPIC is set in .env
if (CONFIG.FEDERATION_DHT_TOPIC) {
if (CONFIG.FEDERATION_COMMUNITY_URL === null) {
throw Error(`Config-Error: missing configuration of property FEDERATION_COMMUNITY_URL`)
}
// eslint-disable-next-line no-console
console.log(
`starting Federation on ${CONFIG.FEDERATION_DHT_TOPIC} ${
CONFIG.FEDERATION_DHT_SEED ? 'with seed...' : 'without seed...'
}`,
)
await startDHT(CONFIG.FEDERATION_DHT_TOPIC) // con,
}
}
main().catch((e) => {