Browse Source

Merge pull request #30 from dskvr/feature/better-single

Feature/better single
update/libraries
Sandwich 2 years ago
committed by GitHub
parent
commit
149e2d8487
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      package.json
  2. 1
      src/App.vue
  3. 14
      src/components/RefreshComponent.vue
  4. 6
      src/components/RelaySingleComponent.vue
  5. 369
      src/pages/SingleRelay.vue
  6. 23
      src/shared.js

4
package.json

@ -1,6 +1,6 @@
{
"name": "nostr-watch",
"version": "0.0.15",
"version": "0.0.16",
"private": true,
"scripts": {
"prebuild": "node ./scripts/geo.js",
@ -31,7 +31,7 @@
"node-emoji": "1.11.0",
"node-polyfill-webpack-plugin": "2.0.1",
"nostr": "0.2.5",
"nostr-relay-inspector": "0.0.9",
"nostr-relay-inspector": "0.0.10",
"nostr-tools": "0.24.1",
"onion-regex": "2.0.8",
"requests": "0.3.0",

1
src/App.vue

@ -12,6 +12,7 @@ export default {
</script>
<style>
body { margin: 0 !important; }
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;

14
src/components/RefreshComponent.vue

@ -1,6 +1,6 @@
<template>
<section id="refresh">
<span>Updated {{ refreshData?.sinceLast }} ago <button @click="invalidate(true)">Update Now</button></span>
<span>Updated {{ refreshData?.sinceLast }} ago <button @click="invalidate(true, this.relay)">Refresh{{ relay ? ` ${relay}` : "" }}</button></span>
<span v-if="preferences.refresh"> Next refresh in: {{ refreshData?.untilNext }}</span>
</section>
</template>
@ -33,7 +33,7 @@ const localMethods = {
this.refreshData.sinceLast = this.timeSinceRefresh()
if(this.isExpired() && this.preferences.refresh)
this.invalidate()
this.invalidate(false, this.relay)
}, 1000)
}
@ -57,9 +57,7 @@ export default defineComponent({
updated(){
this.saveState('preferences')
if(this.isDone()) {
this.saveState('lastUpdate')
}
this.saveState('lastUpdate')
this.refreshData.untilNext = this.timeUntilRefresh()
this.refreshData.sinceLast = this.timeSinceRefresh()
@ -67,6 +65,12 @@ export default defineComponent({
computed: {},
methods: Object.assign(localMethods, sharedMethods),
props: {
relay: {
type: String,
default(){
return ""
}
},
relaysProp:{
type: Object,
default(){

6
src/components/RelaySingleComponent.vue

@ -43,10 +43,6 @@
</ul>
</td>
<!-- <td class="nip nip-11">
<a v-if="result?.info" @click="showModal=true"> </a>
</td> -->
<vue-final-modal v-model="showModal" classes="modal-container" content-class="modal-content">
<div class="modal__title">
<span>{{ result?.info?.name }}</span>
@ -83,7 +79,7 @@
<script>
import { defineComponent} from 'vue'
import { VueFinalModal } from 'vue-final-modal'
import InspectorRelayResult from 'nostr-relay-inspector'
import { InspectorRelayResult } from 'nostr-relay-inspector'
import SafeMail from "@2alheure/vue-safe-mail";
import { countryCodeEmoji } from 'country-code-emoji';
import emoji from 'node-emoji';

369
src/pages/SingleRelay.vue

@ -19,7 +19,6 @@
</column>
</row>
<row container :gutter="12">
<column :xs="12" :md="12" :lg="12" class="title-card">
<div style="display: none">{{result}}</div> <!-- ? -->
@ -30,80 +29,89 @@
<span><img :src="badgeCheck('read')" /></span>
<span><img :src="badgeCheck('write')" /></span>
</span>
</column>
</row>
<br />
<span v-if="result.info?.supported_nips" class="badges">
<span v-for="(nip) in result.info.supported_nips" :key="`${relay}_${nip}`">
<a :href="nipLink(nip)" target="_blank"><img :src="badgeLink(nip)" /></a>
</span>
</span>
<!--table>
<tr>
<th colspan="3"><h4>Status</h4></th>
</tr>
<tr v-if="result.checkClass">
<td :class="result.checkClass.connect" class="connect indicator">Connected</td>
<td :class="result.checkClass.read" class="read indicator">Read</td>
<td :class="result.checkClass.write" class="write indicator">Write</td>
</tr>
</table-->
<table v-if="result.info">
<tr>
<th colspan="2"><h4>Info</h4></th>
</tr>
<tr v-for="(value, key) in Object.entries(result.info).filter(value => value[0] != 'id' && value[0] != 'supported_nips')" :key="`${value}_${key}`">
<td>{{ value[0] }}</td>
<td v-if="value[0]!='contact' && value[0]!='pubkey' && value[0]!='software' && value[0]!='version'">{{ value[1] }} </td>
<td v-if="value[0]=='contact'"><SafeMail :email="value[1]" /></td>
<td v-if="value[0]=='pubkey' || value[0]=='version'"><code>{{ value[1] }}</code></td>
<td v-if="value[0]=='software'"><a href="{{ value[1] }}">{{ value[1] }}</a></td>
</tr>
</table>
<h4>Identities</h4>
<table v-if="result.identities">
<tr v-for="(value, key) in Object.entries(result?.identities)" :key="`${value}_${key}`">
<td>{{ value[0] }}</td>
<td><code>{{ value[1] }}</code></td>
</tr>
</table>
<br />
<div style="display: none">{{result}}</div> <!-- ? -->
<row container :gutter="12" v-if="!result?.check?.connect">
<column :xs="12" :md="12" :lg="12" class="title-card">
This relay appears to be offline.
</column>
</row>
<row container :gutter="12">
<column :xs="12" :md="6" :lg="6" class="title-card">
<br />
<h4>GEO {{geo?.countryCode ? getFlag() : ''}}</h4>
<table v-if="geo[relay]">
<tr v-for="(value, key) in Object.entries(geo[relay])" :key="`${value}_${key}`">
<td>{{ value[0] }}</td>
<td>{{ value[1] }} </td>
</tr>
</table>
<row container :gutter="12" v-if="result?.check?.connect">
<column :xs="12" :md="12" :lg="12" class="title-card">
</column>
<column :xs="12" :md="6" :lg="6" class="title-card">
<h4>DNS</h4>
<table v-if="geo[relay]">
<tr v-for="(value, key) in Object.entries(geo[relay].dns)" :key="`${value}_${key}`">
<td>{{ value[0] }}</td>
<td>{{ value[1] }} </td>
</tr>
</table>
<span v-if="result.info?.supported_nips" class="badges">
<span v-for="(nip) in result.info.supported_nips" :key="`${relay}_${nip}`">
<a :href="nipLink(nip)" target="_blank"><img :src="badgeLink(nip)" /></a>
</span>
</span>
<div style="display: none">{{result}}</div> <!-- ? -->
<table v-if="result.info">
<tr>
<th colspan="2"><h4>Info</h4></th>
</tr>
<tbody v-if="result.info">
<tr v-for="(value, key) in Object.entries(result.info).filter(value => value[0] != 'id' && value[0] != 'supported_nips')" :key="`${value}_${key}`">
<td>{{ value[0] }}</td>
<td v-if="value[0]!='contact' && value[0]!='pubkey' && value[0]!='software' && value[0]!='version'">{{ value[1] }} </td>
<td v-if="value[0]=='contact'"><SafeMail :email="value[1]" /></td>
<td v-if="value[0]=='pubkey' || value[0]=='version'"><code>{{ value[1] }}</code></td>
<td v-if="value[0]=='software'"><a href="{{ value[1] }}">{{ value[1] }}</a></td>
</tr>
</tbody>
<tr v-if="Object.entries(result.info).length == 0 && result.check.connect">
Relay does not have NIP-11 support, or the administrator has not configured the relay to return information.
</tr>
</table>
<h4>Identities</h4>
<table v-if="result.identities">
<tr v-for="(value, key) in Object.entries(result?.identities)" :key="`${value}_${key}`">
<td>{{ value[0] }}</td>
<td><code>{{ value[1] }}</code></td>
</tr>
<tr v-if="Object.entries(result.identities).length==0">
Relay does not provide NIP-05 support and has not registered an administrator key.
</tr>
</table>
<div style="display: none">{{result}}</div> <!-- ? -->
</column>
<column :xs="12" :md="6" :lg="6" class="title-card">
<h4>GEO {{geo?.countryCode ? getFlag() : ''}}</h4>
<table v-if="geo[relay]">
<tr v-for="(value, key) in Object.entries(geo[relay])" :key="`${value}_${key}`">
<td>{{ value[0] }}</td>
<td>{{ value[1] }} </td>
</tr>
</table>
</column>
<column :xs="12" :md="6" :lg="6" class="title-card">
<h4>DNS</h4>
<table v-if="geo[relay]">
<tr v-for="(value, key) in Object.entries(geo[relay].dns)" :key="`${value}_${key}`">
<td>{{ value[0] }}</td>
<td>{{ value[1] }} </td>
</tr>
</table>
</column>
<div style="display: none">{{result}}</div> <!-- ? -->
</column>
</row>
<RefreshComponent
:relay="relay"
/>
<span class="credit"><a href="http://sandwich.farm">Another 🥪 by sandwich.farm</a>, built with <a href="https://github.com/jb55/nostr-js">nostr-js</a> and <a href="https://github.com/dskvr/nostr-relay-inspector">nostr-relay-inspector</a>, inspired by <a href="https://github.com/fiatjaf/nostr-relay-registry">nostr-relay-registry</a></span>
</div>
@ -116,25 +124,43 @@ import { useStorage } from "vue3-storage";
import LeafletSingleComponent from '../components/LeafletSingleComponent.vue'
import NavComponent from '../components/NavComponent.vue'
import RefreshComponent from '../components/RefreshComponent.vue'
import { Row, Column } from 'vue-grid-responsive';
import SafeMail from "@2alheure/vue-safe-mail";
import emoji from 'node-emoji';
import { countryCodeEmoji } from 'country-code-emoji';
import { Inspector, InspectorObservation } from 'nostr-relay-inspector'
// import { Inspector, InspectorObservation } from '../../lib/nostr-relay-inspector'
// import { Inspector, InspectorObservation } from '../../lib/nostr-relay-inspector'
import sharedMethods from '../shared'
import { version } from '../../package.json'
import { relays } from '../../relays.yaml'
import { geo } from '../../geo.yaml'
import { messages as RELAY_MESSAGES, codes as RELAY_CODES } from '../../codes.yaml'
const localMethods = {
relayUrl() {
// We will see what `params` is shortly
return `wss://${this.$route.params.relayUrl}`
},
badgeLink(nip){
return `https://img.shields.io/static/v1?style=for-the-badge&label=NIP&message=${this.nipSignature(nip)}&color=black`
},
badgeCheck(which){
return `https://img.shields.io/static/v1?style=for-the-badge&label=&message=${which}&color=${this.result?.check?.[which] ? 'green' : 'red'}`
},
import crypto from "crypto"
nipSignature(key){
return key.toString().length == 1 ? `0${key}` : key
},
nipFormatted(key){
return `NIP-${this.nipSignature(key)}`
},
nipLink(key){
return `https://github.com/nostr-protocol/nips/blob/master/${this.nipSignature(key)}.md`
},
}
export default defineComponent({
title: "nostr.watch registry & network status",
@ -145,6 +171,7 @@ export default defineComponent({
LeafletSingleComponent,
NavComponent,
SafeMail,
RefreshComponent,
},
data() {
@ -173,215 +200,21 @@ export default defineComponent({
this.storage = useStorage()
this.lastUpdate = this.storage.getStorageSync('lastUpdate')
this.preferences = this.storage.getStorageSync('preferences')
this.result = this.storage.getStorageSync(this.relay)
if(this.isExpired())
this.check(this.relay)
},
computed: {
},
computed: {},
updated() {
Object.keys(this.timeouts).forEach(timeout => clearTimeout(this.timeouts[timeout]))
Object.keys(this.intervals).forEach(interval => clearInterval(this.intervals[interval]))
},
methods: {
isExpired(){
return typeof this.lastUpdate === 'undefined' || Date.now() - this.lastUpdate > this.preferences.cacheExpiration
},
saveState(relay){
this.storage
.setStorage({
key: relay,
data: this.result
})
.then(successCallback => {
console.log(successCallback.errMsg);
})
.catch(failCallback => {
console.log(failCallback.errMsg);
})
this.storage
.setStorage({
key: "lastUpdate",
data: Date.now()
})
.then(successCallback => {
console.log(successCallback.errMsg);
this.lastUpdate = Date.now()
})
.catch(failCallback => {
console.log(failCallback.errMsg);
})
},
relayUrl() {
// We will see what `params` is shortly
return `wss://${this.$route.params.relayUrl}`
},
async check(relay){
//const self = this
/* return new Promise(function(resolve, reject) { */
/* let nip = new Array(99).fill(false);
nip[5] = true
nip[11] = true */
const opts = {
checkLatency: true,
checkNips: true,
/* checkNip: nip, */
/* debug: true */
}
let inspect = new Inspector(relay, opts)
.on('run', (result) => {
result.aggregate = 'processing'
})
.on('open', (e, result) => {
this.result = result
this.result.checkClass = {read: null, write: null, connect: null}
this.setResultClass('connect')
})
.on('complete', (instance) => {
this.result = instance.result
this.messages[this.relay] = instance.inbox
/* this.setFlag(relay) */
this.setAggregateResult()
/* this.adjustResult(relay) */
this.setResultClass('read')
this.setResultClass('write')
this.saveState(relay)
})
.on('notice', (notice) => {
const hash = this.sha1(notice)
let message_obj = RELAY_MESSAGES[hash]
let code_obj = RELAY_CODES[message_obj.code]
let response_obj = {...message_obj, ...code_obj}
this.result.observations.push( new InspectorObservation('notice', response_obj.code, response_obj.description, response_obj.relates_to) )
})
.on('close', (msg) => {
console.warn("CAUTION", msg)
})
.on('error', (err) => {
console.error("ERROR", err)
})
.run()
return inspect;
/* }) */
},
setResultClass (key) {
let result = this.result?.check?.[key] === true
? 'success'
: this.result?.check?.[key] === false
? 'failure'
: 'pending'
this.result.checkClass[key] = result
},
getLoadingClass () {
return this.result?.state == 'complete' ? "relay loaded" : "relay"
},
generateKey (url, key) {
return `${url}_${key}`
},
getFlag () {
return this.geo?.countryCode ? countryCodeEmoji(this.geo.countryCode) : emoji.get('shrug');
},
setCheck (bool) {
return bool ? '✅ ' : ''
},
badgeLink(nip){
return `https://img.shields.io/static/v1?style=for-the-badge&label=NIP&message=${this.nipSignature(nip)}&color=black`
},
badgeCheck(which){
return `https://img.shields.io/static/v1?style=for-the-badge&label=&message=${which}&color=${this.result?.check?.[which] ? 'green' : 'red'}`
},
setCross (bool) {
return !bool ? '❌' : ''
},
setCaution (bool) {
return !bool ? '⚠️' : ''
},
identityList () {
let string = '',
extraString = '',
users = Object.entries(this.result.identities),
count = 0
if(this.result.identities) {
if(this.result.identities.serverAdmin) {
string = `Relay has registered an administrator pubkey: ${this.result.identities.serverAdmin}. `
extraString = "Additionally, "
}
const total = users.filter(([key]) => key!='serverAdmin').length,
isOne = total==1
if(total) {
string = `${string}${extraString}Relay domain contains NIP-05 verification data for:`
users.forEach( ([key]) => {
if(key == "serverAdmin") return
count++
string = `${string} ${(count==total && !isOne) ? 'and' : ''} @${key}${(count!=total && !isOne) ? ', ' : ''}`
})
}
}
return string
},
nipSignature(key){
return key.toString().length == 1 ? `0${key}` : key
},
nipFormatted(key){
return `NIP-${this.nipSignature(key)}`
},
nipLink(key){
return `https://github.com/nostr-protocol/nips/blob/master/${this.nipSignature(key)}.md`
},
async copy(text) {
try {
await navigator.clipboard.writeText(text);
} catch(err) {
console.error(err)
}
},
setAggregateResult () {
if(!this.result) return
let aggregateTally = 0
aggregateTally += this.result?.check.connect ? 1 : 0
aggregateTally += this.result?.check.read ? 1 : 0
aggregateTally += this.result?.check.write ? 1 : 0
if (aggregateTally == 3) {
this.result.aggregate = 'public'
}
else if (aggregateTally == 0) {
this.result.aggregate = 'offline'
}
else {
this.result.aggregate = 'restricted'
}
},
sha1 (message) {
const hash = crypto.createHash('sha1').update(JSON.stringify(message)).digest('hex')
return hash
},
},
methods: Object.assign(localMethods, sharedMethods),
})
</script>

23
src/shared.js

@ -4,19 +4,22 @@ import { messages as RELAY_MESSAGES, codes as RELAY_CODES } from '../codes.yaml'
import crypto from "crypto"
export default {
invalidate: function(force){
invalidate: async function(force, single){
if(!this.isExpired() && !force)
return
this.relays.forEach(async relay => {
await this.check(relay)
this.relays[relay] = this.getState(relay)
this.messages[relay] = this.getState(`${relay}_inbox`)
})
// if(this.preferences.refresh)
// this.timeouts.invalidate = setTimeout(()=> this.invalidate(), 1000)
if(single) {
await this.check(single)
this.relays[single] = this.getState(single)
this.messages[single] = this.getState(`${single}_inbox`)
}
else {
this.relays.forEach(async relay => {
await this.check(relay)
this.relays[relay] = this.getState(relay)
this.messages[relay] = this.getState(`${relay}_inbox`)
})
}
},
isExpired: function(){

Loading…
Cancel
Save