diff --git a/.eslintrc.json b/.eslintrc.json index 52ef66a0f5..af44695f92 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -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" } } ] diff --git a/.github/workflows/fleet-checker.yml b/.github/workflows/fleet-checker.yml new file mode 100644 index 0000000000..4d5427ec07 --- /dev/null +++ b/.github/workflows/fleet-checker.yml @@ -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 diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index f626daaf75..e0d7970768 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -1,6 +1,9 @@ on: workflow_dispatch: +env: + NODE_JS: "20" + jobs: pre-release: name: pre-release diff --git a/ci/wss-checker.js b/ci/wss-checker.js new file mode 100644 index 0000000000..a5375593d9 --- /dev/null +++ b/ci/wss-checker.js @@ -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); + } +})(); diff --git a/package-lock.json b/package-lock.json index e6df9fbf92..649e2211fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index c47db78759..2edb1d6371 100644 --- a/package.json +++ b/package.json @@ -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" } }