Update docs for app session

This commit is contained in:
Jon Staab
2025-04-11 10:18:50 -07:00
parent 7e44fa414c
commit bdef37d404
5 changed files with 231 additions and 90 deletions
+13 -12
View File
@@ -6,13 +6,13 @@ A comprehensive framework for building nostr clients, powering production applic
## What's Included
- **Repository System** - Event storage and query capabilities
- **Router** - Intelligent relay selection for optimal networking
- **Feed Controller** - Manages feed creation and updates
- **Repository** - Event storage and query capabilities
- **Router** - Intelligent relay selection for optimal network access
- **Feed Controller** - Manages feed loading
- **Session Management** - User identity and key management
- **Event Actions** - High-level operations like reacting, replying, etc.
- **Profile Management** - User profile handling and metadata
- **Relay Directories** - Discovery and management of relays
- **Relay Directory** - Discovery and management of relays
- **Web of Trust** - Utilities for building webs of trust
## Quick Example
@@ -61,18 +61,19 @@ const events = await load({
}])
// Or use `request` for more fine-grained subscription control
const req = request({
const abortController = new AbortController()
request({
signal: abortController.signal,
relays: Router.get().ForUser().getUrls(),
filters: [{kinds: [NOTE],
onEvent: (event: TrustedEvent) => {
console.log(event)
},
}])
// Listen for events
req.on(RequestEvent.Event, (event: TrustedEvent) => {
console.log(event)
})
// Close the req
req.close()
// Close the request
abortController.abort()
```
## Installation
+163 -66
View File
@@ -6,6 +6,7 @@ The session system provides a unified way to handle different authentication met
- NIP-07 via Browser Extension
- NIP-46 via Bunker URL or Nostrconnect
- NIP-55 via Android Signer Application
- Read-only pubkey login
## Overview
@@ -16,96 +17,192 @@ Sessions are stored in local storage and can be:
- Switched dynamically
- Backed by different signing methods
## Basic Usage
## NIP 01 Example
The simplest type of login is NIP 01, although it's generally a bad idea to be handling user keys. NIP 46, 44, or 07 login are preferable. However, NIP 01 can be useful for supporting signup, local profiles, or ephemeral keys.
```typescript
import {sessions, pubkey, addSession, dropSession} from '@welshman/app'
import {makeSecret} from '@welshman/signer'
import {loginWithNip01} from '@welshman/app'
// Add multiple sessions
addSession({method: 'nip07', pubkey: 'abc...'})
addSession({method: 'nip46', pubkey: 'def...', secret: '123'})
// Switch between sessions
pubkey.set('abc...') // Activates that session
// Remove a session
dropSession('abc...')
// List all sessions
console.log(sessions.get())
loginWithNip01(makeSecret())
```
## NIP-46 (Bunker) Authentication
## NIP 07 Example
A simple way to sign in for desktop browser users is using [NIP 07](https://github.com/nostr-protocol/nips/blob/master/07.md). This method is easy to implement, but should be used sparingly, since not all users will be using a browser with a nostr signing extension installed.
```typescript
import {Nip46Broker, Nip46Signer} from '@welshman/signer'
import {addSession} from '@welshman/app'
import {Nip07Signer} from '@welshman/signer'
import {loginWithNip07} from '@welshman/app'
// Connect to a bunker
const clientSecret = makeSecret()
const relays = ['wss://relay.damus.io']
const broker = Nip46Broker.get({relays, clientSecret})
const signer = new Nip07Signer()
// Generate nostrconnect URL for the bunker
const connectUrl = await broker.makeNostrconnectUrl({
name: "My App",
url: "https://myapp.com"
})
// Wait for user to approve in bunker
const response = await broker.waitForNostrconnect(connectUrl)
// Create session
addSession({
method: 'nip46',
pubkey: response.event.pubkey,
secret: clientSecret,
handler: {
pubkey: response.event.pubkey,
relays
signer.getPubkey().then(pubkey => {
if (pubkey) {
loginWithNip07(pubkey)
} else {
// User extension does not exist or did not respond
}
})
```
## Using Session Signer
## NIP-46 Authentication
The best default signing scheme is [NIP 46](https://github.com/nostr-protocol/nips/blob/master/46.md), AKA "Nostr Connect". This supports multiple handshakes depending on desired UX, and can support advanced use cases like secure enclaves, self-hosted keys, and FROST multisig.
The simpler `bunker://` handshake is done by asking the user to provide a bunker URL, either by QR code, or by pasting it manually into your application.
```typescript
import {Nip46Broker, makeSecret} from "@welshman/signer"
import {loginWithNip46, nip46Perms} from "@welshman/app"
import {isKeyValid} from "src/util/nostr"
// Make a client secret - this is distinct from the user's private key, and is used
// for communicating securely with the remote signer
const clientSecret = makeSecret()
// Ask the user to input their bunker URL
const bunkerUrl = prompt("Please enter your bunker url")
// Pase the bunker url
const {signerPubkey, connectSecret, relays} = Nip46Broker.parseBunkerUrl(bunkerUrl)
if (!isKeyValid(signerPubkey)) {
alert("Sorry, but that's an invalid public key.")
} else if (relays.length === 0) {
alert("That connection string doesn't have any relays.")
} else {
// Open up a connection with the signer
const broker = Nip46Broker.get({relays, clientSecret, signerPubkey})
// Send a connect request with the default permissions
const result = await broker.connect(connectSecret, nip46Perms)
// Make sure to check the connect secret to prevent hijacking
if (result === connectSecret) {
// Get the user's public key
const pubkey = await broker.getPublicKey()
if (!pubkey) {
alert("Failed to initialize session")
} else {
loginWithNip46(pubkey, clientSecret, signerPubkey, relays)
}
}
}
```
Alternatively, you can provide the user with a `nostrconnect://` URL which they can copy or scan with their signer. This is a better UX for users using a signer on their mobile phone.
```typescript
import {Nip46Broker, makeSecret} from "@welshman/signer"
import {loginWithNip46, nip46Perms} from "@welshman/app"
// Create a client secret
const clientSecret = makeSecret()
// Stop listening if the user cancels login
const abortController = new AbortController()
// Customize to use relays the signer can send responses to
const relays = ['wss://relay.nsec.app/']
// Create a broker
const broker = Nip46Broker.get({clientSecret, relays})
// Create a nostrconnect:// url
const nostrconnect = await broker.makeNostrconnectUrl({
name: "My App",
url: window.origin,
image: window.origin + '/logo.png',
perms: nip46Perms,
})
// Share it with the user. Displaying a QR code is particularly helpful
alert("To connect, paste this URL into your signer: " + nostrconnect)
// Listen for the response
let response
try {
response = await broker.waitForNostrconnect(nostrconnect, abortController.signal)
} catch (errorResponse: any) {
if (errorResponse?.error) {
alert(`Received error from signer: ${errorResponse.error}`)
} else if (errorResponse) {
console.error(errorResponse)
}
}
// If we got a response, the broker is already connected and we can log in
if (response) {
const pubkey = await broker.getPublicKey()
if (!pubkey) {
alert("Failed to initialize session")
} else {
loginWithNip46(pubkey, clientSecret, response.event.pubkey, relays)
}
}
```
## NIP-55 Authentication
For the best UX on Android, use [NIP 55](https://github.com/nostr-protocol/nips/blob/master/55.md). Note that this only works for web applications that have been compiled to native Android applications using [CapacitorJS](https://capacitorjs.com/) and [nostr-signer-capacitor-plugin](https://github.com/chebizarro/nostr-signer-capacitor-plugin).
```typescript
import {getNip55, Nip55Signer, loginWithNip55} from "@welshman/signer"
// Query for installed apps that implement nip 55 signing
getNip55().then(signerApps => {
// We'll choose the first one and auto-login, but in most cases you'll want to offer a choice
if (signerApps.length > 0) {
const signer = new Nip55Signer(signerApps[0].packageName)
const pubkey = await signer.getPubkey()
if (pubkey) {
loginWithNip55(pubkey, app.packageName)
}
}
})
```
## Read-only session
A fun feature of nostr is that you can log in as other people, and see what nostr is like from their perspective (minus encrypted data or course).
```typescript
import {loginWithPubkey} from "@welshman/signer"
// Log in as hodlbod
loginWithPubkey("97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322")
```
## Using the current session
```typescript
import {signer, session} from '@welshman/app'
import {createEvent, NOTE} from '@welshman/util'
// Print the current session - be aware the private key is stored in memory, be very
// careful about how you handle session objects!
console.log(session.get())
// Current session's signer is always ready to use
const event = await signer.get().sign(
createEvent(NOTE, {content: "Hello Nostr!"})
)
// hodlbod's pubkey
const otherPubkey = "97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322"
// Encrypt content for private notes
const encrypted = await signer.get().nip44.encrypt(
pubkey,
"Secret message"
)
const ciphertext = await signer.get().nip44.encrypt(otherPubkey, "Secret message")
// Decrypt automatically detects encryption version
const plaintext = await decrypt(signer, otherPubkey, ciphertext)
```
## Session Types
## Multiple sessions
```typescript
type SessionNip07 = {
method: "nip07"
pubkey: string
}
type SessionNip46 = {
method: "nip46"
pubkey: string
secret: string
handler: {
pubkey: string
relays: string[]
}
}
type SessionNip01 = {
method: "nip01"
pubkey: string
secret: string
}
```
It's possible to support multiple concurrent sessions by simply calling `addSession` multiple times. This will update `sessions`, and set `pubkey` to the most recently added session. You can then switch between sessions by calling `pubkey.set` with a valid session pubkey, and delete sessions using `dropSession(pubkey)`.
+3 -4
View File
@@ -5,10 +5,9 @@ outline: deep
# What is Welshman?
Welshman is a production-grade nostr toolkit powering [Coracle](https://coracle.social) and [Flotilla](https://flotilla.social).
Built as independent, opt-in packages, it lets you choose exactly what you need - from basic event handling to complete feed management.
Built as independent, opt-in packages, it lets you choose exactly what you need - from basic utilities handling to a batteries-included application framework.
Need just a content parser? Grab @welshman/content. Building a complex client? Start with @welshman/app and add more packages as you grow.
Each module is battle-tested in production, designed to work together but never dependent on each other.
Build your next Nostr client with the same tools that power today's leading Nostr applications.
Each module is battle-tested in production, and designed to work together.