From a024d80a85c207bd5639ef7f0ee123c6a307015a Mon Sep 17 00:00:00 2001 From: scobru Date: Sun, 13 Oct 2024 01:17:45 +0200 Subject: [PATCH] to fix AccountProfile --- packages/svelte/package.json | 4 + .../gun/account/AccountAvatar.svelte | 61 ++++ .../gun/account/AccountProfile.svelte | 66 ++++ packages/svelte/src/lib/gun/account.ts | 191 +++++++++++ packages/svelte/src/lib/gun/auth.ts | 196 +++++++++++- packages/svelte/src/lib/gun/avatar.ts | 119 +++++++ packages/svelte/src/lib/gun/colors.ts | 52 +++ packages/svelte/src/lib/gun/crypto.ts | 174 ++++++++++ packages/svelte/src/lib/gun/user.ts | 61 +--- packages/svelte/src/routes/auth/+page.svelte | 11 +- yarn.lock | 301 +++++++++++++++++- 11 files changed, 1182 insertions(+), 54 deletions(-) create mode 100644 packages/svelte/src/lib/components/gun/account/AccountAvatar.svelte create mode 100644 packages/svelte/src/lib/components/gun/account/AccountProfile.svelte create mode 100644 packages/svelte/src/lib/gun/account.ts create mode 100644 packages/svelte/src/lib/gun/avatar.ts create mode 100644 packages/svelte/src/lib/gun/colors.ts create mode 100644 packages/svelte/src/lib/gun/crypto.ts diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 582715f..deda57a 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -16,6 +16,7 @@ "@sveltejs/adapter-auto": "^3.2.4", "@sveltejs/kit": "^2.0.0", "@sveltejs/vite-plugin-svelte": "^4.0.0-next.7", + "@types/color-hash": "^2", "@types/d3": "^7", "@types/dompurify": "^3", "@types/eslint": "^8.56.0", @@ -58,6 +59,7 @@ "abitype": "^1.0.2", "blo": "^1.1.1", "buffer": "^6.0.3", + "color-hash": "^2.0.2", "d3": "^7.9.0", "dompurify": "^3.1.6", "dotenv": "^16.4.5", @@ -65,6 +67,7 @@ "express": "^4.21.0", "express-rate-limit": "^7.4.0", "gun": "^0.2020.1240", + "gun-avatar": "^1.9.4", "gun-eth": "^1.2.13", "ip": "^2.0.1", "js-sha256": "^0.11.0", @@ -74,6 +77,7 @@ "self-adjusting-interval": "^1.0.0", "svelte-wagmi": "^1.0.7", "tiny-secp256k1": "^2.2.3", + "url-regex": "^5.0.0", "viem": "^2.21.7", "vis-network": "^9.1.9", "vite-plugin-svelte": "^3.0.1" diff --git a/packages/svelte/src/lib/components/gun/account/AccountAvatar.svelte b/packages/svelte/src/lib/components/gun/account/AccountAvatar.svelte new file mode 100644 index 0000000..8970e00 --- /dev/null +++ b/packages/svelte/src/lib/components/gun/account/AccountAvatar.svelte @@ -0,0 +1,61 @@ + + +
+ {#if pub} + Avatar + {#if isOwnAvatar} + + + {#if $uploadStatus} +
{$uploadStatus}
+ {/if} + {/if} + {:else} +
+
+
+ {/if} + +
\ No newline at end of file diff --git a/packages/svelte/src/lib/components/gun/account/AccountProfile.svelte b/packages/svelte/src/lib/components/gun/account/AccountProfile.svelte new file mode 100644 index 0000000..2bb7546 --- /dev/null +++ b/packages/svelte/src/lib/components/gun/account/AccountProfile.svelte @@ -0,0 +1,66 @@ + + +{#if $account} +
+

Account Information

+ {#each ['pub', 'Color', 'pulse', 'Blink', 'lastSeen'] as field} +
+
{field}
+
+ {#if field === 'Color'} +
+ {$account.color} + {:else if field === 'Blink'} +
{$account.blink ? 'Yes' : 'No'}
+ {:else} +
{$account[field.toLowerCase().replace(' ', '')]}
+ {/if} +
+
+ {/each} + +

Profile

+ {#if $account.profile} + {#each Object.entries($account.profile) as [field, content]} +
+
{field}
+
+ {#if !isLink(content as string)} +
{content}
+ {:else} + + {content} + + {/if} +
+
+ {/each} + {:else} +

Nessun dato del profilo disponibile.

+ {/if} +
+ {:else} +

Caricamento account...

+ {/if} \ No newline at end of file diff --git a/packages/svelte/src/lib/gun/account.ts b/packages/svelte/src/lib/gun/account.ts new file mode 100644 index 0000000..f0fe756 --- /dev/null +++ b/packages/svelte/src/lib/gun/account.ts @@ -0,0 +1,191 @@ +import { writable, derived, get } from "svelte/store"; +import ms from "ms"; +import { useGun } from "./gun"; +import { useUser } from "./user"; +import SEA from "gun/sea"; +import { useColor } from "./colors"; + +const TIMEOUT = 10000; +const colorDeep = useColor("deep"); + +interface Profile { + name?: string; + first_name?: string; + last_name?: string; + birth_day?: string; +} + +interface Account { + pub?: string; + color?: string; + pulse?: number; + blink?: boolean; + profile?: Profile; + petname?: string; + db?: any; + lastSeen?: string | number; +} + +export function useAccount(pubKey: string) { + console.log("useAccount called with:", pubKey); + const gun = useGun(); + const { user } = useUser(); + console.log("user store:", user); + const pub = writable(pubKey); + + const accountStore = writable({ + pub: pubKey, + color: colorDeep.hex(pubKey), + profile: { name: "" }, + pulse: 0, + blink: false, + db: gun.user(pubKey), + lastSeen: "offline", + }); + + const pulseStore = writable(0); + + const calculateLastSeen = (pulse: number) => { + if (!pulse) return "offline"; + const timeDiff = Date.now() - pulse; + if (timeDiff <= TIMEOUT) return "online"; + return ms(timeDiff); + }; + + + + // Aggiorna lastSeen ogni secondo + const lastSeenInterval = setInterval(() => { + accountStore.update(acc => ({ + ...acc, + lastSeen: calculateLastSeen(get(pulseStore)), + })); + }, 1000); + + const account = derived([pub, user, accountStore, pulseStore], ([$pub, $user, $account, $pulse]) => { + if (!$pub || !$user) return $account; + + gun + .user($pub) + .get("pulse") + .on(d => { + pulseStore.set(d); + accountStore.update(acc => ({ ...acc, blink: !acc.blink, pulse: d })); + }); + + if ($user.is) { + gun + .user() + .get("petnames") + .get($pub) + .on(async d => { + const decrypted = await $user.decrypt(d); + accountStore.update(acc => ({ ...acc, petname: decrypted })); + }); + } + + gun + .user($pub) + .get("profile") + .map() + .on((data, key) => { + accountStore.update(acc => ({ + ...acc, + profile: { ...acc.profile, [key]: data }, + })); + }); + + return $account; + }); + + return { + account, + setPetname, + destroy: () => clearInterval(lastSeenInterval), // Funzione per pulire l'intervallo + }; +} + +export async function setPetname(pub: string, name: string): Promise { + const { user } = useUser(); + if (!get(user).is) return; + const gun = useGun(); + const enc = await get(user).encrypt(name); + gun.user().get("petnames").get(pub).put(enc); +} + +export function useAvatar(pub: string, size: number) { + const gun = useGun(); + const { user } = useUser(); + const avatar = writable(""); + const blink = writable(false); + const uploadStatus = writable(""); + + const updateAvatar = () => { + console.log("Updating avatar for pub:", pub); + gun + .user(pub) + .get("avatar") + .once(data => { + console.log("Avatar data:", data); + if (data) { + avatar.set(data); + } else { + avatar.set(`https://avatars.dicebear.com/api/identicon/${pub}.svg?size=${size}`); + } + }); + }; + + updateAvatar(); + + gun + .user(pub) + .get("pulse") + .on(() => { + blink.update(b => !b); + }); + + const uploadAvatar = async (file: File) => { + console.log("Starting avatar upload"); + uploadStatus.set("Iniziando il caricamento..."); + + const currentUser = get(user); + if (!currentUser || !currentUser.is || currentUser.pub !== pub) { + console.error("Utente non autenticato o non autorizzato"); + uploadStatus.set("Errore: Utente non autenticato o non autorizzato"); + return; + } + + try { + const reader = new FileReader(); + reader.onload = async e => { + const base64 = e.target?.result as string; + uploadStatus.set("Salvando avatar..."); + + gun + .user() + .get("avatar") + .put(base64, ack => { + if (ack.err) { + console.error("Errore nel salvare l'avatar:", ack.err); + uploadStatus.set("Errore nel salvare l'avatar"); + } else { + console.log("Avatar salvato con successo"); + uploadStatus.set("Avatar caricato con successo"); + updateAvatar(); + } + }); + }; + reader.readAsDataURL(file); + } catch (error) { + console.error("Errore durante il caricamento dell'avatar:", error); + uploadStatus.set("Errore durante il caricamento. Riprova."); + } + }; + + return { + avatar: derived(avatar, $a => $a), + blink: derived(blink, $b => $b), + uploadAvatar, + uploadStatus: derived(uploadStatus, $s => $s), + }; +} diff --git a/packages/svelte/src/lib/gun/auth.ts b/packages/svelte/src/lib/gun/auth.ts index 286fc5e..765a4fe 100644 --- a/packages/svelte/src/lib/gun/auth.ts +++ b/packages/svelte/src/lib/gun/auth.ts @@ -1,16 +1,21 @@ -import Gun from "gun"; import "gun-eth"; import { getAccount } from "@wagmi/core"; import { currentUser, gun } from "$lib/stores"; -import { get } from "svelte/store"; +import { derived, get, writable } from "svelte/store"; import { notification } from "$lib/utils/scaffold-eth/notification"; import { wagmiConfig } from "$lib/wagmi"; import type { IGunUserInstance } from "gun/types"; -import { auth, leave, useUser } from "./user"; +import { auth, leave, useUser, isPair } from "./user"; import { useGun } from "./gun"; - +import SEA from "gun/sea"; +import { useAccount } from "./account"; const MESSAGE_TO_SIGN = "Accesso a GunDB con Ethereum"; +const { user: userStore } = useUser(); +const { account } = useAccount(userStore.pub); +git +$: console.log("account", account); + export function initializeAuth() { const gun = useGun(); @@ -66,7 +71,7 @@ export async function signIn(): Promise { await gunInstance.createAndStoreEncryptedPair(account.address, signature); return new Promise(resolve => { - user.db.create(account.address, signature, async (ack: { ok: 0; pub: string } | { err: string }) => { + gunInstance.user().create(account.address, signature, async (ack: { ok: 0; pub: string } | { err: string }) => { if ("err" in ack) { resolve("Errore durante la registrazione: " + ack.err); } else { @@ -127,3 +132,184 @@ export async function login(): Promise { export function logout(): void { leave(); } + +/** + * @typedef {Object} Safe + * @property {boolean} saved - Whether data is saved + * @property {string} password - Stored password + * @property {string} enc - Encrypted data + * @property {string} pass - Stored pass + * @property {Object} rooms - Room information + */ + +/** + * @typedef {Object} Auth + * @property {string} input - User input for password + * @property {boolean} show - Whether to show password + * @property {boolean} safePair - Indicates if the pair is safe + * @property {number} minLength - Minimum length for password + * @property {Safe} safe - Safe storage object + * @property {Object} dec - Decrypted data object + * @property {string} [dec.pass] - Decrypted password + * @property {Object} [dec.pair] - Decrypted key pair + * @property {Object} links - Link generation object + * @property {string} links.pass - Generated pass link + * @property {string} links.pair - Generated pair link + * @property {function(): void} set - Function to set password + */ + +interface Auth { + input: string; + show: boolean; + safePair: boolean; + minLength: number; + safe: { + saved: boolean; + password: string; + enc: string; + pass: string; + rooms: Record; + }; + dec: Record; + links: { + pass: string; + pair: string; + }; + set: () => void; +} + +export const pass = writable({ + input: "", + show: false, + safePair: false, + minLength: 5, + safe: { + saved: false, + password: "", + enc: "", + pass: "", + rooms: {}, + }, + dec: {}, + links: { + pass: "", + pair: "", + }, + set: () => {}, +}); + +const { user } = useUser(); + +// Derived stores for links +const passLink = derived([pass], ([$pass]) => genLink($pass.safe?.enc)); +const pairLink = derived([user], ([$user]) => genLink(JSON.stringify($user.pair()))); + +// Update links in the pass store +pass.update(p => ({ + ...p, + links: { + pass: get(passLink), + pair: get(pairLink), + }, +})); + +function genLink(text = "", auth_url = "#/auth/") { + let base = encodeURIComponent(text); + return window.location.origin + window.location.pathname + auth_url + base; +} + +export function parseLink(link: string, auth_url = "#/auth/") { + let index = link.indexOf(auth_url); + let base = link.substring(index + auth_url.length); + return decodeURIComponent(base); +} + +let initiated = false; + +export function useAuth() { + if (!initiated) { + const gun = useGun(); + gun + .user() + .get("safe") + .map() + .on((d, k) => { + pass.update(p => ({ + ...p, + safe: { ...p.safe, [k]: d }, + })); + }); + + // Svelte equivalent of watchEffect + pass.subscribe(async $pass => { + if (!$pass.show) { + pass.update(p => ({ ...p, dec: {} })); + return; + } + if ($pass?.safe?.pass) { + const decPass = await get(user).decrypt($pass.safe.pass); + pass.update(p => ({ + ...p, + dec: { ...p.dec, pass: decPass }, + input: decPass || "", + })); + } + if ($pass?.safe?.enc) { + const decPair = await SEA.decrypt($pass.safe.enc, $pass.dec.pass); + pass.update(p => ({ + ...p, + dec: { ...p.dec, pair: decPair }, + })); + } + }); + } + initiated = true; + return { pass, setPass, authWithPass }; +} + +export async function hasPass(pub: string) { + const gun = useGun(); + return await gun.get(`~${pub}`).get("safe").get("enc").then(); +} + +async function authWithPass(pub: string, passphrase: string) { + const gun = useGun(); + let encPair = await gun.get(`~${pub}`).get("safe").get("enc").then(); + let pair = await SEA.decrypt(encPair, passphrase); + auth(pair); +} + +async function setPass(text: string) { + const gun = useGun(); + let encPair = await SEA.encrypt(get(user).pair(), text); + let encPass = await get(user).encrypt(text); + gun.user().get("safe").get("enc").put(encPair); + gun.user().get("safe").get("pass").put(encPass); +} + +export function useAuthLink(data: string, passPhrase: string) { + if (!data) return; + const decoded = decodeURIComponent(data); + console.log("dec", decoded); + if (decoded.substring(0, 3) == "SEA") { + if (passPhrase) { + authEncPass(decoded, passPhrase); + } + return "encrypted"; + } else { + try { + let d = JSON.parse(decoded); + if (isPair(d)) { + auth(d); + } + return "success"; + } catch (e) { + return "incorrect link"; + } + } +} + +async function authEncPass(encPair: string, passphrase: string) { + let pair = await SEA.decrypt(encPair, passphrase); + auth(pair); +} diff --git a/packages/svelte/src/lib/gun/avatar.ts b/packages/svelte/src/lib/gun/avatar.ts new file mode 100644 index 0000000..60acbfe --- /dev/null +++ b/packages/svelte/src/lib/gun/avatar.ts @@ -0,0 +1,119 @@ +import { derived, writable, get } from "svelte/store"; +import { useGun } from "./gun"; +import { useUser } from "./user"; +import SEA from 'gun/sea'; + +export function useAvatar(pub: string, size: number) { + const gun = useGun(); + const { user } = useUser(); + const avatar = writable(""); + const blink = writable(false); + const uploadStatus = writable(""); + + const updateAvatar = () => { + console.log('Updating avatar for pub:', pub); + gun.user(pub).get('avatar').once((data) => { + console.log('Avatar data:', data); + if (data) { + if (typeof data === 'string' && data.startsWith('#')) { + // L'avatar è salvato come riferimento + gun.get('#').get(data.slice(1)).once((media) => { + console.log('Retrieved media:', media); + if (media) { + try { + const mediaObj = JSON.parse(media); + avatar.set(`data:${mediaObj.type};base64,${mediaObj.b64}`); + } catch (error) { + console.error('Error parsing media:', error); + avatar.set(`https://avatars.dicebear.com/api/identicon/${pub}.svg?size=${size}`); + } + } else { + avatar.set(`https://avatars.dicebear.com/api/identicon/${pub}.svg?size=${size}`); + } + }); + } else if (typeof data === 'string' && data.startsWith('data:')) { + // L'avatar è già in formato base64 + avatar.set(data); + } else { + avatar.set(`https://avatars.dicebear.com/api/identicon/${pub}.svg?size=${size}`); + } + } else { + avatar.set(`https://avatars.dicebear.com/api/identicon/${pub}.svg?size=${size}`); + } + }); + }; + + updateAvatar(); + + gun.user(pub).get("pulse").on(() => { + blink.update(b => !b); + }); + + const uploadAvatar = async (file: File) => { + console.log('Starting avatar upload'); + uploadStatus.set('Iniziando il caricamento...'); + + const currentUser = get(user); + if (!currentUser || !currentUser.is || currentUser.pub !== pub) { + console.error('Utente non autenticato o non autorizzato'); + uploadStatus.set('Errore: Utente non autenticato o non autorizzato'); + return; + } + + try { + const reader = new FileReader(); + reader.onload = async (e) => { + const base64 = e.target?.result as string; + const mediaType = file.type; + const media = JSON.stringify({ b64: base64.split(',')[1], type: mediaType }); + + console.log('Media prepared:', media.substring(0, 50) + '...'); + uploadStatus.set('Generando hash...'); + + const mediaID = await SEA.work(media, null, null, { name: "SHA-256" }); + console.log('Generated mediaID:', mediaID); + + uploadStatus.set('Salvando media...'); + await new Promise((resolve, reject) => { + gun.get('#').get(mediaID).put(media, (ack) => { + if (ack.err) { + console.error('Errore nel salvare il media:', ack.err); + reject(ack.err); + } else { + console.log('Media salvato con successo'); + resolve(); + } + }); + }); + + // Salva il riferimento all'avatar nello spazio utente + await new Promise((resolve, reject) => { + gun.user().get('avatar').put('#' + mediaID, (ack) => { + if (ack.err) { + console.error('Errore nel salvare il riferimento avatar:', ack.err); + reject(ack.err); + } else { + console.log('Riferimento avatar salvato con successo'); + resolve(); + } + }); + }); + + console.log('Avatar upload completed'); + uploadStatus.set('Caricamento completato!'); + updateAvatar(); + }; + reader.readAsDataURL(file); + } catch (error) { + console.error("Errore durante il caricamento dell'avatar:", error); + uploadStatus.set('Errore durante il caricamento. Riprova.'); + } + }; + + return { + avatar: derived(avatar, $a => $a), + blink: derived(blink, $b => $b), + uploadAvatar, + uploadStatus: derived(uploadStatus, $s => $s) + }; +} diff --git a/packages/svelte/src/lib/gun/colors.ts b/packages/svelte/src/lib/gun/colors.ts new file mode 100644 index 0000000..15aa987 --- /dev/null +++ b/packages/svelte/src/lib/gun/colors.ts @@ -0,0 +1,52 @@ +/** + * Deterministic colors derived from pub keys, hashes or any other string data + * @module Color + * @group UI + * */ + +import ColorHash from "color-hash"; + +/** + * @typedef {'light' | 'regular' | 'deep' | 'dark'} Palette + */ + +const color = { + light: new ColorHash({ + saturation: [0.05, 0.08, 0.22], + lightness: [0.85, 0.87, 0.9], + }), + pale: new ColorHash({ + saturation: [0.05, 0.42, 0.52], + lightness: [0.75, 0.77, 0.9], + }), + regular: new ColorHash({ + saturation: [0.1, 0.5, 0.7], + lightness: [0.3, 0.5, 0.7], + }), + deep: new ColorHash({ + saturation: [0.5, 0.6, 0.7], + lightness: [0.2, 0.35, 0.4], + }), + dark: new ColorHash({ + saturation: [0.02, 0.5, 0.6], + lightness: [0.18, 0.2, 0.5], + }), +}; + +/** + * Get a color generator of a certain palette + * @param {Palette} [palette='deep'] + * @returns {ColorHash} Color-Hash instance + * @see https://github.com/zenozeng/color-hash + * @example + * import {useColor} from '@gun-vue/composables' + * const colorDeep = useColor('deep') + * const color = colorDeep.hex('any text data') + * // color == '#e052ae' + */ +export function useColor(palette = "deep") { + if (typeof palette == "object") { + return new ColorHash(palette); + } + return color[palette]; +} diff --git a/packages/svelte/src/lib/gun/crypto.ts b/packages/svelte/src/lib/gun/crypto.ts new file mode 100644 index 0000000..fdc3835 --- /dev/null +++ b/packages/svelte/src/lib/gun/crypto.ts @@ -0,0 +1,174 @@ +/** + * SEA cryptography abstraction + * @module Crypto + * @group Crypto + */ + +// https://github.com/amark/gun/wiki/Snippets + +import SEA from "gun/sea"; + +/** + * Checks if a given string is a valid hash. + * + * @param {string} str - The string to check. + * @returns {boolean} - Returns true if the string is a valid hash, otherwise false. + */ +export function isHash(str) { + return typeof str === "string" && str.length === 44 && str.charAt(43) === "="; +} + +/** + * @typedef {Object} Entity + * @property {string} pub - The public key. + * @property {string} epub - The elliptic encryption epub. + */ + +/** + * Encrypts data for one receiver entity. + * 1. Generates encryption secret using receiver's epub and sender's pair. + * 2. Encrypts data with this secret. + * + * @param {string} data - Stringified data to be encrypted. + * @param {Object} sender - SEA Pair of the sender – `epriv` key will be used to encrypt the data. + * @param {Entity} receiver - An object with `pub` and `epub` strings - the user.is object of the receiver's account. + * @returns {Promise} - Encrypted data string to be sent. + */ +export async function encFor(data, sender, receiver) { + const secret = await SEA.secret(receiver.epub, sender); + const encryptedData = await SEA.encrypt(data, secret); + return encryptedData; +} + +/** + * Decrypts a private message from an entity. + * 1. Generates secret using sender's `epub` and receiver's pair. + * 2. Decrypts the data with this secret. + * + * @param {string} data - Encrypted private data. + * @param {Entity} sender - An object with `pub` and `epub` strings - the user.is object of the sender's account. + * @param {Object} receiver - SEA Pair of the receiver – `epriv` key will be used to decrypt the data. + * @returns {Promise} - Decrypted data. + */ +export async function decFrom(data, sender, receiver) { + const secret = await SEA.secret(sender.epub, receiver); + const decryptedData = await SEA.decrypt(data, secret); + return decryptedData; +} + +/** + * Generates a SHA-256 hash for the given text. + * + * @param {string} text - The text to hash. + * @returns {Promise} - The generated hash. + */ +export async function hashText(text) { + let hash = await SEA.work(text, null, null, { name: "SHA-256" }); + return hash; +} + +/** + * Generates a SHA-256 hash for the given object. + * + * @param {object} obj - The object to hash. + * @returns {Promise<{hash: string, hashed: string}>} - The generated hash and stringified object. + */ +export async function hashObj(obj) { + let hashed = typeof obj === "string" ? obj : JSON.stringify(obj); + let hash = await hashText(hashed); + return { hash, hashed }; +} + +/** + * Calculates a hex hash for any string data. + * + * @param {string} text - The text to hash. + * @param {string} salt - The salt to use in the hash. + * @returns {Promise} - The hex encoded SHA-1 hash. + */ +export async function getShortHash(text, salt) { + return await SEA.work(text, null, null, { + name: "PBKDF2", + encode: "hex", + salt, + }); +} + +/** + * Converts an unsafe base64 string to a URL-safe base64 string. + * + * @param {string} unsafe - The unsafe base64 string. + * @returns {string} - The URL-safe base64 string. + */ +export function safeHash(unsafe) { + if (!unsafe) return; + const encode_regex = /[+=/]/g; + return unsafe.replace(encode_regex, encodeChar); +} + +/** + * Encodes a character for URL-safe base64. + * + * @param {string} c - The character to encode. + * @returns {string} - The encoded character. + */ +function encodeChar(c) { + switch (c) { + case "+": + return "-"; + case "=": + return "."; + case "/": + return "_"; + } +} + +/** + * Converts a URL-safe base64 string to an unsafe base64 string. + * + * @param {string} safe - The URL-safe base64 string. + * @returns {string} - The unsafe base64 string. + */ +export function unsafeHash(safe) { + if (!safe) return; + const decode_regex = /[._-]/g; + return safe.replace(decode_regex, decodeChar); +} + +/** + * Decodes a URL-safe base64 character. + * + * @param {string} c - The character to decode. + * @returns {string} - The decoded character. + */ +function decodeChar(c) { + switch (c) { + case "-": + return "+"; + case ".": + return "="; + case "_": + return "/"; + } +} + +/** + * Safely parses a JSON string, returning a default object if parsing fails. + * + * @param {string|object} input - The JSON string or object to parse. + * @param {object} [def={}] - The default object to return if parsing fails. + * @returns {object} - The parsed object or the default object. + */ +export function safeJSONParse(input, def = {}) { + // Convert null to empty object + if (!input) { + return def; + } else if (typeof input === "object") { + return input; + } + try { + return JSON.parse(input); + } catch (e) { + return def; + } +} \ No newline at end of file diff --git a/packages/svelte/src/lib/gun/user.ts b/packages/svelte/src/lib/gun/user.ts index 7016aa2..8c5e068 100644 --- a/packages/svelte/src/lib/gun/user.ts +++ b/packages/svelte/src/lib/gun/user.ts @@ -103,9 +103,11 @@ function pair() { export function useUser() { if (!get(user).initiated) { const gun = useGun(); - user.update(u => ({ ...u, db: gun.user(), is: "" })); gun.user().recall({ sessionStorage: true }, () => { console.log("user was recalled"); + if (gun.user().is) { + init(); + } }); gun.on("auth", () => { @@ -133,69 +135,38 @@ export function useUser() { export async function auth(pair, cb = pair => {}) { const gun = useGun(); + if (!isPair(pair)) { console.log("incorrect pair", pair); return; } + gun.user().auth(pair, async ack => { if (ack.err) { console.error("Errore di autenticazione:", ack.err); cb(ack); } else { init(); - console.log("user is authenticated"); const { user } = useUser(); user.update(u => ({ ...u, auth: true })); - console.log("User:", user); + console.log("Successo autenticazione"); cb(ack); } }); } -async function init() { +function init() { const gun = useGun(); - return new Promise(resolve => { - gun.on("auth", () => { - user.update(u => ({ ...u, is: gun.user().is })); - if (user.pulser) { - clearInterval(user.pulser); - } - user.update(u => ({ - ...u, - pulser: setInterval(() => { - gun.user().get("pulse").put(Date.now()); - }, 1000), - })); - - gun.user().get("epub").put(user.is.epub); - - gun - .user() - .get("pulse") - .on(d => { - user.update(u => ({ ...u, blink: !u.blink, pulse: d })); - }); - - gun - .user() - .get("safe") - .map() - .on((d, k) => { - user.update(u => ({ ...u, safe: { ...u.safe, [k]: d } })); - }); - - gun - .user() - .get("profile") - .get("name") - .on(d => { - user.update(u => ({ ...u, name: d })); - }); + const pub = gun.user().is?.pub; + user.update(u => ({ ...u, db: gun.user(), is: gun.user().is, pub: pub })); + console.log("User initialized:", get(user)); +} - user.update(u => ({ ...u, initiated: true })); - resolve(); - }); - }); +function startPulseTimer() { + const gun = useGun(); + setInterval(() => { + gun.user().get('pulse').put(Date.now()); + }, 1000); // Invia un pulse ogni secondo } /** diff --git a/packages/svelte/src/routes/auth/+page.svelte b/packages/svelte/src/routes/auth/+page.svelte index 1f0d873..5fb5e01 100644 --- a/packages/svelte/src/routes/auth/+page.svelte +++ b/packages/svelte/src/routes/auth/+page.svelte @@ -8,6 +8,9 @@ import { initializeAuth, signIn, login, logout } from "$lib/gun/auth"; import { useUser } from "$lib/gun/user"; + import AccountProfile from "$lib/components/gun/account/AccountProfile.svelte"; + import AccountAvatar from "$lib/components/gun/account/AccountAvatar.svelte"; + let errorMessage: string | null = null; let userPair: Record | null = null; @@ -90,7 +93,13 @@ {:else}
-

Benvenuto, {$currentUser}!

+

Benvenuto, {$user?.pub}!

+ +
+ + + +
{#if userPair && Object.keys(userPair).length > 0}
diff --git a/yarn.lock b/yarn.lock index a4ae186..5e43c56 100644 --- a/yarn.lock +++ b/yarn.lock @@ -36,6 +36,31 @@ __metadata: languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/helper-string-parser@npm:7.25.7" + checksum: 0835fda5efe02cdcb5144a939b639acc017ba4aa1cc80524b44032ddb714080d3e40e8f0d3240832b7bd86f5513f0b63d4fe77d8fc52d8c8720ae674182c0753 + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/helper-validator-identifier@npm:7.25.7" + checksum: 062f55208deead4876eb474dc6fd55155c9eada8d0a505434de3b9aa06c34195562e0f3142b22a08793a38d740238efa2fe00ff42956cdcb8ac03f0b6c542247 + languageName: node + linkType: hard + +"@babel/parser@npm:^7.25.3": + version: 7.25.8 + resolution: "@babel/parser@npm:7.25.8" + dependencies: + "@babel/types": ^7.25.8 + bin: + parser: ./bin/babel-parser.js + checksum: c33f6d26542f156927c5dbe131265c791177d271e582338e960f803903086ec5c152bf25deae5f4c061b7bee14dc0b5fd2882ccb5a21c16ee0738d24fcc0406e + languageName: node + linkType: hard + "@babel/runtime@npm:^7.19.4, @babel/runtime@npm:^7.20.6, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.23.2": version: 7.25.6 resolution: "@babel/runtime@npm:7.25.6" @@ -45,6 +70,17 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/types@npm:7.25.8" + dependencies: + "@babel/helper-string-parser": ^7.25.7 + "@babel/helper-validator-identifier": ^7.25.7 + to-fast-properties: ^2.0.0 + checksum: 93d84858e820dbfa0fc4882b3ba6a421544d224ee61455a58eed0af9fc3518b30dc2166b8ba48cdd2e91083c5885ed773c36acf46d177b7b1fad9c35b6eb7639 + languageName: node + linkType: hard + "@byteatatime/wagmi-svelte@npm:^0.1.1": version: 0.1.1 resolution: "@byteatatime/wagmi-svelte@npm:0.1.1" @@ -2320,6 +2356,7 @@ __metadata: "@sveltejs/kit": ^2.0.0 "@sveltejs/vite-plugin-svelte": ^4.0.0-next.7 "@tanstack/svelte-query": ^5.28.9 + "@types/color-hash": ^2 "@types/d3": ^7 "@types/dompurify": ^3 "@types/eslint": ^8.56.0 @@ -2335,6 +2372,7 @@ __metadata: autoprefixer: ^10.4.16 blo: ^1.1.1 buffer: ^6.0.3 + color-hash: ^2.0.2 d3: ^7.9.0 daisyui: ^4.4.20 dompurify: ^3.1.6 @@ -2346,6 +2384,7 @@ __metadata: express: ^4.21.0 express-rate-limit: ^7.4.0 gun: ^0.2020.1240 + gun-avatar: ^1.9.4 gun-eth: ^1.2.13 ip: ^2.0.1 js-sha256: ^0.11.0 @@ -2367,6 +2406,7 @@ __metadata: tslib: ^2.4.1 type-fest: ^4.14.0 typescript: ^5.0.0 + url-regex: ^5.0.0 vercel: ^34.0.0 viem: ^2.21.7 vis-network: ^9.1.9 @@ -2874,6 +2914,13 @@ __metadata: languageName: node linkType: hard +"@types/color-hash@npm:^2": + version: 2.0.0 + resolution: "@types/color-hash@npm:2.0.0" + checksum: 7eedc8a4e09049a0bd4bbc6159e977c8151f65a63d6ea42651f0326e10cdd02644e93f1c81e2d8a7df87af6054cd0f2d3cd8ee966c18ced0e4de935f6e280d60 + languageName: node + linkType: hard + "@types/concat-stream@npm:^1.6.0": version: 1.6.1 resolution: "@types/concat-stream@npm:1.6.1" @@ -3388,6 +3435,13 @@ __metadata: languageName: node linkType: hard +"@types/web-bluetooth@npm:^0.0.16": + version: 0.0.16 + resolution: "@types/web-bluetooth@npm:0.0.16" + checksum: f68a630d062202a25c46d48686ebae1cf429dc70b4578fcf13b8357b2db63e4aedfb6f6d752bd388366be46ebd19c1c9de45f8a15c2631bb79e904fdfc454f94 + languageName: node + linkType: hard + "@typescript-eslint/eslint-plugin@npm:^7.0.0": version: 7.18.0 resolution: "@typescript-eslint/eslint-plugin@npm:7.18.0" @@ -3673,6 +3727,13 @@ __metadata: languageName: node linkType: hard +"@unocss/reset@npm:0.48.4": + version: 0.48.4 + resolution: "@unocss/reset@npm:0.48.4" + checksum: f71763fdd1b04929277603d6c17c0f0e0872ad0348a3efc1db1797161da747c120063ca104dd7347187a53c5fca9399f61ff1503648c6490337cc9fb9863233a + languageName: node + linkType: hard + "@vercel/build-utils@npm:8.3.2": version: 8.3.2 resolution: "@vercel/build-utils@npm:8.3.2" @@ -3889,6 +3950,134 @@ __metadata: languageName: node linkType: hard +"@vue/compiler-core@npm:3.5.12": + version: 3.5.12 + resolution: "@vue/compiler-core@npm:3.5.12" + dependencies: + "@babel/parser": ^7.25.3 + "@vue/shared": 3.5.12 + entities: ^4.5.0 + estree-walker: ^2.0.2 + source-map-js: ^1.2.0 + checksum: 341e5ded344192d71ba940d01b24e6fad400bea3ccbb093f3c57a6c952ad1ba1b6eb622ddc7be7401aebcac3875f1ebdcb6550f7fe9a3debb323d528944ae86b + languageName: node + linkType: hard + +"@vue/compiler-dom@npm:3.5.12": + version: 3.5.12 + resolution: "@vue/compiler-dom@npm:3.5.12" + dependencies: + "@vue/compiler-core": 3.5.12 + "@vue/shared": 3.5.12 + checksum: 519c5a3ba0aca1c712abaa3e77322361339cbff0d997bee5c1ed1338145641e8d0510849ff37938396cf7fe796521d9eac47fbd1fe128ef4dc3a39b28f1e6f5a + languageName: node + linkType: hard + +"@vue/compiler-sfc@npm:3.5.12": + version: 3.5.12 + resolution: "@vue/compiler-sfc@npm:3.5.12" + dependencies: + "@babel/parser": ^7.25.3 + "@vue/compiler-core": 3.5.12 + "@vue/compiler-dom": 3.5.12 + "@vue/compiler-ssr": 3.5.12 + "@vue/shared": 3.5.12 + estree-walker: ^2.0.2 + magic-string: ^0.30.11 + postcss: ^8.4.47 + source-map-js: ^1.2.0 + checksum: cbf90d7c1f3920323056a83a0fdab90b156f4f2849beb77b173dd09298b3a12b805a05b276908f75f890823e807dabe850d97670b0d2d1136e82fe834b64e06d + languageName: node + linkType: hard + +"@vue/compiler-ssr@npm:3.5.12": + version: 3.5.12 + resolution: "@vue/compiler-ssr@npm:3.5.12" + dependencies: + "@vue/compiler-dom": 3.5.12 + "@vue/shared": 3.5.12 + checksum: bddbea9e9bab2f047ea8374623cbcbe3f65f3ac904859335810b760b943e207527e738cc8b494bc55f03cbf56129c1055ce046b654f516b5123ae5231b67d022 + languageName: node + linkType: hard + +"@vue/reactivity@npm:3.5.12": + version: 3.5.12 + resolution: "@vue/reactivity@npm:3.5.12" + dependencies: + "@vue/shared": 3.5.12 + checksum: 4285d429e2f7eaff4ac9aac7c506a9ce7401256fce60158ae9f2b5d9ba70dc8474d38e1aceab5ce93caa9beeb968a27a7ca46de2bac0b50c9ece72cc448328bd + languageName: node + linkType: hard + +"@vue/runtime-core@npm:3.5.12": + version: 3.5.12 + resolution: "@vue/runtime-core@npm:3.5.12" + dependencies: + "@vue/reactivity": 3.5.12 + "@vue/shared": 3.5.12 + checksum: 16b9a72a4eb72e82239a6519fb7ce90691060f69156cd7bc805c18dca290ae7b624892b65b12019b6de81fe11763ab4d1c814987eb9bc32c8879dcb44c73b8e8 + languageName: node + linkType: hard + +"@vue/runtime-dom@npm:3.5.12": + version: 3.5.12 + resolution: "@vue/runtime-dom@npm:3.5.12" + dependencies: + "@vue/reactivity": 3.5.12 + "@vue/runtime-core": 3.5.12 + "@vue/shared": 3.5.12 + csstype: ^3.1.3 + checksum: d47d71877d125ce833da508036dcaf6ea5dd5e0eec81aadec301b25cf600c0d269abe0cf5cecc107b0bef3a558f50e3447d2d4b22b2cbdc0eb40615f8c39cc00 + languageName: node + linkType: hard + +"@vue/server-renderer@npm:3.5.12": + version: 3.5.12 + resolution: "@vue/server-renderer@npm:3.5.12" + dependencies: + "@vue/compiler-ssr": 3.5.12 + "@vue/shared": 3.5.12 + peerDependencies: + vue: 3.5.12 + checksum: 39518149d2f1e9339441482b1c52ea570431ceb42a5ec713c9dc13fe20d2abc1e38a318934a993fc88f9f4ac88aeb45694c4b93bf4ec156206bca0d71353fa21 + languageName: node + linkType: hard + +"@vue/shared@npm:3.5.12": + version: 3.5.12 + resolution: "@vue/shared@npm:3.5.12" + checksum: 11d14773ee39525d8cdd33eb45954f5b3458db41fc2e7e91603583a8ea40ea1fe423854874c89d6f67ce4a6d361af6042cbd0eb41a127ca4a3ba99602f3b80aa + languageName: node + linkType: hard + +"@vueuse/core@npm:9.11.1": + version: 9.11.1 + resolution: "@vueuse/core@npm:9.11.1" + dependencies: + "@types/web-bluetooth": ^0.0.16 + "@vueuse/metadata": 9.11.1 + "@vueuse/shared": 9.11.1 + vue-demi: "*" + checksum: c9b4d65d50441d19d452dfebe4e13c8b56a8ea296b31b9cd93b5a52ee1b461c40ebc530415d3869083852eb874c99d5742311fd8bf750d405f4d52a768aebaa3 + languageName: node + linkType: hard + +"@vueuse/metadata@npm:9.11.1": + version: 9.11.1 + resolution: "@vueuse/metadata@npm:9.11.1" + checksum: d4025257dc891b4ae3712a68b881409e7a58517f7b96f8949a74a99cec5e46f202fc2c326765eed9bffc8fbde6417ee737d3c2ac650799dd9e99fa5a7f9c2363 + languageName: node + linkType: hard + +"@vueuse/shared@npm:9.11.1": + version: 9.11.1 + resolution: "@vueuse/shared@npm:9.11.1" + dependencies: + vue-demi: "*" + checksum: 373a725ba87dd84eec3882cef4dc92706f28b53256f1c23a800444c12de675c71c1773c254def5d9c3096e54b9830154d13540264c1f017770395f04b07d789f + languageName: node + linkType: hard + "@wagmi/connectors@npm:^4.1.14": version: 4.3.10 resolution: "@wagmi/connectors@npm:4.3.10" @@ -6151,6 +6340,13 @@ __metadata: languageName: node linkType: hard +"color-hash@npm:^2.0.2": + version: 2.0.2 + resolution: "color-hash@npm:2.0.2" + checksum: 0ebc108f5ef215df60f4cd12d3005fa97335a667d00a4c652b966de3fc40556df7264acf594a994dbb71baa676a2d3b0ce6f39a390ff4199301bd44e0500de73 + languageName: node + linkType: hard + "color-name@npm:1.1.3": version: 1.1.3 resolution: "color-name@npm:1.1.3" @@ -6485,7 +6681,7 @@ __metadata: languageName: node linkType: hard -"csstype@npm:^3.0.2": +"csstype@npm:^3.0.2, csstype@npm:^3.1.3": version: 3.1.3 resolution: "csstype@npm:3.1.3" checksum: 8db785cc92d259102725b3c694ec0c823f5619a84741b5c7991b8ad135dfaa66093038a1cc63e03361a6cd28d122be48f2106ae72334e067dd619a51f49eddf7 @@ -7326,6 +7522,13 @@ __metadata: languageName: node linkType: hard +"entities@npm:^4.5.0": + version: 4.5.0 + resolution: "entities@npm:4.5.0" + checksum: 853f8ebd5b425d350bffa97dd6958143179a5938352ccae092c62d1267c4e392a039be1bae7d51b6e4ffad25f51f9617531fedf5237f15df302ccfb452cbf2d7 + languageName: node + linkType: hard + "env-paths@npm:^2.2.0": version: 2.2.1 resolution: "env-paths@npm:2.2.1" @@ -9124,6 +9327,18 @@ __metadata: languageName: node linkType: hard +"gun-avatar@npm:^1.9.4": + version: 1.9.4 + resolution: "gun-avatar@npm:1.9.4" + dependencies: + "@unocss/reset": 0.48.4 + "@vueuse/core": 9.11.1 + gun: 0.2020.1239 + vue: ^3.2.45 + checksum: de87c886f7a0804dafe298ffadb831edd54aaad42e3a963d5ea8d64879fd20f379252af934c203669f26dbb06498893a537840cfbf2a5f33606955cc25287d89 + languageName: node + linkType: hard + "gun-eth@npm:^1.2.13": version: 1.2.13 resolution: "gun-eth@npm:1.2.13" @@ -9134,6 +9349,19 @@ __metadata: languageName: node linkType: hard +"gun@npm:0.2020.1239": + version: 0.2020.1239 + resolution: "gun@npm:0.2020.1239" + dependencies: + "@peculiar/webcrypto": ^1.1.1 + ws: ^7.2.1 + dependenciesMeta: + "@peculiar/webcrypto": + optional: true + checksum: c9ef881a725dd90e9bc51eecde8be3a32cd331a88576dc92434394c192cf324f97568ac06fea61d92539d3e6ae2d7c0fb6e666a309012b14c170e592d4a58616 + languageName: node + linkType: hard + "gun@npm:^0.2020.1239, gun@npm:^0.2020.1240": version: 0.2020.1240 resolution: "gun@npm:0.2020.1240" @@ -9740,6 +9968,13 @@ __metadata: languageName: node linkType: hard +"ip-regex@npm:^4.1.0": + version: 4.3.0 + resolution: "ip-regex@npm:4.3.0" + checksum: 7ff904b891221b1847f3fdf3dbb3e6a8660dc39bc283f79eb7ed88f5338e1a3d1104b779bc83759159be266249c59c2160e779ee39446d79d4ed0890dfd06f08 + languageName: node + linkType: hard + "ip@npm:^2.0.1": version: 2.0.1 resolution: "ip@npm:2.0.1" @@ -12068,7 +12303,7 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.4.23, postcss@npm:^8.4.32, postcss@npm:^8.4.38, postcss@npm:^8.4.39, postcss@npm:^8.4.43": +"postcss@npm:^8.4.23, postcss@npm:^8.4.32, postcss@npm:^8.4.38, postcss@npm:^8.4.39, postcss@npm:^8.4.43, postcss@npm:^8.4.47": version: 8.4.47 resolution: "postcss@npm:8.4.47" dependencies: @@ -13412,7 +13647,7 @@ __metadata: languageName: node linkType: hard -"source-map-js@npm:^1.2.1": +"source-map-js@npm:^1.2.0, source-map-js@npm:^1.2.1": version: 1.2.1 resolution: "source-map-js@npm:1.2.1" checksum: 4eb0cd997cdf228bc253bcaff9340afeb706176e64868ecd20efbe6efea931465f43955612346d6b7318789e5265bdc419bc7669c1cebe3db0eb255f57efa76b @@ -14150,6 +14385,15 @@ __metadata: languageName: node linkType: hard +"tlds@npm:^1.203.0": + version: 1.255.0 + resolution: "tlds@npm:1.255.0" + bin: + tlds: bin.js + checksum: 2c6a46d87e24304825131323c7ae2067a66611ef107e4f1e8db55a56152870b252c03eb25410ed73a2950218c0691e33d5955f64db99e0d9b3ce50729b60f3ad + languageName: node + linkType: hard + "tmp@npm:0.0.33": version: 0.0.33 resolution: "tmp@npm:0.0.33" @@ -14159,6 +14403,13 @@ __metadata: languageName: node linkType: hard +"to-fast-properties@npm:^2.0.0": + version: 2.0.0 + resolution: "to-fast-properties@npm:2.0.0" + checksum: be2de62fe58ead94e3e592680052683b1ec986c72d589e7b21e5697f8744cdbf48c266fa72f6c15932894c10187b5f54573a3bcf7da0bfd964d5caf23d436168 + languageName: node + linkType: hard + "to-regex-range@npm:^5.0.1": version: 5.0.1 resolution: "to-regex-range@npm:5.0.1" @@ -14771,6 +15022,16 @@ __metadata: languageName: node linkType: hard +"url-regex@npm:^5.0.0": + version: 5.0.0 + resolution: "url-regex@npm:5.0.0" + dependencies: + ip-regex: ^4.1.0 + tlds: ^1.203.0 + checksum: 94f63ddd39bbc935fa3ee34ab496e1a62b2b6826982e5f7515b8b52acd6439e489d78fc2e202ab4585e0a5cc9cceca15993206153213affa928c41ce819e055c + languageName: node + linkType: hard + "use-sync-external-store@npm:1.2.0": version: 1.2.0 resolution: "use-sync-external-store@npm:1.2.0" @@ -15041,6 +15302,40 @@ __metadata: languageName: node linkType: hard +"vue-demi@npm:*": + version: 0.14.10 + resolution: "vue-demi@npm:0.14.10" + peerDependencies: + "@vue/composition-api": ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + "@vue/composition-api": + optional: true + bin: + vue-demi-fix: bin/vue-demi-fix.js + vue-demi-switch: bin/vue-demi-switch.js + checksum: 9b4106f99be3b0c1dd4a6dc5725f5e8f79c6b98d1eeb849bf2c54416cd77f4aa344960b202768865245cfa82d57f49a9d96f67f5d8e256604b9dac1c5df9a8d6 + languageName: node + linkType: hard + +"vue@npm:^3.2.45": + version: 3.5.12 + resolution: "vue@npm:3.5.12" + dependencies: + "@vue/compiler-dom": 3.5.12 + "@vue/compiler-sfc": 3.5.12 + "@vue/runtime-dom": 3.5.12 + "@vue/server-renderer": 3.5.12 + "@vue/shared": 3.5.12 + peerDependencies: + typescript: "*" + peerDependenciesMeta: + typescript: + optional: true + checksum: 06c233199de6e06f047ee7d8f6ffd85b20cb711d8195330de748e9bb827894ece6e4f4398a6850d06902e39b29eef14f598c31942cbd4917b8edb7560b561378 + languageName: node + linkType: hard + "web-vitals@npm:0.2.4": version: 0.2.4 resolution: "web-vitals@npm:0.2.4"