From 22eab4285b3e581ee3daf5338c33d71008e7b82e Mon Sep 17 00:00:00 2001 From: jemboh Date: Fri, 18 Feb 2022 09:38:14 +0000 Subject: [PATCH] Documentation for Angular and Waku to send and receive messages (#38) Documentation for Angular and Waku to send and receive messages A guide to getting started with Angular 13 and js-waku. It goes through all the steps, from installation, setup, polyfills and working code. --- content/docs/examples.md | 17 +- content/docs/guides/10_angular_relay.md | 452 ++++++++++++++++++++++++ content/docs/guides/_index.md | 4 + 3 files changed, 471 insertions(+), 2 deletions(-) create mode 100644 content/docs/guides/10_angular_relay.md diff --git a/content/docs/examples.md b/content/docs/examples.md index c3d50e5..7ec0778 100644 --- a/content/docs/examples.md +++ b/content/docs/examples.md @@ -11,7 +11,7 @@ To run or studies the example, click on the _repo_ links. ## Minimal ReactJS Chat App -Repo: [min-react-js-chat](https://github.com/status-im/js-waku/tree/main/examples/min-react-js-chat). +Repo: [relay-reactjs-chat](https://github.com/status-im/js-waku/tree/master/examples/relay-reactjs-chat). Demonstrates: @@ -23,7 +23,7 @@ Demonstrates: ## Minimal ReactJS Waku Store App -Repo: [store-reactjs-chat](https://github.com/status-im/js-waku/tree/main/examples/store-reactjs-chat). +Repo: [store-reactjs-chat](https://github.com/status-im/js-waku/tree/master/examples/store-reactjs-chat). Demonstrates: @@ -31,6 +31,19 @@ Demonstrates: - React/JavaScript - Protobuf using [protons](https://www.npmjs.com/package/protons) +## Minimal Angular Chat App + +Repo: [relay-angular-chat](https://github.com/status-im/js-waku/tree/master/examples/relay-angular-chat). + +Demonstrates: + +- Group chat +- Angular/JavaScript +- Waku Relay +- Protobuf using [protons](https://www.npmjs.com/package/protons) +- No async/await syntax +- Observables + ## Vanilla Javascript Using Minified Library Repo: [unpkg-js-store](https://github.com/status-im/js-waku/tree/main/examples/unpkg-js-store). diff --git a/content/docs/guides/10_angular_relay.md b/content/docs/guides/10_angular_relay.md new file mode 100644 index 0000000..3b26376 --- /dev/null +++ b/content/docs/guides/10_angular_relay.md @@ -0,0 +1,452 @@ +--- +title: Send and Receive Messages Using Waku Relay With Angular v13 +date: 2022-02-15T09:00:00+01:00 +weight: 7 +--- + +# 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](/docs/guides/01_choose_content_topic/) 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: + +```shell +npm install -g @angular/cli +ng new relay-angular-chat +cd relay-angular-chat +``` + +### `BigInt` + +Some of js-waku's dependencies use [`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) +that is [only supported by modern browsers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#browser_compatibility). + +To ensure that Angular properly transpiles your webapp code, add the following configuration to the `package.json` file: + +```json +{ + "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: + +```shell +yarn add assert buffer crypto-browserify process stream-browserify +``` + +Then add the following code to `src/polyfills.ts`: + +```js +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"`: + +```json +{ + "paths": { + "assert": ["node_modules/assert"], + "buffer": ["node_modules/buffer"], + "crypto": ["node_modules/crypto-browserify"], + "stream": ["node_modules/stream-browserify"] + } +} +``` + +Now under `"angularCompilerOptions"`, add: + +```json +"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: + +```json +{ + "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. + +```shell +yarn add @types/bl protons +``` + +Create a new folder under `src` named `@types` with the following structure: + +```shell +src/@types +├── protons +│   └── types.d.ts +└── time-cache + └── types.d.ts +``` + +In the `protons/types.d.ts` file add: + +```js +declare module 'protons'; +``` + +In the `time-cache/types.d.ts` file add: + +```js +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](https://npmjs.com/package/js-waku): + +```shell +yarn add js-waku +``` + +Start the dev server and open the dApp in your browser: + +```shell +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: + +```shell +ng generate service waku +``` + +Go to `waku.service.ts` and add the following imports: + +```js +import { Waku } from "js-waku"; +import { ReplaySubject } from "rxjs"; +``` + +replace the `WakuService` class with the following: + +```js +export class WakuService { + + // Create Subject Observable to 'store' the Waku instance + private wakuSubject = new Subject(); + 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`: + +```js +import { WakuService } from "./waku.service"; +``` + +Then update the `AppComponent` class with the following: + +```js +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: + +```html +

{{title}}

+

Waku node's status: {{ wakuStatus }}

+``` + +## Messages + +Now we need to create a component to send, receive and render the messages. + +```shell +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](https://www.npmjs.com/package/protons) +and we're going to use that here so we'll need to import it. + +```js +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`: + +```js +const proto = protons(` +message SimpleChatMessage { + uint64 timestamp = 1; + string text = 2; +} +`); +``` + +Let's also define a message `interface`: + +```js +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. + +```js +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. + +``` + +``` + +## 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`: + +```js +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: + +```js +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: + +```js +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`: + +```html +

Messages

+ +``` + +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](https://github.com/status-im/js-waku/tree/main/examples/relay-angular-chat). diff --git a/content/docs/guides/_index.md b/content/docs/guides/_index.md index 520da4f..779b210 100644 --- a/content/docs/guides/_index.md +++ b/content/docs/guides/_index.md @@ -21,3 +21,7 @@ weight: 30 - [Receive and Send Messages Using Waku Relay With ReactJS](./07_reactjs_relay/) - [Retrieve Messages Using Waku Store With ReactJS](./08_reactjs_store/) + +## Angular + +- [Receive and Send Messages Using Waku Relay With ReactJS](./10_angular_relay/)