mirror of
https://github.com/styppo/hamstr.git
synced 2024-10-18 13:33:22 +00:00
big refactor on core logic.
This commit is contained in:
parent
598dd7459e
commit
c04c4d7b52
@ -11,12 +11,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@quasar/extras": "^1.0.0",
|
||||
"bip32": "^3.0.1",
|
||||
"bip39": "^3.0.4",
|
||||
"core-js": "^3.6.5",
|
||||
"crypto": "^1.0.1",
|
||||
"identicon.js": "^2.3.3",
|
||||
"md-gum-polyfill": "^1.0.0",
|
||||
"nostr-tools": "^0.5.0",
|
||||
"nostr-tools": "^0.6.0",
|
||||
"quasar": "^2.0.0",
|
||||
"stream": "^0.0.2",
|
||||
"vuex": "^4.0.1"
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
/* eslint-env node */
|
||||
const ESLintPlugin = require('eslint-webpack-plugin')
|
||||
const { configure } = require('quasar/wrappers');
|
||||
const {configure} = require('quasar/wrappers')
|
||||
|
||||
module.exports = configure(function (ctx) {
|
||||
return {
|
||||
@ -21,13 +21,10 @@ module.exports = configure(function (ctx) {
|
||||
// app boot file (/src/boot)
|
||||
// --> boot files are part of "main.js"
|
||||
// https://quasar.dev/quasar-cli/boot-files
|
||||
boot: [
|
||||
],
|
||||
boot: [],
|
||||
|
||||
// https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-css
|
||||
css: [
|
||||
'app.scss'
|
||||
],
|
||||
css: ['app.scss'],
|
||||
|
||||
// https://github.com/quasarframework/quasar/tree/dev/extras
|
||||
extras: [
|
||||
@ -40,15 +37,15 @@ module.exports = configure(function (ctx) {
|
||||
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
|
||||
|
||||
'roboto-font', // optional, you are not bound to it
|
||||
'material-icons', // optional, you are not bound to it
|
||||
'material-icons' // optional, you are not bound to it
|
||||
],
|
||||
|
||||
// Full list of options: https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-build
|
||||
build: {
|
||||
vueRouterMode: 'hash', // available values: 'hash', 'history'
|
||||
vueRouterMode: 'history', // available values: 'hash', 'history'
|
||||
|
||||
// transpile: false,
|
||||
// publicPath: '/',
|
||||
publicPath: '/',
|
||||
|
||||
// Add dependencies for transpiling with Babel (Array of string/regex)
|
||||
// (from node_modules, which are by default not transpiled).
|
||||
@ -66,10 +63,11 @@ module.exports = configure(function (ctx) {
|
||||
|
||||
// https://quasar.dev/quasar-cli/handling-webpack
|
||||
// "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain
|
||||
chainWebpack (chain) {
|
||||
chain.plugin('eslint-webpack-plugin')
|
||||
.use(ESLintPlugin, [{ extensions: [ 'js', 'vue' ] }])
|
||||
},
|
||||
chainWebpack(chain) {
|
||||
chain
|
||||
.plugin('eslint-webpack-plugin')
|
||||
.use(ESLintPlugin, [{extensions: ['js', 'vue']}])
|
||||
}
|
||||
},
|
||||
|
||||
// Full list of options: https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-devServer
|
||||
@ -111,14 +109,15 @@ module.exports = configure(function (ctx) {
|
||||
// manualPostHydrationTrigger: true,
|
||||
|
||||
prodPort: 3000, // The default port that the production server should use
|
||||
// (gets superseded if process.env.PORT is specified at runtime)
|
||||
// (gets superseded if process.env.PORT is specified at runtime)
|
||||
|
||||
maxAge: 1000 * 60 * 60 * 24 * 30,
|
||||
// Tell browser when a file from the server should expire from cache (in ms)
|
||||
// Tell browser when a file from the server should expire from cache (in ms)
|
||||
|
||||
chainWebpackWebserver (chain) {
|
||||
chain.plugin('eslint-webpack-plugin')
|
||||
.use(ESLintPlugin, [{ extensions: [ 'js' ] }])
|
||||
chainWebpackWebserver(chain) {
|
||||
chain
|
||||
.plugin('eslint-webpack-plugin')
|
||||
.use(ESLintPlugin, [{extensions: ['js']}])
|
||||
},
|
||||
|
||||
middlewares: [
|
||||
@ -134,9 +133,10 @@ module.exports = configure(function (ctx) {
|
||||
|
||||
// for the custom service worker ONLY (/src-pwa/custom-service-worker.[js|ts])
|
||||
// if using workbox in InjectManifest mode
|
||||
chainWebpackCustomSW (chain) {
|
||||
chain.plugin('eslint-webpack-plugin')
|
||||
.use(ESLintPlugin, [{ extensions: [ 'js' ] }])
|
||||
chainWebpackCustomSW(chain) {
|
||||
chain
|
||||
.plugin('eslint-webpack-plugin')
|
||||
.use(ESLintPlugin, [{extensions: ['js']}])
|
||||
},
|
||||
|
||||
manifest: {
|
||||
@ -193,13 +193,11 @@ module.exports = configure(function (ctx) {
|
||||
|
||||
packager: {
|
||||
// https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#options
|
||||
|
||||
// OS X / Mac App Store
|
||||
// appBundleId: '',
|
||||
// appCategoryType: '',
|
||||
// osxSign: '',
|
||||
// protocol: 'myapp://path',
|
||||
|
||||
// Windows only
|
||||
// win32metadata: { ... }
|
||||
},
|
||||
@ -211,16 +209,18 @@ module.exports = configure(function (ctx) {
|
||||
},
|
||||
|
||||
// "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain
|
||||
chainWebpackMain (chain) {
|
||||
chain.plugin('eslint-webpack-plugin')
|
||||
.use(ESLintPlugin, [{ extensions: [ 'js' ] }])
|
||||
chainWebpackMain(chain) {
|
||||
chain
|
||||
.plugin('eslint-webpack-plugin')
|
||||
.use(ESLintPlugin, [{extensions: ['js']}])
|
||||
},
|
||||
|
||||
// "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain
|
||||
chainWebpackPreload (chain) {
|
||||
chain.plugin('eslint-webpack-plugin')
|
||||
.use(ESLintPlugin, [{ extensions: [ 'js' ] }])
|
||||
},
|
||||
chainWebpackPreload(chain) {
|
||||
chain
|
||||
.plugin('eslint-webpack-plugin')
|
||||
.use(ESLintPlugin, [{extensions: ['js']}])
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
|
@ -1,10 +1,8 @@
|
||||
import Generate from '../components/Generate.vue'
|
||||
import Publish from '../components/Publish.vue'
|
||||
import Reply from '../components/Reply.vue'
|
||||
import Post from '../components/Post.vue'
|
||||
|
||||
export default ({app}) => {
|
||||
app.component('Generate', Generate)
|
||||
app.component('Publish', Publish)
|
||||
app.component('Reply', Reply)
|
||||
app.component('Post', Post)
|
||||
|
@ -1,222 +0,0 @@
|
||||
<template>
|
||||
<q-card class="q-pa-md q-pt-lg q-mt-md">
|
||||
<q-stepper v-model="step" vertical color="primary" animated>
|
||||
<q-step
|
||||
:name="1"
|
||||
title="Generate/Restore"
|
||||
icon="settings"
|
||||
:done="step > 1"
|
||||
>
|
||||
Nostr.org uses a word list of 12 words is used to create your keys, to
|
||||
restore either enter a word list or a Nostr private key.
|
||||
<q-input
|
||||
v-model="recover"
|
||||
:loading="loading"
|
||||
autogrow
|
||||
type="textarea"
|
||||
label="Word List/Private Key"
|
||||
></q-input
|
||||
><br />
|
||||
|
||||
<q-btn
|
||||
color="primary"
|
||||
label="Generate"
|
||||
class="q-mr-md"
|
||||
@click="createKeys"
|
||||
/>
|
||||
<q-btn
|
||||
color="primary"
|
||||
label="Restore"
|
||||
class="q-mr-md"
|
||||
@click="createKeys"
|
||||
/>
|
||||
|
||||
<q-btn
|
||||
v-if="privatekey"
|
||||
color="primary"
|
||||
label="Continue"
|
||||
@click="step = 2"
|
||||
/>
|
||||
</q-step>
|
||||
|
||||
<q-step :name="2" title="Your keys" icon="vpn_key" :done="step > 2">
|
||||
In this client you can restore from a word list but for other clients
|
||||
you will need to use your keys as well.<br /><br />
|
||||
Your private key is used to sign/publish posts.
|
||||
<br />
|
||||
<q-input
|
||||
v-model="privatekey"
|
||||
filled
|
||||
:type="isPwd ? 'password' : 'text'"
|
||||
>
|
||||
<template #prepend>
|
||||
<q-icon
|
||||
name="content_copy"
|
||||
class="cursor-pointer"
|
||||
@click="copyToClip(privatekey)"
|
||||
></q-icon>
|
||||
</template>
|
||||
<template #append>
|
||||
<q-icon
|
||||
:name="isPwd ? 'visibility_off' : 'visibility'"
|
||||
class="cursor-pointer"
|
||||
@click="isPwd = !isPwd"
|
||||
></q-icon>
|
||||
</template>
|
||||
</q-input>
|
||||
<br />
|
||||
Your public key allows other people to read your posts, follow you, and
|
||||
send you private messages.
|
||||
<br />
|
||||
<q-input v-model="publickey" filled type="text">
|
||||
<template #prepend>
|
||||
<q-icon
|
||||
name="content_copy"
|
||||
class="cursor-pointer"
|
||||
@click="copyToClip(publickey)"
|
||||
></q-icon>
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<q-stepper-navigation>
|
||||
<q-btn color="primary" label="Continue" @click="step = 3" />
|
||||
<q-btn
|
||||
flat
|
||||
color="primary"
|
||||
label="Back"
|
||||
class="q-ml-sm"
|
||||
@click="step = 1"
|
||||
/>
|
||||
</q-stepper-navigation>
|
||||
</q-step>
|
||||
|
||||
<q-step :name="3" title="Key storage" icon="lock">
|
||||
To publish your posts this client needs to sign messages with your
|
||||
private key. Choose how this client will access your private key.
|
||||
<div class="q-pa-md q-gutter-sm">
|
||||
<div class="q-gutter-sm">
|
||||
<q-radio
|
||||
v-model="keystoreoption"
|
||||
dense
|
||||
val="local"
|
||||
label="Local Storage (Recommended)"
|
||||
/><br />
|
||||
<q-radio
|
||||
v-model="keystoreoption"
|
||||
dense
|
||||
disable
|
||||
val="url"
|
||||
label="URL (coming soon)"
|
||||
/><br />
|
||||
<q-radio
|
||||
v-model="keystoreoption"
|
||||
dense
|
||||
disable
|
||||
val="external"
|
||||
label="Hardware wallet (coming soon)"
|
||||
/><br />
|
||||
</div>
|
||||
</div>
|
||||
<q-stepper-navigation>
|
||||
<q-btn color="primary" label="Finish" @click="finalGenerate" />
|
||||
<q-btn
|
||||
flat
|
||||
color="primary"
|
||||
label="Back"
|
||||
class="q-ml-sm"
|
||||
@click="step = 2"
|
||||
/>
|
||||
</q-stepper-navigation>
|
||||
</q-step>
|
||||
</q-stepper>
|
||||
</q-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import crypto from 'crypto'
|
||||
import {getPublicKey} from 'nostr-tools'
|
||||
import {copyToClipboard} from 'quasar'
|
||||
import helpersMixin from '../utils/mixin'
|
||||
|
||||
const bip39 = require('bip39')
|
||||
const bip32 = require('bip32')
|
||||
|
||||
export default {
|
||||
mixins: [helpersMixin],
|
||||
|
||||
data() {
|
||||
return {
|
||||
step: 1,
|
||||
loading: false,
|
||||
recover: '',
|
||||
privatekey: null,
|
||||
publickey: null,
|
||||
keystoreoption: 'local',
|
||||
isPwd: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async createKeys() {
|
||||
this.loading = true
|
||||
this.recover = this.recover.trim()
|
||||
|
||||
setTimeout(() => {
|
||||
if (this.recover.split(/ +/).length === 12) {
|
||||
// recover mnemonic
|
||||
let mnemonic = this.recover.split(/ +/).join(' ')
|
||||
let seed = bip39.mnemonicToSeedSync(mnemonic)
|
||||
let root = bip32.fromSeed(seed)
|
||||
this.privatekey = root.privateKey.toString('hex')
|
||||
|
||||
this.recover = mnemonic
|
||||
} else if (/^[0-9a-f]{64}$/.exec(this.recover.toLowerCase())) {
|
||||
// recover private key
|
||||
this.privatekey = this.recover.toLowerCase()
|
||||
|
||||
this.recover = this.privatekey
|
||||
} else {
|
||||
// generate a new seed
|
||||
let randomBytes = crypto.randomBytes(16)
|
||||
let mnemonic = bip39.entropyToMnemonic(randomBytes.toString('hex'))
|
||||
let seed = bip39.mnemonicToSeedSync(mnemonic)
|
||||
let root = bip32.fromSeed(seed)
|
||||
this.privatekey = root.privateKey.toString('hex')
|
||||
this.$q.notify({
|
||||
message: 'MAKE SURE YOU BACKUP YOUR WORD LIST'
|
||||
})
|
||||
|
||||
this.recover = mnemonic
|
||||
}
|
||||
|
||||
this.publickey = getPublicKey(this.privatekey)
|
||||
this.loading = false
|
||||
}, 1)
|
||||
},
|
||||
copyToClip(text) {
|
||||
copyToClipboard(text)
|
||||
.then(() => {
|
||||
this.$q.notify({
|
||||
message: 'COPIED'
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
this.$q.notify({type: 'negative', message: 'FAILED'})
|
||||
})
|
||||
},
|
||||
|
||||
finalGenerate() {
|
||||
this.$store.dispatch('finalGenerate', {
|
||||
privatekey: this.privatekey,
|
||||
publickey: this.publickey,
|
||||
keystoreoption: this.keystoreoption
|
||||
})
|
||||
|
||||
if (this.keystoreoption === 'external') {
|
||||
this.$router.push('/?pub=' + this.publickey + '&prv=' + this.privatekey)
|
||||
} else {
|
||||
this.$router.push('/')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -17,7 +17,7 @@
|
||||
<q-card-section class="col no-shadow">
|
||||
<q-card-section class="q-pa-none" @click="dialogReply = true">
|
||||
<q-item-label
|
||||
>{{ $store.getters.handle(post.pubkey) }}
|
||||
>{{ $store.getters.displayName(post.pubkey) }}
|
||||
<small style="color: grey">
|
||||
{{ niceDate(post.created_at * 1000) }}
|
||||
</small>
|
||||
@ -38,29 +38,6 @@
|
||||
@click="dialogReply = true"
|
||||
>
|
||||
</q-btn>
|
||||
|
||||
<q-btn
|
||||
v-if="post.retry"
|
||||
class="float-right q-mr-xs"
|
||||
round
|
||||
unelevated
|
||||
color="pink"
|
||||
flat
|
||||
icon="settings_backup_restore"
|
||||
size="sm"
|
||||
@click="postAgain(post)"
|
||||
/>
|
||||
<q-btn
|
||||
v-if="post.retry"
|
||||
class="float-right q-mr-xs"
|
||||
round
|
||||
unelevated
|
||||
color="pink"
|
||||
flat
|
||||
icon="cancel"
|
||||
size="sm"
|
||||
@click="deletePost(post)"
|
||||
/>
|
||||
</div>
|
||||
</q-card-section>
|
||||
</q-card-section>
|
||||
@ -77,15 +54,6 @@ export default {
|
||||
return {
|
||||
dialogReply: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
postAgain(post) {
|
||||
this.$store.dispatch('postAgain', post)
|
||||
},
|
||||
|
||||
deletePost(post) {
|
||||
this.$store.dispatch('deletePost', post.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -3,17 +3,15 @@
|
||||
<div class="row">
|
||||
<q-form style="width: 100%" class="q-gutter-md" @submit="sendPost">
|
||||
<q-input
|
||||
v-model="publishtext"
|
||||
v-model="text"
|
||||
style="font-size: 20px"
|
||||
label="Say something"
|
||||
maxlength="280"
|
||||
>
|
||||
<template #before>
|
||||
<q-btn round @click="toProfile($store.state.myProfile.pubkey)">
|
||||
<q-btn round @click="toProfile($store.state.keys.pub)">
|
||||
<q-avatar size="42px">
|
||||
<img
|
||||
:src="$store.getters.avatar($store.state.myProfile.pubkey)"
|
||||
/>
|
||||
<img :src="$store.getters.avatar($store.state.keys.pub)" />
|
||||
</q-avatar>
|
||||
</q-btn>
|
||||
</template>
|
||||
@ -21,7 +19,7 @@
|
||||
|
||||
<div class="float-right">
|
||||
<q-btn
|
||||
v-if="publishtext.length < 280"
|
||||
v-if="text.length < 280"
|
||||
class="float-left q-mr-md"
|
||||
round
|
||||
unelevated
|
||||
@ -37,7 +35,7 @@
|
||||
rounded
|
||||
unelevated
|
||||
dense
|
||||
@click="publishtext = publishtext + emoji.item"
|
||||
@click="text = text + emoji.item"
|
||||
>{{ emoji.item }}</q-btn
|
||||
>
|
||||
<br />
|
||||
@ -48,7 +46,7 @@
|
||||
rounded
|
||||
unelevated
|
||||
dense
|
||||
@click="publishtext = publishtext + emoji.item"
|
||||
@click="text = text + emoji.item"
|
||||
>{{ emoji.item }}</q-btn
|
||||
>
|
||||
</q-popup-proxy>
|
||||
@ -87,13 +85,13 @@ export default {
|
||||
|
||||
data() {
|
||||
return {
|
||||
publishtext: ''
|
||||
text: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
sendPost() {
|
||||
this.$store.dispatch('sendPost', {message: this.publishtext})
|
||||
this.publishtext = ''
|
||||
this.$store.dispatch('sendPost', {message: this.text})
|
||||
this.text = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@
|
||||
|
||||
<q-card-section class="col no-shadow q-pb-none">
|
||||
<q-item-label
|
||||
>{{ $store.getters.handle(post.pubkey) }}
|
||||
>{{ $store.getters.displayName(post.pubkey) }}
|
||||
<small style="color: grey">{{ niceDate(post.created_at) }}</small>
|
||||
</q-item-label>
|
||||
{{ post.content }}
|
||||
@ -25,11 +25,11 @@
|
||||
<q-form
|
||||
style="width: 100%"
|
||||
class="q-gutter-md"
|
||||
@submit="sendReply(replytext, [['e', post.id]])"
|
||||
@submit="sendReply(text, [['e', post.id]])"
|
||||
><q-tooltip> Coming soon </q-tooltip>
|
||||
<q-input
|
||||
disable
|
||||
v-model="replytext"
|
||||
v-model="text"
|
||||
dense
|
||||
style="font-size: 20px"
|
||||
autogrow
|
||||
@ -40,7 +40,7 @@
|
||||
<div class="float-right">
|
||||
<q-btn
|
||||
disable
|
||||
v-if="replytext.length < 280"
|
||||
v-if="text.length < 280"
|
||||
class="float-left q-mr-md"
|
||||
round
|
||||
unelevated
|
||||
@ -56,7 +56,7 @@
|
||||
rounded
|
||||
unelevated
|
||||
dense
|
||||
@click="replytext = replytext + emoji.item"
|
||||
@click="text = text + emoji.item"
|
||||
>{{ emoji.item }}</q-btn
|
||||
>
|
||||
<br />
|
||||
@ -67,7 +67,7 @@
|
||||
rounded
|
||||
unelevated
|
||||
dense
|
||||
@click="replytext = replytext + emoji.item"
|
||||
@click="text = text + emoji.item"
|
||||
>{{ emoji.item }}</q-btn
|
||||
>
|
||||
</q-popup-proxy>
|
||||
@ -109,18 +109,16 @@ export default {
|
||||
props: ['post'],
|
||||
data() {
|
||||
return {
|
||||
publishtext: '',
|
||||
replytext: ''
|
||||
text: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
sendReply() {
|
||||
console.log(this.post.id)
|
||||
this.$store.dispatch('sendPost', {
|
||||
message: this.replytext,
|
||||
message: this.text,
|
||||
tags: [['e', this.post.id]]
|
||||
})
|
||||
this.replytext = ''
|
||||
this.text = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,10 +4,6 @@
|
||||
<Publish />
|
||||
</q-dialog>
|
||||
|
||||
<q-dialog v-model="dialogGenerate" position="top">
|
||||
<Generate />
|
||||
</q-dialog>
|
||||
|
||||
<div class="flex-center column">
|
||||
<div class="row" style="width: 100%">
|
||||
<div
|
||||
@ -106,22 +102,6 @@
|
||||
|
||||
<q-item-section>Settings</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-item
|
||||
v-ripple
|
||||
clickable
|
||||
:active="$route.name === 'help'"
|
||||
active-class="my-menu-link"
|
||||
style="padding: 15px"
|
||||
:to="'/help'"
|
||||
>
|
||||
<q-item-section avatar>
|
||||
<q-icon name="help"></q-icon>
|
||||
</q-item-section>
|
||||
|
||||
<q-item-section>Help</q-item-section>
|
||||
</q-item>
|
||||
<br />
|
||||
</q-list>
|
||||
<q-btn
|
||||
v-if="!$store.getters.disabled"
|
||||
@ -151,8 +131,8 @@
|
||||
color="primary"
|
||||
size="md"
|
||||
dense
|
||||
:label="$store.getters.handle($store.state.myProfile.pubkey)"
|
||||
@click="copyToClip($store.state.myProfile.pubkey)"
|
||||
:label="$store.getters.displayName($store.state.keys.pub)"
|
||||
@click="copyPubKey"
|
||||
>
|
||||
<q-tooltip> Copy public key </q-tooltip></q-btn
|
||||
>
|
||||
@ -203,13 +183,11 @@
|
||||
</q-input>
|
||||
</q-form>
|
||||
</q-card-section>
|
||||
<q-card-section
|
||||
v-if="Object.keys($store.state.theirProfile).length"
|
||||
>
|
||||
<q-card-section v-if="$store.state.following.length">
|
||||
<h6 class="q-ma-none">Following</h6>
|
||||
<q-list>
|
||||
<q-item
|
||||
v-for="(_, pubkey) in $store.state.theirProfile"
|
||||
v-for="(_, pubkey) in $store.state.following"
|
||||
:key="pubkey"
|
||||
v-ripple
|
||||
clickable
|
||||
@ -222,7 +200,7 @@
|
||||
</q-item-section>
|
||||
|
||||
<q-item-section>
|
||||
{{ $store.getters.handle(pubkey) }}
|
||||
{{ $store.getters.displayName(pubkey) }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
@ -234,44 +212,7 @@
|
||||
</div>
|
||||
|
||||
<q-footer bordered style="bottom: 0%; position: fixed" class="bg-white">
|
||||
<q-banner
|
||||
v-if="showInstallBanner"
|
||||
inline-actions
|
||||
dense
|
||||
class="bg-primary text-white"
|
||||
>
|
||||
<template #avatar>
|
||||
<q-avatar>
|
||||
<img src="/icons/favicon-16x16.png" />
|
||||
</q-avatar>
|
||||
</template>
|
||||
<b> INSTALL NOSTR?</b>
|
||||
|
||||
<template #action>
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
class="q-px-sm"
|
||||
label="Yes"
|
||||
@click="installApp()"
|
||||
></q-btn>
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
class="q-px-sm"
|
||||
label="Later"
|
||||
@click="showInstallBanner = false"
|
||||
></q-btn>
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
class="q-px-sm"
|
||||
label="Never"
|
||||
@click="neverInstallApp()"
|
||||
></q-btn>
|
||||
</template>
|
||||
</q-banner>
|
||||
<center>
|
||||
<div class="text-center">
|
||||
<q-tabs class="text-primary small-screen-only">
|
||||
<q-route-tab style="width: 20%" name="home" icon="home" to="/" />
|
||||
|
||||
@ -287,85 +228,39 @@
|
||||
icon="settings"
|
||||
to="/settings"
|
||||
/>
|
||||
<q-route-tab style="width: 20%" name="help" icon="help" to="/help" />
|
||||
</q-tabs>
|
||||
</center>
|
||||
</div>
|
||||
</q-footer>
|
||||
</q-layout>
|
||||
</template>
|
||||
<script>
|
||||
let deferredPrompt
|
||||
import {copyToClipboard} from 'quasar'
|
||||
import helpersMixin from '../utils/mixin'
|
||||
|
||||
export default {
|
||||
name: 'MainLayout',
|
||||
mixins: [helpersMixin],
|
||||
|
||||
data() {
|
||||
return {
|
||||
showInstallBanner: null,
|
||||
dialogGenerate: false,
|
||||
dialogPublish: false,
|
||||
addPubKey: ''
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
let value = this.$q.localStorage.getItem('neverShowBanner')
|
||||
if (!value) {
|
||||
window.addEventListener('beforeinstallprompt', e => {
|
||||
e.preventDefault()
|
||||
deferredPrompt = e
|
||||
this.showInstallBanner = true
|
||||
})
|
||||
}
|
||||
},
|
||||
created: function () {
|
||||
if (this.$store.getters.disabled) {
|
||||
this.$router.push('/help')
|
||||
return
|
||||
}
|
||||
|
||||
this.$store.dispatch('launch')
|
||||
},
|
||||
methods: {
|
||||
installApp() {
|
||||
this.showInstallBanner = false
|
||||
deferredPrompt.prompt()
|
||||
deferredPrompt.userChoice.then(choiceResult => {
|
||||
if (choiceResult.outcome === 'accepted') {
|
||||
console.log('User accepted the install prompt')
|
||||
} else {
|
||||
console.log('User dismissed the install prompt')
|
||||
}
|
||||
})
|
||||
},
|
||||
neverInstallApp() {
|
||||
this.showInstallBanner = false
|
||||
async copyPubKey() {
|
||||
try {
|
||||
this.$q.localStorage.setItem('neverShowBanner', true)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
},
|
||||
copyToClip(text) {
|
||||
copyToClipboard(text)
|
||||
.then(() => {
|
||||
this.$q.notify({
|
||||
message: 'COPIED'
|
||||
})
|
||||
await copyToClipboard(this.$store.state.keys.pub)
|
||||
this.$q.notify({
|
||||
message: 'COPIED'
|
||||
})
|
||||
.catch(() => {
|
||||
this.$q.notify({type: 'negative', message: 'FAILED'})
|
||||
})
|
||||
},
|
||||
addPubFollow() {
|
||||
if (this.addPubKey.trim() !== this.$store.state.myProfile.pubkey) {
|
||||
this.$store.dispatch('startFollowing', this.addPubKey.trim())
|
||||
} else {
|
||||
this.$q.notify({color: 'pink', message: 'You cant follow yourself!'})
|
||||
} catch (err) {
|
||||
this.$q.notify({type: 'negative', message: 'FAILED'})
|
||||
}
|
||||
|
||||
this.addPubKey = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<q-page>
|
||||
<center>
|
||||
<div class="text-center">
|
||||
<strong class="text-h6 q-pa-lg">Chat</strong>
|
||||
</center>
|
||||
</div>
|
||||
<q-btn
|
||||
v-go-back.single
|
||||
flat
|
||||
@ -21,31 +21,28 @@
|
||||
>
|
||||
<q-scroll-area
|
||||
ref="chatScroll"
|
||||
:thumb-style="thumbStyle"
|
||||
:thumb-style="{
|
||||
left: '102%',
|
||||
backgroundColor: '#26A69A',
|
||||
width: '10px',
|
||||
opacity: 0.35
|
||||
}"
|
||||
style="height: 100%; max-width: 100%"
|
||||
>
|
||||
<div v-for="message in messages" :key="message.id">
|
||||
<div
|
||||
v-for="event in $store.state.events.kind4[$route.params.pubkey] ||
|
||||
[]"
|
||||
:key="event.id"
|
||||
>
|
||||
<q-chat-message
|
||||
:text="[
|
||||
message.text +
|
||||
(message.loading
|
||||
? '<small>sending...</small>'
|
||||
: message.failed
|
||||
? '<small>failed. <a class=delete><i class=material-icons>cancel</i></a> <a class=retry><i class=material-icons>settings_backup_restore</i></a></small>'
|
||||
: '')
|
||||
]"
|
||||
:name="message.from.substring(0, 6) + '...'"
|
||||
:avatar="$store.getters.avatar(message.from)"
|
||||
:sent="
|
||||
message.from === $store.state.myProfile.pubkey ? true : false
|
||||
"
|
||||
:stamp="niceDate(new Date(message.created_at * 1000))"
|
||||
:text="[event.plaintext]"
|
||||
:name="$store.getters.displayName(event.pubkey)"
|
||||
:avatar="$store.getters.avatar(event.pubkey)"
|
||||
:sent="event.pubkey === $store.state.keys.pub"
|
||||
:stamp="niceDate(new Date(event.created_at * 1000))"
|
||||
:bg-color="
|
||||
message.from === $store.state.myProfile.pubkey
|
||||
? 'primary'
|
||||
: 'tertiary'
|
||||
event.pubkey === $store.state.keys.pub ? 'primary' : 'tertiary'
|
||||
"
|
||||
@click="ev => clickMessageAction(ev, message.id, message.text)"
|
||||
>
|
||||
</q-chat-message>
|
||||
</div>
|
||||
@ -56,7 +53,7 @@
|
||||
<q-form
|
||||
class="q-gutter-md"
|
||||
@submit="submitMessage"
|
||||
@reset="resetMessage"
|
||||
@reset="this.text = ''"
|
||||
>
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
@ -139,28 +136,7 @@ export default {
|
||||
|
||||
data() {
|
||||
return {
|
||||
text: '',
|
||||
reload: 0, // a hack to recompute messages,
|
||||
thumbStyle: {
|
||||
left: '102%',
|
||||
backgroundColor: '#26A69A',
|
||||
width: '10px',
|
||||
opacity: 0.35
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
messages() {
|
||||
this.$store.state.chatUpdated // hack to recompute
|
||||
this.scroll()
|
||||
|
||||
return (
|
||||
LocalStorage.getItem(`messages.${this.$route.params.pubkey}`).sort(
|
||||
function (a, b) {
|
||||
return a.created_at - b.created_at
|
||||
}
|
||||
) || []
|
||||
)
|
||||
text: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@ -170,23 +146,7 @@ export default {
|
||||
const duration = 350
|
||||
scrollArea.setScrollPosition(scrollTarget.scrollHeight, duration)
|
||||
},
|
||||
async failed() {
|
||||
var messages = this.$q.localStorage.getItem(
|
||||
`messages.${this.$route.params.pubkey}`
|
||||
)
|
||||
if (messages) {
|
||||
for (var i = 0; i < messages.length; i++) {
|
||||
if (messages[i].loading === true) {
|
||||
messages[i].failed = true
|
||||
messages[i].loading = false
|
||||
this.$q.localStorage.set(
|
||||
`messages.${this.$route.params.pubkey}`,
|
||||
messages
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async submitMessage() {
|
||||
await this.$store.dispatch('sendChatMessage', {
|
||||
pubkey: this.$route.params.pubkey,
|
||||
@ -194,39 +154,7 @@ export default {
|
||||
})
|
||||
|
||||
this.text = ''
|
||||
this.$store.commit('chatUpdated')
|
||||
this.scroll()
|
||||
|
||||
setTimeout(() => {
|
||||
this.$store.commit('chatUpdated') // another hack if post fails
|
||||
this.failed()
|
||||
}, 2000)
|
||||
},
|
||||
clickMessageAction(ev, id, text) {
|
||||
ev.preventDefault()
|
||||
|
||||
var action = ev.target
|
||||
for (let i = 0; i < 5; i++) {
|
||||
if (action.classList.contains('retry')) {
|
||||
this.$store.dispatch('deleteChatMessage', {
|
||||
pubkey: this.$route.params.pubkey,
|
||||
id
|
||||
})
|
||||
this.text = text
|
||||
this.submitMessage()
|
||||
} else if (action.classList.contains('delete')) {
|
||||
this.$store.dispatch('deleteChatMessage', {
|
||||
pubkey: this.$route.params.pubkey,
|
||||
id
|
||||
})
|
||||
this.$store.commit('chatUpdated')
|
||||
}
|
||||
|
||||
action = action.parentNode
|
||||
}
|
||||
},
|
||||
resetMessage() {
|
||||
this.text = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,116 +0,0 @@
|
||||
<template>
|
||||
<q-page>
|
||||
<center>
|
||||
<strong class="text-h6 q-pa-lg">Help</strong>
|
||||
</center>
|
||||
<br />
|
||||
<q-btn
|
||||
v-go-back.single
|
||||
flat
|
||||
color="white"
|
||||
icon="arrow_back"
|
||||
label="back"
|
||||
class="small-screen-only fixed-top-left"
|
||||
/>
|
||||
<br /><br />
|
||||
|
||||
<strong>What is Nostr (Notes and other stuff relays)?</strong><br /><br />
|
||||
<p>
|
||||
Nostr is a decentralised collection of relays passing data between
|
||||
clients. Anyone can run a client or relay. This particular 240char limited
|
||||
client is just one way to send data through Nostr.
|
||||
</p>
|
||||
<center>
|
||||
<img
|
||||
class="q-px-auto"
|
||||
style="width: 100%"
|
||||
src="https://i.imgur.com/NsnaiBP.png"
|
||||
/>
|
||||
<br />
|
||||
</center>
|
||||
|
||||
<p>
|
||||
Nostr uses public key cryptography. Posts are signed with your private key
|
||||
and people can follow your posts using your public key. Direct messages in
|
||||
this client are encrypted before being sent through nostr network.
|
||||
</p>
|
||||
<p>
|
||||
<a
|
||||
href="https://github.com/fiatjaf/nostr"
|
||||
target="_blank"
|
||||
style="color: #26a69a"
|
||||
>Learn more about the Nostr protocol</a
|
||||
>
|
||||
</p>
|
||||
<p>
|
||||
<a
|
||||
href="https://github.com/arcbtc/nostr"
|
||||
target="_blank"
|
||||
style="color: #26a69a"
|
||||
>https://github.com/arcbtc/nostr</a
|
||||
>
|
||||
</p>
|
||||
<center>
|
||||
<q-btn
|
||||
v-if="$store.getters.disabled"
|
||||
dense
|
||||
flat
|
||||
class="small-screen-only q-pa-lg"
|
||||
color="primary"
|
||||
size="md"
|
||||
label="Generate or Restore User Account"
|
||||
@click="dialogGenerate = true"
|
||||
></q-btn>
|
||||
</center>
|
||||
|
||||
<q-dialog v-model="dialogGenerate" position="top">
|
||||
<Generate />
|
||||
</q-dialog>
|
||||
|
||||
<q-dialog v-if="$store.getters.disabled" v-model="warningPrompt" persistent>
|
||||
<q-card style="min-width: 350px">
|
||||
<q-card-section>
|
||||
<div class="text-h6">Warning!</div>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-section class="q-pt-none">
|
||||
<p>
|
||||
This is buggy and experimental software running for testing purposes
|
||||
ONLY, any data you put on here will be lost!<br />
|
||||
</p>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-actions align="right" class="text-primary">
|
||||
<q-btn flat label="Cancel" v-close-popup />
|
||||
<q-btn
|
||||
flat
|
||||
label="Proceed"
|
||||
v-close-popup
|
||||
@click="warningPrompt = false"
|
||||
/>
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import helpersMixin from '../utils/mixin'
|
||||
|
||||
export default {
|
||||
name: 'PageHelp',
|
||||
mixins: [helpersMixin],
|
||||
|
||||
data() {
|
||||
return {
|
||||
dialogGenerate: false,
|
||||
warningPrompt: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
setTimeout(() => {
|
||||
this.warningPrompt = true
|
||||
}, 400)
|
||||
}
|
||||
}
|
||||
</script>
|
@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<q-page>
|
||||
<center>
|
||||
<div class="text-center">
|
||||
<strong class="text-h6 q-pa-lg">Encrypted Messages</strong>
|
||||
</center>
|
||||
</div>
|
||||
<q-btn
|
||||
v-go-back.single
|
||||
flat
|
||||
@ -20,7 +20,7 @@
|
||||
<div class="q-mx-auto q-px-md">
|
||||
<q-list>
|
||||
<q-item
|
||||
v-for="(_, followedKey) in $store.state.theirProfile"
|
||||
v-for="(_, followedKey) in $store.state.following"
|
||||
:key="followedKey"
|
||||
v-ripple
|
||||
clickable
|
||||
@ -33,7 +33,7 @@
|
||||
</q-item-section>
|
||||
|
||||
<q-item-section>{{
|
||||
$store.getters.handle(followedKey)
|
||||
$store.getters.displayName(followedKey)
|
||||
}}</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
|
@ -9,7 +9,9 @@
|
||||
class="small-screen-only fixed-top-left q-ma-xs"
|
||||
/>
|
||||
|
||||
<center><strong class="text-h6 q-ma-sm">Profile</strong></center>
|
||||
<div class="text-center">
|
||||
<strong class="text-h6 q-ma-sm">Profile</strong>
|
||||
</div>
|
||||
<br />
|
||||
<br />
|
||||
|
||||
@ -81,7 +83,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import 'md-gum-polyfill'
|
||||
import helpersMixin from '../utils/mixin'
|
||||
import {pool} from '../global'
|
||||
|
||||
@ -100,7 +101,7 @@ export default {
|
||||
|
||||
computed: {
|
||||
isFollowing() {
|
||||
return this.$route.params.pubkey in this.$store.state.theirProfile
|
||||
return this.$store.state.following.includes(this.$route.params.pubkey)
|
||||
}
|
||||
},
|
||||
|
||||
@ -129,12 +130,12 @@ export default {
|
||||
|
||||
methods: {
|
||||
unFollow() {
|
||||
this.$store.dispatch('stopFollowing', this.$route.params.pubkey)
|
||||
this.$store.commit('unfollow', this.$route.params.pubkey)
|
||||
this.$router.push('/')
|
||||
},
|
||||
|
||||
addPubFollow() {
|
||||
this.$store.dispatch('startFollowing', this.$route.params.pubkey)
|
||||
this.$store.commit('follow', this.$route.params.pubkey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -73,10 +73,10 @@
|
||||
/>
|
||||
<q-input
|
||||
disable
|
||||
v-model="imagetemp"
|
||||
v-model="picture"
|
||||
filled
|
||||
type="text"
|
||||
hint="Profile picture (imgur url)"
|
||||
hint="Picture URL"
|
||||
maxlength="150"
|
||||
/>
|
||||
<q-btn
|
||||
@ -97,7 +97,7 @@
|
||||
<div class="row">
|
||||
<div class="col-9">
|
||||
<q-input
|
||||
v-model="relaya"
|
||||
v-model="addingRelay"
|
||||
filled
|
||||
type="textarea"
|
||||
autogrow
|
||||
@ -120,7 +120,7 @@
|
||||
<div class="row">
|
||||
<div class="col-9">
|
||||
<q-select
|
||||
v-model="relayr"
|
||||
v-model="removingRelay"
|
||||
filled
|
||||
:options="$store.state.myProfile.relays"
|
||||
hint="Remove a relay"
|
||||
@ -219,7 +219,7 @@
|
||||
|
||||
<q-card-actions align="right" class="text-primary">
|
||||
<q-btn flat label="Cancel" v-close-popup />
|
||||
<q-btn flat label="Yes, delete storage" @click="deletels" />
|
||||
<q-btn flat label="Yes, delete storage" @click="hardReset" />
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
@ -229,44 +229,31 @@
|
||||
<script>
|
||||
import helpersMixin from '../utils/mixin'
|
||||
import {copyToClipboard} from 'quasar'
|
||||
const bip39 = require('bip39')
|
||||
|
||||
export default {
|
||||
name: 'Settings',
|
||||
mixins: [helpersMixin],
|
||||
data() {
|
||||
const {imagetemp, handle, about} = this.$store.state.myProfile
|
||||
const {name, picture, about} = this.$store.state.me
|
||||
|
||||
return {
|
||||
privatekey: null,
|
||||
publickey: null,
|
||||
deleteAccDialog: false,
|
||||
privateKeyDialog: false,
|
||||
imagetemp,
|
||||
handle,
|
||||
about,
|
||||
relayr: '',
|
||||
relaya: '',
|
||||
removingRelay: '',
|
||||
addingRelay: '',
|
||||
isPrivPwd: true,
|
||||
isPwd: true,
|
||||
addPubKey: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addPubFollow() {
|
||||
if (this.addPubKey.trim() !== this.$store.state.myProfile.pubkey) {
|
||||
this.$store.dispatch('startFollowing', this.addPubKey.trim())
|
||||
} else {
|
||||
this.$q.notify({color: 'pink', message: 'You cant follow yourself!'})
|
||||
}
|
||||
|
||||
this.addPubKey = ''
|
||||
},
|
||||
setProfile() {
|
||||
this.$store.dispatch('saveMeta', {
|
||||
image: this.imagetemp,
|
||||
handle: this.handle,
|
||||
about: this.about
|
||||
this.$store.dispatch('setMetadata', {
|
||||
name: this.handle,
|
||||
about: this.about,
|
||||
picture: this.picture
|
||||
})
|
||||
},
|
||||
privateKey() {
|
||||
@ -285,15 +272,15 @@ export default {
|
||||
this.$q.notify({type: 'negative', message: 'FAILED'})
|
||||
})
|
||||
},
|
||||
relayAdd() {
|
||||
this.$store.dispatch('relayPush', this.relaya)
|
||||
this.relaya = ''
|
||||
addRelay() {
|
||||
this.$store.commit('addRelay', this.addingRelay)
|
||||
this.addingRelay = ''
|
||||
},
|
||||
relayRem() {
|
||||
this.$store.dispatch('relayRemove', this.relayr)
|
||||
this.relayr = ''
|
||||
removeRelay() {
|
||||
this.$store.commit('removeRelat', this.removingRelay)
|
||||
this.removingRelay = ''
|
||||
},
|
||||
deletels() {
|
||||
hardReset() {
|
||||
this.$q.localStorage.clear()
|
||||
window.location.reload()
|
||||
}
|
||||
|
@ -4,29 +4,24 @@ const routes = [
|
||||
component: () => import('layouts/MainLayout.vue'),
|
||||
children: [
|
||||
{path: '', component: () => import('pages/Home.vue'), name: 'home'},
|
||||
{
|
||||
path: '/messages',
|
||||
component: () => import('pages/Messages.vue'),
|
||||
name: 'messages'
|
||||
},
|
||||
{path: '/chat/:pubkey', component: () => import('pages/Chat.vue')},
|
||||
{
|
||||
path: '/user/:pubkey',
|
||||
component: () => import('pages/Profile.vue')
|
||||
},
|
||||
{
|
||||
path: '/notifications',
|
||||
component: () => import('pages/Notifications.vue')
|
||||
},
|
||||
{
|
||||
path: '/settings',
|
||||
component: () => import('pages/Settings.vue'),
|
||||
name: 'settings'
|
||||
},
|
||||
{
|
||||
path: '/help',
|
||||
component: () => import('pages/Help.vue'),
|
||||
name: 'help'
|
||||
path: '/messages',
|
||||
component: () => import('pages/Messages.vue'),
|
||||
name: 'messages'
|
||||
},
|
||||
{path: '/messages/:pubkey', component: () => import('pages/Chat.vue')},
|
||||
{
|
||||
path: '/notifications',
|
||||
component: () => import('pages/Notifications.vue')
|
||||
},
|
||||
{
|
||||
path: '/:pubkey',
|
||||
component: () => import('pages/Profile.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -1,17 +1,30 @@
|
||||
import {getEventHash} from 'nostr-tools'
|
||||
import {encrypt, decrypt} from 'nostr-tools/nip04'
|
||||
import {LocalStorage, Notify} from 'quasar'
|
||||
import 'md-gum-polyfill'
|
||||
|
||||
import {pool} from '../global'
|
||||
|
||||
export function launch(store) {
|
||||
pool.setPrivateKey(store.state.myProfile.privkey)
|
||||
if (!!store.state.keys.pub) {
|
||||
store.commit('setKey') // passing no arguments will cause a new seed to be generated
|
||||
}
|
||||
|
||||
store.state.myProfile.relays.forEach(relay => {
|
||||
pool.addRelay(relay)
|
||||
})
|
||||
// now we already have a key
|
||||
if (!!store.state.keys.priv) {
|
||||
pool.setPrivateKey(store.state.keys.priv)
|
||||
}
|
||||
|
||||
// add default relays
|
||||
if (Object.keys(store.state.relays).length === 0) {
|
||||
store.commit('addRelay', 'wss://freedom-relay.herokuapp.com/ws')
|
||||
store.commit('addRelay', 'wss://relayer.fiatjaf.com')
|
||||
store.commit('addRelay', 'wss://nostr-relay.freeberty.net')
|
||||
}
|
||||
|
||||
// setup pool
|
||||
for (let url in store.state.relays) {
|
||||
pool.addRelay(url)
|
||||
}
|
||||
pool.onNotice((notice, relay) => {
|
||||
Notify.create({
|
||||
message: `Relay ${relay.url} says: ${notice}`,
|
||||
@ -28,153 +41,65 @@ export function restartHomeFeed(store) {
|
||||
homeSubscription = homeSubscription.sub({
|
||||
filter: [
|
||||
{
|
||||
authors: Object.keys(store.state.theirProfile).length
|
||||
? Object.keys(store.state.theirProfile)
|
||||
: null
|
||||
authors: store.state.following.length ? store.state.following : null
|
||||
},
|
||||
{
|
||||
author: store.state.myProfile.pubkey
|
||||
author: store.state.keys.pub
|
||||
},
|
||||
{
|
||||
'#p': store.state.myProfile.pubkey
|
||||
'#p': store.state.keys.pub
|
||||
}
|
||||
],
|
||||
cb: (event, relay) => {
|
||||
switch (event.kind) {
|
||||
case 0:
|
||||
store.commit('addKind0', event)
|
||||
try {
|
||||
event.metadata = JSON.parse(event.content)
|
||||
} catch (err) {}
|
||||
break
|
||||
|
||||
case 1:
|
||||
for (let i = 0; i < store.state.kind1.length; i++) {
|
||||
if (
|
||||
(store.state.kind1[i].loading === true ||
|
||||
store.state.kind1[i].retry === true) &&
|
||||
store.state.kind1[i].id === event.id
|
||||
) {
|
||||
event.retry = false
|
||||
event.loading = false
|
||||
store.commit('replaceKind1', {index: i, event})
|
||||
return
|
||||
} else if (store.state.kind1[i].id === event.id) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
store.commit('addKind1', event)
|
||||
break
|
||||
|
||||
case 4:
|
||||
// a direct encrypted message
|
||||
if (
|
||||
event.tags.find(
|
||||
tag => tag[0] === 'p' && tag[1] === store.state.myProfile.pubkey
|
||||
([tag, value]) => tag === 'p' && value === store.state.keys.pub
|
||||
)
|
||||
) {
|
||||
// it is addressed to us
|
||||
let lsKey = `messages.${event.pubkey}`
|
||||
var messages = LocalStorage.getItem(lsKey) || []
|
||||
|
||||
if (messages.find(({id}) => id === event.id)) {
|
||||
// we already have this one, discard
|
||||
return
|
||||
}
|
||||
|
||||
// decrypt it
|
||||
let [ciphertext, iv] = event.content.split('?iv=')
|
||||
let text = decrypt(
|
||||
store.state.myProfile.privkey,
|
||||
event.plaintext = decrypt(
|
||||
store.state.keys.priv,
|
||||
event.pubkey,
|
||||
ciphertext,
|
||||
iv
|
||||
)
|
||||
|
||||
// store it locally push
|
||||
messages.push({
|
||||
text,
|
||||
from: event.pubkey,
|
||||
id: event.id,
|
||||
created_at: event.created_at,
|
||||
tags: event.tags,
|
||||
loading: false,
|
||||
retry: false
|
||||
})
|
||||
|
||||
LocalStorage.set(lsKey, messages)
|
||||
|
||||
// a hack to update the UI
|
||||
store.commit('chatUpdated')
|
||||
} else if (
|
||||
event.pubkey === store.state.myProfile.pubkey &&
|
||||
event.tags[0][1] in store.state.theirProfile
|
||||
) {
|
||||
} else if (event.pubkey === store.state.keys.pub) {
|
||||
// it is coming from us
|
||||
let p = event.tags.find(tag => tag[0] === 'p')
|
||||
let lsKey = `messages.${p[1]}`
|
||||
var messagesS = LocalStorage.getItem(lsKey)
|
||||
|
||||
if (messagesS.length > 0) {
|
||||
for (var i = 0; i < messagesS.length; i++) {
|
||||
if (
|
||||
messagesS[i].id === event.id &&
|
||||
messagesS[i].loading === true
|
||||
) {
|
||||
messagesS[i].loading = false
|
||||
LocalStorage.set(lsKey, messagesS)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (messagesS.find(({id}) => id === event.id)) {
|
||||
// we already have this one, discard
|
||||
return
|
||||
}
|
||||
|
||||
// decrypt it
|
||||
let [ciphertext, iv] = event.content.split('?iv=')
|
||||
let text = decrypt(
|
||||
store.state.myProfile.privkey,
|
||||
p[1],
|
||||
ciphertext,
|
||||
iv
|
||||
)
|
||||
messagesS.push({
|
||||
text,
|
||||
from: event.pubkey,
|
||||
id: event.id,
|
||||
created_at: event.created_at,
|
||||
tags: event.tags,
|
||||
loading: false,
|
||||
retry: false
|
||||
})
|
||||
LocalStorage.set(lsKey, messagesS)
|
||||
}
|
||||
let [_, target] = event.tags.find(([tag]) => tag === 'p')
|
||||
// decrypt it
|
||||
let [ciphertext, iv] = event.content.split('?iv=')
|
||||
event.plaintext = decrypt(
|
||||
store.state.keys.priv,
|
||||
target,
|
||||
ciphertext,
|
||||
iv
|
||||
)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
store.commit('addEvent', event)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function relayPush(store, url) {
|
||||
store.commit('relayPush', url)
|
||||
pool.addRelay(url, {
|
||||
read: true,
|
||||
write: true
|
||||
})
|
||||
}
|
||||
|
||||
export async function relayRemove(store, url) {
|
||||
store.commit('relaySplice', url)
|
||||
pool.removeRelay(url)
|
||||
}
|
||||
|
||||
export async function sendPost(store, {message, tags = [], kind = 1}) {
|
||||
if (message.length === 0) return
|
||||
|
||||
let event = {
|
||||
pubkey: store.state.myProfile.pubkey,
|
||||
pubkey: store.state.keys.pub,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
kind,
|
||||
tags,
|
||||
@ -184,164 +109,41 @@ export async function sendPost(store, {message, tags = [], kind = 1}) {
|
||||
event.id = await getEventHash(event)
|
||||
pool.publish(event)
|
||||
|
||||
store.commit('addKind1', {
|
||||
...event,
|
||||
loading: true,
|
||||
retry: false
|
||||
})
|
||||
store.commit('addEvent', event)
|
||||
}
|
||||
|
||||
export function postAgain(store, event) {
|
||||
for (let i = 0; i < store.state.kind1.length; i++) {
|
||||
if (store.state.kind1[i].id === event.id) {
|
||||
store.commit('replaceKind1', {
|
||||
index: i,
|
||||
event: {
|
||||
...event,
|
||||
loading: true,
|
||||
retry: false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
pool.publish(event)
|
||||
}
|
||||
|
||||
export async function saveMeta(store, {image, handle, about}) {
|
||||
store.commit('setProfile', {
|
||||
...store.state.myProfile,
|
||||
picture: image,
|
||||
name: handle,
|
||||
about
|
||||
})
|
||||
export async function setMetadata(store, metadata) {
|
||||
store.commit('setProfile', metadata)
|
||||
|
||||
var event = {
|
||||
pubkey: store.state.myProfile.pubkey,
|
||||
pubkey: store.state.keys.pub,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
kind: 0,
|
||||
tags: [],
|
||||
content: JSON.stringify({
|
||||
name: store.state.myProfile.name,
|
||||
about: store.state.myProfile.about,
|
||||
picture: store.state.myProfile.picture
|
||||
})
|
||||
content: JSON.stringify(metadata)
|
||||
}
|
||||
|
||||
event.id = await getEventHash(event)
|
||||
pool.publish(event)
|
||||
}
|
||||
|
||||
export function deletePost(store, postId) {
|
||||
store.commit('deleteKind1', postId)
|
||||
}
|
||||
|
||||
export function startFollowing(store, key) {
|
||||
if (key in store.state.theirProfile) {
|
||||
Notify.create({
|
||||
message: 'Already following',
|
||||
color: 'pink'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (!key.match(/^[0-9a-fA-F]{64}$/)) {
|
||||
Notify.create({
|
||||
message:
|
||||
'Invalid public key. Must be 32 bytes hex-encoded (64 characters).',
|
||||
color: 'pink'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
LocalStorage.set(`messages.${key}`, [])
|
||||
store.commit('startFollowing', key)
|
||||
store.dispatch('restartHomeFeed')
|
||||
}
|
||||
|
||||
export async function stopFollowing(store, key) {
|
||||
if (!(key in store.state.theirProfile)) {
|
||||
Notify.create({
|
||||
message: 'No such user',
|
||||
color: 'pink'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
store.commit('stopFollowing', key)
|
||||
store.dispatch('restartHomeFeed')
|
||||
}
|
||||
|
||||
export function finalGenerate(store, {keystoreoption, publickey, privatekey}) {
|
||||
var profile = {
|
||||
pubkey: publickey,
|
||||
privkey: privatekey,
|
||||
relays: [
|
||||
'wss://nostr-relay.herokuapp.com/ws',
|
||||
'wss://nostr-relay.bigsun.xyz/ws',
|
||||
'wss://freedom-relay.herokuapp.com/ws'
|
||||
// 'wss://relay.nostr.org',
|
||||
// 'wss://nodestr-relay.dolu.dev/ws'
|
||||
],
|
||||
avatar: null,
|
||||
handle: null,
|
||||
about: null
|
||||
}
|
||||
|
||||
if (keystoreoption === 'external') {
|
||||
profile.privkey = null
|
||||
}
|
||||
|
||||
store.commit('setProfile', profile)
|
||||
LocalStorage.set('theirProfile', {})
|
||||
LocalStorage.set('kind1', [])
|
||||
|
||||
store.dispatch('launch')
|
||||
}
|
||||
|
||||
export async function sendChatMessage(store, {pubkey, text}) {
|
||||
export async function sendChatMessage(store, {pubkey, text, replyTo}) {
|
||||
if (text.length === 0) return
|
||||
|
||||
let [ciphertext, iv] = encrypt(store.state.myProfile.privkey, pubkey, text)
|
||||
let [ciphertext, iv] = encrypt(store.state.keys.priv, pubkey, text)
|
||||
|
||||
// make event
|
||||
let event = {
|
||||
pubkey: store.state.myProfile.pubkey,
|
||||
pubkey: store.state.keys.pub,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
kind: 4,
|
||||
tags: [['p', pubkey]],
|
||||
content: ciphertext + '?iv=' + iv
|
||||
}
|
||||
|
||||
let lsKey = `messages.${pubkey}`
|
||||
var messages = LocalStorage.getItem(lsKey) || []
|
||||
|
||||
if (messages.length > 0) {
|
||||
event.tags.push(['e', messages[messages.length - 1].id])
|
||||
if (replyTo) {
|
||||
event.tags.push(['e', replyTo])
|
||||
}
|
||||
event.id = await getEventHash(event)
|
||||
|
||||
let message = {
|
||||
text,
|
||||
from: store.state.myProfile.pubkey,
|
||||
id: event.id,
|
||||
created_at: event.created_at,
|
||||
tags: event.tags,
|
||||
loading: true,
|
||||
failed: false
|
||||
}
|
||||
|
||||
messages.push(message)
|
||||
LocalStorage.set(lsKey, messages)
|
||||
pool.publish(event)
|
||||
}
|
||||
|
||||
export function deleteChatMessage(store, {pubkey, id}) {
|
||||
let lsKey = `messages.${pubkey}`
|
||||
var messages = LocalStorage.getItem(lsKey) || []
|
||||
|
||||
let index = messages.findIndex(message => message.id === id)
|
||||
if (index === -1) return
|
||||
|
||||
messages.splice(index, 1)
|
||||
LocalStorage.set(lsKey, messages)
|
||||
}
|
||||
|
@ -1,29 +1,21 @@
|
||||
import Identicon from 'identicon.js'
|
||||
|
||||
export function disabled(state) {
|
||||
return !state.myProfile
|
||||
return !state.keys.pub
|
||||
}
|
||||
|
||||
export function handle(state, pubkey) {
|
||||
export function displayName(state) {
|
||||
return pubkey => {
|
||||
let profile = state.theirProfile[pubkey]
|
||||
if (profile && profile.name) return profile.name
|
||||
|
||||
let kind0 = state.kind0[pubkey]
|
||||
if (kind0 && kind0.name) return profile.name
|
||||
|
||||
return pubkey.slice(0, 20) + '...'
|
||||
let {metadata = {}} = state.events.kind0[pubkey]
|
||||
if (metadata.name) return metadata.name
|
||||
return pubkey.slice(0, 3) + '...' + pubkey.slice(-4)
|
||||
}
|
||||
}
|
||||
|
||||
export function avatar(state) {
|
||||
return pubkey => {
|
||||
let profile = state.theirProfile[pubkey]
|
||||
if (profile && profile.picture) return profile.picture
|
||||
|
||||
let kind0 = state.kind0[pubkey]
|
||||
if (kind0 && kind0.picture) return profile.picture
|
||||
|
||||
let {metadata = {}} = state.events.kind0[pubkey]
|
||||
if (metadata.picture) return metadata.picture
|
||||
let data = new Identicon(pubkey, 40).toString()
|
||||
return 'data:image/png;base64,' + data
|
||||
}
|
||||
|
@ -1,61 +1,71 @@
|
||||
export function setProfile(state, profile) {
|
||||
state.myProfile = profile
|
||||
import {LocalStorage} from 'quasar'
|
||||
import {getPublicKey} from 'nostr-tools'
|
||||
import bip32 from 'bip32'
|
||||
import * as bip39 from 'bip39'
|
||||
|
||||
export function setKey(state, {seed, priv, pub} = {}) {
|
||||
if (!seed && !priv && !pub) {
|
||||
// generate
|
||||
let randomBytes = crypto.randomBytes(16)
|
||||
let mnemonic = bip39.entropyToMnemonic(randomBytes.toString('hex'))
|
||||
seed = bip39.mnemonicToSeedSync(mnemonic)
|
||||
}
|
||||
|
||||
if (seed) {
|
||||
let root = bip32.fromSeed(seed)
|
||||
priv = root.privateKey.toString('hex')
|
||||
}
|
||||
|
||||
if (priv) {
|
||||
pub = getPublicKey(priv)
|
||||
}
|
||||
|
||||
state.keys = {seed, priv, pub}
|
||||
}
|
||||
|
||||
export function relayPush(state, url) {
|
||||
state.myProfile.relays.push(url)
|
||||
export function setMetadata(state, {name, picture, about}) {
|
||||
state.me = {name, picture, about}
|
||||
}
|
||||
|
||||
export function relaySplice(state, url) {
|
||||
let index = state.myProfile.relays.indexOf(url)
|
||||
if (index === -1) return
|
||||
state.myProfile.relays.splice(index, 1)
|
||||
}
|
||||
|
||||
export function startFollowing(state, key) {
|
||||
// use metadata from kind0 or leave everything blank
|
||||
state.theirProfile = {
|
||||
[key]: state.kind0[key] || {name: null, about: null, picture: null},
|
||||
...state.theirProfile
|
||||
export function addRelay(state, url) {
|
||||
state.relays[url] = {
|
||||
read: true,
|
||||
write: true
|
||||
}
|
||||
}
|
||||
|
||||
export function stopFollowing(state, key) {
|
||||
delete state.theirProfile[key]
|
||||
export function removeRelay(state, url) {
|
||||
delete state.relays[url]
|
||||
}
|
||||
|
||||
export function addKind1(state, event) {
|
||||
state.kind1.unshift(event)
|
||||
}
|
||||
|
||||
export function replaceKind1(state, {index, event}) {
|
||||
state.kind1 = [
|
||||
...state.kind1.slice(0, index),
|
||||
event,
|
||||
...state.kind1.slice(index + 1)
|
||||
]
|
||||
}
|
||||
export function deleteKind1(state, id) {
|
||||
console.log(state.kind1)
|
||||
console.log(id)
|
||||
let index = state.kind1.findIndex(event => event.id === id)
|
||||
console.log(index)
|
||||
if (index !== -1) state.kind1.splice(index, 1)
|
||||
}
|
||||
|
||||
export function addKind0(state, event) {
|
||||
// increment theirProfile with this or store it temporarily
|
||||
try {
|
||||
let {name, about, picture} = JSON.parse(event.content)
|
||||
|
||||
if (event.pubkey in state.theirProfile) {
|
||||
state.theirProfile[event.pubkey] = {name, about, picture}
|
||||
return
|
||||
}
|
||||
state.kind0[event.pubkey] = {name, about, picture}
|
||||
} catch (err) {
|
||||
export function follow(state, key) {
|
||||
if (state.following.includes(key)) {
|
||||
return
|
||||
}
|
||||
state.following.push(key)
|
||||
}
|
||||
|
||||
export function unfollow(state, key) {
|
||||
delete state.following[key]
|
||||
}
|
||||
|
||||
export function addEvent(state, event) {
|
||||
switch (event.kind) {
|
||||
case 0:
|
||||
state.events.kind0[event.pubkey] = event
|
||||
break
|
||||
case 1:
|
||||
if (state.events.kind1.find(e => e.id === event.id)) return
|
||||
state.events.kind1.push(event)
|
||||
break
|
||||
case 4:
|
||||
let peerTag = event.tags.find(([tag]) => tag === 'p')
|
||||
if (!peerTag) return
|
||||
let peer = event.pubkey === state.key.pub ? peerTag[1] : event.pubkey
|
||||
if (state.events.kind4[peer].find(e => e.id === event.id)) return
|
||||
state.events.kind4[peer].push(event)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
export function chatUpdated(state) {
|
||||
|
@ -2,11 +2,15 @@ import {LocalStorage} from 'quasar'
|
||||
|
||||
export default function () {
|
||||
return {
|
||||
myProfile: LocalStorage.getItem('myProfile'),
|
||||
theirProfile: LocalStorage.getItem('theirProfile') || {},
|
||||
|
||||
kind0: {}, // temporary, will be merged with theirProfile or erased at the end
|
||||
kind1: LocalStorage.getItem('kind1') || [],
|
||||
me: LocalStorage.getItem('me') || {}, // { name, picture, about, ... }
|
||||
keys: LocalStorage.getItem('keys') || {}, // { seed, priv, pub }
|
||||
relays: LocalStorage.getItem('relays') || {}, // { [url]: {} }
|
||||
following: LocalStorage.getItem('following') || [], // [ pubkeys... ]
|
||||
events: {
|
||||
kind0: LocalStorage.getItem('events.kind0') || {}, // { [pubkey]: event }
|
||||
kind1: LocalStorage.getItem('events.kind1') || [], // [ events... ]
|
||||
kind4: LocalStorage.getItem('events.kind4') || {} // { [pubkey]: [events...] }
|
||||
},
|
||||
|
||||
chatUpdated: 1 // hack
|
||||
}
|
||||
|
@ -4,20 +4,19 @@ export default function (store) {
|
||||
store.subscribe(({type, payload}, state) => {
|
||||
switch (type) {
|
||||
case 'setProfile':
|
||||
case 'relayPush':
|
||||
case 'relaySplice':
|
||||
console.log('storing', state.myProfile)
|
||||
LocalStorage.set('myProfile', state.myProfile)
|
||||
LocalStorage.set('me', state.me)
|
||||
case 'addRelay':
|
||||
case 'removeRelay':
|
||||
LocalStorage.set('relays', state.relays)
|
||||
break
|
||||
case 'startFollowing':
|
||||
case 'stopFollowing':
|
||||
case 'addKind0':
|
||||
LocalStorage.set('theirProfile', state.theirProfile)
|
||||
break
|
||||
case 'addKind1':
|
||||
case 'replaceKind1':
|
||||
case 'deleteKind1':
|
||||
LocalStorage.set('kind1', state.kind1)
|
||||
case 'follow':
|
||||
case 'unfollow':
|
||||
LocalStorage.set('following', state.following)
|
||||
case 'addEvent':
|
||||
LocalStorage.set(
|
||||
`events.${payload.kind}`,
|
||||
state.events[`kind${payload.kind}`]
|
||||
)
|
||||
break
|
||||
}
|
||||
})
|
||||
|
@ -11,7 +11,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
toProfile(pubkey) {
|
||||
this.$router.push('/user/' + pubkey)
|
||||
this.$router.push('/' + pubkey)
|
||||
},
|
||||
|
||||
niceDate(value) {
|
||||
|
81
yarn.lock
81
yarn.lock
@ -992,6 +992,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.3.tgz#0300943770e04231041a51bd39f0439b5c7ab4f0"
|
||||
integrity sha512-nkalE/f1RvRGChwBnEIoBfSEYOXnCRdleKuv6+lePbMDrMZXeDQnqak5XDOeBgrPPyPfAdcCu/B5z+v3VhplGg==
|
||||
|
||||
"@noble/secp256k1@^1.3.0":
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.3.0.tgz#426880cf0355b24d81c129af1ec31dfa6eee8b9c"
|
||||
integrity sha512-wuFthUc6Ul4xflhY5u1+p1bDILPzVEisekxt5M+iLg4R+gG6+k2jnRR19sC9fMtzlsN5sKloBwprziDS0XlmyQ==
|
||||
|
||||
"@nodelib/fs.scandir@2.1.5":
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
|
||||
@ -1270,6 +1275,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.12.tgz#ac7fb693ac587ee182c3780c26eb65546a1a3c10"
|
||||
integrity sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==
|
||||
|
||||
"@types/node@10.12.18":
|
||||
version "10.12.18"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.18.tgz#1d3ca764718915584fcd9f6344621b7672665c67"
|
||||
integrity sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==
|
||||
|
||||
"@types/node@11.11.6":
|
||||
version "11.11.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-11.11.6.tgz#df929d1bb2eee5afdda598a41930fe50b43eaa6a"
|
||||
@ -1850,6 +1860,13 @@ balanced-match@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
||||
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
|
||||
|
||||
base-x@^3.0.2:
|
||||
version "3.0.9"
|
||||
resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320"
|
||||
integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==
|
||||
dependencies:
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
base64-js@^1.3.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
||||
@ -1870,6 +1887,18 @@ binary-extensions@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
|
||||
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
|
||||
|
||||
bip32@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/bip32/-/bip32-3.0.1.tgz#1d1121469cce6e910e0ec3a5a1990dd62687e2a3"
|
||||
integrity sha512-Uhpp9aEx3iyiO7CpbNGFxv9WcMIVdGoHG04doQ5Ln0u60uwDah7jUSc3QMV/fSZGm/Oo01/OeAmYevXV+Gz5jQ==
|
||||
dependencies:
|
||||
"@types/node" "10.12.18"
|
||||
bs58check "^2.1.1"
|
||||
create-hash "^1.2.0"
|
||||
create-hmac "^1.1.7"
|
||||
typeforce "^1.11.5"
|
||||
wif "^2.0.6"
|
||||
|
||||
bip39@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.0.4.tgz#5b11fed966840b5e1b8539f0f54ab6392969b2a0"
|
||||
@ -1948,6 +1977,22 @@ browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4
|
||||
node-releases "^2.0.1"
|
||||
picocolors "^1.0.0"
|
||||
|
||||
bs58@^4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a"
|
||||
integrity sha1-vhYedsNU9veIrkBx9j806MTwpCo=
|
||||
dependencies:
|
||||
base-x "^3.0.2"
|
||||
|
||||
bs58check@<3.0.0, bs58check@^2.1.1:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc"
|
||||
integrity sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==
|
||||
dependencies:
|
||||
bs58 "^4.0.0"
|
||||
create-hash "^1.1.0"
|
||||
safe-buffer "^5.1.2"
|
||||
|
||||
buffer-crc32@^0.2.1, buffer-crc32@^0.2.13:
|
||||
version "0.2.13"
|
||||
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
||||
@ -2344,7 +2389,7 @@ crc32-stream@^4.0.2:
|
||||
crc-32 "^1.2.0"
|
||||
readable-stream "^3.4.0"
|
||||
|
||||
create-hash@^1.1.0, create-hash@^1.1.2:
|
||||
create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196"
|
||||
integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==
|
||||
@ -2355,7 +2400,7 @@ create-hash@^1.1.0, create-hash@^1.1.2:
|
||||
ripemd160 "^2.0.1"
|
||||
sha.js "^2.4.0"
|
||||
|
||||
create-hmac@^1.1.4:
|
||||
create-hmac@^1.1.4, create-hmac@^1.1.7:
|
||||
version "1.1.7"
|
||||
resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff"
|
||||
integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==
|
||||
@ -4168,11 +4213,6 @@ make-dir@^3.0.2, make-dir@^3.1.0:
|
||||
dependencies:
|
||||
semver "^6.0.0"
|
||||
|
||||
md-gum-polyfill@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/md-gum-polyfill/-/md-gum-polyfill-1.0.0.tgz#829a512d25ed0318c0c49a961048b505f670a2db"
|
||||
integrity sha1-gppRLSXtAxjAxJqWEEi1BfZwots=
|
||||
|
||||
md5.js@^1.3.4:
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
|
||||
@ -4373,11 +4413,6 @@ no-case@^3.0.4:
|
||||
lower-case "^2.0.2"
|
||||
tslib "^2.0.3"
|
||||
|
||||
noble-secp256k1@^1.1.1:
|
||||
version "1.2.14"
|
||||
resolved "https://registry.yarnpkg.com/noble-secp256k1/-/noble-secp256k1-1.2.14.tgz#39429c941d51211ca40161569cee03e61d72599e"
|
||||
integrity sha512-GSCXyoZBUaaPwVWdYncMEmzlSUjF9J/YeEHpklYJCyg8wPuJP3NzDx0BkiwArzINkdX2HJHvUJhL6vVWPOQQcg==
|
||||
|
||||
node-forge@^0.10.0:
|
||||
version "0.10.0"
|
||||
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
|
||||
@ -4415,14 +4450,14 @@ normalize-url@^6.0.1:
|
||||
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a"
|
||||
integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==
|
||||
|
||||
nostr-tools@^0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-0.5.0.tgz#cb641ff21d035169590cc7185eca6aed6a9a101c"
|
||||
integrity sha512-JxLOP8psDx3Ye08ar8pbY7u08zJUIo9sS3CqpPqfvgV86Blv9oImKP/GfODP0H3MbfOKk4zdYQt6+qZie0k8uA==
|
||||
nostr-tools@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-0.6.0.tgz#03f6ac3bd72ed7ead9b2472b655228ebc011bb92"
|
||||
integrity sha512-XBi+tuRQo9prL8n1Wxu3/O1NYGc4tQaIExaLfebj3eg0um8xDnKzgpmbRL+dTXqaZmWKnTtTAbZvea2uluXIPw==
|
||||
dependencies:
|
||||
"@noble/secp256k1" "^1.3.0"
|
||||
buffer "^6.0.3"
|
||||
dns-packet "^5.2.4"
|
||||
noble-secp256k1 "^1.1.1"
|
||||
websocket-polyfill "^0.0.3"
|
||||
|
||||
npm-run-path@^4.0.1:
|
||||
@ -5920,6 +5955,11 @@ typedarray-to-buffer@^3.1.5:
|
||||
dependencies:
|
||||
is-typedarray "^1.0.0"
|
||||
|
||||
typeforce@^1.11.5:
|
||||
version "1.18.0"
|
||||
resolved "https://registry.yarnpkg.com/typeforce/-/typeforce-1.18.0.tgz#d7416a2c5845e085034d70fcc5b6cc4a90edbfdc"
|
||||
integrity sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==
|
||||
|
||||
typescript@4.4.2:
|
||||
version "4.4.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.2.tgz#6d618640d430e3569a1dfb44f7d7e600ced3ee86"
|
||||
@ -6268,6 +6308,13 @@ which@^2.0.1:
|
||||
dependencies:
|
||||
isexe "^2.0.0"
|
||||
|
||||
wif@^2.0.6:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/wif/-/wif-2.0.6.tgz#08d3f52056c66679299726fade0d432ae74b4704"
|
||||
integrity sha1-CNP1IFbGZnkplyb63g1DKudLRwQ=
|
||||
dependencies:
|
||||
bs58check "<3.0.0"
|
||||
|
||||
wildcard@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec"
|
||||
|
Loading…
Reference in New Issue
Block a user