mirror of
https://github.com/logos-messaging/js-waku.git
synced 2026-01-03 14:23: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",
|
"$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/master/cspell.schema.json",
|
||||||
"language": "en",
|
"language": "en",
|
||||||
"words": [
|
"words": [
|
||||||
"abortable",
|
|
||||||
"acks",
|
|
||||||
"Addrs",
|
|
||||||
"ahadns",
|
|
||||||
"Alives",
|
|
||||||
"alphabeta",
|
|
||||||
"arrayify",
|
|
||||||
"Arraylike",
|
|
||||||
"asym",
|
|
||||||
"autoshard",
|
|
||||||
"autosharding",
|
|
||||||
"backoff",
|
"backoff",
|
||||||
"backoffs",
|
"backoffs",
|
||||||
"bitauth",
|
|
||||||
"bitjson",
|
"bitjson",
|
||||||
|
"bitauth",
|
||||||
"bufbuild",
|
"bufbuild",
|
||||||
"chainsafe",
|
|
||||||
"cimg",
|
"cimg",
|
||||||
"cipherparams",
|
|
||||||
"ciphertext",
|
|
||||||
"circleci",
|
"circleci",
|
||||||
"circom",
|
|
||||||
"codecov",
|
"codecov",
|
||||||
"codegen",
|
|
||||||
"commitlint",
|
"commitlint",
|
||||||
"cooldown",
|
|
||||||
"dependabot",
|
"dependabot",
|
||||||
"dialable",
|
|
||||||
"dingpu",
|
"dingpu",
|
||||||
"discv",
|
|
||||||
"Dlazy",
|
"Dlazy",
|
||||||
"dnsaddr",
|
|
||||||
"Dockerode",
|
|
||||||
"Dout",
|
"Dout",
|
||||||
"Dscore",
|
"Dscore",
|
||||||
"ecies",
|
|
||||||
"editorconfig",
|
"editorconfig",
|
||||||
"Encrypters",
|
|
||||||
"enr",
|
|
||||||
"enrs",
|
|
||||||
"enrtree",
|
|
||||||
"ephem",
|
|
||||||
"esnext",
|
"esnext",
|
||||||
"ethersproject",
|
|
||||||
"execa",
|
"execa",
|
||||||
"exponentiate",
|
"exponentiate",
|
||||||
"extip",
|
|
||||||
"fanout",
|
"fanout",
|
||||||
"floodsub",
|
"floodsub",
|
||||||
"fontsource",
|
|
||||||
"globby",
|
"globby",
|
||||||
"gossipsub",
|
"gossipsub",
|
||||||
"hackathons",
|
|
||||||
"huilong",
|
|
||||||
"iasked",
|
"iasked",
|
||||||
"ihave",
|
"ihave",
|
||||||
"ihaves",
|
"ihaves",
|
||||||
"ineed",
|
"ineed",
|
||||||
"IPAM",
|
|
||||||
"ipfs",
|
|
||||||
"isready",
|
|
||||||
"iwant",
|
"iwant",
|
||||||
"jdev",
|
"jdev",
|
||||||
"jswaku",
|
|
||||||
"kdfparams",
|
|
||||||
"keccak",
|
|
||||||
"keypair",
|
|
||||||
"lamport",
|
|
||||||
"lastpub",
|
"lastpub",
|
||||||
"libauth",
|
"libauth",
|
||||||
"libp",
|
"libp",
|
||||||
"lightpush",
|
|
||||||
"LINEA",
|
|
||||||
"livechat",
|
"livechat",
|
||||||
"Merkle",
|
|
||||||
"mkdir",
|
"mkdir",
|
||||||
"mplex",
|
|
||||||
"multiaddr",
|
"multiaddr",
|
||||||
"multiaddresses",
|
|
||||||
"multiaddrs",
|
"multiaddrs",
|
||||||
"multicodec",
|
|
||||||
"multicodecs",
|
"multicodecs",
|
||||||
"multiformats",
|
"mplex",
|
||||||
"multihashes",
|
|
||||||
"muxed",
|
"muxed",
|
||||||
"muxer",
|
"muxer",
|
||||||
"muxers",
|
|
||||||
"mvps",
|
"mvps",
|
||||||
"nodekey",
|
"nodekey",
|
||||||
"nwaku",
|
|
||||||
"opendns",
|
|
||||||
"peerhave",
|
"peerhave",
|
||||||
"portfinder",
|
|
||||||
"prettierignore",
|
"prettierignore",
|
||||||
"proto",
|
"proto",
|
||||||
"protobuf",
|
"protobuf",
|
||||||
"protoc",
|
"protoc",
|
||||||
"proxiable",
|
|
||||||
"reactjs",
|
"reactjs",
|
||||||
"recid",
|
|
||||||
"rlnrelay",
|
"rlnrelay",
|
||||||
"rlnv",
|
|
||||||
"roadmap",
|
|
||||||
"sandboxed",
|
"sandboxed",
|
||||||
"scanf",
|
|
||||||
"secio",
|
"secio",
|
||||||
"seckey",
|
|
||||||
"secp",
|
|
||||||
"sharded",
|
|
||||||
"sscanf",
|
|
||||||
"Startable",
|
|
||||||
"staticnode",
|
"staticnode",
|
||||||
"statusim",
|
"statusim",
|
||||||
"statusteam",
|
|
||||||
"submodule",
|
"submodule",
|
||||||
"submodules",
|
"submodules",
|
||||||
"supercrypto",
|
|
||||||
"transpiled",
|
"transpiled",
|
||||||
"typedoc",
|
"typedoc",
|
||||||
"undialable",
|
|
||||||
"unencrypted",
|
|
||||||
"unhandle",
|
|
||||||
"unmarshal",
|
|
||||||
"unmount",
|
|
||||||
"unmounts",
|
"unmounts",
|
||||||
"unsubscription",
|
|
||||||
"untracked",
|
"untracked",
|
||||||
"upgrader",
|
"upgrader",
|
||||||
"vacp",
|
|
||||||
"varint",
|
|
||||||
"viem",
|
|
||||||
"vkey",
|
|
||||||
"wagmi",
|
|
||||||
"waku",
|
"waku",
|
||||||
"wakuconnect",
|
|
||||||
"wakunode",
|
"wakunode",
|
||||||
"wakuorg",
|
|
||||||
"wakuv",
|
|
||||||
"webfonts",
|
"webfonts",
|
||||||
"weboko",
|
"websockets"
|
||||||
"websockets",
|
|
||||||
"wifi",
|
|
||||||
"WTNS",
|
|
||||||
"xsalsa20",
|
|
||||||
"zerokit",
|
|
||||||
"Привет",
|
|
||||||
"مرحبا"
|
|
||||||
],
|
|
||||||
"flagWords": [
|
|
||||||
"pubSub: pubsub",
|
|
||||||
"pubSubTopics: pubsubTopics",
|
|
||||||
"pubSubTopic: pubsubTopic",
|
|
||||||
"PubSub: Pubsub",
|
|
||||||
"PubSubTopics: PubsubTopics",
|
|
||||||
"PubSubTopic: PubsubTopic",
|
|
||||||
"DefaultPubSubTopic: DefaultPubsubTopic"
|
|
||||||
],
|
],
|
||||||
|
"flagWords": [],
|
||||||
"ignorePaths": [
|
"ignorePaths": [
|
||||||
"package.json",
|
"package.json",
|
||||||
"package-lock.json",
|
"package-lock.json",
|
||||||
@ -168,23 +76,6 @@
|
|||||||
"node_modules/**",
|
"node_modules/**",
|
||||||
"build",
|
"build",
|
||||||
"gen",
|
"gen",
|
||||||
"proto",
|
"proto"
|
||||||
"*.spec.ts",
|
|
||||||
"*.log",
|
|
||||||
"CHANGELOG.md"
|
|
||||||
],
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "import",
|
|
||||||
"pattern": "/import .*/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "multiaddr",
|
|
||||||
"pattern": "//dns4/.*/"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ignoreRegExpList": [
|
|
||||||
"import",
|
|
||||||
"multiaddr"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
"root": true,
|
||||||
"parser": "@typescript-eslint/parser",
|
"parser": "@typescript-eslint/parser",
|
||||||
"parserOptions": {
|
"parserOptions": { "project": "./tsconfig.dev.json" },
|
||||||
"project": ["./tsconfig.json"]
|
|
||||||
},
|
|
||||||
"env": { "es6": true },
|
"env": { "es6": true },
|
||||||
"ignorePatterns": [
|
"ignorePatterns": ["node_modules", "build", "coverage", "proto"],
|
||||||
"node_modules",
|
|
||||||
"build",
|
|
||||||
"coverage",
|
|
||||||
"proto"
|
|
||||||
],
|
|
||||||
"plugins": ["import", "eslint-comments", "functional"],
|
"plugins": ["import", "eslint-comments", "functional"],
|
||||||
"extends": [
|
"extends": [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
"plugin:eslint-comments/recommended",
|
"plugin:eslint-comments/recommended",
|
||||||
"plugin:@typescript-eslint/recommended",
|
"plugin:@typescript-eslint/recommended",
|
||||||
"plugin:import/typescript",
|
"plugin:import/typescript",
|
||||||
"plugin:prettier/recommended"
|
"prettier",
|
||||||
|
"prettier/@typescript-eslint"
|
||||||
],
|
],
|
||||||
"globals": { "BigInt": true, "console": true, "WebAssembly": true },
|
"globals": { "BigInt": true, "console": true, "WebAssembly": true },
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-restricted-imports": [
|
"@typescript-eslint/explicit-function-return-type": ["error"],
|
||||||
"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-module-boundary-types": "off",
|
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||||
"eslint-comments/disable-enable-pair": [
|
"eslint-comments/disable-enable-pair": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{ "allowWholeFile": true }
|
||||||
"allowWholeFile": true
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"eslint-comments/no-unused-disable": "error",
|
"eslint-comments/no-unused-disable": "error",
|
||||||
"import/order": [
|
"import/order": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{ "newlines-between": "always", "alphabetize": { "order": "asc" } }
|
||||||
"newlines-between": "always",
|
|
||||||
"alphabetize": {
|
|
||||||
"order": "asc"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"no-constant-condition": ["error", { "checkLoops": false }],
|
"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": [
|
"sort-imports": [
|
||||||
"error",
|
"error",
|
||||||
{ "ignoreDeclarationSort": true, "ignoreCase": true }
|
{ "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": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
"files": ["*.spec.ts", "**/test_utils/*.ts", "*.js", "*.cjs"],
|
"files": ["*.spec.ts", "**/test_utils/*.ts"],
|
||||||
"rules": {
|
"rules": {
|
||||||
"@typescript-eslint/no-non-null-assertion": "off",
|
"@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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
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 kind of change does this PR introduce?** (Bug fix, feature, docs update, ...)
|
||||||
<!--
|
|
||||||
What problem does this PR address?
|
|
||||||
Clearly describe the issue or feature the PR aims to solve.
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Solution
|
- **What is the current behavior?** (You can also link to an open issue here)
|
||||||
<!--
|
|
||||||
Describe how the problem is solved in this PR.
|
|
||||||
- Provide an overview of the changes made.
|
|
||||||
- Highlight any significant design decisions or architectural changes.
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Notes
|
- **What is the new behavior (if this is a feature change)?**
|
||||||
<!--
|
|
||||||
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
|
|
||||||
|
|
||||||
---
|
- **Other information**:
|
||||||
|
|
||||||
#### 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.
|
|
||||||
|
|||||||
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:
|
on:
|
||||||
issues:
|
issues:
|
||||||
types: [opened]
|
types: [opened]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
add-to-project:
|
add-new-issue-to-new-column:
|
||||||
name: Add issue to project
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/add-to-project@v0.5.0
|
- uses: alex-page/github-project-automation-plus@v0.6.0
|
||||||
with:
|
with:
|
||||||
project-url: https://github.com/orgs/waku-org/projects/2
|
project: js-waku
|
||||||
github-token: ${{ secrets.ADD_TO_PROJECT_20240815 }}
|
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:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- "master"
|
- 'main'
|
||||||
- "staging"
|
- 'staging'
|
||||||
- "trying"
|
- 'trying'
|
||||||
pull_request:
|
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:
|
jobs:
|
||||||
check:
|
build_and_test:
|
||||||
runs-on: ubuntu-latest
|
env:
|
||||||
|
BUF_VERSION: '0.41.0'
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node: [14]
|
||||||
|
os: [ubuntu-latest, macos-latest]
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
repository: waku-org/js-waku
|
submodules: 'recursive'
|
||||||
|
|
||||||
- uses: actions/setup-node@v3
|
- name: Get nim-waku HEAD
|
||||||
with:
|
id: nim-waku-head
|
||||||
node-version: ${{ env.NODE_JS }}
|
shell: bash
|
||||||
- uses: ./.github/actions/npm
|
run: cd nim-waku && echo "::set-output name=ref::$(git rev-parse HEAD)"
|
||||||
- run: npm run build
|
|
||||||
- run: npm run check
|
|
||||||
- run: npm run doc
|
|
||||||
|
|
||||||
proto:
|
- name: Cache nim-waku binary
|
||||||
runs-on: ubuntu-latest
|
id: cache-nim-waku
|
||||||
steps:
|
uses: actions/cache@v2
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
with:
|
||||||
repository: waku-org/js-waku
|
path: |
|
||||||
- uses: actions/setup-node@v3
|
./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:
|
with:
|
||||||
node-version: ${{ env.NODE_JS }}
|
node-version: ${{ matrix.node }}
|
||||||
- uses: ./.github/actions/npm
|
|
||||||
|
# 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
|
- name: Generate protobuf code
|
||||||
run: |
|
run: npm run proto
|
||||||
npm run proto
|
|
||||||
npm run fix
|
|
||||||
- name: Check all protobuf code was committed
|
- name: Check all protobuf code was committed
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
res=$(git status --short --ignore-submodules)
|
[ $(git status --short --ignore-submodules|wc -l) -eq 0 ]
|
||||||
echo -n "'$res'" # For debug purposes
|
|
||||||
[ $(echo -n "$res"|wc -l) -eq 0 ]
|
|
||||||
|
|
||||||
browser:
|
- name: build
|
||||||
runs-on: ubuntu-latest
|
run: npm run build
|
||||||
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
|
|
||||||
|
|
||||||
node:
|
- name: Check no proto files changed
|
||||||
uses: ./.github/workflows/test-node.yml
|
shell: bash
|
||||||
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 }}
|
|
||||||
run: |
|
run: |
|
||||||
npm run setup:contract-abi -w @waku/rln || {
|
[ $(git status --short --ignore-submodules|wc -l) -eq 0 ]
|
||||||
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
|
- name: test
|
||||||
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 }}
|
|
||||||
env:
|
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/*
|
.idea/*
|
||||||
.angular
|
.nyc_output
|
||||||
build
|
build
|
||||||
bundle
|
|
||||||
dist
|
|
||||||
node_modules
|
node_modules
|
||||||
|
test
|
||||||
src/**.js
|
src/**.js
|
||||||
coverage
|
coverage
|
||||||
*.log
|
*.log
|
||||||
*.tsbuildinfo
|
yarn.lock
|
||||||
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/
|
|
||||||
|
|||||||
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
|
# package.json is formatted by package managers, so we ignore it here
|
||||||
.husky
|
package.json
|
||||||
.vscode
|
gen
|
||||||
nwaku
|
|
||||||
*/**/build
|
|
||||||
*/**/bundle
|
|
||||||
*/**/dist
|
|
||||||
*/**/node_modules
|
|
||||||
*/**/CHANGELOG.md
|
|
||||||
|
|||||||
@ -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.userWords": [], // only use words from .cspell.json
|
||||||
"cSpell.enabled": true,
|
"cSpell.enabled": true,
|
||||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
|
"editor.formatOnSave": true,
|
||||||
"editor.codeActionsOnSave": {
|
|
||||||
"source.fixAll.eslint": "explicit"
|
|
||||||
},
|
|
||||||
"editor.formatOnSave": false, // Disable general format on save
|
|
||||||
"typescript.tsdk": "node_modules/typescript/lib",
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
"typescript.enablePromptUseWorkspaceTsdk": true
|
"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.
|
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:
|
To help ensure your PR passes, just run before committing:
|
||||||
|
|
||||||
- `npm run fix`: To format your code,
|
- `npm run fix`: To format your code,
|
||||||
- `npm run check`: To check your code for linting errors,
|
- `npm run test`: To run all tests, including lint checks.
|
||||||
- `npm run test`: To run all tests
|
|
||||||
|
|
||||||
## Build & Test
|
## Build & Test
|
||||||
|
|
||||||
To build and test this repository, you need:
|
To build and test this repository, you need:
|
||||||
|
|
||||||
- [Node.js & npm](https://nodejs.org/en/).
|
- [Node.js & npm](https://nodejs.org/en/)
|
||||||
- Chrome (for browser testing).
|
- [bufbuild](https://github.com/bufbuild/buf) (only if changing protobuf files)
|
||||||
|
- [protoc](https://grpc.io/docs/protoc-installation/) (only if changing protobuf files)
|
||||||
Run `npm run build` at least once so that intra-dependencies are resolved.
|
|
||||||
|
|
||||||
To ensure interoperability with [nim-waku](https://github.com/status-im/nim-waku/), some tests are run against a nim-waku node.
|
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`.
|
To build nim-waku, you also need [Rust](https://www.rust-lang.org/tools/install).
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
## Guidelines
|
## Guidelines
|
||||||
|
|
||||||
- Please follow [Chris Beam's commit message guide](https://chris.beams.io/posts/git-commit/) for commit patches,
|
- Please follow [Chris Beam's commit message guide](https://chris.beams.io/posts/git-commit/),
|
||||||
- Please test new code, we use [mocha](https://mochajs.org/),
|
- Usually best to test new code,
|
||||||
[chai](https://www.chaijs.com/),
|
|
||||||
[fast-check](https://github.com/dubzzz/fast-check)
|
|
||||||
and [karma](https://karma-runner.github.io/).
|
|
||||||
|
|
||||||
### Committing Patches
|
### 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
|
Please refer to the [Git manual](https://git-scm.com/doc) for more information
|
||||||
about Git.
|
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
|
# 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)
|
Install `js-waku` package:
|
||||||
- [Full documentation](https://docs.waku.org/guides/js-waku)
|
|
||||||
- [API documentation (`master` branch)](https://js.waku.org/)
|
```shell
|
||||||
- [Waku](https://waku.org/)
|
npm install js-waku
|
||||||
- [Vac](https://vac.dev/)
|
```
|
||||||
|
|
||||||
API Documentation can also be generated locally:
|
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
|
```shell
|
||||||
git clone https://github.com/waku-org/js-waku.git
|
|
||||||
cd js-waku
|
|
||||||
npm install
|
npm install
|
||||||
npm run doc
|
npm run doc
|
||||||
```
|
```
|
||||||
|
|
||||||
# Using Nix shell
|
## Waku Protocol Support
|
||||||
```shell
|
|
||||||
git clone https://github.com/waku-org/js-waku.git
|
You can track progress on the [project board](https://github.com/status-im/js-waku/projects/1).
|
||||||
cd js-waku
|
|
||||||
nix develop
|
- ✔: Supported
|
||||||
npm install
|
- 🚧: Implementation in progress
|
||||||
npm run doc
|
- ⛔: 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
|
## 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
|
## Contributing
|
||||||
|
|
||||||
See [CONTRIBUTING.md](https://github.com/waku-org/js-waku/blob/master/CONTRIBUTING.md).
|
See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Licensed and distributed under either of
|
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
|
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.
|
at your option. These files may not be copied, modified, or distributed except according to those terms.
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
status = [
|
status = [
|
||||||
"check",
|
"build_and_test (14, ubuntu-latest)",
|
||||||
"proto",
|
"build_and_test (14, macos-latest)",
|
||||||
"browser",
|
"examples_build_and_test (web-chat)",
|
||||||
"node",
|
"examples_build_and_test (cli-chat)"
|
||||||
]
|
]
|
||||||
block_labels = ["work-in-progress"]
|
block_labels = ["work-in-progress"]
|
||||||
delete_merged_branches = true
|
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",
|
"name": "js-waku",
|
||||||
"private": true,
|
"version": "0.2.0",
|
||||||
"type": "module",
|
"description": "TypeScript implementation of the Waku v2 protocol",
|
||||||
"workspaces": [
|
"main": "build/main/index.js",
|
||||||
"packages/proto",
|
"typings": "build/main/index.d.ts",
|
||||||
"packages/interfaces",
|
"module": "build/module/index.js",
|
||||||
"packages/utils",
|
"repository": "https://github.com/status-im/js-waku",
|
||||||
"packages/enr",
|
"license": "MIT OR Apache-2.0",
|
||||||
"packages/core",
|
"keywords": [
|
||||||
"packages/discovery",
|
"waku",
|
||||||
"packages/message-encryption",
|
"decentralised",
|
||||||
"packages/sds",
|
"communication"
|
||||||
"packages/rln",
|
|
||||||
"packages/sdk",
|
|
||||||
"packages/relay",
|
|
||||||
"packages/run",
|
|
||||||
"packages/tests",
|
|
||||||
"packages/reliability-tests",
|
|
||||||
"packages/browser-tests",
|
|
||||||
"packages/build-utils",
|
|
||||||
"packages/react"
|
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepare": "husky",
|
"build": "run-s build:*",
|
||||||
"build": "npm run build --workspaces --if-present",
|
"build:main": "tsc -p tsconfig.json",
|
||||||
"build:esm": "npm run build:esm --workspaces --if-present",
|
"build:module": "tsc -p tsconfig.module.json",
|
||||||
"size": "npm run build && size-limit",
|
"build:dev": "tsc -p tsconfig.dev.json",
|
||||||
"fix": "run-s fix:*",
|
"fix": "run-s fix:*",
|
||||||
"fix:workspaces": "npm run fix --workspaces --if-present",
|
"fix:prettier": "prettier \"src/**/*.ts\" \"./*.json\" --write",
|
||||||
"check": "run-s check:*",
|
"fix:lint": "eslint src --ext .ts --fix",
|
||||||
"check:workspaces": "npm run check --workspaces --if-present",
|
"pretest": "run-s pretest:*",
|
||||||
"check:ws": "[ $(ls -1 ./packages|wc -l) -eq $(cat package.json | jq '.workspaces | length') ] || exit 1 # check no packages left behind",
|
"pretest:1-init-git-submodules": "[ -f './nim-waku/build/wakunode2' ] || git submodule update --init --recursive",
|
||||||
"test": "NODE_ENV=test npm run test --workspaces --if-present",
|
"pretest:2-build-nim-waku": "[ -f './nim-waku/build/wakunode2' ] || run-s nim-waku:build",
|
||||||
"test:browser": "NODE_ENV=test npm run test:browser --workspaces --if-present",
|
"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)",
|
||||||
"test:node": "NODE_ENV=test npm run test:node --workspaces --if-present",
|
"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:longevity": "npm --prefix packages/reliability-tests run test:longevity",
|
"test": "run-s build test:*",
|
||||||
"test:high-throughput": "npm --prefix packages/reliability-tests run test:high-throughput",
|
"test:lint": "eslint src --ext .ts",
|
||||||
"test:throughput-sizes": "npm --prefix packages/reliability-tests run test:throughput-sizes",
|
"test:prettier": "prettier \"src/**/*.ts\" \"./*.json\" --list-different",
|
||||||
"test:network-latency": "npm --prefix packages/reliability-tests run test:network-latency",
|
"test:spelling": "cspell \"{README.md,.github/*.md,src/**/*.ts}\"",
|
||||||
"test:low-bandwidth": "npm --prefix packages/reliability-tests run test:low-bandwidth",
|
"test:unit": "nyc --silent mocha",
|
||||||
"test:packet-loss": "npm --prefix packages/reliability-tests run test:packet-loss",
|
"proto": "run-s proto:*",
|
||||||
"proto": "npm run proto --workspaces --if-present",
|
"proto:lint": "buf lint",
|
||||||
"deploy": "node ci/deploy.js",
|
"proto:build": "buf generate",
|
||||||
"doc": "run-s doc:*",
|
"check-cli": "run-s test diff-integration-tests check-integration-tests",
|
||||||
"doc:html": "typedoc --options typedoc.cjs",
|
"check-integration-tests": "run-s check-integration-test:*",
|
||||||
"doc:cname": "echo 'js.waku.org' > docs/CNAME",
|
"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.'",
|
||||||
"publish": "node ./ci/publish.js"
|
"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": {
|
"devDependencies": {
|
||||||
"@size-limit/preset-big-lib": "^11.0.2",
|
"@istanbuljs/nyc-config-typescript": "^1.0.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.6.0",
|
"@types/app-root-path": "^1.2.4",
|
||||||
"@typescript-eslint/parser": "^6.21.0",
|
"@types/axios": "^0.14.0",
|
||||||
"eslint": "^8.56.0",
|
"@types/chai": "^4.2.15",
|
||||||
"eslint-config-prettier": "^9.0.0",
|
"@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-eslint-comments": "^3.2.0",
|
||||||
"eslint-plugin-functional": "^6.0.1",
|
"eslint-plugin-functional": "^3.0.2",
|
||||||
"eslint-plugin-import": "^2.27.5",
|
"eslint-plugin-import": "^2.22.0",
|
||||||
"eslint-plugin-prettier": "^5.1.3",
|
"fast-check": "^2.14.0",
|
||||||
"gh-pages": "^6.1.1",
|
"gh-pages": "^3.1.0",
|
||||||
"husky": "^9.0.11",
|
"mocha": "^8.3.2",
|
||||||
"karma": "^6.4.2",
|
"npm-run-all": "^4.1.5",
|
||||||
"karma-chrome-launcher": "^3.2.0",
|
"nyc": "^15.1.0",
|
||||||
"karma-firefox-launcher": "^2.1.3",
|
"open-cli": "^6.0.1",
|
||||||
"karma-mocha": "^2.0.1",
|
"p-timeout": "^4.1.0",
|
||||||
"karma-webkit-launcher": "^2.4.0",
|
"prettier": "^2.1.1",
|
||||||
"karma-webpack": "github:codymikol/karma-webpack#2337a82beb078c0d8e25ae8333a06249b8e72828",
|
"standard-version": "^9.0.0",
|
||||||
"lint-staged": "^15.4.3",
|
"tail": "^2.2.0",
|
||||||
"playwright": "^1.40.1",
|
"ts-node": "^9.1.1",
|
||||||
"size-limit": "^11.0.1",
|
"typedoc": "^0.20.29",
|
||||||
"ts-loader": "9.5.2",
|
"typescript": "^4.0.2"
|
||||||
"ts-node": "10.9.2",
|
|
||||||
"typedoc": "0.28.5",
|
|
||||||
"typescript": "5.8.3",
|
|
||||||
"wscat": "^6.0.1"
|
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"files": [
|
||||||
"*.{ts,js}": [
|
"build/main",
|
||||||
"eslint --fix"
|
"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