diff --git a/package-lock.json b/package-lock.json index d138588d..2bc9e684 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,6 +42,7 @@ "next-intl": "3.1.4", "next-themes": "^0.2.1", "nodemailer": "^6.9.9", + "p-limit": "^6.1.0", "pug": "^3.0.3", "qrcode.react": "^3.1.0", "react": "18.2.0", @@ -5619,6 +5620,33 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-changed-files/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-changed-files/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/jest-circus": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", @@ -5662,6 +5690,21 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/jest-circus/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/jest-circus/node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -5682,6 +5725,18 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, + "node_modules/jest-circus/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/jest-cli": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", @@ -6366,6 +6421,33 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-runner/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-runner/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/jest-runtime": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", @@ -7529,15 +7611,14 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.1.0.tgz", + "integrity": "sha512-H0jc0q1vOzlEk0TqAKXKZxdl7kX3OFUzCnNVUnq5Pc3DGo0kpeaMuPqxQn235HibwBEb0/pm9dgKTjXy66fBkg==", "dependencies": { - "yocto-queue": "^0.1.0" + "yocto-queue": "^1.1.1" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -9865,12 +9946,11 @@ } }, "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", + "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", "engines": { - "node": ">=10" + "node": ">=12.20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" diff --git a/package.json b/package.json index 84598532..009456f5 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "next-intl": "3.1.4", "next-themes": "^0.2.1", "nodemailer": "^6.9.9", + "p-limit": "^6.1.0", "pug": "^3.0.3", "qrcode.react": "^3.1.0", "react": "18.2.0", @@ -105,4 +106,4 @@ "prisma": { "seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts" } -} \ No newline at end of file +} diff --git a/src/server/api/routers/networkRouter.ts b/src/server/api/routers/networkRouter.ts index b4fb6b0e..62c05a45 100644 --- a/src/server/api/routers/networkRouter.ts +++ b/src/server/api/routers/networkRouter.ts @@ -78,7 +78,6 @@ export const networkRouter = createTRPCRouter({ if (input.central) { return await ztController.central_network_detail(ctx, input.nwid, input.central); } - // First, retrieve the network with organization details let networkFromDatabase = await ctx.prisma.network.findUnique({ where: { @@ -185,7 +184,6 @@ export const networkRouter = createTRPCRouter({ } // check if there is other network using same routes and return a notification const targetIPs = ztControllerResponse.network.routes.map((route) => route.target); - interface DuplicateRoutes { authorId: string; routes: RoutesEntity[]; @@ -223,6 +221,7 @@ export const networkRouter = createTRPCRouter({ // Convert the map back to an array of merged members const mergedMembers = [...mergedMembersMap.values()]; + // Construct the final response object return { network: { diff --git a/src/server/api/services/memberService.ts b/src/server/api/services/memberService.ts index 8184f4da..7ecbe94d 100644 --- a/src/server/api/services/memberService.ts +++ b/src/server/api/services/memberService.ts @@ -1,7 +1,7 @@ import { UserContext } from "~/types/ctx"; import { MemberEntity, Peers } from "~/types/local/member"; -import * as ztController from "~/utils/ztApi"; import { determineConnectionStatus } from "../utils/memberUtils"; +import * as ztController from "~/utils/ztApi"; import { prisma } from "~/server/db"; import { sendWebhook } from "~/utils/webhook"; import { HookType, MemberJoined } from "~/types/webhooks"; @@ -21,19 +21,23 @@ export const syncMemberPeersAndStatus = async ( ztMembers: MemberEntity[], ) => { if (ztMembers.length === 0) return []; + // get peers + const controllerPeers = await ztController.peers(ctx); + const updatedMembers = await Promise.all( ztMembers.map(async (ztMember) => { // TODO currently there is no way to distinguish peers by network id, so we have to fetch all peers // this will make the node active in all networks it is part of if it is active in one of them. // Should open a issue at ZeroTier - const peers = await ztController.peer(ctx, ztMember.address).catch(() => null); + const peers = controllerPeers.filter( + (peer) => peer.address === ztMember.address, + )[0]; // Retrieve the member from the database const dbMember = await retrieveActiveMemberFromDatabase(nwid, ztMember.id); // Find the active preferred path in the peers object const activePreferredPath = findActivePreferredPeerPath(peers); - const { physicalAddress, ...restOfDbMembers } = dbMember || {}; // Merge the data from the database with the data from Controller @@ -41,7 +45,7 @@ export const syncMemberPeersAndStatus = async ( ...restOfDbMembers, ...ztMember, physicalAddress: activePreferredPath?.address ?? physicalAddress, - peers, + peers: peers || {}, } as MemberEntity; // Update the connection status @@ -98,7 +102,7 @@ export const syncMemberPeersAndStatus = async ( * @param peers - The peers object containing paths. * @returns The active preferred path, or undefined if not found. */ -const findActivePreferredPeerPath = (peers: Peers) => { +const findActivePreferredPeerPath = (peers: Peers | null) => { if (!peers || typeof peers !== "object" || !Array.isArray(peers.paths)) { return null; } diff --git a/src/types/local/member.d.ts b/src/types/local/member.d.ts index e423a732..c0c92ab8 100644 --- a/src/types/local/member.d.ts +++ b/src/types/local/member.d.ts @@ -67,22 +67,16 @@ interface CentralMemberConfig { } export interface Peers { - active: boolean; address: string; isBonded: boolean; latency: number; - lastReceive: number; - lastSend: number; - localSocket?: number; paths?: Paths[]; role: string; version: string; - physicalAddress: string; + physicalAddress?: string; versionMajor: number; versionMinor: number; versionRev: number; - preferred: boolean; - trustedPathId: number; } export interface Paths { diff --git a/src/utils/ztApi.ts b/src/utils/ztApi.ts index f64c7fa1..e899ca6d 100644 --- a/src/utils/ztApi.ts +++ b/src/utils/ztApi.ts @@ -625,7 +625,7 @@ export const member_details = async ( // Get all peers // https://docs.zerotier.com/service/v1/#operation/getPeers -export const peers = async (ctx: UserContext): Promise => { +export const peers = async (ctx: UserContext): Promise => { // get headers based on local or central api const { localControllerUrl } = await getOptions(ctx, false); @@ -635,7 +635,7 @@ export const peers = async (ctx: UserContext): Promise => { try { const response: AxiosResponse = await axios.get(addr, { headers }); - return response.data as ZTControllerGetPeer; + return response.data as ZTControllerGetPeer[]; } catch (error) { const message = `${error} (peers)`; throw new APIError(message, error as AxiosError);