Building blockchain data streams into Angular 5 using websockets (part 2)

Building websockets and blockchain into Angular 5 (part 2)

The demo:

Working demo, Drupal → Angular via blockchain
  1. Index:
  2. Introduction
  3. Step 1: Write to a steam
  4. Step 2:Prepare Angular
  5. Conclusion

Chainfrog are really happy and excited by the interest we’ve been getting in our blockchain activity recently. Having developed a patented database synchronization tool to share data securely via blockchain, the strongest feedback we’ve received is a desire to see ‘under the hood’. As I can’t give away our IP, I have removed our tool from the middle and have a full end to end demonstration for using blockchain as a web communication protocol. If you want to read a bit about the ‘how’ and the ‘why’, feel free to revisit my previous blogs:

If you want to get your hands on the juicy code, read-on. This tutorial will allow you to be up and running with open-source tools in a few hours.

Step 1: Hook multichain daemon and write to a steam

In the previous step, we’ve got our two cloud nodes setup and connected to the same blockchain. Now we need a way to connect the output of our blockchain to our web application. There are many ways we could do this (for example, we could use drush to update Drupal, or we could use a reverse proxy and post data via http request), but in this demo I’ve chosen to use web sockets and pipes. The reason behind this is that we can subscribe our Angular application to the web socket and then we get a ‘real time’ update every-time that a block is published to the blockchain. There are many ways to skin a cat and I’ve chosen the more interesting over the more usual!

So the first step is for us to hook up the daemon. In the multichain runtime parameters https://www.multichain.com/developers/runtime-parameters/ we can see the ‘blocknotify’ command, a command executed every time a new block is published to the blockchain.

blocknotifyExecute this command when a new block is added at the end of the current chain. A %s in the command parameters will be substituted with the block hash.

So, the plan is simply to write the block hash to a FIFO in linux, then subscribe to this file through our ExpressJS reverse proxy.

# login to node 2
cd /var/www/medium-demo-angular
mkdir server
cd server
mkfifo blocks.fifo
# Write block hashes to fifo when initalize daemon
multichaind medium-demo-blockchain@159.65.5203:7891 -blocknotify="echo '%s' > /var/www/medium-demo-angular/server/blocks.fifo" -daemon
#Check this is working
nano fifo.sh

fifo.sh

#!/bin/bashpipe=/var/www/medium-demo-angular/server/blocks.fifotrap "rm -f $pipe" EXITwhile true
do
if read line <$pipe; then
echo $line
fi
done
echo "Reader exiting"#exit

Now we can run the script!

sh fifo.sh

Now if we simply add content to https://medium-demo-drupal.blockbinder.com (or your node 1), the terminal will be subscribed to the FIFO and you should get output like the below. Hashes from our blockchain!

All going well, your command line is now full of wonderful block hashes!
cd /var/www/medium-demo-angular
mkdir server
cd server
# Setup express server
nano package.json

package.json

{
"name": "auth",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {},
"dependencies": {
"body-parser": "^1.18.2",
"express": "^4.16.2",
"ws": "^5.0.0"
}
}

Install packages

npm install
node server.js

Setup express server

const express = require('express'), bodyParser = require('body-parser');
const http = require('http');
const url = require('url');
const WebSocket = require('ws');
const app = express();
app.use(bodyParser.json())
var exec = require('child_process').exec;
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
var fs = require('fs')
wss.broadcast = function broadcast(msg) {
console.log(msg);
wss.clients.forEach(function each(client) {
client.send(msg);
});
};
wss.on('connection', function connection(ws, req) {
const location = url.parse(req.url, true);
// You might use location.query.access_token to authenticate or share sessions
// or req.headers.cookie (see http://stackoverflow.com/a/16395220/151312)
console.log('Server is connected');
// PIPE LISTENER
const fd = fs.openSync('/var/www/medium-demo-angular/server/blocks.fifo', 'r+')
const stream = fs.createReadStream(null, {fd})
stream.on('data', data => {
function puts(error, stdout, stderr) { sys.puts(stdout) }
exec("multichain-cli medium-demo-blockchain liststreamitems root", function(error, stdout, stderr) {
if (!error) {
// things worked!
// console.log(stdout)
wss.broadcast(stdout);
} else {
console.log(stderr)
// things failed :(
}
});
})ws.on('close', function(code, reason) {
console.log(code);
console.log(reason);
});
});app.get("/retrieve-database" , function (request, response) {function puts(error, stdout, stderr) { sys.puts(stdout) }
exec("multichain-cli medium-demo-blockchain liststreamitems root", function(error, stdout, stderr) {
if (!error) {
// things worked!
console.log(stdout)
response.send(stdout)
} else {
console.log(error)
// things failed :(
}
});
});
server.listen(3500, function listening() {
console.log('Listening on %d', server.address().port);
});

Congrats, we now have an ExpressJS reverse proxy opening a websocket which is subscribed to a pipe! Cool isn’t it (not the most efficient method but definitely the most fun). We now need to subscribe Angular to a websocket, ensuring real-time updates to the HTML.

Step 2 — Hook up Angular to the express server.

Now, we just need a way for Angular to connect to the express server. There are a few files you will need to edit on the Angular side:

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HttpModule } from '@angular/http';
import { HttpClientModule } from '@angular/common/http';
// Material inputs
import { MatInputModule, MatCheckboxModule, MatToolbarModule } from '@angular/material';
import { MatIconModule } from '@angular/material';
import { MatListModule } from '@angular/material';
import { MatCardModule } from '@angular/material';
import { MatButtonModule } from '@angular/material';
import { MatGridListModule } from '@angular/material';
import { MatMenuModule } from '@angular/material';
import { MatSidenavModule } from '@angular/material';
import { MatChipsModule } from '@angular/material';
import { MatRadioModule, MatPaginatorModule } from '@angular/material';
import { MatStepperModule } from '@angular/material/stepper';
import { MatSelectModule } from '@angular/material/select';
import { MatTabsModule } from '@angular/material/tabs';
import { MatTableModule } from '@angular/material/table';
import { DataService } from './data.service';
import { WebSocketService } from './websocket.service';
import { AppComponent } from './app.component';
import { HeaderComponent } from './blocks/header/header.component';
import { FooterComponent } from './blocks/footer/footer.component';
import { FlexLayoutModule } from '@angular/flex-layout';
@NgModule({
declarations: [
AppComponent,
HeaderComponent,
FooterComponent
],
imports: [
BrowserModule,
FormsModule,
ReactiveFormsModule,
MatIconModule,
MatListModule,
MatCardModule,
MatInputModule,
MatCheckboxModule,
MatButtonModule,
MatGridListModule,
MatChipsModule,
MatMenuModule,
MatSidenavModule,
MatSelectModule,
MatRadioModule,
MatStepperModule,
MatPaginatorModule,
MatToolbarModule,
MatTabsModule,
MatTableModule,
HttpModule,
HttpClientModule,
BrowserAnimationsModule,
FlexLayoutModule
],
providers: [DataService, WebSocketService],
bootstrap: [AppComponent]
})
export class AppModule { }

app.component.ts

import { Component, ViewChild, ElementRef, OnInit, AfterViewInit } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { WebSocketSubject } from 'rxjs/observable/dom/WebSocketSubject';
import { ActivatedRoute, Router, NavigationEnd } from '@angular/router'; // <-- do not forget to import
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MatPaginator, MatTableDataSource } from '@angular/material';
import { DataService } from './data.service';
import { WebSocketService } from './websocket.service';
import { trigger,style,transition,animate,keyframes,query,stagger,state } from '@angular/animations';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
providers: [DataService, WebSocketService],
animations: [ trigger('onOffTrigger', [
state('off', style({
backgroundColor: '#E5E7E9',
transform: 'scale(1)'
})),
state('on', style({
backgroundColor: 'green',
transform: 'scale(1.1)'
})),
transition('off => on', animate('.6s 100ms ease-in')),
transition('on => off', animate('.7s 100ms ease-out'))
])]
})
export class AppComponent {
title = 'app';
addProduct: FormGroup;
@ViewChild(MatPaginator) paginator: MatPaginator;
// Data for table
displayedColumns = ['title','body','date'];
dataSource = new MatTableDataSource();
array1: any = [];
array2: any = [];
constructor(
private _formBuilder: FormBuilder,
public dataService : DataService,
private wsService: WebSocketService
) {
this.wsService.createObservableSocket('wss://angular.blockbinder.com/api/')
.subscribe(message => {
console.log(message);
this.dataService.tableData().subscribe(updatedData => {
this.array2 = updatedData['data'];
var length = this.array1.length;
var length2 = this.array2.length;
var lengthdifference = length2 - length;
for(let key in this.array2){
this.array2[key].active = 'off';
}
this.dataSource = new MatTableDataSource(this.array2);
if(lengthdifference > 0){
this.array2[0].active = 'on';
}
setTimeout(()=>{ //<<<--- using ()=> syntax
this.array2[0].active = 'off';
},3000);
})
},
err => console.log(err),
() => console.log('stream complete')
);
}
ngAfterViewInit() {
this.dataService.tableData().subscribe(response => {
this.array1 = response['data'];
for(let key in this.array1){
this.array1[key].active = 'off';
}
this.dataSource = new MatTableDataSource(this.array1);
})
}
}

app.component.html

<app-header></app-header>  
<mat-sidenav-container>
<mat-sidenav #sidenav mode="side" class="sidebar" [fixedInViewport]="false" [fixedTopGap]="0" [fixedBottomGap]="0">
<div class="sidebar__container fullwidth">
</div>
</mat-sidenav>
<mat-sidenav-content>
<div class="container-fluid primary">
<div class="container__inner" id="top">
<section class="main">
<h1>Welcome to our Medium demo, here you can see Drupal data appearing in real time in an Angular table! The fun bit -- it was transferred via Blockchain</h1>
<div
fxLayout
fxLayout.xs="column"
fxLayoutAlign="center"
fxLayoutGap="30px"
fxLayoutGap.xs="0"
>
<div fxFlex>
<mat-table fxFlex #table [dataSource]="dataSource">
<!-- Value Column -->
<ng-container matColumnDef="title">
<mat-header-cell *matHeaderCellDef>Title</mat-header-cell>
<mat-cell *matCellDef="let element">
{{element.data.json.title}}
</mat-cell>
</ng-container><!-- Value Column -->
<ng-container matColumnDef="body">
<mat-header-cell *matHeaderCellDef>body</mat-header-cell>
<mat-cell *matCellDef="let element">
{{element.data.json.body}}
</mat-cell>
</ng-container><!-- Value Column -->
<ng-container matColumnDef="date">
<mat-header-cell *matHeaderCellDef>date</mat-header-cell>
<mat-cell *matCellDef="let element">
{{element.blocktime}}
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row [@onOffTrigger]="row.active" *matRowDef="let row; columns: displayedColumns;"></mat-row>
</mat-table>
</div>
</div>
</section>
</div>
</div>
</mat-sidenav-content>
</mat-sidenav-container>
<app-footer></app-footer>

package.json

{
"name": "blockbinder-shop-api",
"version": "0.0.0",
"license": "MIT",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build --prod",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/animations": "^5.2.0",
"@angular/cdk": "^5.1.0",
"@angular/common": "^5.2.0",
"@angular/compiler": "^5.2.0",
"@angular/core": "^5.2.0",
"@angular/forms": "^5.2.0",
"@angular/http": "^5.2.0",
"@angular/material": "^5.1.0",
"@angular/platform-browser": "^5.2.2",
"@angular/platform-browser-dynamic": "^5.2.2",
"@angular/router": "^5.2.0",
"@angular/flex-layout": "git+https://github.com/angular/flex-layout-builds.git",
"core-js": "^2.4.1",
"rxjs": "^5.5.6",
"zone.js": "^0.8.19"
},
"devDependencies": {
"@angular/cli": "1.6.6",
"@angular/compiler-cli": "^5.2.0",
"@angular/language-service": "^5.2.0",
"@types/jasmine": "~2.8.3",
"@types/jasminewd2": "~2.0.2",
"@types/node": "~6.0.60",
"codelyzer": "^4.0.1",
"jasmine-core": "~2.8.0",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~2.0.0",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "^1.2.1",
"karma-jasmine": "~1.1.0",
"karma-jasmine-html-reporter": "^0.2.2",
"protractor": "~5.1.2",
"ts-node": "~4.1.0",
"tslint": "~5.9.1",
"typescript": "~2.5.3"
}
}

websocket.service.ts

import { Observable } from 'rxjs/Rx';export class WebSocketService {
ws: WebSocket;
createObservableSocket(url:string): Observable<string>{
this.ws = new WebSocket(url);
return new Observable(observer => {
this.ws.onmessage = (event) => observer.next(event.data);
this.ws.onerror = (event) => observer.error(event);
this.ws.onclose = (event) => observer.complete();
});
}
}

data.service.ts

import { Injectable, Inject }    from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/Rx';
import 'rxjs/add/operator/toPromise';
class promise {};@Injectable()
export class DataService {
host: string = 'https://angular.blockbinder.com/api/';constructor(private http: HttpClient) {}tableData() : Observable<promise> {
return this.http.get(
this.host+'retrieve-database'
);
}
}

As is the case with Angular, often dependencies will update and so you will need to do a few edits to your application to ensure the safe building / running. If you want the complete code including styles etc for reference I’ve put it into github here: https://github.com/Ejb503/medium-demo-angular

Now we have Angular setup and subscribing to the blocks that are coming out of the blockchain, let’s build the app.

# load node 2
cd /var/www/medium-demo-angular
ng build

Conclusion

In this two part demo, we have setup two web applications that are communicating via blockchain. We have a Drupal application on one end, and as the content is added, it is appearing live in the Angular 5 on the other end. While this has been an example of a->b via blockchain, we can also expand this for more useful applications. a->b,c,d for example, where multiple nodes update their data when the source is updated. This can also be two way, so changes from the Angular application feed back into Drupal.

This is just a simple prototype and example of using blockchain to synchronize web applications! Please read the others blogs for more on the debate of ‘why’ you would choose to do this!

COVID-19 Blockchain response ->

In these difficult times we’ve published a POC system for a blockchain distributed system to provide data feeds and implement a traffic light system, follow the blogs here: https://medium.com/@ed_burton/a-blockchain-covid-response-system-poc-889d9b74786e

--

--

--

Founder @ yown.it

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Samsung Galaxy S10 to Include Storage For Private Crypto Keys

BlockchainSpace Partners with Japanese Gaming Powerhouse LCA Game Guild

Uniswap Clone Script

📣 Weekly Development Status Updates on Zodium!

Kyber’s Blockchain Infrastructure Provider: Alchemy

HOSWAP LP Farming: What You Need To Know

Is blockchain an effective tool to fight against Coronavirus?

InfoCorp Partners Green Delta Insurance

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Edward Burton

Edward Burton

Founder @ yown.it

More from Medium

Connection Pooling in Database

Reactive markup using vanilla Javascript

The Iteration of Array Using for-Loop

JavaScript Variable Scope and Declaration