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

defy.tools

v1.5.2

Published

Tools for handling Private Channel NOSTR Events defined in NIP-76 and used at https://defy.socal/.

Downloads

10

Readme

defy.tools

Tools for developing Encrypted Nostr Clients. Depends on @scure & @noble packages.

Overview

  • Each message is encrypted with a unique key, signed with another unique key, and reveals no identifying information about the author or the intended recipient.
  • Keys are exchanged via a Notification event requiring the recipients private key to read the enclosed key.
  • Event content owner is verified through event BIP32 key derivation.

Installation

 npm install defy.tools # or yarn add defy.tools

Demo Client

For a demonstration of a secure messaging client using this library see https://gitlab.com/d5i/defy.demo .

Usage Examples

Creating a keyStore

Using web storage:

const keyStore = KeyStoreStorage.create(KeyStoreWebStorage);

// without a password the key will be saved in sessionStorage 
// encrypted with a randomly generated key this will be
// available for 15 minutes.
await keyStore.save();

// with a password, the key will be saved in localStorage
// encrypted with a hash of the password
await keyStore.save("A Demo Password");    

Using file storage:

// uses file a file located at $HOME/.defy/defy-backup to store the key
const keyStore = KeyStoreStorage.create(KeyStoreFileStorage);
await keyStore.save("A Demo Password");

Reloading a Saved KeyStore

// reload from session
const keyStoreA = await KeyStoreStorage.fromStorage(KeyStoreWebStorage);

// reload from local
const keyStoreB = await KeyStoreStorage.fromStorage(KeyStoreWebStorage, "A Demo Password");

// reload from file
const keyStoreC = await KeyStoreStorage.fromStorage(KeyStoreFileStorage, "A Demo Password");    

Using a RelayPool

Use RelayPool after setting it with the setDefyContext() method.

const keyStore = await KeyStoreStorage.fromStorage(KeyStoreFileStorage);
const relayPool = new RelayPool(); // or any class that extends RelayPool
relayPool.addRelay('wss://my.relay.url', true, true, false);

// relay methods require a context.  for now we are assuming 
// there is only one context per process.   
setDefyContext({ keyStore, relayPool });

keyStore.load().subscribe(keyStore => {
    // do stuff with keyStore.channels / keyStore.contacts
})

All methods in the model with names like load, query, publish, listen and delete (and others) require a relayPool object set in the context.

Using a RelayPool as a Web Worker

Same as above, but the last parameter to addRelay is true and we have copied the defy.tools.relay-worker.js file to our public root directory.

const keyStore = await KeyStoreStorage.fromStorage(KeyStoreFileStorage);
const relayPool = new RelayPool(); // or any class that extends RelayPool
relayPool.addRelay('wss://my.relay.url', true, true, true);

setDefyContext({ keyStore, relayPool });

keyStore.load().subscribe(result => {
    const { channels, contacts } = result;
    // do stuff
})

To use RelayPool as a Web Worker, make sure the defy.tools.relay-worker.js script is copied to your web project public root path. Here is a simple custom vite plugin example that can do that automatically. Then you can add the copied file to your .gitignore .

  plugins: [
  ...
      {
      name: 'copy-relay-worker',
      buildStart: async () => {
          const relayWorkerJs = 'defy.tools.relay-worker.js';
          const srcJs = `${__dirname}/node_modules/defy.tools/dist/${relayWorkerJs}`;
          const srcMap = `${__dirname}/node_modules/defy.tools/dist/${relayWorkerJs}.map`;
          fs.copyFileSync(srcJs, `${__dirname}/public/${relayWorkerJs}`);
          if (fs.existsSync(srcMap)) {
          fs.copyFileSync(srcMap, `${__dirname}/public/${relayWorkerJs}.map`);
          }
      }
      },
  ],

Creating New Private Channels

Due to the nature of sequential indexes, we should not create new documents on the root index until we know it is loaded with the existing documents. We will create a documents meta object to help make this faster later. But still we would need to wait for that too.

Example assumes RelayPool in context and the keyStore load() has been called and the documentsIndex is in the 'ready' state.

const { keyStore } = useDefyContext();
const info: PrivateChannelPayload = {
    kind: NostrKinds.ChannelMetadata,
    pubkey: keyStore.ownerPubKey,
    name: 'New Channel',
    created_at: getNowSeconds(),
    contacts: {},
    shares:{}
};
// assume we set the relayPool in the context earlier
keyStore.load().pipe(
    mergeMap(() => PrivateChannel.create(info, true)),
).subscribe(channel => {
    // do stuff with the channel
});

Creating a Text Note on a Private Channel

Example assumes RelayPool in context

const { keyStore } = useDefyContext();

// determine your the channel:
const channel = keyStore.channels[0];

channel.saveNote('Hello World').subscribe()

Creating a Text Reply or Reaction to Private Channel Note

Example assumes RelayPool in context

const note = channel.posts[0]; // find the note to reply / react to
// a reply
note.reply('Hi', 1).subscribe();
// a reaction
note.reply('👍', 7).subscribe();

Listen for Notes on a Channel

Example assumes RelayPool in context

const { keyStore } = useDefyContext();

const channel = keyStore.channels[0];
const [sub$, endSub] = channel.listen();

sub$.subscribe(note => {
  // do stuff with the note
  // consider also using other rxjs operators to buffer 
  // and/or otherwise control the flow of messages
});
// when finished, clean up with the endSub() to end both
// the rxjs subscription and the close the relay request
endSub();

Load the last 50 Notes on the Channel

Example assumes RelayPool in context

const { keyStore } = useDefyContext();

const channel = keyStore.channels[0];
const [sub$, endSub] = channel.dkxPost.query<Note>({
    keepAlive: false,
    limit: 50
});
sub$.pipe(
    reduce((notes, note) => {
        notes.push(note);
        return notes;
    }, [] as Note[]),
).subscribe(notes => {
    // do stuff with notes
});

Initiate a DM

Example assumes RelayPool in context and the keyStore load() has been called and the documentsIndex is in the 'ready' state. v1.3.2 supports group conversations with an array of pubkeys.

const { keyStore } = useDefyContext();
const davesPubKey = '828adc3061e4f0c3f1984dce96003eb89d2ab279e703e01de806c9b2ba33ff73';
ContactChannel.create([davesPubKey]).pipe(
    mergeMap(msgChannel => {
        // until Dave responds, messages are sent to 
        // him via his Notifications index
        return msgChannel.sendMessage("Hi Dave!");
    })
).subscribe();

Listen for new contacts

Example assumes RelayPool in context and the keyStore load() has been called and the documentsIndex is in the 'ready' state.

const { keyStore } = useDefyContext();
const [sub$, endSub] = keyStore.notifyIndex.listen<Notification>();
sub$.pipe(
    // make sure there is a channelId in the notification
    filter(notif => Boolean(notif.content.tags?.[0]?.[0] === 'e')),
    mergeMap(notif => {
        const contact = keyStore.contacts.find(x => x.channelId === notif.content.tags![0][1]);
        if(contact){
            return of(contact);
        } else {
            return ContactChannel.create(notif, true)
        }
    })
).subscribe(contact => {
    // a saved contact that we can send and receive messages on
    console.log(contact);
});

Read DM Messages from existing contacts

Example assumes RelayPool in context and the keyStore load() has been called and the documentsIndex is in the 'ready' state.

const { keyStore } = useDefyContext();

// look for a one on one conversation
const davesPubKey = '828adc3061e4f0c3f1984dce96003eb89d2ab279e703e01de806c9b2ba33ff73';
let contact = keyStore.contacts.find(x => x.content.contacts[davesPubKey] 
        && Object.keys(x.indexes).length == 1);
// or look by channelId// or look by channelId
const channelId = ContactChannel.getChannelId([
    davesPubKey, keyStore.ownerPubKey
])
contact = keyStore.contacts.find(x => x.channelId === channelId)

const [sub$, endSub] = contact.listen();
sub$.pipe(
    bufferTime(1000),
    tap(messages => {
        console.log(messages);
    })
).subscribe();

Version 1.5.2 Changes

  • added ability to call PrivateChannel.listen with keepAlive = false so the observable can get completed

License

MIT (c) 2023, 2024 David Krause https://defy.social, see LICENSE file.