Get connect create_account working

This commit is contained in:
Jon Staab 2024-02-08 13:38:17 -08:00
parent 18c6f8b486
commit 0775915ae5
12 changed files with 100 additions and 51 deletions

1
.env
View File

@ -15,3 +15,4 @@ VITE_CLIENT_NAME=Coracle
VITE_CLIENT_ID=31990:97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322:1685968093690
VITE_BUGSNAG_API_KEY=
VITE_BUILD_HASH=
VITE_LOG_LEVEL=warn

View File

@ -3,7 +3,7 @@
import {sortBy, inc} from "ramda"
import {closure, first, sleep} from "hurdak"
import {generatePrivateKey} from "src/util/nostr"
import FlexColumn from 'src/partials/FlexColumn.svelte'
import FlexColumn from "src/partials/FlexColumn.svelte"
import OnboardingIntro from "src/app/views/OnboardingIntro.svelte"
import OnboardingProfile from "src/app/views/OnboardingProfile.svelte"
import OnboardingFollows from "src/app/views/OnboardingFollows.svelte"
@ -18,6 +18,7 @@
publishNote,
publishPetnames,
publishProfile,
publishRelays,
loginWithPrivateKey,
listenForNotifications,
getFollowedPubkeys,
@ -120,15 +121,15 @@
{:else if stage === "profile"}
<OnboardingProfile {setStage} {profile} />
{:else if stage === "follows"}
<OnboardingFollows {setStage} bind:petnames />
<OnboardingFollows {setStage} bind:petnames bind:relays />
{:else if stage === "note"}
<OnboardingNote {setStage} {signup} />
{/if}
{/key}
<div class="flex gap-2 m-auto">
<div class="bg-mid rounded-full w-2 h-2" class:bg-lighter={stage === "intro"} />
<div class="bg-mid rounded-full w-2 h-2" class:bg-lighter={stage === "profile"} />
<div class="bg-mid rounded-full w-2 h-2" class:bg-lighter={stage === "follows"} />
<div class="bg-mid rounded-full w-2 h-2" class:bg-lighter={stage === "note"} />
<div class="m-auto flex gap-2">
<div class="h-2 w-2 rounded-full bg-mid" class:bg-lighter={stage === "intro"} />
<div class="h-2 w-2 rounded-full bg-mid" class:bg-lighter={stage === "profile"} />
<div class="h-2 w-2 rounded-full bg-mid" class:bg-lighter={stage === "follows"} />
<div class="h-2 w-2 rounded-full bg-mid" class:bg-lighter={stage === "note"} />
</div>
</FlexColumn>

View File

@ -2,11 +2,11 @@
import {reject} from "ramda"
import Input from "src/partials/Input.svelte"
import Anchor from "src/partials/Anchor.svelte"
import Heading from "src/partials/Heading.svelte"
import PersonSummary from "src/app/shared/PersonSummary.svelte"
import type {Person} from "src/engine"
import {env, mention, loadPeople, searchPeople} from "src/engine"
import {mention, loadPeople, searchPeople} from "src/engine"
export let relays
export let petnames
export let setStage
@ -29,7 +29,9 @@
</script>
<div class="flex gap-3">
<p class="bg-light rounded-full w-12 h-12 -mt-2 -ml-1 flex justify-center items-center text-lg">3/4</p>
<p class="-ml-1 -mt-2 flex h-12 w-12 items-center justify-center rounded-full bg-light text-lg">
3/4
</p>
<p class="text-2xl font-bold">Find your people</p>
</div>
<p>Search for people and topics, or browse our top suggestions below.</p>

View File

@ -1,15 +1,16 @@
<script lang="ts">
import {appName} from 'src/partials/state'
import {appName} from "src/partials/state"
import Anchor from "src/partials/Anchor.svelte"
export let setStage
const tutorialUrl = "https://nostr.com/"
const next = () => setStage("profile")
</script>
<div class="flex gap-3">
<p class="bg-light rounded-full w-12 h-12 -mt-2 -ml-1 flex justify-center items-center text-lg">1/4</p>
<p class="-ml-1 -mt-2 flex h-12 w-12 items-center justify-center rounded-full bg-light text-lg">
1/4
</p>
<p class="text-2xl font-bold">New to Nostr?</p>
</div>
<p class="sm:hidden">
@ -18,18 +19,20 @@
<p class="hidden sm:block">
Learn about the protocol at your own pace by watching one of our tutorial videos.
</p>
<div class="flex gap-2 flex-col sm:flex-row">
<Anchor class="relative aspect-[4/3] rounded-xl overflow-hidden flex justify-center items-center p-8 text-center sm:w-1/2">
<div class="flex flex-col gap-2 sm:flex-row">
<Anchor
class="relative flex aspect-[4/3] items-center justify-center overflow-hidden rounded-xl p-8 text-center sm:w-1/2">
<div
class="absolute inset-0 opacity-75 hover:opacity-100 transition-opacity"
class="absolute inset-0 opacity-75 transition-opacity hover:opacity-100"
style="background: url('/images/jakob-owens-8tyCOqTqdqg-unsplash.png'" />
<p class="relative staatliches text-5xl text-white">{appName} in 30 seconds</p>
<p class="staatliches relative text-5xl text-white">{appName} in 30 seconds</p>
</Anchor>
<Anchor class="relative aspect-[4/3] rounded-xl overflow-hidden justify-center items-center p-8 text-center sm:w-1/2 hidden sm:flex">
<Anchor
class="relative hidden aspect-[4/3] items-center justify-center overflow-hidden rounded-xl p-8 text-center sm:flex sm:w-1/2">
<div
class="absolute inset-0 opacity-75 hover:opacity-100 transition-opacity"
class="absolute inset-0 opacity-75 transition-opacity hover:opacity-100"
style="background: url('/images/sean-105m46GatAg-unsplash.png'" />
<p class="relative staatliches text-5xl text-white">{appName} deep dive</p>
<p class="staatliches relative text-5xl text-white">{appName} deep dive</p>
</Anchor>
</div>
<p>

View File

@ -1,9 +1,10 @@
import {equals, pick} from "ramda"
import {equals} from "ramda"
import type {EventTemplate} from "nostr-tools"
import {nip04, finalizeEvent} from "nostr-tools"
import {Emitter, Subscription, createEvent} from "paravel"
import {Emitter, now, Subscription, createEvent} from "paravel"
import {randomId, sleep} from "hurdak"
import {NostrConnect} from "nostr-tools/kinds"
import logger from "src/util/logger"
import {getPublicKey} from "src/util/nostr"
import {tryJson} from "src/util/misc"
import type {Event} from "src/engine/events/model"
@ -39,12 +40,18 @@ export class NostrConnectBroker extends Emitter {
this.#sub = subscribePersistent({
relays: this.handler.relays,
filters: [{kinds: [NostrConnect], "#p": [getPublicKey(this.connectKey)]}],
filters: [
{
since: now() - 30,
kinds: [NostrConnect],
"#p": [getPublicKey(this.connectKey)],
},
],
onEvent: async (e: Event) => {
const json = await nip04.decrypt(this.connectKey, e.pubkey, e.content)
const {id, result, error} = tryJson(() => JSON.parse(json)) || {error: "invalid-response"}
console.info("NostrConnect response:", id, result, error)
logger.info("NostrConnect response:", {id, result, error})
if (result === "auth_url") {
window.open(error, "Coracle", "width=600,height=800,popup=yes")
@ -59,17 +66,16 @@ export class NostrConnectBroker extends Emitter {
// nsecbunker has a race condition
await this.#ready
console.info("NostrConnect request:", method, params)
const id = randomId()
const kind = admin ? 24134 : NostrConnect
const pubkey = admin ? this.handler.pubkey : this.pubkey
const payload = JSON.stringify({id, method, params})
const content = await nip04.encrypt(this.connectKey, pubkey, payload)
const template = createEvent(NostrConnect, {content, tags: [["p", pubkey]]})
const event = finalizeEvent(template, this.connectKey as any)
Publisher.publish({event, relays: this.handler.relays})
logger.info("NostrConnect request:", {id, method, params})
Publisher.publish({event, relays: this.handler.relays, silent: true})
return new Promise((resolve, reject) => {
this.once(`response-${id}`, ({result, error}) => {
@ -103,7 +109,11 @@ export class NostrConnectBroker extends Emitter {
}
signEvent(event: EventTemplate) {
return this.request("sign_event", [event])
return tryJson(async () => {
const res = (await this.request("sign_event", [JSON.stringify(event)])) as string
return JSON.parse(res)
})
}
nip04Encrypt(pk: string, message: string) {

View File

@ -32,6 +32,7 @@ export const getClientTags = () => {
export type PublisherOpts = {
timeout?: number
silent?: boolean
verb?: string
}
@ -62,11 +63,13 @@ export class Publisher extends EventEmitter {
return publisher
}
publish(relays, {timeout = 10_000, verb}: PublisherOpts = {}) {
publish(relays, {timeout = 10_000, verb, silent}: PublisherOpts = {}) {
const urls = getUrls(relays)
const executor = getExecutor(urls)
if (!silent) {
info(`Publishing to ${urls.length} relays`, this.event, urls)
}
const timeouts = new Set<string>()
const succeeded = new Set<string>()

View File

@ -1,7 +1,6 @@
import type {SubscriptionOpts} from "paravel"
import {Subscription, now} from "paravel"
import {assoc, map} from "ramda"
import {updateIn, sleep} from "hurdak"
import {Subscription} from "paravel"
import {sleep} from "hurdak"
import {LOCAL_RELAY_URL} from "src/util/nostr"
import type {Event} from "src/engine/events/model"
import {deletes} from "src/engine/events/state"

View File

@ -1,5 +1,4 @@
import {omit, assoc} from "ramda"
import {createEvent} from "paravel"
import {generatePrivateKey, getPublicKey, appDataKeys} from "src/util/nostr"
import type {NostrConnectHandler} from "src/engine/network/model"
import {createAndPublish, NostrConnectBroker} from "src/engine/network/utils"
@ -44,7 +43,8 @@ export const loginWithNsecBunker = async (pubkey, connectToken, connectRelay) =>
export const loginWithNostrConnect = async (username, connectHandler: NostrConnectHandler) => {
const connectKey = generatePrivateKey()
const {pubkey} = await fetchHandle(`${username}@${connectHandler.domain}`)
const broker = NostrConnectBroker.get(pubkey, connectKey, connectHandler)
let broker = NostrConnectBroker.get(pubkey, connectKey, connectHandler)
// TODO: create account should return the new pubkey, we shouldn't have to call connect.
// we're also leaking listeners because this promise never resolves. Hack a proper return
@ -52,20 +52,21 @@ export const loginWithNostrConnect = async (username, connectHandler: NostrConne
if (!pubkey) {
broker.createAccount(username)
await new Promise(resolve => {
await new Promise<void>(resolve => {
const onMouseMove = () => {
resolve()
document.body.removeEventListener('mousemove', onMouseMove)
document.body.removeEventListener("mousemove", onMouseMove)
}
setTimeout(() => {
document.body.addEventListener('mousemove', onMouseMove)
document.body.addEventListener("mousemove", onMouseMove)
}, 1000)
})
// Now that the account has ostensibly been created, get our new pubkey and set it to the broker
const {pubkey} = await fetchHandle(`${username}@${connectHandler.domain}`)
broker.pubkey = pubkey
broker = NostrConnectBroker.get(pubkey, connectKey, connectHandler)
}
const result = await broker.connect()

View File

@ -9,9 +9,12 @@ export class Connect {
const {pubkey, connectKey, connectHandler} = session
this.broker = NostrConnectBroker.get(pubkey, connectKey, connectHandler)
if (!this.broker.connected) {
this.broker.connect()
}
}
}
isEnabled() {
return this.session?.method === "connect"

View File

@ -1,3 +1,4 @@
import {memoize} from "src/util/misc"
import {sessions} from "../state"
import {Nip04} from "./nip04"
import {Nip44} from "./nip44"
@ -15,13 +16,14 @@ export * from "./connect"
export const getSession = pubkey => sessions.get()[pubkey]
export const getConnect = session => new Connect(session)
export const getConnect = memoize(session => new Connect(session))
export const getSigner = session => new Signer(session, getConnect(session))
export const getSigner = memoize(session => new Signer(session, getConnect(session)))
export const getNip44 = session => new Nip44(session, getConnect(session))
export const getNip44 = memoize(session => new Nip44(session, getConnect(session)))
export const getNip04 = session => new Nip04(session, getConnect(session))
export const getNip04 = memoize(session => new Nip04(session, getConnect(session)))
export const getNip59 = session =>
new Nip59(session, getNip04(session), getNip44(session), getSigner(session))
export const getNip59 = memoize(
session => new Nip59(session, getNip04(session), getNip44(session), getSigner(session)),
)

View File

@ -1,5 +1,5 @@
import {nip04} from "nostr-tools"
import {switcherFn, sleep, tryFunc} from "hurdak"
import {switcherFn, tryFunc} from "hurdak"
import type {Session} from "src/engine/session/model"
import type {Connect} from "./connect"
import {withExtension} from "./nip07"

View File

@ -1,3 +1,27 @@
export const info = (...message) => console.log(...message)
export const warn = (...message) => console.warn(...message)
export const error = (...message) => console.error(...message)
const levels = ["info", "warn", "error"]
let level = import.meta.env.VITE_LOG_LEVEL
export const setLevel = l => {
level = l
}
export const info = (...message) => {
if (levels.indexOf(level) <= levels.indexOf("info")) {
console.log(...message)
}
}
export const warn = (...message) => {
if (levels.indexOf(level) <= levels.indexOf("warn")) {
console.warn(...message)
}
}
export const error = (...message) => {
if (levels.indexOf(level) <= levels.indexOf("error")) {
console.error(...message)
}
}
export default {info, warn, error}