kopia lustrzana https://github.com/nostr-protocol/nostr
javascript client somewhat a skeleton.
rodzic
5924154724
commit
4d51568354
|
@ -0,0 +1,2 @@
|
|||
node_modules
|
||||
bundle.*
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"boxcrate": "^2.1.1",
|
||||
"buffer": "^6.0.1",
|
||||
"elliptic": "^6.5.3",
|
||||
"sha.js": "^2.4.11",
|
||||
"vue": "^3.0.2",
|
||||
"vue-router": "^4.0.0-rc.2",
|
||||
"vuex": "^4.0.0-rc.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^16.0.0",
|
||||
"@rollup/plugin-inject": "^4.0.2",
|
||||
"@rollup/plugin-json": "^4.1.0",
|
||||
"@rollup/plugin-node-resolve": "^10.0.0",
|
||||
"@vue/compiler-sfc": "^3.0.2",
|
||||
"rollup": "^2.33.1",
|
||||
"rollup-plugin-css-only": "^2.1.0",
|
||||
"rollup-plugin-inject-process-env": "^1.3.1",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"rollup-plugin-vue": "^6.0.0-beta.11"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
import vuePlugin from 'rollup-plugin-vue'
|
||||
import resolve from '@rollup/plugin-node-resolve'
|
||||
import commonjs from '@rollup/plugin-commonjs'
|
||||
import {terser} from 'rollup-plugin-terser'
|
||||
import css from 'rollup-plugin-css-only'
|
||||
import inject from '@rollup/plugin-inject'
|
||||
import injectProcessEnv from 'rollup-plugin-inject-process-env'
|
||||
import json from '@rollup/plugin-json'
|
||||
|
||||
const production = !!process.env.PRODUCTION
|
||||
|
||||
export default {
|
||||
input: 'src/main.js',
|
||||
output: {
|
||||
sourcemap: true,
|
||||
format: 'iife',
|
||||
name: 'app',
|
||||
file: 'static/bundle.js'
|
||||
},
|
||||
plugins: [
|
||||
css({output: 'static/bundle.css'}),
|
||||
|
||||
vuePlugin({
|
||||
include: /\.html$/,
|
||||
preprocessStyles: true
|
||||
}),
|
||||
|
||||
json({
|
||||
// exclude: '**/bip39/src/wordlists/!(english).json',
|
||||
indent: ''
|
||||
}),
|
||||
|
||||
resolve({
|
||||
browser: true,
|
||||
preferBuiltins: false
|
||||
}),
|
||||
|
||||
commonjs(),
|
||||
|
||||
inject({
|
||||
Buffer: ['buffer', 'Buffer']
|
||||
}),
|
||||
|
||||
injectProcessEnv({
|
||||
NODE_ENV: production ? 'production' : 'development'
|
||||
}),
|
||||
|
||||
production && terser()
|
||||
]
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<template>
|
||||
<div class="nav">
|
||||
<router-link to="/setup">Setup</router-link>
|
||||
<span class="pubkey">{{ $store.getters.pubKeyHex }}</span>
|
||||
<router-link to="/">Home</router-link>
|
||||
</div>
|
||||
<div>
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.nav > * {
|
||||
margin: 0 10px;
|
||||
}
|
||||
.pubkey {
|
||||
display: inline-block;
|
||||
width: 160px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
.pubkey:hover {
|
||||
width: auto;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,61 @@
|
|||
<template>
|
||||
<form @submit="publishNote">
|
||||
<legend>Publishing to {{ writeServersList }}</legend>
|
||||
<label
|
||||
>Write anything:
|
||||
<textarea :disabled="publishing" v-model="text"></textarea>
|
||||
</label>
|
||||
<button :disabled="publishing">Publish</button>
|
||||
</form>
|
||||
<p>Data providers: {{ readServersList }}</p>
|
||||
<div v-if="$store.state.loadingNotes">
|
||||
<p>Loading notes...</p>
|
||||
</div>
|
||||
<div v-else-if="$store.state.following.length === 0">
|
||||
<p>You're not following anyone.</p>
|
||||
</div>
|
||||
<div v-else-if="$store.state.notes.length === 0">
|
||||
<p>Didn't find any data.</p>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-for="note in $store.state.notes">
|
||||
<Note v-bind="note" :key="note.id" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {text: '', publishing: false}
|
||||
},
|
||||
computed: {
|
||||
readServersList() {
|
||||
return JSON.stringify(this.$store.getters.readServers)
|
||||
.replace(/"/g, '')
|
||||
.replace(/,/g, ' ')
|
||||
},
|
||||
writeServersList() {
|
||||
return JSON.stringify(this.$store.getters.writeServers)
|
||||
.replace(/"/g, '')
|
||||
.replace(/,/g, ' ')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async publishNote(ev) {
|
||||
ev.preventDefault()
|
||||
this.publishing = true
|
||||
|
||||
try {
|
||||
await this.$store.dispatch('publishNote', this.text)
|
||||
this.text = ''
|
||||
} catch (err) {
|
||||
console.log('error publishing', err)
|
||||
}
|
||||
this.publishing = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
|
@ -0,0 +1,5 @@
|
|||
<template></template>
|
||||
|
||||
<script></script>
|
||||
|
||||
<style></style>
|
|
@ -0,0 +1,5 @@
|
|||
<template></template>
|
||||
|
||||
<script></script>
|
||||
|
||||
<style></style>
|
|
@ -0,0 +1,21 @@
|
|||
<template>
|
||||
<div><b>Secret Key:</b> {{ $store.getters.privkeyHex }}</div>
|
||||
<div>
|
||||
<b>Relays:</b>
|
||||
{{ JSON.stringify($store.state.relays) }}
|
||||
</div>
|
||||
<div>
|
||||
<b>Following:</b>
|
||||
{{ JSON.stringify($store.state.following) }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
|
@ -0,0 +1,31 @@
|
|||
import {createApp} from 'vue/dist/vue.esm-bundler.js'
|
||||
import {createRouter, createWebHashHistory} from 'vue-router'
|
||||
|
||||
import App from './App.html'
|
||||
import Home from './Home.html'
|
||||
import Setup from './Setup.html'
|
||||
import Profile from './Profile.html'
|
||||
import Note from './Note.html'
|
||||
|
||||
import store from './store'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes: [
|
||||
{path: '/', component: Home},
|
||||
{path: '/setup', component: Setup},
|
||||
{path: '/:key', component: Profile},
|
||||
{path: '/n/:id', component: Note}
|
||||
]
|
||||
})
|
||||
|
||||
const app = createApp({})
|
||||
|
||||
app.use(router)
|
||||
app.use(store)
|
||||
app.component('App', App)
|
||||
app.component('Home', Home)
|
||||
app.component('Setup', Setup)
|
||||
app.component('Profile', Profile)
|
||||
app.component('Note', Note)
|
||||
app.mount('#app')
|
|
@ -0,0 +1,97 @@
|
|||
import {createStore, createLogger} from 'vuex'
|
||||
import BoxCrate from 'boxcrate'
|
||||
import elliptic from 'elliptic'
|
||||
import shajs from 'sha.js'
|
||||
|
||||
const boxcrate = new BoxCrate({
|
||||
expiredCheckType: 'active',
|
||||
expiredCheckInterval: 60000
|
||||
})
|
||||
const ec = new elliptic.ec('secp256k1')
|
||||
|
||||
export default createStore({
|
||||
plugins: process.env.NODE_ENV !== 'production' ? [createLogger()] : [],
|
||||
state() {
|
||||
var relays = {
|
||||
'http://0.0.0.0:7447': 'rw'
|
||||
}
|
||||
|
||||
var key = null
|
||||
let following = []
|
||||
|
||||
relays = boxcrate.storage.getItem('relays') || relays
|
||||
key = boxcrate.storage.getItem('key') || key
|
||||
following = boxcrate.storage.getItem('following') || following
|
||||
|
||||
// generate key if doesn't exist
|
||||
if (key) key = ec.keyFromPrivate(key, 'hex')
|
||||
else {
|
||||
key = ec.genKeyPair()
|
||||
boxcrate.storage.setItem('key', key.getPrivate('hex'))
|
||||
}
|
||||
|
||||
return {
|
||||
relays,
|
||||
key,
|
||||
following
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
privKeyHex: state => state.key.getPrivate('hex'),
|
||||
pubKeyHex: state => state.key.getPublic(true, 'hex'),
|
||||
writeServers: state =>
|
||||
Object.keys(state.relays).filter(
|
||||
host => state.relays[host].indexOf('w') !== -1
|
||||
),
|
||||
readServers: state =>
|
||||
Object.keys(state.relays).filter(
|
||||
host => state.relays[host].indexOf('r') !== -1
|
||||
)
|
||||
},
|
||||
mutations: {},
|
||||
actions: {
|
||||
publishNote(store, text) {
|
||||
let evt = {
|
||||
pubkey: store.getters.pubKeyHex,
|
||||
time: Math.round(new Date().getTime() / 1000),
|
||||
kind: 1,
|
||||
content: text
|
||||
}
|
||||
|
||||
let hash = shajs('sha256').update(serializeEvent(evt)).digest()
|
||||
evt.id = hash.toString('hex')
|
||||
evt.signature = store.state.key.sign(hash).toDER('hex')
|
||||
|
||||
for (let i = 0; i < store.getters.writeServers.length; i++) {
|
||||
let host = store.getters.writeServers[i]
|
||||
window.fetch(host + '/save_update', {
|
||||
method: 'POST',
|
||||
headers: {'content-type': 'application/json'},
|
||||
body: JSON.stringify(evt)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function serializeEvent(evt) {
|
||||
let version = Buffer.alloc(1)
|
||||
version.writeUInt8(0)
|
||||
|
||||
let pubkey = Buffer.from(evt.pubkey, 'hex')
|
||||
|
||||
let time = Buffer.alloc(4)
|
||||
time.writeUInt32BE(evt.time)
|
||||
|
||||
let kind = Buffer.alloc(1)
|
||||
kind.writeUInt8(kind)
|
||||
|
||||
let reference = Buffer.alloc(0)
|
||||
if (evt.reference) {
|
||||
reference = Buffer.from(evt.reference, 'hex')
|
||||
}
|
||||
|
||||
let content = Buffer.from(evt.content)
|
||||
|
||||
return Buffer.concat([version, pubkey, time, kind, reference, content])
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<title>profiles 📡</title>
|
||||
<link rel="stylesheet" href="/bundle.css" />
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
<App />
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script src="/bundle.js"></script>
|
Ładowanie…
Reference in New Issue