Send and Receive Messages Using Waku Relay With Angular v13 | Waku Connect Docs
Send and Receive Messages Using Waku Relay With Angular v13

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 that is .

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 :

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 initialize 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 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 .