mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge pull request #2955 from gradido/2946-feature-x-com-3-introduce-business-communities
feat(federation): x com 3 introduce business communities
This commit is contained in:
commit
c44b589df5
@ -1,10 +1,11 @@
|
|||||||
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
|
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
|
||||||
import { GraphQLClient } from 'graphql-request'
|
import { GraphQLClient } from 'graphql-request'
|
||||||
|
|
||||||
import { getPublicKey } from '@/federation/query/getPublicKey'
|
import { getPublicKey } from '@/federation/client/1_0/query/getPublicKey'
|
||||||
import { backendLogger as logger } from '@/server/logger'
|
import { backendLogger as logger } from '@/server/logger'
|
||||||
|
|
||||||
export class FederationClient_1_0 {
|
// eslint-disable-next-line camelcase
|
||||||
|
export class FederationClient {
|
||||||
dbCom: DbFederatedCommunity
|
dbCom: DbFederatedCommunity
|
||||||
endpoint: string
|
endpoint: string
|
||||||
client: GraphQLClient
|
client: GraphQLClient
|
||||||
5
backend/src/federation/client/1_1/FederationClient.ts
Normal file
5
backend/src/federation/client/1_1/FederationClient.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
import { FederationClient as V1_0_FederationClient } from '@/federation/client/1_0/FederationClient'
|
||||||
|
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
export class FederationClient extends V1_0_FederationClient {}
|
||||||
@ -1,21 +1,23 @@
|
|||||||
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
|
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
|
||||||
|
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
import { FederationClient as V1_0_FederationClient } from '@/federation/client/1_0/FederationClient'
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
import { FederationClient as V1_1_FederationClient } from '@/federation/client/1_1/FederationClient'
|
||||||
import { ApiVersionType } from '@/federation/enum/apiVersionType'
|
import { ApiVersionType } from '@/federation/enum/apiVersionType'
|
||||||
|
|
||||||
import { FederationClient_1_0 } from './FederationClient_1_0'
|
// eslint-disable-next-line camelcase
|
||||||
import { FederationClient_1_1 } from './FederationClient_1_1'
|
type FederationClient = V1_0_FederationClient | V1_1_FederationClient
|
||||||
|
|
||||||
type FederationClientType = FederationClient_1_0 | FederationClient_1_1
|
interface FederationClientInstance {
|
||||||
|
|
||||||
interface ClientInstance {
|
|
||||||
id: number
|
id: number
|
||||||
// eslint-disable-next-line no-use-before-define
|
// eslint-disable-next-line no-use-before-define
|
||||||
client: FederationClientType
|
client: FederationClient
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
|
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
|
||||||
export class FederationClient {
|
export class FederationClientFactory {
|
||||||
private static instanceArray: ClientInstance[] = []
|
private static instanceArray: FederationClientInstance[] = []
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Singleton's constructor should always be private to prevent direct
|
* The Singleton's constructor should always be private to prevent direct
|
||||||
@ -27,9 +29,9 @@ export class FederationClient {
|
|||||||
private static createFederationClient = (dbCom: DbFederatedCommunity) => {
|
private static createFederationClient = (dbCom: DbFederatedCommunity) => {
|
||||||
switch (dbCom.apiVersion) {
|
switch (dbCom.apiVersion) {
|
||||||
case ApiVersionType.V1_0:
|
case ApiVersionType.V1_0:
|
||||||
return new FederationClient_1_0(dbCom)
|
return new V1_0_FederationClient(dbCom)
|
||||||
case ApiVersionType.V1_1:
|
case ApiVersionType.V1_1:
|
||||||
return new FederationClient_1_1(dbCom)
|
return new V1_1_FederationClient(dbCom)
|
||||||
default:
|
default:
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -41,14 +43,19 @@ export class FederationClient {
|
|||||||
* This implementation let you subclass the Singleton class while keeping
|
* This implementation let you subclass the Singleton class while keeping
|
||||||
* just one instance of each subclass around.
|
* just one instance of each subclass around.
|
||||||
*/
|
*/
|
||||||
public static getInstance(dbCom: DbFederatedCommunity): FederationClientType | null {
|
public static getInstance(dbCom: DbFederatedCommunity): FederationClient | null {
|
||||||
const instance = FederationClient.instanceArray.find((instance) => instance.id === dbCom.id)
|
const instance = FederationClientFactory.instanceArray.find(
|
||||||
|
(instance) => instance.id === dbCom.id,
|
||||||
|
)
|
||||||
if (instance) {
|
if (instance) {
|
||||||
return instance.client
|
return instance.client
|
||||||
}
|
}
|
||||||
const client = FederationClient.createFederationClient(dbCom)
|
const client = FederationClientFactory.createFederationClient(dbCom)
|
||||||
if (client) {
|
if (client) {
|
||||||
FederationClient.instanceArray.push({ id: dbCom.id, client } as ClientInstance)
|
FederationClientFactory.instanceArray.push({
|
||||||
|
id: dbCom.id,
|
||||||
|
client,
|
||||||
|
} as FederationClientInstance)
|
||||||
}
|
}
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
@ -1,3 +0,0 @@
|
|||||||
import { FederationClient_1_0 } from './FederationClient_1_0'
|
|
||||||
|
|
||||||
export class FederationClient_1_1 extends FederationClient_1_0 {}
|
|
||||||
@ -8,6 +8,8 @@
|
|||||||
import { Connection } from '@dbTools/typeorm'
|
import { Connection } from '@dbTools/typeorm'
|
||||||
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
|
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
|
||||||
import { ApolloServerTestClient } from 'apollo-server-testing'
|
import { ApolloServerTestClient } from 'apollo-server-testing'
|
||||||
|
import { GraphQLClient } from 'graphql-request'
|
||||||
|
import { Response } from 'graphql-request/dist/types'
|
||||||
|
|
||||||
import { testEnvironment, cleanDB } from '@test/helpers'
|
import { testEnvironment, cleanDB } from '@test/helpers'
|
||||||
import { logger } from '@test/testSetup'
|
import { logger } from '@test/testSetup'
|
||||||
@ -57,10 +59,23 @@ describe('validate Communities', () => {
|
|||||||
expect(logger.debug).toBeCalledWith(`Federation: found 0 dbCommunities`)
|
expect(logger.debug).toBeCalledWith(`Federation: found 0 dbCommunities`)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('with one Community of api 1_0', () => {
|
describe('with one Community of api 1_0 and not matching pubKey', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/require-await
|
||||||
|
jest.spyOn(GraphQLClient.prototype, 'rawRequest').mockImplementation(async () => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
getPublicKey: {
|
||||||
|
publicKey: 'somePubKey',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as Response<unknown>
|
||||||
|
})
|
||||||
const variables1 = {
|
const variables1 = {
|
||||||
publicKey: Buffer.from('11111111111111111111111111111111'),
|
publicKey: Buffer.from(
|
||||||
|
'1111111111111111111111111111111111111111111111111111111111111111',
|
||||||
|
),
|
||||||
apiVersion: '1_0',
|
apiVersion: '1_0',
|
||||||
endPoint: 'http//localhost:5001/api/',
|
endPoint: 'http//localhost:5001/api/',
|
||||||
lastAnnouncedAt: new Date(),
|
lastAnnouncedAt: new Date(),
|
||||||
@ -89,11 +104,85 @@ describe('validate Communities', () => {
|
|||||||
'http//localhost:5001/api/1_0/',
|
'http//localhost:5001/api/1_0/',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
it('logs not matching publicKeys', () => {
|
||||||
|
expect(logger.warn).toBeCalledWith(
|
||||||
|
'Federation: received not matching publicKey:',
|
||||||
|
'somePubKey',
|
||||||
|
expect.stringMatching('1111111111111111111111111111111111111111111111111111111111111111'),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('with one Community of api 1_0 and matching pubKey', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/require-await
|
||||||
|
jest.spyOn(GraphQLClient.prototype, 'rawRequest').mockImplementation(async () => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
getPublicKey: {
|
||||||
|
publicKey: '1111111111111111111111111111111111111111111111111111111111111111',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as Response<unknown>
|
||||||
|
})
|
||||||
|
const variables1 = {
|
||||||
|
publicKey: Buffer.from(
|
||||||
|
'1111111111111111111111111111111111111111111111111111111111111111',
|
||||||
|
),
|
||||||
|
apiVersion: '1_0',
|
||||||
|
endPoint: 'http//localhost:5001/api/',
|
||||||
|
lastAnnouncedAt: new Date(),
|
||||||
|
}
|
||||||
|
await DbFederatedCommunity.createQueryBuilder()
|
||||||
|
.insert()
|
||||||
|
.into(DbFederatedCommunity)
|
||||||
|
.values(variables1)
|
||||||
|
.orUpdate({
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
conflict_target: ['id', 'publicKey', 'apiVersion'],
|
||||||
|
overwrite: ['end_point', 'last_announced_at'],
|
||||||
|
})
|
||||||
|
.execute()
|
||||||
|
await DbFederatedCommunity.update({}, { verifiedAt: null })
|
||||||
|
jest.clearAllMocks()
|
||||||
|
await validateCommunities()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('logs one community found', () => {
|
||||||
|
expect(logger.debug).toBeCalledWith(`Federation: found 1 dbCommunities`)
|
||||||
|
})
|
||||||
|
it('logs requestGetPublicKey for community api 1_0 ', () => {
|
||||||
|
expect(logger.info).toBeCalledWith(
|
||||||
|
'Federation: getPublicKey from endpoint',
|
||||||
|
'http//localhost:5001/api/1_0/',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
it('logs community pubKey verified', () => {
|
||||||
|
expect(logger.info).toHaveBeenNthCalledWith(
|
||||||
|
3,
|
||||||
|
'Federation: verified community with',
|
||||||
|
'http//localhost:5001/api/',
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
describe('with two Communities of api 1_0 and 1_1', () => {
|
describe('with two Communities of api 1_0 and 1_1', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
// eslint-disable-next-line @typescript-eslint/require-await
|
||||||
|
jest.spyOn(GraphQLClient.prototype, 'rawRequest').mockImplementation(async () => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
getPublicKey: {
|
||||||
|
publicKey: '1111111111111111111111111111111111111111111111111111111111111111',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as Response<unknown>
|
||||||
|
})
|
||||||
const variables2 = {
|
const variables2 = {
|
||||||
publicKey: Buffer.from('11111111111111111111111111111111'),
|
publicKey: Buffer.from(
|
||||||
|
'1111111111111111111111111111111111111111111111111111111111111111',
|
||||||
|
),
|
||||||
apiVersion: '1_1',
|
apiVersion: '1_1',
|
||||||
endPoint: 'http//localhost:5001/api/',
|
endPoint: 'http//localhost:5001/api/',
|
||||||
lastAnnouncedAt: new Date(),
|
lastAnnouncedAt: new Date(),
|
||||||
@ -109,6 +198,7 @@ describe('validate Communities', () => {
|
|||||||
})
|
})
|
||||||
.execute()
|
.execute()
|
||||||
|
|
||||||
|
await DbFederatedCommunity.update({}, { verifiedAt: null })
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
await validateCommunities()
|
await validateCommunities()
|
||||||
})
|
})
|
||||||
@ -132,7 +222,9 @@ describe('validate Communities', () => {
|
|||||||
let dbCom: DbFederatedCommunity
|
let dbCom: DbFederatedCommunity
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const variables3 = {
|
const variables3 = {
|
||||||
publicKey: Buffer.from('11111111111111111111111111111111'),
|
publicKey: Buffer.from(
|
||||||
|
'1111111111111111111111111111111111111111111111111111111111111111',
|
||||||
|
),
|
||||||
apiVersion: '2_0',
|
apiVersion: '2_0',
|
||||||
endPoint: 'http//localhost:5001/api/',
|
endPoint: 'http//localhost:5001/api/',
|
||||||
lastAnnouncedAt: new Date(),
|
lastAnnouncedAt: new Date(),
|
||||||
@ -150,6 +242,7 @@ describe('validate Communities', () => {
|
|||||||
dbCom = await DbFederatedCommunity.findOneOrFail({
|
dbCom = await DbFederatedCommunity.findOneOrFail({
|
||||||
where: { publicKey: variables3.publicKey, apiVersion: variables3.apiVersion },
|
where: { publicKey: variables3.publicKey, apiVersion: variables3.apiVersion },
|
||||||
})
|
})
|
||||||
|
await DbFederatedCommunity.update({}, { verifiedAt: null })
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
await validateCommunities()
|
await validateCommunities()
|
||||||
})
|
})
|
||||||
|
|||||||
@ -3,9 +3,11 @@
|
|||||||
import { IsNull } from '@dbTools/typeorm'
|
import { IsNull } from '@dbTools/typeorm'
|
||||||
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
|
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
|
||||||
|
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
import { FederationClient as V1_0_FederationClient } from '@/federation/client/1_0/FederationClient'
|
||||||
|
import { FederationClientFactory } from '@/federation/client/FederationClientFactory'
|
||||||
import { backendLogger as logger } from '@/server/logger'
|
import { backendLogger as logger } from '@/server/logger'
|
||||||
|
|
||||||
import { FederationClient } from './client/FederationClient'
|
|
||||||
import { ApiVersionType } from './enum/apiVersionType'
|
import { ApiVersionType } from './enum/apiVersionType'
|
||||||
|
|
||||||
export function startValidateCommunities(timerInterval: number): void {
|
export function startValidateCommunities(timerInterval: number): void {
|
||||||
@ -37,11 +39,13 @@ export async function validateCommunities(): Promise<void> {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const client = FederationClient.getInstance(dbCom)
|
const client = FederationClientFactory.getInstance(dbCom)
|
||||||
const pubKey = await client?.getPublicKey()
|
// eslint-disable-next-line camelcase
|
||||||
|
if (client instanceof V1_0_FederationClient) {
|
||||||
|
const pubKey = await client.getPublicKey()
|
||||||
if (pubKey && pubKey === dbCom.publicKey.toString()) {
|
if (pubKey && pubKey === dbCom.publicKey.toString()) {
|
||||||
await DbFederatedCommunity.update({ id: dbCom.id }, { verifiedAt: new Date() })
|
await DbFederatedCommunity.update({ id: dbCom.id }, { verifiedAt: new Date() })
|
||||||
logger.info('Federation: verified community', dbCom)
|
logger.info('Federation: verified community with', dbCom.endPoint)
|
||||||
} else {
|
} else {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
'Federation: received not matching publicKey:',
|
'Federation: received not matching publicKey:',
|
||||||
@ -49,6 +53,7 @@ export async function validateCommunities(): Promise<void> {
|
|||||||
dbCom.publicKey.toString(),
|
dbCom.publicKey.toString(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(`Error:`, err)
|
logger.error(`Error:`, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -57,7 +57,7 @@ EMAIL_CODE_REQUEST_TIME=10
|
|||||||
WEBHOOK_ELOPAGE_SECRET=secret
|
WEBHOOK_ELOPAGE_SECRET=secret
|
||||||
|
|
||||||
# Federation
|
# Federation
|
||||||
FEDERATION_DHT_CONFIG_VERSION=v2.2023-02-07
|
FEDERATION_DHT_CONFIG_VERSION=v3.2023-04-26
|
||||||
# if you set the value of FEDERATION_DHT_TOPIC, the DHT hyperswarm will start to announce and listen
|
# if you set the value of FEDERATION_DHT_TOPIC, the DHT hyperswarm will start to announce and listen
|
||||||
# on an hash created from this topic
|
# on an hash created from this topic
|
||||||
# FEDERATION_DHT_TOPIC=GRADIDO_HUB
|
# FEDERATION_DHT_TOPIC=GRADIDO_HUB
|
||||||
|
|||||||
@ -8,6 +8,10 @@ DB_PASSWORD=$DB_PASSWORD
|
|||||||
DB_DATABASE=gradido_community
|
DB_DATABASE=gradido_community
|
||||||
TYPEORM_LOGGING_RELATIVE_PATH=$TYPEORM_LOGGING_RELATIVE_PATH
|
TYPEORM_LOGGING_RELATIVE_PATH=$TYPEORM_LOGGING_RELATIVE_PATH
|
||||||
|
|
||||||
|
# Community
|
||||||
|
COMMUNITY_NAME=$COMMUNITY_NAME
|
||||||
|
COMMUNITY_DESCRIPTION=$COMMUNITY_DESCRIPTION
|
||||||
|
|
||||||
# Federation
|
# Federation
|
||||||
FEDERATION_DHT_CONFIG_VERSION=$FEDERATION_DHT_CONFIG_VERSION
|
FEDERATION_DHT_CONFIG_VERSION=$FEDERATION_DHT_CONFIG_VERSION
|
||||||
# if you set the value of FEDERATION_DHT_TOPIC, the DHT hyperswarm will start to announce and listen
|
# if you set the value of FEDERATION_DHT_TOPIC, the DHT hyperswarm will start to announce and listen
|
||||||
|
|||||||
@ -6,7 +6,7 @@ module.exports = {
|
|||||||
collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'],
|
collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'],
|
||||||
coverageThreshold: {
|
coverageThreshold: {
|
||||||
global: {
|
global: {
|
||||||
lines: 80,
|
lines: 83,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setupFiles: ['<rootDir>/test/testSetup.ts'],
|
setupFiles: ['<rootDir>/test/testSetup.ts'],
|
||||||
|
|||||||
@ -23,7 +23,8 @@
|
|||||||
"nodemon": "^2.0.20",
|
"nodemon": "^2.0.20",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"tsconfig-paths": "^4.1.2",
|
"tsconfig-paths": "^4.1.2",
|
||||||
"typescript": "^4.9.4"
|
"typescript": "^4.9.4",
|
||||||
|
"uuid": "^8.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/dotenv": "^8.2.0",
|
"@types/dotenv": "^8.2.0",
|
||||||
@ -31,6 +32,7 @@
|
|||||||
"@types/node": "^18.11.18",
|
"@types/node": "^18.11.18",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.48.0",
|
"@typescript-eslint/eslint-plugin": "^5.48.0",
|
||||||
"@typescript-eslint/parser": "^5.48.0",
|
"@typescript-eslint/parser": "^5.48.0",
|
||||||
|
"@types/uuid": "^8.3.4",
|
||||||
"eslint": "^8.31.0",
|
"eslint": "^8.31.0",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint-config-standard": "^17.0.0",
|
"eslint-config-standard": "^17.0.0",
|
||||||
|
|||||||
@ -9,7 +9,7 @@ const constants = {
|
|||||||
LOG_LEVEL: process.env.LOG_LEVEL || 'info',
|
LOG_LEVEL: process.env.LOG_LEVEL || 'info',
|
||||||
CONFIG_VERSION: {
|
CONFIG_VERSION: {
|
||||||
DEFAULT: 'DEFAULT',
|
DEFAULT: 'DEFAULT',
|
||||||
EXPECTED: 'v2.2023-02-07',
|
EXPECTED: 'v3.2023-04-26',
|
||||||
CURRENT: '',
|
CURRENT: '',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -28,6 +28,12 @@ const database = {
|
|||||||
process.env.TYPEORM_LOGGING_RELATIVE_PATH || 'typeorm.dht-node.log',
|
process.env.TYPEORM_LOGGING_RELATIVE_PATH || 'typeorm.dht-node.log',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const community = {
|
||||||
|
COMMUNITY_NAME: process.env.COMMUNITY_NAME || 'Gradido Entwicklung',
|
||||||
|
COMMUNITY_DESCRIPTION:
|
||||||
|
process.env.COMMUNITY_DESCRIPTION || 'Gradido-Community einer lokalen Entwicklungsumgebung.',
|
||||||
|
}
|
||||||
|
|
||||||
const federation = {
|
const federation = {
|
||||||
FEDERATION_DHT_TOPIC: process.env.FEDERATION_DHT_TOPIC || 'GRADIDO_HUB',
|
FEDERATION_DHT_TOPIC: process.env.FEDERATION_DHT_TOPIC || 'GRADIDO_HUB',
|
||||||
FEDERATION_DHT_SEED: process.env.FEDERATION_DHT_SEED || null,
|
FEDERATION_DHT_SEED: process.env.FEDERATION_DHT_SEED || null,
|
||||||
@ -51,6 +57,7 @@ const CONFIG = {
|
|||||||
...constants,
|
...constants,
|
||||||
...server,
|
...server,
|
||||||
...database,
|
...database,
|
||||||
|
...community,
|
||||||
...federation,
|
...federation,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,8 +5,10 @@ import { startDHT } from './index'
|
|||||||
import DHT from '@hyperswarm/dht'
|
import DHT from '@hyperswarm/dht'
|
||||||
import CONFIG from '@/config'
|
import CONFIG from '@/config'
|
||||||
import { logger } from '@test/testSetup'
|
import { logger } from '@test/testSetup'
|
||||||
|
import { Community as DbCommunity } from '@entity/Community'
|
||||||
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
|
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
|
||||||
import { testEnvironment, cleanDB } from '@test/helpers'
|
import { testEnvironment, cleanDB } from '@test/helpers'
|
||||||
|
import { validate as validateUUID, version as versionUUID } from 'uuid'
|
||||||
|
|
||||||
CONFIG.FEDERATION_DHT_SEED = '64ebcb0e3ad547848fef4197c6e2332f'
|
CONFIG.FEDERATION_DHT_SEED = '64ebcb0e3ad547848fef4197c6e2332f'
|
||||||
|
|
||||||
@ -114,6 +116,9 @@ describe('federation', () => {
|
|||||||
const hashSpy = jest.spyOn(DHT, 'hash')
|
const hashSpy = jest.spyOn(DHT, 'hash')
|
||||||
const keyPairSpy = jest.spyOn(DHT, 'keyPair')
|
const keyPairSpy = jest.spyOn(DHT, 'keyPair')
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
CONFIG.FEDERATION_COMMUNITY_URL = 'https://test.gradido.net'
|
||||||
|
CONFIG.COMMUNITY_NAME = 'Gradido Test Community'
|
||||||
|
CONFIG.COMMUNITY_DESCRIPTION = 'Community to test the federation'
|
||||||
DHT.mockClear()
|
DHT.mockClear()
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
await cleanDB()
|
await cleanDB()
|
||||||
@ -132,6 +137,64 @@ describe('federation', () => {
|
|||||||
expect(DHT).toBeCalledWith({ keyPair: keyPairMock })
|
expect(DHT).toBeCalledWith({ keyPair: keyPairMock })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('stores the home community in community table ', async () => {
|
||||||
|
const result = await DbCommunity.find()
|
||||||
|
expect(result).toEqual([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(Number),
|
||||||
|
foreign: false,
|
||||||
|
url: 'https://test.gradido.net/api/',
|
||||||
|
publicKey: expect.any(Buffer),
|
||||||
|
communityUuid: expect.any(String),
|
||||||
|
authenticatedAt: null,
|
||||||
|
name: 'Gradido Test Community',
|
||||||
|
description: 'Community to test the federation',
|
||||||
|
creationDate: expect.any(Date),
|
||||||
|
createdAt: expect.any(Date),
|
||||||
|
updatedAt: null,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
expect(validateUUID(result[0].communityUuid ? result[0].communityUuid : '')).toEqual(true)
|
||||||
|
expect(versionUUID(result[0].communityUuid ? result[0].communityUuid : '')).toEqual(4)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('creates 3 entries in table federated_communities', async () => {
|
||||||
|
const result = await DbFederatedCommunity.find({ order: { id: 'ASC' } })
|
||||||
|
await expect(result).toHaveLength(3)
|
||||||
|
await expect(result).toEqual([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(Number),
|
||||||
|
foreign: false,
|
||||||
|
publicKey: expect.any(Buffer),
|
||||||
|
apiVersion: '1_0',
|
||||||
|
endPoint: 'https://test.gradido.net/api/',
|
||||||
|
lastAnnouncedAt: null,
|
||||||
|
createdAt: expect.any(Date),
|
||||||
|
updatedAt: null,
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(Number),
|
||||||
|
foreign: false,
|
||||||
|
publicKey: expect.any(Buffer),
|
||||||
|
apiVersion: '1_1',
|
||||||
|
endPoint: 'https://test.gradido.net/api/',
|
||||||
|
lastAnnouncedAt: null,
|
||||||
|
createdAt: expect.any(Date),
|
||||||
|
updatedAt: null,
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(Number),
|
||||||
|
foreign: false,
|
||||||
|
publicKey: expect.any(Buffer),
|
||||||
|
apiVersion: '2_0',
|
||||||
|
endPoint: 'https://test.gradido.net/api/',
|
||||||
|
lastAnnouncedAt: null,
|
||||||
|
createdAt: expect.any(Date),
|
||||||
|
updatedAt: null,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
describe('DHT node', () => {
|
describe('DHT node', () => {
|
||||||
it('creates a server', () => {
|
it('creates a server', () => {
|
||||||
expect(nodeCreateServerMock).toBeCalled()
|
expect(nodeCreateServerMock).toBeCalled()
|
||||||
@ -780,21 +843,21 @@ describe('federation', () => {
|
|||||||
socketEventMocks.open()
|
socketEventMocks.open()
|
||||||
})
|
})
|
||||||
|
|
||||||
it.skip('calls socket write with own api versions', () => {
|
it('calls socket write with own api versions', () => {
|
||||||
expect(socketWriteMock).toBeCalledWith(
|
expect(socketWriteMock).toBeCalledWith(
|
||||||
Buffer.from(
|
Buffer.from(
|
||||||
JSON.stringify([
|
JSON.stringify([
|
||||||
{
|
{
|
||||||
api: '1_0',
|
api: '1_0',
|
||||||
url: 'http://localhost/api/',
|
url: 'https://test.gradido.net/api/',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
api: '1_1',
|
api: '1_1',
|
||||||
url: 'http://localhost/api/',
|
url: 'https://test.gradido.net/api/',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
api: '2_0',
|
api: '2_0',
|
||||||
url: 'http://localhost/api/',
|
url: 'https://test.gradido.net/api/',
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
@ -804,5 +867,101 @@ describe('federation', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('restart DHT', () => {
|
||||||
|
let homeCommunity: DbCommunity
|
||||||
|
let federatedCommunities: DbFederatedCommunity[]
|
||||||
|
|
||||||
|
describe('without changes', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
DHT.mockClear()
|
||||||
|
jest.clearAllMocks()
|
||||||
|
homeCommunity = (await DbCommunity.find())[0]
|
||||||
|
federatedCommunities = await DbFederatedCommunity.find({ order: { id: 'ASC' } })
|
||||||
|
await startDHT(TEST_TOPIC)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not change home community in community table except updated at column ', async () => {
|
||||||
|
await expect(DbCommunity.find()).resolves.toEqual([
|
||||||
|
{
|
||||||
|
...homeCommunity,
|
||||||
|
updatedAt: expect.any(Date),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('rewrites the 3 entries in table federated_communities', async () => {
|
||||||
|
const result = await DbFederatedCommunity.find()
|
||||||
|
await expect(result).toHaveLength(3)
|
||||||
|
await expect(result).toEqual([
|
||||||
|
{
|
||||||
|
...federatedCommunities[0],
|
||||||
|
id: expect.any(Number),
|
||||||
|
createdAt: expect.any(Date),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...federatedCommunities[1],
|
||||||
|
id: expect.any(Number),
|
||||||
|
createdAt: expect.any(Date),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...federatedCommunities[2],
|
||||||
|
id: expect.any(Number),
|
||||||
|
createdAt: expect.any(Date),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('changeing URL, name and description', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
CONFIG.FEDERATION_COMMUNITY_URL = 'https://test2.gradido.net'
|
||||||
|
CONFIG.COMMUNITY_NAME = 'Second Gradido Test Community'
|
||||||
|
CONFIG.COMMUNITY_DESCRIPTION = 'Another Community to test the federation'
|
||||||
|
DHT.mockClear()
|
||||||
|
jest.clearAllMocks()
|
||||||
|
homeCommunity = (await DbCommunity.find())[0]
|
||||||
|
federatedCommunities = await DbFederatedCommunity.find({ order: { id: 'ASC' } })
|
||||||
|
await startDHT(TEST_TOPIC)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('updates URL, name, description and updated at columns ', async () => {
|
||||||
|
await expect(DbCommunity.find()).resolves.toEqual([
|
||||||
|
{
|
||||||
|
...homeCommunity,
|
||||||
|
url: 'https://test2.gradido.net/api/',
|
||||||
|
name: 'Second Gradido Test Community',
|
||||||
|
description: 'Another Community to test the federation',
|
||||||
|
updatedAt: expect.any(Date),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('rewrites the 3 entries in table federated_communities with new endpoint', async () => {
|
||||||
|
const result = await DbFederatedCommunity.find()
|
||||||
|
await expect(result).toHaveLength(3)
|
||||||
|
await expect(result).toEqual([
|
||||||
|
{
|
||||||
|
...federatedCommunities[0],
|
||||||
|
id: expect.any(Number),
|
||||||
|
createdAt: expect.any(Date),
|
||||||
|
endPoint: 'https://test2.gradido.net/api/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...federatedCommunities[1],
|
||||||
|
id: expect.any(Number),
|
||||||
|
createdAt: expect.any(Date),
|
||||||
|
endPoint: 'https://test2.gradido.net/api/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...federatedCommunities[2],
|
||||||
|
id: expect.any(Number),
|
||||||
|
createdAt: expect.any(Date),
|
||||||
|
endPoint: 'https://test2.gradido.net/api/',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -4,10 +4,15 @@ import DHT from '@hyperswarm/dht'
|
|||||||
import { logger } from '@/server/logger'
|
import { logger } from '@/server/logger'
|
||||||
import CONFIG from '@/config'
|
import CONFIG from '@/config'
|
||||||
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
|
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
|
||||||
|
import { Community as DbCommunity } from '@entity/Community'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
|
||||||
const KEY_SECRET_SEEDBYTES = 32
|
const KEY_SECRET_SEEDBYTES = 32
|
||||||
const getSeed = (): Buffer | null =>
|
const getSeed = (): Buffer | null => {
|
||||||
CONFIG.FEDERATION_DHT_SEED ? Buffer.alloc(KEY_SECRET_SEEDBYTES, CONFIG.FEDERATION_DHT_SEED) : null
|
return CONFIG.FEDERATION_DHT_SEED
|
||||||
|
? Buffer.alloc(KEY_SECRET_SEEDBYTES, CONFIG.FEDERATION_DHT_SEED)
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
|
||||||
const POLLTIME = 20000
|
const POLLTIME = 20000
|
||||||
const SUCCESSTIME = 120000
|
const SUCCESSTIME = 120000
|
||||||
@ -19,7 +24,7 @@ enum ApiVersionType {
|
|||||||
V1_1 = '1_1',
|
V1_1 = '1_1',
|
||||||
V2_0 = '2_0',
|
V2_0 = '2_0',
|
||||||
}
|
}
|
||||||
type CommunityApi = {
|
export type CommunityApi = {
|
||||||
api: string
|
api: string
|
||||||
url: string
|
url: string
|
||||||
}
|
}
|
||||||
@ -28,10 +33,12 @@ export const startDHT = async (topic: string): Promise<void> => {
|
|||||||
try {
|
try {
|
||||||
const TOPIC = DHT.hash(Buffer.from(topic))
|
const TOPIC = DHT.hash(Buffer.from(topic))
|
||||||
const keyPair = DHT.keyPair(getSeed())
|
const keyPair = DHT.keyPair(getSeed())
|
||||||
logger.info(`keyPairDHT: publicKey=${keyPair.publicKey.toString('hex')}`)
|
const pubKeyString = keyPair.publicKey.toString('hex')
|
||||||
|
logger.info(`keyPairDHT: publicKey=${pubKeyString}`)
|
||||||
logger.debug(`keyPairDHT: secretKey=${keyPair.secretKey.toString('hex')}`)
|
logger.debug(`keyPairDHT: secretKey=${keyPair.secretKey.toString('hex')}`)
|
||||||
|
await writeHomeCommunityEntry(pubKeyString)
|
||||||
|
|
||||||
const ownApiVersions = await writeFederatedHomeCommunityEnries(keyPair.publicKey)
|
const ownApiVersions = await writeFederatedHomeCommunityEntries(pubKeyString)
|
||||||
logger.info(`ApiList: ${JSON.stringify(ownApiVersions)}`)
|
logger.info(`ApiList: ${JSON.stringify(ownApiVersions)}`)
|
||||||
|
|
||||||
const node = new DHT({ keyPair })
|
const node = new DHT({ keyPair })
|
||||||
@ -138,7 +145,7 @@ export const startDHT = async (topic: string): Promise<void> => {
|
|||||||
data.peers.forEach((peer: any) => {
|
data.peers.forEach((peer: any) => {
|
||||||
const pubKey = peer.publicKey.toString('hex')
|
const pubKey = peer.publicKey.toString('hex')
|
||||||
if (
|
if (
|
||||||
pubKey !== keyPair.publicKey.toString('hex') &&
|
pubKey !== pubKeyString &&
|
||||||
!successfulRequests.includes(pubKey) &&
|
!successfulRequests.includes(pubKey) &&
|
||||||
!errorfulRequests.includes(pubKey) &&
|
!errorfulRequests.includes(pubKey) &&
|
||||||
!collectedPubKeys.includes(pubKey)
|
!collectedPubKeys.includes(pubKey)
|
||||||
@ -179,7 +186,7 @@ export const startDHT = async (topic: string): Promise<void> => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function writeFederatedHomeCommunityEnries(pubKey: any): Promise<CommunityApi[]> {
|
async function writeFederatedHomeCommunityEntries(pubKey: string): Promise<CommunityApi[]> {
|
||||||
const homeApiVersions: CommunityApi[] = Object.values(ApiVersionType).map(function (apiEnum) {
|
const homeApiVersions: CommunityApi[] = Object.values(ApiVersionType).map(function (apiEnum) {
|
||||||
const comApi: CommunityApi = {
|
const comApi: CommunityApi = {
|
||||||
api: apiEnum,
|
api: apiEnum,
|
||||||
@ -189,21 +196,68 @@ async function writeFederatedHomeCommunityEnries(pubKey: any): Promise<Community
|
|||||||
})
|
})
|
||||||
try {
|
try {
|
||||||
// first remove privious existing homeCommunity entries
|
// first remove privious existing homeCommunity entries
|
||||||
DbFederatedCommunity.createQueryBuilder().delete().where({ foreign: false }).execute()
|
await DbFederatedCommunity.createQueryBuilder().delete().where({ foreign: false }).execute()
|
||||||
|
for (const homeApiVersion of homeApiVersions) {
|
||||||
homeApiVersions.forEach(async function (homeApi) {
|
const homeCom = DbFederatedCommunity.create()
|
||||||
const homeCom = new DbFederatedCommunity()
|
|
||||||
homeCom.foreign = false
|
homeCom.foreign = false
|
||||||
homeCom.apiVersion = homeApi.api
|
homeCom.apiVersion = homeApiVersion.api
|
||||||
homeCom.endPoint = homeApi.url
|
homeCom.endPoint = homeApiVersion.url
|
||||||
homeCom.publicKey = pubKey.toString('hex')
|
homeCom.publicKey = Buffer.from(pubKey)
|
||||||
|
|
||||||
// this will NOT update the updatedAt column, to distingue between a normal update and the last announcement
|
|
||||||
await DbFederatedCommunity.insert(homeCom)
|
await DbFederatedCommunity.insert(homeCom)
|
||||||
logger.info(`federation home-community inserted successfully: ${JSON.stringify(homeCom)}`)
|
logger.info(`federation home-community inserted successfully:`, homeApiVersion)
|
||||||
})
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new Error(`Federation: Error writing HomeCommunity-Entries: ${err}`)
|
throw new Error(`Federation: Error writing federated HomeCommunity-Entries: ${err}`)
|
||||||
}
|
}
|
||||||
return homeApiVersions
|
return homeApiVersions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function writeHomeCommunityEntry(pubKey: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
// check for existing homeCommunity entry
|
||||||
|
let homeCom = await DbCommunity.findOne({
|
||||||
|
foreign: false,
|
||||||
|
publicKey: Buffer.from(pubKey),
|
||||||
|
})
|
||||||
|
if (!homeCom) {
|
||||||
|
// check if a homecommunity with a different publicKey still exists
|
||||||
|
homeCom = await DbCommunity.findOne({ foreign: false })
|
||||||
|
}
|
||||||
|
if (homeCom) {
|
||||||
|
// simply update the existing entry, but it MUST keep the ID and UUID because of possible relations
|
||||||
|
homeCom.publicKey = Buffer.from(pubKey)
|
||||||
|
homeCom.url = CONFIG.FEDERATION_COMMUNITY_URL + '/api/'
|
||||||
|
homeCom.name = CONFIG.COMMUNITY_NAME
|
||||||
|
homeCom.description = CONFIG.COMMUNITY_DESCRIPTION
|
||||||
|
await DbCommunity.save(homeCom)
|
||||||
|
logger.info(`home-community updated successfully:`, homeCom)
|
||||||
|
} else {
|
||||||
|
// insert a new homecommunity entry including a new ID and a new but ensured unique UUID
|
||||||
|
homeCom = new DbCommunity()
|
||||||
|
homeCom.foreign = false
|
||||||
|
homeCom.publicKey = Buffer.from(pubKey)
|
||||||
|
homeCom.communityUuid = await newCommunityUuid()
|
||||||
|
homeCom.url = CONFIG.FEDERATION_COMMUNITY_URL + '/api/'
|
||||||
|
homeCom.name = CONFIG.COMMUNITY_NAME
|
||||||
|
homeCom.description = CONFIG.COMMUNITY_DESCRIPTION
|
||||||
|
homeCom.creationDate = new Date()
|
||||||
|
await DbCommunity.insert(homeCom)
|
||||||
|
logger.info(`home-community inserted successfully:`, homeCom)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(`Federation: Error writing HomeCommunity-Entry: ${err}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const newCommunityUuid = async (): Promise<string> => {
|
||||||
|
let uuid: string
|
||||||
|
let countIds: number
|
||||||
|
do {
|
||||||
|
uuid = uuidv4()
|
||||||
|
countIds = await DbCommunity.count({ where: { communityUuid: uuid } })
|
||||||
|
if (countIds > 0) {
|
||||||
|
logger.info('CommunityUuid creation conflict...')
|
||||||
|
}
|
||||||
|
} while (countIds > 0)
|
||||||
|
return uuid
|
||||||
|
}
|
||||||
|
|||||||
@ -21,9 +21,8 @@ async function main() {
|
|||||||
logger.fatal('Fatal: Database Version incorrect')
|
logger.fatal('Fatal: Database Version incorrect')
|
||||||
throw new Error('Fatal: Database Version incorrect')
|
throw new Error('Fatal: Database Version incorrect')
|
||||||
}
|
}
|
||||||
|
logger.debug(`dhtseed set by CONFIG.FEDERATION_DHT_SEED=${CONFIG.FEDERATION_DHT_SEED}`)
|
||||||
// eslint-disable-next-line no-console
|
logger.info(
|
||||||
console.log(
|
|
||||||
`starting Federation on ${CONFIG.FEDERATION_DHT_TOPIC} ${
|
`starting Federation on ${CONFIG.FEDERATION_DHT_TOPIC} ${
|
||||||
CONFIG.FEDERATION_DHT_SEED ? 'with seed...' : 'without seed...'
|
CONFIG.FEDERATION_DHT_SEED ? 'with seed...' : 'without seed...'
|
||||||
}`,
|
}`,
|
||||||
|
|||||||
@ -22,8 +22,8 @@ const context = {
|
|||||||
|
|
||||||
export const cleanDB = async () => {
|
export const cleanDB = async () => {
|
||||||
// this only works as long we do not have foreign key constraints
|
// this only works as long we do not have foreign key constraints
|
||||||
for (let i = 0; i < entities.length; i++) {
|
for (const entity of entities) {
|
||||||
await resetEntity(entities[i])
|
await resetEntity(entity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -769,6 +769,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
|
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
|
||||||
integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==
|
integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==
|
||||||
|
|
||||||
|
"@types/uuid@^8.3.4":
|
||||||
|
version "8.3.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc"
|
||||||
|
integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==
|
||||||
|
|
||||||
"@types/yargs-parser@*":
|
"@types/yargs-parser@*":
|
||||||
version "21.0.0"
|
version "21.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b"
|
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b"
|
||||||
@ -4138,6 +4143,11 @@ url-parse@^1.5.3:
|
|||||||
querystringify "^2.1.1"
|
querystringify "^2.1.1"
|
||||||
requires-port "^1.0.0"
|
requires-port "^1.0.0"
|
||||||
|
|
||||||
|
uuid@^8.3.2:
|
||||||
|
version "8.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||||
|
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||||
|
|
||||||
v8-compile-cache-lib@^3.0.1:
|
v8-compile-cache-lib@^3.0.1:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
|
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user