mirror of
https://github.com/logos-messaging/js-waku.git
synced 2026-01-04 06:43:12 +00:00
Compare commits
No commits in common. "master" and "v0.2.0" have entirely different histories.
121
.cspell.json
121
.cspell.json
@ -1,165 +1,73 @@
|
||||
{
|
||||
"version": "0.2",
|
||||
"version": "0.1",
|
||||
"$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/master/cspell.schema.json",
|
||||
"language": "en",
|
||||
"words": [
|
||||
"abortable",
|
||||
"acks",
|
||||
"Addrs",
|
||||
"ahadns",
|
||||
"Alives",
|
||||
"alphabeta",
|
||||
"arrayify",
|
||||
"Arraylike",
|
||||
"asym",
|
||||
"autoshard",
|
||||
"autosharding",
|
||||
"backoff",
|
||||
"backoffs",
|
||||
"bitauth",
|
||||
"bitjson",
|
||||
"bitauth",
|
||||
"bufbuild",
|
||||
"chainsafe",
|
||||
"cimg",
|
||||
"cipherparams",
|
||||
"ciphertext",
|
||||
"circleci",
|
||||
"circom",
|
||||
"codecov",
|
||||
"codegen",
|
||||
"commitlint",
|
||||
"cooldown",
|
||||
"dependabot",
|
||||
"dialable",
|
||||
"dingpu",
|
||||
"discv",
|
||||
"Dlazy",
|
||||
"dnsaddr",
|
||||
"Dockerode",
|
||||
"Dout",
|
||||
"Dscore",
|
||||
"ecies",
|
||||
"editorconfig",
|
||||
"Encrypters",
|
||||
"enr",
|
||||
"enrs",
|
||||
"enrtree",
|
||||
"ephem",
|
||||
"esnext",
|
||||
"ethersproject",
|
||||
"execa",
|
||||
"exponentiate",
|
||||
"extip",
|
||||
"fanout",
|
||||
"floodsub",
|
||||
"fontsource",
|
||||
"globby",
|
||||
"gossipsub",
|
||||
"hackathons",
|
||||
"huilong",
|
||||
"iasked",
|
||||
"ihave",
|
||||
"ihaves",
|
||||
"ineed",
|
||||
"IPAM",
|
||||
"ipfs",
|
||||
"isready",
|
||||
"iwant",
|
||||
"jdev",
|
||||
"jswaku",
|
||||
"kdfparams",
|
||||
"keccak",
|
||||
"keypair",
|
||||
"lamport",
|
||||
"lastpub",
|
||||
"libauth",
|
||||
"libp",
|
||||
"lightpush",
|
||||
"LINEA",
|
||||
"livechat",
|
||||
"Merkle",
|
||||
"mkdir",
|
||||
"mplex",
|
||||
"multiaddr",
|
||||
"multiaddresses",
|
||||
"multiaddrs",
|
||||
"multicodec",
|
||||
"multicodecs",
|
||||
"multiformats",
|
||||
"multihashes",
|
||||
"mplex",
|
||||
"muxed",
|
||||
"muxer",
|
||||
"muxers",
|
||||
"mvps",
|
||||
"nodekey",
|
||||
"nwaku",
|
||||
"opendns",
|
||||
"peerhave",
|
||||
"portfinder",
|
||||
"prettierignore",
|
||||
"proto",
|
||||
"protobuf",
|
||||
"protoc",
|
||||
"proxiable",
|
||||
"reactjs",
|
||||
"recid",
|
||||
"rlnrelay",
|
||||
"rlnv",
|
||||
"roadmap",
|
||||
"sandboxed",
|
||||
"scanf",
|
||||
"secio",
|
||||
"seckey",
|
||||
"secp",
|
||||
"sharded",
|
||||
"sscanf",
|
||||
"Startable",
|
||||
"staticnode",
|
||||
"statusim",
|
||||
"statusteam",
|
||||
"submodule",
|
||||
"submodules",
|
||||
"supercrypto",
|
||||
"transpiled",
|
||||
"typedoc",
|
||||
"undialable",
|
||||
"unencrypted",
|
||||
"unhandle",
|
||||
"unmarshal",
|
||||
"unmount",
|
||||
"unmounts",
|
||||
"unsubscription",
|
||||
"untracked",
|
||||
"upgrader",
|
||||
"vacp",
|
||||
"varint",
|
||||
"viem",
|
||||
"vkey",
|
||||
"wagmi",
|
||||
"waku",
|
||||
"wakuconnect",
|
||||
"wakunode",
|
||||
"wakuorg",
|
||||
"wakuv",
|
||||
"webfonts",
|
||||
"weboko",
|
||||
"websockets",
|
||||
"wifi",
|
||||
"WTNS",
|
||||
"xsalsa20",
|
||||
"zerokit",
|
||||
"Привет",
|
||||
"مرحبا"
|
||||
],
|
||||
"flagWords": [
|
||||
"pubSub: pubsub",
|
||||
"pubSubTopics: pubsubTopics",
|
||||
"pubSubTopic: pubsubTopic",
|
||||
"PubSub: Pubsub",
|
||||
"PubSubTopics: PubsubTopics",
|
||||
"PubSubTopic: PubsubTopic",
|
||||
"DefaultPubSubTopic: DefaultPubsubTopic"
|
||||
"websockets"
|
||||
],
|
||||
"flagWords": [],
|
||||
"ignorePaths": [
|
||||
"package.json",
|
||||
"package-lock.json",
|
||||
@ -168,23 +76,6 @@
|
||||
"node_modules/**",
|
||||
"build",
|
||||
"gen",
|
||||
"proto",
|
||||
"*.spec.ts",
|
||||
"*.log",
|
||||
"CHANGELOG.md"
|
||||
],
|
||||
"patterns": [
|
||||
{
|
||||
"name": "import",
|
||||
"pattern": "/import .*/"
|
||||
},
|
||||
{
|
||||
"name": "multiaddr",
|
||||
"pattern": "//dns4/.*/"
|
||||
}
|
||||
],
|
||||
"ignoreRegExpList": [
|
||||
"import",
|
||||
"multiaddr"
|
||||
"proto"
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,24 +0,0 @@
|
||||
**/node_modules
|
||||
**/.git
|
||||
**/.vscode
|
||||
**/dist
|
||||
**/build
|
||||
**/.DS_Store
|
||||
**/.env*
|
||||
**/*.log
|
||||
|
||||
# Exclude all packages except browser-tests and browser-container
|
||||
packages/discovery/
|
||||
packages/tests/
|
||||
packages/utils/
|
||||
packages/sds/
|
||||
packages/sdk/
|
||||
packages/relay/
|
||||
packages/rln/
|
||||
packages/message-hash/
|
||||
packages/proto/
|
||||
packages/enr/
|
||||
packages/interfaces/
|
||||
packages/message-encryption/
|
||||
packages/core/
|
||||
packages/build-utils/
|
||||
106
.eslintrc.json
106
.eslintrc.json
@ -1,130 +1,42 @@
|
||||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": ["./tsconfig.json"]
|
||||
},
|
||||
"parserOptions": { "project": "./tsconfig.dev.json" },
|
||||
"env": { "es6": true },
|
||||
"ignorePatterns": [
|
||||
"node_modules",
|
||||
"build",
|
||||
"coverage",
|
||||
"proto"
|
||||
],
|
||||
"ignorePatterns": ["node_modules", "build", "coverage", "proto"],
|
||||
"plugins": ["import", "eslint-comments", "functional"],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:eslint-comments/recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:import/typescript",
|
||||
"plugin:prettier/recommended"
|
||||
"prettier",
|
||||
"prettier/@typescript-eslint"
|
||||
],
|
||||
"globals": { "BigInt": true, "console": true, "WebAssembly": true },
|
||||
"rules": {
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"paths": [{
|
||||
"name": "debug",
|
||||
"message": "The usage of 'debug' package directly is disallowed. Please use the custom logger from @waku/utils instead."
|
||||
}]
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/explicit-member-accessibility": "error",
|
||||
"@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],
|
||||
"prettier/prettier": [
|
||||
"error",
|
||||
{
|
||||
"trailingComma": "none"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": ["error"],
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"eslint-comments/disable-enable-pair": [
|
||||
"error",
|
||||
{
|
||||
"allowWholeFile": true
|
||||
}
|
||||
{ "allowWholeFile": true }
|
||||
],
|
||||
"eslint-comments/no-unused-disable": "error",
|
||||
"import/order": [
|
||||
"error",
|
||||
{
|
||||
"newlines-between": "always",
|
||||
"alphabetize": {
|
||||
"order": "asc"
|
||||
}
|
||||
}
|
||||
{ "newlines-between": "always", "alphabetize": { "order": "asc" } }
|
||||
],
|
||||
"no-constant-condition": ["error", { "checkLoops": false }],
|
||||
"import/no-extraneous-dependencies": [
|
||||
"error",
|
||||
{
|
||||
"devDependencies": [
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts",
|
||||
"**/tests/**",
|
||||
"**/rollup.config.js",
|
||||
"**/playwright.config.ts",
|
||||
"**/.eslintrc.cjs",
|
||||
"**/karma.conf.cjs"
|
||||
]
|
||||
}
|
||||
],
|
||||
"sort-imports": [
|
||||
"error",
|
||||
{ "ignoreDeclarationSort": true, "ignoreCase": true }
|
||||
],
|
||||
"no-console": "error",
|
||||
"@typescript-eslint/no-floating-promises": "error",
|
||||
"@typescript-eslint/await-thenable": "error",
|
||||
"@typescript-eslint/no-misused-promises": "error",
|
||||
"@typescript-eslint/no-explicit-any": "warn",
|
||||
"id-match": ["error", "^(?!.*[pP]ubSub)"],
|
||||
"import/extensions": [
|
||||
"error",
|
||||
"ignorePackages",
|
||||
{
|
||||
"js": "never",
|
||||
"jsx": "never",
|
||||
"ts": "always",
|
||||
"tsx": "never"
|
||||
}
|
||||
]
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.spec.ts", "**/test_utils/*.ts", "*.js", "*.cjs"],
|
||||
"files": ["*.spec.ts", "**/test_utils/*.ts"],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"no-console": "off",
|
||||
"import/no-extraneous-dependencies": [
|
||||
"error",
|
||||
{
|
||||
"devDependencies": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts", "*.mts", "*.cts", "*.tsx"],
|
||||
"rules": {
|
||||
"@typescript-eslint/explicit-function-return-type": [
|
||||
"error",
|
||||
{
|
||||
"allowExpressions": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["**/ci/*.js"],
|
||||
"rules": {
|
||||
"no-undef": "off",
|
||||
"@typescript-eslint/explicit-member-accessibility": "off",
|
||||
"@typescript-eslint/no-floating-promises": "off",
|
||||
"import/no-extraneous-dependencies": "off"
|
||||
"@typescript-eslint/no-non-null-assertion": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@ -1 +0,0 @@
|
||||
* @waku-org/js-waku
|
||||
3
.github/CONTRIBUTING.md
vendored
Normal file
3
.github/CONTRIBUTING.md
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# Example Contributing Guidelines
|
||||
|
||||
This is an example of GitHub's contributing guidelines file. Check out GitHub's [CONTRIBUTING.md help center article](https://help.github.com/articles/setting-guidelines-for-repository-contributors/) for more information.
|
||||
9
.github/ISSUE_TEMPLATE.md
vendored
Normal file
9
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
- **I'm submitting a ...**
|
||||
[ ] bug report
|
||||
[ ] feature request
|
||||
[ ] question about the decisions made in the repository
|
||||
[ ] question about how to use this project
|
||||
|
||||
- **Summary**
|
||||
|
||||
- **Other information** (e.g. detailed explanation, stack traces, related issues, suggestions how to fix, links for us to have context, eg. StackOverflow, personal fork, etc.)
|
||||
53
.github/ISSUE_TEMPLATE/🚀-feature-request.md
vendored
53
.github/ISSUE_TEMPLATE/🚀-feature-request.md
vendored
@ -1,53 +0,0 @@
|
||||
---
|
||||
name: "\U0001F680 Feature request"
|
||||
about: Suggest an idea for this project
|
||||
title: 'feat:'
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Delete not needed sections below.
|
||||
-->
|
||||
|
||||
### Description
|
||||
<!--
|
||||
What problem are you facing, or what improvement are you suggesting?
|
||||
- Clearly describe the problem or need.
|
||||
- Explain why this feature would be useful.
|
||||
-->
|
||||
|
||||
### User Story
|
||||
<!--
|
||||
Describe the feature from the perspective of the user. Use the following format:
|
||||
- As a [type of user], I want to [do something], so that [reason/benefit].
|
||||
|
||||
Examples:
|
||||
- As a developer, I want to see detailed error logs, so that I can debug issues more effectively.
|
||||
- As a user, I want to be able to subscribe to a content topic without manually specifying a peer.
|
||||
-->
|
||||
|
||||
### Proposed Solution / Feature Design
|
||||
<!--
|
||||
Describe how the feature should work.
|
||||
- Provide a high-level summary of the solution.
|
||||
- Include details on the behavior, user interaction, and functionality.
|
||||
-->
|
||||
|
||||
#### Optional: Diagram or Draft of Design
|
||||
<!--
|
||||
If applicable, include visual aids or drafts to clarify your proposal.
|
||||
- Attach diagrams or design drafts to illustrate your idea.
|
||||
|
||||
Use Mermaid syntax - https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-diagrams
|
||||
-->
|
||||
|
||||
### Notes
|
||||
<!--
|
||||
Anything relevant:
|
||||
- issues or previous features;
|
||||
- discussion threads;
|
||||
- docs;
|
||||
- features in other projects;
|
||||
-->
|
||||
58
.github/ISSUE_TEMPLATE/🪳-bug-report.md
vendored
58
.github/ISSUE_TEMPLATE/🪳-bug-report.md
vendored
@ -1,58 +0,0 @@
|
||||
---
|
||||
name: "\U0001FAB3 Bug report"
|
||||
about: Create a report about a problem, observation or feedback.
|
||||
title: 'bug: '
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Delete not needed sections below.
|
||||
-->
|
||||
|
||||
### Description
|
||||
<!--
|
||||
Provide a clear and concise description of the bug.
|
||||
- What is happening?
|
||||
- What did you expect to happen?
|
||||
-->
|
||||
|
||||
### Expected Behavior
|
||||
<!--
|
||||
Describe what you expected to happen.
|
||||
-->
|
||||
|
||||
### Steps to Reproduce
|
||||
<!--
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. Scroll down to '...'
|
||||
4. See error
|
||||
-->
|
||||
|
||||
### Environment Details
|
||||
<!--
|
||||
Include details about your environment, such as:
|
||||
- Version of js-waku packages
|
||||
- Browser/Node.js version
|
||||
- Operating System
|
||||
- Any other context that might be relevant
|
||||
-->
|
||||
|
||||
<details>
|
||||
<summary>Logs</summary>
|
||||
|
||||
<!--
|
||||
Include any relevant logs or error messages here.
|
||||
Ensure sensitive information is redacted.
|
||||
|
||||
Add following flag: `debug=waku:*`
|
||||
- In browser to `localStorage`.
|
||||
- In NodeJS as ENV.
|
||||
-->
|
||||
|
||||
[Paste logs here]
|
||||
|
||||
</details>
|
||||
31
.github/PULL_REQUEST_TEMPLATE.md
vendored
31
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,30 +1,7 @@
|
||||
### Problem / Description
|
||||
<!--
|
||||
What problem does this PR address?
|
||||
Clearly describe the issue or feature the PR aims to solve.
|
||||
-->
|
||||
- **What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...)
|
||||
|
||||
### Solution
|
||||
<!--
|
||||
Describe how the problem is solved in this PR.
|
||||
- Provide an overview of the changes made.
|
||||
- Highlight any significant design decisions or architectural changes.
|
||||
-->
|
||||
- **What is the current behavior?** (You can also link to an open issue here)
|
||||
|
||||
### Notes
|
||||
<!--
|
||||
Additional context, considerations, or information relevant to this PR.
|
||||
- Are there known limitations or trade-offs in the solution?
|
||||
- Include links to related discussions, documents, or references.
|
||||
-->
|
||||
- Resolves
|
||||
- Related to
|
||||
- **What is the new behavior (if this is a feature change)?**
|
||||
|
||||
---
|
||||
|
||||
#### Checklist
|
||||
- [ ] Code changes are **covered by unit tests**.
|
||||
- [ ] Code changes are **covered by e2e tests**, if applicable.
|
||||
- [ ] **Dogfooding has been performed**, if feasible.
|
||||
- [ ] A **test version has been published**, if required.
|
||||
- [ ] All **CI checks** pass successfully.
|
||||
- **Other information**:
|
||||
|
||||
10
.github/actions/npm/action.yml
vendored
10
.github/actions/npm/action.yml
vendored
@ -1,10 +0,0 @@
|
||||
name: npm i
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- if: ${{ github.ref == 'refs/heads/master' || github.head_ref == 'release-please--branches--master' }}
|
||||
run: npm i
|
||||
shell: bash
|
||||
- if: ${{ github.ref != 'refs/heads/master' && github.head_ref != 'release-please--branches--master' }}
|
||||
uses: bahmutov/npm-install@v1
|
||||
27
.github/actions/prune-vm/action.yml
vendored
27
.github/actions/prune-vm/action.yml
vendored
@ -1,27 +0,0 @@
|
||||
# Inspired by https://github.com/AdityaGarg8/remove-unwanted-software
|
||||
# to free up disk space. Currently removes Dotnet, Android and Haskell.
|
||||
name: Remove unwanted software
|
||||
description: Default GitHub runners come with a lot of unnecessary software
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Disk space report before modification
|
||||
shell: bash
|
||||
run: |
|
||||
echo "==> Available space before cleanup"
|
||||
echo
|
||||
df -h
|
||||
- name: Maximize build disk space
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
sudo rm -rf /usr/share/dotnet
|
||||
sudo rm -rf /usr/local/lib/android
|
||||
sudo rm -rf /opt/ghc
|
||||
sudo rm -rf /usr/local/.ghcup
|
||||
- name: Disk space report after modification
|
||||
shell: bash
|
||||
run: |
|
||||
echo "==> Available space after cleanup"
|
||||
echo
|
||||
df -h
|
||||
11
.github/dependabot.yml
vendored
11
.github/dependabot.yml
vendored
@ -1,11 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
open-pull-requests-limit: 2
|
||||
schedule:
|
||||
interval: "daily"
|
||||
versioning-strategy: increase
|
||||
commit-message:
|
||||
prefix: "chore(deps)"
|
||||
include: "scope"
|
||||
12
.github/workflows/add-action-project.yml
vendored
12
.github/workflows/add-action-project.yml
vendored
@ -1,15 +1,15 @@
|
||||
name: Add new issues to Waku project board
|
||||
name: Add new issues to js-waku project board
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
add-to-project:
|
||||
name: Add issue to project
|
||||
add-new-issue-to-new-column:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/add-to-project@v0.5.0
|
||||
- uses: alex-page/github-project-automation-plus@v0.6.0
|
||||
with:
|
||||
project-url: https://github.com/orgs/waku-org/projects/2
|
||||
github-token: ${{ secrets.ADD_TO_PROJECT_20240815 }}
|
||||
project: js-waku
|
||||
column: New
|
||||
repo-token: ${{ secrets.GH_ACTION_PROJECT_MGMT }}
|
||||
|
||||
220
.github/workflows/ci.yml
vendored
220
.github/workflows/ci.yml
vendored
@ -3,155 +3,107 @@ name: CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
- "staging"
|
||||
- "trying"
|
||||
- 'main'
|
||||
- 'staging'
|
||||
- 'trying'
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
nim_wakunode_image:
|
||||
description: "Docker hub image name taken from https://hub.docker.com/r/wakuorg/nwaku/tags. Format: wakuorg/nwaku:v0.20.0"
|
||||
required: false
|
||||
type: string
|
||||
|
||||
env:
|
||||
NODE_JS: "24"
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
build_and_test:
|
||||
env:
|
||||
BUF_VERSION: '0.41.0'
|
||||
strategy:
|
||||
matrix:
|
||||
node: [14]
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: waku-org/js-waku
|
||||
submodules: 'recursive'
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_JS }}
|
||||
- uses: ./.github/actions/npm
|
||||
- run: npm run build
|
||||
- run: npm run check
|
||||
- run: npm run doc
|
||||
- name: Get nim-waku HEAD
|
||||
id: nim-waku-head
|
||||
shell: bash
|
||||
run: cd nim-waku && echo "::set-output name=ref::$(git rev-parse HEAD)"
|
||||
|
||||
proto:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Cache nim-waku binary
|
||||
id: cache-nim-waku
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
repository: waku-org/js-waku
|
||||
- uses: actions/setup-node@v3
|
||||
path: |
|
||||
./nim-waku/build/wakunode2
|
||||
./nim-waku/vendor/rln/target/debug
|
||||
key: nim-waku-build-${{ matrix.os }}-v3-${{ steps.nim-waku-head.outputs.ref }}
|
||||
|
||||
- name: Install NodeJS
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ env.NODE_JS }}
|
||||
- uses: ./.github/actions/npm
|
||||
node-version: ${{ matrix.node }}
|
||||
|
||||
# This would have been done part of npm pretest but it gives better
|
||||
# visibility in the CI if done as a separate step
|
||||
- name: Build wakunode2
|
||||
shell: bash
|
||||
run: (cd nim-waku && ./build/wakunode2 --help) || npm run nim-waku:build
|
||||
|
||||
- name: Ensure wakunode2 is ready
|
||||
shell: bash
|
||||
run: cd nim-waku && ./build/wakunode2 --help
|
||||
|
||||
- name: Cache buf binary
|
||||
id: cache-buf-bin
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /opt/hostedtoolcache/buf/
|
||||
key: buf-bin-v${{ env.BUF_VERSION }}-ubuntu-latest-v1
|
||||
|
||||
- name: Install bufbuild
|
||||
uses: mu-io/setup-buf@v2beta
|
||||
with:
|
||||
buf-version: ${{ env.BUF_VERSION }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install Protoc
|
||||
uses: arduino/setup-protoc@v1
|
||||
with:
|
||||
version: '3.x'
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Cache npm cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: node-${{ matrix.os }}-${{ matrix.node }}-v1-${{ hashFiles('**/package-lock.json') }}
|
||||
|
||||
- name: install using npm ci
|
||||
uses: bahmutov/npm-install@v1
|
||||
|
||||
- name: Generate protobuf code
|
||||
run: |
|
||||
npm run proto
|
||||
npm run fix
|
||||
run: npm run proto
|
||||
|
||||
- name: Check all protobuf code was committed
|
||||
shell: bash
|
||||
run: |
|
||||
res=$(git status --short --ignore-submodules)
|
||||
echo -n "'$res'" # For debug purposes
|
||||
[ $(echo -n "$res"|wc -l) -eq 0 ]
|
||||
[ $(git status --short --ignore-submodules|wc -l) -eq 0 ]
|
||||
|
||||
browser:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: mcr.microsoft.com/playwright:v1.56.1-jammy
|
||||
env:
|
||||
HOME: "/root"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
repository: waku-org/js-waku
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_JS }}
|
||||
- uses: ./.github/actions/npm
|
||||
- run: npm run build:esm
|
||||
- run: npm run test:browser
|
||||
- name: build
|
||||
run: npm run build
|
||||
|
||||
node:
|
||||
uses: ./.github/workflows/test-node.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
nim_wakunode_image: ${{ inputs.nim_wakunode_image || 'wakuorg/nwaku:v0.36.0' }}
|
||||
test_type: node
|
||||
allure_reports: true
|
||||
|
||||
node_optional:
|
||||
uses: ./.github/workflows/test-node.yml
|
||||
with:
|
||||
nim_wakunode_image: ${{ inputs.nim_wakunode_image || 'wakuorg/nwaku:v0.36.0' }}
|
||||
test_type: node-optional
|
||||
|
||||
node_with_nwaku_master:
|
||||
uses: ./.github/workflows/test-node.yml
|
||||
with:
|
||||
nim_wakunode_image: harbor.status.im/wakuorg/nwaku:latest
|
||||
test_type: nwaku-master
|
||||
|
||||
maybe-release:
|
||||
name: release
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||
needs: [check, proto, browser, node]
|
||||
steps:
|
||||
- uses: googleapis/release-please-action@v4
|
||||
id: release
|
||||
with:
|
||||
token: ${{ secrets.CI_TOKEN }}
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
repository: waku-org/js-waku
|
||||
if: ${{ steps.release.outputs.releases_created }}
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
if: ${{ steps.release.outputs.releases_created }}
|
||||
with:
|
||||
node-version: ${{ env.NODE_JS }}
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
if: ${{ steps.release.outputs.releases_created }}
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- run: npm install
|
||||
if: ${{ steps.release.outputs.releases_created }}
|
||||
|
||||
- run: npm run build
|
||||
if: ${{ steps.release.outputs.releases_created }}
|
||||
|
||||
- name: Setup Foundry
|
||||
if: ${{ steps.release.outputs.releases_created }}
|
||||
uses: foundry-rs/foundry-toolchain@v1
|
||||
with:
|
||||
version: nightly
|
||||
|
||||
- name: Generate RLN contract ABIs
|
||||
id: rln-abi
|
||||
if: ${{ steps.release.outputs.releases_created }}
|
||||
- name: Check no proto files changed
|
||||
shell: bash
|
||||
run: |
|
||||
npm run setup:contract-abi -w @waku/rln || {
|
||||
echo "::warning::Failed to generate contract ABIs, marking @waku/rln as private to skip publishing"
|
||||
cd packages/rln
|
||||
node -e "const fs = require('fs'); const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); pkg.private = true; fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));"
|
||||
echo "failed=true" >> $GITHUB_OUTPUT
|
||||
}
|
||||
[ $(git status --short --ignore-submodules|wc -l) -eq 0 ]
|
||||
|
||||
- name: Rebuild with new ABIs
|
||||
if: ${{ steps.release.outputs.releases_created && steps.rln-abi.outputs.failed != 'true' }}
|
||||
run: |
|
||||
npm install -w packages/rln
|
||||
npm run build -w @waku/rln || {
|
||||
echo "::warning::Failed to build @waku/rln, marking as private to skip publishing"
|
||||
cd packages/rln
|
||||
node -e "const fs = require('fs'); const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); pkg.private = true; fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));"
|
||||
}
|
||||
|
||||
- run: npm run publish
|
||||
if: ${{ steps.release.outputs.releases_created }}
|
||||
- name: test
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_JS_WAKU_PUBLISH }}
|
||||
DEBUG: "waku:nim-waku*,waku:test*"
|
||||
run: npm run test
|
||||
|
||||
- name: Upload logs on failure
|
||||
uses: actions/upload-artifact@v2
|
||||
if: failure()
|
||||
with:
|
||||
name: nim-waku-logs
|
||||
path: log/
|
||||
|
||||
29
.github/workflows/conventional-commits.yml
vendored
29
.github/workflows/conventional-commits.yml
vendored
@ -1,29 +0,0 @@
|
||||
name: "Conventional PR"
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- synchronize
|
||||
|
||||
jobs:
|
||||
main:
|
||||
name: Validate Pull Request Title
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: amannn/action-semantic-pull-request@v5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
# fix: bug fix on prod code
|
||||
# feat: new feature on prod code
|
||||
# test: only modify test or test utils
|
||||
# doc: only modify docs/comments
|
||||
# chore: anything else
|
||||
types: |
|
||||
fix
|
||||
feat
|
||||
test
|
||||
doc
|
||||
chore
|
||||
61
.github/workflows/deploy-gh-pages.yml
vendored
Normal file
61
.github/workflows/deploy-gh-pages.yml
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
name: Webchat Deploy GH Pages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
|
||||
jobs:
|
||||
deploy_gh_pages:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set git author identity
|
||||
run: |
|
||||
git config user.name "GitHub Action On js-waku Repo"
|
||||
git config user.email "franck+ghpages@status.im"
|
||||
|
||||
- name: Install NodeJS
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
|
||||
- name: Cache npm cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: node-v1-${{ hashFiles('**/package-lock.json') }}
|
||||
|
||||
- name: "[js-waku] install using npm ci"
|
||||
uses: bahmutov/npm-install@v1
|
||||
|
||||
- name: "[js-waku] build"
|
||||
run: npm run build
|
||||
|
||||
- name: install using npm i
|
||||
run: npm install
|
||||
working-directory: examples/web-chat
|
||||
|
||||
- name: build web app
|
||||
run: npm run build
|
||||
working-directory: examples/web-chat
|
||||
|
||||
- name: Deploy web chat app on gh pages
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./examples/web-chat/build
|
||||
|
||||
- name: Generate docs
|
||||
run: npm run doc:html
|
||||
|
||||
- name: Deploy documentation on gh pages
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
keep_files: true # Do not delete web chat app
|
||||
publish_dir: ./build/docs
|
||||
destination_dir: docs
|
||||
45
.github/workflows/examples-ci.yml
vendored
Normal file
45
.github/workflows/examples-ci.yml
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
name: Examples CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- 'staging'
|
||||
- 'trying'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
examples_build_and_test:
|
||||
strategy:
|
||||
matrix:
|
||||
example: [ cli-chat, web-chat ]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install NodeJS
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
|
||||
- name: Cache npm cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: examples-node-v1-${{ hashFiles('**/package-lock.json') }}
|
||||
|
||||
- name: "[js-waku] install using npm ci"
|
||||
uses: bahmutov/npm-install@v1
|
||||
|
||||
- name: "[js-waku] build"
|
||||
run: npm run build
|
||||
|
||||
- name: ${{ matrix.example }} install using npm i
|
||||
run: npm install
|
||||
working-directory: examples/${{ matrix.example }}
|
||||
|
||||
- name: ${{ matrix.example }} test
|
||||
run: npm run test
|
||||
working-directory: examples/${{ matrix.example }}
|
||||
26
.github/workflows/fleet-checker.yml
vendored
26
.github/workflows/fleet-checker.yml
vendored
@ -1,26 +0,0 @@
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
NODE_JS: "22"
|
||||
|
||||
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
|
||||
40
.github/workflows/playwright.yml
vendored
40
.github/workflows/playwright.yml
vendored
@ -1,40 +0,0 @@
|
||||
name: Playwright tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
env:
|
||||
NODE_JS: "22"
|
||||
# Firefox in container fails due to $HOME not being owned by user running commands
|
||||
# more details https://github.com/microsoft/playwright/issues/6500
|
||||
HOME: "/root"
|
||||
|
||||
jobs:
|
||||
test:
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: mcr.microsoft.com/playwright:v1.56.1-jammy
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_JS }}
|
||||
|
||||
- uses: ./.github/actions/npm
|
||||
|
||||
- name: Build entire monorepo
|
||||
run: npm run build
|
||||
|
||||
- name: Run Playwright tests
|
||||
run: npm run test --workspace=@waku/browser-tests
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
62
.github/workflows/pre-release.yml
vendored
62
.github/workflows/pre-release.yml
vendored
@ -1,62 +0,0 @@
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
NODE_JS: "24"
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
pre-release:
|
||||
name: pre-release
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: waku-org/js-waku
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_JS }}
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- run: npm install
|
||||
|
||||
- run: npm run build
|
||||
|
||||
- name: Setup Foundry
|
||||
uses: foundry-rs/foundry-toolchain@v1
|
||||
with:
|
||||
version: nightly
|
||||
|
||||
- name: Generate RLN contract ABIs
|
||||
id: rln-abi
|
||||
run: |
|
||||
npm run setup:contract-abi -w @waku/rln || {
|
||||
echo "::warning::Failed to generate contract ABIs, marking @waku/rln as private to skip publishing"
|
||||
cd packages/rln
|
||||
node -e "const fs = require('fs'); const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); pkg.private = true; fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));"
|
||||
echo "failed=true" >> $GITHUB_OUTPUT
|
||||
}
|
||||
|
||||
- name: Rebuild with new ABIs
|
||||
if: steps.rln-abi.outputs.failed != 'true'
|
||||
run: |
|
||||
npm install -w packages/rln
|
||||
npm run build -w @waku/rln || {
|
||||
echo "::warning::Failed to build @waku/rln, marking as private to skip publishing"
|
||||
cd packages/rln
|
||||
node -e "const fs = require('fs'); const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); pkg.private = true; fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));"
|
||||
}
|
||||
|
||||
- run: npm run publish -- --tag next
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_JS_WAKU_PUBLISH }}
|
||||
14
.github/workflows/size-limit.yml
vendored
14
.github/workflows/size-limit.yml
vendored
@ -1,14 +0,0 @@
|
||||
name: "size"
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
size:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_JOB_NUMBER: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: fryorcraken/size-limit-action@v2
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
128
.github/workflows/test-node.yml
vendored
128
.github/workflows/test-node.yml
vendored
@ -1,128 +0,0 @@
|
||||
# WARNING: This workflow is used by upstream workflows (jswaku, nwaku, gowaku) via workflow_call.
|
||||
# DO NOT modify the name, inputs, or other parts of this workflow that might break upstream CI.
|
||||
|
||||
name: Run Test
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
# IMPORTANT: Do not change the name or properties of these inputs.
|
||||
# If you add new required inputs make sure that they have default value or you make the change upstream as well
|
||||
inputs:
|
||||
nim_wakunode_image:
|
||||
required: true
|
||||
type: string
|
||||
test_type:
|
||||
required: true
|
||||
type: string
|
||||
debug:
|
||||
required: false
|
||||
type: string
|
||||
default: ''
|
||||
allure_reports:
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
env:
|
||||
NODE_JS: "24"
|
||||
# Ensure test type conditions remain consistent.
|
||||
WAKU_SERVICE_NODE_PARAMS: ${{ (inputs.test_type == 'go-waku-master') && '--min-relay-peers-to-publish=0' || '' }}
|
||||
DEBUG: ${{ inputs.debug }}
|
||||
GITHUB_TOKEN: ${{ secrets.DEPLOY_TEST_REPORTS_PAT }}
|
||||
|
||||
jobs:
|
||||
node:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
WAKUNODE_IMAGE: ${{ inputs.nim_wakunode_image }}
|
||||
ALLURE_REPORTS: ${{ inputs.allure_reports }}
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
checks: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
repository: waku-org/js-waku
|
||||
|
||||
- name: Remove unwanted software
|
||||
uses: ./.github/actions/prune-vm
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_JS }}
|
||||
|
||||
- uses: ./.github/actions/npm
|
||||
|
||||
- run: npm run build:esm
|
||||
|
||||
- name: Run tests
|
||||
timeout-minutes: 30
|
||||
run: ${{ (inputs.test_type == 'node-optional') && 'npm run test:optional --workspace=@waku/tests' || 'npm run test:node' }}
|
||||
|
||||
- name: Merge allure reports
|
||||
if: always() && env.ALLURE_REPORTS == 'true'
|
||||
run: node ci/mergeAllureResults.cjs
|
||||
|
||||
- name: Get allure history
|
||||
if: always() && env.ALLURE_REPORTS == 'true'
|
||||
uses: actions/checkout@v3
|
||||
continue-on-error: true
|
||||
with:
|
||||
repository: waku-org/allure-jswaku
|
||||
ref: gh-pages
|
||||
path: gh-pages
|
||||
token: ${{ env.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup allure report
|
||||
if: always() && env.ALLURE_REPORTS == 'true'
|
||||
uses: simple-elf/allure-report-action@master
|
||||
id: allure-report
|
||||
with:
|
||||
allure_results: allure-results
|
||||
gh_pages: gh-pages
|
||||
allure_history: allure-history
|
||||
keep_reports: 30
|
||||
|
||||
- name: Deploy report to Github Pages
|
||||
if: always() && env.ALLURE_REPORTS == 'true'
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
personal_token: ${{ env.GITHUB_TOKEN }}
|
||||
external_repository: waku-org/allure-jswaku
|
||||
publish_branch: gh-pages
|
||||
publish_dir: allure-history
|
||||
|
||||
- name: Upload debug logs on failure
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: ${{ inputs.test_type }}-debug.log
|
||||
path: debug.log
|
||||
|
||||
- name: Sanitize log filenames
|
||||
if: failure()
|
||||
run: |
|
||||
find packages/tests/log/ -type f | while read fname; do
|
||||
dir=$(dirname "$fname")
|
||||
base=$(basename "$fname")
|
||||
sanitized_base=$(echo $base | tr -d '\"*:<>?|' | sed 's/[\\/\r\n]/_/g' | sed 's/_$//')
|
||||
if [ "$base" != "$sanitized_base" ]; then
|
||||
mv "$fname" "$dir/$sanitized_base"
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Upload logs on failure
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: ${{ inputs.test_type }}-logs
|
||||
path: packages/tests/log/
|
||||
|
||||
- name: Create test summary
|
||||
if: always() && env.ALLURE_REPORTS == 'true'
|
||||
run: |
|
||||
echo "## Run Information" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **NWAKU**: ${{ env.WAKUNODE_IMAGE }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "## Test Results" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Allure report will be available at: https://waku-org.github.io/allure-jswaku/${{ github.run_number }}" >> $GITHUB_STEP_SUMMARY
|
||||
106
.github/workflows/test-reliability.yml
vendored
106
.github/workflows/test-reliability.yml
vendored
@ -1,106 +0,0 @@
|
||||
name: Run Reliability Test
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
test_type:
|
||||
description: 'Type of reliability test to run'
|
||||
required: true
|
||||
default: 'longevity'
|
||||
type: choice
|
||||
options:
|
||||
- longevity
|
||||
- high-throughput
|
||||
- throughput-sizes
|
||||
- network-latency
|
||||
- low-bandwidth
|
||||
- packet-loss
|
||||
- all
|
||||
|
||||
env:
|
||||
NODE_JS: "24"
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
checks: write
|
||||
strategy:
|
||||
matrix:
|
||||
test_type: [longevity, high-throughput, throughput-sizes, network-latency, low-bandwidth, packet-loss]
|
||||
fail-fast: false
|
||||
if: ${{ github.event.inputs.test_type == 'all' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
repository: waku-org/js-waku
|
||||
|
||||
- name: Remove unwanted software
|
||||
uses: ./.github/actions/prune-vm
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_JS }}
|
||||
|
||||
- uses: ./.github/actions/npm
|
||||
|
||||
- run: npm run build:esm
|
||||
|
||||
- name: Run tests
|
||||
timeout-minutes: 150
|
||||
run: |
|
||||
if [ "${{ matrix.test_type }}" = "high-throughput" ]; then
|
||||
npm run test:high-throughput
|
||||
elif [ "${{ matrix.test_type }}" = "throughput-sizes" ]; then
|
||||
npm run test:throughput-sizes
|
||||
elif [ "${{ matrix.test_type }}" = "network-latency" ]; then
|
||||
npm run test:network-latency
|
||||
elif [ "${{ matrix.test_type }}" = "low-bandwidth" ]; then
|
||||
npm run test:low-bandwidth
|
||||
elif [ "${{ matrix.test_type }}" = "packet-loss" ]; then
|
||||
npm run test:packet-loss
|
||||
else
|
||||
npm run test:longevity
|
||||
fi
|
||||
|
||||
single-test:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
checks: write
|
||||
if: ${{ github.event.inputs.test_type != 'all' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
repository: waku-org/js-waku
|
||||
|
||||
- name: Remove unwanted software
|
||||
uses: ./.github/actions/prune-vm
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_JS }}
|
||||
|
||||
- uses: ./.github/actions/npm
|
||||
|
||||
- run: npm run build:esm
|
||||
|
||||
- name: Run tests
|
||||
timeout-minutes: 150
|
||||
run: |
|
||||
if [ "${{ github.event.inputs.test_type }}" = "high-throughput" ]; then
|
||||
npm run test:high-throughput
|
||||
elif [ "${{ github.event.inputs.test_type }}" = "throughput-sizes" ]; then
|
||||
npm run test:throughput-sizes
|
||||
elif [ "${{ github.event.inputs.test_type }}" = "network-latency" ]; then
|
||||
npm run test:network-latency
|
||||
elif [ "${{ github.event.inputs.test_type }}" = "low-bandwidth" ]; then
|
||||
npm run test:low-bandwidth
|
||||
elif [ "${{ github.event.inputs.test_type }}" = "packet-loss" ]; then
|
||||
npm run test:packet-loss
|
||||
else
|
||||
npm run test:longevity
|
||||
fi
|
||||
20
.gitignore
vendored
20
.gitignore
vendored
@ -1,23 +1,9 @@
|
||||
.idea/*
|
||||
.angular
|
||||
.nyc_output
|
||||
build
|
||||
bundle
|
||||
dist
|
||||
node_modules
|
||||
test
|
||||
src/**.js
|
||||
coverage
|
||||
*.log
|
||||
*.tsbuildinfo
|
||||
docs
|
||||
test-results
|
||||
playwright-report
|
||||
example
|
||||
packages/discovery/mock_local_storage
|
||||
.cursorrules
|
||||
.giga
|
||||
.cursor
|
||||
.DS_Store
|
||||
CLAUDE.md
|
||||
.env
|
||||
postgres-data/
|
||||
packages/rln/waku-rlnv2-contract/
|
||||
yarn.lock
|
||||
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "nim-waku"]
|
||||
path = nim-waku
|
||||
url = https://github.com/status-im/nim-waku.git
|
||||
@ -1 +0,0 @@
|
||||
npx lint-staged
|
||||
6
.mocharc.json
Normal file
6
.mocharc.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"extension": ["ts"],
|
||||
"spec": "src/**/*.spec.ts",
|
||||
"require": "ts-node/register",
|
||||
"exit": true
|
||||
}
|
||||
@ -1,9 +1,3 @@
|
||||
.github
|
||||
.husky
|
||||
.vscode
|
||||
nwaku
|
||||
*/**/build
|
||||
*/**/bundle
|
||||
*/**/dist
|
||||
*/**/node_modules
|
||||
*/**/CHANGELOG.md
|
||||
# package.json is formatted by package managers, so we ignore it here
|
||||
package.json
|
||||
gen
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
{
|
||||
"packages/utils": "0.0.27",
|
||||
"packages/proto": "0.0.15",
|
||||
"packages/interfaces": "0.0.34",
|
||||
"packages/enr": "0.0.33",
|
||||
"packages/core": "0.0.40",
|
||||
"packages/message-encryption": "0.0.38",
|
||||
"packages/relay": "0.0.23",
|
||||
"packages/sdk": "0.0.36",
|
||||
"packages/discovery": "0.0.13",
|
||||
"packages/sds": "0.0.8",
|
||||
"packages/rln": "0.1.10",
|
||||
"packages/react": "0.0.8",
|
||||
"packages/run": "0.0.2"
|
||||
}
|
||||
@ -1,65 +0,0 @@
|
||||
module.exports = [
|
||||
{
|
||||
name: "Waku node",
|
||||
path: "packages/sdk/bundle/index.js",
|
||||
import: "{ WakuNode }",
|
||||
},
|
||||
{
|
||||
name: "Waku Simple Light Node",
|
||||
path: ["packages/sdk/bundle/index.js", "packages/core/bundle/index.js"],
|
||||
import: {
|
||||
"packages/sdk/bundle/index.js":
|
||||
"{ createLightNode, createEncoder, createDecoder, bytesToUtf8, utf8ToBytes, Decoder, Encoder, DecodedMessage, WakuNode }",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ECIES encryption",
|
||||
path: "packages/message-encryption/bundle/ecies.js",
|
||||
import: "{ generatePrivateKey, createEncoder, createDecoder }",
|
||||
},
|
||||
{
|
||||
name: "Symmetric encryption",
|
||||
path: "packages/message-encryption/bundle/symmetric.js",
|
||||
import: "{ generateSymmetricKey, createEncoder, createDecoder }",
|
||||
},
|
||||
{
|
||||
name: "DNS discovery",
|
||||
path: "packages/discovery/bundle/index.js",
|
||||
import: "{ PeerDiscoveryDns }",
|
||||
},
|
||||
{
|
||||
name: "Peer Exchange discovery",
|
||||
path: "packages/discovery/bundle/index.js",
|
||||
import: "{ wakuPeerExchangeDiscovery }",
|
||||
},
|
||||
{
|
||||
name: "Peer Cache Discovery",
|
||||
path: "packages/discovery/bundle/index.js",
|
||||
import: "{ wakuPeerCacheDiscovery }",
|
||||
},
|
||||
{
|
||||
name: "Privacy preserving protocols",
|
||||
path: "packages/relay/bundle/index.js",
|
||||
import: "{ Relay }",
|
||||
},
|
||||
{
|
||||
name: "Waku Filter",
|
||||
path: "packages/sdk/bundle/index.js",
|
||||
import: "{ Filter }",
|
||||
},
|
||||
{
|
||||
name: "Waku LightPush",
|
||||
path: "packages/sdk/bundle/index.js",
|
||||
import: "{ LightPush }",
|
||||
},
|
||||
{
|
||||
name: "History retrieval protocols",
|
||||
path: "packages/sdk/bundle/index.js",
|
||||
import: "{ Store }",
|
||||
},
|
||||
{
|
||||
name: "Deterministic Message Hashing",
|
||||
path: ["packages/core/bundle/index.js"],
|
||||
import: "{ messageHash }",
|
||||
},
|
||||
];
|
||||
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@ -1,11 +1,7 @@
|
||||
{
|
||||
"cSpell.userWords": [], // only use words from .cspell.json
|
||||
"cSpell.enabled": true,
|
||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit"
|
||||
},
|
||||
"editor.formatOnSave": false, // Disable general format on save
|
||||
"editor.formatOnSave": true,
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true
|
||||
}
|
||||
|
||||
34
CHANGELOG.md
Normal file
34
CHANGELOG.md
Normal file
@ -0,0 +1,34 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.2.0] - 2021-05-14
|
||||
|
||||
### Added
|
||||
- `WakuRelay.getPeers` method.
|
||||
- Use `WakuRelay.getPeers` in web chat app example to disable send button.
|
||||
|
||||
### Changed
|
||||
- Enable passing `string`s to `addPeerToAddressBook`.
|
||||
- Use `addPeerToAddressBook` in examples and usage doc.
|
||||
- Settle on `js-waku` name across the board.
|
||||
- **Breaking**: `RelayDefaultTopic` renamed to `DefaultPubsubTopic`.
|
||||
|
||||
## [0.1.0] - 2021-05-12
|
||||
|
||||
### Added
|
||||
- Add usage section to the README.
|
||||
- Support of [Waku v2 Relay](https://rfc.vac.dev/spec/11/).
|
||||
- Support of [Waku v2 Store](https://rfc.vac.dev/spec/13/).
|
||||
- [Node Chat App example](./examples/cli-chat).
|
||||
- [ReactJS Chat App example](./examples/web-chat).
|
||||
- [Typedoc Documentation](https://status-im.github.io/js-waku/docs).
|
||||
|
||||
[Unreleased]: https://github.com/status-im/js-waku/compare/v0.2.0...HEAD
|
||||
[0.2.0]: https://github.com/status-im/js-waku/compare/v0.1.0...v0.2.0
|
||||
[0.1.0]: https://github.com/status-im/js-waku/compare/f46ce77f57c08866873b5c80acd052e0ddba8bc9...v0.1.0
|
||||
@ -11,35 +11,28 @@ This project board is to prioritize the work of core contributors so do not be d
|
||||
Do note that we have a [CI](./.github/workflows/ci.yml) powered by GitHub Action.
|
||||
To help ensure your PR passes, just run before committing:
|
||||
|
||||
- `npm run fix`: To format your code,
|
||||
- `npm run check`: To check your code for linting errors,
|
||||
- `npm run test`: To run all tests
|
||||
- `npm run fix`: To format your code,
|
||||
- `npm run test`: To run all tests, including lint checks.
|
||||
|
||||
|
||||
## Build & Test
|
||||
|
||||
To build and test this repository, you need:
|
||||
|
||||
- [Node.js & npm](https://nodejs.org/en/).
|
||||
- Chrome (for browser testing).
|
||||
|
||||
Run `npm run build` at least once so that intra-dependencies are resolved.
|
||||
|
||||
- [Node.js & npm](https://nodejs.org/en/)
|
||||
- [bufbuild](https://github.com/bufbuild/buf) (only if changing protobuf files)
|
||||
- [protoc](https://grpc.io/docs/protoc-installation/) (only if changing protobuf files)
|
||||
|
||||
To ensure interoperability with [nim-waku](https://github.com/status-im/nim-waku/), some tests are run against a nim-waku node.
|
||||
This is why the relevant docker images for the node is pulled as part of the `pretest` script that runs before `npm run test`.
|
||||
This is why `nim-waku` is present as a [git submodule](https://git-scm.com/book/en/v2/Git-Tools-Submodules), which itself contain several submodules.
|
||||
At this stage, it is not possible to exclude nim-waku tests, hence `git submodule update --init --recursive` is run before testing (see [`pretest` script](https://github.com/status-im/js-waku/blob/main/package.json)).
|
||||
|
||||
If you do not want to run `npm run test`, you can still pull the relevant nim-waku docker image by running `npm run pretest`.
|
||||
|
||||
Note that we run tests in both NodeJS and browser environments (using [karma](https://karma-runner.github.io/)).
|
||||
Files named `*.node.spec.ts` are only run in NodeJS environment;
|
||||
Files named `*.spec.ts` are run in both NodeJS and browser environment.
|
||||
To build nim-waku, you also need [Rust](https://www.rust-lang.org/tools/install).
|
||||
|
||||
## Guidelines
|
||||
|
||||
- Please follow [Chris Beam's commit message guide](https://chris.beams.io/posts/git-commit/) for commit patches,
|
||||
- Please test new code, we use [mocha](https://mochajs.org/),
|
||||
[chai](https://www.chaijs.com/),
|
||||
[fast-check](https://github.com/dubzzz/fast-check)
|
||||
and [karma](https://karma-runner.github.io/).
|
||||
- Please follow [Chris Beam's commit message guide](https://chris.beams.io/posts/git-commit/),
|
||||
- Usually best to test new code,
|
||||
|
||||
### Committing Patches
|
||||
|
||||
@ -63,11 +56,3 @@ Commit messages should never contain any `@` mentions (usernames prefixed with "
|
||||
Please refer to the [Git manual](https://git-scm.com/doc) for more information
|
||||
about Git.
|
||||
|
||||
### Releasing
|
||||
|
||||
`js-waku` has two types of releases:
|
||||
- public releases;
|
||||
- pre releases;
|
||||
|
||||
Public releases happen by merging PRs opened by `release-please` action.
|
||||
Pre releases happen manually by triggering [this workflow](https://github.com/waku-org/js-waku/actions/workflows/pre-release.yml)
|
||||
|
||||
186
README.md
186
README.md
@ -1,59 +1,183 @@
|
||||

|
||||

|
||||
[](https://discord.waku.org)
|
||||
|
||||
# js-waku
|
||||
|
||||
A TypeScript implementation of the [Waku v2 protocol](https://rfc.vac.dev/spec/10/).
|
||||
A JavaScript implementation of the [Waku v2 protocol](https://rfc.vac.dev/spec/10/).
|
||||
|
||||
## Documentation
|
||||
## Usage
|
||||
|
||||
- [Quick start](https://docs.waku.org/guides/js-waku/#getting-started)
|
||||
- [Full documentation](https://docs.waku.org/guides/js-waku)
|
||||
- [API documentation (`master` branch)](https://js.waku.org/)
|
||||
- [Waku](https://waku.org/)
|
||||
- [Vac](https://vac.dev/)
|
||||
|
||||
API Documentation can also be generated locally:
|
||||
Install `js-waku` package:
|
||||
|
||||
```shell
|
||||
npm install js-waku
|
||||
```
|
||||
|
||||
Start a waku node:
|
||||
|
||||
```javascript
|
||||
import { Waku } from 'js-waku';
|
||||
|
||||
const waku = await Waku.create();
|
||||
```
|
||||
|
||||
Connect to a new peer:
|
||||
|
||||
```javascript
|
||||
import { multiaddr } from 'multiaddr';
|
||||
import PeerId from 'peer-id';
|
||||
|
||||
// Directly dial a new peer
|
||||
await waku.dial('/dns4/node-01.do-ams3.jdev.misc.statusim.net/tcp/7010/wss/p2p/16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ');
|
||||
|
||||
// Or, add peer to address book so it auto dials in the background
|
||||
waku.addPeerToAddressBook(
|
||||
'16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ',
|
||||
['/dns4/node-01.do-ams3.jdev.misc.statusim.net/tcp/7010/wss']
|
||||
);
|
||||
```
|
||||
|
||||
The `contentTopic` is a metadata `string` that allows categorization of messages on the waku network.
|
||||
Depending on your use case, you can either create a new `contentTopic` or look at the [RFCs](https://rfc.vac.dev/) and use an existing `contentTopic`.
|
||||
See the [Waku v2 Message spec](https://rfc.vac.dev/spec/14/) for more details.
|
||||
|
||||
Listen to new messages received via [Waku v2 Relay](https://rfc.vac.dev/spec/11/), filtering the `contentTopic` to `waku/2/my-cool-app/proto`:
|
||||
|
||||
```javascript
|
||||
waku.relay.addObserver((msg) => {
|
||||
console.log("Message received:", msg.payloadAsUtf8)
|
||||
}, ["waku/2/my-cool-app/proto"]);
|
||||
```
|
||||
|
||||
Send a message on the waku relay network:
|
||||
|
||||
```javascript
|
||||
import { WakuMessage } from 'js-waku';
|
||||
|
||||
const msg = WakuMessage.fromUtf8String("Here is a message!", "waku/2/my-cool-app/proto")
|
||||
await waku.relay.send(msg);
|
||||
```
|
||||
|
||||
The [Waku v2 Store protocol](https://rfc.vac.dev/spec/13/) enables full nodes to store messages received via relay
|
||||
and clients to retrieve them (e.g. after resuming connectivity).
|
||||
The protocol implements pagination meaning that it may take several queries to retrieve all messages.
|
||||
|
||||
Query a waku store peer to check historical messages:
|
||||
|
||||
```javascript
|
||||
// Process messages once they are all retrieved:
|
||||
const messages = await waku.store.queryHistory(storePeerId, ["waku/2/my-cool-app/proto"]);
|
||||
messages.forEach((msg) => {
|
||||
console.log("Message retrieved:", msg.payloadAsUtf8)
|
||||
})
|
||||
|
||||
// Or, pass a callback function to be executed as pages are received:
|
||||
waku.store.queryHistory(storePeerId, ["waku/2/my-cool-app/proto"],
|
||||
(messages) => {
|
||||
messages.forEach((msg) => {
|
||||
console.log("Message retrieved:", msg.payloadAsUtf8)
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
Find more [examples](#examples) below
|
||||
or checkout the latest `main` branch documentation at [https://status-im.github.io/js-waku/docs/](https://status-im.github.io/js-waku/docs/).
|
||||
|
||||
Docs can also be generated locally using:
|
||||
|
||||
```shell
|
||||
git clone https://github.com/waku-org/js-waku.git
|
||||
cd js-waku
|
||||
npm install
|
||||
npm run doc
|
||||
```
|
||||
|
||||
# Using Nix shell
|
||||
```shell
|
||||
git clone https://github.com/waku-org/js-waku.git
|
||||
cd js-waku
|
||||
nix develop
|
||||
npm install
|
||||
npm run doc
|
||||
```
|
||||
## Waku Protocol Support
|
||||
|
||||
You can track progress on the [project board](https://github.com/status-im/js-waku/projects/1).
|
||||
|
||||
- ✔: Supported
|
||||
- 🚧: Implementation in progress
|
||||
- ⛔: Support is not planned
|
||||
|
||||
| Spec | Implementation Status |
|
||||
| ---- | -------------- |
|
||||
|[6/WAKU1](https://rfc.vac.dev/spec/6)|⛔|
|
||||
|[7/WAKU-DATA](https://rfc.vac.dev/spec/7)|⛔|
|
||||
|[8/WAKU-MAIL](https://rfc.vac.dev/spec/8)|⛔|
|
||||
|[9/WAKU-RPC](https://rfc.vac.dev/spec/9)|⛔|
|
||||
|[10/WAKU2](https://rfc.vac.dev/spec/10)|🚧|
|
||||
|[11/WAKU2-RELAY](https://rfc.vac.dev/spec/11)|✔|
|
||||
|[12/WAKU2-FILTER](https://rfc.vac.dev/spec/12)||
|
||||
|[13/WAKU2-STORE](https://rfc.vac.dev/spec/13)|✔ (querying node only)|
|
||||
|[14/WAKU2-MESSAGE](https://rfc.vac.dev/spec/14)|✔|
|
||||
|[15/WAKU2-BRIDGE](https://rfc.vac.dev/spec/15)||
|
||||
|[16/WAKU2-RPC](https://rfc.vac.dev/spec/16)|⛔|
|
||||
|[17/WAKU2-RLNRELAY](https://rfc.vac.dev/spec/17)||
|
||||
|[18/WAKU2-SWAP](https://rfc.vac.dev/spec/18)||
|
||||
|
||||
## Bugs, Questions & Features
|
||||
|
||||
If you encounter any bug or would like to propose new features, feel free to [open an issue](https://github.com/waku-org/js-waku/issues/new/).
|
||||
If you encounter any bug or would like to propose new features, feel free to [open an issue](https://github.com/status-im/js-waku/issues/new/).
|
||||
|
||||
For general discussion, get help or latest news, join us on [Vac Discord](https://discord.gg/Nrac59MfSX) or the [Waku Telegram Group](https://t.me/waku_org).
|
||||
For support, questions & more general topics, please join the discussion on the [Vac forum](https://forum.vac.dev/tag/js-waku) (use _\#js-waku_ tag).
|
||||
|
||||
## Roadmap
|
||||
## Examples
|
||||
|
||||
You can track progress on the [project board](https://github.com/orgs/waku-org/projects/2/views/1).
|
||||
## Web Chat App (ReactJS)
|
||||
|
||||
A ReactJS chat app is provided as a showcase of the library used in the browser.
|
||||
A deployed version is available at https://status-im.github.io/js-waku/
|
||||
|
||||
Find the code in the [examples folder](https://github.com/status-im/js-waku/tree/main/examples/web-chat).
|
||||
|
||||
To run a development version locally, do:
|
||||
|
||||
```shell
|
||||
git clone https://github.com/status-im/js-waku/ ; cd js-waku
|
||||
npm install # Install dependencies for js-waku
|
||||
npm run build # Build js-waku
|
||||
cd examples/web-chat
|
||||
npm install # Install dependencies for the web app
|
||||
npm run start # Start development server to serve the web app on http://localhost:3000/js-waku
|
||||
```
|
||||
|
||||
Use `/help` to see the available commands.
|
||||
|
||||
## CLI Chat App (NodeJS)
|
||||
|
||||
A node chat app is provided as a working example of the library.
|
||||
It is interoperable with the [nim-waku chat app example](https://github.com/status-im/nim-waku/blob/master/examples/v2/chat2.nim).
|
||||
|
||||
Find the code in the [examples folder](https://github.com/status-im/js-waku/tree/main/examples/cli-chat).
|
||||
|
||||
To run the chat app, first ensure you have [Node.js](https://nodejs.org/en/) v14 or above:
|
||||
|
||||
```shell
|
||||
node --version
|
||||
```
|
||||
|
||||
Then, install and run:
|
||||
|
||||
```shell
|
||||
git clone https://github.com/status-im/js-waku/ ; cd js-waku
|
||||
npm install # Install dependencies for js-waku
|
||||
npm run build # Build js-waku
|
||||
cd examples/cli-chat
|
||||
npm install # Install dependencies for the cli app
|
||||
npm run start -- --staticNode /ip4/134.209.139.210/tcp/30303/p2p/16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ
|
||||
```
|
||||
|
||||
You can also specify an optional `listenAddr` parameter (.e.g `--listenAddr /ip4/0.0.0.0/tcp/7777/ws`).
|
||||
This is only useful if you want a remote node to dial to your chat app,
|
||||
it is not necessary in normal usage when you just connect to the fleet.
|
||||
|
||||
## Contributing
|
||||
|
||||
See [CONTRIBUTING.md](https://github.com/waku-org/js-waku/blob/master/CONTRIBUTING.md).
|
||||
See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
||||
|
||||
## License
|
||||
|
||||
Licensed and distributed under either of
|
||||
|
||||
- MIT license: [LICENSE-MIT](https://github.com/waku-org/js-waku/blob/master/LICENSE-MIT) or http://opensource.org/licenses/MIT
|
||||
* MIT license: [LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT
|
||||
|
||||
or
|
||||
|
||||
- Apache License, Version 2.0, ([LICENSE-APACHE-v2](https://github.com/waku-org/js-waku/blob/master/LICENSE-APACHE-v2) or http://www.apache.org/licenses/LICENSE-2.0)
|
||||
* Apache License, Version 2.0, ([LICENSE-APACHE-v2](LICENSE-APACHE-v2) or http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
||||
at your option. These files may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
status = [
|
||||
"check",
|
||||
"proto",
|
||||
"browser",
|
||||
"node",
|
||||
"build_and_test (14, ubuntu-latest)",
|
||||
"build_and_test (14, macos-latest)",
|
||||
"examples_build_and_test (web-chat)",
|
||||
"examples_build_and_test (cli-chat)"
|
||||
]
|
||||
block_labels = ["work-in-progress"]
|
||||
delete_merged_branches = true
|
||||
|
||||
6
buf.gen.yaml
Normal file
6
buf.gen.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
version: v1beta1
|
||||
|
||||
plugins:
|
||||
- name: ts_proto
|
||||
out: ./src/proto
|
||||
opt: grpc_js,esModuleInterop=true
|
||||
71
ci/Jenkinsfile
vendored
71
ci/Jenkinsfile
vendored
@ -1,71 +0,0 @@
|
||||
#!/usr/bin/env groovy
|
||||
library 'status-jenkins-lib@v1.9.27'
|
||||
|
||||
pipeline {
|
||||
agent {
|
||||
docker {
|
||||
label 'linuxcontainer'
|
||||
image 'harbor.status.im/infra/ci-build-containers:linux-base-1.0.0'
|
||||
args '--volume=/nix:/nix ' +
|
||||
'--volume=/etc/nix:/etc/nix ' +
|
||||
'--user jenkins'
|
||||
}
|
||||
}
|
||||
|
||||
options {
|
||||
disableConcurrentBuilds()
|
||||
disableRestartFromStage()
|
||||
/* manage how many builds we keep */
|
||||
buildDiscarder(logRotator(
|
||||
numToKeepStr: '20',
|
||||
daysToKeepStr: '30',
|
||||
))
|
||||
}
|
||||
|
||||
environment {
|
||||
GIT_AUTHOR_NAME = 'status-im-auto'
|
||||
GIT_AUTHOR_EMAIL = 'auto@status.im'
|
||||
PUPPETEER_SKIP_DOWNLOAD = 'true'
|
||||
NO_COLOR = 'true'
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Deps') {
|
||||
steps {
|
||||
script {
|
||||
nix.develop('npm install', pure: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Packages') {
|
||||
steps {
|
||||
script {
|
||||
nix.develop('npm run build', pure: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Build') {
|
||||
steps {
|
||||
script {
|
||||
nix.develop('npm run doc', pure: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Publish') {
|
||||
when { expression { GIT_BRANCH.endsWith('master') } }
|
||||
steps {
|
||||
sshagent(credentials: ['status-im-auto-ssh']) {
|
||||
script {
|
||||
nix.develop('npm run deploy', pure: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
always { cleanWs() }
|
||||
}
|
||||
}
|
||||
11
ci/README.md
11
ci/README.md
@ -1,11 +0,0 @@
|
||||
# Description
|
||||
|
||||
Configuration of CI builds executed under a Jenkins instance at https://ci.status.im/.
|
||||
|
||||
# Website
|
||||
|
||||
The `Jenkinsfile.gh-pages` file builds the documentation site with this job:
|
||||
https://ci.infra.status.im/job/website/job/js.waku.org/
|
||||
|
||||
And deploys it via `gh-pages` branch and [GitHub Pages](https://pages.github.com/) to:
|
||||
https://js.waku.org/
|
||||
45
ci/deploy.js
45
ci/deploy.js
@ -1,45 +0,0 @@
|
||||
import { promisify } from "util";
|
||||
|
||||
import { publish } from "gh-pages";
|
||||
|
||||
/* fix for "Unhandled promise rejections" */
|
||||
process.on("unhandledRejection", (err) => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
const ghpublish = promisify(publish);
|
||||
|
||||
const Args = process.argv.slice(2);
|
||||
const USE_HTTPS = Args[0] && Args[0].toUpperCase() === "HTTPS";
|
||||
|
||||
const branch = "gh-pages";
|
||||
const org = "waku-org";
|
||||
const repo = "js-waku";
|
||||
/* use SSH auth by default */
|
||||
let repoUrl = USE_HTTPS
|
||||
? `https://github.com/${org}/${repo}.git`
|
||||
: `git@github.com:${org}/${repo}.git`;
|
||||
|
||||
/* alternative auth using GitHub user and API token */
|
||||
if (typeof process.env.GH_USER !== "undefined") {
|
||||
repoUrl =
|
||||
"https://" +
|
||||
process.env.GH_USER +
|
||||
":" +
|
||||
process.env.GH_TOKEN +
|
||||
"@" +
|
||||
`github.com/${org}/${repo}.git`;
|
||||
}
|
||||
|
||||
const main = async (url, branch) => {
|
||||
console.log(`Pushing to: ${url}`);
|
||||
console.log(`On branch: ${branch}`);
|
||||
await ghpublish("docs", {
|
||||
repo: url,
|
||||
branch: branch,
|
||||
dotfiles: true,
|
||||
silent: false
|
||||
});
|
||||
};
|
||||
|
||||
void main(repoUrl, branch);
|
||||
@ -1,19 +0,0 @@
|
||||
const fs = require("fs-extra");
|
||||
const glob = require("glob");
|
||||
|
||||
const ROOT_ALLURE_RESULTS = "./allure-results"; // Target directory in the root
|
||||
|
||||
fs.ensureDirSync(ROOT_ALLURE_RESULTS);
|
||||
|
||||
const directories = glob.sync("packages/**/allure-results");
|
||||
|
||||
directories.forEach((dir) => {
|
||||
const files = fs.readdirSync(dir);
|
||||
files.forEach((file) => {
|
||||
const sourcePath = `${dir}/${file}`;
|
||||
const targetPath = `${ROOT_ALLURE_RESULTS}/${file}`;
|
||||
fs.copyFileSync(sourcePath, targetPath);
|
||||
});
|
||||
});
|
||||
|
||||
console.log("All allure-results directories merged successfully!");
|
||||
178
ci/publish.js
178
ci/publish.js
@ -1,178 +0,0 @@
|
||||
import cp from "child_process";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { promisify } from "util";
|
||||
|
||||
const PACKAGE_JSON = "package.json";
|
||||
// hack to get __dirname
|
||||
const DIR = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const NEXT_TAG = "next";
|
||||
const LATEST_TAG = "latest";
|
||||
const CURRENT_TAG = readPublishTag();
|
||||
|
||||
const exec = promisify(cp.exec);
|
||||
const readFile = promisify(fs.readFile);
|
||||
const writeFile = promisify(fs.writeFile);
|
||||
|
||||
run()
|
||||
.then(() => {
|
||||
console.info("Successfully published packages.");
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Failed at publishing packages with ", err.message);
|
||||
});
|
||||
|
||||
async function run() {
|
||||
const rootPackage = await readJSON(path.resolve(DIR, "../", PACKAGE_JSON));
|
||||
const workspacePaths = rootPackage.workspaces;
|
||||
|
||||
if (CURRENT_TAG === NEXT_TAG) {
|
||||
await makeReleaseCandidate();
|
||||
}
|
||||
|
||||
const workspaces = await Promise.all(workspacePaths.map(readWorkspace));
|
||||
|
||||
if (CURRENT_TAG === NEXT_TAG) {
|
||||
await upgradeWakuDependencies(workspaces);
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
workspaces
|
||||
.filter(async (info) => {
|
||||
const allowPublishing = await shouldBePublished(info);
|
||||
|
||||
if (allowPublishing) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
})
|
||||
.map(async (info) => {
|
||||
try {
|
||||
await exec(
|
||||
`npm publish --workspace ${info.workspace} --tag ${CURRENT_TAG} --access public`
|
||||
);
|
||||
console.info(
|
||||
`Successfully published ${info.workspace} with version ${info.version}.`
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(
|
||||
`Cannot publish ${info.workspace} with version ${info.version}. Error: ${err.message}`
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async function readJSON(path) {
|
||||
const rawJSON = await readFile(path, "utf-8");
|
||||
return JSON.parse(rawJSON);
|
||||
}
|
||||
|
||||
async function writeWorkspace(packagePath, text) {
|
||||
const resolvedPath = path.resolve(DIR, "../", packagePath, PACKAGE_JSON);
|
||||
await writeFile(resolvedPath, text);
|
||||
}
|
||||
|
||||
async function readWorkspace(packagePath) {
|
||||
const json = await readJSON(
|
||||
path.resolve(DIR, "../", packagePath, PACKAGE_JSON)
|
||||
);
|
||||
|
||||
return {
|
||||
name: json.name,
|
||||
private: !!json.private,
|
||||
version: json.version,
|
||||
workspace: packagePath,
|
||||
rawPackageJson: json
|
||||
};
|
||||
}
|
||||
|
||||
async function shouldBePublished(info) {
|
||||
if (info.private) {
|
||||
console.info(`Skipping ${info.name} because it is private.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const npmTag = `${info.name}@${info.version}`;
|
||||
const { stdout } = await exec(`npm view ${npmTag} version`);
|
||||
|
||||
if (stdout.trim() !== info.version.trim()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
console.info(`Workspace ${info.path} is already published.`);
|
||||
} catch (err) {
|
||||
if (err.message.includes("code E404")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
console.error(
|
||||
`Cannot check published version of ${info.path}. Received error: ${err.message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function makeReleaseCandidate() {
|
||||
try {
|
||||
console.info("Marking workspace versions as release candidates.");
|
||||
await exec(
|
||||
`npm version prerelease --preid $(git rev-parse --short HEAD) --workspaces true`
|
||||
);
|
||||
} catch (e) {
|
||||
console.error("Failed to mark release candidate versions.");
|
||||
}
|
||||
}
|
||||
|
||||
function readPublishTag() {
|
||||
const args = process.argv.slice(2);
|
||||
const tagIndex = args.indexOf("--tag");
|
||||
|
||||
if (tagIndex !== -1 && args[tagIndex + 1]) {
|
||||
return args[tagIndex + 1];
|
||||
}
|
||||
|
||||
return LATEST_TAG;
|
||||
}
|
||||
|
||||
async function upgradeWakuDependencies(workspaces) {
|
||||
console.log("Upgrading Waku dependencies in workspaces.");
|
||||
const map = workspaces.reduce((acc, item) => {
|
||||
if (!item.private) {
|
||||
acc[item.name] = item;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
const packageNames = Object.keys(map);
|
||||
const promises = workspaces.map(async (info) => {
|
||||
if (info.private) {
|
||||
return;
|
||||
}
|
||||
["dependencies", "devDependencies", "peerDependencies"].forEach((type) => {
|
||||
const deps = info.rawPackageJson[type];
|
||||
if (!deps) {
|
||||
return;
|
||||
}
|
||||
packageNames.forEach((name) => {
|
||||
if (deps[name]) {
|
||||
deps[name] = map[name].version;
|
||||
}
|
||||
});
|
||||
});
|
||||
try {
|
||||
await writeWorkspace(info.workspace, JSON.stringify(info.rawPackageJson));
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Failed to update package.json for ${info.name} with: `,
|
||||
error
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
for (const promise of promises) {
|
||||
await promise;
|
||||
}
|
||||
}
|
||||
@ -1,188 +0,0 @@
|
||||
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
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
})();
|
||||
35
examples/cli-chat/.eslintrc.json
Normal file
35
examples/cli-chat/.eslintrc.json
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": { "project": "./tsconfig.json" },
|
||||
"env": { "es6": true },
|
||||
"ignorePatterns": ["node_modules"],
|
||||
"plugins": ["import", "eslint-comments", "functional"],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:eslint-comments/recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:import/typescript",
|
||||
"prettier",
|
||||
"prettier/@typescript-eslint"
|
||||
],
|
||||
"globals": { "BigInt": true, "console": true, "WebAssembly": true },
|
||||
"rules": {
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"eslint-comments/disable-enable-pair": [
|
||||
"error",
|
||||
{ "allowWholeFile": true }
|
||||
],
|
||||
"eslint-comments/no-unused-disable": "error",
|
||||
"import/order": [
|
||||
"error",
|
||||
{ "newlines-between": "always", "alphabetize": { "order": "asc" } }
|
||||
],
|
||||
"no-constant-condition": ["error", { "checkLoops": false }],
|
||||
"sort-imports": [
|
||||
"error",
|
||||
{ "ignoreDeclarationSort": true, "ignoreCase": true }
|
||||
]
|
||||
}
|
||||
}
|
||||
4
examples/cli-chat/.gitignore
vendored
Normal file
4
examples/cli-chat/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
# Generated directories:
|
||||
.nyc_output/
|
||||
node_modules/
|
||||
/tsconfig.tsbuildinfo
|
||||
5
examples/cli-chat/.mocharc.json
Normal file
5
examples/cli-chat/.mocharc.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"extension": ["ts"],
|
||||
"spec": "src/**/*.spec.ts",
|
||||
"require": "ts-node/register"
|
||||
}
|
||||
3
examples/cli-chat/README.md
Normal file
3
examples/cli-chat/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# A NodeJS CLI Chat App powered by js-waku
|
||||
|
||||
See js-waku [README](../../README.md#cli-chat-app-nodejs) for details.
|
||||
13776
examples/cli-chat/package-lock.json
generated
Normal file
13776
examples/cli-chat/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
76
examples/cli-chat/package.json
Normal file
76
examples/cli-chat/package.json
Normal file
@ -0,0 +1,76 @@
|
||||
{
|
||||
"name": "js-waku-cli-chat",
|
||||
"version": "0.1.0",
|
||||
"description": "A NodeJS CLI Chat App powered by js-waku",
|
||||
"main": "./index.ts",
|
||||
"repository": "https://github.com/status-im/js-waku",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"keywords": [
|
||||
"waku",
|
||||
"decentralised",
|
||||
"communication"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "run-s build:*",
|
||||
"build:main": "tsc -p tsconfig.json",
|
||||
"fix": "run-s fix:*",
|
||||
"fix:prettier": "prettier \"src/**/*.ts\" \"./*.json\" --write",
|
||||
"fix:lint": "eslint src --ext .ts --fix",
|
||||
"start": "ts-node src/index.ts",
|
||||
"test": "run-s build test:*",
|
||||
"test:lint": "eslint src --ext .ts",
|
||||
"test:prettier": "prettier \"src/**/*.ts\" \"./*.json\" --list-different",
|
||||
"test:spelling": "cspell \"{README.md,src/**/*.ts}\" -c ../../.cspell.json",
|
||||
"test:unit": "nyc --silent mocha",
|
||||
"watch:build": "tsc -p tsconfig.json -w",
|
||||
"watch:test": "nyc --silent mocha --watch",
|
||||
"version": "standard-version",
|
||||
"reset-hard": "git clean -dfx && git reset --hard && npm i"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"dependencies": {
|
||||
"js-waku": "../../build/main",
|
||||
"libp2p-tcp": "^0.15.4",
|
||||
"prompt-sync": "^4.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@istanbuljs/nyc-config-typescript": "^1.0.1",
|
||||
"@types/app-root-path": "^1.2.4",
|
||||
"@types/chai": "^4.2.15",
|
||||
"@types/mocha": "^8.2.2",
|
||||
"@types/node": "^14.14.31",
|
||||
"@typescript-eslint/eslint-plugin": "^4.0.1",
|
||||
"@typescript-eslint/parser": "^4.0.1",
|
||||
"chai": "^4.3.4",
|
||||
"cspell": "^4.1.0",
|
||||
"eslint": "^7.8.0",
|
||||
"eslint-config-prettier": "^6.11.0",
|
||||
"eslint-plugin-eslint-comments": "^3.2.0",
|
||||
"eslint-plugin-functional": "^3.0.2",
|
||||
"eslint-plugin-import": "^2.22.0",
|
||||
"mocha": "^8.3.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"nyc": "^15.1.0",
|
||||
"prettier": "^2.1.1",
|
||||
"standard-version": "^9.0.0",
|
||||
"ts-node": "^9.1.1",
|
||||
"typedoc": "^0.20.29",
|
||||
"typescript": "^4.0.2"
|
||||
},
|
||||
"prettier": {
|
||||
"singleQuote": true
|
||||
},
|
||||
"files": [
|
||||
"!**/*.spec.*",
|
||||
"!**/*.json",
|
||||
"README.md"
|
||||
],
|
||||
"nyc": {
|
||||
"extends": "@istanbuljs/nyc-config-typescript",
|
||||
"exclude": [
|
||||
"**/*.spec.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
17
examples/cli-chat/src/chat.spec.ts
Normal file
17
examples/cli-chat/src/chat.spec.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { expect } from 'chai';
|
||||
import { ChatMessage } from 'js-waku';
|
||||
|
||||
import { formatMessage } from './chat';
|
||||
|
||||
describe('CLI Chat app', () => {
|
||||
it('Format message', () => {
|
||||
const date = new Date(234325324);
|
||||
const chatMessage = ChatMessage.fromUtf8String(
|
||||
date,
|
||||
'alice',
|
||||
'Hello world!'
|
||||
);
|
||||
|
||||
expect(formatMessage(chatMessage)).to.match(/^<.*> alice: Hello world!$/);
|
||||
});
|
||||
});
|
||||
130
examples/cli-chat/src/chat.ts
Normal file
130
examples/cli-chat/src/chat.ts
Normal file
@ -0,0 +1,130 @@
|
||||
import readline from 'readline';
|
||||
import util from 'util';
|
||||
|
||||
import { ChatMessage, StoreCodec, Waku, WakuMessage } from 'js-waku';
|
||||
import TCP from 'libp2p-tcp';
|
||||
import { multiaddr, Multiaddr } from 'multiaddr';
|
||||
|
||||
const ChatContentTopic = 'dingpu';
|
||||
|
||||
export default async function startChat(): Promise<void> {
|
||||
const opts = processArguments();
|
||||
|
||||
const waku = await Waku.create({
|
||||
listenAddresses: [opts.listenAddr],
|
||||
modules: { transport: [TCP] },
|
||||
});
|
||||
console.log('PeerId: ', waku.libp2p.peerId.toB58String());
|
||||
console.log('Listening on ');
|
||||
waku.libp2p.multiaddrs.forEach((address) => {
|
||||
console.log(`\t- ${address}`);
|
||||
});
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
|
||||
let nick = 'js-waku';
|
||||
try {
|
||||
const question = util.promisify(rl.question).bind(rl);
|
||||
// Looks like wrong type definition of promisify is picked.
|
||||
// May be related to https://github.com/DefinitelyTyped/DefinitelyTyped/issues/20497
|
||||
nick = ((await question(
|
||||
'Please choose a nickname: '
|
||||
)) as unknown) as string;
|
||||
} catch (e) {
|
||||
console.log('Using default nick. Due to ', e);
|
||||
}
|
||||
|
||||
console.log(`Hi, ${nick}!`);
|
||||
|
||||
waku.relay.addObserver(
|
||||
(message) => {
|
||||
if (message.payload) {
|
||||
const chatMsg = ChatMessage.decode(message.payload);
|
||||
console.log(formatMessage(chatMsg));
|
||||
}
|
||||
},
|
||||
[ChatContentTopic]
|
||||
);
|
||||
|
||||
if (opts.staticNode) {
|
||||
console.log(`Dialing ${opts.staticNode}`);
|
||||
await waku.dial(opts.staticNode);
|
||||
}
|
||||
|
||||
// If we connect to a peer with WakuStore, we run the protocol
|
||||
// TODO: Instead of doing it `once` it should always be done but
|
||||
// only new messages should be printed
|
||||
waku.libp2p.peerStore.once(
|
||||
'change:protocols',
|
||||
async ({ peerId, protocols }) => {
|
||||
if (protocols.includes(StoreCodec)) {
|
||||
console.log(
|
||||
`Retrieving archived messages from ${peerId.toB58String()}`
|
||||
);
|
||||
const messages = await waku.store.queryHistory(peerId, [
|
||||
ChatContentTopic,
|
||||
]);
|
||||
messages?.map((msg) => {
|
||||
if (msg.payload) {
|
||||
const chatMsg = ChatMessage.decode(msg.payload);
|
||||
console.log(formatMessage(chatMsg));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
console.log('Ready to chat!');
|
||||
rl.prompt();
|
||||
for await (const line of rl) {
|
||||
rl.prompt();
|
||||
const chatMessage = ChatMessage.fromUtf8String(new Date(), nick, line);
|
||||
|
||||
const msg = WakuMessage.fromBytes(chatMessage.encode(), ChatContentTopic);
|
||||
await waku.relay.send(msg);
|
||||
}
|
||||
}
|
||||
|
||||
interface Options {
|
||||
staticNode?: Multiaddr;
|
||||
listenAddr: string;
|
||||
}
|
||||
|
||||
function processArguments(): Options {
|
||||
const passedArgs = process.argv.slice(2);
|
||||
|
||||
let opts: Options = { listenAddr: '/ip4/0.0.0.0/tcp/0' };
|
||||
|
||||
while (passedArgs.length) {
|
||||
const arg = passedArgs.shift();
|
||||
switch (arg) {
|
||||
case '--staticNode':
|
||||
opts = Object.assign(opts, {
|
||||
staticNode: multiaddr(passedArgs.shift()!),
|
||||
});
|
||||
break;
|
||||
case '--listenAddr':
|
||||
opts = Object.assign(opts, { listenAddr: passedArgs.shift() });
|
||||
break;
|
||||
default:
|
||||
console.log(`Unsupported argument: ${arg}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
return opts;
|
||||
}
|
||||
|
||||
export function formatMessage(chatMsg: ChatMessage): string {
|
||||
const timestamp = chatMsg.timestamp.toLocaleString([], {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
hour12: false,
|
||||
});
|
||||
return `<${timestamp}> ${chatMsg.nick}: ${chatMsg.payloadAsUtf8}`;
|
||||
}
|
||||
5
examples/cli-chat/src/index.ts
Normal file
5
examples/cli-chat/src/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import startChat from './chat';
|
||||
|
||||
(async () => {
|
||||
await startChat();
|
||||
})();
|
||||
5
examples/cli-chat/src/types/types.d.ts
vendored
Normal file
5
examples/cli-chat/src/types/types.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
declare module 'libp2p-tcp' {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const TCP: any;
|
||||
export = TCP;
|
||||
}
|
||||
54
examples/cli-chat/tsconfig.json
Normal file
54
examples/cli-chat/tsconfig.json
Normal file
@ -0,0 +1,54 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"incremental": true,
|
||||
"target": "es2017",
|
||||
"rootDir": "src",
|
||||
"noEmit": true,
|
||||
"moduleResolution": "node",
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"inlineSourceMap": true,
|
||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
|
||||
"resolveJsonModule": true /* Include modules imported with .json extension. */,
|
||||
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */,
|
||||
"strictNullChecks": true /* Enable strict null checks. */,
|
||||
"strictFunctionTypes": true /* Enable strict checking of function types. */,
|
||||
"strictPropertyInitialization": true /* Enable strict checking of property initialization in classes. */,
|
||||
"noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */,
|
||||
"alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */,
|
||||
|
||||
/* Additional Checks */
|
||||
"noUnusedLocals": true /* Report errors on unused locals. */,
|
||||
"noUnusedParameters": true /* Report errors on unused parameters. */,
|
||||
"noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
|
||||
"noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
|
||||
/* Debugging Options */
|
||||
"traceResolution": false /* Report module resolution log messages. */,
|
||||
"listEmittedFiles": false /* Print names of generated files part of the compilation. */,
|
||||
"listFiles": false /* Print names of files part of the compilation. */,
|
||||
"pretty": true /* Stylize errors and messages using color and context. */,
|
||||
|
||||
// Due to broken types in indirect dependencies
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */,
|
||||
// "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */,
|
||||
|
||||
"lib": ["es2017"],
|
||||
"types": ["node", "mocha"],
|
||||
"typeRoots": ["node_modules/@types", "src/types"]
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules/**"],
|
||||
"compileOnSave": false,
|
||||
"ts-node": {
|
||||
"files": true
|
||||
}
|
||||
}
|
||||
23
examples/web-chat/.gitignore
vendored
Normal file
23
examples/web-chat/.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
3
examples/web-chat/.prettierignore
Normal file
3
examples/web-chat/.prettierignore
Normal file
@ -0,0 +1,3 @@
|
||||
# package.json is formatted by package managers, so we ignore it here
|
||||
package.json
|
||||
gen
|
||||
3
examples/web-chat/README.md
Normal file
3
examples/web-chat/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# A React Web Chat App powered by js-waku
|
||||
|
||||
See js-waku [README](../../README.md#web-chat-app-reactjs) for details.
|
||||
44966
examples/web-chat/package-lock.json
generated
Normal file
44966
examples/web-chat/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
63
examples/web-chat/package.json
Normal file
63
examples/web-chat/package.json
Normal file
@ -0,0 +1,63 @@
|
||||
{
|
||||
"name": "web-chat",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"homepage": "/js-waku",
|
||||
"dependencies": {
|
||||
"@livechat/ui-kit": "*",
|
||||
"js-waku": "../../build/main",
|
||||
"react": "^16.14.0",
|
||||
"react-dom": "^16.14.0",
|
||||
"server-name-generator": "^1.0.5",
|
||||
"web-vitals": "^1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "^5.11.10",
|
||||
"@testing-library/react": "^11.2.6",
|
||||
"@testing-library/user-event": "^12.8.3",
|
||||
"@types/jest": "^26.0.22",
|
||||
"@types/node": "^12.20.7",
|
||||
"@types/react": "^17.0.3",
|
||||
"@types/react-dom": "^17.0.3",
|
||||
"cspell": "^5.3.12",
|
||||
"gh-pages": "^3.1.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^2.2.1",
|
||||
"react-scripts": "4.0.3",
|
||||
"typescript": "^4.2.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test:unit": "react-scripts test",
|
||||
"eject": "react-scripts eject",
|
||||
"fix": "run-s fix:*",
|
||||
"test": "run-s build test:*",
|
||||
"test:lint": "eslint src --ext .ts --ext .tsx",
|
||||
"test:prettier": "prettier \"src/**/*.{ts,tsx}\" \"./*.json\" --list-different",
|
||||
"test:spelling": "cspell \"{README.md,.github/*.md,src/**/*.{ts,tsx},public/**/*.html}\" -c ../../.cspell.json",
|
||||
"fix:prettier": "prettier \"src/**/*.{ts,tsx}\" \"./*.json\" --write",
|
||||
"fix:lint": "eslint src --ext .ts --ext .tsx --fix",
|
||||
"js-waku:build": "cd ../; npm run build",
|
||||
"predeploy": "run-s js-waku:build build",
|
||||
"deploy": "gh-pages -d build"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
42
examples/web-chat/public/index.html
Normal file
42
examples/web-chat/public/index.html
Normal file
@ -0,0 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Chat app powered by js-waku"
|
||||
/>
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>Waku v2 chat app</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
25
examples/web-chat/public/manifest.json
Normal file
25
examples/web-chat/public/manifest.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"short_name": "Waku v2 chat app",
|
||||
"name": "Chat app powered by js-waku",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
3
examples/web-chat/public/robots.txt
Normal file
3
examples/web-chat/public/robots.txt
Normal file
@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
38
examples/web-chat/src/App.css
Normal file
38
examples/web-chat/src/App.css
Normal file
@ -0,0 +1,38 @@
|
||||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
160
examples/web-chat/src/App.tsx
Normal file
160
examples/web-chat/src/App.tsx
Normal file
@ -0,0 +1,160 @@
|
||||
import PeerId from 'peer-id';
|
||||
import { useEffect, useState } from 'react';
|
||||
import './App.css';
|
||||
import { ChatMessage, WakuMessage, StoreCodec, Waku } from 'js-waku';
|
||||
import handleCommand from './command';
|
||||
import Room from './Room';
|
||||
import { WakuContext } from './WakuContext';
|
||||
import { ThemeProvider } from '@livechat/ui-kit';
|
||||
import { generate } from 'server-name-generator';
|
||||
|
||||
const themes = {
|
||||
AuthorName: {
|
||||
css: {
|
||||
fontSize: '1.1em',
|
||||
},
|
||||
},
|
||||
Message: {
|
||||
css: {
|
||||
margin: '0em',
|
||||
padding: '0em',
|
||||
fontSize: '0.83em',
|
||||
},
|
||||
},
|
||||
MessageText: {
|
||||
css: {
|
||||
margin: '0em',
|
||||
padding: '0.1em',
|
||||
paddingLeft: '1em',
|
||||
fontSize: '1.1em',
|
||||
},
|
||||
},
|
||||
MessageGroup: {
|
||||
css: {
|
||||
margin: '0em',
|
||||
padding: '0.2em',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const ChatContentTopic = 'dingpu';
|
||||
|
||||
export default function App() {
|
||||
let [newMessages, setNewMessages] = useState<ChatMessage[]>([]);
|
||||
let [archivedMessages, setArchivedMessages] = useState<ChatMessage[]>([]);
|
||||
let [stateWaku, setWaku] = useState<Waku | undefined>(undefined);
|
||||
let [nick, setNick] = useState<string>(generate());
|
||||
|
||||
useEffect(() => {
|
||||
const handleRelayMessage = (wakuMsg: WakuMessage) => {
|
||||
if (wakuMsg.payload) {
|
||||
const chatMsg = ChatMessage.decode(wakuMsg.payload);
|
||||
if (chatMsg) {
|
||||
setNewMessages([chatMsg]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleProtocolChange = async (
|
||||
waku: Waku,
|
||||
{ peerId, protocols }: { peerId: PeerId; protocols: string[] }
|
||||
) => {
|
||||
if (protocols.includes(StoreCodec)) {
|
||||
console.log(`${peerId.toB58String()}: retrieving archived messages}`);
|
||||
try {
|
||||
const response = await waku.store.queryHistory(peerId, [
|
||||
ChatContentTopic,
|
||||
]);
|
||||
console.log(`${peerId.toB58String()}: messages retrieved:`, response);
|
||||
if (response) {
|
||||
const messages = response
|
||||
.map((wakuMsg) => wakuMsg.payload)
|
||||
.filter((payload) => !!payload)
|
||||
.map((payload) => ChatMessage.decode(payload as Uint8Array));
|
||||
setArchivedMessages(messages);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(
|
||||
`${peerId.toB58String()}: error encountered when retrieving archived messages`,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!stateWaku) {
|
||||
initWaku(setWaku)
|
||||
.then(() => console.log('Waku init done'))
|
||||
.catch((e) => console.log('Waku init failed ', e));
|
||||
} else {
|
||||
stateWaku.relay.addObserver(handleRelayMessage, [ChatContentTopic]);
|
||||
|
||||
stateWaku.libp2p.peerStore.on(
|
||||
'change:protocols',
|
||||
handleProtocolChange.bind({}, stateWaku)
|
||||
);
|
||||
|
||||
// To clean up listener when component unmounts
|
||||
return () => {
|
||||
stateWaku?.libp2p.peerStore.removeListener(
|
||||
'change:protocols',
|
||||
handleProtocolChange.bind({}, stateWaku)
|
||||
);
|
||||
};
|
||||
}
|
||||
}, [stateWaku]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="chat-app"
|
||||
style={{ height: '100vh', width: '100vw', overflow: 'hidden' }}
|
||||
>
|
||||
<WakuContext.Provider value={{ waku: stateWaku }}>
|
||||
<ThemeProvider theme={themes}>
|
||||
<Room
|
||||
nick={nick}
|
||||
newMessages={newMessages}
|
||||
archivedMessages={archivedMessages}
|
||||
commandHandler={(input: string) => {
|
||||
const { command, response } = handleCommand(
|
||||
input,
|
||||
stateWaku,
|
||||
setNick
|
||||
);
|
||||
const commandMessages = response.map((msg) => {
|
||||
return ChatMessage.fromUtf8String(new Date(), command, msg);
|
||||
});
|
||||
setNewMessages(commandMessages);
|
||||
}}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
</WakuContext.Provider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
async function initWaku(setter: (waku: Waku) => void) {
|
||||
try {
|
||||
const waku = await Waku.create({
|
||||
config: {
|
||||
pubsub: {
|
||||
enabled: true,
|
||||
emitSelf: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
setter(waku);
|
||||
|
||||
waku.addPeerToAddressBook(
|
||||
'16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ',
|
||||
['/dns4/node-01.do-ams3.jdev.misc.statusim.net/tcp/7010/wss']
|
||||
);
|
||||
waku.addPeerToAddressBook(
|
||||
'16Uiu2HAmSyrYVycqBCWcHyNVQS6zYQcdQbwyov1CDijboVRsQS37',
|
||||
['/dns4/node-01.do-ams3.jdev.misc.statusim.net/tcp/7009/wss']
|
||||
);
|
||||
} catch (e) {
|
||||
console.log('Issue starting waku ', e);
|
||||
}
|
||||
}
|
||||
140
examples/web-chat/src/ChatList.tsx
Normal file
140
examples/web-chat/src/ChatList.tsx
Normal file
@ -0,0 +1,140 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { ChatMessage } from 'js-waku';
|
||||
import {
|
||||
Message,
|
||||
MessageText,
|
||||
MessageGroup,
|
||||
MessageList,
|
||||
} from '@livechat/ui-kit';
|
||||
|
||||
interface Props {
|
||||
archivedMessages: ChatMessage[];
|
||||
newMessages: ChatMessage[];
|
||||
}
|
||||
|
||||
export default function ChatList(props: Props) {
|
||||
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
||||
let updatedMessages;
|
||||
|
||||
if (IsThereNewMessages(props.newMessages, messages)) {
|
||||
updatedMessages = messages.slice().concat(props.newMessages);
|
||||
if (IsThereNewMessages(props.archivedMessages, updatedMessages)) {
|
||||
updatedMessages = copyMergeUniqueReplace(
|
||||
props.archivedMessages,
|
||||
updatedMessages
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (IsThereNewMessages(props.archivedMessages, messages)) {
|
||||
updatedMessages = copyMergeUniqueReplace(
|
||||
props.archivedMessages,
|
||||
messages
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (updatedMessages) {
|
||||
setMessages(updatedMessages);
|
||||
}
|
||||
|
||||
const messagesGroupedBySender = groupMessagesBySender(messages).map(
|
||||
(currentMessageGroup) => (
|
||||
<MessageGroup onlyFirstWithMeta>
|
||||
{currentMessageGroup.map((currentMessage) => (
|
||||
<Message
|
||||
key={
|
||||
currentMessage.timestamp.valueOf() +
|
||||
currentMessage.nick +
|
||||
currentMessage.payloadAsUtf8
|
||||
}
|
||||
authorName={currentMessage.nick}
|
||||
date={formatDisplayDate(currentMessage)}
|
||||
>
|
||||
<MessageText>{currentMessage.payloadAsUtf8}</MessageText>
|
||||
</Message>
|
||||
))}
|
||||
</MessageGroup>
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<MessageList active containScrollInSubtree>
|
||||
{messagesGroupedBySender}
|
||||
<AlwaysScrollToBottom newMessages={props.newMessages} />
|
||||
</MessageList>
|
||||
);
|
||||
}
|
||||
|
||||
function groupMessagesBySender(messageArray: ChatMessage[]): ChatMessage[][] {
|
||||
let currentSender = -1;
|
||||
let lastNick = '';
|
||||
let messagesBySender: ChatMessage[][] = [];
|
||||
let currentSenderMessage = 0;
|
||||
|
||||
for (let currentMessage of messageArray) {
|
||||
if (lastNick !== currentMessage.nick) {
|
||||
currentSender++;
|
||||
messagesBySender[currentSender] = [];
|
||||
currentSenderMessage = 0;
|
||||
lastNick = currentMessage.nick;
|
||||
}
|
||||
messagesBySender[currentSender][currentSenderMessage++] = currentMessage;
|
||||
}
|
||||
return messagesBySender;
|
||||
}
|
||||
|
||||
function formatDisplayDate(message: ChatMessage): string {
|
||||
return message.timestamp.toLocaleString([], {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
hour12: false,
|
||||
});
|
||||
}
|
||||
|
||||
const AlwaysScrollToBottom = (props: { newMessages: ChatMessage[] }) => {
|
||||
const elementRef = useRef<HTMLDivElement>();
|
||||
|
||||
useEffect(() => {
|
||||
// @ts-ignore
|
||||
elementRef.current.scrollIntoView();
|
||||
}, [props.newMessages]);
|
||||
|
||||
// @ts-ignore
|
||||
return <div ref={elementRef} />;
|
||||
};
|
||||
|
||||
function IsThereNewMessages(
|
||||
newValues: ChatMessage[],
|
||||
currentValues: ChatMessage[]
|
||||
): boolean {
|
||||
if (newValues.length === 0) return false;
|
||||
if (currentValues.length === 0) return true;
|
||||
|
||||
return !newValues.find((newMsg) =>
|
||||
currentValues.find(isEqual.bind({}, newMsg))
|
||||
);
|
||||
}
|
||||
|
||||
function copyMergeUniqueReplace(
|
||||
newValues: ChatMessage[],
|
||||
currentValues: ChatMessage[]
|
||||
) {
|
||||
const copy = currentValues.slice();
|
||||
newValues.forEach((msg) => {
|
||||
if (!copy.find(isEqual.bind({}, msg))) {
|
||||
copy.push(msg);
|
||||
}
|
||||
});
|
||||
copy.sort((a, b) => a.timestamp.valueOf() - b.timestamp.valueOf());
|
||||
return copy;
|
||||
}
|
||||
|
||||
function isEqual(lhs: ChatMessage, rhs: ChatMessage): boolean {
|
||||
return (
|
||||
lhs.nick === rhs.nick &&
|
||||
lhs.payloadAsUtf8 === rhs.payloadAsUtf8 &&
|
||||
lhs.timestamp.toString() === rhs.timestamp.toString()
|
||||
);
|
||||
}
|
||||
58
examples/web-chat/src/MessageInput.tsx
Normal file
58
examples/web-chat/src/MessageInput.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
import { ChangeEvent, KeyboardEvent, useState } from 'react';
|
||||
import { useWaku } from './WakuContext';
|
||||
import {
|
||||
TextInput,
|
||||
TextComposer,
|
||||
Row,
|
||||
Fill,
|
||||
Fit,
|
||||
SendButton,
|
||||
} from '@livechat/ui-kit';
|
||||
|
||||
interface Props {
|
||||
sendMessage: ((msg: string) => Promise<void>) | undefined;
|
||||
}
|
||||
|
||||
export default function MessageInput(props: Props) {
|
||||
const [inputText, setInputText] = useState<string>('');
|
||||
const { waku } = useWaku();
|
||||
|
||||
const sendMessage = async () => {
|
||||
if (props.sendMessage) {
|
||||
await props.sendMessage(inputText);
|
||||
setInputText('');
|
||||
}
|
||||
};
|
||||
|
||||
const messageHandler = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setInputText(event.target.value);
|
||||
};
|
||||
|
||||
const keyPressHandler = async (event: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key === 'Enter') {
|
||||
await sendMessage();
|
||||
}
|
||||
};
|
||||
|
||||
// Enable the button if there are relay peers available or the user is sending a command
|
||||
const activeButton =
|
||||
(waku && waku.relay.getPeers().size !== 0) || inputText.startsWith('/');
|
||||
|
||||
return (
|
||||
<TextComposer
|
||||
onKeyDown={keyPressHandler}
|
||||
onChange={messageHandler}
|
||||
active={activeButton}
|
||||
onButtonClick={sendMessage}
|
||||
>
|
||||
<Row align="center">
|
||||
<Fill>
|
||||
<TextInput value={inputText} />
|
||||
</Fill>
|
||||
<Fit>
|
||||
<SendButton />
|
||||
</Fit>
|
||||
</Row>
|
||||
</TextComposer>
|
||||
);
|
||||
}
|
||||
62
examples/web-chat/src/Room.tsx
Normal file
62
examples/web-chat/src/Room.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
import { ChatMessage, WakuMessage } from 'js-waku';
|
||||
import { ChatContentTopic } from './App';
|
||||
import ChatList from './ChatList';
|
||||
import MessageInput from './MessageInput';
|
||||
import { useWaku } from './WakuContext';
|
||||
import { TitleBar } from '@livechat/ui-kit';
|
||||
|
||||
interface Props {
|
||||
newMessages: ChatMessage[];
|
||||
archivedMessages: ChatMessage[];
|
||||
commandHandler: (cmd: string) => void;
|
||||
nick: string;
|
||||
}
|
||||
|
||||
export default function Room(props: Props) {
|
||||
const { waku } = useWaku();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="chat-container"
|
||||
style={{ height: '98vh', display: 'flex', flexDirection: 'column' }}
|
||||
>
|
||||
<TitleBar title="Waku v2 chat app" />
|
||||
<ChatList
|
||||
newMessages={props.newMessages}
|
||||
archivedMessages={props.archivedMessages}
|
||||
/>
|
||||
<MessageInput
|
||||
sendMessage={
|
||||
waku
|
||||
? async (messageToSend) => {
|
||||
return handleMessage(
|
||||
messageToSend,
|
||||
props.nick,
|
||||
props.commandHandler,
|
||||
waku.relay.send.bind(waku.relay)
|
||||
);
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
async function handleMessage(
|
||||
message: string,
|
||||
nick: string,
|
||||
commandHandler: (cmd: string) => void,
|
||||
messageSender: (msg: WakuMessage) => Promise<void>
|
||||
) {
|
||||
if (message.startsWith('/')) {
|
||||
commandHandler(message);
|
||||
} else {
|
||||
const chatMessage = ChatMessage.fromUtf8String(new Date(), nick, message);
|
||||
const wakuMsg = WakuMessage.fromBytes(
|
||||
chatMessage.encode(),
|
||||
ChatContentTopic
|
||||
);
|
||||
return messageSender(wakuMsg);
|
||||
}
|
||||
}
|
||||
9
examples/web-chat/src/WakuContext.ts
Normal file
9
examples/web-chat/src/WakuContext.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { createContext, useContext } from 'react';
|
||||
import { Waku } from 'js-waku';
|
||||
|
||||
export type WakuContextType = {
|
||||
waku?: Waku;
|
||||
};
|
||||
|
||||
export const WakuContext = createContext<WakuContextType>({ waku: undefined });
|
||||
export const useWaku = () => useContext(WakuContext);
|
||||
30
examples/web-chat/src/WakuMock.test.ts
Normal file
30
examples/web-chat/src/WakuMock.test.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import WakuMock, { Message } from './WakuMock';
|
||||
|
||||
test('Messages are emitted', async () => {
|
||||
const wakuMock = await WakuMock.create();
|
||||
|
||||
let message: Message;
|
||||
wakuMock.on('message', (msg) => {
|
||||
message = msg;
|
||||
});
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
// @ts-ignore
|
||||
expect(message.message).toBeDefined();
|
||||
});
|
||||
|
||||
test('Messages are sent', async () => {
|
||||
const wakuMock = await WakuMock.create();
|
||||
|
||||
const text = 'This is a message.';
|
||||
|
||||
let message: Message;
|
||||
wakuMock.on('message', (msg) => {
|
||||
message = msg;
|
||||
});
|
||||
|
||||
await wakuMock.send(text);
|
||||
|
||||
// @ts-ignore
|
||||
expect(message.message).toEqual(text);
|
||||
});
|
||||
69
examples/web-chat/src/WakuMock.ts
Normal file
69
examples/web-chat/src/WakuMock.ts
Normal file
@ -0,0 +1,69 @@
|
||||
class EventEmitter<T> {
|
||||
public callbacks: { [key: string]: Array<(data: T) => void> };
|
||||
|
||||
constructor() {
|
||||
this.callbacks = {};
|
||||
}
|
||||
|
||||
on(event: string, cb: (data: T) => void) {
|
||||
if (!this.callbacks[event]) this.callbacks[event] = [];
|
||||
this.callbacks[event].push(cb);
|
||||
}
|
||||
|
||||
emit(event: string, data: T) {
|
||||
let cbs = this.callbacks[event];
|
||||
if (cbs) {
|
||||
cbs.forEach((cb) => cb(data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface Message {
|
||||
timestamp: Date;
|
||||
handle: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export default class WakuMock extends EventEmitter<Message> {
|
||||
index: number;
|
||||
intervalId?: number | NodeJS.Timeout;
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
this.index = 0;
|
||||
}
|
||||
|
||||
public static async create(): Promise<WakuMock> {
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
|
||||
const wakuMock = new WakuMock();
|
||||
wakuMock.startInterval();
|
||||
return wakuMock;
|
||||
}
|
||||
|
||||
public async send(message: string): Promise<void> {
|
||||
const timestamp = new Date();
|
||||
const handle = 'me';
|
||||
this.emit('message', {
|
||||
timestamp,
|
||||
handle,
|
||||
message,
|
||||
});
|
||||
}
|
||||
|
||||
private startInterval() {
|
||||
if (this.intervalId === undefined) {
|
||||
this.intervalId = setInterval(this.emitMessage.bind(this), 1000);
|
||||
}
|
||||
}
|
||||
|
||||
private emitMessage() {
|
||||
const handle = 'you';
|
||||
const timestamp = new Date();
|
||||
this.emit('message', {
|
||||
timestamp,
|
||||
handle,
|
||||
message: `This is message #${this.index++}.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
143
examples/web-chat/src/command.ts
Normal file
143
examples/web-chat/src/command.ts
Normal file
@ -0,0 +1,143 @@
|
||||
import { multiaddr } from 'multiaddr';
|
||||
import PeerId from 'peer-id';
|
||||
import { Waku } from 'js-waku';
|
||||
|
||||
function help(): string[] {
|
||||
return [
|
||||
'/nick <nickname>: set a new nickname',
|
||||
'/info: some information about the node',
|
||||
'/connect <Multiaddr>: connect to the given peer',
|
||||
'/help: Display this help',
|
||||
];
|
||||
}
|
||||
|
||||
function nick(
|
||||
nick: string | undefined,
|
||||
setNick: (nick: string) => void
|
||||
): string[] {
|
||||
if (!nick) {
|
||||
return ['No nick provided'];
|
||||
}
|
||||
setNick(nick);
|
||||
return [`New nick: ${nick}`];
|
||||
}
|
||||
|
||||
function info(waku: Waku | undefined): string[] {
|
||||
if (!waku) {
|
||||
return ['Waku node is starting'];
|
||||
}
|
||||
return [`PeerId: ${waku.libp2p.peerId.toB58String()}`];
|
||||
}
|
||||
|
||||
function connect(peer: string | undefined, waku: Waku | undefined): string[] {
|
||||
if (!waku) {
|
||||
return ['Waku node is starting'];
|
||||
}
|
||||
if (!peer) {
|
||||
return ['No peer provided'];
|
||||
}
|
||||
try {
|
||||
const peerMultiaddr = multiaddr(peer);
|
||||
const peerId = peerMultiaddr.getPeerId();
|
||||
if (!peerId) {
|
||||
return ['Peer Id needed to dial'];
|
||||
}
|
||||
waku.addPeerToAddressBook(PeerId.createFromB58String(peerId), [
|
||||
peerMultiaddr,
|
||||
]);
|
||||
return [
|
||||
`${peerId}: ${peerMultiaddr.toString()} added to address book, autodial in progress`,
|
||||
];
|
||||
} catch (e) {
|
||||
return ['Invalid multiaddr: ' + e];
|
||||
}
|
||||
}
|
||||
|
||||
function peers(waku: Waku | undefined): string[] {
|
||||
if (!waku) {
|
||||
return ['Waku node is starting'];
|
||||
}
|
||||
let response: string[] = [];
|
||||
waku.libp2p.peerStore.peers.forEach((peer, peerId) => {
|
||||
response.push(peerId + ':');
|
||||
let addresses = ' addresses: [';
|
||||
peer.addresses.forEach(({ multiaddr }) => {
|
||||
addresses += ' ' + multiaddr.toString() + ',';
|
||||
});
|
||||
addresses = addresses.replace(/,$/, '');
|
||||
addresses += ']';
|
||||
response.push(addresses);
|
||||
let protocols = ' protocols: [';
|
||||
protocols += peer.protocols;
|
||||
protocols += ']';
|
||||
response.push(protocols);
|
||||
});
|
||||
if (response.length === 0) {
|
||||
response.push('Not connected to any peer.');
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
function connections(waku: Waku | undefined): string[] {
|
||||
if (!waku) {
|
||||
return ['Waku node is starting'];
|
||||
}
|
||||
let response: string[] = [];
|
||||
waku.libp2p.connections.forEach(
|
||||
(
|
||||
connections: import('libp2p-interfaces/src/connection/connection')[],
|
||||
peerId
|
||||
) => {
|
||||
response.push(peerId + ':');
|
||||
let strConnections = ' connections: [';
|
||||
connections.forEach((connection) => {
|
||||
strConnections += JSON.stringify(connection.stat);
|
||||
strConnections += '; ' + JSON.stringify(connection.streams);
|
||||
});
|
||||
strConnections += ']';
|
||||
response.push(strConnections);
|
||||
}
|
||||
);
|
||||
if (response.length === 0) {
|
||||
response.push('Not connected to any peer.');
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
export default function handleCommand(
|
||||
input: string,
|
||||
waku: Waku | undefined,
|
||||
setNick: (nick: string) => void
|
||||
): { command: string; response: string[] } {
|
||||
let response: string[] = [];
|
||||
const args = parseInput(input);
|
||||
const command = args.shift()!;
|
||||
switch (command) {
|
||||
case '/help':
|
||||
help().map((str) => response.push(str));
|
||||
break;
|
||||
case '/nick':
|
||||
nick(args.shift(), setNick).map((str) => response.push(str));
|
||||
break;
|
||||
case '/info':
|
||||
info(waku).map((str) => response.push(str));
|
||||
break;
|
||||
case '/connect':
|
||||
connect(args.shift(), waku).map((str) => response.push(str));
|
||||
break;
|
||||
case '/peers':
|
||||
peers(waku).map((str) => response.push(str));
|
||||
break;
|
||||
case '/connections':
|
||||
connections(waku).map((str) => response.push(str));
|
||||
break;
|
||||
default:
|
||||
response.push(`Unknown Command '${command}'`);
|
||||
}
|
||||
return { command, response };
|
||||
}
|
||||
|
||||
export function parseInput(input: string): string[] {
|
||||
const clean = input.trim().replaceAll(/\s\s+/g, ' ');
|
||||
return clean.split(' ');
|
||||
}
|
||||
30
examples/web-chat/src/index.css
Normal file
30
examples/web-chat/src/index.css
Normal file
@ -0,0 +1,30 @@
|
||||
@import-normalize; /* bring in normalize.css styles */
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
|
||||
.room-row {
|
||||
text-align: left;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.room-row:after {
|
||||
clear: both;
|
||||
content: "";
|
||||
display: table;
|
||||
}
|
||||
|
||||
.chat-room{
|
||||
margin: 2px;
|
||||
}
|
||||
11
examples/web-chat/src/index.tsx
Normal file
11
examples/web-chat/src/index.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
1
examples/web-chat/src/react-app-env.d.ts
vendored
Normal file
1
examples/web-chat/src/react-app-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="react-scripts" />
|
||||
5
examples/web-chat/src/setupTests.ts
Normal file
5
examples/web-chat/src/setupTests.ts
Normal file
@ -0,0 +1,5 @@
|
||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
||||
1
examples/web-chat/src/types/types.d.ts
vendored
Normal file
1
examples/web-chat/src/types/types.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
declare module '@livechat/ui-kit';
|
||||
24
examples/web-chat/tsconfig.json
Normal file
24
examples/web-chat/tsconfig.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"incremental": true,
|
||||
"target": "es2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"typeRoots": ["node_modules/@types", "src/types"]
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
26
flake.lock
generated
26
flake.lock
generated
@ -1,26 +0,0 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1761016216,
|
||||
"narHash": "sha256-G/iC4t/9j/52i/nm+0/4ybBmAF4hzR8CNHC75qEhjHo=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "481cf557888e05d3128a76f14c76397b7d7cc869",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"ref": "nixos-25.05",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
33
flake.nix
33
flake.nix
@ -1,33 +0,0 @@
|
||||
{
|
||||
description = "Nix flake development shell.";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "nixpkgs/nixos-25.05";
|
||||
};
|
||||
|
||||
outputs =
|
||||
{ self, nixpkgs }:
|
||||
let
|
||||
supportedSystems = [
|
||||
"x86_64-linux"
|
||||
"aarch64-linux"
|
||||
"x86_64-darwin"
|
||||
"aarch64-darwin"
|
||||
];
|
||||
forEachSystem = nixpkgs.lib.genAttrs supportedSystems;
|
||||
pkgsFor = forEachSystem (system: import nixpkgs { inherit system; });
|
||||
in
|
||||
rec {
|
||||
formatter = forEachSystem (system: pkgsFor.${system}.nixpkgs-fmt);
|
||||
|
||||
devShells = forEachSystem (system: {
|
||||
default = pkgsFor.${system}.mkShellNoCC {
|
||||
packages = with pkgsFor.${system}.buildPackages; [
|
||||
git # 2.44.1
|
||||
openssh # 9.7p1
|
||||
nodejs_20 # v20.15.1
|
||||
];
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
@ -1,72 +0,0 @@
|
||||
/* eslint-env node */
|
||||
const playwright = require("playwright");
|
||||
const webpack = require("webpack");
|
||||
|
||||
if (!process.env.CHROME_BIN) {
|
||||
process.env.CHROME_BIN = playwright.chromium.executablePath();
|
||||
}
|
||||
console.log("Using CHROME_BIN:", process.env.CHROME_BIN);
|
||||
if (!process.env.FIREFOX_BIN) {
|
||||
process.env.FIREFOX_BIN = playwright.firefox.executablePath();
|
||||
}
|
||||
console.log("Using FIREFOX_BIN:", process.env.FIREFOX_BIN);
|
||||
|
||||
module.exports = function (config) {
|
||||
const configuration = {
|
||||
frameworks: ["webpack", "mocha"],
|
||||
files: ["src/**/!(node).spec.ts"],
|
||||
preprocessors: {
|
||||
"src/**/!(node).spec.ts": ["webpack"]
|
||||
},
|
||||
envPreprocessor: ["CI"],
|
||||
reporters: ["progress"],
|
||||
browsers: process.env.CI
|
||||
? ["ChromeHeadlessCI", "FirefoxHeadless"]
|
||||
: ["ChromeHeadless", "FirefoxHeadless"],
|
||||
customLaunchers: {
|
||||
ChromeHeadlessCI: {
|
||||
base: "ChromeHeadless",
|
||||
flags: [
|
||||
"--no-sandbox",
|
||||
"--disable-gpu",
|
||||
"--disable-dev-shm-usage",
|
||||
"--disable-software-rasterizer",
|
||||
"--disable-extensions"
|
||||
]
|
||||
}
|
||||
},
|
||||
singleRun: true,
|
||||
client: {
|
||||
mocha: {
|
||||
timeout: 6000 // Default is 2s
|
||||
}
|
||||
},
|
||||
webpack: {
|
||||
mode: "development",
|
||||
module: {
|
||||
rules: [{ test: /\.([cm]?ts|tsx)$/, loader: "ts-loader" }]
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
"process.env.CI": process.env.CI || false,
|
||||
"process.env.DISPLAY": "Browser"
|
||||
}),
|
||||
new webpack.ProvidePlugin({
|
||||
process: "process/browser.js"
|
||||
})
|
||||
],
|
||||
resolve: {
|
||||
extensions: [".ts", ".tsx", ".js"],
|
||||
extensionAlias: {
|
||||
".js": [".js", ".ts"],
|
||||
".cjs": [".cjs", ".cts"],
|
||||
".mjs": [".mjs", ".mts"]
|
||||
}
|
||||
},
|
||||
stats: { warnings: false },
|
||||
devtool: "inline-source-map"
|
||||
}
|
||||
};
|
||||
|
||||
config.set(configuration);
|
||||
};
|
||||
21
netlify.toml
Normal file
21
netlify.toml
Normal file
@ -0,0 +1,21 @@
|
||||
[build]
|
||||
publish = "build/html/"
|
||||
|
||||
# Default build command.
|
||||
command = '''
|
||||
npm install
|
||||
npm run build
|
||||
npm run doc:html
|
||||
cd examples/web-chat
|
||||
npm install
|
||||
npm run build
|
||||
cd ../../
|
||||
mkdir -p ./build/html
|
||||
mv -v examples/web-chat/build ./build/html/js-waku
|
||||
mv -v build/docs ./build/html/
|
||||
'''
|
||||
|
||||
[[redirects]]
|
||||
from = "/"
|
||||
to = "/js-waku"
|
||||
status = 200
|
||||
1
nim-waku
Submodule
1
nim-waku
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 7c5df3379b42f1e5ddad66e84e152aec6ebdf10d
|
||||
54535
package-lock.json
generated
54535
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
197
package.json
197
package.json
@ -1,83 +1,134 @@
|
||||
{
|
||||
"name": "@waku/root",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"workspaces": [
|
||||
"packages/proto",
|
||||
"packages/interfaces",
|
||||
"packages/utils",
|
||||
"packages/enr",
|
||||
"packages/core",
|
||||
"packages/discovery",
|
||||
"packages/message-encryption",
|
||||
"packages/sds",
|
||||
"packages/rln",
|
||||
"packages/sdk",
|
||||
"packages/relay",
|
||||
"packages/run",
|
||||
"packages/tests",
|
||||
"packages/reliability-tests",
|
||||
"packages/browser-tests",
|
||||
"packages/build-utils",
|
||||
"packages/react"
|
||||
"name": "js-waku",
|
||||
"version": "0.2.0",
|
||||
"description": "TypeScript implementation of the Waku v2 protocol",
|
||||
"main": "build/main/index.js",
|
||||
"typings": "build/main/index.d.ts",
|
||||
"module": "build/module/index.js",
|
||||
"repository": "https://github.com/status-im/js-waku",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"keywords": [
|
||||
"waku",
|
||||
"decentralised",
|
||||
"communication"
|
||||
],
|
||||
"scripts": {
|
||||
"prepare": "husky",
|
||||
"build": "npm run build --workspaces --if-present",
|
||||
"build:esm": "npm run build:esm --workspaces --if-present",
|
||||
"size": "npm run build && size-limit",
|
||||
"build": "run-s build:*",
|
||||
"build:main": "tsc -p tsconfig.json",
|
||||
"build:module": "tsc -p tsconfig.module.json",
|
||||
"build:dev": "tsc -p tsconfig.dev.json",
|
||||
"fix": "run-s fix:*",
|
||||
"fix:workspaces": "npm run fix --workspaces --if-present",
|
||||
"check": "run-s check:*",
|
||||
"check:workspaces": "npm run check --workspaces --if-present",
|
||||
"check:ws": "[ $(ls -1 ./packages|wc -l) -eq $(cat package.json | jq '.workspaces | length') ] || exit 1 # check no packages left behind",
|
||||
"test": "NODE_ENV=test npm run test --workspaces --if-present",
|
||||
"test:browser": "NODE_ENV=test npm run test:browser --workspaces --if-present",
|
||||
"test:node": "NODE_ENV=test npm run test:node --workspaces --if-present",
|
||||
"test:longevity": "npm --prefix packages/reliability-tests run test:longevity",
|
||||
"test:high-throughput": "npm --prefix packages/reliability-tests run test:high-throughput",
|
||||
"test:throughput-sizes": "npm --prefix packages/reliability-tests run test:throughput-sizes",
|
||||
"test:network-latency": "npm --prefix packages/reliability-tests run test:network-latency",
|
||||
"test:low-bandwidth": "npm --prefix packages/reliability-tests run test:low-bandwidth",
|
||||
"test:packet-loss": "npm --prefix packages/reliability-tests run test:packet-loss",
|
||||
"proto": "npm run proto --workspaces --if-present",
|
||||
"deploy": "node ci/deploy.js",
|
||||
"doc": "run-s doc:*",
|
||||
"doc:html": "typedoc --options typedoc.cjs",
|
||||
"doc:cname": "echo 'js.waku.org' > docs/CNAME",
|
||||
"publish": "node ./ci/publish.js"
|
||||
"fix:prettier": "prettier \"src/**/*.ts\" \"./*.json\" --write",
|
||||
"fix:lint": "eslint src --ext .ts --fix",
|
||||
"pretest": "run-s pretest:*",
|
||||
"pretest:1-init-git-submodules": "[ -f './nim-waku/build/wakunode2' ] || git submodule update --init --recursive",
|
||||
"pretest:2-build-nim-waku": "[ -f './nim-waku/build/wakunode2' ] || run-s nim-waku:build",
|
||||
"nim-waku:build": "(cd nim-waku; NIMFLAGS=\"-d:chronicles_colors=off -d:chronicles_sinks=textlines -d:chronicles_log_level=TRACE\" make -j$(nproc --all 2>/dev/null || echo 2) wakunode2)",
|
||||
"nim-waku:upgrade": "(cd nim-waku && git pull origin master && rm -rf ./build/ ./vendor && make -j$(nproc --all 2>/dev/null || echo 2) update) && run-s nim-waku:build",
|
||||
"test": "run-s build test:*",
|
||||
"test:lint": "eslint src --ext .ts",
|
||||
"test:prettier": "prettier \"src/**/*.ts\" \"./*.json\" --list-different",
|
||||
"test:spelling": "cspell \"{README.md,.github/*.md,src/**/*.ts}\"",
|
||||
"test:unit": "nyc --silent mocha",
|
||||
"proto": "run-s proto:*",
|
||||
"proto:lint": "buf lint",
|
||||
"proto:build": "buf generate",
|
||||
"check-cli": "run-s test diff-integration-tests check-integration-tests",
|
||||
"check-integration-tests": "run-s check-integration-test:*",
|
||||
"diff-integration-tests": "mkdir -p diff && rm -rf diff/test && cp -r test diff/test && rm -rf diff/test/test-*/.git && cd diff && git init --quiet && git add -A && git commit --quiet --no-verify --allow-empty -m 'WIP' && echo '\\n\\nCommitted most recent integration test output in the \"diff\" directory. Review the changes with \"cd diff && git diff HEAD\" or your preferred git diff viewer.'",
|
||||
"watch:build": "tsc -p tsconfig.json -w",
|
||||
"watch:test": "nyc --silent mocha --watch",
|
||||
"cov": "run-s build test:unit cov:html cov:lcov && open-cli coverage/index.html",
|
||||
"cov:html": "nyc report --reporter=html",
|
||||
"cov:lcov": "nyc report --reporter=lcov",
|
||||
"cov:send": "run-s cov:lcov && codecov",
|
||||
"cov:check": "nyc report && nyc check-coverage --lines 100 --functions 100 --branches 100",
|
||||
"doc": "run-s doc:html && open-cli build/docs/index.html",
|
||||
"doc:html": "typedoc --exclude **/*.spec.ts --out build/docs src/",
|
||||
"doc:json": "typedoc src/ --exclude **/*.spec.ts --json build/docs/typedoc.json",
|
||||
"doc:publish": "gh-pages -m \"[ci skip] Updates\" -d build/docs",
|
||||
"version": "standard-version",
|
||||
"reset-hard": "git clean -dfx && git reset --hard && npm i && npm run build && for d in examples/*; do (cd $d; npm i); done",
|
||||
"prepare-release": "run-s reset-hard test cov:check doc:html version doc:publish"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bitauth/libauth": "^1.17.1",
|
||||
"debug": "^4.3.1",
|
||||
"it-concat": "^2.0.0",
|
||||
"it-length-prefixed": "^5.0.2",
|
||||
"libp2p": "^0.31.0",
|
||||
"libp2p-gossipsub": "^0.9.0",
|
||||
"libp2p-mplex": "^0.10.3",
|
||||
"libp2p-noise": "^3.0.0",
|
||||
"libp2p-tcp": "^0.15.4",
|
||||
"libp2p-websockets": "^0.15.6",
|
||||
"multiaddr": "^9.0.1",
|
||||
"prompt-sync": "^4.2.0",
|
||||
"ts-proto": "^1.79.7",
|
||||
"uuid": "^8.3.2",
|
||||
"yarg": "^1.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@size-limit/preset-big-lib": "^11.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "^6.6.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"@istanbuljs/nyc-config-typescript": "^1.0.1",
|
||||
"@types/app-root-path": "^1.2.4",
|
||||
"@types/axios": "^0.14.0",
|
||||
"@types/chai": "^4.2.15",
|
||||
"@types/google-protobuf": "^3.7.4",
|
||||
"@types/mocha": "^8.2.2",
|
||||
"@types/node": "^14.14.31",
|
||||
"@types/tail": "^2.0.0",
|
||||
"@types/uuid": "^8.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.0.1",
|
||||
"@typescript-eslint/parser": "^4.0.1",
|
||||
"app-root-path": "^3.0.0",
|
||||
"axios": "^0.21.1",
|
||||
"chai": "^4.3.4",
|
||||
"codecov": "^3.5.0",
|
||||
"cspell": "^4.1.0",
|
||||
"cz-conventional-changelog": "^3.3.0",
|
||||
"eslint": "^7.8.0",
|
||||
"eslint-config-prettier": "^6.11.0",
|
||||
"eslint-plugin-eslint-comments": "^3.2.0",
|
||||
"eslint-plugin-functional": "^6.0.1",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"gh-pages": "^6.1.1",
|
||||
"husky": "^9.0.11",
|
||||
"karma": "^6.4.2",
|
||||
"karma-chrome-launcher": "^3.2.0",
|
||||
"karma-firefox-launcher": "^2.1.3",
|
||||
"karma-mocha": "^2.0.1",
|
||||
"karma-webkit-launcher": "^2.4.0",
|
||||
"karma-webpack": "github:codymikol/karma-webpack#2337a82beb078c0d8e25ae8333a06249b8e72828",
|
||||
"lint-staged": "^15.4.3",
|
||||
"playwright": "^1.40.1",
|
||||
"size-limit": "^11.0.1",
|
||||
"ts-loader": "9.5.2",
|
||||
"ts-node": "10.9.2",
|
||||
"typedoc": "0.28.5",
|
||||
"typescript": "5.8.3",
|
||||
"wscat": "^6.0.1"
|
||||
"eslint-plugin-functional": "^3.0.2",
|
||||
"eslint-plugin-import": "^2.22.0",
|
||||
"fast-check": "^2.14.0",
|
||||
"gh-pages": "^3.1.0",
|
||||
"mocha": "^8.3.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"nyc": "^15.1.0",
|
||||
"open-cli": "^6.0.1",
|
||||
"p-timeout": "^4.1.0",
|
||||
"prettier": "^2.1.1",
|
||||
"standard-version": "^9.0.0",
|
||||
"tail": "^2.2.0",
|
||||
"ts-node": "^9.1.1",
|
||||
"typedoc": "^0.20.29",
|
||||
"typescript": "^4.0.2"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,js}": [
|
||||
"eslint --fix"
|
||||
"files": [
|
||||
"build/main",
|
||||
"build/module",
|
||||
"!**/*.spec.*",
|
||||
"!**/*.json",
|
||||
"CHANGELOG.md",
|
||||
"LICENSE",
|
||||
"README.md"
|
||||
],
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "cz-conventional-changelog"
|
||||
}
|
||||
},
|
||||
"prettier": {
|
||||
"singleQuote": true
|
||||
},
|
||||
"nyc": {
|
||||
"extends": "@istanbuljs/nyc-config-typescript",
|
||||
"exclude": [
|
||||
"**/*.spec.js"
|
||||
]
|
||||
},
|
||||
"version": ""
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
node_modules
|
||||
build
|
||||
.DS_Store
|
||||
*.log
|
||||
@ -1,3 +0,0 @@
|
||||
EXAMPLE_TEMPLATE="headless"
|
||||
EXAMPLE_NAME="headless"
|
||||
EXAMPLE_PORT="8080"
|
||||
@ -1,45 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
parserOptions: {
|
||||
ecmaVersion: 2022,
|
||||
sourceType: "module"
|
||||
},
|
||||
env: {
|
||||
node: true,
|
||||
browser: true,
|
||||
es2021: true
|
||||
},
|
||||
plugins: ["import"],
|
||||
extends: ["eslint:recommended"],
|
||||
rules: {
|
||||
"no-unused-vars": ["error", { "argsIgnorePattern": "^_", "ignoreRestSiblings": true }]
|
||||
},
|
||||
globals: {
|
||||
process: true
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ["*.spec.ts", "**/test_utils/*.ts"],
|
||||
rules: {
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"no-console": "off",
|
||||
"import/no-extraneous-dependencies": ["error", { "devDependencies": true }]
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ["*.ts"],
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: "./tsconfig.dev.json"
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ["*.d.ts"],
|
||||
rules: {
|
||||
"no-unused-vars": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
@ -1,72 +0,0 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
# Build stage - install all dependencies and build
|
||||
FROM node:22-bullseye AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package.json and temporarily remove workspace dependencies that can't be resolved
|
||||
COPY package.json package.json.orig
|
||||
RUN sed '/"@waku\/tests": "\*",/d' package.json.orig > package.json
|
||||
RUN npm install --no-audit --no-fund
|
||||
|
||||
COPY src ./src
|
||||
COPY types ./types
|
||||
COPY tsconfig.json ./
|
||||
COPY web ./web
|
||||
|
||||
RUN npm run build
|
||||
|
||||
# Production stage - only runtime dependencies
|
||||
FROM node:22-bullseye
|
||||
|
||||
# Install required system deps for Playwright Chromium
|
||||
RUN apt-get update && apt-get install -y \
|
||||
wget \
|
||||
gnupg \
|
||||
ca-certificates \
|
||||
fonts-liberation \
|
||||
libatk-bridge2.0-0 \
|
||||
libatk1.0-0 \
|
||||
libatspi2.0-0 \
|
||||
libcups2 \
|
||||
libdbus-1-3 \
|
||||
libdrm2 \
|
||||
libgtk-3-0 \
|
||||
libnspr4 \
|
||||
libnss3 \
|
||||
libx11-xcb1 \
|
||||
libxcomposite1 \
|
||||
libxdamage1 \
|
||||
libxfixes3 \
|
||||
libxkbcommon0 \
|
||||
libxrandr2 \
|
||||
xdg-utils \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files and install only production dependencies
|
||||
COPY package.json package.json.orig
|
||||
RUN sed '/"@waku\/tests": "\*",/d' package.json.orig > package.json
|
||||
RUN npm install --only=production --no-audit --no-fund
|
||||
|
||||
# Copy built application from builder stage
|
||||
COPY --from=builder /app/dist ./dist
|
||||
|
||||
# Install Playwright browsers (Chromium only) at runtime layer
|
||||
RUN npx playwright install --with-deps chromium
|
||||
|
||||
ENV PORT=8080 \
|
||||
NODE_ENV=production
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
# Use a script to handle CLI arguments and environment variables
|
||||
COPY scripts/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
||||
CMD ["npm", "run", "start:server"]
|
||||
|
||||
|
||||
@ -1,174 +0,0 @@
|
||||
# Waku Browser Tests
|
||||
|
||||
This package provides a containerized Waku light node simulation server for testing and development. The server runs a headless browser using Playwright and exposes a REST API similar to the nwaku REST API. A Dockerfile is provided to allow programmatic simulation and "deployment" of js-waku nodes in any Waku orchestration environment that uses Docker (e.g. [10ksim](https://github.com/vacp2p/10ksim) ).
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Build and Run
|
||||
|
||||
```bash
|
||||
# Build the application
|
||||
npm run build
|
||||
|
||||
# Start the server (port 8080)
|
||||
npm run start:server
|
||||
|
||||
# Build and run Docker container
|
||||
npm run docker:build
|
||||
docker run -p 8080:8080 waku-browser-tests:local
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Configure the Waku node using environment variables:
|
||||
|
||||
### Network Configuration
|
||||
- `WAKU_CLUSTER_ID`: Cluster ID (default: 1)
|
||||
- `WAKU_SHARD`: Specific shard number - enables static sharding mode (optional)
|
||||
|
||||
**Sharding Behavior:**
|
||||
- **Auto-sharding** (default): Uses `numShardsInCluster: 8` across cluster 1
|
||||
- **Static sharding**: When `WAKU_SHARD` is set, uses only that specific shard
|
||||
|
||||
### Bootstrap Configuration
|
||||
- `WAKU_ENR_BOOTSTRAP`: Enable ENR bootstrap mode with custom bootstrap peers (comma-separated)
|
||||
- `WAKU_LIGHTPUSH_NODE`: Preferred lightpush node multiaddr (Docker only)
|
||||
|
||||
### ENR Bootstrap Mode
|
||||
|
||||
When `WAKU_ENR_BOOTSTRAP` is set:
|
||||
- Disables default bootstrap (`defaultBootstrap: false`)
|
||||
- Enables DNS discovery using production ENR trees
|
||||
- Enables peer exchange and peer cache
|
||||
- Uses the specified ENR for additional bootstrap peers
|
||||
|
||||
```bash
|
||||
# Example: ENR bootstrap mode
|
||||
WAKU_ENR_BOOTSTRAP="enr:-QEnuEBEAyErHEfhiQxAVQoWowGTCuEF9fKZtXSd7H_PymHFhGJA3rGAYDVSHKCyJDGRLBGsloNbS8AZF33IVuefjOO6BIJpZIJ2NIJpcIQS39tkim11bHRpYWRkcnO4lgAvNihub2RlLTAxLmRvLWFtczMud2FrdXYyLnRlc3Quc3RhdHVzaW0ubmV0BgG73gMAODcxbm9kZS0wMS5hYy1jbi1ob25na29uZy1jLndha3V2Mi50ZXN0LnN0YXR1c2ltLm5ldAYBu94DACm9A62t7AQL4Ef5ZYZosRpQTzFVAB8jGjf1TER2wH-0zBOe1-MDBNLeA4lzZWNwMjU2azGhAzfsxbxyCkgCqq8WwYsVWH7YkpMLnU2Bw5xJSimxKav-g3VkcIIjKA" npm run start:server
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
The server exposes the following HTTP endpoints:
|
||||
|
||||
### Node Management
|
||||
- `GET /`: Health check - returns server status
|
||||
- `GET /waku/v1/peer-info`: Get node peer information
|
||||
- `POST /waku/v1/wait-for-peers`: Wait for peers with specific protocols
|
||||
|
||||
### Messaging
|
||||
- `POST /lightpush/v3/message`: Send message via lightpush
|
||||
|
||||
### Static Files
|
||||
- `GET /app/index.html`: Web application entry point
|
||||
- `GET /app/*`: Static web application files
|
||||
|
||||
### Examples
|
||||
|
||||
#### Send a Message (Auto-sharding)
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/lightpush/v3/message \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"pubsubTopic": "",
|
||||
"message": {
|
||||
"contentTopic": "/test/1/example/proto",
|
||||
"payload": "SGVsbG8gV2FrdQ==",
|
||||
"version": 1
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
#### Send a Message (Explicit pubsub topic)
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/lightpush/v3/message \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"pubsubTopic": "/waku/2/rs/1/4",
|
||||
"message": {
|
||||
"contentTopic": "/test/1/example/proto",
|
||||
"payload": "SGVsbG8gV2FrdQ==",
|
||||
"version": 1
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
#### Wait for Peers
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/waku/v1/wait-for-peers \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"timeoutMs": 30000,
|
||||
"protocols": ["lightpush", "filter"]
|
||||
}'
|
||||
```
|
||||
|
||||
#### Get Peer Info
|
||||
```bash
|
||||
curl -X GET http://localhost:8080/waku/v1/peer-info
|
||||
```
|
||||
|
||||
## CLI Usage
|
||||
|
||||
Run with CLI arguments:
|
||||
|
||||
```bash
|
||||
# Custom cluster and shard
|
||||
node dist/src/server.js --cluster-id=2 --shard=0
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
The package includes several test suites:
|
||||
|
||||
```bash
|
||||
# Basic server functionality tests (default)
|
||||
npm test
|
||||
|
||||
# Docker testing workflow
|
||||
npm run docker:build
|
||||
npm run test:integration
|
||||
|
||||
# All tests
|
||||
npm run test:all
|
||||
|
||||
# Individual test suites:
|
||||
npm run test:server # Server-only tests
|
||||
npm run test:e2e # End-to-end tests
|
||||
```
|
||||
|
||||
**Test Types:**
|
||||
- `server.spec.ts` - Tests basic server functionality and static file serving
|
||||
- `integration.spec.ts` - Tests Docker container integration with external services
|
||||
- `e2e.spec.ts` - Full end-to-end tests using nwaku nodes
|
||||
|
||||
## Docker Usage
|
||||
|
||||
The package includes Docker support for containerized testing:
|
||||
|
||||
```bash
|
||||
# Build image
|
||||
docker build -t waku-browser-tests:local .
|
||||
|
||||
# Run with ENR bootstrap
|
||||
docker run -p 8080:8080 \
|
||||
-e WAKU_ENR_BOOTSTRAP="enr:-QEnuE..." \
|
||||
-e WAKU_CLUSTER_ID="1" \
|
||||
waku-browser-tests:local
|
||||
|
||||
# Run with specific configuration
|
||||
docker run -p 8080:8080 \
|
||||
-e WAKU_CLUSTER_ID="2" \
|
||||
-e WAKU_SHARD="0" \
|
||||
waku-browser-tests:local
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
The server automatically:
|
||||
- Creates a Waku light node on startup
|
||||
- Configures network settings from environment variables
|
||||
- Enables appropriate protocols (lightpush, filter)
|
||||
- Handles peer discovery and connection management
|
||||
|
||||
All endpoints are CORS-enabled for cross-origin requests.
|
||||
@ -1,42 +0,0 @@
|
||||
{
|
||||
"name": "@waku/browser-tests",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "npm run start:server",
|
||||
"start:server": "PORT=8080 node ./dist/src/server.js",
|
||||
"test": "npx playwright test tests/server.spec.ts --reporter=line",
|
||||
"test:all": "npx playwright test --reporter=line",
|
||||
"test:server": "npx playwright test tests/server.spec.ts --reporter=line",
|
||||
"test:integration": "npx playwright test tests/integration.spec.ts --reporter=line",
|
||||
"test:e2e": "npx playwright test tests/e2e.spec.ts --reporter=line",
|
||||
"build:server": "tsc -p tsconfig.json",
|
||||
"build:web": "esbuild web/index.ts --bundle --format=esm --platform=browser --outdir=dist/web && cp web/index.html dist/web/index.html",
|
||||
"build": "npm-run-all -s build:server build:web",
|
||||
"docker:build": "docker build -t waku-browser-tests:local . && docker tag waku-browser-tests:local waku-browser-tests:latest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@playwright/test": "^1.51.1",
|
||||
"@waku/discovery": "^0.0.11",
|
||||
"@waku/interfaces": "^0.0.33",
|
||||
"@waku/sdk": "^0.0.34",
|
||||
"@waku/utils": "0.0.27",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv-flow": "^0.4.0",
|
||||
"express": "^4.21.2",
|
||||
"filter-obj": "^2.0.2",
|
||||
"it-first": "^3.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.15",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/node": "^20.10.0",
|
||||
"@waku/tests": "*",
|
||||
"axios": "^1.8.4",
|
||||
"esbuild": "^0.21.5",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"testcontainers": "^10.9.0",
|
||||
"typescript": "5.8.3"
|
||||
}
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
import { Logger } from "@waku/utils";
|
||||
|
||||
const log = new Logger("playwright-config");
|
||||
|
||||
if (!process.env.CI) {
|
||||
try {
|
||||
await import("dotenv-flow/config.js");
|
||||
} catch (e) {
|
||||
log.warn("dotenv-flow not found; skipping env loading");
|
||||
}
|
||||
}
|
||||
|
||||
const EXAMPLE_PORT = process.env.EXAMPLE_PORT || "8080";
|
||||
const BASE_URL = `http://127.0.0.1:${EXAMPLE_PORT}`;
|
||||
const TEST_IGNORE = process.env.CI ? ["tests/e2e.spec.ts"] : [];
|
||||
|
||||
export default defineConfig({
|
||||
testDir: "./tests",
|
||||
testIgnore: TEST_IGNORE,
|
||||
fullyParallel: true,
|
||||
forbidOnly: !!process.env.CI,
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
workers: process.env.CI ? 2 : undefined,
|
||||
reporter: "html",
|
||||
use: {
|
||||
baseURL: BASE_URL,
|
||||
|
||||
trace: "on-first-retry"
|
||||
},
|
||||
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
use: { ...devices["Desktop Chrome"] }
|
||||
}
|
||||
]
|
||||
|
||||
});
|
||||
@ -1,54 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Docker entrypoint script for waku-browser-tests
|
||||
# Handles CLI arguments and converts them to environment variables
|
||||
# Supports reading discovered addresses from /etc/addrs/addrs.env (10k sim pattern)
|
||||
echo "docker-entrypoint.sh"
|
||||
echo "Using address: $addrs1"
|
||||
# Only set WAKU_LIGHTPUSH_NODE if it's not already set and addrs1 is available
|
||||
if [ -z "$WAKU_LIGHTPUSH_NODE" ] && [ -n "$addrs1" ]; then
|
||||
export WAKU_LIGHTPUSH_NODE="$addrs1"
|
||||
fi
|
||||
echo "Num Args: $#"
|
||||
echo "Args: $@"
|
||||
|
||||
echo "WAKU_LIGHTPUSH_NODE=$WAKU_LIGHTPUSH_NODE"
|
||||
|
||||
# Parse command line arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--cluster-id=*)
|
||||
export WAKU_CLUSTER_ID="${1#*=}"
|
||||
echo "Setting WAKU_CLUSTER_ID=${WAKU_CLUSTER_ID}"
|
||||
shift
|
||||
;;
|
||||
--shard=*)
|
||||
export WAKU_SHARD="${1#*=}"
|
||||
echo "Setting WAKU_SHARD=${WAKU_SHARD}"
|
||||
shift
|
||||
;;
|
||||
--lightpushnode=*)
|
||||
export WAKU_LIGHTPUSH_NODE="${1#*=}"
|
||||
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, notify user and keep it for the main command
|
||||
echo "Warning: Unknown argument '$1' will be passed to the main command"
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# If no specific command is provided, use the default CMD
|
||||
if [ $# -eq 0 ]; then
|
||||
set -- "npm" "run" "start:server"
|
||||
fi
|
||||
|
||||
# Execute the main command
|
||||
exec "$@"
|
||||
@ -1,67 +0,0 @@
|
||||
import { Browser, chromium, Page } from "@playwright/test";
|
||||
import { Logger } from "@waku/utils";
|
||||
|
||||
const log = new Logger("browser-test");
|
||||
|
||||
let browser: Browser | undefined;
|
||||
let page: Page | undefined;
|
||||
|
||||
export async function initBrowser(appPort: number): Promise<void> {
|
||||
try {
|
||||
const launchArgs = ["--no-sandbox", "--disable-setuid-sandbox"];
|
||||
|
||||
browser = await chromium.launch({
|
||||
headless: true,
|
||||
args: launchArgs
|
||||
});
|
||||
|
||||
if (!browser) {
|
||||
throw new Error("Failed to initialize browser");
|
||||
}
|
||||
|
||||
page = await browser.newPage();
|
||||
|
||||
// Forward browser console to server logs
|
||||
page.on('console', msg => {
|
||||
const type = msg.type();
|
||||
const text = msg.text();
|
||||
log.info(`[Browser Console ${type.toUpperCase()}] ${text}`);
|
||||
});
|
||||
|
||||
page.on('pageerror', error => {
|
||||
log.error('[Browser Page Error]', error.message);
|
||||
});
|
||||
|
||||
await page.goto(`http://localhost:${appPort}/app/index.html`, {
|
||||
waitUntil: "networkidle",
|
||||
});
|
||||
|
||||
await page.waitForFunction(
|
||||
() => {
|
||||
return window.wakuApi && typeof window.wakuApi.createWakuNode === "function";
|
||||
},
|
||||
{ timeout: 30000 }
|
||||
);
|
||||
|
||||
log.info("Browser initialized successfully with wakuApi");
|
||||
} catch (error) {
|
||||
log.error("Error initializing browser:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export function getPage(): Page | undefined {
|
||||
return page;
|
||||
}
|
||||
|
||||
export function setPage(pageInstance: Page | undefined): void {
|
||||
page = pageInstance;
|
||||
}
|
||||
|
||||
export async function closeBrowser(): Promise<void> {
|
||||
if (browser) {
|
||||
await browser.close();
|
||||
browser = undefined;
|
||||
page = undefined;
|
||||
}
|
||||
}
|
||||
@ -1,87 +0,0 @@
|
||||
import { Router } from "express";
|
||||
import { Logger } from "@waku/utils";
|
||||
import {
|
||||
createEndpointHandler,
|
||||
validators,
|
||||
errorHandlers,
|
||||
} from "../utils/endpoint-handler.js";
|
||||
|
||||
interface LightPushResult {
|
||||
successes: string[];
|
||||
failures: Array<{ error: string; peerId?: string }>;
|
||||
}
|
||||
|
||||
const log = new Logger("routes:waku");
|
||||
const router = Router();
|
||||
|
||||
const corsEndpoints = [
|
||||
"/waku/v1/wait-for-peers",
|
||||
"/waku/v1/peer-info",
|
||||
"/lightpush/v3/message",
|
||||
];
|
||||
|
||||
corsEndpoints.forEach((endpoint) => {
|
||||
router.head(endpoint, (_req, res) => {
|
||||
res.status(200).end();
|
||||
});
|
||||
});
|
||||
|
||||
router.post(
|
||||
"/waku/v1/wait-for-peers",
|
||||
createEndpointHandler({
|
||||
methodName: "waitForPeers",
|
||||
validateInput: (body: unknown) => {
|
||||
const bodyObj = body as { timeoutMs?: number; protocols?: string[] };
|
||||
return [
|
||||
bodyObj.timeoutMs || 10000,
|
||||
bodyObj.protocols || ["lightpush", "filter"],
|
||||
];
|
||||
},
|
||||
transformResult: () => ({
|
||||
success: true,
|
||||
message: "Successfully connected to peers",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/waku/v1/peer-info",
|
||||
createEndpointHandler({
|
||||
methodName: "getPeerInfo",
|
||||
validateInput: validators.noInput,
|
||||
}),
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/lightpush/v3/message",
|
||||
createEndpointHandler({
|
||||
methodName: "pushMessageV3",
|
||||
validateInput: (body: unknown): [string, string, string] => {
|
||||
const validatedRequest = validators.requireLightpushV3(body);
|
||||
|
||||
return [
|
||||
validatedRequest.message.contentTopic,
|
||||
validatedRequest.message.payload,
|
||||
validatedRequest.pubsubTopic,
|
||||
];
|
||||
},
|
||||
handleError: errorHandlers.lightpushError,
|
||||
transformResult: (result: unknown) => {
|
||||
const lightPushResult = result as LightPushResult;
|
||||
if (lightPushResult && lightPushResult.successes && lightPushResult.successes.length > 0) {
|
||||
log.info("[Server] Message successfully sent via v3 lightpush!");
|
||||
return {
|
||||
success: true,
|
||||
result: lightPushResult,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
error: "Could not publish message: no suitable peers",
|
||||
};
|
||||
}
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
export default router;
|
||||
@ -1,244 +0,0 @@
|
||||
import { fileURLToPath } from "url";
|
||||
import * as path from "path";
|
||||
|
||||
import cors from "cors";
|
||||
import express, { Request, Response } from "express";
|
||||
import { Logger } from "@waku/utils";
|
||||
|
||||
import wakuRouter from "./routes/waku.js";
|
||||
import { initBrowser, getPage, closeBrowser } from "./browser/index.js";
|
||||
import {
|
||||
DEFAULT_CLUSTER_ID,
|
||||
DEFAULT_NUM_SHARDS,
|
||||
Protocols,
|
||||
AutoSharding,
|
||||
StaticSharding,
|
||||
} from "@waku/interfaces";
|
||||
import { CreateNodeOptions } from "@waku/sdk";
|
||||
import type { WindowNetworkConfig } from "../types/global.js";
|
||||
|
||||
interface NodeError extends Error {
|
||||
code?: string;
|
||||
}
|
||||
|
||||
const log = new Logger("server");
|
||||
const app = express();
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
import * as fs from "fs";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const distRoot = path.resolve(__dirname, "..");
|
||||
const webDir = path.resolve(distRoot, "web");
|
||||
|
||||
app.get("/app/index.html", (_req: Request, res: Response) => {
|
||||
try {
|
||||
const htmlPath = path.join(webDir, "index.html");
|
||||
let htmlContent = fs.readFileSync(htmlPath, "utf8");
|
||||
|
||||
const networkConfig: WindowNetworkConfig = {};
|
||||
if (process.env.WAKU_CLUSTER_ID) {
|
||||
networkConfig.clusterId = parseInt(process.env.WAKU_CLUSTER_ID, 10);
|
||||
}
|
||||
if (process.env.WAKU_SHARD) {
|
||||
networkConfig.shards = [parseInt(process.env.WAKU_SHARD, 10)];
|
||||
log.info("Using static shard:", networkConfig.shards);
|
||||
}
|
||||
|
||||
const lightpushNode = process.env.WAKU_LIGHTPUSH_NODE || null;
|
||||
const enrBootstrap = process.env.WAKU_ENR_BOOTSTRAP || null;
|
||||
|
||||
log.info("Network config on server start, pre headless:", networkConfig);
|
||||
|
||||
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>`;
|
||||
|
||||
htmlContent = htmlContent.replace(originalPattern, replacement);
|
||||
|
||||
res.setHeader("Content-Type", "text/html");
|
||||
res.send(htmlContent);
|
||||
} catch (error) {
|
||||
log.error("Error serving dynamic index.html:", error);
|
||||
res.status(500).send("Error loading page");
|
||||
}
|
||||
});
|
||||
|
||||
app.use("/app", express.static(webDir, { index: false }));
|
||||
|
||||
app.use(wakuRouter);
|
||||
|
||||
async function startAPI(requestedPort: number): Promise<number> {
|
||||
try {
|
||||
app.get("/", (_req: Request, res: Response) => {
|
||||
res.json({ status: "Waku simulation server is running" });
|
||||
});
|
||||
|
||||
app
|
||||
.listen(requestedPort, () => {
|
||||
log.info(`API server running on http://localhost:${requestedPort}`);
|
||||
})
|
||||
.on("error", (error: NodeError) => {
|
||||
if (error.code === "EADDRINUSE") {
|
||||
log.error(
|
||||
`Port ${requestedPort} is already in use. Please close the application using this port and try again.`,
|
||||
);
|
||||
} else {
|
||||
log.error("Error starting server:", error);
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
|
||||
return requestedPort;
|
||||
} catch (error) {
|
||||
log.error("Error starting server:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function startServer(port: number = 3000): Promise<void> {
|
||||
try {
|
||||
const actualPort = await startAPI(port);
|
||||
await initBrowser(actualPort);
|
||||
|
||||
try {
|
||||
log.info("Auto-starting node with CLI configuration...");
|
||||
|
||||
const hasEnrBootstrap = Boolean(process.env.WAKU_ENR_BOOTSTRAP);
|
||||
|
||||
const networkConfig: AutoSharding | StaticSharding = process.env.WAKU_SHARD
|
||||
? ({
|
||||
clusterId: process.env.WAKU_CLUSTER_ID
|
||||
? parseInt(process.env.WAKU_CLUSTER_ID, 10)
|
||||
: DEFAULT_CLUSTER_ID,
|
||||
shards: [parseInt(process.env.WAKU_SHARD, 10)],
|
||||
} as StaticSharding)
|
||||
: ({
|
||||
clusterId: process.env.WAKU_CLUSTER_ID
|
||||
? parseInt(process.env.WAKU_CLUSTER_ID, 10)
|
||||
: DEFAULT_CLUSTER_ID,
|
||||
numShardsInCluster: DEFAULT_NUM_SHARDS,
|
||||
} as AutoSharding);
|
||||
|
||||
const createOptions: CreateNodeOptions = {
|
||||
defaultBootstrap: false,
|
||||
...(hasEnrBootstrap && {
|
||||
discovery: {
|
||||
dns: true,
|
||||
peerExchange: true,
|
||||
peerCache: true,
|
||||
},
|
||||
}),
|
||||
networkConfig,
|
||||
};
|
||||
|
||||
log.info(
|
||||
`Bootstrap mode: ${hasEnrBootstrap ? "ENR-only (defaultBootstrap=false)" : "default bootstrap (defaultBootstrap=true)"}`,
|
||||
);
|
||||
if (hasEnrBootstrap) {
|
||||
log.info(`ENR bootstrap peers: ${process.env.WAKU_ENR_BOOTSTRAP}`);
|
||||
}
|
||||
|
||||
log.info(
|
||||
`Network config: ${JSON.stringify(networkConfig)}`,
|
||||
);
|
||||
|
||||
await getPage()?.evaluate((config) => {
|
||||
return window.wakuApi.createWakuNode(config);
|
||||
}, createOptions);
|
||||
await getPage()?.evaluate(() => window.wakuApi.startNode());
|
||||
|
||||
try {
|
||||
await getPage()?.evaluate(() =>
|
||||
window.wakuApi.waitForPeers?.(5000, [Protocols.LightPush]),
|
||||
);
|
||||
log.info("Auto-start completed with bootstrap peers");
|
||||
} catch (peerError) {
|
||||
log.info(
|
||||
"Auto-start completed (no bootstrap peers found - may be expected with test ENRs)",
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
log.warn("Auto-start failed:", e);
|
||||
}
|
||||
} catch (error) {
|
||||
log.error("Error starting server:", error);
|
||||
}
|
||||
}
|
||||
|
||||
process.on("uncaughtException", (error) => {
|
||||
log.error("Uncaught Exception:", error);
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
process.on("unhandledRejection", (reason, promise) => {
|
||||
log.error("Unhandled Rejection at:", promise, "reason:", reason);
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
const gracefulShutdown = async (signal: string) => {
|
||||
log.info(`Received ${signal}, gracefully shutting down...`);
|
||||
try {
|
||||
await closeBrowser();
|
||||
} catch (e) {
|
||||
log.warn("Error closing browser:", e);
|
||||
}
|
||||
process.exit(0);
|
||||
};
|
||||
|
||||
process.on("SIGINT", () => gracefulShutdown("SIGINT"));
|
||||
process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
|
||||
|
||||
function parseCliArgs() {
|
||||
const args = process.argv.slice(2);
|
||||
let clusterId: number | undefined;
|
||||
let shard: number | undefined;
|
||||
|
||||
for (const arg of args) {
|
||||
if (arg.startsWith("--cluster-id=")) {
|
||||
clusterId = parseInt(arg.split("=")[1], 10);
|
||||
if (isNaN(clusterId)) {
|
||||
log.error("Invalid cluster-id value. Must be a number.");
|
||||
process.exit(1);
|
||||
}
|
||||
} else if (arg.startsWith("--shard=")) {
|
||||
shard = parseInt(arg.split("=")[1], 10);
|
||||
if (isNaN(shard)) {
|
||||
log.error("Invalid shard value. Must be a number.");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { clusterId, shard };
|
||||
}
|
||||
|
||||
const isMainModule = process.argv[1] === fileURLToPath(import.meta.url);
|
||||
|
||||
if (isMainModule) {
|
||||
const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000;
|
||||
const cliArgs = parseCliArgs();
|
||||
|
||||
if (cliArgs.clusterId !== undefined) {
|
||||
process.env.WAKU_CLUSTER_ID = cliArgs.clusterId.toString();
|
||||
log.info(`Using CLI cluster ID: ${cliArgs.clusterId}`);
|
||||
}
|
||||
if (cliArgs.shard !== undefined) {
|
||||
process.env.WAKU_SHARD = cliArgs.shard.toString();
|
||||
log.info(`Using CLI shard: ${cliArgs.shard}`);
|
||||
}
|
||||
|
||||
void startServer(port);
|
||||
}
|
||||
@ -1,197 +0,0 @@
|
||||
import { Request, Response } from "express";
|
||||
import { Logger } from "@waku/utils";
|
||||
import { getPage } from "../browser/index.js";
|
||||
import type { ITestBrowser } from "../../types/global.js";
|
||||
|
||||
const log = new Logger("endpoint-handler");
|
||||
|
||||
export interface LightpushV3Request {
|
||||
pubsubTopic: string;
|
||||
message: {
|
||||
payload: string;
|
||||
contentTopic: string;
|
||||
version: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface LightpushV3Response {
|
||||
success?: boolean;
|
||||
error?: string;
|
||||
result?: {
|
||||
successes: string[];
|
||||
failures: Array<{
|
||||
error: string;
|
||||
peerId?: string;
|
||||
}>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface EndpointConfig<TInput = unknown, TOutput = unknown> {
|
||||
methodName: string;
|
||||
validateInput?: (_requestBody: unknown) => TInput;
|
||||
transformResult?: (_sdkResult: unknown) => TOutput;
|
||||
handleError?: (_caughtError: Error) => { code: number; message: string };
|
||||
preCheck?: () => Promise<void> | void;
|
||||
logResult?: boolean;
|
||||
}
|
||||
|
||||
export function createEndpointHandler<TInput = unknown, TOutput = unknown>(
|
||||
config: EndpointConfig<TInput, TOutput>,
|
||||
) {
|
||||
return async (req: Request, res: Response) => {
|
||||
try {
|
||||
let input: TInput;
|
||||
try {
|
||||
input = config.validateInput
|
||||
? config.validateInput(req.body)
|
||||
: req.body;
|
||||
} catch (validationError) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: `Invalid input: ${validationError instanceof Error ? validationError.message : String(validationError)}`,
|
||||
});
|
||||
}
|
||||
|
||||
if (config.preCheck) {
|
||||
try {
|
||||
await config.preCheck();
|
||||
} catch (checkError) {
|
||||
return res.status(503).json({
|
||||
code: 503,
|
||||
message: checkError instanceof Error ? checkError.message : String(checkError),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const page = getPage();
|
||||
if (!page) {
|
||||
return res.status(503).json({
|
||||
code: 503,
|
||||
message: "Browser not initialized",
|
||||
});
|
||||
}
|
||||
|
||||
const result = await page.evaluate(
|
||||
({ methodName, params }) => {
|
||||
const testWindow = window as ITestBrowser;
|
||||
if (!testWindow.wakuApi) {
|
||||
throw new Error("window.wakuApi is not available");
|
||||
}
|
||||
|
||||
const wakuApi = testWindow.wakuApi as unknown as Record<string, unknown>;
|
||||
const method = wakuApi[methodName];
|
||||
if (typeof method !== "function") {
|
||||
throw new Error(`window.wakuApi.${methodName} is not a function`);
|
||||
}
|
||||
|
||||
if (params === null || params === undefined) {
|
||||
return method.call(testWindow.wakuApi);
|
||||
} else if (Array.isArray(params)) {
|
||||
return method.apply(testWindow.wakuApi, params);
|
||||
} else {
|
||||
return method.call(testWindow.wakuApi, params);
|
||||
}
|
||||
},
|
||||
{ methodName: config.methodName, params: input },
|
||||
);
|
||||
|
||||
if (config.logResult !== false) {
|
||||
log.info(
|
||||
`[${config.methodName}] Result:`,
|
||||
JSON.stringify(result, null, 2),
|
||||
);
|
||||
}
|
||||
|
||||
const finalResult = config.transformResult
|
||||
? config.transformResult(result)
|
||||
: result;
|
||||
|
||||
res.status(200).json(finalResult);
|
||||
} catch (error) {
|
||||
if (config.handleError) {
|
||||
const errorResponse = config.handleError(error as Error);
|
||||
return res.status(errorResponse.code).json({
|
||||
code: errorResponse.code,
|
||||
message: errorResponse.message,
|
||||
});
|
||||
}
|
||||
|
||||
log.error(`[${config.methodName}] Error:`, error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: `Could not execute ${config.methodName}: ${error instanceof Error ? error.message : String(error)}`,
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const validators = {
|
||||
requireLightpushV3: (body: unknown): LightpushV3Request => {
|
||||
// Type guard to check if body is an object
|
||||
if (!body || typeof body !== "object") {
|
||||
throw new Error("Request body must be an object");
|
||||
}
|
||||
|
||||
const bodyObj = body as Record<string, unknown>;
|
||||
|
||||
if (
|
||||
bodyObj.pubsubTopic !== undefined &&
|
||||
typeof bodyObj.pubsubTopic !== "string"
|
||||
) {
|
||||
throw new Error("pubsubTopic must be a string if provided");
|
||||
}
|
||||
if (!bodyObj.message || typeof bodyObj.message !== "object") {
|
||||
throw new Error("message is required and must be an object");
|
||||
}
|
||||
|
||||
const message = bodyObj.message as Record<string, unknown>;
|
||||
|
||||
if (
|
||||
!message.contentTopic ||
|
||||
typeof message.contentTopic !== "string"
|
||||
) {
|
||||
throw new Error("message.contentTopic is required and must be a string");
|
||||
}
|
||||
if (!message.payload || typeof message.payload !== "string") {
|
||||
throw new Error(
|
||||
"message.payload is required and must be a string (base64 encoded)",
|
||||
);
|
||||
}
|
||||
if (
|
||||
message.version !== undefined &&
|
||||
typeof message.version !== "number"
|
||||
) {
|
||||
throw new Error("message.version must be a number if provided");
|
||||
}
|
||||
|
||||
return {
|
||||
pubsubTopic: (bodyObj.pubsubTopic as string) || "",
|
||||
message: {
|
||||
payload: message.payload as string,
|
||||
contentTopic: message.contentTopic as string,
|
||||
version: (message.version as number) || 1,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
noInput: () => null,
|
||||
};
|
||||
|
||||
export const errorHandlers = {
|
||||
lightpushError: (error: Error) => {
|
||||
if (
|
||||
error.message.includes("size exceeds") ||
|
||||
error.message.includes("stream reset")
|
||||
) {
|
||||
return {
|
||||
code: 503,
|
||||
message:
|
||||
"Could not publish message: message size exceeds gossipsub max message size",
|
||||
};
|
||||
}
|
||||
return {
|
||||
code: 500,
|
||||
message: `Could not publish message: ${error.message}`,
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -1,117 +0,0 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import axios from "axios";
|
||||
import { StartedTestContainer } from "testcontainers";
|
||||
import { DefaultTestRoutingInfo } from "@waku/tests";
|
||||
import {
|
||||
startBrowserTestsContainer,
|
||||
stopContainer
|
||||
} from "./utils/container-helpers.js";
|
||||
import {
|
||||
createTwoNodeNetwork,
|
||||
getDockerAccessibleMultiaddr,
|
||||
stopNwakuNodes,
|
||||
TwoNodeNetwork
|
||||
} from "./utils/nwaku-helpers.js";
|
||||
import {
|
||||
ENV_BUILDERS,
|
||||
TEST_CONFIG,
|
||||
ASSERTIONS
|
||||
} from "./utils/test-config.js";
|
||||
|
||||
test.describe.configure({ mode: "serial" });
|
||||
|
||||
let container: StartedTestContainer;
|
||||
let nwakuNodes: TwoNodeNetwork;
|
||||
let baseUrl: string;
|
||||
|
||||
test.beforeAll(async () => {
|
||||
nwakuNodes = await createTwoNodeNetwork();
|
||||
|
||||
const lightPushPeerAddr = await getDockerAccessibleMultiaddr(nwakuNodes.nodes[0]);
|
||||
|
||||
const result = await startBrowserTestsContainer({
|
||||
environment: {
|
||||
...ENV_BUILDERS.withLocalLightPush(lightPushPeerAddr),
|
||||
DEBUG: "waku:*",
|
||||
WAKU_LIGHTPUSH_NODE: lightPushPeerAddr,
|
||||
},
|
||||
networkMode: "waku",
|
||||
});
|
||||
|
||||
container = result.container;
|
||||
baseUrl = result.baseUrl;
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
await Promise.all([
|
||||
stopContainer(container),
|
||||
stopNwakuNodes(nwakuNodes?.nodes || []),
|
||||
]);
|
||||
});
|
||||
|
||||
test("WakuHeadless can discover nwaku peer and use it for light push", async () => {
|
||||
test.setTimeout(TEST_CONFIG.DEFAULT_TEST_TIMEOUT);
|
||||
|
||||
const contentTopic = TEST_CONFIG.DEFAULT_CONTENT_TOPIC;
|
||||
const testMessage = TEST_CONFIG.DEFAULT_TEST_MESSAGE;
|
||||
|
||||
await new Promise((r) => setTimeout(r, TEST_CONFIG.WAKU_INIT_DELAY));
|
||||
|
||||
const healthResponse = await axios.get(`${baseUrl}/`, { timeout: 5000 });
|
||||
ASSERTIONS.serverHealth(healthResponse);
|
||||
|
||||
try {
|
||||
await axios.post(`${baseUrl}/waku/v1/wait-for-peers`, {
|
||||
timeoutMs: 10000,
|
||||
protocols: ["lightpush"],
|
||||
}, { timeout: 15000 });
|
||||
} catch {
|
||||
// Ignore errors
|
||||
}
|
||||
|
||||
const peerInfoResponse = await axios.get(`${baseUrl}/waku/v1/peer-info`);
|
||||
ASSERTIONS.peerInfo(peerInfoResponse);
|
||||
|
||||
const routingInfo = DefaultTestRoutingInfo;
|
||||
|
||||
const subscriptionResults = await Promise.all([
|
||||
nwakuNodes.nodes[0].ensureSubscriptions([routingInfo.pubsubTopic]),
|
||||
nwakuNodes.nodes[1].ensureSubscriptions([routingInfo.pubsubTopic])
|
||||
]);
|
||||
|
||||
expect(subscriptionResults[0]).toBe(true);
|
||||
expect(subscriptionResults[1]).toBe(true);
|
||||
|
||||
await new Promise((r) => setTimeout(r, TEST_CONFIG.SUBSCRIPTION_DELAY));
|
||||
|
||||
const base64Payload = btoa(testMessage);
|
||||
|
||||
const pushResponse = await axios.post(`${baseUrl}/lightpush/v3/message`, {
|
||||
pubsubTopic: routingInfo.pubsubTopic,
|
||||
message: {
|
||||
contentTopic,
|
||||
payload: base64Payload,
|
||||
version: 1,
|
||||
},
|
||||
});
|
||||
|
||||
ASSERTIONS.lightPushV3Success(pushResponse);
|
||||
|
||||
await new Promise((r) => setTimeout(r, TEST_CONFIG.MESSAGE_PROPAGATION_DELAY));
|
||||
|
||||
const [node1Messages, node2Messages] = await Promise.all([
|
||||
nwakuNodes.nodes[0].messages(contentTopic),
|
||||
nwakuNodes.nodes[1].messages(contentTopic)
|
||||
]);
|
||||
|
||||
|
||||
const totalMessages = node1Messages.length + node2Messages.length;
|
||||
expect(totalMessages).toBeGreaterThanOrEqual(1);
|
||||
|
||||
const receivedMessages = [...node1Messages, ...node2Messages];
|
||||
expect(receivedMessages.length).toBeGreaterThan(0);
|
||||
|
||||
const receivedMessage = receivedMessages[0];
|
||||
ASSERTIONS.messageContent(receivedMessage, testMessage, contentTopic);
|
||||
|
||||
});
|
||||
@ -1,134 +0,0 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import axios from "axios";
|
||||
import { StartedTestContainer } from "testcontainers";
|
||||
import {
|
||||
createLightNode,
|
||||
LightNode,
|
||||
Protocols,
|
||||
IDecodedMessage,
|
||||
} from "@waku/sdk";
|
||||
import { DEFAULT_CLUSTER_ID, DEFAULT_NUM_SHARDS } from "@waku/interfaces";
|
||||
import { startBrowserTestsContainer, stopContainer } from "./utils/container-helpers.js";
|
||||
import { ENV_BUILDERS, TEST_CONFIG } from "./utils/test-config.js";
|
||||
|
||||
test.describe.configure({ mode: "serial" });
|
||||
|
||||
let container: StartedTestContainer;
|
||||
let baseUrl: string;
|
||||
let wakuNode: LightNode;
|
||||
|
||||
test.beforeAll(async () => {
|
||||
const result = await startBrowserTestsContainer({
|
||||
environment: {
|
||||
...ENV_BUILDERS.withProductionEnr(),
|
||||
DEBUG: "waku:*",
|
||||
},
|
||||
});
|
||||
|
||||
container = result.container;
|
||||
baseUrl = result.baseUrl;
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
if (wakuNode) {
|
||||
try {
|
||||
await wakuNode.stop();
|
||||
} catch {
|
||||
// Ignore errors
|
||||
}
|
||||
}
|
||||
|
||||
await stopContainer(container);
|
||||
});
|
||||
|
||||
test("cross-network message delivery: SDK light node receives server lightpush", async () => {
|
||||
test.setTimeout(TEST_CONFIG.DEFAULT_TEST_TIMEOUT);
|
||||
|
||||
const contentTopic = TEST_CONFIG.DEFAULT_CONTENT_TOPIC;
|
||||
const testMessage = TEST_CONFIG.DEFAULT_TEST_MESSAGE;
|
||||
|
||||
wakuNode = await createLightNode({
|
||||
defaultBootstrap: true,
|
||||
discovery: {
|
||||
dns: true,
|
||||
peerExchange: true,
|
||||
peerCache: true,
|
||||
},
|
||||
networkConfig: {
|
||||
clusterId: DEFAULT_CLUSTER_ID,
|
||||
numShardsInCluster: DEFAULT_NUM_SHARDS,
|
||||
},
|
||||
libp2p: {
|
||||
filterMultiaddrs: false,
|
||||
},
|
||||
});
|
||||
|
||||
await wakuNode.start();
|
||||
|
||||
await wakuNode.waitForPeers(
|
||||
[Protocols.Filter, Protocols.LightPush],
|
||||
30000,
|
||||
);
|
||||
|
||||
const messages: IDecodedMessage[] = [];
|
||||
const decoder = wakuNode.createDecoder({ contentTopic });
|
||||
|
||||
if (
|
||||
!(await wakuNode.filter.subscribe([decoder], (message) => {
|
||||
messages.push(message);
|
||||
}))
|
||||
) {
|
||||
throw new Error("Failed to subscribe to Filter");
|
||||
}
|
||||
|
||||
await new Promise((r) => setTimeout(r, 2000));
|
||||
|
||||
const messagePromise = new Promise<void>((resolve) => {
|
||||
const originalLength = messages.length;
|
||||
const checkForMessage = () => {
|
||||
if (messages.length > originalLength) {
|
||||
resolve();
|
||||
} else {
|
||||
setTimeout(checkForMessage, 100);
|
||||
}
|
||||
};
|
||||
checkForMessage();
|
||||
});
|
||||
|
||||
await axios.post(`${baseUrl}/waku/v1/wait-for-peers`, {
|
||||
timeoutMs: 30000, // Increased timeout
|
||||
protocols: ["lightpush", "filter"],
|
||||
});
|
||||
|
||||
await new Promise((r) => setTimeout(r, 10000));
|
||||
|
||||
const base64Payload = btoa(testMessage);
|
||||
|
||||
const pushResponse = await axios.post(`${baseUrl}/lightpush/v3/message`, {
|
||||
pubsubTopic: decoder.pubsubTopic,
|
||||
message: {
|
||||
contentTopic,
|
||||
payload: base64Payload,
|
||||
version: 1,
|
||||
},
|
||||
});
|
||||
|
||||
expect(pushResponse.status).toBe(200);
|
||||
expect(pushResponse.data.success).toBe(true);
|
||||
|
||||
await Promise.race([
|
||||
messagePromise,
|
||||
new Promise((_, reject) =>
|
||||
setTimeout(() => {
|
||||
reject(new Error("Timeout waiting for message"));
|
||||
}, 45000),
|
||||
),
|
||||
]);
|
||||
|
||||
expect(messages).toHaveLength(1);
|
||||
const receivedMessage = messages[0];
|
||||
expect(receivedMessage.contentTopic).toBe(contentTopic);
|
||||
|
||||
const receivedPayload = new TextDecoder().decode(receivedMessage.payload);
|
||||
expect(receivedPayload).toBe(testMessage);
|
||||
});
|
||||
@ -1,82 +0,0 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import axios from "axios";
|
||||
import { spawn, ChildProcess } from "child_process";
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname, join } from "path";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
test.describe.configure({ mode: "serial" });
|
||||
|
||||
test.describe("Server Tests", () => {
|
||||
let serverProcess: ChildProcess;
|
||||
let baseUrl = "http://localhost:3000";
|
||||
|
||||
test.beforeAll(async () => {
|
||||
const serverPath = join(__dirname, "..", "dist", "src", "server.js");
|
||||
|
||||
serverProcess = spawn("node", [serverPath], {
|
||||
stdio: "pipe",
|
||||
env: { ...process.env, PORT: "3000" }
|
||||
});
|
||||
|
||||
serverProcess.stdout?.on("data", (_data: Buffer) => {
|
||||
});
|
||||
|
||||
serverProcess.stderr?.on("data", (_data: Buffer) => {
|
||||
});
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000));
|
||||
|
||||
let serverReady = false;
|
||||
for (let i = 0; i < 30; i++) {
|
||||
try {
|
||||
const res = await axios.get(`${baseUrl}/`, { timeout: 2000 });
|
||||
if (res.status === 200) {
|
||||
serverReady = true;
|
||||
break;
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors
|
||||
}
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
}
|
||||
|
||||
expect(serverReady).toBe(true);
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
if (serverProcess) {
|
||||
serverProcess.kill("SIGTERM");
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
}
|
||||
});
|
||||
|
||||
test("server 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("static files are served", async () => {
|
||||
const htmlRes = await axios.get(`${baseUrl}/app/index.html`);
|
||||
expect(htmlRes.status).toBe(200);
|
||||
expect(htmlRes.data).toContain("Waku Test Environment");
|
||||
|
||||
const jsRes = await axios.get(`${baseUrl}/app/index.js`);
|
||||
expect(jsRes.status).toBe(200);
|
||||
expect(jsRes.data).toContain("WakuHeadless");
|
||||
});
|
||||
|
||||
test("Waku node auto-started", async () => {
|
||||
try {
|
||||
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) {
|
||||
expect(error.response?.status).toBe(400);
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -1,128 +0,0 @@
|
||||
import axios from "axios";
|
||||
import { GenericContainer, StartedTestContainer } from "testcontainers";
|
||||
import { Logger } from "@waku/utils";
|
||||
|
||||
const log = new Logger("container-helpers");
|
||||
|
||||
export interface ContainerSetupOptions {
|
||||
environment?: Record<string, string>;
|
||||
networkMode?: string;
|
||||
timeout?: number;
|
||||
maxAttempts?: number;
|
||||
}
|
||||
|
||||
export interface ContainerSetupResult {
|
||||
container: StartedTestContainer;
|
||||
baseUrl: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a waku-browser-tests Docker container with proper health checking.
|
||||
* Follows patterns from @waku/tests package for retry logic and cleanup.
|
||||
*/
|
||||
export async function startBrowserTestsContainer(
|
||||
options: ContainerSetupOptions = {}
|
||||
): Promise<ContainerSetupResult> {
|
||||
const {
|
||||
environment = {},
|
||||
networkMode = "bridge",
|
||||
timeout = 2000,
|
||||
maxAttempts = 60
|
||||
} = options;
|
||||
|
||||
log.info("Starting waku-browser-tests container...");
|
||||
|
||||
let generic = new GenericContainer("waku-browser-tests:local")
|
||||
.withExposedPorts(8080)
|
||||
.withNetworkMode(networkMode);
|
||||
|
||||
// Apply environment variables
|
||||
for (const [key, value] of Object.entries(environment)) {
|
||||
generic = generic.withEnvironment({ [key]: value });
|
||||
}
|
||||
|
||||
const container = await generic.start();
|
||||
|
||||
// Set up container logging - stream all output from the start
|
||||
const logs = await container.logs();
|
||||
logs.on("data", (b) => process.stdout.write("[container] " + b.toString()));
|
||||
logs.on("error", (err) => log.error("[container log error]", err));
|
||||
|
||||
// Give container time to initialize
|
||||
await new Promise((r) => setTimeout(r, 5000));
|
||||
|
||||
const mappedPort = container.getMappedPort(8080);
|
||||
const baseUrl = `http://127.0.0.1:${mappedPort}`;
|
||||
|
||||
// Wait for server readiness with retry logic (following waku/tests patterns)
|
||||
const serverReady = await waitForServerReady(baseUrl, maxAttempts, timeout);
|
||||
|
||||
if (!serverReady) {
|
||||
await logFinalContainerState(container);
|
||||
throw new Error("Container failed to become ready");
|
||||
}
|
||||
|
||||
log.info("✅ Browser tests container ready");
|
||||
await new Promise((r) => setTimeout(r, 500)); // Final settling time
|
||||
|
||||
return { container, baseUrl };
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for server to become ready with exponential backoff and detailed logging.
|
||||
* Follows retry patterns from @waku/tests ServiceNode.
|
||||
*/
|
||||
async function waitForServerReady(
|
||||
baseUrl: string,
|
||||
maxAttempts: number,
|
||||
timeout: number
|
||||
): Promise<boolean> {
|
||||
for (let i = 0; i < maxAttempts; i++) {
|
||||
try {
|
||||
const res = await axios.get(`${baseUrl}/`, { timeout });
|
||||
if (res.status === 200) {
|
||||
log.info(`Server is ready after ${i + 1} attempts`);
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
if (i % 10 === 0) {
|
||||
log.info(`Attempt ${i + 1}/${maxAttempts} failed:`, error.code || error.message);
|
||||
}
|
||||
}
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs final container state for debugging, following waku/tests error handling patterns.
|
||||
*/
|
||||
async function logFinalContainerState(container: StartedTestContainer): Promise<void> {
|
||||
try {
|
||||
const finalLogs = await container.logs({ tail: 50 });
|
||||
log.info("=== Final Container Logs ===");
|
||||
finalLogs.on("data", (b) => log.info(b.toString()));
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
} catch (logError) {
|
||||
log.error("Failed to get container logs:", logError);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gracefully stops containers with retry logic, following teardown patterns from waku/tests.
|
||||
*/
|
||||
export async function stopContainer(container: StartedTestContainer): Promise<void> {
|
||||
if (!container) return;
|
||||
|
||||
log.info("Stopping container gracefully...");
|
||||
try {
|
||||
await container.stop({ timeout: 10000 });
|
||||
log.info("Container stopped successfully");
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
log.warn(
|
||||
"Container stop had issues (expected):",
|
||||
message
|
||||
);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user