+ Send and Receive Messages Using Waku Relay With Angular v13 + # +
+It is easy to use Waku Connect with Angular v13.
+In this guide, we will demonstrate how your Angular dApp can use Waku Relay +to send and receive messages.
+Before starting, you need to choose a Content Topic for your dApp. +Check out the how to choose a content topic guide to learn more about content topics.
+For this guide, we are using a single content topic: /relay-angular-chat/1/chat/proto
.
+ Setup + # +
+Create a new Angular app:
+npm install -g @angular/cli
+ng new relay-angular-chat
+cd relay-angular-chat
+
+ BigInt
+ #
+
+Some of js-waku’s dependencies use BigInt
+that is only supported by modern browsers.
To ensure that Angular properly transpiles your webapp code, add the following configuration to the package.json
file:
{
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not ie <= 99",
+ "not android <= 4.4.4",
+ "not dead",
+ "not op_mini all"
+ ]
+ }
+}
+
+ Polyfills + # +
+A number of Web3 and libp2p dependencies need polyfills. +These must be explicitly declared when using webpack 5.
+The latest Angular
version (v13) uses webpack 5.
We will describe below a method to configure polyfills when using Angular v13 / webpack v5
.
+This may not be necessary if you use webpack 4.
Start by installing the polyfill libraries:
+yarn add assert buffer crypto-browserify process stream-browserify
+
Then add the following code to src/polyfills.ts
:
import * as process from 'process';
+(window as any).process = process;
+(window as any).global = window;
+global.Buffer = global.Buffer || require('buffer').Buffer;
+
Now tell Angular where to find these libraries by adding the following to tsconfig.json
+under "compilerOptions"
:
{
+ "paths": {
+ "assert": ["node_modules/assert"],
+ "buffer": ["node_modules/buffer"],
+ "crypto": ["node_modules/crypto-browserify"],
+ "stream": ["node_modules/stream-browserify"]
+ }
+}
+
Now under "angularCompilerOptions"
, add:
"allowSyntheticDefaultImports": true
+
Finally, set the "target"
to be "es2020"
due to the aforementioned BigInt
usage.
+ Module loading warnings + # +
+There will be some warnings due to module loading.
+We can fix them by setting the "allowedCommonJsDependencies"
key under
+architect -> build -> options
with the following:
{
+ "allowedCommonJsDependencies": [
+ "libp2p-gossipsub/src/utils",
+ "rlp",
+ "multiaddr/src/convert",
+ "varint",
+ "multihashes",
+ "@chainsafe/libp2p-noise/dist/src/noise",
+ "debug",
+ "libp2p",
+ "libp2p-bootstrap",
+ "libp2p-crypto",
+ "libp2p-websockets",
+ "libp2p-websockets/src/filters",
+ "libp2p/src/ping",
+ "multiaddr",
+ "peer-id",
+ "buffer",
+ "crypto",
+ "ecies-geth",
+ "secp256k1",
+ "libp2p-gossipsub",
+ "it-concat",
+ "protons"
+ ]
+}
+
+ Types + # +
+There are some type definitions we need to install and some that we don’t have.
+yarn add @types/bl protons
+
Create a new folder under src
named @types
with the following structure:
src/@types
+├── protons
+│ └── types.d.ts
+└── time-cache
+ └── types.d.ts
+
In the protons/types.d.ts
file add:
declare module 'protons';
+
In the time-cache/types.d.ts
file add:
declare module "time-cache" {
+
+ interface TimeCacheInterface {
+ put(key: string, value: any, validity: number): void;
+ get(key: string): any;
+ has(key: string): boolean;
+ }
+
+ type TimeCache = TimeCacheInterface;
+
+ function TimeCache(options: object): TimeCache;
+
+ export = TimeCache;
+}
+
+ js-waku + # +
+Then, install js-waku:
+yarn add js-waku
+
Start the dev server and open the dApp in your browser:
+yarn run start
+
+ Create Waku Instance + # +
+In order to interact with the Waku network, you first need a Waku instance.
+We’re going to wrap the js-waku
library in a Service so we can inject it to different components when needed.
Generate the Waku service:
+ng generate service waku
+
Go to waku.service.ts
and add the following imports:
import { Waku } from "js-waku";
+import { ReplaySubject } from "rxjs";
+
replace the WakuService
class with the following:
export class WakuService {
+
+ // Create Subject Observable to 'store' the Waku instance
+ private wakuSubject = new Subject<Waku>();
+ public waku = this.wakuSubject.asObservable();
+
+ // Create BehaviorSubject Observable to 'store' the Waku status
+ private wakuStatusSubject = new BehaviorSubject('');
+ public wakuStatus = this.wakuStatusSubject.asObservable();
+
+ constructor() { }
+
+ init() {
+ // Connect node
+ Waku.create({ bootstrap: { default: true } }).then(waku => {
+ // Update Observable values
+ this.wakuSubject.next(waku);
+ this.wakuStatusSubject.next('Connecting...');
+
+ waku.waitForRemotePeer().then(() => {
+ // Update Observable value
+ this.wakuStatusSubject.next('Connected');
+ });
+ });
+ }
+}
+
When using the bootstrap
option, it may take some time to connect to other peers.
+That’s why we use the waku.waitForRemotePeer
function to ensure that there are relay peers available to send and receive messages.
Now we can inject the WakuService
in to the AppComponent
class to initialise the node and
+subscribe to any status changes.
Firstly, import the WakuService
:
import { WakuService } from "./waku.service";
+
Then update the AppComponent
class with the following:
export class AppComponent {
+
+ title: string = 'relay-angular-chat';
+ wakuStatus!: string;
+
+ // Inject the service
+ constructor(private wakuService: WakuService) {}
+
+ ngOnInit(): void {
+ // Call the `init` function on the service
+ this.wakuService.init();
+ // Subscribe to the `wakuStatus` Observable and update the property when it changes
+ this.wakuService.wakuStatus.subscribe(wakuStatus => {
+ this.wakuStatus = wakuStatus;
+ });
+ }
+}
+
Add the following HTML to the app.component.html
to show the title and render the connection status:
<h1>{{title}}</h1>
+<p>Waku node's status: {{ wakuStatus }}</p>
+
+ Messages + # +
+Now we need to create a component to send, receive and render the messages.
+ng generate component messages
+
You might need to add this to NgModule
for Angular to pick up the new component.
+Import and add MessagesComponent
to the declarations
array in app.module.ts
.
We’re going to need the WakuService
again but also the Waku
and WakuMessage
classes from js-waku
.
+We already installed protons
+and we’re going to use that here so we’ll need to import it.
import { WakuService } from "../waku.service";
+import { Waku, WakuMessage } from "js-waku";
+import protons from "protons";
+
Let’s use protons
to define the Protobuf message format with two fields:
+timestamp
and text
:
const proto = protons(`
+message SimpleChatMessage {
+ uint64 timestamp = 1;
+ string text = 2;
+}
+`);
+
Let’s also define a message interface
:
interface MessageInterface {
+ timestamp: Date;
+ text: string;
+}
+
+ Send Messages + # +
+In order to send a message, we need to define a few things.
+The contentTopic
is the topic we want subscribe to and the payload
is the message.
+We’ve also defined a timestamp
so let’s create that.
The messageCount
property is just to distinguish between messages.
We also need our waku
instance and wakuStatus
property.
+We will subscribe to the waku
and wakuStatus
Observables from the WakuService
to get them.
export class MessagesComponent {
+
+ contentTopic: string = `/relay-angular-chat/1/chat/proto`;
+ messageCount: number = 0;
+ waku!: Waku;
+
+ // ...
+
+ // Inject the `WakuService`
+ constructor(private wakuService: WakuService) { }
+
+ ngOnInit(): void {
+ // Subscribe to the `wakuStatus` Observable and update the property when it changes
+ this.wakuService.wakuStatus.subscribe(wakuStatus => {
+ this.wakuStatus = wakuStatus;
+ });
+
+ // Subscribe to the `waku` Observable and update the property when it changes
+ this.wakuService.waku.subscribe(waku => {
+ this.waku = waku;
+ });
+ }
+
+ sendMessage(): void {
+ const time = new Date().getTime();
+
+ const payload = proto.SimpleChatMessage.encode({
+ timestamp: time,
+ text: `Here is a message #${this.messageCount}`,
+ });
+
+ WakuMessage.fromBytes(payload, this.contentTopic).then(wakuMessage => {
+ this.waku.relay.send(wakuMessage).then(() => {
+ console.log(`Message #${this.messageCount} sent`);
+ this.messageCount += 1;
+ });
+ });
+ }
+}
+
Then, add a button to the messages.component.html
file to wire it up to the sendMessage()
function.
+It will also disable the button until the node is connected.
<button (click)="sendMessage()" [disabled]="wakuStatus !== 'Connected'">Send Message</button>
+
+ Receive Messages + # +
+To process incoming messages, you need to register an observer on Waku Relay.
+First, you need to define the observer function which decodes the message
+and pushes it in to the messages
array.
Again, in the messages.component.ts
:
export class MessagesComponent {
+ // ...
+ // Store the messages in an array
+ messages: MessageInterface[] = [];
+ // ...
+
+ processIncomingMessages = (wakuMessage: WakuMessage) => {
+ if (!wakuMessage.payload) return;
+
+ const { timestamp, text } = proto.SimpleChatMessage.decode(
+ wakuMessage.payload
+ );
+
+ const time = new Date();
+ time.setTime(timestamp);
+ const message = { text, timestamp: time };
+
+ this.messages.push(message);
+ };
+}
+
We’ll also need to delete the observer when the component gets destroyed +to avoid memory leaks:
+ngOnDestroy(): void {
+ this.waku.relay.deleteObserver(this.processIncomingMessages, [this.contentTopic]);
+}
+
Angular won’t delete the observer when the page reloads so we’ll have to hook that up ourselves.
+Add the following to the ngOnInit()
function:
window.onbeforeunload = () => this.ngOnDestroy();
+
+ Display Messages + # +
+Congratulations! The Waku work is now done. +Your dApp is able to send and receive messages using Waku. +For the sake of completeness, let’s display received messages on the page.
+We’ve already added the messages
array and pushed the incoming message to it.
+So all we have to do now is render them to the page.
In the messages.component.html
, add the following under the button
:
<h2>Messages</h2>
+<ul class="messages">
+ <li *ngFor="let message of messages">
+ <span>{{ message.timestamp }} {{ message.text }}</span>
+ </li>
+</ul>
+
And Voilà! You should now be able to send and receive messages. +Try it out by opening the app from different browsers!
+You can see the complete code in the Relay Angular Chat Example App.
+