Compare commits

8 Commits
master ... dev

Author SHA1 Message Date
4b8fc1abdb login stuff and user profile 2024-06-28 06:16:32 -04:00
nate
9cd73c12db Login stuff 2024-06-28 05:09:32 -04:00
68600a4c4b Login stuff 2024-06-25 09:36:15 -04:00
nate
b1066a1584 login stuff 2024-06-17 21:19:34 -04:00
nate
4712566d20 login stuff 2024-06-16 22:23:42 -04:00
nate
b5612876be navbar toggle and event dispatch 2024-06-16 07:19:41 -04:00
nate
a7ec9a5100 Working on login 2024-06-14 16:43:05 -04:00
nate
13ea2ccd9e Nostr access 2024-06-12 19:00:18 -04:00
29 changed files with 782 additions and 118 deletions

1
.gitignore vendored
View File

@@ -10,3 +10,4 @@ pnpm-lock.yaml
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
.vscode

83
package-lock.json generated
View File

@@ -8,15 +8,20 @@
"name": "cofabricate",
"version": "0.0.1",
"dependencies": {
"@noble/hashes": "^1.4.0",
"@nostr-dev-kit/ndk": "^2.8.2",
"@nostr-dev-kit/ndk-svelte": "^2.2.15",
"@nostr-dev-kit/ndk-svelte-components": "^2.2.16"
"@nostr-dev-kit/ndk-svelte-components": "^2.2.16",
"markdown-it": "^14.1.0",
"sanitize-html": "^2.13.0"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"@types/eslint": "^8.56.7",
"@types/markdown-it": "^14.1.1",
"@types/sanitize-html": "^2.11.0",
"eslint": "^9.0.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.36.0",
@@ -1212,11 +1217,42 @@
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"dev": true
},
"node_modules/@types/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
"dev": true
},
"node_modules/@types/markdown-it": {
"version": "14.1.1",
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.1.tgz",
"integrity": "sha512-4NpsnpYl2Gt1ljyBGrKMxFYAYvpqbnnkgP/i/g+NLpjEUa3obn1XJCur9YbEXKDAkaXqsR1LbDnGEJ0MmKFxfg==",
"dev": true,
"dependencies": {
"@types/linkify-it": "^5",
"@types/mdurl": "^2"
}
},
"node_modules/@types/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
"dev": true
},
"node_modules/@types/pug": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.10.tgz",
"integrity": "sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA=="
},
"node_modules/@types/sanitize-html": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.11.0.tgz",
"integrity": "sha512-7oxPGNQHXLHE48r/r/qjn7q0hlrs3kL7oZnGj0Wf/h9tj/6ibFyRkNbsDxaBBZ4XUZ0Dx5LGCyDJ04ytSofacQ==",
"dev": true,
"dependencies": {
"htmlparser2": "^8.0.0"
}
},
"node_modules/@types/unist": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz",
@@ -1626,8 +1662,7 @@
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"node_modules/aria-query": {
"version": "5.3.0",
@@ -3082,6 +3117,14 @@
"node": ">=10"
}
},
"node_modules/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
"dependencies": {
"uc.micro": "^2.0.0"
}
},
"node_modules/local-pkg": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz",
@@ -3149,6 +3192,22 @@
"@jridgewell/sourcemap-codec": "^1.4.15"
}
},
"node_modules/markdown-it": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
"integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
"dependencies": {
"argparse": "^2.0.1",
"entities": "^4.4.0",
"linkify-it": "^5.0.0",
"mdurl": "^2.0.0",
"punycode.js": "^2.3.1",
"uc.micro": "^2.1.0"
},
"bin": {
"markdown-it": "bin/markdown-it.mjs"
}
},
"node_modules/marked": {
"version": "9.1.6",
"resolved": "https://registry.npmjs.org/marked/-/marked-9.1.6.tgz",
@@ -3192,6 +3251,11 @@
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
"integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA=="
},
"node_modules/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="
},
"node_modules/merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@@ -3829,6 +3893,14 @@
"node": ">=6"
}
},
"node_modules/punycode.js": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
"engines": {
"node": ">=6"
}
},
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -4591,6 +4663,11 @@
"resolved": "https://registry.npmjs.org/typescript-lru-cache/-/typescript-lru-cache-2.0.0.tgz",
"integrity": "sha512-Jp57Qyy8wXeMkdNuZiglE6v2Cypg13eDA1chHwDG6kq51X7gk4K7P7HaDdzZKCxkegXkVHNcPD0n5aW6OZH3aA=="
},
"node_modules/uc.micro": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="
},
"node_modules/ufo": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz",

View File

@@ -3,7 +3,7 @@
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev",
"dev": "vite dev --host 0.0.0.0 --port 8091",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",

View File

@@ -1,18 +1,17 @@
<!doctype html>
<html data-theme="dark" lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
<!-- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css" /> -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.0/css/bulma.min.css" />
</head>
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css" />
</head>
<body data-sveltekit-preload-data="hover">
<main class="container">
<div style="display: contents">%sveltekit.body%</div>
</main>
</body>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">
<div class="container is-fluid">%sveltekit.body%</div>
</div>
</body>
</html>

18
src/lib/app.d.ts vendored Normal file
View File

@@ -0,0 +1,18 @@
declare global {
namespace App {
interface UserProfile {
id?: string;
name?: string;
displayName?: string;
image?: string;
banner?: string;
bio?: string;
nip05?: string;
lud16?: string;
about?: string;
zapService?: string;
}
}
}
export {};

View File

@@ -0,0 +1,51 @@
<script lang="ts">
import { ndk } from '$lib/stores/nostr';
import { currentUser } from '$lib/store';
import NDK, { NDKUser } from '@nostr-dev-kit/ndk';
import { get } from 'svelte/store';
import { onMount } from 'svelte';
let image: string | undefined;
let _ndk = get(ndk);
let sessionProfile: NDKUser | null = null;
onMount(() => {
const sessionProfileObj = sessionStorage.getItem('user');
sessionProfile = sessionProfileObj ? JSON.parse(sessionProfileObj) : null;
if (sessionProfile) {
image = sessionProfile.profile?.image;
}
});
const _currentUser = get(currentUser);
let random = (Math.random() + 1).toString(36).substring(6);
let defaultImage = `https://robohash.org/${random}`;
image = defaultImage;
$: {
console.debug(sessionProfile);
image = sessionProfile?.profile?.image;
}
$: ndkUpdated(_ndk);
function ndkUpdated(_ndk: NDK) {
console.debug('NDK updated');
if (_ndk.activeUser && _ndk.activeUser.profile && _ndk.activeUser.profile.image) {
image = _ndk.activeUser.profile.image;
} else {
image = defaultImage;
}
_ndk = _ndk;
}
</script>
<figure class="image">
{#if !_ndk.activeUser}
<img src={defaultImage} alt="Generic Avatar" class="is-rounded" />
{:else}
<img src={image} alt="User Avatar" class="is-rounded" />
{/if}
</figure>

View File

@@ -0,0 +1,9 @@
<!-- ndk-svelte-components/LoginDropdown.svelte -->
<script lang="ts">
import { ndk } from '$lib/stores/nostr';
import Nip07Button from './LoginModal/Nip07Button.svelte';
</script>
{#if !$ndk.signer}
<div></div>
{/if}

View File

@@ -0,0 +1,9 @@
<!--lib/components/CollapsableDropdown.svelte-->
<script lang="ts">
</script>
<div class="dropdown">
<div class="dropdown-trigger">
<button class="button" aria-haspopup="true" aria-controls="dropdown-menu"> </button>
</div>
</div>

View File

@@ -0,0 +1,25 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import Nip07Button from "./Nip07Button.svelte";
export let active = false;
const dispatch = createEventDispatcher();
function toggleModal() {
dispatch('toggleModal');
}
</script>
<div class="modal" class:is-active={active}>
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Login</p>
<button class="delete" aria-label="close" on:click={toggleModal}></button>
</header>
<section class="modal-card-body">
<Nip07Button on:closeModal={toggleModal}/>
</section>
</div>
</div>

View File

@@ -0,0 +1,42 @@
<script lang="ts">
import { ndk } from '$lib/stores/nostr';
import { login } from '$lib/utils/login';
import { currentUser } from '$lib/store';
import { browser } from '$app/environment';
import { createEventDispatcher } from 'svelte';
import { get } from 'svelte/store';
let noNip07: boolean;
let _ndk = get(ndk);
$: if (browser) {
noNip07 = !window.nostr;
}
const dispatch = createEventDispatcher();
async function nip07Login() {
const user = await login('nip07', undefined);
if (!user) alert('Nip07 Login Failed');
else {
$currentUser = user;
await $currentUser.fetchProfile();
localStorage.setItem('nostr-key-method', 'nip07');
localStorage.setItem('nostr-target-npub', $currentUser.npub);
_ndk.activeUser = $currentUser;
dispatch('closeModal');
console.debug($currentUser);
}
}
</script>
{#if noNip07}
<div class="alert">
<span>No Nostr extension in your browser</span>
<div class="hidden">
<button class="button is-small">Need Help?</button>
</div>
</div>
{:else}
<button on:click={nip07Login}>Use Browser Extension</button>
{/if}

View File

@@ -0,0 +1,61 @@
<script lang="ts">
import Avatar from '$lib/components/Avatar.svelte';
import { ndk } from '$lib/stores/nostr';
import { currentUser } from '$lib/store';
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
let active = false;
function signIn() {
console.debug('signing in');
dispatch('signin');
}
</script>
<nav class="navbar" aria-label="main navigation">
<div class="navbar-brand">
<h1 class="title is-1">CoFabricate</h1>
<!-- svelte-ignore a11y-missing-attribute -->
<button
class="navbar-burger"
class:is-active={active}
aria-label="menu"
aria-expanded="false"
data-target="mainMenu"
on:click={() => (active = !active)}
>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</button>
</div>
<div id="mainMenu" class="navbar-menu" class:is-active={active}>
<div class="navbar-end">
<div class="navbar-item">
<a class="button" href="/">Home</a>
</div>
<div class="navbar-item">
<a class="button" href="/about">About</a>
</div>
<div class="navbar-item">
<button on:click={signIn}>
<Avatar />
</button>
</div>
</div>
</div>
<!-- <div id="mobileMenu" class="navbar-menu is-hidden-desktop" class:is-active={active}>
<aside class="menu">
</aside>
</div> -->
</nav>
<style>
.nav-menu {
display: flex;
flex-direction: column;
}
</style>

View File

@@ -0,0 +1,10 @@
<script lang="ts">
import { currentUser } from '$lib/store';
import { ndk } from '$lib/stores/nostr';
import { NDKList } from '@nostr-dev-kit/ndk';
import { get, writable } from 'svelte/store';
const $ndk = get(ndk);
export const userBlossom = writable<NDKList | null>(null);
</script>

View File

@@ -0,0 +1,22 @@
import { get as getStore } from 'svelte/store';
import { ndk as ndkStore } from '$lib/stores/nostr';
import GetUserParams, { type NDKUserParams } from '@nostr-dev-kit/ndk';
// import { liveQuery, type Observable } from 'dexie';
import { browser } from '$app/environment';
// import { db } from '$lib/interfaces/db';
const UserInterface = {
//get: (opts: GetUserParams): Observable<App.UserProfile> => {
get: (opts: NDKUserParams): App.UserProfile => {
const ndk = getStore(ndkStore);
const user = ndk.getUser(opts);
console.log('get user', opts);
let userProfile = { ...(user.profile || {}), id: user.pubkey };
user.fetchProfile().then(async () => {
userProfile = { ...userProfile, ...(user.profile || {}) };
});
return userProfile;
}
};
export default UserInterface;

View File

@@ -1,19 +1,19 @@
import { writable } from "svelte/store";
import { NDKUser } from "@nostr-dev-kit/ndk";
import { writable } from 'svelte/store';
import { NDKUser } from '@nostr-dev-kit/ndk';
export const currentUser = writable<NDKUser | null>(null);
export const currentUserFollowPubkeys = writable<string[] | undefined>(undefined);
export const backgroundBanner = writable<string | null>(null);
export type ScopeSelection = {
label: string;
id: string;
pubkeys: string[] | undefined;
label: string;
id: string;
pubkeys: string[] | undefined;
};
export const currentScope = writable<ScopeSelection>({
label: 'global',
id: 'global',
pubkeys: undefined,
label: 'global',
id: 'global',
pubkeys: undefined
});
let zapEvent: any;

View File

@@ -1,35 +1,47 @@
import { writable } from "svelte/store";
import NDK from "@nostr-dev-kit/ndk";
import NDKSvelte from "@nostr-dev-kit/ndk-svelte";
import { RelayList } from "@nostr-dev-kit/ndk-svelte-components";
import { writable } from 'svelte/store';
//import NDKSvelte from '@nostr-dev-kit/ndk-svelte';
import NDK from '@nostr-dev-kit/ndk';
import { browser } from '$app/environment';
let relays;
try {
relays = localStorage.getItem('relays');
} catch (e) {}
if (browser) relays = localStorage.getItem('relays');
} catch (e) {
console.error(e);
}
let relayList: string[] = [];
if (relays) {
relayList = JSON.parse(relays);
relayList = JSON.parse(relays);
}
export const defaultRelays = [
'wss://purplepag.es',
'wss://relay.damus.io'
]
export const defaultRelays = ['wss://purplepag.es', 'wss://relay.damus.io', 'wss://relay.f7z.io'];
if (!relayList || !Array.isArray(relayList) || relayList.length === 0) {
relayList = defaultRelays;
relayList = defaultRelays;
}
const _ndk: NDKSvelte = new NDKSvelte({
devWriteRelayUrls: ['wss://relay.strfront.com'],
explicitRelayUrls: relayList,
enableOutboxModel: true,
}) as NDKSvelte;
const _ndk: NDK = new NDK({
devWriteRelayUrls: ['wss://relay.strfront.com'],
explicitRelayUrls: relayList,
enableOutboxModel: true,
autoConnectUserRelays: true,
autoFetchUserMutelist: true,
clientName: 'cofabricate'
}) as NDK;
const ndk = writable(_ndk);
_ndk.connect();
export default ndk;
console.debug('Nostr store current user: ', _ndk.activeUser?.profile);
const ndkStore = writable(_ndk);
export const ndk = ndkStore;
const _bunkerNDK = new NDK({
explicitRelayUrls: [...defaultRelays, 'wss://relay.nsecbunker.com', 'wss://nostr.vulpem.com']
});
export const bunkerNDK = writable(_bunkerNDK);

12
src/lib/stores/sesson.ts Normal file
View File

@@ -0,0 +1,12 @@
import { writable } from 'svelte/store';
// import { ndk } from "$lib/stores/nostr";
// import type { NDKUser } from "@nostr-dev-kit/ndk";
// let $ndk = get(ndk);
// export let user: NDKUser | undefined = undefined;
export type LoginState = 'logging-in' | 'logged-in' | 'contacting-remote-signer' | 'logged-out';
export const loginState = writable<LoginState | null>(null);
// if ($ndk.cacheAdapter?.fetchProfile)

View File

@@ -1,82 +1,82 @@
import { findEphemeralSigner } from "$lib/signers/ephemeral";
import { NDKPrivateKeySigner, type NDKSigner, type NDKUser } from "@nostr-dev-kit/ndk";
import { writable, get as getStore, derived } from "svelte/store";
import ndkStore from "./nostr";
import { currentUser as currentUserStore } from "../store";
import type NDKList from "$lib/ndk-kinds/lists";
import { findEphemeralSigner } from '$lib/signers/ephemeral';
import NDK, { NDKPrivateKeySigner, type NDKSigner, type NDKUser } from '@nostr-dev-kit/ndk';
import { writable, get as getStore, derived } from 'svelte/store';
import { ndk } from './nostr';
import { currentUser as currentUserStore } from '../store';
import type NDKList from '$lib/ndk-kinds/lists';
export type SignerStoreItem = {
signer: NDKPrivateKeySigner;
user: NDKUser;
saved: boolean;
name?: string;
id: string;
signer: NDKPrivateKeySigner;
user: NDKUser;
saved: boolean;
name?: string;
id: string;
};
type SignerItems = Map<string, SignerStoreItem>;
export const signers = writable<SignerItems>(new Map());
export const npubSigners = derived(signers, ($signers) => {
const npubs = new Map<string, NDKSigner>();
const npubs = new Map<string, NDKSigner>();
for (const entry of $signers) {
const { user, signer } = entry[1];
for (const entry of $signers) {
const { user, signer } = entry[1];
npubs.set(user.npub, signer);
}
npubs.set(user.npub, signer);
}
return npubs;
return npubs;
});
async function getDelegatedSignerName(list: NDKList) {
let name = '';
const currentUser: NDKUser = getStore(currentUserStore);
let name = '';
const currentUser = getStore(currentUserStore);
if (!currentUser?.profile) {
currentUser.ndk = getStore(ndkStore);
await currentUser?.fetchProfile();
}
if (currentUser && !currentUser.profile) {
currentUser.ndk = getStore(ndk);
await currentUser?.fetchProfile();
}
if (currentUser?.profile?.name) {
name = currentUser.profile.displayName + `'s `;
}
if (currentUser?.profile?.name) {
name = currentUser.profile.displayName + `'s `;
}
return name + list.name;
return name + list.name;
}
export async function getSigner(list: NDKList): Promise<SignerStoreItem> {
const store = getStore(signers);
const id = list.encode();
let item = store.get(id);
const store = getStore(signers);
const id = list.encode();
let item = store.get(id);
if (item) return item;
if (item) return item;
const ndk = getStore(ndkStore);
let signer = await findEphemeralSigner(ndk, ndk.signer!, {
associatedEventNip19: list.encode(),
});
const _ndk: NDK = getStore(ndk);
let signer = await findEphemeralSigner(_ndk, _ndk.signer!, {
associatedEventNip19: list.encode()
});
if (signer) {
console.log(`found a signer for list ${list.name}`);
item = {
signer: signer!,
user: await signer.user(),
saved: true,
id,
};
} else {
signer = NDKPrivateKeySigner.generate();
item = {
signer,
user: await signer.user(),
saved: false,
name: await getDelegatedSignerName(list),
id,
};
}
item.user.ndk = ndk;
if (signer) {
console.log(`found a signer for list ${list.name}`);
item = {
signer: signer!,
user: await signer.user(),
saved: true,
id
};
} else {
signer = NDKPrivateKeySigner.generate();
item = {
signer,
user: await signer.user(),
saved: false,
name: await getDelegatedSignerName(list),
id
};
}
item.user.ndk = _ndk;
store.set(id, item);
store.set(id, item);
return item;
return item;
}

4
src/lib/types.ts Normal file
View File

@@ -0,0 +1,4 @@
export enum CofabKind {
Supplier = 30000,
Order = 30001
}

167
src/lib/utils/login.ts Normal file
View File

@@ -0,0 +1,167 @@
import type NDK from '@nostr-dev-kit/ndk';
import {
NDKNip07Signer,
NDKNip46Signer,
NDKPrivateKeySigner,
NDKUser,
type Hexpubkey,
type NDKSigner
} from '@nostr-dev-kit/ndk';
import { bunkerNDK, ndk } from '$lib/stores/nostr';
import { currentUser } from '$lib/store';
import { loginState } from '$lib/stores/sesson';
import { get } from 'svelte/store';
import { browser } from '$app/environment';
export type LoginMethod = 'none' | 'pk' | 'nip07' | 'nip46';
let $ndk: NDK;
const $bunkerNDK: NDK = get(bunkerNDK);
export async function login(method: LoginMethod, userPubkey?: string): Promise<NDKUser | null> {
console.debug(`logging in with ${method}`);
let u: NDKUser | null | undefined;
$ndk = get(ndk);
switch (method) {
case 'none':
loginState.set(null);
return null;
case 'pk':
return await pkLogin();
case 'nip07':
u = await nip07Login($ndk);
console.debug('Logged in as: ', u);
loginState.set('logged-in');
return u;
case 'nip46':
return await nip46Login(userPubkey);
default: {
const promise = new Promise<NDKUser | null>((resolve) => {
let loadAttempts = 0;
const loadNip07Interval = setInterval(async () => {
if (window.nostr) {
clearInterval(loadNip07Interval);
const user = await nip07Login($ndk);
resolve(user);
}
if (loadAttempts++ > 10) clearInterval(loadNip07Interval);
}, 100);
});
return promise;
}
}
}
async function pkLogin(): Promise<NDKUser | null> {
const key = localStorage.getItem('nostr-key');
if (!key) return null;
else return await pkSignin(key);
}
async function pkSignin(key: string): Promise<NDKUser | null> {
const signer = new NDKPrivateKeySigner(key);
const user = await signer.user();
if (user) loggedIn(signer, user!, 'pk');
await user.fetchProfile();
currentUser.set(user);
sessionStorage.setItem('user', JSON.stringify(user));
return user;
}
async function nip07Login(ndk: NDK): Promise<NDKUser | null> {
const storedNpub = browser ? localStorage.getItem('pubkey') : '';
let user: NDKUser | null = null;
if (storedNpub) {
user = new NDKUser({ npub: storedNpub });
user.ndk = ndk;
console.debug('Nip07 - logging in with stored npub', storedNpub);
}
if (window.nostr) {
try {
ndk.signer = new NDKNip07Signer();
user = await ndk.signer?.blockUntilReady();
user.ndk = ndk;
user.fetchProfile();
ndk.activeUser = user;
user.ndk = ndk;
console.debug('Nip07 Login user:', user);
console.debug('NDK: ', ndk);
if (user) localStorage.setItem('nostr-key-method', 'nip07');
localStorage.setItem('pubkey', user.pubkey);
await user.fetchProfile();
currentUser.set(user);
sessionStorage.setItem('user', JSON.stringify(user));
$ndk = ndk;
} catch (e) {
console.error(`NIP-07 login error: ${e}`);
}
}
if (user) await user.fetchProfile();
return user;
}
async function nip46Login(remotePubkey?: Hexpubkey): Promise<NDKUser | null> {
const existingPrivateKey = localStorage.getItem('nostr-nsecbunker-key')!;
let remoteUser: NDKUser | undefined;
console.debug({ existingPrivateKey, remotePubkey });
if (!existingPrivateKey) return null;
if (remotePubkey) remoteUser = $ndk.getUser({ pubkey: remotePubkey });
if (!remoteUser) return null;
currentUser.set(remoteUser);
console.debug('NIP46 setting user: ', remoteUser);
$bunkerNDK.pool.on('relay:ready', async () => {
console.debug('bunker relay ready');
loginState.set('contacting-remote-signer');
await nip46SignIn(existingPrivateKey, remoteUser!);
});
console.debug('connecting to nsecbunker relay');
$bunkerNDK.connect(2500);
return remoteUser;
}
async function nip46SignIn(existingPrivateKey: string, remoteUser: NDKUser) {
remoteUser.ndk = $bunkerNDK;
let localSigner: NDKPrivateKeySigner | null = null;
if (existingPrivateKey) localSigner = new NDKPrivateKeySigner(existingPrivateKey);
else {
alert('Local signer not available');
return null;
}
const remoteSigner = new NDKNip46Signer($bunkerNDK, remoteUser.pubkey, localSigner!);
console.debug('contacting remote signer');
remoteSigner.blockUntilReady();
console.debug('Remote signer connected');
localStorage.setItem('nostr-nsecbunker-key', localSigner.privateKey!);
loggedIn(remoteSigner, remoteUser, 'nip46');
}
export async function loggedIn(signer: NDKSigner, u: NDKUser, method: LoginMethod) {
const _ndk = get(ndk);
_ndk.signer = signer;
u.ndk = _ndk;
await u.fetchProfile();
currentUser.set(u);
_ndk.activeUser = u;
console.log('DEBUG setting user (loggedIn)', u);
loginState.set('logged-in');
localStorage.setItem('pubkey', u.pubkey);
localStorage.setItem('nostr-key-method', method);
}

0
src/lib/utils/modal.ts Normal file
View File

View File

@@ -1,11 +1,24 @@
<nav>
<ul>
<li><h1>Welcome to CoFabricate</h1></li>
</ul>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/login">Sign In</a></li>
</ul>
</nav>
<script lang="ts">
import NavBar from '$lib/components/NavBar.svelte';
import LoginModal from '$lib/components/LoginModal/Modal.svelte';
import { login } from "$lib/utils/login";
import { browserSetup } from "./browser-setup";
import { onMount } from 'svelte';
let modalActive = false;
onMount(async () => {
await signIn();
});
function toggleModal() {
modalActive=!modalActive;
}
async function signIn() {
browserSetup();
}
</script>
<NavBar on:signin={toggleModal} />
<slot></slot>
<LoginModal active={modalActive} on:toggleModal={toggleModal} />

View File

@@ -0,0 +1,5 @@
// import {ndk} from "$lib/stores/nostr";
// export function load({params}) {
// return ndk;
// }

View File

@@ -1,5 +1,31 @@
<script lang="ts">
const img = 'https://picsum.photos/800/500';
</script>
<svelte:head>
<title>Decentralized Manufacturing</title>
<title>Decentralized Manufacturing</title>
</svelte:head>
<h1>Home</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua. Consequat id porta nibh venenatis cras sed felis eget. Fermentum et
sollicitudin ac orci phasellus egestas tellus rutrum tellus. Lobortis mattis aliquam faucibus
purus in massa tempor nec. In nisl nisi scelerisque eu ultrices vitae. Dui sapien eget mi proin
sed libero enim. Pharetra massa massa ultricies mi quis. Quam viverra orci sagittis eu volutpat
odio. Et malesuada fames ac turpis egestas integer eget. Tellus elementum sagittis vitae et leo.
Lorem ipsum dolor sit amet consectetur adipiscing elit pellentesque. Nulla posuere sollicitudin
aliquam ultrices sagittis orci a scelerisque purus.
</p>
<img src={img} alt="placeholder" />
<p>
Imperdiet dui accumsan sit amet nulla. At erat pellentesque adipiscing commodo elit at. Eu mi
bibendum neque egestas. Leo vel orci porta non pulvinar neque laoreet. Quam pellentesque nec nam
aliquam sem et tortor consequat. Blandit volutpat maecenas volutpat blandit. Praesent semper
feugiat nibh sed pulvinar proin gravida hendrerit lectus. Malesuada proin libero nunc consequat
interdum. Risus pretium quam vulputate dignissim. Sollicitudin nibh sit amet commodo nulla. Et
netus et malesuada fames ac turpis egestas sed. Enim sed faucibus turpis in eu mi bibendum neque.
Blandit massa enim nec dui nunc. Aliquam ultrices sagittis orci a scelerisque purus. Pharetra diam
sit amet nisl suscipit adipiscing bibendum. Lorem donec massa sapien faucibus et molestie. Enim
tortor at auctor urna nunc id cursus metus. A erat nam at lectus urna duis convallis convallis.
Tempor id eu nisl nunc mi ipsum.
</p>

View File

@@ -0,0 +1,22 @@
<script lang="ts">
import { page } from "$app/stores";
import { ndk } from "$lib/stores/nostr";
import type NDK from "@nostr-dev-kit/ndk";
import { onMount } from "svelte";
import { get } from "svelte/store";
let id: string;
let { user } = $page.data;
let _ndk: NDK;
onMount(() => {
_ndk = get(ndk);
})
$: if (id !== $page.params.id){
id = $page.params.id;
user.ndk = _ndk;
}
</script>

View File

@@ -0,0 +1,22 @@
<script lang="ts">
import { page } from "$app/stores";
import { ndk } from "$lib/stores/nostr";
import type NDK from "@nostr-dev-kit/ndk";
import { onMount } from "svelte";
import { get } from "svelte/store";
let id: string;
let { user } = $page.data;
let _ndk: NDK;
onMount(() => {
_ndk = get(ndk);
})
$: if (id !== $page.params.id){
id = $page.params.id;
user.ndk = _ndk;
}
</script>

View File

@@ -0,0 +1,4 @@
<script lang="ts">
import { page } from "$app/stores";
import type { NDKUser } from "@nostr-dev-kit/ndk";
</script>

View File

@@ -0,0 +1,48 @@
import { loginState } from '$lib/stores/sesson';
import { loggedIn, login, type LoginMethod } from '$lib/utils/login';
import { ndk } from '$lib/stores/nostr';
import { get } from 'svelte/store';
import { currentUser } from '$lib/store';
import { NDKNip07Signer, NDKUser } from '@nostr-dev-kit/ndk';
import { browser } from '$app/environment';
const _ndk = get(ndk);
export async function browserSetup() {
const pubkey = browser ? localStorage.getItem('pubkey') : '';
if (pubkey) {
const u = _ndk.getUser({ pubkey });
u.fetchProfile();
loginState.set('logging-in');
currentUser.set(u);
}
const method = localStorage.getItem('nostr-key-method') as LoginMethod;
if (!pubkey && method !== 'none') return newSessionTryNip07();
if (method && pubkey) {
console.debug(`logging in with ${method} as ${pubkey}`);
return await login(method, pubkey);
}
}
export async function newSessionTryNip07() {
let signer: NDKNip07Signer | undefined;
let u: NDKUser | null | undefined;
try {
console.debug('trying nip07 signer');
signer = new NDKNip07Signer();
u = await signer.blockUntilReady();
u.fetchProfile();
} catch (e) {
console.debug('nip07Signer failed', e);
}
if (u && signer) {
console.debug('nip07Signer succeeded');
await loggedIn(signer, u, 'nip07');
}
}

View File

@@ -2,4 +2,4 @@
<title>Log In</title>
</svelte:head>
<p>Log In</p>n
<p>Log In</p>

View File

@@ -11,7 +11,12 @@ const config = {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter()
adapter: adapter(),
alias: {
$lib: 'src/lib',
$utils: 'src/lib/utils',
$comp: 'src/lib/components'
}
}
};