npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@cmdcode/use-nostr

v0.1.1

Published

Turn-key library for using Nostr with React.

Downloads

13

Readme

use-nostr

This project is designed to be a turn-key library for using Nostr with React.

  • Login with pubkey, npub, seckey, nsec, extension, or generate a new key.
  • Nostr client API with get, list, sub, and publish.
  • Configure a list of relays for your client to use.
  • Uses a simple reducer store with update method.
  • Helper boolean, status and error messages for reactive components.

NEW: Nostr rooms now available! Easily create group messaging using a shared secret!

Coming soon:

  • Sign / verify custom messages and challenges using the Signer API.
  • Receive a Taproot HD wallet derived from your nostr signing device.
  • Better integration of user profile and relay list.
  • Remote signing support.

This project is fully typed and designed to work with intellisense.

More documentation coming soon!

Import

To make use-nostr available across your entire react app, wrap your root component with the included NostrProvider component:

// Example entrypoint for react / nextjs.
// Your project may look slightly different.
import { NostrProvider } from '@cmdcode/use-nostr'

export default function App ({ Component, pageProps }) {
  return (
    <NostrProvider>
      <Component {...pageProps} />
    </NostrProvider>
  )
}

Basic Usage

With the NostrProvider configured, importing the library and store is relatively simple.

Here is a basic example of reading and updating your relay list:

import { useState } from 'react'
import { useNostr } from '@cmdcode/use-nostr'

export default function Relays () {
  const { store, update }   = useNostr()
  const [ input, setInput ] = useState('')

  function handleSubmit() {
    update({ relays: [ ...store.relays, input ] })
  }

  return (
    <div>
      <p>Relays</p>
      { store.relays && <pre>{JSON.stringify(store.relays, null, 2)}</pre> }
      <input onChange={(e) => { setInput(e.target.value) }} value={input} />
      <button onClick={handleSubmit}>Add Relay</button>
    </div>
  )
}

Here is an example login flow that covers all methods of signing in:

import { useState } from 'react'
import { useNostr } from '@cmdcode/use-nostr'

export default function Login () {
  const [ pubkey, setPubKey ] = useState('')
  const [ seckey, setSecKey ] = useState('')

  const { hasExtension, login, store } = useNostr()

  return (
    <div className='container'>
      <div className='card'>
        <label>Login Via Extention:</label>
        <button disabled={!hasExtension} onClick={login.withExt}>Login</button>
      </div>
      <div className='card'>
        <label>Login with your Public Key or npub:</label>
        <input value={pubkey} onChange={(e) => setPubKey(e.target.value)}></input>
        <button onClick={() => login.withPubKey(pubkey)}>Login</button>
      </div>
      <div className='card'>
        <label>Login with your Secret Key or nsec:</label>
        <input value={seckey} onChange={(e) => setSecKey(e.target.value)}></input>
        <div className='field'>
          <button onClick={() => login.withSecKey(seckey)}>Login</button>
          <button onClick={login.generateKey}>Generate</button>
        </div>
      </div>
    </div>
  )
}

Library API

The library is split into several modules that plug into the main store.

Store API

const { store, update, reset, setError } = useNostr()

// The main data store.
store : NostrStore
// Update the data store using a JSON object.
update   = (store : Partial<NostrStore>) => void
// Resets the data store. Provide an 
// optional JSON object for defaults.
reset    = (store : Partial<NostrStore>) => void
// Sets an error message in the store, 
// plus print to console.
setError = (err : Error) => void

interface NostrStore {
  // Schema for the data store.
  client      ?: Client
  connection   : 'none' | 'conn' | 'ok' | 'error'
  hasExtension : boolean
  isConnected  : boolean
  isLoading    : boolean
  error       ?: string
  pubkey      ?: string
  profile     ?: Profile
  relays       : string[]
  signer      ?: Signer
}

Login API

const { login, logout } = useNostr()

login = {
  // Login with NIP-07 extension (if available).
  withExt     : () => void,
  // Login with npub or pubkey hex (read only).
  withPubKey  : (string) => void,
  // Login with nsec or seckey hex.
  withSecKey  : (string) => void,
  // Generate a new (ephemeral) keypair.
  generateKey : () => void
}

// Removes all user data from the store.
logout = () => void

Client API

const { client } = useNostr()

client = {
  // Checks pool connection and returns a boolean result.
  connected : () => Promise<boolean>,
  // Publish an event from a partial JSON template.
  publish   : (event : Partial<Event>) => Promise<Event>,
  // Fetch the top event using the provided filter.
  get  : (filter  : Filter)   => Promise<Event>,
  // Fetch a list of events using the provided filters.
  list : (filters : Filter[]) => Promise<Event[]>,
  // Publish a signed event and receive a Pub emitter.
  pub  : (event   : Event)    => Promise<Pub>,
  // Subscribe to a list of filters and receive a Sub emitter.
  sub  : (filters : Filter[]) => Promise<Sub>
}

Profile API

const { getProfile, setProfile } = useNostr()

// Fetch your profile from the relays.
getProfile = () => Promise<Profile | undefined>
// Update your profile using a partial JSON template.
setprofile = (updates : Partial<Profile>) => Promise<void>

Room API

Rooms are a way to pass messages in real-time between a group of users. You can create or join a room using a secret string that is shared by all parties. All messages within the room are encrypted and covertly tagged using the shared secret.

A room object works like a typical event emitter. You can broadcast a custom event to the room using pub, and listen for custom events using on, once or within. You can also emit events only to yourself using emit. Any callback methods registered to an event will receive a payload of data from the publisher, plus the event envelope it was wrapped in.

const { joinRoom } = useNostr()

// Multiple parties can join a single room using a secret string.
const room = joinRoom('secretstring')

// You can publish any type of payload to any custom named event.
room.pub('customevent', { hello: 'world!' })

// You can attach a callback method to any custom event.
room.on('customevent', (payload, envelope) => {
  console.log(payload) // { hello: 'world!' }
  // The envelope is the event object itself.
  console.log(envelope)
  /* {
   *   "kind": 21111,
   *   "tags": [ 
   *     [ "h", "11ed64797736fdb7577bc10987e8b0a82210a93e90d39a217306e714504e97a4" ],
   *     [ "expiration", "1772782615" ]
   *   ],
   *   "content": "6JArpHDMApqEY7dDuf_mo-KZHfMbZrZ6o6qJnXqxRuc8QzAWRz4zJ4kjzwCzM3Utf-_WeRJ-E59TCr3esj65gw?iv=MJjiOgQ7cGjAT5k6wWpHfg",
   *   "created_at": 1686382615
   * }
   */

class NostrRoom {
  cache     : Array<EventRecord> // A rolling cache of past events.
  config    : RoomConfig  // Configuration for the room.
  events    : Callbacks   // List of callbacks registered for each event label.
  connected : boolean     // Returns true once the room has an active subscription.
  members   : string[]    // A list of pubkeys that represent active participants.
  roomId    : Buff        // The identifer used to tag each event (for filtering).
  sharedKey : Buff        // The shared key used for encryption (computed from secret).

  // Emit an event only to yourself. Useful for internal logic.
  emit (eventName: string, ...args: any[]) => void
  // Broadcast an event to everyone in the room.
  pub (
    eventName : string,
    payload   : Json,
    template ?: Partial<EventTemplate>
  ) => Promise<Event | undefined>
  // Register a callback method for a particular event.
  on (eventName : string, fn : Function)   => void
  // Register a callback that only executes once.
  once (eventName : string, fn : Function) => void
  // Register a callback that only exists for a limited time.
  within (eventName : string, fn : Function, timeout: number) => void
  // Remove a specific callback from the event list.
  remove (eventName : string, fn : Function) => void
  // Remove all callbacks registered to a specific event label.
  prune(eventName: string) => void
  // Unsubscribe the room from the relay pool.
  leave() => void
}

type EventRecord = [ eventName: string, payload: any, envelope: Event ]
type Callbacks   = Record<string, Set<Function>>

interface RoomConfig {
  cacheSize      : number      // Sets the number of past events to store in cache.
  allowEcho      : boolean     // Toggles receipt of events that you published yourself.
  encryption     : boolean     // Toggles encryption for event content.
  expiration     : number      // Events are set to expire after the time period.
  filter         : Filter      // Customize the filter used to subscribe to room events.
  inactiveLimit ?: number      // Users in the room are marked inactive after the time period.
  kind           : number      // Set which kind number to use for each event envelope.
  tags           : string[][]  // Set custom tags to be added to each event envelope.
}

Signer API

Coming soon!

Development / Testing

This library uses yarn for package management and vite for a development / demo server.

## Start the vite development server:
yarn dev
## Build a new release of the package:
yarn release

Bugs / Issues

If you run into any bugs or have any questions, please submit an issue ticket.

Contribution

Feel free to fork and make contributions. Suggestions are welcome!

Dependencies

This library contains minimal dependencies.

React
React library for hooks and JSX.
https://github.com/facebook/react

nostr-tools
Nostr utility library for all nostr stuff.
https://github.com/nbd-wtf/nostr-tools

Resources

Nostr Implementation Possibilities
This site is an index of current and draft NIPs.
https://nips.be

License

Use this library however you want!

Contact

You can find me on nostr at: npub1gg5uy8cpqx4u8wj9yvlpwm5ht757vudmrzn8y27lwunt5f2ytlusklulq3