Add connection to relay and display live stream of 100 latest events

This commit is contained in:
SondreB 2022-12-21 19:08:41 +01:00
parent 2ff68bb702
commit eb37e154e6
No known key found for this signature in database
GPG Key ID: D6CC44C75005FDBF
15 changed files with 1676 additions and 92 deletions

View File

@ -55,3 +55,11 @@ Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To u
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
## Notes
Thoughts and ideas:
- Validate the content of certain limit and don't render at all if content is too long, or at least cut the content and only render X length. Then allow users to manually retrieve
that exact event upon request.

View File

@ -20,20 +20,11 @@
"outputPath": "dist",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": [
"zone.js"
],
"polyfills": ["zone.js", "src/polyfills.ts"],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/404.html",
"src/assets",
"src/manifest.webmanifest"
],
"styles": [
"src/styles.scss"
],
"assets": ["src/favicon.ico", "src/404.html", "src/assets", "src/manifest.webmanifest"],
"styles": ["src/styles.scss"],
"scripts": [],
"serviceWorker": true,
"ngswConfigPath": "ngsw-config.json"
@ -86,20 +77,11 @@
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"polyfills": [
"zone.js",
"zone.js/testing"
],
"polyfills": ["zone.js", "zone.js/testing"],
"tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/assets",
"src/manifest.webmanifest"
],
"styles": [
"src/styles.scss"
],
"assets": ["src/favicon.ico", "src/assets", "src/manifest.webmanifest"],
"styles": ["src/styles.scss"],
"scripts": []
}
}

1498
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -26,7 +26,13 @@
"@fontsource/material-icons": "^4.5.4",
"@noble/secp256k1": "^1.7.0",
"@scure/base": "^1.1.1",
"buffer": "^6.0.3",
"crypto-browserify": "^3.12.0",
"moment": "^2.29.4",
"nostr-tools": "^1.0.0-beta",
"rxjs": "~7.5.0",
"sanitize-html": "^2.8.1",
"stream-browserify": "^3.0.0",
"tslib": "^2.3.0",
"zone.js": "~0.12.0"
},
@ -36,6 +42,7 @@
"@angular/compiler-cli": "^15.0.0",
"@blockcore/tsconfig": "0.0.1",
"@types/jasmine": "~4.3.0",
"@types/sanitize-html": "^2.8.0",
"jasmine-core": "~4.5.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.1.0",

View File

@ -37,9 +37,10 @@ import { ReactiveFormsModule } from '@angular/forms';
import { LayoutModule } from '@angular/cdk/layout';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatDialogModule } from '@angular/material/dialog';
import { AgoPipe } from './shared/ago.pipe';
@NgModule({
declarations: [AppComponent, ConnectComponent, LogoutComponent, HomeComponent],
declarations: [AppComponent, ConnectComponent, LogoutComponent, HomeComponent, AgoPipe],
imports: [
BrowserModule,
AppRoutingModule,

View File

@ -1 +1,11 @@
Notes!
<mat-list>
<div mat-subheader>Latest 100 Events</div>
<mat-list-item *ngFor="let event of events; trackBy: trackByFn">
<mat-icon matListItemIcon>text_snippet</mat-icon>
<!-- <div matListItemTitle>{{ event.pubkey }} - {{ event.kind }}-{{ event.id }}</div> -->
<div matListItemTitle>{{ event.content }}</div>
<div matListItemLine>{{ event.created_at | ago }}</div>
</mat-list-item>
</mat-list>
<!-- <div *ngFor="let event of events; trackBy: trackByFn">{{event.content}}</div> -->

View File

@ -2,13 +2,23 @@ import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { ApplicationState } from '../services/applicationstate.service';
import { Utilities } from '../services/utilities.service';
import { relayInit } from 'nostr-tools';
import * as moment from 'moment';
import { EventValidation } from '../services/eventvalidation.service';
import { NostrEvent } from '../services/interfaces';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
})
export class HomeComponent {
constructor(public appState: ApplicationState, private utilities: Utilities, private router: Router) {}
constructor(public appState: ApplicationState, private validator: EventValidation, private utilities: Utilities, private router: Router) {}
public trackByFn(index: number, item: NostrEvent) {
return item.id;
}
events: NostrEvent[] = [];
async ngOnInit() {
const publicKey = localStorage.getItem('blockcore:notes:nostr:pubkey');
@ -28,5 +38,54 @@ export class HomeComponent {
} else {
this.router.navigateByUrl('/connect');
}
// const relay = relayInit('wss://relay.nostr.info');
const relay = relayInit('wss://relay.damus.io');
relay.on('connect', () => {
console.log(`connected to ${relay.url}, ${relay.status}`);
});
relay.on('disconnect', () => {
console.log(`DISCONNECTED! ${relay.url}`);
});
relay.on('notice', () => {
console.log(`NOTICE FROM ${relay.url}`);
});
relay.connect();
const hourAgo = moment().subtract(1, 'hours').unix();
const fiveMinutesAgo = moment().subtract(5, 'minutes').unix();
//const sub = relay.sub([{ ids: ['d7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027'] }], { });
const sub = relay.sub([{ kinds: [1], since: fiveMinutesAgo }], {});
this.events = [];
sub.on('event', (event: any) => {
// Validate the event:
const valid = this.validator.validateEvent(event);
if (!valid) {
debugger;
console.log('INVALID EVENT!');
return;
}
const parsed = this.validator.sanitizeEvent(event);
// console.log('we got the event we wanted:', parsed);
this.events.unshift(parsed);
if (this.events.length > 100) {
this.events.length = 80;
}
});
// sub.on('eose', () => {
// sub.unsub();
// });
}
}

View File

@ -1 +1,5 @@
Notes!
Notes!
asd
fas
<dfn><aside>fd</aside></dfn>

View File

@ -1,7 +1,52 @@
import { Component } from '@angular/core';
// import { relayInit, validateEvent, verifySignature, signEvent, getEventHash, getPublicKey } from 'nostr-tools';
import { relayInit } from 'nostr-tools';
@Component({
selector: 'app-notes',
templateUrl: './notes.component.html',
})
export class NotesComponent {}
export class NotesComponent {
constructor() {}
ngOnInit() {
// const pool = relayPool();
// pool.addRelay('wss://relay.nostr.info', { read: true, write: true });
// pool.addRelay('wss://nostr.openchain.fr', { read: true, write: true });
// // pool.addRelay('wss://relay.damus.io', {read: true, write: true});
// pool.addRelay('wss://nostr-relay.wlvs.space', { read: true, write: true });
// pool.addRelay('wss://relay.nostr.ch', { read: true, write: true });
// pool.addRelay('wss://nostr.sandwich.farm', { read: true, write: true });
// console.log('DOES THIS HAPPEN?!?!');
// const relay = relayInit('wss://relay.nostr.info');
// relay.on('connect', () => {
// console.log(`connected to ${relay.url}`);
// });
// relay.on('disconnect', () => {
// console.log(`DISCONNECTED! ${relay.url}`);
// });
// relay.on('notice', () => {
// console.log(`NOTICE FROM ${relay.url}`);
// });
// relay.connect();
// // let's query for an event that exists
// let sub = relay.sub([
// {
// ids: ['d7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027'],
// },
// ]);
// sub.on('event', (event) => {
// console.log('we got the event we wanted:', event);
// });
// sub.on('eose', () => {
// sub.unsub();
// });
}
}

View File

@ -0,0 +1,63 @@
import { Injectable } from '@angular/core';
import { NostrEvent } from './interfaces';
import * as sanitizeHtml from 'sanitize-html';
@Injectable({
providedIn: 'root',
})
export class EventValidation {
contentLimit = 280;
tagsLimit = 10;
sanitizeEvent(event: NostrEvent) {
// Allow only a super restricted set of tags and attributes
const clean = sanitizeHtml(event.content, {
allowedTags: ['b', 'i', 'em', 'strong', 'a', 'img'],
allowedAttributes: {
a: ['href'],
img: ['src'], // Only allow src and nothing else on images.
},
allowedIframeHostnames: ['www.youtube.com'],
});
event.content = clean;
return event;
}
/** Returns true if valid, false if not valid. Does not throw error for optimization purposes. */
validateEvent(event: NostrEvent) {
if (event.pubkey.length < 60 || event.pubkey.length > 70) {
return null;
}
if (!event.sig || !event.id) {
return null;
}
if (event.sig.length < 100 || event.pubkey.length > 150) {
return null;
}
if (event.id.length !== 64) {
return null;
}
if (typeof event.kind !== 'number' || typeof event.created_at !== 'number') {
return null;
}
// Reduce the content length to reduce system resource usage and improve UI experience.
if (event.content.length > this.contentLimit) {
event.content = event.content.substring(0, this.contentLimit);
event.contentCut = true;
}
if (event.tags && event.tags.length > this.tagsLimit) {
event.tags = event.tags.splice(0, this.tagsLimit);
event.tagsCut = true;
}
return event;
}
}

View File

@ -0,0 +1,6 @@
import { Event } from 'nostr-tools';
export interface NostrEvent extends Event {
contentCut: boolean;
tagsCut: boolean;
}

View File

@ -0,0 +1,10 @@
import { Pipe, PipeTransform } from '@angular/core';
import * as moment from 'moment';
@Pipe({ name: 'ago' })
export class AgoPipe implements PipeTransform {
transform(value: number): string {
const date = moment.unix(value);
return date.fromNow();
}
}

9
src/polyfills.ts Normal file
View File

@ -0,0 +1,9 @@
import { Buffer } from 'buffer';
(window as any).global = window;
global.Buffer = Buffer;
global.process = {
env: { DEBUG: undefined },
version: '',
nextTick: require('next-tick'),
} as any;

View File

@ -6,7 +6,8 @@
"types": []
},
"files": [
"src/main.ts"
"src/main.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.d.ts"

View File

@ -1,8 +1,11 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "@blockcore/tsconfig",
"compileOnSave": false,
"compilerOptions": {
"paths": {
"crypto": ["./node_modules/crypto-browserify"],
"stream": ["./node_modules/stream-browserify"]
},
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"forceConsistentCasingInFileNames": true,