Browse Source

Working refactor

update/libraries
dskvr 2 years ago
parent
commit
7157301c54
  1. 11
      Dockerfile
  2. 31
      README.md
  3. 5
      babel.config.js
  4. 29
      build.js
  5. 41
      codes.yaml
  6. 9
      docker-compose.yaml
  7. 19
      jsconfig.json
  8. 125
      main.js
  9. 4
      nginx/conf.d/default.conf
  10. 16
      onion.js
  11. 77
      package.json
  12. 8
      relays.yaml
  13. 24
      src/App.vue
  14. BIN
      src/assets/logo.png
  15. 603
      src/components/BaseRelays.vue
  16. 0
      src/components/NavComponent.vue
  17. 39
      src/components/RelayListComponent.vue
  18. 38
      src/components/RelaySingleComponent.vue
  19. 0
      src/index.js
  20. 4
      src/main.js
  21. 23
      src/router/index.js
  22. 9
      src/router/routes.js
  23. 0
      src/utils/test-connection-clearnet.js
  24. 0
      src/utils/test-connection-onion.js
  25. 25
      vue.config.js
  26. 6179
      yarn-error.log

11
Dockerfile

@ -2,15 +2,18 @@ FROM node:14-alpine as build
WORKDIR /app
COPY public/index.html /app/public/index.html
COPY main.js package.json build.js /app/
COPY . /app/
RUN yarn \
&& yarn run build
&& yarn build
RUN yarn global add yaml2json
RUN yaml2json relays.yaml > dist/relays.json
FROM nginx:stable-alpine as nginx-nostr-relay-registry
COPY ./nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/public /app
COPY --from=build /app/dist /app

31
README.md

@ -1,23 +1,24 @@
# nostr-relay-registry
A dynamic registry of nostr relays that tests for very basic tasks in real-time.
## Docker
Build the docker image:
```bash
docker build -t nostr-relay-registry .
## Project setup
```
yarn install
```
Run the container interactively:
```bash
docker run --rm -it --name=nostr-relay-registry -p 8080:80 nostr-relay-registry
### Compiles and hot-reloads for development
```
yarn serve
```
Run container in the background:
### Compiles and minifies for production
```
yarn build
```
```bash
docker run -d --restart unless-stopped --name nostr-relay-registry -p 8080:80 nostr-relay-registry
### Lints and fixes files
```
yarn lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

5
babel.config.js

@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

29
build.js

@ -1,29 +0,0 @@
#!/usr/bin/env node
const esbuild = require('esbuild')
const alias = require('esbuild-plugin-alias')
const nodeGlobals = require('@esbuild-plugins/node-globals-polyfill').default
const vuePlugin = require("esbuild-vue");
const prod = process.argv.indexOf('prod') !== -1
esbuild
.build({
bundle: true,
entryPoints: ['main.js'],
outdir: 'public',
plugins: [
alias({
stream: require.resolve('readable-stream')
}),
nodeGlobals({buffer: true}),
vuePlugin(),
],
minify: true,
sourcemap: prod ? false : 'inline',
define: {
window: 'self',
global: 'self'
}
})
.then(() => console.log('build success.'))

41
codes.yaml

@ -0,0 +1,41 @@
messages:
11cb5ca38038c3eb41bd014814f6e2e18da18ff1:
text: "we don't accept any events"
code: "READ_ONLY"
1003d4ec1466033d0dcc4a1babc6c5f409784593:
text: "NIP-05 verification needed to publish events"
code: "NIP_05_REQUIRED"
2e36f6955db854ac51105aa198fdf37cec694135:
text: "[ERROR]: Pubkey is not whitelisted."
code: "WHITELIST_REQUIRED"
5d6c9cb06d52c3f0456cc08fdf883dbe19b3c782:
text: "failed to save event from 5a462fa6044b4b8da318528a6987a45e3adf832bd1c64bd6910eacfecdf07541"
code: "BLOCKS_WRITE_STATUS_CHECK"
2c791b26eda3205558c235973ca84cc68a80e5e0:
text: "pubkey is not allowed to publish to this relay"
code: "BLOCKS_WRITE_STATUS_CHECK"
203442bfdf9f7f1562f1164b6dc79fefb42790ac:
text: 'failed to save event 41ce9bc50da77dda5542f020370ecc2b056d8f2be93c1cedf1bf57efcab095b0: pq: duplicate key value violates unique constraint "ididx"'
code: "BLOCKS_WRITE_STATUS_CHECK"
af155223f0e51dea64560c4ef6dc341a2775af76:
text: 'failed to save event from 5a462fa6044b4b8da318528a6987a45e3adf832bd1c64bd6910eacfecdf07541'
code: 'BLOCKS_WRITE_STATUS_CHECK'
codes:
READ_ONLY:
type: "write_restricted"
description: "This relay only access read queries"
WRITE_ONLY:
type: "write_restricted"
description: "This relay only accepts the publishing of events"
NIP_05_REQUIRED:
type: "write_restricted"
description: "This relay only accepts the publishing of events from NIP-05 verified public keys"
WHITELIST_REQUIRED:
type: "write_restricted"
description: "This relay onoly accepts the publishing of events from whitelisted public keys"
BLOCKS_WRITE_STATUS_CHECK:
type: "maybe_public"
description: "This relay blocks the events that enable us to test writing to the relay, so there's some uncertainty"

9
docker-compose.yaml

@ -0,0 +1,9 @@
services:
nostr-relay-registry:
ports:
- '80:80'
restart: always
logging:
options:
max-size: 1g
image: nrr

19
jsconfig.json

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}

125
main.js

@ -1,125 +0,0 @@
import {createApp, h} from 'vue'
import {relayConnect} from 'nostr-tools/relay'
import yaml from 'js-yaml'
import fs from 'fs'
const App = {
data() {
return {
relays: yaml.load(fs.readFileSync('relays.yml', 'utf8')).relays,
status: {}
}
},
mounted() {
this.connections = {}
this.relays.forEach(async url => {
this.status[url] = {}
try {
let conn = relayConnect(
url,
() => {},
() => {
this.status[url].didConnect = false
}
)
this.status[url].didConnect = true
try {
await conn.publish({
id: '41ce9bc50da77dda5542f020370ecc2b056d8f2be93c1cedf1bf57efcab095b0',
pubkey:
'5a462fa6044b4b8da318528a6987a45e3adf832bd1c64bd6910eacfecdf07541',
created_at: 1640305962,
kind: 1,
tags: [],
content: 'running branle',
sig: '08e6303565e9282f32bed41eee4136f45418f366c0ec489ef4f90d13de1b3b9fb45e14c74f926441f8155236fb2f6fef5b48a5c52b19298a0585a2c06afe39ed'
})
this.status[url].didPublish = true
} catch (err) {
this.status[url].didPublish = false
}
let {unsub} = conn.sub(
{
cb: () => {
this.status[url].didQuery = true
unsub()
clearTimeout(willUnsub)
},
filter: {
ids: [
'41ce9bc50da77dda5542f020370ecc2b056d8f2be93c1cedf1bf57efcab095b0'
]
}
},
'nostr-registry'
)
let willUnsub = setTimeout(() => {
unsub()
this.status[url].didQuery = false
}, 3000)
} catch (err) {
this.status[url].didConnect = false
}
})
},
// render() {
// return h('table', {style: {fontSize: '28px'}}, [
// h('tr', [h('th'), h('th', 'connect'), h('th', 'read'), h('th', 'write')]),
// ...this.relays.map(url =>
// h('tr', [
// h(
// 'th',
// {
// style: {
// textAlign: 'right',
// whiteSpace: 'pre-wrap',
// wordWrap: 'break-word',
// wordBreak: 'break-all'
// }
// },
// url
// ),
// ...['didConnect', 'didQuery', 'didPublish'].map(attr =>
// h(
// 'td',
// {
// style: {
// fontSize: '50px',
// textAlign: 'center',
// padding: '5px 35px',
// color:
// this.status?.[url]?.[attr] === true
// ? 'green'
// : this.status?.[url]?.[attr] === false
// ? 'red'
// : 'silver'
// }
// },
// '•'
// )
// )
// ])
// ),
// h('tr', [
// h('th', {style: {textAlign: 'right'}}, 'Your relay here:'),
// h('th', {colSpan: 3}, [
// h(
// 'a',
// {
// href: 'https://github.com/fiatjaf/nostr-relay-registry',
// style: {color: 'black'}
// },
// '________________'
// )
// ])
// ])
// ])
// }
}
createApp(App).mount('#app')

4
nginx/conf.d/default.conf

@ -2,6 +2,10 @@ server {
listen 80;
root /app;
location /relays/ {
rewrite ^ relays.json break;
}
location / {
index index.html;
}

16
onion.js

@ -0,0 +1,16 @@
const url = require('url');
const http = require('http');
const https = require('https');
const SocksProxyAgent = require('socks-proxy-agent');
// Use the SOCKS_PROXY env var if using a custom bind address or port for your TOR proxy:
const proxy = process.env.SOCKS_PROXY || 'socks5h://127.0.0.1:9050';
console.log('Using proxy server %j', proxy);
// The default HTTP endpoint here is DuckDuckGo's v3 onion address:
const endpoint = process.argv[2] || 'https://duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion';
console.log('Attempting to GET %j', endpoint);
// Prepare options for the http/s module by parsing the endpoint URL:
let options = url.parse(endpoint);
const agent = new SocksProxyAgent(proxy);
// Here we pass the socks proxy agent to the http/s module:
options.agent = agent;

77
package.json

@ -1,20 +1,69 @@
{
"name": "nostr-relay-registry",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve --host localhost",
"build": "vue-cli-service build",
"watch": "vue-cli-service build --watch",
"lint": "vue-cli-service lint"
},
"dependencies": {
"@esbuild-plugins/node-globals-polyfill": "^0.1.1",
"buffer": "^6.0.3",
"esbuild": "^0.14.21",
"esbuild-plugin-alias": "^0.2.1",
"events": "^3.3.0",
"nostr-tools": "^0.22.1",
"readable-stream": "^3.6.0",
"vue": "3"
"core-js": "^3.8.3",
"country-code-emoji": "2.3.0",
"doh-resolver": "1.2.8",
"geoip-lite": "1.4.6",
"global": "4.4.0",
"ip-fetch": "1.0.10",
"js-yaml": "4.1.0",
"json-loader": "^0.5.7",
"json-server": "0.17.1",
"leaflet": "1.9.3",
"node-emoji": "1.11.0",
"node-polyfill-webpack-plugin": "2.0.1",
"nostr": "https://github.com/dskvr/nostr-js",
"nostr-tools": "0.24.1",
"onion-regex": "2.0.8",
"requests": "0.3.0",
"socks-proxy-agent": "7.0.0",
"stream-browserify": "3.0.0",
"vue": "^3.2.13",
"vue-grid-responsive": "1.3.0",
"vue-nav-tabs": "0.5.7",
"vue-simple-maps": "1.1.3",
"vue3-popper": "1.5.0",
"yaml-loader": "^0.6.0",
"yaml2json": "1.0.2"
},
"devDependencies": {
"esbuild-vue": "1.2.2",
"json-server": "0.17.1"
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3",
"vue-cli-plugin-yaml-loader": "~1.0.0",
"webpack-cli": "5.0.0"
},
"scripts": {
"build": "./build.js prod",
"watch": "ag -l --js | entr ./build.js"
}
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/vue3-essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "@babel/eslint-parser"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead",
"not ie 11"
]
}

8
relays.yml → relays.yaml

@ -1,7 +1,4 @@
relays:
- 'wss://nostr-pub.wellorder.net'
- 'wss://relayer.fiatjaf.com'
- 'wss://nostr.rocks'
- 'wss://rsslay.fiatjaf.com'
- 'wss://freedom-relay.herokuapp.com/ws'
- 'wss://nostr-relay.freeberty.net'
@ -11,7 +8,6 @@ relays:
- 'wss://nostr-relay.untethr.me'
- 'wss://nostr.semisol.dev'
- 'wss://nostr-pub.semisol.dev'
- 'ws://jgqaglhautb4k6e6i2g34jakxiemqp6z4wynlirltuukgkft2xuglmqd.onion'
- 'wss://nostr-verified.wellorder.net'
- 'wss://nostr.drss.io'
- 'wss://nostr.unknown.place'
@ -25,3 +21,7 @@ relays:
- 'wss://nostr.ono.re'
- 'wss://relay.grunch.dev'
- 'wss://relay.cynsar.foundation'
- 'wss://nostr-pub.wellorder.net'
- 'wss://relayer.fiatjaf.com'
- 'wss://nostr.rocks'
- 'wss://nostr.sandwich.farm'

24
src/App.vue

@ -1,11 +1,25 @@
<template>
<router-view />
<BaseRelays />
</template>
<script>
import { defineComponent } from 'vue'
import BaseRelays from './components/BaseRelays.vue'
export default defineComponent({
name: 'App'
})
export default {
name: 'App',
components: {
BaseRelays,
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

BIN
src/assets/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

603
src/components/BaseRelays.vue

@ -0,0 +1,603 @@
<template>
<div>
<div class="text-h5 text-bold q-py-md q-px-sm full-width flex row justify-start">
<h1>Nostr Relay Registry</h1>
<span>Next ping in {{ nextPing }} seconds</span> |
<span v-if="relays.filter((url) => status[url] && !status[url].complete).length > 0">Processing {{relays.filter((url) => status[url].complete).length}}/{{relays.length}}</span>
</div>
<row container :gutter="12">
<column :xs="12" :md="12" :lg="6">
<div>
<h2><span class="indicator badge readwrite">{{ query('public').length }}</span>Public</h2>
<table class="online" v-if="query('public').length > 0">
<tr>
<th></th>
<th></th>
<th>🔌</th>
<th>👁🗨</th>
<th></th>
<th>🌎</th>
<!-- <td>wl</td>
<td>nip-05><td> -->
<th></th>
<th>ℹ️</th>
</tr>
<tr v-for="relay in query('public')" :key="{relay}" :class="getLoadingClass(relay)">
<td :key="generateKey(relay, 'aggregate')"><span :class="getAggregateStatusClass(relay)"></span></td>
<td class="left-align relay-url" @click="copy(relay)">{{ relay }}</td>
<td :key="generateKey(relay, 'didConnect')"><span :class="getStatusClass(relay, 'didConnect')"></span></td>
<td :key="generateKey(relay, 'didRead')"><span :class="getStatusClass(relay, 'didRead')"></span></td>
<td :key="generateKey(relay, 'didWrite')"><span :class="getStatusClass(relay, 'didWrite')"></span></td>
<td>{{status[relay].flag}}</td>
<td><span v-if="status[relay].didConnect">{{ status[relay].latency }}<span v-if="status[relay].latency">ms</span></span></td>
<td>
<Popper v-if="Object.keys(status[relay].messages).length">
{{ status[relay].type }}
<button @mouseover="showPopper">log</button>
<template #content>
<ul>
<li v-for="(message, key) in status[relay].messages" :key="generateKey(relay, key)">{{key}}</li>
</ul>
</template>
</Popper>
</td>
</tr>
</table>
</div>
</column>
<column :xs="12" :md="12" :lg="6">
<div>
<h2><span class="indicator badge write-only">{{ query('restricted').length }}</span>Restricted</h2>
<table class="online">
<tr>
<th></th>
<th></th>
<th>🔌</th>
<th>👁🗨</th>
<th></th>
<th>🌎</th>
<th></th>
<th>ℹ️</th>
</tr>
<tr v-for="relay in query('restricted')" :key="{relay}" :class="getLoadingClass(relay)">
<td :key="generateKey(relay, 'aggregate')"><span :class="getAggregateStatusClass(relay)"><span></span><span></span></span></td>
<td class="left-align relay-url" @click="copy(relay)">{{ relay }}</td>
<td :key="generateKey(relay, 'didConnect')"><span :class="getStatusClass(relay, 'didConnect')"></span></td>
<td :key="generateKey(relay, 'didRead')"><span :class="getStatusClass(relay, 'didRead')"></span></td>
<td :key="generateKey(relay, 'didWrite')"><span :class="getStatusClass(relay, 'didWrite')"></span></td>
<td>{{status[relay].flag}}</td>
<td><span v-if="status[relay].didConnect">{{ status[relay].latency }}<span v-if="status[relay].latency">ms</span></span></td>
<td>
<Popper v-if="Object.keys(status[relay].messages).length">
<button @mouseover="showPopper">log</button>
<template #content>
<ul>
<li v-for="(message, key) in status[relay].messages" :key="generateKey(relay, key)">{{key}}</li>
</ul>
</template>
</Popper>
</td>
</tr>
</table>
<h2><span class="indicator badge offline">{{ query('offline').length }}</span>Offline</h2>
<table v-if="query('offline').length > 0">
<tr>
<th></th>
<th></th>
<th>🔌</th>
<th>👁🗨</th>
<th></th>
<th>msg</th>
</tr>
<tr v-for="relay in query('offline')" :key="{relay}" :class="getLoadingClass(relay)">
<td :key="generateKey(relay, 'aggregate')"><span :class="getAggregateStatusClass(relay)"></span></td>
<td class="left-align relay-url">{{ relay }}</td>
<td :key="generateKey(relay, 'didConnect')"><span :class="getStatusClass(relay, 'didConnect')"></span></td>
<td :key="generateKey(relay, 'didRead')"><span :class="getStatusClass(relay, 'didRead')"></span></td>
<td :key="generateKey(relay, 'didWrite')"><span :class="getStatusClass(relay, 'didWrite')"></span></td>
<td>
<Popper v-if="Object.keys(status[relay].messages).length">
<button>log</button>
<template #content>
<ul>
<li v-for="(message, key) in status[relay].messages" :key="generateKey(relay, key)">{{key}}</li>
</ul>
</template>
</Popper>
</td>
</tr>
</table>
</div>
</column>
</row>
<!-- <h2>Processing</h2>
<table v-if="relays.filter((url) => !status[url].complete).length > 0">
<tr>
<th></th>
</tr>
<tr v-for="relay in relays.filter((url) => !status[url].complete)" :key="{relay}" :class="getLoadingClass(relay)">
<td>{{ relay }}</td>
</tr>
</table>
<a href="./relays/">JSON API</a> -->
</div>
</template>
<script>
import { defineComponent} from 'vue'
import { relayConnect } from 'nostr-tools/relay'
import { Row, Column } from 'vue-grid-responsive';
import Popper from "vue3-popper";
import onionRegex from 'onion-regex';
import countryCodeEmoji from 'country-code-emoji'
import emoji from 'node-emoji'
import { relays } from '../../relays.yaml'
import { messages as RELAY_MESSAGES, codes as RELAY_CODES } from '../../codes.yaml'
import crypto from "crypto"
const refreshMillis = 3*60*1000
export default defineComponent({
name: 'BaseRelays',
components: {
Row,
Column,
Popper
},
data() {
return {
relays,
status: {},
lastPing: Date.now(),
nextPing: Date.now() + (60*1000),
connections: {},
latency: {},
}
},
methods: {
// query (group, filterType) {
query (group) {
let unordered,
filterFn
// if(filterByType) {
// filterFn = (relay) => this.status?.[relay]?.aggregate == group || this.status?.[relay]?.[filterType];
// } else {
filterFn = (relay) => this.status?.[relay]?.aggregate == group
// }
unordered = this.relays.filter(filterFn);
if (unordered.length) {
return unordered.sort((relay1, relay2) => {
return this.status?.[relay1]?.latency - this.status?.[relay2]?.latency
})
}
return []
},
getAggregateStatusClass (url) {
let status = ''
if (this.status?.[url]?.aggregate == null) {
status = 'unprocessed'
}
else if (this.status?.[url]?.aggregate == 'public') {
status = 'readwrite'
}
else if (this.status?.[url]?.aggregate == 'restricted') {
if(this.status?.[url]?.didWrite) {
status = 'write-only'
} else {
status = 'read-only'
}
}
else if (this.status?.[url]?.aggregate == 'offline') {
status = 'offline'
}
return `aggregate indicator ${status}`
},
getStatusClass (url, key) {
let status = this.status?.[url]?.[key] === true
? 'green'
: this.status?.[url]?.[key] === false
? 'red'
: 'silver'
return `indicator ${status}`
},
getLoadingClass (url) {
return this.status?.[url]?.complete ? "relay loaded" : "relay"
},
setAggregateStatus (url) {
let aggregateTally = 0
aggregateTally += this.status?.[url]?.didConnect ? 1 : 0
aggregateTally += this.status?.[url]?.didRead ? 1 : 0
aggregateTally += this.status?.[url]?.didWrite ? 1 : 0
if (aggregateTally == 3) {
this.status[url].aggregate = 'public'
}
else if (aggregateTally == 0) {
this.status[url].aggregate = 'offline'
}
else {
this.status[url].aggregate = 'restricted'
}
},
async copy(text) {
try {
await navigator.clipboard.writeText(text);
} catch($e) {
console.log('Cannot copy');
}
},
setComplete (url) {
this.setAggregateStatus(url)
this.status[url].complete = true
},
generateKey (url, key) {
return `${url}_${key}`
},
testConnect (url) {
console.log(url, "CONNECT", "TEST")
this.connections[url] = relayConnect(
url,
// () => {},
(message) => {
console.log(url, "CONNECT", "SUCCESS")
const hash = this.sha1(message)
let message_obj = RELAY_MESSAGES[hash]
let code_obj = RELAY_CODES[message_obj.code]
console.log(hash)
console.dir(message_obj)
console.dir(code_obj)
message_obj.type = code_obj.type
this.status[url].messages[message] = message_obj
this.adjustStatus(url, hash)
// console.log("RECIEVED MESSAGE!")
// console.dir(this.status[url].messages)
},
() => {
console.log(url, "CONNECT", "FAILURE")
this.status[url].didConnect = false
this.status[url].didRead = false
this.status[url].didWrite = false
this.setComplete(url)
}
)
this.status[url].didConnect = true
},
async testRead (url) {
console.dir(this.connections[url])
// console.log(this.connections[url]['get status']())
console.log(url, "READ", "TEST")
let start
start = Date.now();
let {unsub} = await this.connections[url].sub(
{
cb: () => {
console.log(url, "READ", "SUCCESS")
this.status[url].didRead = true
this.setComplete(url)
this.latency[url].read = Date.now() - start;
unsub()
clearTimeout(willUnsub)
},
filter: {
ids: [
'41ce9bc50da77dda5542f020370ecc2b056d8f2be93c1cedf1bf57efcab095b0'
]
}
},
'nostr-registry'
)
let willUnsub = setTimeout(() => {
unsub()
console.log(url, "READ", "FAILURE")
if(!this.status[url].maybe_public) this.status[url].didRead = false
this.setComplete(url)
}, 10000)
},
async testWrite (url) {
console.log(url, "WRITE", "TEST")
let start
start = Date.now();
await this.connections[url].publish({
id: '41ce9bc50da77dda5542f020370ecc2b056d8f2be93c1cedf1bf57efcab095b0',
pubkey:
'5a462fa6044b4b8da318528a6987a45e3adf832bd1c64bd6910eacfecdf07541',
created_at: 1640305962,
kind: 1,
tags: [],
content: 'running branle',
sig: '08e6303565e9282f32bed41eee4136f45418f366c0ec489ef4f90d13de1b3b9fb45e14c74f926441f8155236fb2f6fef5b48a5c52b19298a0585a2c06afe39ed'
})
this.latency[url].write = Date.now() - start;
},
async testRelay (url) {
this.lastPing = Date.now()
this.latency[url] = {}
this.status[url].messages = {}
try {
//Test Connect
this.testConnect(url)
//Test Write
try {
await this.testWrite(url)
console.log(url, "WRITE", "SUCCESS")
this.status[url].didWrite = true
} catch (err) {
console.log(url, "WRITE", "FAILURE")
this.status[url].didWrite = false
this.setComplete(url)
}
//Test Read
this.testRead(url)
} catch (err) {
this.status[url].didConnect = false
this.setComplete(url)
}
if(this.status[url].didRead){
this.setLatency(url)
}
await this.getIP(url)
await this.setGeo(url)
this.setFlag(url)
},
isOnion(url){
return onionRegex().test(url)
},
setLatency(url) {
this.status[url].latency = this.latency[url].read
},
testRelayLatency(){
console.log('testing latency')
this.relays.forEach(url => {
// this.testWrite(url, true)
this.testRead(url, true)
this.setLatency(url)
})
this.lastPing = Date.now()
},
async getIP(url){
let ip
await fetch(`https://1.1.1.1/dns-query?name=${url.replace('wss://', '')}`, { headers: { 'accept': 'application/dns-json' } })
.then(response => response.json())
.then((data) => { ip = data.Answer ? data.Answer[data.Answer.length-1].data : false });
this.status[url].ip = ip
console.log('IP:', ip)
},
async setGeo(url){
if (!this.status[url].ip) return
await fetch(`http://ip-api.com/json/${this.status[url].ip}`, { headers: { 'accept': 'application/dns-json' } })
.then(response => response.json())
.then((data) => { this.status[url].geo = data });
console.dir(this.status[url].geo)
},
setFlag (url) {
this.status[url].flag = this.status[url].geo?.countryCode ? countryCodeEmoji(this.status[url].geo.countryCode) : emoji.get('shrug');
},
adjustStatus (url, hash) {
let code = RELAY_MESSAGES[hash].code,
type = RELAY_CODES[code].type
this.status[url][type] = code
if (type == "maybe_public") {
this.status[url].didWrite = true
this.status[url].didRead = true
}
if (type == "write_restricted") {
this.status[url].didWrite = false
}
},
sha1 (message) {
const hash = crypto.createHash('sha1').update(JSON.stringify(message)).digest('hex')
// console.log(message, ':', hash)
return hash
},
},
mounted() {
this.relays.forEach(async url => {
this.status[url] = {} //statusInterface
if (this.isOnion(url)) {
url = `${url}.to` //add proxy
}
await this.testRelay(url)
})
// eslint-disable-next-line
let latencyTimeout = setTimeout(() => { this.testRelayLatency() }, 10000)
// eslint-disable-next-line
let latencyIntVal = setInterval(() => { this.testRelayLatency() }, refreshMillis)
// eslint-disable-next-line
let counterIntVal = setInterval(() => {
this.nextPing = Math.round((this.lastPing + refreshMillis - Date.now())/1000)
}, 1000)
},
})
</script>
<style lang='css' scoped>
.q-tabs {
border-bottom: 1px solid var(--q-accent)
}
table {
width:100%;
}
.left-align {
text-align:left;
}
tr.relay td {
font-style: italic;
opacity: 0.5;
}
tr.relay.loaded td {
font-style: normal;
opacity: 1;
}
.indicator {
display:block;
margin: 0 auto;
height: 14px;
width: 14px;
border-radius: 7px;
border-width:0px;
}
.badge {
height:auto;
width: auto;
display:inline-block;
padding: 2px 5px;
font-size: 15px;
position: relative;
top: -3px;
min-width: 15px;
margin-right:5px;
}
.badge.readwrite,
.badge.offline {
color: white;
}
.badge.write-only,
.badge.read-only {
background-color:orange !important;
}
.aggregate.indicator {
background-color: transparent;
border-radius: 0px;
border-style: solid;
}
.indicator.silver {
background-color: #c0c0c0;
border-color: rgba(55,55,55,0.5);
}
.indicator.green {
background-color: green;
border-color: rgba(0,255,0,0.5);
}
.indicator.red {
background-color: red;
border-color: rgba(255,0,0,0.5);
}
.indicator.orange {
background-color: orange;
border-color: rgba(255, 191, 0,0.5);
}
.indicator.readwrite {
background-color: green;
border-color: rgba(0,255,0,0.5);
}
.indicator.read-only {
position:relative;
border-color: transparent;
background-color: transparent
}
.indicator.read-only span:first-child {
position:absolute;
width: 0;
height: 0;
border-top: 14px solid green;
border-right: 14px solid transparent;
}
.indicator.read-only span:last-child {
position:absolute;
width: 0;
height: 0;
border-bottom: 14px solid orange;
border-left: 14px solid transparent;
}
.indicator.write-only {
position:relative;
border-color: transparent;
background-color: transparent
}
.indicator.write-only span:first-child {
position:absolute;
width: 0;
height: 0;
border-bottom: 14px solid orange;
border-left: 14px solid transparent;
}
.indicator.write-only span:last-child {
position:absolute;
width: 0;
height: 0;
border-top: 14px solid green;
border-right: 14px solid transparent;
}
.indicator.offline {
background-color: red;
border-color: rgba(255,0,0,0.5);
}
table.online .relay-url {
cursor: pointer;
}
</style>

0
src/pages/IndexPage.vue → src/components/NavComponent.vue

39
src/components/RelayListComponent.vue

@ -0,0 +1,39 @@
<template>
<h2><span class="indicator badge readwrite">{{ query('public').length }}</span>Public</h2>
<table class="online" v-if="query('public').length > 0">
<tr>
<th></th>
<th></th>
<th>🔌</th>
<th>👁🗨</th>
<th></th>
<th>🌎</th>
<!-- <td>wl</td>
<td>nip-05><td> -->
<th></th>
<th>ℹ️</th>
</tr>
<tr v-for="relay in query('public')" :key="{relay}" :class="getLoadingClass(relay)">
<RelaySingle
:relay="{{relay}}"
:didConnect="{{didConnect}}"
:didRead="{{didRead}}"
:didWrite="{{didWrite}}"
/>
</tr>
</table>
</template>
<script>
import { defineComponent} from 'vue'
export default defineComponent({
name: 'RelaySingle',
components: {
Popper
},
data() {
}
})
</script>

38
src/components/RelaySingleComponent.vue

@ -0,0 +1,38 @@
<template>
<td :key="generateKey(relay, 'aggregate')"><span :class="getAggregateStatusClass(relay)"></span></td>
<td class="left-align relay-url" @click="copy(relay)">{{ relay }}</td>
<td :key="generateKey(relay, 'didConnect')"><span :class="getStatusClass(relay, 'didConnect')"></span></td>
<td :key="generateKey(relay, 'didRead')"><span :class="getStatusClass(relay, 'didRead')"></span></td>
<td :key="generateKey(relay, 'didWrite')"><span :class="getStatusClass(relay, 'didWrite')"></span></td>
<td>{{status[relay].flag}}</td>
<td><span v-if="status[relay].didConnect">{{ status[relay].latency }}<span v-if="status[relay].latency">ms</span></span></td>
<td>
<Popper v-if="Object.keys(status[relay].messages).length">
{{ status[relay].type }}
<button @mouseover="showPopper">log</button>
<template #content>
<ul>
<li v-for="(message, key) in status[relay].messages" :key="generateKey(relay, key)">{{key}}</li>
</ul>
</template>
</Popper>
</td>
</template>
<script>
import { defineComponent} from 'vue'
export default defineComponent({
name: 'RelaySingle',
components: {
Popper
},
props: [
'relay',
'status',
]
data() {
return {}
}
})
</script>

0
src/index.js

4
src/main.js

@ -0,0 +1,4 @@
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')

23
src/router/index.js

@ -1,23 +0,0 @@
import { route } from 'quasar/wrappers'
import {
createRouter,
createWebHistory,
createWebHashHistory,
} from 'vue-router'
import routes from './routes'
export default route(() => {
const createHistory =
process.env.VUE_ROUTER_MODE === 'history'
? createWebHistory
: createWebHashHistory
const Router = createRouter({
routes,
history: createHistory(
process.env.MODE === 'ssr' ? void 0 : process.env.VUE_ROUTER_BASE
),
})
return Router
})

9
src/router/routes.js

@ -1,9 +0,0 @@
const routes = [
{
path: '/',
component: () => import('layouts/MainLayout.vue'),
children: [
{ path: '', component: () => import('pages/IndexPage.vue') }
]
export default routes

0
src/utils/test-connection-clearnet.js

0
src/utils/test-connection-onion.js

25
vue.config.js

@ -0,0 +1,25 @@
const { defineConfig } = require('@vue/cli-service')
const NodePolyfillPlugin = require("node-polyfill-webpack-plugin");
module.exports = defineConfig({
transpileDependencies: true,
devServer: {
port: 8080
},
configureWebpack: {
// watch: true,
plugins: [new NodePolyfillPlugin()],
optimization: {
splitChunks: {
chunks: "all",
},
},
},
chainWebpack: config => {
config.module
.rule('yaml')
.test(/\.ya?ml?$/)
.use('yaml-loader')
.loader('yaml-loader')
}
})

6179
yarn-error.log

File diff suppressed because it is too large
Loading…
Cancel
Save