aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2022-12-21 15:48:14 -0800
committerAdam Soltys <[email protected]>2022-12-21 15:48:14 -0800
commit6ae27be919808df935fb140fd56bb6ae010a219b (patch)
treeba9f5d862cc48abf5b49acfdbd32b87216eadba1
parent5c0675fa6815a70033c17a9310d0fa1f9c569e92 (diff)
downloadcoinos-ui-6ae27be919808df935fb140fd56bb6ae010a219b.tar.gz
coinos-ui-6ae27be919808df935fb140fd56bb6ae010a219b.zip
add nostr message field to user profile page
-rw-r--r--package.json1
-rw-r--r--pnpm-lock.yaml30
-rw-r--r--src/lib/utils.js24
-rw-r--r--src/routes/(app)/[username]/+page.server.js9
-rw-r--r--src/routes/(app)/[username]/+page.svelte60
-rw-r--r--src/routes/(app)/[username]/+server.js6
-rw-r--r--src/routes/(app)/[username]/generate/+page.svelte27
7 files changed, 129 insertions, 28 deletions
diff --git a/package.json b/package.json
index dac10511..8e86dc1d 100644
--- a/package.json
+++ b/package.json
@@ -24,6 +24,7 @@
"date-fns": "^2.29.3",
"leaflet": "^1.9.3",
"leaflet.markercluster": "^1.5.3",
+ "nostr": "^0.2.7",
"postcss": "^8.4.19",
"prettier": "^2.7.1",
"prettier-plugin-svelte": "^2.8.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2353621a..af33679d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -15,6 +15,7 @@ specifiers:
date-fns: ^2.29.3
leaflet: ^1.9.3
leaflet.markercluster: ^1.5.3
+ nostr: ^0.2.7
postcss: ^8.4.19
prettier: ^2.7.1
prettier-plugin-svelte: ^2.8.1
@@ -49,6 +50,7 @@ devDependencies:
date-fns: 2.29.3
leaflet: 1.9.3
leaflet.markercluster: [email protected]
+ nostr: 0.2.7
postcss: 8.4.19
prettier: 2.7.1
prettier-plugin-svelte: 2.8.1_nryolsexf6k3znhuh4uzpugsem
@@ -1529,6 +1531,11 @@ packages:
hasBin: true
dev: true
+ /noble-secp256k1/1.2.14:
+ resolution: {integrity: sha512-GSCXyoZBUaaPwVWdYncMEmzlSUjF9J/YeEHpklYJCyg8wPuJP3NzDx0BkiwArzINkdX2HJHvUJhL6vVWPOQQcg==}
+ deprecated: Switch to namespaced @noble/secp256k1 for security and feature updates
+ dev: true
+
/node-addon-api/2.0.2:
resolution: {integrity: sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==}
dev: true
@@ -1585,6 +1592,16 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
+ /nostr/0.2.7:
+ resolution: {integrity: sha512-Yq5tkiCTJtohs7H7Fx+Ki83bR8b3xapdwYi17HunhW5EQfZ9rI0cIXFyihj2ZRkv/0YYvVBiCp/KIaLYt5LyfQ==}
+ dependencies:
+ noble-secp256k1: 1.2.14
+ ws: 8.11.0
+ transitivePeerDependencies:
+ - bufferutil
+ - utf-8-validate
+ dev: true
+
/object-hash/3.0.0:
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
engines: {node: '>= 6'}
@@ -2372,6 +2389,19 @@ packages:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
dev: true
+ /ws/8.11.0:
+ resolution: {integrity: sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: ^5.0.2
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+ dev: true
+
/xtend/4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}
diff --git a/src/lib/utils.js b/src/lib/utils.js
index f59b6359..14646ad0 100644
--- a/src/lib/utils.js
+++ b/src/lib/utils.js
@@ -1,3 +1,5 @@
+import * as ecc from 'tiny-secp256k1';
+import { BIP32Factory } from 'bip32';
import { browser } from '$app/environment';
import { toast } from '@zerodevx/svelte-toast';
import { goto } from '$app/navigation';
@@ -168,3 +170,25 @@ export const wait = async (f, s = 300) => {
if (i >= s) throw new Error('timeout');
return f();
};
+
+export const stretch = async (password, salt) =>
+ crypto.subtle.deriveKey(
+ {
+ name: 'PBKDF2',
+ salt,
+ iterations: 100000,
+ hash: 'SHA-256'
+ },
+ await crypto.subtle.importKey(
+ 'raw',
+ new TextEncoder().encode(password),
+ { name: 'PBKDF2' },
+ false,
+ ['deriveBits', 'deriveKey']
+ ),
+ { name: 'AES-GCM', length: 256 },
+ true,
+ ['encrypt', 'decrypt']
+ );
+
+export const bip32 = await BIP32Factory(ecc);
diff --git a/src/routes/(app)/[username]/+page.server.js b/src/routes/(app)/[username]/+page.server.js
index 01dfab5a..547f754f 100644
--- a/src/routes/(app)/[username]/+page.server.js
+++ b/src/routes/(app)/[username]/+page.server.js
@@ -1,14 +1,19 @@
import { get } from '$lib/utils';
import Qr from 'qrcode-base64';
-export async function load({ params, url }) {
+export async function load({ params, parent, url }) {
+ let { user } = await parent();
+ let pubkey;
+ if (user) ({ pubkey } = user);
+ else pubkey = params.username;
+
let { username } = params;
let text = `${encodeURI(username)}@${url.hostname}`;
let src = Qr.drawImg(text, { size: 300 });
let events = [];
try {
- events = await get(`/${username}/events`);
+ events = await get(`/${pubkey}/events`);
} catch (e) {
console.log(e);
}
diff --git a/src/routes/(app)/[username]/+page.svelte b/src/routes/(app)/[username]/+page.svelte
index 78d84653..837257ef 100644
--- a/src/routes/(app)/[username]/+page.svelte
+++ b/src/routes/(app)/[username]/+page.svelte
@@ -1,12 +1,53 @@
<script>
+ import { Buffer } from 'buffer';
+ import { entropyToMnemonic, mnemonicToSeedSync } from 'bip39';
+ import { enhance } from '$app/forms';
import { Feed, Icon, PageNotFound } from '$comp';
import { t } from '$lib/translations';
+ import { bech32m } from 'bech32';
+ import { calculateId, signId } from 'nostr';
+ import { bip32, stretch, post } from '$lib/utils';
+
+ const { decode, fromWords } = bech32m;
export let data;
+ let message;
+
let { events, user, subject, src, text } = data;
let followed = false;
let { username: n } = subject;
+
+ let submit = async () => {
+ let mnemonic, key, password, seed, entropy, child, privkey;
+ entropy = Buffer.from(
+ await crypto.subtle.decrypt(
+ { name: 'AES-GCM', iv: new Uint8Array(16) },
+ await stretch(password, Buffer.from(user.salt, 'hex')),
+ Uint8Array.from(fromWords(decode(user.cipher, 180).words))
+ ),
+ 'hex'
+ ).toString('hex');
+
+ mnemonic = entropyToMnemonic(entropy);
+ seed = mnemonicToSeedSync(mnemonic);
+ key = bip32.fromSeed(seed);
+ child = key.derivePath("m/44'/1237'/0'/0/0");
+ privkey = child.privateKey;
+
+ let event = {
+ pubkey: user.pubkey,
+ created_at: Math.floor(Date.now() / 1000),
+ kind: 1,
+ content: message,
+ tags: []
+ };
+
+ event.id = await calculateId(event);
+ event.sig = await signId(privkey, event.id);
+ await post(`/${user.username}`, { event });
+ };
+
$: username = n.length > 60 ? n.substr(0, 6) : n;
</script>
@@ -69,9 +110,24 @@
</div>
</div>
- <div class="w-full flex md:pt-12 lg:pt-0">
+ <div class="w-full md:pt-12 lg:pt-0">
+ {#if user.username === n}
+ <form on:submit|preventDefault={submit}>
+ <input
+ name="message"
+ bind:value={message}
+ placeholder="What's on your mind?"
+ class="w-full my-5 max-w-lg mx-auto"
+ />
+ </form>
+ {/if}
+
<div class="mx-auto space-y-5 mt-5 lg:max-w-lg xl:max-w-2xl">
- <Feed {events} />
+ {#if events.length}
+ <Feed {events} />
+ {:else}
+ <div class="text-center">You haven't posted anything yet</div>
+ {/if}
</div>
</div>
</div>
diff --git a/src/routes/(app)/[username]/+server.js b/src/routes/(app)/[username]/+server.js
new file mode 100644
index 00000000..d0b1242f
--- /dev/null
+++ b/src/routes/(app)/[username]/+server.js
@@ -0,0 +1,6 @@
+import { auth, post } from '$lib/utils';
+import { json } from '@sveltejs/kit';
+
+export async function POST({ cookies, request }) {
+ return json(post('/nostr/send', await request.json(), auth(cookies)));
+}
diff --git a/src/routes/(app)/[username]/generate/+page.svelte b/src/routes/(app)/[username]/generate/+page.svelte
index ee67237c..fd6b6f7e 100644
--- a/src/routes/(app)/[username]/generate/+page.svelte
+++ b/src/routes/(app)/[username]/generate/+page.svelte
@@ -1,42 +1,20 @@
<script>
import { Buffer } from 'buffer';
import { onMount } from 'svelte';
- import { BIP32Factory } from 'bip32';
import { bech32m } from 'bech32';
import { generateMnemonic, mnemonicToEntropy, mnemonicToSeedSync } from 'bip39';
import * as ecc from 'tiny-secp256k1';
- import { post } from '$lib/utils';
+ import { bip32, stretch, post } from '$lib/utils';
import { goto } from '$app/navigation';
export let data;
let { user } = data;
- const { encode, decode, fromWords, toWords } = bech32m;
-
- const stretch = async (password, salt) =>
- crypto.subtle.deriveKey(
- {
- name: 'PBKDF2',
- salt,
- iterations: 100000,
- hash: 'SHA-256'
- },
- await crypto.subtle.importKey(
- 'raw',
- new TextEncoder().encode(password),
- { name: 'PBKDF2' },
- false,
- ['deriveBits', 'deriveKey']
- ),
- { name: 'AES-GCM', length: 256 },
- true,
- ['encrypt', 'decrypt']
- );
+ const { encode, toWords } = bech32m;
let mnemonic, key, password, seed, entropy, cipher, pubkey, salt, child;
onMount(async () => {
- let bip32 = await BIP32Factory(ecc);
mnemonic = generateMnemonic();
seed = mnemonicToSeedSync(mnemonic);
entropy = mnemonicToEntropy(mnemonic);
@@ -54,6 +32,7 @@
user.pubkey = Buffer.from(ecc.xOnlyPointFromPoint(child.publicKey)).toString('hex');
user.cipher = encode('en', toWords(bytes), 180);
+ user.salt = Buffer.from(salt).toString('hex');
await post(`/${user.username}/generate`, user);
goto(`/${user.username}/dashboard`);