mirror of
https://github.com/logos-messaging/logos-messaging-js.git
synced 2026-02-07 01:33:07 +00:00
feat: set bootstrap ENR via cli arg
This commit is contained in:
parent
15d8134e5f
commit
5530796678
4
.github/workflows/playwright.yml
vendored
4
.github/workflows/playwright.yml
vendored
@ -30,8 +30,8 @@ jobs:
|
||||
|
||||
- uses: ./.github/actions/npm
|
||||
|
||||
- name: Build browser test environment
|
||||
run: npm run build --workspace=@waku/browser-tests
|
||||
- name: Build entire monorepo
|
||||
run: npm run build
|
||||
|
||||
- name: Run Playwright tests
|
||||
run: npm run test --workspace=@waku/browser-tests
|
||||
|
||||
15
package-lock.json
generated
15
package-lock.json
generated
@ -34343,6 +34343,7 @@
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@playwright/test": "^1.51.1",
|
||||
"@waku/discovery": "^0.0.11",
|
||||
"@waku/sdk": "^0.0.34",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.21.2"
|
||||
@ -34351,6 +34352,7 @@
|
||||
"@types/cors": "^2.8.15",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/node": "^20.10.0",
|
||||
"@waku/discovery": "^0.0.11",
|
||||
"axios": "^1.8.4",
|
||||
"dotenv-flow": "^0.4.0",
|
||||
"esbuild": "^0.21.5",
|
||||
@ -34760,6 +34762,19 @@
|
||||
"undici-types": "~6.19.2"
|
||||
}
|
||||
},
|
||||
"packages/browser-tests/node_modules/@waku/discovery/node_modules/@waku/interfaces": {
|
||||
"version": "0.0.26",
|
||||
"resolved": "https://registry.npmjs.org/@waku/interfaces/-/interfaces-0.0.26.tgz",
|
||||
"integrity": "sha512-YZU4+1j8n7lEKFTz3RTHaNm4Jsv1kurG87G7ZZZwFRuzHjTVizGldI5Nfu8eUemr8dGIBCgadybXqJY/GjsLcA==",
|
||||
"extraneous": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"@waku/proto": "^0.0.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"packages/browser-tests/node_modules/dotenv": {
|
||||
"version": "7.0.0",
|
||||
"dev": true,
|
||||
|
||||
@ -47,7 +47,8 @@ ENV PORT=8080 \
|
||||
NODE_ENV=production \
|
||||
CHROMIUM_NO_SANDBOX=1 \
|
||||
WAKU_CLUSTER_ID=${WAKU_CLUSTER_ID:-} \
|
||||
WAKU_SHARD=${WAKU_SHARD:-}
|
||||
WAKU_SHARD=${WAKU_SHARD:-} \
|
||||
WAKU_ENR_BOOTSTRAP=${WAKU_ENR_BOOTSTRAP:-}
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
|
||||
@ -96,6 +96,20 @@ Waku nodes are automatically created and started when the server launches. Confi
|
||||
- `WAKU_CLUSTER_ID`: Set the cluster ID (default: uses bootstrap configuration)
|
||||
- `WAKU_SHARD`: Set a specific shard (optional)
|
||||
- `WAKU_LIGHTPUSH_NODE`: Specify a preferred lightpush node address (optional)
|
||||
- `WAKU_ENR_BOOTSTRAP`: Specify custom ENR bootstrap peers (comma-separated, optional)
|
||||
|
||||
### Example: Using ENR Bootstrap Peers
|
||||
|
||||
```bash
|
||||
# Via Docker CLI
|
||||
docker run -p 8080:8080 \
|
||||
-e WAKU_ENR_BOOTSTRAP="enr:-MS4QGcHBZAnpu6qNYe_T6TGDCV6c9_3UsXlj5XlXY6QvLCUQKqajqDfs0aKOs7BISJzGxA7TuDzYXap4sP6JYUZ2Y9GAYh2F0dG5ldHOIAAAAAAAAAACEZXRoMpEJZZp0BAAAAf__________gmlkgnY0gmlwhC5QoeSJc2VjcDI1NmsxoQOZxJYJVoTfwo7zEom6U6L5Txrs3H9X0P_XBJbbOZBczYYN1ZHCCdl8" \
|
||||
waku-browser-tests
|
||||
|
||||
# Via Docker entrypoint argument
|
||||
docker run -p 8080:8080 waku-browser-tests \
|
||||
--enr-bootstrap="enr:-MS4QGcHBZAnpu6qNYe_T6TGDCV6c9_3UsXlj5XlXY6QvLCUQKqajqDfs0aKOs7BISJzGxA7TuDzYXap4sP6JYUZ2Y9GAYh2F0dG5ldHOIAAAAAAAAAACEZXRoMpEJZZp0BAAAAf__________gmlkgnY0gmlwhC5QoeSJc2VjcDI1NmsxoQOZxJYJVoTfwo7zEom6U6L5Txrs3H9X0P_XBJbbOZBczYYN1ZHCCdl8"
|
||||
```
|
||||
|
||||
### Example: Dialing to specific peers with the Waku REST API compatible endpoint
|
||||
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
"@types/cors": "^2.8.15",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/node": "^20.10.0",
|
||||
"@waku/discovery": "^0.0.11",
|
||||
"axios": "^1.8.4",
|
||||
"dotenv-flow": "^0.4.0",
|
||||
"esbuild": "^0.21.5",
|
||||
@ -30,6 +31,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@playwright/test": "^1.51.1",
|
||||
"@waku/discovery": "^0.0.11",
|
||||
"@waku/sdk": "^0.0.34",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.21.2"
|
||||
|
||||
@ -1,10 +1,6 @@
|
||||
// For dynamic import of dotenv-flow
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
// Only load dotenv-flow in non-CI environments
|
||||
if (!process.env.CI) {
|
||||
// Need to use .js extension for ES modules
|
||||
// eslint-disable-next-line import/extensions
|
||||
try {
|
||||
await import("dotenv-flow/config.js");
|
||||
} catch (e) {
|
||||
@ -14,35 +10,22 @@ if (!process.env.CI) {
|
||||
|
||||
const EXAMPLE_PORT = process.env.EXAMPLE_PORT || "8080";
|
||||
const BASE_URL = `http://127.0.0.1:${EXAMPLE_PORT}`;
|
||||
// Ignore docker-based tests on CI
|
||||
const TEST_IGNORE = process.env.CI ? ["tests/docker-*.spec.ts"] : [];
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: "./tests",
|
||||
testIgnore: TEST_IGNORE,
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 2 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: "html",
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: BASE_URL,
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: "on-first-retry"
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
|
||||
@ -32,6 +32,11 @@ while [[ $# -gt 0 ]]; do
|
||||
echo "Setting WAKU_LIGHTPUSH_NODE=${WAKU_LIGHTPUSH_NODE}"
|
||||
shift
|
||||
;;
|
||||
--enr-bootstrap=*)
|
||||
export WAKU_ENR_BOOTSTRAP="${1#*=}"
|
||||
echo "Setting WAKU_ENR_BOOTSTRAP=${WAKU_ENR_BOOTSTRAP}"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
# Unknown argument, keep it for the main command
|
||||
break
|
||||
|
||||
@ -1,15 +1,10 @@
|
||||
import { Browser, chromium, Page } from "@playwright/test";
|
||||
|
||||
// Global variable to store the browser and page
|
||||
let browser: Browser | undefined;
|
||||
let page: Page | undefined;
|
||||
|
||||
/**
|
||||
* Initialize browser and load the Waku web app
|
||||
*/
|
||||
export async function initBrowser(appPort: number): Promise<void> {
|
||||
try {
|
||||
// Support sandbox-less mode for containers
|
||||
const launchArgs =
|
||||
process.env.CHROMIUM_NO_SANDBOX === "1"
|
||||
? ["--no-sandbox", "--disable-setuid-sandbox"]
|
||||
@ -26,12 +21,10 @@ export async function initBrowser(appPort: number): Promise<void> {
|
||||
|
||||
page = await browser.newPage();
|
||||
|
||||
// Load the Waku web app
|
||||
await page.goto(`http://localhost:${appPort}/app/index.html`, {
|
||||
waitUntil: "networkidle",
|
||||
});
|
||||
|
||||
// Wait for wakuApi to be available
|
||||
await page.waitForFunction(
|
||||
() => {
|
||||
return window.wakuApi && typeof window.wakuApi.createWakuNode === "function";
|
||||
@ -46,23 +39,14 @@ export async function initBrowser(appPort: number): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current page instance
|
||||
*/
|
||||
export function getPage(): Page | undefined {
|
||||
return page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the page instance (for use by server.ts)
|
||||
*/
|
||||
export function setPage(pageInstance: Page | undefined): void {
|
||||
page = pageInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the browser instance
|
||||
*/
|
||||
export async function closeBrowser(): Promise<void> {
|
||||
if (browser) {
|
||||
await browser.close();
|
||||
|
||||
@ -4,7 +4,6 @@ import { getPage } from "../browser/index.js";
|
||||
|
||||
const router = Router();
|
||||
|
||||
// CORS preflight handlers
|
||||
const corsEndpoints = [
|
||||
"/waku/v1/wait-for-peers",
|
||||
"/waku/v1/dial-peers",
|
||||
@ -22,11 +21,8 @@ corsEndpoints.forEach(endpoint => {
|
||||
});
|
||||
});
|
||||
|
||||
// Node lifecycle is now handled automatically on server start
|
||||
|
||||
// Messaging endpoints
|
||||
|
||||
// Peer management endpoints
|
||||
router.post("/waku/v1/wait-for-peers", createEndpointHandler({
|
||||
methodName: "waitForPeers",
|
||||
validateInput: (body) => [
|
||||
@ -44,7 +40,6 @@ router.post("/waku/v1/dial-peers", createEndpointHandler({
|
||||
validateInput: validators.requirePeerAddrs
|
||||
}));
|
||||
|
||||
// Information endpoints (GET)
|
||||
router.get("/waku/v1/peer-info", createEndpointHandler({
|
||||
methodName: "getPeerInfo",
|
||||
validateInput: validators.noInput
|
||||
@ -65,32 +60,19 @@ router.get("/waku/v1/connection-status", createEndpointHandler({
|
||||
validateInput: validators.noInput
|
||||
}));
|
||||
|
||||
// nwaku v3 lightpush endpoint
|
||||
|
||||
|
||||
router.post("/lightpush/v3/message", createEndpointHandler({
|
||||
methodName: "pushMessageV3",
|
||||
validateInput: (body: any): [string, string, string] => {
|
||||
validateInput: (body: any): [string, string] => {
|
||||
const validatedRequest = validators.requireLightpushV3(body);
|
||||
|
||||
// For v3 API, we pass the base64 payload directly to the method
|
||||
// The WakuHeadless pushMessageV3 method will handle base64 decoding
|
||||
return [
|
||||
validatedRequest.message.contentTopic,
|
||||
validatedRequest.message.payload, // Keep as base64
|
||||
validatedRequest.pubsubTopic
|
||||
validatedRequest.message.payload,
|
||||
];
|
||||
},
|
||||
handleError: errorHandlers.lightpushError,
|
||||
preCheck: async () => {
|
||||
try {
|
||||
console.log("[Server] Waiting for Lightpush peers before sending message...");
|
||||
await getPage()?.evaluate(() => {
|
||||
return window.wakuApi.waitForPeers?.(10000, ["lightpush"] as any);
|
||||
});
|
||||
console.log("[Server] Found Lightpush peers");
|
||||
} catch (e) {
|
||||
console.warn("[Server] No Lightpush peers found:", e);
|
||||
}
|
||||
},
|
||||
transformResult: (result) => {
|
||||
if (result && result.successes && result.successes.length > 0) {
|
||||
console.log("[Server] Message successfully sent via v3 lightpush!");
|
||||
@ -108,7 +90,6 @@ router.post("/lightpush/v3/message", createEndpointHandler({
|
||||
}));
|
||||
|
||||
|
||||
// Custom handler for the execute endpoint since it needs special logic
|
||||
router.post("/waku/v1/execute", async (req, res) => {
|
||||
try {
|
||||
const { functionName, params = [] } = req.body;
|
||||
|
||||
@ -17,20 +17,14 @@ import * as fs from "fs";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const distRoot = path.resolve(__dirname, ".."); // server.js is in dist/src/, so go up to dist/
|
||||
const distRoot = path.resolve(__dirname, "..");
|
||||
const webDir = path.resolve(distRoot, "web");
|
||||
console.log("Setting up static file serving:");
|
||||
console.log("__dirname:", __dirname);
|
||||
console.log("webDir:", webDir);
|
||||
console.log("Files in webDir:", fs.readdirSync(webDir));
|
||||
|
||||
// Serve dynamic index.html with network configuration BEFORE static files
|
||||
app.get("/app/index.html", (_req: Request, res: Response) => {
|
||||
try {
|
||||
const htmlPath = path.join(webDir, "index.html");
|
||||
let htmlContent = fs.readFileSync(htmlPath, "utf8");
|
||||
|
||||
// Build network configuration from environment variables
|
||||
const networkConfig: any = {};
|
||||
if (process.env.WAKU_CLUSTER_ID) {
|
||||
networkConfig.clusterId = parseInt(process.env.WAKU_CLUSTER_ID, 10);
|
||||
@ -39,13 +33,13 @@ app.get("/app/index.html", (_req: Request, res: Response) => {
|
||||
networkConfig.shards = [parseInt(process.env.WAKU_SHARD, 10)];
|
||||
}
|
||||
|
||||
// Get lightpushnode configuration from environment
|
||||
const lightpushNode = process.env.WAKU_LIGHTPUSH_NODE || null;
|
||||
const enrBootstrap = process.env.WAKU_ENR_BOOTSTRAP || null;
|
||||
|
||||
// Inject network configuration and lightpushnode as global variables
|
||||
const configScript = ` <script>
|
||||
window.__WAKU_NETWORK_CONFIG = ${JSON.stringify(networkConfig)};
|
||||
window.__WAKU_LIGHTPUSH_NODE = ${JSON.stringify(lightpushNode)};
|
||||
window.__WAKU_ENR_BOOTSTRAP = ${JSON.stringify(enrBootstrap)};
|
||||
</script>`;
|
||||
const originalPattern = ' <script type="module" src="./index.js"></script>';
|
||||
const replacement = `${configScript}\n <script type="module" src="./index.js"></script>`;
|
||||
@ -60,7 +54,6 @@ app.get("/app/index.html", (_req: Request, res: Response) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Serve static files (excluding index.html which is handled above)
|
||||
app.use("/app", express.static(webDir, { index: false }));
|
||||
|
||||
app.use(wakuRouter);
|
||||
@ -101,31 +94,52 @@ async function startServer(port: number = 3000): Promise<void> {
|
||||
const actualPort = await startAPI(port);
|
||||
await initBrowser(actualPort);
|
||||
|
||||
// Auto-create/start with consistent bootstrap approach
|
||||
try {
|
||||
console.log("Auto-starting node with CLI configuration...");
|
||||
|
||||
// Build network config from environment variables for auto-start
|
||||
const networkConfig: any = { defaultBootstrap: true };
|
||||
if (process.env.WAKU_CLUSTER_ID) {
|
||||
networkConfig.networkConfig = networkConfig.networkConfig || {};
|
||||
networkConfig.networkConfig.clusterId = parseInt(process.env.WAKU_CLUSTER_ID, 10);
|
||||
const hasEnrBootstrap = Boolean(process.env.WAKU_ENR_BOOTSTRAP);
|
||||
const networkConfig: any = {
|
||||
defaultBootstrap: !hasEnrBootstrap,
|
||||
...(hasEnrBootstrap && {
|
||||
discovery: {
|
||||
dns: true,
|
||||
peerExchange: true,
|
||||
peerCache: true
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
console.log(`Bootstrap mode: ${hasEnrBootstrap ? 'ENR-only (defaultBootstrap=false)' : 'default bootstrap (defaultBootstrap=true)'}`);
|
||||
if (hasEnrBootstrap) {
|
||||
console.log(`ENR bootstrap peers: ${process.env.WAKU_ENR_BOOTSTRAP}`);
|
||||
console.log(`Discovery options: peerExchange=true, dns=false, peerCache=true`);
|
||||
}
|
||||
|
||||
networkConfig.networkConfig = {
|
||||
clusterId: process.env.WAKU_CLUSTER_ID ? parseInt(process.env.WAKU_CLUSTER_ID, 10) : 1,
|
||||
numShardsInCluster: 8
|
||||
};
|
||||
|
||||
if (process.env.WAKU_SHARD) {
|
||||
networkConfig.networkConfig = networkConfig.networkConfig || {};
|
||||
networkConfig.networkConfig.shards = [parseInt(process.env.WAKU_SHARD, 10)];
|
||||
delete networkConfig.networkConfig.numShardsInCluster;
|
||||
}
|
||||
|
||||
console.log(`Network config: ${JSON.stringify(networkConfig.networkConfig)}`);
|
||||
|
||||
await getPage()?.evaluate((config) => {
|
||||
return window.wakuApi.createWakuNode(config);
|
||||
}, networkConfig);
|
||||
await getPage()?.evaluate(() => window.wakuApi.startNode());
|
||||
|
||||
// Wait for bootstrap peers to connect
|
||||
await getPage()?.evaluate(() =>
|
||||
window.wakuApi.waitForPeers?.(5000, ["lightpush"] as any),
|
||||
);
|
||||
console.log("Auto-start completed with bootstrap peers");
|
||||
try {
|
||||
await getPage()?.evaluate(() =>
|
||||
window.wakuApi.waitForPeers?.(5000, ["lightpush"] as any),
|
||||
);
|
||||
console.log("Auto-start completed with bootstrap peers");
|
||||
} catch (peerError) {
|
||||
console.log("Auto-start completed (no bootstrap peers found - may be expected with test ENRs)");
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Auto-start failed:", e);
|
||||
}
|
||||
@ -136,10 +150,8 @@ async function startServer(port: number = 3000): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
// Process error handlers to prevent container from crashing
|
||||
process.on("uncaughtException", (error) => {
|
||||
console.error("Uncaught Exception:", error);
|
||||
// Don't exit in production/container environment
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
process.exit(1);
|
||||
}
|
||||
@ -147,35 +159,24 @@ process.on("uncaughtException", (error) => {
|
||||
|
||||
process.on("unhandledRejection", (reason, promise) => {
|
||||
console.error("Unhandled Rejection at:", promise, "reason:", reason);
|
||||
// Don't exit in production/container environment
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
process.on("SIGINT", (async () => {
|
||||
console.log("Received SIGINT, gracefully shutting down...");
|
||||
const gracefulShutdown = async (signal: string) => {
|
||||
console.log(`Received ${signal}, gracefully shutting down...`);
|
||||
try {
|
||||
await closeBrowser();
|
||||
} catch (e) {
|
||||
console.warn("Error closing browser:", e);
|
||||
}
|
||||
process.exit(0);
|
||||
}) as any);
|
||||
};
|
||||
|
||||
process.on("SIGTERM", (async () => {
|
||||
console.log("Received SIGTERM, gracefully shutting down...");
|
||||
try {
|
||||
await closeBrowser();
|
||||
} catch (e) {
|
||||
console.warn("Error closing browser:", e);
|
||||
}
|
||||
process.exit(0);
|
||||
}) as any);
|
||||
process.on("SIGINT", () => gracefulShutdown("SIGINT"));
|
||||
process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
|
||||
|
||||
/**
|
||||
* Parse CLI arguments for cluster, shard, and lightpushnode configuration
|
||||
*/
|
||||
function parseCliArgs() {
|
||||
const args = process.argv.slice(2);
|
||||
let clusterId: number | undefined;
|
||||
@ -213,7 +214,6 @@ if (isMainModule) {
|
||||
const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000;
|
||||
const cliArgs = parseCliArgs();
|
||||
|
||||
// Set global configuration for CLI arguments
|
||||
if (cliArgs.clusterId !== undefined) {
|
||||
process.env.WAKU_CLUSTER_ID = cliArgs.clusterId.toString();
|
||||
console.log(`Using CLI cluster ID: ${cliArgs.clusterId}`);
|
||||
|
||||
@ -1,13 +1,10 @@
|
||||
import { Request, Response } from "express";
|
||||
import { getPage } from "../browser/index.js";
|
||||
|
||||
/**
|
||||
* nwaku v3 Lightpush API interfaces
|
||||
*/
|
||||
export interface LightpushV3Request {
|
||||
pubsubTopic: string;
|
||||
message: {
|
||||
payload: string; // base64 encoded
|
||||
payload: string;
|
||||
contentTopic: string;
|
||||
version: number;
|
||||
};
|
||||
@ -17,7 +14,7 @@ export interface LightpushV3Response {
|
||||
success?: boolean;
|
||||
error?: string;
|
||||
result?: {
|
||||
successes: string[]; // PeerIds converted to strings
|
||||
successes: string[];
|
||||
failures: Array<{
|
||||
error: string;
|
||||
peerId?: string;
|
||||
@ -26,40 +23,23 @@ export interface LightpushV3Response {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for an endpoint handler
|
||||
*/
|
||||
/* eslint-disable no-unused-vars */
|
||||
export interface EndpointConfig<TInput = any, TOutput = any> {
|
||||
/** Name of the method to call on window.wakuApi */
|
||||
methodName: string;
|
||||
/** Optional input validation function - takes request body, returns validated input */
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
validateInput?: (requestBody: any) => TInput;
|
||||
/** Optional transformation of the result before sending response - takes SDK result, returns transformed result */
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
transformResult?: (sdkResult: any) => TOutput;
|
||||
/** Optional custom error handling - takes error, returns response with code and message */
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
handleError?: (caughtError: Error) => { code: number; message: string };
|
||||
/** Optional pre-execution checks */
|
||||
preCheck?: () => Promise<void> | void;
|
||||
/** Whether to log the result (default: true) */
|
||||
logResult?: boolean;
|
||||
}
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
/**
|
||||
* Generic endpoint handler that follows the pattern:
|
||||
* 1. Parse and validate inputs
|
||||
* 2. Call function on WakuHeadless instance via page.evaluate
|
||||
* 3. Wait for result
|
||||
* 4. Log result
|
||||
* 5. Return result or error
|
||||
*/
|
||||
export function createEndpointHandler<TInput = any, TOutput = any>(
|
||||
config: EndpointConfig<TInput, TOutput>
|
||||
) {
|
||||
return async (req: Request, res: Response) => {
|
||||
try {
|
||||
// Step 1: Parse and validate inputs
|
||||
let input: TInput;
|
||||
try {
|
||||
input = config.validateInput ? config.validateInput(req.body) : req.body;
|
||||
@ -70,7 +50,6 @@ export function createEndpointHandler<TInput = any, TOutput = any>(
|
||||
});
|
||||
}
|
||||
|
||||
// Pre-execution checks
|
||||
if (config.preCheck) {
|
||||
try {
|
||||
await config.preCheck();
|
||||
@ -82,7 +61,6 @@ export function createEndpointHandler<TInput = any, TOutput = any>(
|
||||
}
|
||||
}
|
||||
|
||||
// Check browser availability
|
||||
const page = getPage();
|
||||
if (!page) {
|
||||
return res.status(503).json({
|
||||
@ -91,7 +69,6 @@ export function createEndpointHandler<TInput = any, TOutput = any>(
|
||||
});
|
||||
}
|
||||
|
||||
// Step 2 & 3: Call function and wait for result
|
||||
const result = await page.evaluate(
|
||||
({ methodName, params }) => {
|
||||
if (!window.wakuApi) {
|
||||
@ -103,7 +80,6 @@ export function createEndpointHandler<TInput = any, TOutput = any>(
|
||||
throw new Error(`window.wakuApi.${methodName} is not a function`);
|
||||
}
|
||||
|
||||
// Handle both parameterized and parameterless methods
|
||||
if (params === null || params === undefined) {
|
||||
return method.call(window.wakuApi);
|
||||
} else if (Array.isArray(params)) {
|
||||
@ -115,17 +91,14 @@ export function createEndpointHandler<TInput = any, TOutput = any>(
|
||||
{ methodName: config.methodName, params: input }
|
||||
);
|
||||
|
||||
// Step 4: Log result
|
||||
if (config.logResult !== false) {
|
||||
console.log(`[${config.methodName}] Result:`, JSON.stringify(result, null, 2));
|
||||
}
|
||||
|
||||
// Step 5: Transform and return result
|
||||
const finalResult = config.transformResult ? config.transformResult(result) : result;
|
||||
|
||||
res.status(200).json(finalResult);
|
||||
} catch (error: any) {
|
||||
// Custom error handling
|
||||
if (config.handleError) {
|
||||
const errorResponse = config.handleError(error);
|
||||
return res.status(errorResponse.code).json({
|
||||
@ -134,7 +107,6 @@ export function createEndpointHandler<TInput = any, TOutput = any>(
|
||||
});
|
||||
}
|
||||
|
||||
// Default error handling
|
||||
console.error(`[${config.methodName}] Error:`, error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
@ -144,9 +116,6 @@ export function createEndpointHandler<TInput = any, TOutput = any>(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Common validation functions
|
||||
*/
|
||||
export const validators = {
|
||||
requireLightpushV3: (body: any): LightpushV3Request => {
|
||||
if (!body.pubsubTopic || typeof body.pubsubTopic !== "string") {
|
||||
@ -187,9 +156,6 @@ export const validators = {
|
||||
passThrough: (body: any) => body
|
||||
};
|
||||
|
||||
/**
|
||||
* Common error handlers
|
||||
*/
|
||||
export const errorHandlers = {
|
||||
lightpushError: (error: Error) => {
|
||||
if (error.message.includes("size exceeds") || error.message.includes("stream reset")) {
|
||||
|
||||
@ -1,27 +1,33 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import axios from "axios";
|
||||
import { GenericContainer, StartedTestContainer } from "testcontainers";
|
||||
import { createLightNode, waitForRemotePeer, LightNode, Protocols } from "@waku/sdk";
|
||||
import {
|
||||
createLightNode,
|
||||
waitForRemotePeer,
|
||||
LightNode,
|
||||
Protocols,
|
||||
} from "@waku/sdk";
|
||||
|
||||
test.describe.configure({ mode: "serial" });
|
||||
|
||||
let container: StartedTestContainer;
|
||||
let baseUrl = "http://127.0.0.1:8080";
|
||||
let wakuNode: LightNode;
|
||||
let unsubscribe: () => void;
|
||||
|
||||
test.beforeAll(async () => {
|
||||
// Build and run the container once for the suite; reuse across tests
|
||||
const generic = new GenericContainer(
|
||||
"waku-browser-tests:local",
|
||||
).withExposedPorts(8080);
|
||||
const testEnr =
|
||||
"enr:-QEnuEBEAyErHEfhiQxAVQoWowGTCuEF9fKZtXSd7H_PymHFhGJA3rGAYDVSHKCyJDGRLBGsloNbS8AZF33IVuefjOO6BIJpZIJ2NIJpcIQS39tkim11bHRpYWRkcnO4lgAvNihub2RlLTAxLmRvLWFtczMud2FrdXYyLnRlc3Quc3RhdHVzaW0ubmV0BgG73gMAODcxbm9kZS0wMS5hYy1jbi1ob25na29uZy1jLndha3V2Mi50ZXN0LnN0YXR1c2ltLm5ldAYBu94DACm9A62t7AQL4Ef5ZYZosRpQTzFVAB8jGjf1TER2wH-0zBOe1-MDBNLeA4lzZWNwMjU2azGhAzfsxbxyCkgCqq8WwYsVWH7YkpMLnU2Bw5xJSimxKav-g3VkcIIjKA";
|
||||
|
||||
const generic = new GenericContainer("waku-browser-tests:local")
|
||||
.withExposedPorts(8080)
|
||||
.withEnvironment({
|
||||
WAKU_ENR_BOOTSTRAP: testEnr,
|
||||
WAKU_CLUSTER_ID: "1",
|
||||
});
|
||||
|
||||
container = await generic.start();
|
||||
|
||||
console.log("Container started, waiting for initialization...");
|
||||
await new Promise((r) => setTimeout(r, 2000)); // Give container more time to start
|
||||
|
||||
// Get initial container logs for debugging
|
||||
await new Promise((r) => setTimeout(r, 5000));
|
||||
const logs = await container.logs({ tail: 100 });
|
||||
logs.on("data", (b) => process.stdout.write("[container] " + b.toString()));
|
||||
logs.on("error", (err) => console.error("[container log error]", err));
|
||||
@ -29,12 +35,9 @@ test.beforeAll(async () => {
|
||||
const mappedPort = container.getMappedPort(8080);
|
||||
baseUrl = `http://127.0.0.1:${mappedPort}`;
|
||||
|
||||
// Probe readiness - wait for both server and browser
|
||||
let serverReady = false;
|
||||
// let browserReady = false;
|
||||
|
||||
// Wait for server to be ready with more debugging
|
||||
for (let i = 0; i < 60; i++) { // Increased attempts from 40 to 60
|
||||
for (let i = 0; i < 60; i++) {
|
||||
// Increased attempts from 40 to 60
|
||||
try {
|
||||
const res = await axios.get(`${baseUrl}/`, { timeout: 2000 }); // Increased timeout
|
||||
if (res.status === 200) {
|
||||
@ -43,20 +46,19 @@ test.beforeAll(async () => {
|
||||
break;
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (i % 10 === 0) { // Log every 10th attempt
|
||||
if (i % 10 === 0) {
|
||||
console.log(`Attempt ${i + 1}/60 failed:`, error.code || error.message);
|
||||
}
|
||||
}
|
||||
await new Promise((r) => setTimeout(r, 1000)); // Increased wait time from 500ms to 1000ms
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
}
|
||||
|
||||
if (!serverReady) {
|
||||
// Get final container logs for debugging
|
||||
try {
|
||||
const finalLogs = await container.logs({ tail: 50 });
|
||||
console.log("=== Final Container Logs ===");
|
||||
finalLogs.on("data", (b) => console.log(b.toString()));
|
||||
await new Promise(r => setTimeout(r, 1000)); // Give logs time to print
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
} catch (logError) {
|
||||
console.error("Failed to get container logs:", logError);
|
||||
}
|
||||
@ -68,16 +70,6 @@ test.beforeAll(async () => {
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
// Clean up subscription first
|
||||
try {
|
||||
if (typeof unsubscribe === 'function') {
|
||||
unsubscribe();
|
||||
console.log("Filter subscription cleaned up");
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("Filter cleanup had issues:", (error as any).message);
|
||||
}
|
||||
|
||||
if (wakuNode) {
|
||||
console.log("Stopping Waku node...");
|
||||
try {
|
||||
@ -91,158 +83,64 @@ test.afterAll(async () => {
|
||||
if (container) {
|
||||
console.log("Stopping container gracefully...");
|
||||
try {
|
||||
// Give the container a chance to shut down gracefully
|
||||
await container.stop({ timeout: 10000 });
|
||||
console.log("Container stopped successfully");
|
||||
} catch (error) {
|
||||
console.warn("Container stop had issues (expected):", (error as any).message);
|
||||
console.warn(
|
||||
"Container stop had issues (expected):",
|
||||
(error as any).message,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("container: health endpoint", async () => {
|
||||
const res = await axios.get(`${baseUrl}/`);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.data.status).toBe("Waku simulation server is running");
|
||||
});
|
||||
|
||||
// Test that the node is auto-created and auto-started
|
||||
test("container: node auto-started", async () => {
|
||||
// Node should be auto-created and started, so just check peer info
|
||||
const res = await axios.get(`${baseUrl}/waku/v1/peer-info`);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.data.peerId).toBeDefined();
|
||||
expect(res.data.multiaddrs).toBeDefined();
|
||||
});
|
||||
|
||||
test("container: node ready and push", async () => {
|
||||
// Node is auto-created and started with environment variables
|
||||
|
||||
// Wait for Lightpush peers with longer timeout for real network connections
|
||||
console.log("⏳ Waiting for Lightpush peers to connect...");
|
||||
try {
|
||||
await axios.post(`${baseUrl}/waku/v1/wait-for-peers`, {
|
||||
timeoutMs: 30000,
|
||||
protocols: ["lightpush"] // 30 second timeout for real network
|
||||
});
|
||||
console.log("✅ Found Lightpush peers");
|
||||
} catch (e) {
|
||||
console.error("❌ Failed to find Lightpush peers:", e);
|
||||
throw new Error("Failed to connect to Lightpush peers - this should succeed in all environments");
|
||||
}
|
||||
|
||||
// Also wait for Filter peers
|
||||
console.log("⏳ Waiting for Filter peers to connect...");
|
||||
try {
|
||||
await axios.post(`${baseUrl}/waku/v1/wait-for-peers`, {
|
||||
timeoutMs: 30000,
|
||||
protocols: ["filter"] // 30 second timeout for real network
|
||||
});
|
||||
console.log("✅ Found Filter peers");
|
||||
} catch (e) {
|
||||
console.warn("⚠️ No Filter peers found (non-critical):", e);
|
||||
}
|
||||
|
||||
// Test lightpush endpoint - expect it to succeed with real peers
|
||||
console.log("📤 Attempting to push message to Waku network...");
|
||||
const testMessage = "Hello from Docker container test";
|
||||
const base64Payload = btoa(testMessage); // Convert to base64
|
||||
|
||||
const push = await axios.post(`${baseUrl}/lightpush/v3/message`, {
|
||||
pubsubTopic: "/waku/2/default-waku/proto",
|
||||
message: {
|
||||
contentTopic: "/test/1/message/proto",
|
||||
payload: base64Payload,
|
||||
version: 1
|
||||
},
|
||||
});
|
||||
|
||||
// Verify successful push (v3 API returns { success: boolean, result?: SDKProtocolResult })
|
||||
expect(push.status).toBe(200);
|
||||
expect(push.data).toBeDefined();
|
||||
expect(push.data.success).toBe(true);
|
||||
expect(push.data.result).toBeDefined();
|
||||
expect(push.data.result.successes).toBeDefined();
|
||||
expect(push.data.result.successes.length).toBeGreaterThan(0);
|
||||
console.log("✅ Message successfully pushed to Waku network!");
|
||||
|
||||
// Log a clean summary instead of raw JSON
|
||||
const successCount = push.data.result.successes?.length || 0;
|
||||
const failureCount = push.data.result.failures?.length || 0;
|
||||
console.log(`📊 Push Summary: ${successCount} success(es), ${failureCount} failure(s)`);
|
||||
|
||||
if (successCount > 0) {
|
||||
console.log("📤 Successfully sent to peers:");
|
||||
push.data.result.successes.forEach((peerIdString: string, index: number) => {
|
||||
console.log(` ${index + 1}. ${peerIdString}`);
|
||||
});
|
||||
}
|
||||
|
||||
if (failureCount > 0) {
|
||||
console.log("❌ Failed to send to peers:");
|
||||
push.data.result.failures.forEach((failure: { error: string; peerId?: string }, index: number) => {
|
||||
const peerInfo = failure.peerId || 'unknown peer';
|
||||
console.log(` ${index + 1}. ${peerInfo} - ${failure.error}`);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test("cross-network message delivery: SDK light node receives server lightpush", async () => {
|
||||
test.setTimeout(120000); // 2 minute timeout
|
||||
|
||||
const contentTopic = "/test/1/cross-network/proto";
|
||||
const pubsubTopic = "/waku/2/default-waku/proto";
|
||||
const testMessage = "Hello from SDK to Docker server test";
|
||||
|
||||
console.log("🚀 Creating SDK light node with same config as server...");
|
||||
|
||||
// Create light node with same configuration as the docker server
|
||||
wakuNode = await createLightNode({
|
||||
defaultBootstrap: true,
|
||||
discovery: {
|
||||
dns: true,
|
||||
peerExchange: true,
|
||||
peerCache: true,
|
||||
},
|
||||
networkConfig: {
|
||||
clusterId: 1,
|
||||
numShardsInCluster: 8
|
||||
numShardsInCluster: 8,
|
||||
},
|
||||
libp2p: {
|
||||
filterMultiaddrs: false
|
||||
}
|
||||
filterMultiaddrs: false,
|
||||
},
|
||||
});
|
||||
|
||||
await wakuNode.start();
|
||||
console.log("✅ SDK light node started");
|
||||
|
||||
// Wait for filter peer to connect
|
||||
console.log("⏳ Waiting for Filter peers to connect...");
|
||||
await waitForRemotePeer(wakuNode, [Protocols.Filter]);
|
||||
console.log("✅ Connected to Filter peers");
|
||||
await waitForRemotePeer(
|
||||
wakuNode,
|
||||
[Protocols.Filter, Protocols.LightPush],
|
||||
30000,
|
||||
);
|
||||
|
||||
// Set up message subscription
|
||||
console.log("📡 Setting up message subscription...");
|
||||
const messages: any[] = [];
|
||||
const decoder = wakuNode.createDecoder({ contentTopic });
|
||||
|
||||
console.log(`🔍 Subscribing to contentTopic: "${contentTopic}" on pubsubTopic: "${pubsubTopic}"`);
|
||||
|
||||
// Create decoder that matches the server's encoder (using same pattern as server)
|
||||
const decoder = wakuNode.createDecoder({ contentTopic, pubsubTopic });
|
||||
console.log("🔧 Created decoder with pubsubTopic:", decoder.pubsubTopic);
|
||||
|
||||
// Set up message subscription and WAIT for it to be established
|
||||
try {
|
||||
unsubscribe = await wakuNode.filter.subscribe(
|
||||
[decoder],
|
||||
(message) => {
|
||||
console.log("📥 Received message via Filter!");
|
||||
console.log(`📝 Message details: topic=${message.contentTopic}, payload="${new TextDecoder().decode(message.payload)}"`);
|
||||
if (
|
||||
!(await wakuNode.filter.subscribe([decoder], (message) => {
|
||||
messages.push(message);
|
||||
}
|
||||
);
|
||||
console.log("✅ Filter subscription established successfully");
|
||||
}))
|
||||
) {
|
||||
throw new Error("Failed to subscribe to Filter");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("❌ Failed to subscribe to Filter:", error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Give extra time for subscription to propagate to network
|
||||
console.log("⏳ Waiting for subscription to propagate...");
|
||||
await new Promise(r => setTimeout(r, 2000));
|
||||
await new Promise((r) => setTimeout(r, 2000));
|
||||
|
||||
const messagePromise = new Promise<void>((resolve) => {
|
||||
const originalLength = messages.length;
|
||||
@ -256,84 +154,40 @@ test("cross-network message delivery: SDK light node receives server lightpush",
|
||||
checkForMessage();
|
||||
});
|
||||
|
||||
// Server node is auto-created and started
|
||||
console.log("✅ Server node auto-configured and ready");
|
||||
|
||||
// CRITICAL: Wait for server node to find peers BEFORE attempting to send
|
||||
console.log("⏳ Waiting for server to connect to Lightpush peers...");
|
||||
await axios.post(`${baseUrl}/waku/v1/wait-for-peers`, {
|
||||
timeoutMs: 30000,
|
||||
protocols: ["lightpush"]
|
||||
timeoutMs: 30000, // Increased timeout
|
||||
protocols: ["lightpush", "filter"],
|
||||
});
|
||||
console.log("✅ Server connected to Lightpush peers");
|
||||
|
||||
console.log("⏳ Waiting for server to connect to Filter peers...");
|
||||
try {
|
||||
await axios.post(`${baseUrl}/waku/v1/wait-for-peers`, {
|
||||
timeoutMs: 30000,
|
||||
protocols: ["filter"]
|
||||
});
|
||||
console.log("✅ Server connected to Filter peers");
|
||||
} catch (e) {
|
||||
console.warn("⚠️ Server didn't connect to Filter peers:", e);
|
||||
}
|
||||
await new Promise((r) => setTimeout(r, 10000));
|
||||
|
||||
// Give nodes extra time to discover each other and establish proper mesh connectivity
|
||||
console.log("⏳ Allowing time for nodes to discover each other...");
|
||||
await new Promise(r => setTimeout(r, 8000));
|
||||
|
||||
// Debug: Check peer information before sending
|
||||
console.log("🔍 Checking peer connections...");
|
||||
try {
|
||||
const peerInfo = await axios.get(`${baseUrl}/waku/v1/peer-info`);
|
||||
console.log(`📊 Server peer count: ${JSON.stringify(peerInfo.data)}`);
|
||||
} catch (e) {
|
||||
console.warn("⚠️ Could not get peer info:", e);
|
||||
}
|
||||
|
||||
// IMPORTANT: Verify filter is ready before sending
|
||||
console.log("🔍 Verifying filter subscription is active before sending...");
|
||||
|
||||
// Send message via server's lightpush
|
||||
console.log("📤 Sending message via server lightpush...");
|
||||
const base64Payload = btoa(testMessage);
|
||||
|
||||
const pushResponse = await axios.post(`${baseUrl}/lightpush/v3/message`, {
|
||||
pubsubTopic,
|
||||
pubsubTopic: decoder.pubsubTopic,
|
||||
message: {
|
||||
contentTopic,
|
||||
payload: base64Payload,
|
||||
version: 1
|
||||
}
|
||||
version: 1,
|
||||
},
|
||||
});
|
||||
|
||||
expect(pushResponse.status).toBe(200);
|
||||
expect(pushResponse.data.success).toBe(true);
|
||||
console.log("✅ Message sent via server lightpush");
|
||||
|
||||
// Wait for message to be received by SDK node (with longer timeout for network propagation)
|
||||
console.log("⏳ Waiting for message to be received by SDK node...");
|
||||
console.log("💡 Note: Filter messages may take time to propagate through the network...");
|
||||
|
||||
await Promise.race([
|
||||
messagePromise,
|
||||
new Promise((_, reject) =>
|
||||
setTimeout(() => {
|
||||
console.error(`❌ Timeout after 45 seconds. Messages received: ${messages.length}`);
|
||||
reject(new Error("Timeout waiting for message"));
|
||||
}, 45000)
|
||||
)
|
||||
}, 45000),
|
||||
),
|
||||
]);
|
||||
|
||||
// Verify message was received
|
||||
expect(messages).toHaveLength(1);
|
||||
const receivedMessage = messages[0];
|
||||
expect(receivedMessage.contentTopic).toBe(contentTopic);
|
||||
|
||||
// Decode and verify payload
|
||||
const receivedPayload = new TextDecoder().decode(receivedMessage.payload);
|
||||
expect(receivedPayload).toBe(testMessage);
|
||||
|
||||
console.log("🎉 SUCCESS: Message successfully sent from server and received by SDK node!");
|
||||
console.log(`📝 Message content: "${receivedPayload}"`);
|
||||
});
|
||||
|
||||
@ -7,7 +7,6 @@ import { dirname, join } from "path";
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Run this entire file in serial mode to avoid port collisions
|
||||
test.describe.configure({ mode: "serial" });
|
||||
|
||||
test.describe("Server Tests", () => {
|
||||
@ -15,7 +14,6 @@ test.describe("Server Tests", () => {
|
||||
let baseUrl = "http://localhost:3000";
|
||||
|
||||
test.beforeAll(async () => {
|
||||
// Start the server
|
||||
const serverPath = join(__dirname, "..", "dist", "src", "server.js");
|
||||
console.log("Starting server from:", serverPath);
|
||||
|
||||
@ -24,7 +22,6 @@ test.describe("Server Tests", () => {
|
||||
env: { ...process.env, PORT: "3000" }
|
||||
});
|
||||
|
||||
// Log server output
|
||||
serverProcess.stdout?.on("data", (data: Buffer) => {
|
||||
console.log("[Server]", data.toString().trim());
|
||||
});
|
||||
@ -33,11 +30,9 @@ test.describe("Server Tests", () => {
|
||||
console.error("[Server Error]", data.toString().trim());
|
||||
});
|
||||
|
||||
// Wait for server to start
|
||||
console.log("Waiting for server to start...");
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000));
|
||||
|
||||
// Wait for server to be ready
|
||||
let serverReady = false;
|
||||
for (let i = 0; i < 30; i++) {
|
||||
try {
|
||||
@ -73,12 +68,10 @@ test.describe("Server Tests", () => {
|
||||
});
|
||||
|
||||
test("static files are served", async () => {
|
||||
// Check if the main HTML file is accessible
|
||||
const htmlRes = await axios.get(`${baseUrl}/app/index.html`);
|
||||
expect(htmlRes.status).toBe(200);
|
||||
expect(htmlRes.data).toContain("Waku Test Environment");
|
||||
|
||||
// Check if the JavaScript file is accessible
|
||||
const jsRes = await axios.get(`${baseUrl}/app/index.js`);
|
||||
expect(jsRes.status).toBe(200);
|
||||
expect(jsRes.data).toContain("WakuHeadless");
|
||||
@ -86,16 +79,12 @@ test.describe("Server Tests", () => {
|
||||
|
||||
test("Waku node auto-started", async () => {
|
||||
try {
|
||||
// Node should be auto-created and started on server initialization
|
||||
// Check that the peer info endpoint works
|
||||
const infoRes = await axios.get(`${baseUrl}/waku/v1/peer-info`);
|
||||
expect(infoRes.status).toBe(200);
|
||||
expect(infoRes.data.peerId).toBeDefined();
|
||||
expect(infoRes.data.multiaddrs).toBeDefined();
|
||||
} catch (error: any) {
|
||||
// If browser initialization failed, this test will fail - that's expected
|
||||
console.log("Waku node test failed (expected if browser not initialized):", error.response?.data?.error || error.message);
|
||||
// Validation error due to missing required networkConfig field results in 400
|
||||
expect(error.response?.status).toBe(400);
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,33 +1,26 @@
|
||||
// @ts-nocheck
|
||||
import {
|
||||
createLightNode,
|
||||
LightNode,
|
||||
Protocols,
|
||||
NetworkConfig,
|
||||
SDKProtocolResult,
|
||||
CreateNodeOptions,
|
||||
} from "@waku/sdk";
|
||||
import type { PeerId } from "@libp2p/interface";
|
||||
import { bootstrap } from "@libp2p/bootstrap";
|
||||
import { EnrDecoder, TransportProtocol } from "@waku/enr";
|
||||
|
||||
/**
|
||||
* Enhanced SDKProtocolResult with serializable peer IDs for browser/Node.js communication
|
||||
*/
|
||||
export interface SerializableSDKProtocolResult {
|
||||
successes: string[]; // Converted PeerId objects to strings
|
||||
successes: string[];
|
||||
failures: Array<{
|
||||
error: string;
|
||||
peerId?: string; // Converted PeerId to string if available
|
||||
peerId?: string;
|
||||
}>;
|
||||
[key: string]: any; // Allow for other SDK result properties
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert SDKProtocolResult to a serializable format for browser/Node.js communication
|
||||
*/
|
||||
function makeSerializable(result: SDKProtocolResult): SerializableSDKProtocolResult {
|
||||
function makeSerializable(result: any): SerializableSDKProtocolResult {
|
||||
return {
|
||||
...result,
|
||||
successes: result.successes.map((peerId: PeerId) => peerId.toString()),
|
||||
successes: result.successes.map((peerId: any) => peerId.toString()),
|
||||
failures: result.failures.map((failure: any) => ({
|
||||
error: failure.error || failure.toString(),
|
||||
peerId: failure.peerId ? failure.peerId.toString() : undefined
|
||||
@ -35,66 +28,103 @@ function makeSerializable(result: SDKProtocolResult): SerializableSDKProtocolRes
|
||||
};
|
||||
}
|
||||
|
||||
async function convertEnrToMultiaddrs(enrString: string): Promise<string[]> {
|
||||
try {
|
||||
const enr = await EnrDecoder.fromString(enrString);
|
||||
const allMultiaddrs = enr.getAllLocationMultiaddrs();
|
||||
const multiaddrs: string[] = [];
|
||||
|
||||
for (const multiaddr of allMultiaddrs) {
|
||||
const maStr = multiaddr.toString();
|
||||
multiaddrs.push(maStr);
|
||||
}
|
||||
if (multiaddrs.length === 0) {
|
||||
const tcpMultiaddr = enr.getFullMultiaddr(TransportProtocol.TCP);
|
||||
if (tcpMultiaddr) {
|
||||
const tcpStr = tcpMultiaddr.toString();
|
||||
multiaddrs.push(tcpStr);
|
||||
}
|
||||
const udpMultiaddr = enr.getFullMultiaddr(TransportProtocol.UDP);
|
||||
if (udpMultiaddr) {
|
||||
const udpStr = udpMultiaddr.toString();
|
||||
multiaddrs.push(udpStr);
|
||||
}
|
||||
}
|
||||
|
||||
return multiaddrs;
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export class WakuHeadless {
|
||||
waku: LightNode | null;
|
||||
networkConfig: NetworkConfig;
|
||||
lightpushNode: string | null;
|
||||
constructor(networkConfig?: Partial<NetworkConfig>, lightpushNode?: string) {
|
||||
enrBootstrap: string | null;
|
||||
constructor(networkConfig?: Partial<NetworkConfig>, lightpushNode?: string, enrBootstrap?: string) {
|
||||
this.waku = null as unknown as LightNode;
|
||||
// Use provided config or defaults
|
||||
this.networkConfig = this.buildNetworkConfig(networkConfig);
|
||||
this.lightpushNode = lightpushNode || null;
|
||||
this.enrBootstrap = enrBootstrap || null;
|
||||
|
||||
if (this.lightpushNode) {
|
||||
console.log(`Configured preferred lightpush node: ${this.lightpushNode}`);
|
||||
}
|
||||
if (this.enrBootstrap) {
|
||||
console.log(`Configured ENR bootstrap: ${this.enrBootstrap}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build network configuration from provided config or defaults
|
||||
*/
|
||||
private buildNetworkConfig(providedConfig?: Partial<NetworkConfig>): NetworkConfig {
|
||||
// Default configuration
|
||||
let config: NetworkConfig = {
|
||||
clusterId: 1,
|
||||
numShardsInCluster: 8 // Enable auto-sharding by default
|
||||
};
|
||||
private shouldUseCustomBootstrap(options: CreateNodeOptions): boolean {
|
||||
const hasEnr = Boolean(this.enrBootstrap);
|
||||
const isDefaultBootstrap = Boolean(options.defaultBootstrap);
|
||||
const shouldUse = hasEnr && !isDefaultBootstrap;
|
||||
|
||||
return shouldUse;
|
||||
}
|
||||
|
||||
// Apply provided configuration
|
||||
if (providedConfig) {
|
||||
config.clusterId = providedConfig.clusterId ?? config.clusterId;
|
||||
|
||||
// If specific shards are provided, use static sharding
|
||||
if (providedConfig.shards && providedConfig.shards.length > 0) {
|
||||
config.shards = providedConfig.shards;
|
||||
delete config.numShardsInCluster; // Remove auto-sharding when using static shards
|
||||
console.log(`Using static sharding with shard(s) ${providedConfig.shards.join(', ')} on cluster ${config.clusterId}`);
|
||||
} else if (providedConfig.numShardsInCluster) {
|
||||
config.numShardsInCluster = providedConfig.numShardsInCluster;
|
||||
console.log(`Using auto-sharding with ${config.numShardsInCluster} shards on cluster ${config.clusterId}`);
|
||||
} else {
|
||||
console.log(`Using auto-sharding with ${config.numShardsInCluster} shards on cluster ${config.clusterId}`);
|
||||
}
|
||||
} else {
|
||||
console.log(`Using default auto-sharding with ${config.numShardsInCluster} shards on cluster ${config.clusterId}`);
|
||||
private async getBootstrapMultiaddrs(): Promise<string[]> {
|
||||
if (!this.enrBootstrap) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return config;
|
||||
const enrList = this.enrBootstrap.split(',').map(enr => enr.trim());
|
||||
const allMultiaddrs: string[] = [];
|
||||
|
||||
for (const enr of enrList) {
|
||||
const multiaddrs = await convertEnrToMultiaddrs(enr);
|
||||
if (multiaddrs.length > 0) {
|
||||
allMultiaddrs.push(...multiaddrs);
|
||||
}
|
||||
}
|
||||
|
||||
return allMultiaddrs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and start a Waku light node with default bootstrap
|
||||
* Optionally override the network config
|
||||
* @param networkConfig
|
||||
*/
|
||||
async start() {
|
||||
this.waku = await createLightNode({
|
||||
defaultBootstrap: true,
|
||||
networkConfig: this.networkConfig,
|
||||
});
|
||||
await this.waku?.start();
|
||||
private buildNetworkConfig(providedConfig?: Partial<NetworkConfig>): NetworkConfig {
|
||||
const clusterId = providedConfig?.clusterId ?? 1;
|
||||
|
||||
// Check if static sharding is requested through environment or config
|
||||
const staticShards = (providedConfig as any)?.shards;
|
||||
if (staticShards && Array.isArray(staticShards) && staticShards.length > 0) {
|
||||
return {
|
||||
clusterId,
|
||||
shards: staticShards
|
||||
} as NetworkConfig;
|
||||
}
|
||||
|
||||
// Default to auto-sharding
|
||||
const numShardsInCluster = (providedConfig as any)?.numShardsInCluster ?? 8;
|
||||
return {
|
||||
clusterId,
|
||||
numShardsInCluster
|
||||
} as NetworkConfig;
|
||||
}
|
||||
|
||||
|
||||
async pushMessage(
|
||||
contentTopic: string,
|
||||
payload: string,
|
||||
@ -125,14 +155,9 @@ export class WakuHeadless {
|
||||
throw new Error("Lightpush service not available");
|
||||
}
|
||||
|
||||
console.log(`Preparing to send message with contentTopic: ${contentTopic}`);
|
||||
console.log(`Using network config:`, this.networkConfig);
|
||||
|
||||
// Use the WakuNode's createEncoder method which handles auto-sharding properly
|
||||
const encoder = this.waku.createEncoder({ contentTopic });
|
||||
|
||||
console.log("Encoder created with pubsubTopic:", encoder.pubsubTopic);
|
||||
// Send the message using lightpush
|
||||
const result = await lightPush.send(encoder, {
|
||||
payload: processedPayload,
|
||||
timestamp: new Date(),
|
||||
@ -141,28 +166,6 @@ export class WakuHeadless {
|
||||
// Convert to serializable format for cross-context communication
|
||||
const serializableResult = makeSerializable(result);
|
||||
|
||||
// Log a cleaner representation of the lightpush result
|
||||
if (serializableResult.successes && serializableResult.successes.length > 0) {
|
||||
console.log(`✅ Message sent successfully to ${serializableResult.successes.length} peer(s):`);
|
||||
|
||||
// Get current connected peers for better identification
|
||||
const connectedPeers = this.waku.libp2p.getPeers();
|
||||
|
||||
serializableResult.successes.forEach((peerIdString: string, index: number) => {
|
||||
console.log(` ${index + 1}. ${peerIdString}`);
|
||||
});
|
||||
|
||||
// Show connected peer count for context
|
||||
if (connectedPeers.length > 0) {
|
||||
console.log(`📡 Connected to ${connectedPeers.length} total peer(s)`);
|
||||
}
|
||||
|
||||
if (serializableResult.failures && serializableResult.failures.length > 0) {
|
||||
console.log(`❌ Failed to send to ${serializableResult.failures.length} peer(s)`);
|
||||
}
|
||||
} else {
|
||||
console.log("Message send result:", serializableResult);
|
||||
}
|
||||
return serializableResult;
|
||||
} catch (error) {
|
||||
console.error("Error sending message via lightpush:", error);
|
||||
@ -175,7 +178,6 @@ export class WakuHeadless {
|
||||
async pushMessageV3(
|
||||
contentTopic: string,
|
||||
payload: string,
|
||||
pubsubTopic: string,
|
||||
): Promise<SerializableSDKProtocolResult> {
|
||||
if (!this.waku) {
|
||||
throw new Error("Waku node not started");
|
||||
@ -203,32 +205,23 @@ export class WakuHeadless {
|
||||
throw new Error("Lightpush service not available");
|
||||
}
|
||||
|
||||
console.log(`Preparing to send message with contentTopic: ${contentTopic}, pubsubTopic: ${pubsubTopic}`);
|
||||
console.log(`Using network config:`, this.networkConfig);
|
||||
|
||||
// Create encoder with explicit pubsubTopic for v3 API compatibility
|
||||
const encoder = this.waku.createEncoder({ contentTopic, pubsubTopic });
|
||||
const encoder = this.waku.createEncoder({ contentTopic });
|
||||
|
||||
console.log("Encoder created with pubsubTopic:", encoder.pubsubTopic);
|
||||
|
||||
// Send the message using lightpush with preferred peer if configured
|
||||
let result;
|
||||
if (this.lightpushNode) {
|
||||
console.log(`Attempting to send via preferred lightpush node: ${this.lightpushNode}`);
|
||||
try {
|
||||
// Try to send to preferred peer first
|
||||
const preferredPeerId = await this.getPeerIdFromMultiaddr(this.lightpushNode);
|
||||
const preferredPeerId = this.getPeerIdFromMultiaddr(this.lightpushNode);
|
||||
if (preferredPeerId) {
|
||||
result = await lightPush.send(encoder, {
|
||||
payload: processedPayload,
|
||||
timestamp: new Date(),
|
||||
}, { peerId: preferredPeerId });
|
||||
});
|
||||
console.log("✅ Message sent via preferred lightpush node");
|
||||
} else {
|
||||
throw new Error("Could not extract peer ID from preferred node address");
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("Failed to send via preferred node, falling back to default:", error);
|
||||
result = await lightPush.send(encoder, {
|
||||
payload: processedPayload,
|
||||
timestamp: new Date(),
|
||||
@ -244,28 +237,6 @@ export class WakuHeadless {
|
||||
// Convert to serializable format for cross-context communication
|
||||
const serializableResult = makeSerializable(result);
|
||||
|
||||
// Log a cleaner representation of the lightpush result
|
||||
if (serializableResult.successes && serializableResult.successes.length > 0) {
|
||||
console.log(`✅ v3 Message sent successfully to ${serializableResult.successes.length} peer(s):`);
|
||||
|
||||
// Get current connected peers for better identification
|
||||
const connectedPeers = this.waku.libp2p.getPeers();
|
||||
|
||||
serializableResult.successes.forEach((peerIdString: string, index: number) => {
|
||||
console.log(` ${index + 1}. ${peerIdString}`);
|
||||
});
|
||||
|
||||
// Show connected peer count for context
|
||||
if (connectedPeers.length > 0) {
|
||||
console.log(`📡 Connected to ${connectedPeers.length} total peer(s)`);
|
||||
}
|
||||
|
||||
if (serializableResult.failures && serializableResult.failures.length > 0) {
|
||||
console.log(`❌ Failed to send to ${serializableResult.failures.length} peer(s)`);
|
||||
}
|
||||
} else {
|
||||
console.log("v3 Message send result:", serializableResult);
|
||||
}
|
||||
return serializableResult;
|
||||
} catch (error) {
|
||||
console.error("Error sending message via v3 lightpush:", error);
|
||||
@ -283,17 +254,14 @@ export class WakuHeadless {
|
||||
throw new Error("Waku node not started");
|
||||
}
|
||||
|
||||
console.log(`Waiting for peers with protocols ${protocols} (timeout: ${timeoutMs}ms)...`);
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
await this.waku.waitForPeers(protocols, timeoutMs);
|
||||
const elapsed = Date.now() - startTime;
|
||||
console.log(`Found peers after ${elapsed}ms`);
|
||||
|
||||
// Log connected peers
|
||||
const peers = this.waku.libp2p.getPeers();
|
||||
console.log(`Connected to ${peers.length} peers:`, peers.map(p => p.toString()));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
@ -317,42 +285,48 @@ export class WakuHeadless {
|
||||
console.warn("ignore previous waku stop error");
|
||||
}
|
||||
|
||||
// Store the network config from options if provided
|
||||
if (options.networkConfig) {
|
||||
this.networkConfig = options.networkConfig;
|
||||
}
|
||||
|
||||
console.log("Creating Waku node with options:", JSON.stringify(options, null, 2));
|
||||
console.log("Using network config:", JSON.stringify(this.networkConfig, null, 2));
|
||||
|
||||
// Configure for real network connectivity
|
||||
const createOptions = {
|
||||
...options,
|
||||
// Always use our stored network config
|
||||
networkConfig: this.networkConfig,
|
||||
libp2p: {
|
||||
...options.libp2p,
|
||||
filterMultiaddrs: false,
|
||||
connectionManager: {
|
||||
minConnections: 1,
|
||||
maxConnections: 50,
|
||||
connectionGater: {
|
||||
// Allow all connections
|
||||
denyDialPeer: () => false,
|
||||
denyDialMultiaddr: () => false,
|
||||
denyInboundConnection: () => false,
|
||||
denyOutboundConnection: () => false,
|
||||
denyInboundEncryptedConnection: () => false,
|
||||
denyOutboundEncryptedConnection: () => false,
|
||||
denyInboundUpgradedConnection: () => false,
|
||||
denyOutboundUpgradedConnection: () => false,
|
||||
},
|
||||
let libp2pConfig: any = {
|
||||
...options.libp2p,
|
||||
filterMultiaddrs: false,
|
||||
connectionManager: {
|
||||
minConnections: 1,
|
||||
maxConnections: 50,
|
||||
connectionGater: {
|
||||
denyDialPeer: () => false,
|
||||
denyDialMultiaddr: () => false,
|
||||
denyInboundConnection: () => false,
|
||||
denyOutboundConnection: () => false,
|
||||
denyInboundEncryptedConnection: () => false,
|
||||
denyOutboundEncryptedConnection: () => false,
|
||||
denyInboundUpgradedConnection: () => false,
|
||||
denyOutboundUpgradedConnection: () => false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (this.enrBootstrap && this.shouldUseCustomBootstrap(options)) {
|
||||
const multiaddrs = await this.getBootstrapMultiaddrs();
|
||||
|
||||
if (multiaddrs.length > 0) {
|
||||
libp2pConfig.peerDiscovery = [
|
||||
bootstrap({ list: multiaddrs }),
|
||||
...(options.libp2p?.peerDiscovery || [])
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
const createOptions = {
|
||||
...options,
|
||||
networkConfig: this.networkConfig,
|
||||
libp2p: libp2pConfig,
|
||||
};
|
||||
|
||||
this.waku = await createLightNode(createOptions);
|
||||
console.log("Waku node created successfully");
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
@ -360,11 +334,8 @@ export class WakuHeadless {
|
||||
if (!this.waku) {
|
||||
throw new Error("Waku node not created");
|
||||
}
|
||||
console.log("Starting Waku node...");
|
||||
await this.waku.start();
|
||||
console.log("Waku node started, peer ID:", this.waku.libp2p.peerId.toString());
|
||||
|
||||
// If a preferred lightpush node is configured, dial it
|
||||
if (this.lightpushNode) {
|
||||
await this.dialPreferredLightpushNode();
|
||||
}
|
||||
@ -372,69 +343,22 @@ export class WakuHeadless {
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Dial the preferred lightpush node if configured
|
||||
*/
|
||||
private async dialPreferredLightpushNode() {
|
||||
if (!this.waku || !this.lightpushNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`Dialing preferred lightpush node: ${this.lightpushNode}`);
|
||||
await this.waku.dial(this.lightpushNode);
|
||||
console.log(`Successfully connected to preferred lightpush node: ${this.lightpushNode}`);
|
||||
} catch (error) {
|
||||
console.warn(`Failed to dial preferred lightpush node ${this.lightpushNode}:`, error);
|
||||
// Don't throw error - fallback to default peer discovery
|
||||
} catch {
|
||||
// Ignore dial errors
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract peer ID from multiaddr string
|
||||
*/
|
||||
private async getPeerIdFromMultiaddr(multiaddr: string): Promise<any | null> {
|
||||
if (!this.waku) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// Check if this peer is already connected
|
||||
const connectedPeers = this.waku.libp2p.getPeers();
|
||||
|
||||
// Try to match by the multiaddr - this is a simplified approach
|
||||
// In a real implementation, you'd parse the multiaddr to extract the peer ID
|
||||
for (const peerId of connectedPeers) {
|
||||
try {
|
||||
const peerInfo = await this.waku.libp2p.peerStore.get(peerId);
|
||||
for (const addr of peerInfo.addresses) {
|
||||
if (addr.multiaddr.toString().includes(multiaddr.split('/')[2])) {
|
||||
console.log(`Found matching peer ID for ${multiaddr}: ${peerId.toString()}`);
|
||||
return peerId;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Continue searching
|
||||
}
|
||||
}
|
||||
|
||||
// If not found, try to extract from multiaddr format
|
||||
// Format: /ip4/x.x.x.x/tcp/port/p2p/peerID
|
||||
const parts = multiaddr.split('/');
|
||||
const p2pIndex = parts.indexOf('p2p');
|
||||
if (p2pIndex !== -1 && p2pIndex + 1 < parts.length) {
|
||||
const peerIdString = parts[p2pIndex + 1];
|
||||
console.log(`Extracted peer ID from multiaddr: ${peerIdString}`);
|
||||
// For now, return as string - the actual implementation might need proper PeerId construction
|
||||
return peerIdString;
|
||||
}
|
||||
|
||||
console.warn(`Could not extract peer ID from multiaddr: ${multiaddr}`);
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.warn("Error extracting peer ID from multiaddr:", error);
|
||||
return null;
|
||||
}
|
||||
private getPeerIdFromMultiaddr(multiaddr: string): string | null {
|
||||
const parts = multiaddr.split('/');
|
||||
const p2pIndex = parts.indexOf('p2p');
|
||||
return (p2pIndex !== -1 && p2pIndex + 1 < parts.length) ? parts[p2pIndex + 1] : null;
|
||||
}
|
||||
|
||||
async stopNode() {
|
||||
@ -488,9 +412,6 @@ export class WakuHeadless {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available protocols from connected peers
|
||||
*/
|
||||
getAvailablePeerProtocols() {
|
||||
if (!this.waku) {
|
||||
throw new Error("Waku node not started");
|
||||
@ -500,16 +421,12 @@ export class WakuHeadless {
|
||||
const libp2p = this.waku.libp2p;
|
||||
const availableProtocols = new Set<string>();
|
||||
|
||||
// Get protocols from our own node
|
||||
const ownProtocols = Array.from(libp2p.getProtocols());
|
||||
const ownProtocols = Array.from(libp2p.getProtocols());
|
||||
ownProtocols.forEach(p => availableProtocols.add(p));
|
||||
|
||||
// Try to get protocols from connected peers
|
||||
if (libp2p.components && libp2p.components.connectionManager) {
|
||||
if (libp2p.components && libp2p.components.connectionManager) {
|
||||
const connections = libp2p.components.connectionManager.getConnections();
|
||||
connections.forEach((conn: any) => {
|
||||
// Note: Getting peer protocols might require additional libp2p methods
|
||||
// For now, we'll just log the connection info
|
||||
console.log(`Peer ${conn.remotePeer.toString()} connected via ${conn.remoteAddr.toString()}`);
|
||||
});
|
||||
}
|
||||
@ -529,9 +446,7 @@ export class WakuHeadless {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get detailed peer connection status for debugging
|
||||
*/
|
||||
|
||||
getPeerConnectionStatus() {
|
||||
if (!this.waku) {
|
||||
throw new Error("Waku node not started");
|
||||
@ -540,18 +455,15 @@ export class WakuHeadless {
|
||||
try {
|
||||
const libp2p = this.waku.libp2p;
|
||||
|
||||
// Basic info that should always be available
|
||||
const basicInfo = {
|
||||
const basicInfo: any = {
|
||||
peerId: libp2p.peerId.toString(),
|
||||
listenAddresses: libp2p.getMultiaddrs().map((a: any) => a.toString()),
|
||||
protocols: Array.from(libp2p.getProtocols()),
|
||||
networkConfig: this.networkConfig,
|
||||
// Add debug info about libp2p
|
||||
libp2pKeys: Object.keys(libp2p),
|
||||
libp2pType: typeof libp2p,
|
||||
};
|
||||
|
||||
// Try to get connection info if available
|
||||
try {
|
||||
if (libp2p.components && libp2p.components.connectionManager) {
|
||||
const connectionManager = libp2p.components.connectionManager;
|
||||
@ -570,27 +482,19 @@ export class WakuHeadless {
|
||||
basicInfo.connectionError = `Connection manager error: ${connError instanceof Error ? connError.message : String(connError)}`;
|
||||
}
|
||||
|
||||
// Try to get peer store info if available
|
||||
try {
|
||||
if (libp2p.peerStore) {
|
||||
const peerStore = libp2p.peerStore;
|
||||
if (typeof peerStore.getPeers === 'function') {
|
||||
const peers = Array.from(peerStore.getPeers()).map((peerId: any) => peerId.toString());
|
||||
basicInfo.peers = peers;
|
||||
} else {
|
||||
basicInfo.peers = [];
|
||||
basicInfo.peerError = `peerStore.getPeers is not a function`;
|
||||
}
|
||||
if (typeof libp2p.getPeers === 'function') {
|
||||
const peers = libp2p.getPeers().map((peerId: any) => peerId.toString());
|
||||
basicInfo.peers = peers;
|
||||
} else {
|
||||
basicInfo.peers = [];
|
||||
basicInfo.peerError = `No peerStore found`;
|
||||
basicInfo.peerError = `libp2p.getPeers is not a function`;
|
||||
}
|
||||
} catch (peerError) {
|
||||
basicInfo.peers = [];
|
||||
basicInfo.peerError = `Peer store error: ${peerError instanceof Error ? peerError.message : String(peerError)}`;
|
||||
basicInfo.peerError = `Peer error: ${peerError instanceof Error ? peerError.message : String(peerError)}`;
|
||||
}
|
||||
|
||||
// Try to check if started
|
||||
try {
|
||||
if (libp2p.status) {
|
||||
basicInfo.isStarted = libp2p.status;
|
||||
@ -612,22 +516,19 @@ export class WakuHeadless {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Expose a singleton instance on window for Playwright to use
|
||||
(() => {
|
||||
try {
|
||||
console.log("Initializing WakuHeadless...");
|
||||
|
||||
// Check for global network configuration set by server
|
||||
const globalNetworkConfig = (window as any).__WAKU_NETWORK_CONFIG;
|
||||
|
||||
// Check for global lightpushnode configuration set by server
|
||||
const globalLightpushNode = (window as any).__WAKU_LIGHTPUSH_NODE;
|
||||
const globalEnrBootstrap = (window as any).__WAKU_ENR_BOOTSTRAP;
|
||||
|
||||
const instance = new WakuHeadless(globalNetworkConfig, globalLightpushNode);
|
||||
const instance = new WakuHeadless(globalNetworkConfig, globalLightpushNode, globalEnrBootstrap);
|
||||
|
||||
// @ts-ignore - will add proper typings in global.d.ts
|
||||
(window as any).wakuApi = instance;
|
||||
console.log(
|
||||
"WakuHeadless initialized successfully:",
|
||||
@ -635,7 +536,6 @@ export class WakuHeadless {
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error initializing WakuHeadless:", error);
|
||||
// Set a fallback to help with debugging
|
||||
(window as any).wakuApi = {
|
||||
start: () =>
|
||||
Promise.reject(new Error("WakuHeadless failed to initialize")),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user