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": {
|
"dependencies": {
|
||||||
"@quasar/extras": "^1.0.0",
|
"@quasar/extras": "^1.0.0",
|
||||||
|
"bip32": "^3.0.1",
|
||||||
"bip39": "^3.0.4",
|
"bip39": "^3.0.4",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"crypto": "^1.0.1",
|
"crypto": "^1.0.1",
|
||||||
"identicon.js": "^2.3.3",
|
"identicon.js": "^2.3.3",
|
||||||
"md-gum-polyfill": "^1.0.0",
|
"nostr-tools": "^0.6.0",
|
||||||
"nostr-tools": "^0.5.0",
|
|
||||||
"quasar": "^2.0.0",
|
"quasar": "^2.0.0",
|
||||||
"stream": "^0.0.2",
|
"stream": "^0.0.2",
|
||||||
"vuex": "^4.0.1"
|
"vuex": "^4.0.1"
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
/* eslint-env node */
|
/* eslint-env node */
|
||||||
const ESLintPlugin = require('eslint-webpack-plugin')
|
const ESLintPlugin = require('eslint-webpack-plugin')
|
||||||
const { configure } = require('quasar/wrappers');
|
const {configure} = require('quasar/wrappers')
|
||||||
|
|
||||||
module.exports = configure(function (ctx) {
|
module.exports = configure(function (ctx) {
|
||||||
return {
|
return {
|
||||||
@ -21,13 +21,10 @@ module.exports = configure(function (ctx) {
|
|||||||
// app boot file (/src/boot)
|
// app boot file (/src/boot)
|
||||||
// --> boot files are part of "main.js"
|
// --> boot files are part of "main.js"
|
||||||
// https://quasar.dev/quasar-cli/boot-files
|
// https://quasar.dev/quasar-cli/boot-files
|
||||||
boot: [
|
boot: [],
|
||||||
],
|
|
||||||
|
|
||||||
// https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-css
|
// https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-css
|
||||||
css: [
|
css: ['app.scss'],
|
||||||
'app.scss'
|
|
||||||
],
|
|
||||||
|
|
||||||
// https://github.com/quasarframework/quasar/tree/dev/extras
|
// https://github.com/quasarframework/quasar/tree/dev/extras
|
||||||
extras: [
|
extras: [
|
||||||
@ -40,15 +37,15 @@ module.exports = configure(function (ctx) {
|
|||||||
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
|
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
|
||||||
|
|
||||||
'roboto-font', // optional, you are not bound to it
|
'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
|
// Full list of options: https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-build
|
||||||
build: {
|
build: {
|
||||||
vueRouterMode: 'hash', // available values: 'hash', 'history'
|
vueRouterMode: 'history', // available values: 'hash', 'history'
|
||||||
|
|
||||||
// transpile: false,
|
// transpile: false,
|
||||||
// publicPath: '/',
|
publicPath: '/',
|
||||||
|
|
||||||
// Add dependencies for transpiling with Babel (Array of string/regex)
|
// Add dependencies for transpiling with Babel (Array of string/regex)
|
||||||
// (from node_modules, which are by default not transpiled).
|
// (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
|
// https://quasar.dev/quasar-cli/handling-webpack
|
||||||
// "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain
|
// "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain
|
||||||
chainWebpack (chain) {
|
chainWebpack(chain) {
|
||||||
chain.plugin('eslint-webpack-plugin')
|
chain
|
||||||
.use(ESLintPlugin, [{ extensions: [ 'js', 'vue' ] }])
|
.plugin('eslint-webpack-plugin')
|
||||||
},
|
.use(ESLintPlugin, [{extensions: ['js', 'vue']}])
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Full list of options: https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-devServer
|
// 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,
|
// manualPostHydrationTrigger: true,
|
||||||
|
|
||||||
prodPort: 3000, // The default port that the production server should use
|
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,
|
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) {
|
chainWebpackWebserver(chain) {
|
||||||
chain.plugin('eslint-webpack-plugin')
|
chain
|
||||||
.use(ESLintPlugin, [{ extensions: [ 'js' ] }])
|
.plugin('eslint-webpack-plugin')
|
||||||
|
.use(ESLintPlugin, [{extensions: ['js']}])
|
||||||
},
|
},
|
||||||
|
|
||||||
middlewares: [
|
middlewares: [
|
||||||
@ -134,9 +133,10 @@ module.exports = configure(function (ctx) {
|
|||||||
|
|
||||||
// for the custom service worker ONLY (/src-pwa/custom-service-worker.[js|ts])
|
// for the custom service worker ONLY (/src-pwa/custom-service-worker.[js|ts])
|
||||||
// if using workbox in InjectManifest mode
|
// if using workbox in InjectManifest mode
|
||||||
chainWebpackCustomSW (chain) {
|
chainWebpackCustomSW(chain) {
|
||||||
chain.plugin('eslint-webpack-plugin')
|
chain
|
||||||
.use(ESLintPlugin, [{ extensions: [ 'js' ] }])
|
.plugin('eslint-webpack-plugin')
|
||||||
|
.use(ESLintPlugin, [{extensions: ['js']}])
|
||||||
},
|
},
|
||||||
|
|
||||||
manifest: {
|
manifest: {
|
||||||
@ -193,13 +193,11 @@ module.exports = configure(function (ctx) {
|
|||||||
|
|
||||||
packager: {
|
packager: {
|
||||||
// https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#options
|
// https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#options
|
||||||
|
|
||||||
// OS X / Mac App Store
|
// OS X / Mac App Store
|
||||||
// appBundleId: '',
|
// appBundleId: '',
|
||||||
// appCategoryType: '',
|
// appCategoryType: '',
|
||||||
// osxSign: '',
|
// osxSign: '',
|
||||||
// protocol: 'myapp://path',
|
// protocol: 'myapp://path',
|
||||||
|
|
||||||
// Windows only
|
// Windows only
|
||||||
// win32metadata: { ... }
|
// win32metadata: { ... }
|
||||||
},
|
},
|
||||||
@ -211,16 +209,18 @@ module.exports = configure(function (ctx) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain
|
// "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain
|
||||||
chainWebpackMain (chain) {
|
chainWebpackMain(chain) {
|
||||||
chain.plugin('eslint-webpack-plugin')
|
chain
|
||||||
.use(ESLintPlugin, [{ extensions: [ 'js' ] }])
|
.plugin('eslint-webpack-plugin')
|
||||||
|
.use(ESLintPlugin, [{extensions: ['js']}])
|
||||||
},
|
},
|
||||||
|
|
||||||
// "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain
|
// "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain
|
||||||
chainWebpackPreload (chain) {
|
chainWebpackPreload(chain) {
|
||||||
chain.plugin('eslint-webpack-plugin')
|
chain
|
||||||
.use(ESLintPlugin, [{ extensions: [ 'js' ] }])
|
.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 Publish from '../components/Publish.vue'
|
||||||
import Reply from '../components/Reply.vue'
|
import Reply from '../components/Reply.vue'
|
||||||
import Post from '../components/Post.vue'
|
import Post from '../components/Post.vue'
|
||||||
|
|
||||||
export default ({app}) => {
|
export default ({app}) => {
|
||||||
app.component('Generate', Generate)
|
|
||||||
app.component('Publish', Publish)
|
app.component('Publish', Publish)
|
||||||
app.component('Reply', Reply)
|
app.component('Reply', Reply)
|
||||||
app.component('Post', Post)
|
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="col no-shadow">
|
||||||
<q-card-section class="q-pa-none" @click="dialogReply = true">
|
<q-card-section class="q-pa-none" @click="dialogReply = true">
|
||||||
<q-item-label
|
<q-item-label
|
||||||
>{{ $store.getters.handle(post.pubkey) }}
|
>{{ $store.getters.displayName(post.pubkey) }}
|
||||||
<small style="color: grey">
|
<small style="color: grey">
|
||||||
{{ niceDate(post.created_at * 1000) }}
|
{{ niceDate(post.created_at * 1000) }}
|
||||||
</small>
|
</small>
|
||||||
@ -38,29 +38,6 @@
|
|||||||
@click="dialogReply = true"
|
@click="dialogReply = true"
|
||||||
>
|
>
|
||||||
</q-btn>
|
</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>
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
@ -77,15 +54,6 @@ export default {
|
|||||||
return {
|
return {
|
||||||
dialogReply: false
|
dialogReply: false
|
||||||
}
|
}
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
postAgain(post) {
|
|
||||||
this.$store.dispatch('postAgain', post)
|
|
||||||
},
|
|
||||||
|
|
||||||
deletePost(post) {
|
|
||||||
this.$store.dispatch('deletePost', post.id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -3,17 +3,15 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<q-form style="width: 100%" class="q-gutter-md" @submit="sendPost">
|
<q-form style="width: 100%" class="q-gutter-md" @submit="sendPost">
|
||||||
<q-input
|
<q-input
|
||||||
v-model="publishtext"
|
v-model="text"
|
||||||
style="font-size: 20px"
|
style="font-size: 20px"
|
||||||
label="Say something"
|
label="Say something"
|
||||||
maxlength="280"
|
maxlength="280"
|
||||||
>
|
>
|
||||||
<template #before>
|
<template #before>
|
||||||
<q-btn round @click="toProfile($store.state.myProfile.pubkey)">
|
<q-btn round @click="toProfile($store.state.keys.pub)">
|
||||||
<q-avatar size="42px">
|
<q-avatar size="42px">
|
||||||
<img
|
<img :src="$store.getters.avatar($store.state.keys.pub)" />
|
||||||
:src="$store.getters.avatar($store.state.myProfile.pubkey)"
|
|
||||||
/>
|
|
||||||
</q-avatar>
|
</q-avatar>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</template>
|
</template>
|
||||||
@ -21,7 +19,7 @@
|
|||||||
|
|
||||||
<div class="float-right">
|
<div class="float-right">
|
||||||
<q-btn
|
<q-btn
|
||||||
v-if="publishtext.length < 280"
|
v-if="text.length < 280"
|
||||||
class="float-left q-mr-md"
|
class="float-left q-mr-md"
|
||||||
round
|
round
|
||||||
unelevated
|
unelevated
|
||||||
@ -37,7 +35,7 @@
|
|||||||
rounded
|
rounded
|
||||||
unelevated
|
unelevated
|
||||||
dense
|
dense
|
||||||
@click="publishtext = publishtext + emoji.item"
|
@click="text = text + emoji.item"
|
||||||
>{{ emoji.item }}</q-btn
|
>{{ emoji.item }}</q-btn
|
||||||
>
|
>
|
||||||
<br />
|
<br />
|
||||||
@ -48,7 +46,7 @@
|
|||||||
rounded
|
rounded
|
||||||
unelevated
|
unelevated
|
||||||
dense
|
dense
|
||||||
@click="publishtext = publishtext + emoji.item"
|
@click="text = text + emoji.item"
|
||||||
>{{ emoji.item }}</q-btn
|
>{{ emoji.item }}</q-btn
|
||||||
>
|
>
|
||||||
</q-popup-proxy>
|
</q-popup-proxy>
|
||||||
@ -87,13 +85,13 @@ export default {
|
|||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
publishtext: ''
|
text: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
sendPost() {
|
sendPost() {
|
||||||
this.$store.dispatch('sendPost', {message: this.publishtext})
|
this.$store.dispatch('sendPost', {message: this.text})
|
||||||
this.publishtext = ''
|
this.text = ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
<q-card-section class="col no-shadow q-pb-none">
|
<q-card-section class="col no-shadow q-pb-none">
|
||||||
<q-item-label
|
<q-item-label
|
||||||
>{{ $store.getters.handle(post.pubkey) }}
|
>{{ $store.getters.displayName(post.pubkey) }}
|
||||||
<small style="color: grey">{{ niceDate(post.created_at) }}</small>
|
<small style="color: grey">{{ niceDate(post.created_at) }}</small>
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
{{ post.content }}
|
{{ post.content }}
|
||||||
@ -25,11 +25,11 @@
|
|||||||
<q-form
|
<q-form
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
class="q-gutter-md"
|
class="q-gutter-md"
|
||||||
@submit="sendReply(replytext, [['e', post.id]])"
|
@submit="sendReply(text, [['e', post.id]])"
|
||||||
><q-tooltip> Coming soon </q-tooltip>
|
><q-tooltip> Coming soon </q-tooltip>
|
||||||
<q-input
|
<q-input
|
||||||
disable
|
disable
|
||||||
v-model="replytext"
|
v-model="text"
|
||||||
dense
|
dense
|
||||||
style="font-size: 20px"
|
style="font-size: 20px"
|
||||||
autogrow
|
autogrow
|
||||||
@ -40,7 +40,7 @@
|
|||||||
<div class="float-right">
|
<div class="float-right">
|
||||||
<q-btn
|
<q-btn
|
||||||
disable
|
disable
|
||||||
v-if="replytext.length < 280"
|
v-if="text.length < 280"
|
||||||
class="float-left q-mr-md"
|
class="float-left q-mr-md"
|
||||||
round
|
round
|
||||||
unelevated
|
unelevated
|
||||||
@ -56,7 +56,7 @@
|
|||||||
rounded
|
rounded
|
||||||
unelevated
|
unelevated
|
||||||
dense
|
dense
|
||||||
@click="replytext = replytext + emoji.item"
|
@click="text = text + emoji.item"
|
||||||
>{{ emoji.item }}</q-btn
|
>{{ emoji.item }}</q-btn
|
||||||
>
|
>
|
||||||
<br />
|
<br />
|
||||||
@ -67,7 +67,7 @@
|
|||||||
rounded
|
rounded
|
||||||
unelevated
|
unelevated
|
||||||
dense
|
dense
|
||||||
@click="replytext = replytext + emoji.item"
|
@click="text = text + emoji.item"
|
||||||
>{{ emoji.item }}</q-btn
|
>{{ emoji.item }}</q-btn
|
||||||
>
|
>
|
||||||
</q-popup-proxy>
|
</q-popup-proxy>
|
||||||
@ -109,18 +109,16 @@ export default {
|
|||||||
props: ['post'],
|
props: ['post'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
publishtext: '',
|
text: ''
|
||||||
replytext: ''
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
sendReply() {
|
sendReply() {
|
||||||
console.log(this.post.id)
|
|
||||||
this.$store.dispatch('sendPost', {
|
this.$store.dispatch('sendPost', {
|
||||||
message: this.replytext,
|
message: this.text,
|
||||||
tags: [['e', this.post.id]]
|
tags: [['e', this.post.id]]
|
||||||
})
|
})
|
||||||
this.replytext = ''
|
this.text = ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,6 @@
|
|||||||
<Publish />
|
<Publish />
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
|
|
||||||
<q-dialog v-model="dialogGenerate" position="top">
|
|
||||||
<Generate />
|
|
||||||
</q-dialog>
|
|
||||||
|
|
||||||
<div class="flex-center column">
|
<div class="flex-center column">
|
||||||
<div class="row" style="width: 100%">
|
<div class="row" style="width: 100%">
|
||||||
<div
|
<div
|
||||||
@ -106,22 +102,6 @@
|
|||||||
|
|
||||||
<q-item-section>Settings</q-item-section>
|
<q-item-section>Settings</q-item-section>
|
||||||
</q-item>
|
</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-list>
|
||||||
<q-btn
|
<q-btn
|
||||||
v-if="!$store.getters.disabled"
|
v-if="!$store.getters.disabled"
|
||||||
@ -151,8 +131,8 @@
|
|||||||
color="primary"
|
color="primary"
|
||||||
size="md"
|
size="md"
|
||||||
dense
|
dense
|
||||||
:label="$store.getters.handle($store.state.myProfile.pubkey)"
|
:label="$store.getters.displayName($store.state.keys.pub)"
|
||||||
@click="copyToClip($store.state.myProfile.pubkey)"
|
@click="copyPubKey"
|
||||||
>
|
>
|
||||||
<q-tooltip> Copy public key </q-tooltip></q-btn
|
<q-tooltip> Copy public key </q-tooltip></q-btn
|
||||||
>
|
>
|
||||||
@ -203,13 +183,11 @@
|
|||||||
</q-input>
|
</q-input>
|
||||||
</q-form>
|
</q-form>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section
|
<q-card-section v-if="$store.state.following.length">
|
||||||
v-if="Object.keys($store.state.theirProfile).length"
|
|
||||||
>
|
|
||||||
<h6 class="q-ma-none">Following</h6>
|
<h6 class="q-ma-none">Following</h6>
|
||||||
<q-list>
|
<q-list>
|
||||||
<q-item
|
<q-item
|
||||||
v-for="(_, pubkey) in $store.state.theirProfile"
|
v-for="(_, pubkey) in $store.state.following"
|
||||||
:key="pubkey"
|
:key="pubkey"
|
||||||
v-ripple
|
v-ripple
|
||||||
clickable
|
clickable
|
||||||
@ -222,7 +200,7 @@
|
|||||||
</q-item-section>
|
</q-item-section>
|
||||||
|
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
{{ $store.getters.handle(pubkey) }}
|
{{ $store.getters.displayName(pubkey) }}
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
@ -234,44 +212,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<q-footer bordered style="bottom: 0%; position: fixed" class="bg-white">
|
<q-footer bordered style="bottom: 0%; position: fixed" class="bg-white">
|
||||||
<q-banner
|
<div class="text-center">
|
||||||
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>
|
|
||||||
<q-tabs class="text-primary small-screen-only">
|
<q-tabs class="text-primary small-screen-only">
|
||||||
<q-route-tab style="width: 20%" name="home" icon="home" to="/" />
|
<q-route-tab style="width: 20%" name="home" icon="home" to="/" />
|
||||||
|
|
||||||
@ -287,85 +228,39 @@
|
|||||||
icon="settings"
|
icon="settings"
|
||||||
to="/settings"
|
to="/settings"
|
||||||
/>
|
/>
|
||||||
<q-route-tab style="width: 20%" name="help" icon="help" to="/help" />
|
|
||||||
</q-tabs>
|
</q-tabs>
|
||||||
</center>
|
</div>
|
||||||
</q-footer>
|
</q-footer>
|
||||||
</q-layout>
|
</q-layout>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
let deferredPrompt
|
|
||||||
import {copyToClipboard} from 'quasar'
|
import {copyToClipboard} from 'quasar'
|
||||||
import helpersMixin from '../utils/mixin'
|
import helpersMixin from '../utils/mixin'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'MainLayout',
|
name: 'MainLayout',
|
||||||
mixins: [helpersMixin],
|
mixins: [helpersMixin],
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showInstallBanner: null,
|
|
||||||
dialogGenerate: false,
|
dialogGenerate: false,
|
||||||
dialogPublish: false,
|
dialogPublish: false,
|
||||||
addPubKey: ''
|
addPubKey: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
|
||||||
let value = this.$q.localStorage.getItem('neverShowBanner')
|
|
||||||
if (!value) {
|
|
||||||
window.addEventListener('beforeinstallprompt', e => {
|
|
||||||
e.preventDefault()
|
|
||||||
deferredPrompt = e
|
|
||||||
this.showInstallBanner = true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created: function () {
|
created: function () {
|
||||||
if (this.$store.getters.disabled) {
|
|
||||||
this.$router.push('/help')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$store.dispatch('launch')
|
this.$store.dispatch('launch')
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
installApp() {
|
async copyPubKey() {
|
||||||
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
|
|
||||||
try {
|
try {
|
||||||
this.$q.localStorage.setItem('neverShowBanner', true)
|
await copyToClipboard(this.$store.state.keys.pub)
|
||||||
} catch (e) {
|
this.$q.notify({
|
||||||
console.log(e)
|
message: 'COPIED'
|
||||||
}
|
|
||||||
},
|
|
||||||
copyToClip(text) {
|
|
||||||
copyToClipboard(text)
|
|
||||||
.then(() => {
|
|
||||||
this.$q.notify({
|
|
||||||
message: 'COPIED'
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.catch(() => {
|
} catch (err) {
|
||||||
this.$q.notify({type: 'negative', message: 'FAILED'})
|
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!'})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.addPubKey = ''
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<q-page>
|
<q-page>
|
||||||
<center>
|
<div class="text-center">
|
||||||
<strong class="text-h6 q-pa-lg">Chat</strong>
|
<strong class="text-h6 q-pa-lg">Chat</strong>
|
||||||
</center>
|
</div>
|
||||||
<q-btn
|
<q-btn
|
||||||
v-go-back.single
|
v-go-back.single
|
||||||
flat
|
flat
|
||||||
@ -21,31 +21,28 @@
|
|||||||
>
|
>
|
||||||
<q-scroll-area
|
<q-scroll-area
|
||||||
ref="chatScroll"
|
ref="chatScroll"
|
||||||
:thumb-style="thumbStyle"
|
:thumb-style="{
|
||||||
|
left: '102%',
|
||||||
|
backgroundColor: '#26A69A',
|
||||||
|
width: '10px',
|
||||||
|
opacity: 0.35
|
||||||
|
}"
|
||||||
style="height: 100%; max-width: 100%"
|
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
|
<q-chat-message
|
||||||
:text="[
|
:text="[event.plaintext]"
|
||||||
message.text +
|
:name="$store.getters.displayName(event.pubkey)"
|
||||||
(message.loading
|
:avatar="$store.getters.avatar(event.pubkey)"
|
||||||
? '<small>sending...</small>'
|
:sent="event.pubkey === $store.state.keys.pub"
|
||||||
: message.failed
|
:stamp="niceDate(new Date(event.created_at * 1000))"
|
||||||
? '<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))"
|
|
||||||
:bg-color="
|
:bg-color="
|
||||||
message.from === $store.state.myProfile.pubkey
|
event.pubkey === $store.state.keys.pub ? 'primary' : 'tertiary'
|
||||||
? 'primary'
|
|
||||||
: 'tertiary'
|
|
||||||
"
|
"
|
||||||
@click="ev => clickMessageAction(ev, message.id, message.text)"
|
|
||||||
>
|
>
|
||||||
</q-chat-message>
|
</q-chat-message>
|
||||||
</div>
|
</div>
|
||||||
@ -56,7 +53,7 @@
|
|||||||
<q-form
|
<q-form
|
||||||
class="q-gutter-md"
|
class="q-gutter-md"
|
||||||
@submit="submitMessage"
|
@submit="submitMessage"
|
||||||
@reset="resetMessage"
|
@reset="this.text = ''"
|
||||||
>
|
>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-8">
|
<div class="col-8">
|
||||||
@ -139,28 +136,7 @@ export default {
|
|||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
text: '',
|
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
|
|
||||||
}
|
|
||||||
) || []
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -170,23 +146,7 @@ export default {
|
|||||||
const duration = 350
|
const duration = 350
|
||||||
scrollArea.setScrollPosition(scrollTarget.scrollHeight, duration)
|
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() {
|
async submitMessage() {
|
||||||
await this.$store.dispatch('sendChatMessage', {
|
await this.$store.dispatch('sendChatMessage', {
|
||||||
pubkey: this.$route.params.pubkey,
|
pubkey: this.$route.params.pubkey,
|
||||||
@ -194,39 +154,7 @@ export default {
|
|||||||
})
|
})
|
||||||
|
|
||||||
this.text = ''
|
this.text = ''
|
||||||
this.$store.commit('chatUpdated')
|
|
||||||
this.scroll()
|
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>
|
<template>
|
||||||
<q-page>
|
<q-page>
|
||||||
<center>
|
<div class="text-center">
|
||||||
<strong class="text-h6 q-pa-lg">Encrypted Messages</strong>
|
<strong class="text-h6 q-pa-lg">Encrypted Messages</strong>
|
||||||
</center>
|
</div>
|
||||||
<q-btn
|
<q-btn
|
||||||
v-go-back.single
|
v-go-back.single
|
||||||
flat
|
flat
|
||||||
@ -20,7 +20,7 @@
|
|||||||
<div class="q-mx-auto q-px-md">
|
<div class="q-mx-auto q-px-md">
|
||||||
<q-list>
|
<q-list>
|
||||||
<q-item
|
<q-item
|
||||||
v-for="(_, followedKey) in $store.state.theirProfile"
|
v-for="(_, followedKey) in $store.state.following"
|
||||||
:key="followedKey"
|
:key="followedKey"
|
||||||
v-ripple
|
v-ripple
|
||||||
clickable
|
clickable
|
||||||
@ -33,7 +33,7 @@
|
|||||||
</q-item-section>
|
</q-item-section>
|
||||||
|
|
||||||
<q-item-section>{{
|
<q-item-section>{{
|
||||||
$store.getters.handle(followedKey)
|
$store.getters.displayName(followedKey)
|
||||||
}}</q-item-section>
|
}}</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
|
@ -9,7 +9,9 @@
|
|||||||
class="small-screen-only fixed-top-left q-ma-xs"
|
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 />
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
@ -81,7 +83,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import 'md-gum-polyfill'
|
|
||||||
import helpersMixin from '../utils/mixin'
|
import helpersMixin from '../utils/mixin'
|
||||||
import {pool} from '../global'
|
import {pool} from '../global'
|
||||||
|
|
||||||
@ -100,7 +101,7 @@ export default {
|
|||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
isFollowing() {
|
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: {
|
methods: {
|
||||||
unFollow() {
|
unFollow() {
|
||||||
this.$store.dispatch('stopFollowing', this.$route.params.pubkey)
|
this.$store.commit('unfollow', this.$route.params.pubkey)
|
||||||
this.$router.push('/')
|
this.$router.push('/')
|
||||||
},
|
},
|
||||||
|
|
||||||
addPubFollow() {
|
addPubFollow() {
|
||||||
this.$store.dispatch('startFollowing', this.$route.params.pubkey)
|
this.$store.commit('follow', this.$route.params.pubkey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,10 +73,10 @@
|
|||||||
/>
|
/>
|
||||||
<q-input
|
<q-input
|
||||||
disable
|
disable
|
||||||
v-model="imagetemp"
|
v-model="picture"
|
||||||
filled
|
filled
|
||||||
type="text"
|
type="text"
|
||||||
hint="Profile picture (imgur url)"
|
hint="Picture URL"
|
||||||
maxlength="150"
|
maxlength="150"
|
||||||
/>
|
/>
|
||||||
<q-btn
|
<q-btn
|
||||||
@ -97,7 +97,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-9">
|
<div class="col-9">
|
||||||
<q-input
|
<q-input
|
||||||
v-model="relaya"
|
v-model="addingRelay"
|
||||||
filled
|
filled
|
||||||
type="textarea"
|
type="textarea"
|
||||||
autogrow
|
autogrow
|
||||||
@ -120,7 +120,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-9">
|
<div class="col-9">
|
||||||
<q-select
|
<q-select
|
||||||
v-model="relayr"
|
v-model="removingRelay"
|
||||||
filled
|
filled
|
||||||
:options="$store.state.myProfile.relays"
|
:options="$store.state.myProfile.relays"
|
||||||
hint="Remove a relay"
|
hint="Remove a relay"
|
||||||
@ -219,7 +219,7 @@
|
|||||||
|
|
||||||
<q-card-actions align="right" class="text-primary">
|
<q-card-actions align="right" class="text-primary">
|
||||||
<q-btn flat label="Cancel" v-close-popup />
|
<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-actions>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
@ -229,44 +229,31 @@
|
|||||||
<script>
|
<script>
|
||||||
import helpersMixin from '../utils/mixin'
|
import helpersMixin from '../utils/mixin'
|
||||||
import {copyToClipboard} from 'quasar'
|
import {copyToClipboard} from 'quasar'
|
||||||
const bip39 = require('bip39')
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Settings',
|
name: 'Settings',
|
||||||
mixins: [helpersMixin],
|
mixins: [helpersMixin],
|
||||||
data() {
|
data() {
|
||||||
const {imagetemp, handle, about} = this.$store.state.myProfile
|
const {name, picture, about} = this.$store.state.me
|
||||||
|
|
||||||
return {
|
return {
|
||||||
privatekey: null,
|
privatekey: null,
|
||||||
publickey: null,
|
publickey: null,
|
||||||
deleteAccDialog: false,
|
deleteAccDialog: false,
|
||||||
privateKeyDialog: false,
|
privateKeyDialog: false,
|
||||||
imagetemp,
|
removingRelay: '',
|
||||||
handle,
|
addingRelay: '',
|
||||||
about,
|
|
||||||
relayr: '',
|
|
||||||
relaya: '',
|
|
||||||
isPrivPwd: true,
|
isPrivPwd: true,
|
||||||
isPwd: true,
|
isPwd: true,
|
||||||
addPubKey: ''
|
addPubKey: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
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() {
|
setProfile() {
|
||||||
this.$store.dispatch('saveMeta', {
|
this.$store.dispatch('setMetadata', {
|
||||||
image: this.imagetemp,
|
name: this.handle,
|
||||||
handle: this.handle,
|
about: this.about,
|
||||||
about: this.about
|
picture: this.picture
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
privateKey() {
|
privateKey() {
|
||||||
@ -285,15 +272,15 @@ export default {
|
|||||||
this.$q.notify({type: 'negative', message: 'FAILED'})
|
this.$q.notify({type: 'negative', message: 'FAILED'})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
relayAdd() {
|
addRelay() {
|
||||||
this.$store.dispatch('relayPush', this.relaya)
|
this.$store.commit('addRelay', this.addingRelay)
|
||||||
this.relaya = ''
|
this.addingRelay = ''
|
||||||
},
|
},
|
||||||
relayRem() {
|
removeRelay() {
|
||||||
this.$store.dispatch('relayRemove', this.relayr)
|
this.$store.commit('removeRelat', this.removingRelay)
|
||||||
this.relayr = ''
|
this.removingRelay = ''
|
||||||
},
|
},
|
||||||
deletels() {
|
hardReset() {
|
||||||
this.$q.localStorage.clear()
|
this.$q.localStorage.clear()
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
}
|
}
|
||||||
|
@ -4,29 +4,24 @@ const routes = [
|
|||||||
component: () => import('layouts/MainLayout.vue'),
|
component: () => import('layouts/MainLayout.vue'),
|
||||||
children: [
|
children: [
|
||||||
{path: '', component: () => import('pages/Home.vue'), name: 'home'},
|
{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',
|
path: '/settings',
|
||||||
component: () => import('pages/Settings.vue'),
|
component: () => import('pages/Settings.vue'),
|
||||||
name: 'settings'
|
name: 'settings'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/help',
|
path: '/messages',
|
||||||
component: () => import('pages/Help.vue'),
|
component: () => import('pages/Messages.vue'),
|
||||||
name: 'help'
|
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 {getEventHash} from 'nostr-tools'
|
||||||
import {encrypt, decrypt} from 'nostr-tools/nip04'
|
import {encrypt, decrypt} from 'nostr-tools/nip04'
|
||||||
import {LocalStorage, Notify} from 'quasar'
|
import {LocalStorage, Notify} from 'quasar'
|
||||||
import 'md-gum-polyfill'
|
|
||||||
|
|
||||||
import {pool} from '../global'
|
import {pool} from '../global'
|
||||||
|
|
||||||
export function launch(store) {
|
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 => {
|
// now we already have a key
|
||||||
pool.addRelay(relay)
|
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) => {
|
pool.onNotice((notice, relay) => {
|
||||||
Notify.create({
|
Notify.create({
|
||||||
message: `Relay ${relay.url} says: ${notice}`,
|
message: `Relay ${relay.url} says: ${notice}`,
|
||||||
@ -28,153 +41,65 @@ export function restartHomeFeed(store) {
|
|||||||
homeSubscription = homeSubscription.sub({
|
homeSubscription = homeSubscription.sub({
|
||||||
filter: [
|
filter: [
|
||||||
{
|
{
|
||||||
authors: Object.keys(store.state.theirProfile).length
|
authors: store.state.following.length ? store.state.following : null
|
||||||
? Object.keys(store.state.theirProfile)
|
|
||||||
: null
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
author: store.state.myProfile.pubkey
|
author: store.state.keys.pub
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'#p': store.state.myProfile.pubkey
|
'#p': store.state.keys.pub
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
cb: (event, relay) => {
|
cb: (event, relay) => {
|
||||||
switch (event.kind) {
|
switch (event.kind) {
|
||||||
case 0:
|
case 0:
|
||||||
store.commit('addKind0', event)
|
try {
|
||||||
|
event.metadata = JSON.parse(event.content)
|
||||||
|
} catch (err) {}
|
||||||
break
|
break
|
||||||
|
|
||||||
case 1:
|
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
|
break
|
||||||
|
|
||||||
case 4:
|
case 4:
|
||||||
// a direct encrypted message
|
// a direct encrypted message
|
||||||
if (
|
if (
|
||||||
event.tags.find(
|
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
|
// 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
|
// decrypt it
|
||||||
let [ciphertext, iv] = event.content.split('?iv=')
|
let [ciphertext, iv] = event.content.split('?iv=')
|
||||||
let text = decrypt(
|
event.plaintext = decrypt(
|
||||||
store.state.myProfile.privkey,
|
store.state.keys.priv,
|
||||||
event.pubkey,
|
event.pubkey,
|
||||||
ciphertext,
|
ciphertext,
|
||||||
iv
|
iv
|
||||||
)
|
)
|
||||||
|
} else if (event.pubkey === store.state.keys.pub) {
|
||||||
// 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
|
|
||||||
) {
|
|
||||||
// it is coming from us
|
// it is coming from us
|
||||||
let p = event.tags.find(tag => tag[0] === 'p')
|
let [_, target] = event.tags.find(([tag]) => tag === 'p')
|
||||||
let lsKey = `messages.${p[1]}`
|
// decrypt it
|
||||||
var messagesS = LocalStorage.getItem(lsKey)
|
let [ciphertext, iv] = event.content.split('?iv=')
|
||||||
|
event.plaintext = decrypt(
|
||||||
if (messagesS.length > 0) {
|
store.state.keys.priv,
|
||||||
for (var i = 0; i < messagesS.length; i++) {
|
target,
|
||||||
if (
|
ciphertext,
|
||||||
messagesS[i].id === event.id &&
|
iv
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
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}) {
|
export async function sendPost(store, {message, tags = [], kind = 1}) {
|
||||||
if (message.length === 0) return
|
if (message.length === 0) return
|
||||||
|
|
||||||
let event = {
|
let event = {
|
||||||
pubkey: store.state.myProfile.pubkey,
|
pubkey: store.state.keys.pub,
|
||||||
created_at: Math.floor(Date.now() / 1000),
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
kind,
|
kind,
|
||||||
tags,
|
tags,
|
||||||
@ -184,164 +109,41 @@ export async function sendPost(store, {message, tags = [], kind = 1}) {
|
|||||||
event.id = await getEventHash(event)
|
event.id = await getEventHash(event)
|
||||||
pool.publish(event)
|
pool.publish(event)
|
||||||
|
|
||||||
store.commit('addKind1', {
|
store.commit('addEvent', event)
|
||||||
...event,
|
|
||||||
loading: true,
|
|
||||||
retry: false
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function postAgain(store, event) {
|
export async function setMetadata(store, metadata) {
|
||||||
for (let i = 0; i < store.state.kind1.length; i++) {
|
store.commit('setProfile', metadata)
|
||||||
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
|
|
||||||
})
|
|
||||||
|
|
||||||
var event = {
|
var event = {
|
||||||
pubkey: store.state.myProfile.pubkey,
|
pubkey: store.state.keys.pub,
|
||||||
created_at: Math.floor(Date.now() / 1000),
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
kind: 0,
|
kind: 0,
|
||||||
tags: [],
|
tags: [],
|
||||||
content: JSON.stringify({
|
content: JSON.stringify(metadata)
|
||||||
name: store.state.myProfile.name,
|
|
||||||
about: store.state.myProfile.about,
|
|
||||||
picture: store.state.myProfile.picture
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
event.id = await getEventHash(event)
|
event.id = await getEventHash(event)
|
||||||
pool.publish(event)
|
pool.publish(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deletePost(store, postId) {
|
export async function sendChatMessage(store, {pubkey, text, replyTo}) {
|
||||||
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}) {
|
|
||||||
if (text.length === 0) return
|
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
|
// make event
|
||||||
let event = {
|
let event = {
|
||||||
pubkey: store.state.myProfile.pubkey,
|
pubkey: store.state.keys.pub,
|
||||||
created_at: Math.floor(Date.now() / 1000),
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
kind: 4,
|
kind: 4,
|
||||||
tags: [['p', pubkey]],
|
tags: [['p', pubkey]],
|
||||||
content: ciphertext + '?iv=' + iv
|
content: ciphertext + '?iv=' + iv
|
||||||
}
|
}
|
||||||
|
if (replyTo) {
|
||||||
let lsKey = `messages.${pubkey}`
|
event.tags.push(['e', replyTo])
|
||||||
var messages = LocalStorage.getItem(lsKey) || []
|
|
||||||
|
|
||||||
if (messages.length > 0) {
|
|
||||||
event.tags.push(['e', messages[messages.length - 1].id])
|
|
||||||
}
|
}
|
||||||
event.id = await getEventHash(event)
|
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)
|
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'
|
import Identicon from 'identicon.js'
|
||||||
|
|
||||||
export function disabled(state) {
|
export function disabled(state) {
|
||||||
return !state.myProfile
|
return !state.keys.pub
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handle(state, pubkey) {
|
export function displayName(state) {
|
||||||
return pubkey => {
|
return pubkey => {
|
||||||
let profile = state.theirProfile[pubkey]
|
let {metadata = {}} = state.events.kind0[pubkey]
|
||||||
if (profile && profile.name) return profile.name
|
if (metadata.name) return metadata.name
|
||||||
|
return pubkey.slice(0, 3) + '...' + pubkey.slice(-4)
|
||||||
let kind0 = state.kind0[pubkey]
|
|
||||||
if (kind0 && kind0.name) return profile.name
|
|
||||||
|
|
||||||
return pubkey.slice(0, 20) + '...'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function avatar(state) {
|
export function avatar(state) {
|
||||||
return pubkey => {
|
return pubkey => {
|
||||||
let profile = state.theirProfile[pubkey]
|
let {metadata = {}} = state.events.kind0[pubkey]
|
||||||
if (profile && profile.picture) return profile.picture
|
if (metadata.picture) return metadata.picture
|
||||||
|
|
||||||
let kind0 = state.kind0[pubkey]
|
|
||||||
if (kind0 && kind0.picture) return profile.picture
|
|
||||||
|
|
||||||
let data = new Identicon(pubkey, 40).toString()
|
let data = new Identicon(pubkey, 40).toString()
|
||||||
return 'data:image/png;base64,' + data
|
return 'data:image/png;base64,' + data
|
||||||
}
|
}
|
||||||
|
@ -1,61 +1,71 @@
|
|||||||
export function setProfile(state, profile) {
|
import {LocalStorage} from 'quasar'
|
||||||
state.myProfile = profile
|
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) {
|
export function setMetadata(state, {name, picture, about}) {
|
||||||
state.myProfile.relays.push(url)
|
state.me = {name, picture, about}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function relaySplice(state, url) {
|
export function addRelay(state, url) {
|
||||||
let index = state.myProfile.relays.indexOf(url)
|
state.relays[url] = {
|
||||||
if (index === -1) return
|
read: true,
|
||||||
state.myProfile.relays.splice(index, 1)
|
write: true
|
||||||
}
|
|
||||||
|
|
||||||
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 stopFollowing(state, key) {
|
export function removeRelay(state, url) {
|
||||||
delete state.theirProfile[key]
|
delete state.relays[url]
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addKind1(state, event) {
|
export function follow(state, key) {
|
||||||
state.kind1.unshift(event)
|
if (state.following.includes(key)) {
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
return
|
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) {
|
export function chatUpdated(state) {
|
||||||
|
@ -2,11 +2,15 @@ import {LocalStorage} from 'quasar'
|
|||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
return {
|
return {
|
||||||
myProfile: LocalStorage.getItem('myProfile'),
|
me: LocalStorage.getItem('me') || {}, // { name, picture, about, ... }
|
||||||
theirProfile: LocalStorage.getItem('theirProfile') || {},
|
keys: LocalStorage.getItem('keys') || {}, // { seed, priv, pub }
|
||||||
|
relays: LocalStorage.getItem('relays') || {}, // { [url]: {} }
|
||||||
kind0: {}, // temporary, will be merged with theirProfile or erased at the end
|
following: LocalStorage.getItem('following') || [], // [ pubkeys... ]
|
||||||
kind1: LocalStorage.getItem('kind1') || [],
|
events: {
|
||||||
|
kind0: LocalStorage.getItem('events.kind0') || {}, // { [pubkey]: event }
|
||||||
|
kind1: LocalStorage.getItem('events.kind1') || [], // [ events... ]
|
||||||
|
kind4: LocalStorage.getItem('events.kind4') || {} // { [pubkey]: [events...] }
|
||||||
|
},
|
||||||
|
|
||||||
chatUpdated: 1 // hack
|
chatUpdated: 1 // hack
|
||||||
}
|
}
|
||||||
|
@ -4,20 +4,19 @@ export default function (store) {
|
|||||||
store.subscribe(({type, payload}, state) => {
|
store.subscribe(({type, payload}, state) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'setProfile':
|
case 'setProfile':
|
||||||
case 'relayPush':
|
LocalStorage.set('me', state.me)
|
||||||
case 'relaySplice':
|
case 'addRelay':
|
||||||
console.log('storing', state.myProfile)
|
case 'removeRelay':
|
||||||
LocalStorage.set('myProfile', state.myProfile)
|
LocalStorage.set('relays', state.relays)
|
||||||
break
|
break
|
||||||
case 'startFollowing':
|
case 'follow':
|
||||||
case 'stopFollowing':
|
case 'unfollow':
|
||||||
case 'addKind0':
|
LocalStorage.set('following', state.following)
|
||||||
LocalStorage.set('theirProfile', state.theirProfile)
|
case 'addEvent':
|
||||||
break
|
LocalStorage.set(
|
||||||
case 'addKind1':
|
`events.${payload.kind}`,
|
||||||
case 'replaceKind1':
|
state.events[`kind${payload.kind}`]
|
||||||
case 'deleteKind1':
|
)
|
||||||
LocalStorage.set('kind1', state.kind1)
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -11,7 +11,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
toProfile(pubkey) {
|
toProfile(pubkey) {
|
||||||
this.$router.push('/user/' + pubkey)
|
this.$router.push('/' + pubkey)
|
||||||
},
|
},
|
||||||
|
|
||||||
niceDate(value) {
|
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"
|
resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.3.tgz#0300943770e04231041a51bd39f0439b5c7ab4f0"
|
||||||
integrity sha512-nkalE/f1RvRGChwBnEIoBfSEYOXnCRdleKuv6+lePbMDrMZXeDQnqak5XDOeBgrPPyPfAdcCu/B5z+v3VhplGg==
|
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":
|
"@nodelib/fs.scandir@2.1.5":
|
||||||
version "2.1.5"
|
version "2.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
|
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"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.12.tgz#ac7fb693ac587ee182c3780c26eb65546a1a3c10"
|
||||||
integrity sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==
|
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":
|
"@types/node@11.11.6":
|
||||||
version "11.11.6"
|
version "11.11.6"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-11.11.6.tgz#df929d1bb2eee5afdda598a41930fe50b43eaa6a"
|
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"
|
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
||||||
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
|
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:
|
base64-js@^1.3.1:
|
||||||
version "1.5.1"
|
version "1.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
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"
|
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
|
||||||
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
|
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:
|
bip39@^3.0.4:
|
||||||
version "3.0.4"
|
version "3.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.0.4.tgz#5b11fed966840b5e1b8539f0f54ab6392969b2a0"
|
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"
|
node-releases "^2.0.1"
|
||||||
picocolors "^1.0.0"
|
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:
|
buffer-crc32@^0.2.1, buffer-crc32@^0.2.13:
|
||||||
version "0.2.13"
|
version "0.2.13"
|
||||||
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
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"
|
crc-32 "^1.2.0"
|
||||||
readable-stream "^3.4.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"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196"
|
resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196"
|
||||||
integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==
|
integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==
|
||||||
@ -2355,7 +2400,7 @@ create-hash@^1.1.0, create-hash@^1.1.2:
|
|||||||
ripemd160 "^2.0.1"
|
ripemd160 "^2.0.1"
|
||||||
sha.js "^2.4.0"
|
sha.js "^2.4.0"
|
||||||
|
|
||||||
create-hmac@^1.1.4:
|
create-hmac@^1.1.4, create-hmac@^1.1.7:
|
||||||
version "1.1.7"
|
version "1.1.7"
|
||||||
resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff"
|
resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff"
|
||||||
integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==
|
integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==
|
||||||
@ -4168,11 +4213,6 @@ make-dir@^3.0.2, make-dir@^3.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
semver "^6.0.0"
|
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:
|
md5.js@^1.3.4:
|
||||||
version "1.3.5"
|
version "1.3.5"
|
||||||
resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
|
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"
|
lower-case "^2.0.2"
|
||||||
tslib "^2.0.3"
|
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:
|
node-forge@^0.10.0:
|
||||||
version "0.10.0"
|
version "0.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
|
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"
|
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a"
|
||||||
integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==
|
integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==
|
||||||
|
|
||||||
nostr-tools@^0.5.0:
|
nostr-tools@^0.6.0:
|
||||||
version "0.5.0"
|
version "0.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-0.5.0.tgz#cb641ff21d035169590cc7185eca6aed6a9a101c"
|
resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-0.6.0.tgz#03f6ac3bd72ed7ead9b2472b655228ebc011bb92"
|
||||||
integrity sha512-JxLOP8psDx3Ye08ar8pbY7u08zJUIo9sS3CqpPqfvgV86Blv9oImKP/GfODP0H3MbfOKk4zdYQt6+qZie0k8uA==
|
integrity sha512-XBi+tuRQo9prL8n1Wxu3/O1NYGc4tQaIExaLfebj3eg0um8xDnKzgpmbRL+dTXqaZmWKnTtTAbZvea2uluXIPw==
|
||||||
dependencies:
|
dependencies:
|
||||||
|
"@noble/secp256k1" "^1.3.0"
|
||||||
buffer "^6.0.3"
|
buffer "^6.0.3"
|
||||||
dns-packet "^5.2.4"
|
dns-packet "^5.2.4"
|
||||||
noble-secp256k1 "^1.1.1"
|
|
||||||
websocket-polyfill "^0.0.3"
|
websocket-polyfill "^0.0.3"
|
||||||
|
|
||||||
npm-run-path@^4.0.1:
|
npm-run-path@^4.0.1:
|
||||||
@ -5920,6 +5955,11 @@ typedarray-to-buffer@^3.1.5:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-typedarray "^1.0.0"
|
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:
|
typescript@4.4.2:
|
||||||
version "4.4.2"
|
version "4.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.2.tgz#6d618640d430e3569a1dfb44f7d7e600ced3ee86"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.2.tgz#6d618640d430e3569a1dfb44f7d7e600ced3ee86"
|
||||||
@ -6268,6 +6308,13 @@ which@^2.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
isexe "^2.0.0"
|
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:
|
wildcard@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec"
|
resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec"
|
||||||
|
Loading…
Reference in New Issue
Block a user