feat: introduce WSS checker CI (#2366)

* feat: introduce WSS checker CI

* rename CI step

* specify nodejs

* try

* remove not needed dep

* use npx and fail on failure

* run by dispatch only and improve naming

* fix fail detection
This commit is contained in:
Sasha 2025-04-18 22:51:07 +02:00 committed by GitHub
parent fd2f5b7b8d
commit e45736ff98
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 271 additions and 9 deletions

View File

@ -116,7 +116,10 @@
{
"files": ["**/ci/*.js"],
"rules": {
"no-undef": "off"
"no-undef": "off",
"@typescript-eslint/explicit-member-accessibility": "off",
"@typescript-eslint/no-floating-promises": "off",
"import/no-extraneous-dependencies": "off"
}
}
]

26
.github/workflows/fleet-checker.yml vendored Normal file
View File

@ -0,0 +1,26 @@
on:
workflow_dispatch:
env:
NODE_JS: "20"
jobs:
pre-release:
name: fleet-checker
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch'
steps:
- uses: actions/checkout@v3
with:
repository: waku-org/js-waku
- uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_JS }}
registry-url: "https://registry.npmjs.org"
- run: npm install
- run: npm run build
- run: node --unhandled-rejections=none ./ci/wss-checker.js

View File

@ -1,6 +1,9 @@
on:
workflow_dispatch:
env:
NODE_JS: "20"
jobs:
pre-release:
name: pre-release

192
ci/wss-checker.js Normal file
View File

@ -0,0 +1,192 @@
import cp from "child_process";
import { promisify } from "util";
import { createLightNode } from "@waku/sdk";
const exec = promisify(cp.exec);
class Fleet {
static async create() {
const url = "https://fleets.status.im";
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const fleet = await response.json();
if (!Fleet.isRecordValid(fleet)) {
throw Error("invalid_fleet_record");
}
return new Fleet(fleet);
} catch (error) {
console.error(`Error fetching data from ${url}:`, error);
throw error;
}
}
static isRecordValid(fleet) {
let isValid = true;
if (!fleet.fleets) {
console.error("No fleet records are present.");
isValid = false;
}
if (!fleet.fleets["waku.sandbox"]) {
console.error("No waku.sandbox records are present.");
isValid = false;
} else if (!fleet.fleets["waku.sandbox"]["wss/p2p/waku"]) {
console.error("No waku.sandbox WSS multi-addresses are present.");
isValid = false;
}
if (!fleet.fleets["waku.test"]) {
console.error("No waku.test records are present.");
isValid = false;
} else if (!fleet.fleets["waku.test"]["wss/p2p/waku"]) {
console.error("No waku.test WSS multi-addresses are present.");
isValid = false;
}
if (!isValid) {
console.error(`Got ${JSON.stringify(fleet)}`);
}
return isValid;
}
constructor(fleet) {
this.fleet = fleet;
}
get sandbox() {
return this.fleet.fleets["waku.sandbox"]["wss/p2p/waku"];
}
get test() {
return this.fleet.fleets["waku.test"]["wss/p2p/waku"];
}
}
class ConnectionChecker {
static waku;
static lock = false;
static async checkPlainWss(maddrs) {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "1";
const results = await Promise.all(
maddrs.map((v) => ConnectionChecker.dialPlainWss(v))
);
console.log(
"Raw WSS connection:\n",
results.map(([addr, result]) => `${addr}:\t${result}`).join("\n")
);
return results;
}
static async dialPlainWss(maddr) {
const { domain, port } = ConnectionChecker.parseMaddr(maddr);
return [
maddr,
await ConnectionChecker.spawn(`npx wscat -c wss://${domain}:${port}`)
];
}
static async checkWakuWss(maddrs) {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
const waku = await createLightNode({
defaultBootstrap: false,
libp2p: {
hideWebSocketInfo: true
},
networkConfig: {
clusterId: 42,
shards: [0]
}
});
const results = await Promise.all(
maddrs.map((v) => ConnectionChecker.dialWaku(waku, v))
);
console.log(
"Libp2p WSS connection:\n",
results.map(([addr, result]) => `${addr}:\t${result}`).join("\n")
);
return results;
}
static async dialWaku(waku, maddr) {
try {
await waku.dial(maddr);
return [maddr, "OK"];
} catch (e) {
return [maddr, "FAIL"];
}
}
static parseMaddr(multiaddr) {
const regex = /\/dns4\/([^/]+)\/tcp\/(\d+)/;
const match = multiaddr.match(regex);
if (!match) {
throw new Error(
"Invalid multiaddress format. Expected /dns4/domain/tcp/port pattern."
);
}
return {
domain: match[1],
port: parseInt(match[2], 10)
};
}
static async spawn(command) {
try {
console.info(`Spawning command: ${command}`);
const { stderr } = await exec(command);
return stderr || "OK";
} catch (e) {
return "FAIL";
}
}
}
async function run() {
const fleet = await Fleet.create();
const sandbox = Object.values(fleet.sandbox);
const test = Object.values(fleet.test);
let maddrs = [...sandbox, ...test];
const plainWssResult = await ConnectionChecker.checkPlainWss(maddrs);
const wakuWssResult = await ConnectionChecker.checkWakuWss(maddrs);
const plainWssFail = plainWssResult.some(([_, status]) => status === "FAIL");
const wakuWssFail = wakuWssResult.some(([_, status]) => status === "FAIL");
if (plainWssFail || wakuWssFail) {
process.exit(1);
}
process.exit(0);
}
(async () => {
try {
await run();
} catch (error) {
console.error("Unhandled error:", error);
process.exit(1);
}
})();

48
package-lock.json generated
View File

@ -23,9 +23,6 @@
"packages/build-utils",
"packages/react-native-polyfills"
],
"dependencies": {
"@waku/utils": "^0.0.21"
},
"devDependencies": {
"@size-limit/preset-big-lib": "^11.0.2",
"@typescript-eslint/eslint-plugin": "^6.6.0",
@ -50,7 +47,8 @@
"ts-loader": "^9.5.1",
"ts-node": "^10.9.2",
"typedoc": "^0.25.9",
"typescript": "^5.3.3"
"typescript": "^5.3.3",
"wscat": "^6.0.1"
}
},
"node_modules/@ampproject/remapping": {
@ -41704,6 +41702,48 @@
}
}
},
"node_modules/wscat": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/wscat/-/wscat-6.0.1.tgz",
"integrity": "sha512-a2xnAkRYBQ879s7BuUXJk4k/z22mk+a7l95aVK7GzY5d9DCb//i1PsbRT/Z80EJXHXbvp8VDBK38P0Xv+/ruJA==",
"dev": true,
"license": "MIT",
"dependencies": {
"commander": "^12.1.0",
"https-proxy-agent": "^7.0.5",
"read": "^4.0.0",
"ws": "^8.0.0"
},
"bin": {
"wscat": "bin/wscat"
},
"engines": {
"node": ">=18"
}
},
"node_modules/wscat/node_modules/mute-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz",
"integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==",
"dev": true,
"license": "ISC",
"engines": {
"node": "^18.17.0 || >=20.5.0"
}
},
"node_modules/wscat/node_modules/read": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/read/-/read-4.1.0.tgz",
"integrity": "sha512-uRfX6K+f+R8OOrYScaM3ixPY4erg69f8DN6pgTvMcA9iRc8iDhwrA4m3Yu8YYKsXJgVvum+m8PkRboZwwuLzYA==",
"dev": true,
"license": "ISC",
"dependencies": {
"mute-stream": "^2.0.0"
},
"engines": {
"node": "^18.17.0 || >=20.5.0"
}
},
"node_modules/xcode": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/xcode/-/xcode-3.0.1.tgz",

View File

@ -65,14 +65,12 @@
"ts-loader": "^9.5.1",
"ts-node": "^10.9.2",
"typedoc": "^0.25.9",
"typescript": "^5.3.3"
"typescript": "^5.3.3",
"wscat": "^6.0.1"
},
"lint-staged": {
"*.{ts,js}": [
"eslint --fix"
]
},
"dependencies": {
"@waku/utils": "^0.0.21"
}
}