diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 00000000..e5b6d8d6 --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,8 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/cold-pianos-tickle.md b/.changeset/cold-pianos-tickle.md new file mode 100644 index 00000000..e7993579 --- /dev/null +++ b/.changeset/cold-pianos-tickle.md @@ -0,0 +1,5 @@ +--- +"@nostr-dev-kit/ndk": minor +--- + +deprecate user.zap/ndk.zap -- use new NDKZapper instead diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 00000000..77b04199 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "fixed": [], + "linked": [], + "access": "public", + "baseBranch": "master", + "updateInternalDependencies": "patch", + "ignore": [] +} diff --git a/.changeset/eighty-toes-refuse.md b/.changeset/eighty-toes-refuse.md new file mode 100644 index 00000000..21a099c7 --- /dev/null +++ b/.changeset/eighty-toes-refuse.md @@ -0,0 +1,5 @@ +--- +"@nostr-dev-kit/ndk": patch +--- + +move NWC to ndk-wallet diff --git a/.changeset/fifty-llamas-boil.md b/.changeset/fifty-llamas-boil.md new file mode 100644 index 00000000..657b902d --- /dev/null +++ b/.changeset/fifty-llamas-boil.md @@ -0,0 +1,5 @@ +--- +"@nostr-dev-kit/ndk": patch +--- + +add pubkey hint to e tags diff --git a/.changeset/flat-garlics-taste.md b/.changeset/flat-garlics-taste.md new file mode 100644 index 00000000..e59d34d0 --- /dev/null +++ b/.changeset/flat-garlics-taste.md @@ -0,0 +1,5 @@ +--- +"@nostr-dev-kit/ndk-mobile": patch +--- + +add useUserProfile hook diff --git a/.changeset/fresh-oranges-guess.md b/.changeset/fresh-oranges-guess.md new file mode 100644 index 00000000..a56d009c --- /dev/null +++ b/.changeset/fresh-oranges-guess.md @@ -0,0 +1,5 @@ +--- +"@nostr-dev-kit/ndk-svelte": minor +--- + +add support for Svelte 5's runes diff --git a/.changeset/great-keys-knock.md b/.changeset/great-keys-knock.md new file mode 100644 index 00000000..11cf8620 --- /dev/null +++ b/.changeset/great-keys-knock.md @@ -0,0 +1,5 @@ +--- +"@nostr-dev-kit/ndk-mobile": patch +--- + +add LRU cache for profiles diff --git a/.changeset/khaki-pets-smell.md b/.changeset/khaki-pets-smell.md new file mode 100644 index 00000000..4638c43b --- /dev/null +++ b/.changeset/khaki-pets-smell.md @@ -0,0 +1,5 @@ +--- +"@nostr-dev-kit/ndk": patch +--- + +fix bug where both a and e tags were going in zap requests diff --git a/.changeset/pretty-berries-talk.md b/.changeset/pretty-berries-talk.md new file mode 100644 index 00000000..76efd29c --- /dev/null +++ b/.changeset/pretty-berries-talk.md @@ -0,0 +1,5 @@ +--- +"@nostr-dev-kit/ndk-wallet": patch +--- + +NWC support diff --git a/.changeset/purple-fireants-invite.md b/.changeset/purple-fireants-invite.md new file mode 100644 index 00000000..5e7433de --- /dev/null +++ b/.changeset/purple-fireants-invite.md @@ -0,0 +1,5 @@ +--- +"@nostr-dev-kit/ndk-mobile": patch +--- + +add sync profile fetching from cache diff --git a/.changeset/silly-cows-boil.md b/.changeset/silly-cows-boil.md new file mode 100644 index 00000000..f809c2d9 --- /dev/null +++ b/.changeset/silly-cows-boil.md @@ -0,0 +1,5 @@ +--- +"@nostr-dev-kit/ndk-cache-dexie": patch +--- + +add support in dexie cache to retrieve profile info synchronously diff --git a/.changeset/strong-files-fly.md b/.changeset/strong-files-fly.md new file mode 100644 index 00000000..2fef01c2 --- /dev/null +++ b/.changeset/strong-files-fly.md @@ -0,0 +1,5 @@ +--- +"@nostr-dev-kit/ndk": patch +--- + +add NIP-22 support diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 00000000..0ccefc48 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,13 @@ +# FIXME: Hardcoded node version +# FIXME: Use prod and dev stages! +FROM node:20-slim + +RUN corepack enable + +# FIXME: We need to use a WORKDIR because of +# https://stackoverflow.com/a/65443098 but +# the choice of the actual dir is quite arbitrary. +COPY . /app +WORKDIR /app + +RUN pnpm install diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..709d82c2 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,29 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node +{ + "name": "NDK devcontainer", + "build": { + // Path is relative to the devcontainer.json file. + "dockerfile": "Dockerfile", + "context": "../" + }, + "customizations": { + "vscode": { + "settings": { + "terminal.integrated.shell.linux": "/bin/bash" + } + } + }, + // Add pnpm bin to path, so we have turbo available + "postStartCommand": "echo 'export PATH=$(pnpm bin):$PATH' >> ~/.bashrc && . ~/.bashrc" + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "yarn install", + // Configure tool-specific properties. + // "customizations": {}, + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..e5dc490a --- /dev/null +++ b/.eslintignore @@ -0,0 +1,13 @@ +.DS_Store +node_modules +build +dist +package +.env +.env.* +!.env.example + +# Ignore files for PNPM, NPM and YARN +pnpm-lock.yaml +package-lock.json +yarn.lock diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..8b05a81c --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ["@nostr-dev-kit/custom"], +}; diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..605f05db --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,66 @@ +# Sample workflow for building and deploying a VitePress site to GitHub Pages +# +name: Deploy VitePress site to Pages + +on: + # Runs on pushes targeting the `main` branch. Change this to `master` if you're + # using the `master` branch as the default branch. + push: + branches: + - '*' + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: pages + cancel-in-progress: false + +jobs: + # Build job + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Not needed if lastUpdated is not enabled + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '20' + - name: Setup PNPM + uses: pnpm/action-setup@v4 + - name: Setup Pages + uses: actions/configure-pages@v4 + - name: Install dependencies + run: pnpm install # or pnpm install / yarn install / bun install + - name: Build with VitePress + run: pnpm docs:build # or pnpm docs:build / yarn docs:build / bun run docs:build + - name: Build typedoc + run: cd ndk && pnpm typedoc --out ../docs/.vitepress/dist/api + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: docs/.vitepress/dist + + # Deployment job + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + needs: build + runs-on: ubuntu-latest + name: Deploy + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..f757754f --- /dev/null +++ b/.prettierignore @@ -0,0 +1,5 @@ +dist +docs +coverage +**/.changeset +**/.svelte-kit diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..4431ccbf --- /dev/null +++ b/.prettierrc @@ -0,0 +1,16 @@ +{ + "useTabs": false, + "tabWidth": 4, + "singleQuote": false, + "semi": true, + "trailingComma": "es5", + "printWidth": 100, + "overrides": [ + { + "files": "*.svelte", + "options": { + "parser": "svelte" + } + } + ] +} diff --git a/BUILD.md b/BUILD.md new file mode 100644 index 00000000..741a89e4 --- /dev/null +++ b/BUILD.md @@ -0,0 +1,20 @@ +# Build NDK + +NDK is structured as a monorepo using `pnpm` as the package manager. + +``` +git clone https://github.com/nostr-dev-kit/ndk +cd ndk +pnpm install +pnpm build +``` + +If you only care about building ndk core and not the family of packages you can just + +``` +git clone https://github.com/nostr-dev-kit/ndk +cd ndk +pnpm install +cd ndk +pnpm build +``` diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..2a6ea523 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Pablo Fernandez + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/REFERENCES.md b/REFERENCES.md new file mode 100644 index 00000000..48274ac7 --- /dev/null +++ b/REFERENCES.md @@ -0,0 +1,45 @@ +# Open source Nostr apps using NDK + +This is a running list of applications using NDK that are open source. Use these codebases to understand +how others handle Nostr things using NDK. + +If you are the author of an application that uses NDK, send a pull-request to this repo adding your application +to this list. + +- [Highlighter](https://github.com/kind-0/highlighter) - By [@pablof7z](https://njump.me/npub1l2vyh47mk2p0qlsku7hg0vn29faehy9hy34ygaclpn66ukqp3afqutajft) + - Svelte, frontend +- [nsecBunker](https://github.com/kind-0/nsecbunkerd) - By [@pablof7z](https://njump.me/npub1l2vyh47mk2p0qlsku7hg0vn29faehy9hy34ygaclpn66ukqp3afqutajft) + - Typescript, backend +- [Highlighter Chrome Extension](https://github.com/pablof7z/highlighter-chrome-extension/) - By [@pablof7z](https://njump.me/npub1l2vyh47mk2p0qlsku7hg0vn29faehy9hy34ygaclpn66ukqp3afqutajft) + - Typescript, Chrome extension +- [Nostr Data Vending Machine](https://github.com/pablof7z/nostr-data-vending-machine) - By [@pablof7z](https://njump.me/npub1l2vyh47mk2p0qlsku7hg0vn29faehy9hy34ygaclpn66ukqp3afqutajft) + - Typescript, backend +- [Nostr Chat Widget](https://github.com/pablof7z/nostr-chat-widget) - By [@pablof7z](https://njump.me/npub1l2vyh47mk2p0qlsku7hg0vn29faehy9hy34ygaclpn66ukqp3afqutajft) + - Svelte, Rollup, embeddable widget +- [Zapstr](https://github.com/zapstr/zapstr) - By [@pablof7z](https://njump.me/npub1l2vyh47mk2p0qlsku7hg0vn29faehy9hy34ygaclpn66ukqp3afqutajft) + - Svelte, Frontend +- [Ostrich.work](https://github.com/erskingardner/ostrich.work) - By [@jeffg](https://njump.me/npub1zuuajd7u3sx8xu92yav9jwxpr839cs0kc3q6t56vd5u9q033xmhsk6c2uc) + - Svelte, frontend +- [Listr.lol](https://github.com/erskingardner/listr) - By [@jeffg](https://njump.me/npub1zuuajd7u3sx8xu92yav9jwxpr839cs0kc3q6t56vd5u9q033xmhsk6c2uc) + - Svelte, frontend +- [Lume](https://github.com/luminous-devs/lume) - By [@reya](https://njump.me/npub1zfss807aer0j26mwp2la0ume0jqde3823rmu97ra6sgyyg956e0s6xw445) + - Tauri, Desktop app +- [Nostr App Manager](https://github.com/nostrband/nostr-app-manager) - By [@nostrband](https://njump.me/npub1wc4rc9wxl2gfzxl384g0cw3f79nrms0sfdpe02y7aasy7c3we4sqd0qywr) + - React, Frontend +- [Stemstr](https://github.com/stemstr/Client) - By [@stemstr](https://njump.me/npub1stemstrls4f5plqeqkeq43gtjhtycuqd9w25v5r5z5ygaq2n2sjsd6mul5) +- [Audgit.ai](https://github.com/ArcadeLabsInc/audgit.ai) - By [@ArcadeLabsInc](https://njump.me/npub1tlv67m7xvlyplzexuynmfpguvyet0sjffce3y8vu0suuyuwgzauqjk7fdm) +- [Swarmstr](https://github.com/ptrio42/swarmstr.com) - By [@pitiunited](https://njump.me/npub178umpxtdflcm7a08nexvs4mu384kx0ngg9w8ltm5eut6q7lcp0vq05qrg4) +- [zapddit](https://github.com/vivganes/zapddit) - By [@vivganes](https://njump.me/npub1ltx67888tz7lqnxlrg06x234vjnq349tcfyp52r0lstclp548mcqnuz40t) +- [Nuxstr](https://github.com/Sebastix/nuxstr) - By [@Sebastix](https://njump.me/sebastian@sebastix.dev) + - Nuxt (Vue), frontend +- [Flockstr](https://github.com/zmeyer44/flockstr) - By [@zach](https://njump.me/npub1zach44xjpc4yyhx6pgse2cj2pf98838kja03dv2e8ly8lfr094vqvm5dy5) +- [Flare.pub](https://github.com/zmeyer44/flare) - By [@zach](https://njump.me/npub1zach44xjpc4yyhx6pgse2cj2pf98838kja03dv2e8ly8lfr094vqvm5dy5) +- [Pinstr.app](https://github.com/sepehr-safari/pinstr) - By [@sepehr](https://njump.me/nprofile1qqsru22d9lfnnwck54qr4phrvey50h2q33xc0gqxv5j03ftn4efu4rspr9mhxue69uhhyetvv9ujuumwdae8gtnnda3kjctv9uq3wamnwvaz7tmjwdekccte9ehx7um5wghx6mm99uq36amnwvaz7tmwdaehgu3wd46hg6tw09mkzmrvv46zucm0d5hsv6ffvh) + - React, Nostr Web Client +- [Nostr-Hooks](https://github.com/ostyjs/nostr-hooks) - By [@sepehr](https://njump.me/nprofile1qqsru22d9lfnnwck54qr4phrvey50h2q33xc0gqxv5j03ftn4efu4rspr9mhxue69uhhyetvv9ujuumwdae8gtnnda3kjctv9uq3wamnwvaz7tmjwdekccte9ehx7um5wghx6mm99uq36amnwvaz7tmwdaehgu3wd46hg6tw09mkzmrvv46zucm0d5hsv6ffvh) + - Stateful wrapper library of React hooks around NDK. +- [magicCity h=n](https://github.com/tezosmiami/hicetnunc) - By [@hicetnunc2000](https://github.com/hicetnunc2000/), [@tezosmiami](https://njump.me/npub190rqwj0nud4uhvmaeg7cgn0gypu0s09j87vqjluhfhju0req2khsskh9w7) +- [notepress](https://github.com/utxo-one/notepress) - By [@utxo](httsp://njump.me/_@utxo.one) + - No frameworks! A very simple long-form (NIP-23) reader +- [Olas 🌊](https://github.com/pablof7z/snapstr) - By [@pablof7z](https://njump.me/f7z.io) + - React Native, mobile-only app diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts new file mode 100644 index 00000000..75669e0d --- /dev/null +++ b/docs/.vitepress/config.mts @@ -0,0 +1,78 @@ +import { defineConfig } from 'vitepress' +import { withMermaid } from "vitepress-plugin-mermaid"; + +// https://vitepress.dev/reference/site-config +export default withMermaid(defineConfig({ + title: "NDK", + description: "NDK Docs", + base: "/ndk/", + ignoreDeadLinks: true, + themeConfig: { + // https://vitepress.dev/reference/default-theme-config + nav: [ + { text: 'Home', link: '/' }, + { text: 'API Reference', link: '/api/', target: '_blank' }, + { text: 'Wiki', link: 'https://wikifreedia.xyz/?c=NDK', target: '_blank' }, + ], + + sidebar: [ + { + text: "Getting Started", + items: [ + { text: 'Introduction', link: '/getting-started/introduction' }, + { text: 'Usage', link: '/getting-started/usage' }, + { text: 'Signers', link: '/getting-started/signers' }, + ] + }, + { + text: 'Tutorial', + items: [ + { text: 'Local-first', link: '/tutorial/local-first' }, + { text: 'Publishing', link: '/tutorial/publishing' }, + { text: "Subscription Management", link: '/tutorial/subscription-management' }, + { text: "Speed", link: '/tutorial/speed' }, + { text: 'Zaps', link: '/tutorial/zaps' }, + ] + }, + { + text: "Cache Adapters", + items: [ + { text: 'In-memory + dexie', link: '/cache/dexie' }, + { text: 'Local Nostr Relay', link: '/cache/nostr' }, + ] + }, + { + text: "Wallet", + items: [ + { text: 'Introduction', link: '/wallet/index' }, + { text: 'Nutsack (NIP-60)', link: '/wallet/nutsack' }, + { text: 'Nutzaps', link: '/wallet/nutzaps' }, + ] + }, + { + text: "Wrappers", + items: [ + { text: 'NDK Svelte', link: '/wrappers/svelte' }, + ] + }, + { + text: "Mobile", + items: [ + { text: 'Introduction', link: '/mobile/index' }, + { text: 'Session', link: '/mobile/session' }, + { text: 'Wallet', link: '/mobile/wallet' }, + ] + }, + { + text: "Internals", + items: [ + { text: "Subscription Lifecycle", link: '/internals/subscriptions' }, + ] + } + ], + + socialLinks: [ + { icon: 'github', link: 'https://github.com/nostr-dev-kit/ndk' } + ] + } +})) \ No newline at end of file diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts new file mode 100644 index 00000000..def4cfc8 --- /dev/null +++ b/docs/.vitepress/theme/index.ts @@ -0,0 +1,17 @@ +// https://vitepress.dev/guide/custom-theme +import { h } from 'vue' +import type { Theme } from 'vitepress' +import DefaultTheme from 'vitepress/theme' +import './style.css' + +export default { + extends: DefaultTheme, + Layout: () => { + return h(DefaultTheme.Layout, null, { + // https://vitepress.dev/guide/extending-default-theme#layout-slots + }) + }, + enhanceApp({ app, router, siteData }) { + // ... + } +} satisfies Theme diff --git a/docs/.vitepress/theme/style.css b/docs/.vitepress/theme/style.css new file mode 100644 index 00000000..d63aee82 --- /dev/null +++ b/docs/.vitepress/theme/style.css @@ -0,0 +1,139 @@ +/** + * Customize default theme styling by overriding CSS variables: + * https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css + */ + +/** + * Colors + * + * Each colors have exact same color scale system with 3 levels of solid + * colors with different brightness, and 1 soft color. + * + * - `XXX-1`: The most solid color used mainly for colored text. It must + * satisfy the contrast ratio against when used on top of `XXX-soft`. + * + * - `XXX-2`: The color used mainly for hover state of the button. + * + * - `XXX-3`: The color for solid background, such as bg color of the button. + * It must satisfy the contrast ratio with pure white (#ffffff) text on + * top of it. + * + * - `XXX-soft`: The color used for subtle background such as custom container + * or badges. It must satisfy the contrast ratio when putting `XXX-1` colors + * on top of it. + * + * The soft color must be semi transparent alpha channel. This is crucial + * because it allows adding multiple "soft" colors on top of each other + * to create a accent, such as when having inline code block inside + * custom containers. + * + * - `default`: The color used purely for subtle indication without any + * special meanings attched to it such as bg color for menu hover state. + * + * - `brand`: Used for primary brand colors, such as link text, button with + * brand theme, etc. + * + * - `tip`: Used to indicate useful information. The default theme uses the + * brand color for this by default. + * + * - `warning`: Used to indicate warning to the users. Used in custom + * container, badges, etc. + * + * - `danger`: Used to show error, or dangerous message to the users. Used + * in custom container, badges, etc. + * -------------------------------------------------------------------------- */ + + :root { + --vp-c-default-1: var(--vp-c-gray-1); + --vp-c-default-2: var(--vp-c-gray-2); + --vp-c-default-3: var(--vp-c-gray-3); + --vp-c-default-soft: var(--vp-c-gray-soft); + + --vp-c-brand-1: var(--vp-c-indigo-1); + --vp-c-brand-2: var(--vp-c-indigo-2); + --vp-c-brand-3: var(--vp-c-indigo-3); + --vp-c-brand-soft: var(--vp-c-indigo-soft); + + --vp-c-tip-1: var(--vp-c-brand-1); + --vp-c-tip-2: var(--vp-c-brand-2); + --vp-c-tip-3: var(--vp-c-brand-3); + --vp-c-tip-soft: var(--vp-c-brand-soft); + + --vp-c-warning-1: var(--vp-c-yellow-1); + --vp-c-warning-2: var(--vp-c-yellow-2); + --vp-c-warning-3: var(--vp-c-yellow-3); + --vp-c-warning-soft: var(--vp-c-yellow-soft); + + --vp-c-danger-1: var(--vp-c-red-1); + --vp-c-danger-2: var(--vp-c-red-2); + --vp-c-danger-3: var(--vp-c-red-3); + --vp-c-danger-soft: var(--vp-c-red-soft); +} + +/** + * Component: Button + * -------------------------------------------------------------------------- */ + +:root { + --vp-button-brand-border: transparent; + --vp-button-brand-text: var(--vp-c-white); + --vp-button-brand-bg: var(--vp-c-brand-3); + --vp-button-brand-hover-border: transparent; + --vp-button-brand-hover-text: var(--vp-c-white); + --vp-button-brand-hover-bg: var(--vp-c-brand-2); + --vp-button-brand-active-border: transparent; + --vp-button-brand-active-text: var(--vp-c-white); + --vp-button-brand-active-bg: var(--vp-c-brand-1); +} + +/** + * Component: Home + * -------------------------------------------------------------------------- */ + +:root { + --vp-home-hero-name-color: transparent; + --vp-home-hero-name-background: -webkit-linear-gradient( + 120deg, + #bd34fe 30%, + #41d1ff + ); + + --vp-home-hero-image-background-image: linear-gradient( + -45deg, + #bd34fe 50%, + #47caff 50% + ); + --vp-home-hero-image-filter: blur(44px); +} + +@media (min-width: 640px) { + :root { + --vp-home-hero-image-filter: blur(56px); + } +} + +@media (min-width: 960px) { + :root { + --vp-home-hero-image-filter: blur(68px); + } +} + +/** + * Component: Custom Block + * -------------------------------------------------------------------------- */ + +:root { + --vp-custom-block-tip-border: transparent; + --vp-custom-block-tip-text: var(--vp-c-text-1); + --vp-custom-block-tip-bg: var(--vp-c-brand-soft); + --vp-custom-block-tip-code-bg: var(--vp-c-brand-soft); +} + +/** + * Component: Algolia + * -------------------------------------------------------------------------- */ + +.DocSearch { + --docsearch-primary-color: var(--vp-c-brand-1) !important; +} + diff --git a/docs/api-examples.md b/docs/api-examples.md new file mode 100644 index 00000000..6bd8bb5c --- /dev/null +++ b/docs/api-examples.md @@ -0,0 +1,49 @@ +--- +outline: deep +--- + +# Runtime API Examples + +This page demonstrates usage of some of the runtime APIs provided by VitePress. + +The main `useData()` API can be used to access site, theme, and page data for the current page. It works in both `.md` and `.vue` files: + +```md + + +## Results + +### Theme Data +
{{ theme }}
+ +### Page Data +
{{ page }}
+ +### Page Frontmatter +
{{ frontmatter }}
+``` + + + +## Results + +### Theme Data +
{{ theme }}
+ +### Page Data +
{{ page }}
+ +### Page Frontmatter +
{{ frontmatter }}
+ +## More + +Check out the documentation for the [full list of runtime APIs](https://vitepress.dev/reference/runtime-api#usedata). diff --git a/docs/cache/dexie.md b/docs/cache/dexie.md new file mode 100644 index 00000000..114b53bd --- /dev/null +++ b/docs/cache/dexie.md @@ -0,0 +1,153 @@ +# Dexie Cache + +Meant to be used client-side within a browser context. This is a cache adapter for [Dexie](https://dexie.org/), a wrapper around IndexedDB. + +## Usage + +NDK will attempt to use the Dexie adapter to store users, events, and tags. The default behaviour is to always check the cache first and then hit relays, replacing older cached events as needed. + +## Support + +- [x] Events +- [x] User profiles +- [x] Event<>Tag indexes +- [x] NIP-05 lookups +- [x] Unpublished events + +### Install + +``` +pnpm add @nostr-dev-kit/ndk-cache-dexie +``` + +### Add as a cache adapter + +```ts +import NDKCacheAdapterDexie from "@nostr-dev-kit/ndk-cache-dexie"; + +const dexieAdapter = new NDKCacheAdapterDexie({ dbName: 'your-db-name' }); +const ndk = new NDK({cacheAdapter: dexieAdapter, ...other config options}); +``` + +🚨 Because Dexie only exists client-side, this cache adapter will not work in pure node.js environments. You'll need to make sure that you're using the right cache adapter in the right place (e.g. Redis on the backend, Dexie on the frontend). + +## Slowness + +Because IndexDB is painfully slow, this adapter will primarly act via an LRU cache that periodically flushes to the database. Individual read/writes don't directly hit the database. + +## Options + +[**NDK Dexie Cache Adapter**](../README.md) • **Docs** + +*** + +[NDK Dexie Cache Adapter](../globals.md) / NDKCacheAdapterDexieOptions + +# Interface: NDKCacheAdapterDexieOptions + +## Properties + +### dbName? + +> `optional` **dbName**: `string` + +The name of the database to use + +#### Defined in + +[ndk-cache-dexie/src/index.ts:34](https://github.com/nostr-dev-kit/ndk/blob/26ea669eeeadbc93b894cac1f29829e9a41694cb/ndk-cache-dexie/src/index.ts#L34) + +*** + +### debug? + +> `optional` **debug**: `Debugger` + +Debug instance to use for logging + +#### Defined in + +[ndk-cache-dexie/src/index.ts:39](https://github.com/nostr-dev-kit/ndk/blob/26ea669eeeadbc93b894cac1f29829e9a41694cb/ndk-cache-dexie/src/index.ts#L39) + +*** + +### eventCacheSize? + +> `optional` **eventCacheSize**: `number` + +#### Defined in + +[ndk-cache-dexie/src/index.ts:53](https://github.com/nostr-dev-kit/ndk/blob/26ea669eeeadbc93b894cac1f29829e9a41694cb/ndk-cache-dexie/src/index.ts#L53) + +*** + +### eventTagsCacheSize? + +> `optional` **eventTagsCacheSize**: `number` + +#### Defined in + +[ndk-cache-dexie/src/index.ts:54](https://github.com/nostr-dev-kit/ndk/blob/26ea669eeeadbc93b894cac1f29829e9a41694cb/ndk-cache-dexie/src/index.ts#L54) + +*** + +### expirationTime? + +> `optional` **expirationTime**: `number` + +The number of seconds to store events in Dexie (IndexedDB) before they expire +Defaults to 3600 seconds (1 hour) + +#### Defined in + +[ndk-cache-dexie/src/index.ts:45](https://github.com/nostr-dev-kit/ndk/blob/26ea669eeeadbc93b894cac1f29829e9a41694cb/ndk-cache-dexie/src/index.ts#L45) + +*** + +### indexableKinds? + +> `optional` **indexableKinds**: `number`[] \| `"all"` \| `"none"` + +The kinds of events that should be indexed + +#### Default + +```ts +"all" +``` + +#### Defined in + +[ndk-cache-dexie/src/index.ts:60](https://github.com/nostr-dev-kit/ndk/blob/26ea669eeeadbc93b894cac1f29829e9a41694cb/ndk-cache-dexie/src/index.ts#L60) + +*** + +### nip05CacheSize? + +> `optional` **nip05CacheSize**: `number` + +#### Defined in + +[ndk-cache-dexie/src/index.ts:52](https://github.com/nostr-dev-kit/ndk/blob/26ea669eeeadbc93b894cac1f29829e9a41694cb/ndk-cache-dexie/src/index.ts#L52) + +*** + +### profileCacheSize? + +> `optional` **profileCacheSize**: `number` + +Number of profiles to keep in an LRU cache + +#### Defined in + +[ndk-cache-dexie/src/index.ts:50](https://github.com/nostr-dev-kit/ndk/blob/26ea669eeeadbc93b894cac1f29829e9a41694cb/ndk-cache-dexie/src/index.ts#L50) + +*** + +### zapperCacheSize? + +> `optional` **zapperCacheSize**: `number` + +#### Defined in + +[ndk-cache-dexie/src/index.ts:51](https://github.com/nostr-dev-kit/ndk/blob/26ea669eeeadbc93b894cac1f29829e9a41694cb/ndk-cache-dexie/src/index.ts#L51) diff --git a/docs/cache/nostr.md b/docs/cache/nostr.md new file mode 100644 index 00000000..ec166e2a --- /dev/null +++ b/docs/cache/nostr.md @@ -0,0 +1,34 @@ +# Nostr Cache Adapter + +NDK cache adapter using a nostr relay as the database. + +This cache adapter is meant to be run against a local relay. This adapter will generate two NDK instances: + +`ndk` -- This talks exclusively to the local relay, with outbox model disabled. +`fallbackNdk` -- This is used to hydrate the cache and uses the outbox model -- each query the cache receives is placed in a queue in the background so that subsequent requests can be served from the cache. All events from other relays + +## Usage + +### Install + +``` +npm add @nostr-dev-kit/ndk-cache-nostr + +``` + +### Add as a cache adapter + +```ts +import NDKCacheAdapterNostr from "@nostr-dev-kit/ndk-cache-nostr"; + +const cacheAdapter = new NDKCacheAdapterNostr({ + relayUrl: 'ws://localhost:5577', +}); +const ndk = new NDK({ cacheAdapter }); +``` + +If running server-side in a NodeJS environment, you should make sure to polyfill `WebSocket`. + +# License + +MIT diff --git a/docs/getting-started/introduction.md b/docs/getting-started/introduction.md new file mode 100644 index 00000000..50c63b8f --- /dev/null +++ b/docs/getting-started/introduction.md @@ -0,0 +1,35 @@ +# Getting started + +## Installation + +```sh +npm add @nostr-dev-kit/ndk +``` + +## Debugging + +NDK uses the `debug` package to assist in understanding what's happening behind the hood. If you are building a package +that runs on the server define the `DEBUG` envionment variable like + +```sh +export DEBUG='ndk:*' +``` + +or in the browser enable it by writing in the DevTools console + +```sh +localStorage.debug = 'ndk:*' +``` + +## Network Debugging + +You can construct NDK passing a netDebug callback to receive network traffic events, particularly useful for debugging applications not running in a browser. + +```ts +const netDebug = (msg: string, relay: NDKRelay, direction?: "send" | "recv") = { + const hostname = new URL(relay.url).hostname; + netDebug(hostname, msg, direction); +} + +ndk = new NDK({ netDebug }); +``` diff --git a/docs/getting-started/signers.md b/docs/getting-started/signers.md new file mode 100644 index 00000000..47346bef --- /dev/null +++ b/docs/getting-started/signers.md @@ -0,0 +1,38 @@ +# Signers + +NDK uses signers _optionally_ passed in to sign events. Note that it is possible to use NDK without signing events (e.g. [to get someone's profile](https://github.com/nostr-dev-kit/ndk-cli/blob/master/src/commands/profile.ts)). + +Signing adapters can be passed in when NDK is instantiated or later during runtime. + +### Using a NIP-07 browser extension (e.g. Alby, nos2x) + +Instatiate NDK with a NIP-07 signer + +```ts +// Import the package, NIP-07 signer and NDK event +import NDK, { NDKEvent, NDKNip07Signer } from "@nostr-dev-kit/ndk"; + +const nip07signer = new NDKNip07Signer(); +const ndk = new NDK({ signer: nip07signer }); +``` + +NDK can now ask for permission, via their NIP-07 extension, to... + +**Read the user's public key** + +```ts +nip07signer.user().then(async (user) => { + if (!!user.npub) { + console.log("Permission granted to read their public key:", user.npub); + } +}); +``` + +**Sign & publish events** + +```ts +const ndkEvent = new NDKEvent(ndk); +ndkEvent.kind = 1; +ndkEvent.content = "Hello, world!"; +ndkEvent.publish(); // This will trigger the extension to ask the user to confirm signing. +``` \ No newline at end of file diff --git a/docs/getting-started/usage.md b/docs/getting-started/usage.md new file mode 100644 index 00000000..068f985c --- /dev/null +++ b/docs/getting-started/usage.md @@ -0,0 +1,57 @@ +# Usage + +## Instantiate an NDK instance + +You can pass an object with several options to a newly created instance of NDK. + +- `explicitRelayUrls` – an array of relay URLs. +- `signer` - an instance of a [signer](#signers). +- `cacheAdapter` - an instance of a [Cache Adapter](#caching) +- `debug` - Debug instance to use for logging. Defaults to `debug("ndk")`. + +```ts +// Import the package +import NDK from "@nostr-dev-kit/ndk"; + +// Create a new NDK instance with explicit relays +const ndk = new NDK({ + explicitRelayUrls: ["wss://a.relay", "wss://another.relay"], +}); +``` + +If the signer implements the `getRelays()` method, NDK will use the relays returned by that method as the explicit relays. + +```ts +// Import the package +import NDK, { NDKNip07Signer } from "@nostr-dev-kit/ndk"; + +// Create a new NDK instance with just a signer (provided the signer implements the getRelays() method) +const nip07signer = new NDKNip07Signer(); +const ndk = new NDK({ signer: nip07signer }); +``` + +Note: In normal client use, it's best practice to instantiate NDK as a singleton class. [See more below](#architecture-decisions--suggestions). + +## Connecting + +After you've instatiated NDK, you need to tell it to connect before you'll be able to interact with any relays. + +```ts +// Import the package +import NDK from "@nostr-dev-kit/ndk"; + +// Create a new NDK instance with explicit relays +const ndk = new NDK({ + explicitRelayUrls: ["wss://a.relay", "wss://another.relay"], +}); +// Now connect to specified relays +await ndk.connect(); +``` + +## Architecture decisions & suggestions + +- Users of NDK should instantiate a single NDK instance. +- That instance tracks state with all relays connected, explicit and otherwise. +- All relays are tracked in a single pool that handles connection errors/reconnection logic. +- RelaySets are assembled ad-hoc as needed depending on the queries set, although some RelaySets might be long-lasting, like the `explicitRelayUrls` specified by the user. +- RelaySets are always a subset of the pool of all available relays. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..52fac990 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,18 @@ +--- +# https://vitepress.dev/reference/default-theme-home-page +layout: home + +hero: + name: "NDK Documentation" + tagline: "Nostr Development Kit Docs" + actions: + - theme: brand + text: Getting Started + link: /getting-started/introduction.html + - theme: secondary + text: References + link: https://github.com/nostr-dev-kit/ndk/blob/master/REFERENCES.md + +--- + +NDK is a nostr development kit that makes the experience of building Nostr-related applications, whether they are relays, clients, or anything in between, better, more reliable and overall nicer to work with than existing solutions. \ No newline at end of file diff --git a/docs/internals/subscriptions.md b/docs/internals/subscriptions.md new file mode 100644 index 00000000..ab328ce8 --- /dev/null +++ b/docs/internals/subscriptions.md @@ -0,0 +1,205 @@ +# Subscriptions Lifecycle +When an application creates a subscription a lot of things happen under the hood. + +Say we want to see `kind:1` events from pubkeys `123`, `456`, and `678`. + +```ts +const subscription = ndk.subscribe({ kinds: [1], authors: [ "123", "456", "678" ]}) +``` + +Since the application level didn't explicitly provide a relay-set, which is the most common use case, NDK will calculate a relay set based on the outbox model plus a variety of some other factors. + +So the first thing we'll do before talking to relays is, decide to *which* relays we should talk to. + +The `calculateRelaySetsFromFilters` function will take care of this and provide us with a map of relay URLs and filters for each relay. + +This means that the query, as specified by the client might be broken into distinct queries specialized for the different relays. + +For example, if we have 3 relays, and the query is for `kind:1` events from pubkeys `a` and `b`, the `calculateRelaySetsFromFilters` function might return something like this: + +```ts +{ + "wss://relay1": { kinds: [1], authors: [ "a" ] }, + "wss://relay2": { kinds: [1], authors: [ "b" ] }, +} +``` + +```mermaid +flowchart TD + Client -->|"kinds: [1], authors: [a, b]"| Subscription1 + Subscription1 -->|"kinds: [1], authors: [a]"| wss://relay1 + Subscription1 -->|"kinds: [1], authors: [b]"| wss://relay2 +``` + +## Subscription bundling +Once the subscription has been split into the filters each relay should receive, the filters are sent to the individual `NDKRelay`'s `NDKRelaySubscriptionManager` instances. + +`NDKRelaySubscriptionManager` is responsible for keeping track of the active and scheduled subscriptions that are pending to be executed within an individual relay. + +This is an important aspect to consider: + +> `NDKSubscription` have a different lifecycle than `NDKRelaySubscription`. For example, a subscription that is set to close after EOSE might still be active within the `NDKSubscription` lifecycle, but it might have been already been closed within the `NDKRelaySubscription` lifecycle, since NDK attempts to keep the minimum amount of open subscriptions at any given time. + +## NDKRelaySubscription +Most NDK subscriptions (by default) are set to be executed with a grouping delay. Will cover what this looks like in practice later, but for now, let's understand than when the `NDKRelaySubscriptionManager` receives an order, it might not execute it right away. + +The different filters that can be grouped together (thus executed as a single `REQ` within a relay) are grouped within the same `NDKRelaySubscription` instance and the execution scheduler is computed respecting what each individual `NDKSubscription` has requested. + +(For example, if a subscription with a `groupingDelay` of `at-least` 500 millisecond has been grouped with another subscription with a `groupingDelay` of `at-least` 1000 milliseconds, the `NDKRelaySubscriptionManager` will wait 1000 ms before sending the `REQ` to this particular relay). + +### Execution +Once the filter is executed at the relay level, the `REQ` is submitted into that relay's `NDKRelayConnectivity` instance, which will take care of monitoring for responses for this particular REQ and communicate them back into the `NDKRelaySubscription` instance. + +Each `EVENT` that comes back as a response to our `REQ` within this `NDKRelaySubscription` instance is then compared with the filters of each `NDKSubscription` that has been grouped and if it matches, it is sent back to the `NDKSubscription` instance. + + +# Example + +If an application requests `kind:1` of pubkeys `123`, `456`, and `789`. It creates an `NDKSubscription`: + +```ts +ndk.subscribe({ kinds: [1], authors: [ "123", "456", "789" ]}, { groupableDelay: 500, groupableDelayType: 'at-least' }) +// results in NDKSubscription1 with filters { kinds: [1], authors: [ "123", "456", "789" ] } +``` + +Some other part of the application requests a kind:7 from pubkey `123` at the same time. + +```ts +ndk.subscribe({ kinds: [7], authors: [ "123" ]}, { groupableDelay: 500, groupableDelayType: 'at-most' }) +// results in NDKSubscription2 with filters { kinds: [7], authors: [ "123" ] } +``` + +```mermaid +flowchart TD + subgraph Subscriptions Lifecycle + A[Application] -->|"kinds: [1], authors: [123, 456, 678], groupingDelay: at-least 500ms"| B[NDKSubscription1] + + A2[Application] -->|"kinds: [7], authors: [123], groupingDelay: at-most 1000ms"| B2[NDKSubscription2] + end +``` + +Both subscriptions have their relayset calculated by NDK and, the resulting filters are sent into the `NDKRelaySubscriptionManager`, which will decide what, and how filters can be grouped. + +```mermaid +flowchart TD + subgraph Subscriptions Lifecycle + A[Application] -->|"kinds: [1], authors: [123, 456, 678], groupingDelay: at-least 500ms"| B[NDKSubscription1] + B --> C{Calculate Relay Sets} + + A2[Application] -->|"kinds: [7], authors: [123], groupingDelay: at-most 1000ms"| B2[NDKSubscription2] + B2 --> C2{Calculate Relay Sets} + end + + subgraph Subscription Bundling + C -->|"kinds: [1], authors: [123]"| E1[wss://relay1 NDKRelaySubscriptionManager] + C -->|"kinds: [1], authors: [456]"| E2[wss://relay2 NDKRelaySubscriptionManager] + C -->|"kinds: [1], authors: [678]"| E3[wss://relay3 NDKRelaySubscriptionManager] + + C2 -->|"kinds: [7], authors: [123]"| E1 + end +``` + +The `NDKRelaySubscriptionManager` will create `NDKRelaySubscription` instances, or add filters to them if `NDKRelaySubscription` with the same filter fingerprint exists. + +```mermaid +flowchart TD + subgraph Subscriptions Lifecycle + A[Application] -->|"kinds: [1], authors: [123, 456, 678], groupingDelay: at-least 500ms"| B[NDKSubscription1] + B --> C{Calculate Relay Sets} + + A2[Application] -->|"kinds: [7], authors: [123], groupingDelay: at-most 1000ms"| B2[NDKSubscription2] + B2 --> C2{Calculate Relay Sets} + end + + subgraph Subscription Bundling + C -->|"kinds: [1], authors: [123]"| E1[wss://relay1 NDKRelaySubscriptionManager] + C -->|"kinds: [1], authors: [456]"| E2[wss://relay2 NDKRelaySubscriptionManager] + C -->|"kinds: [1], authors: [678]"| E3[wss://relay3 NDKRelaySubscriptionManager] + + C2 -->|"kinds: [7], authors: [123]"| E1 + + E1 -->|"Grouping Delay: at-most 1000ms"| F1[NDKRelaySubscription] + E2 -->|"Grouping Delay: at-least 500ms"| F2[NDKRelaySubscription] + E3 -->|"Grouping Delay: at-least 500ms"| F3[NDKRelaySubscription] + end +``` + +Each individual `NDKRelaySubscription` computes the execution schedule of the filters it has received and sends them to the `NDKRelayConnectivity` instance, which in turns sends the `REQ` to the relay. + +```mermaid +flowchart TD + subgraph Subscriptions Lifecycle + A[Application] -->|"kinds: [1], authors: [123, 456, 678], groupingDelay: at-least 500ms"| B[NDKSubscription1] + B --> C{Calculate Relay Sets} + + A2[Application] -->|"kinds: [7], authors: [123], groupingDelay: at-most 1000ms"| B2[NDKSubscription2] + B2 --> C2{Calculate Relay Sets} + end + + subgraph Subscription Bundling + C -->|"kinds: [1], authors: [123]"| E1[wss://relay1 NDKRelaySubscriptionManager] + C -->|"kinds: [1], authors: [456]"| E2[wss://relay2 NDKRelaySubscriptionManager] + C -->|"kinds: [1], authors: [678]"| E3[wss://relay3 NDKRelaySubscriptionManager] + + C2 -->|"kinds: [7], authors: [123]"| E1 + + E1 -->|"Grouping Delay: at-most 1000ms"| F1[NDKRelaySubscription] + E2 -->|"Grouping Delay: at-least 500ms"| F2[NDKRelaySubscription] + E3 -->|"Grouping Delay: at-least 500ms"| F3[NDKRelaySubscription] + + F1 -->|"REQ: kinds: [1, 7], authors: [123]"| G1[NDKRelayConnectivity] + F2 -->|"REQ: kinds: [1], authors: [456]"| G2[NDKRelayConnectivity] + F3 -->|"REQ: kinds: [1], authors: [678]"| G3[NDKRelayConnectivity] + end + + subgraph Execution + G1 -->|"Send REQ to wss://relay1 after 1000ms"| R1[Relay1] + G2 -->|"Send REQ to wss://relay2 after 500ms"| R2[Relay2] + G3 -->|"Send REQ to wss://relay3 after 500ms"| R3[Relay3] + end +``` + +As the events come from the relays, `NDKRelayConnectivity` will send them back to the `NDKRelaySubscription` instance, which will compare the event with the filters of the `NDKSubscription` instances that have been grouped together and send the received event back to the correct `NDKSubscription` instance. + +```mermaid +flowchart TD + subgraph Subscriptions Lifecycle + A[Application] -->|"kinds: [1], authors: [123, 456, 678], groupingDelay: at-least 500ms"| B[NDKSubscription1] + B --> C{Calculate Relay Sets} + + A2[Application] -->|"kinds: [7], authors: [123], groupingDelay: at-most 1000ms"| B2[NDKSubscription2] + B2 --> C2{Calculate Relay Sets} + end + + subgraph Subscription Bundling + C -->|"kinds: [1], authors: [123]"| E1[wss://relay1 NDKRelaySubscriptionManager] + C -->|"kinds: [1], authors: [456]"| E2[wss://relay2 NDKRelaySubscriptionManager] + C -->|"kinds: [1], authors: [678]"| E3[wss://relay3 NDKRelaySubscriptionManager] + + C2 -->|"kinds: [7], authors: [123]"| E1 + + E1 -->|"Grouping Delay: at-most 1000ms"| F1[NDKRelaySubscription] + E2 -->|"Grouping Delay: at-least 500ms"| F2[NDKRelaySubscription] + E3 -->|"Grouping Delay: at-least 500ms"| F3[NDKRelaySubscription] + + F1 -->|"REQ: kinds: [1, 7], authors: [123]"| G1[NDKRelayConnectivity] + F2 -->|"REQ: kinds: [1], authors: [456]"| G2[NDKRelayConnectivity] + F3 -->|"REQ: kinds: [1], authors: [678]"| G3[NDKRelayConnectivity] + end + + subgraph Execution + G1 -->|"Send REQ to wss://relay1 after 1000ms"| R1[Relay1] + G2 -->|"Send REQ to wss://relay2 after 500ms"| R2[Relay2] + G3 -->|"Send REQ to wss://relay3 after 500ms"| R3[Relay3] + + R1 -->|"EVENT: kinds: [1]"| H1[NDKRelaySubscription] + R1 -->|"EVENT: kinds: [7]"| H2[NDKRelaySubscription] + R2 -->|"EVENT"| H3[NDKRelaySubscription] + R3 -->|"EVENT"| H4[NDKRelaySubscription] + + H1 -->|"Matched Filters: kinds: [1]"| I1[NDKSubscription1] + H2 -->|"Matched Filters: kinds: [7]"| I2[NDKSubscription2] + H3 -->|"Matched Filters: kinds: [1]"| I1 + H4 -->|"Matched Filters: kinds: [1]"| I1 + end +``` \ No newline at end of file diff --git a/docs/markdown-examples.md b/docs/markdown-examples.md new file mode 100644 index 00000000..f9258a55 --- /dev/null +++ b/docs/markdown-examples.md @@ -0,0 +1,85 @@ +# Markdown Extension Examples + +This page demonstrates some of the built-in markdown extensions provided by VitePress. + +## Syntax Highlighting + +VitePress provides Syntax Highlighting powered by [Shiki](https://github.com/shikijs/shiki), with additional features like line-highlighting: + +**Input** + +````md +```js{4} +export default { + data () { + return { + msg: 'Highlighted!' + } + } +} +``` +```` + +**Output** + +```js{4} +export default { + data () { + return { + msg: 'Highlighted!' + } + } +} +``` + +## Custom Containers + +**Input** + +```md +::: info +This is an info box. +::: + +::: tip +This is a tip. +::: + +::: warning +This is a warning. +::: + +::: danger +This is a dangerous warning. +::: + +::: details +This is a details block. +::: +``` + +**Output** + +::: info +This is an info box. +::: + +::: tip +This is a tip. +::: + +::: warning +This is a warning. +::: + +::: danger +This is a dangerous warning. +::: + +::: details +This is a details block. +::: + +## More + +Check out the documentation for the [full list of markdown extensions](https://vitepress.dev/guide/markdown). diff --git a/docs/mobile/index.md b/docs/mobile/index.md new file mode 100644 index 00000000..e78b9544 --- /dev/null +++ b/docs/mobile/index.md @@ -0,0 +1,53 @@ +# NDK Mobile + +A React Native/Expo implementation of [NDK (Nostr Development Kit)](https://github.com/nostr-dev-kit/ndk) that provides a complete toolkit for building Nostr applications on mobile platforms. + +## Features + +- 🔐 Multiple signer implementations (NIP-07, NIP-46, Private Key) +- 💾 SQLite-based caching for offline support +- 🔄 Subscription management with automatic reconnection +- 📱 React Native and Expo compatibility +- 🪝 React hooks for easy state management +- 👛 Integrated wallet support + +## Installation + +```sh +npm install @nostr-dev-kit/ndk-mobile +``` + +## Usage + +When using this library don't import `@nostr-dev-kit/ndk` directly, instead import `@nostr-dev-kit/ndk-mobile`. `ndk-mobile` exports the same classes as `ndk`, so you can just swap the import. + +Once you have imported the library, you can use the `NDKProvider` to wrap your application. Pass as props all the typical arguments you would pass to an `new NDK()` call. + +```tsx +function App() { + return {/* your app here */}; +} +``` + +## useNDK() + +`useNDK()` provides access to the `ndk` instance and some other useful information. + +```tsx +function LoginScreen() { + const { ndk, currentUser, login } = useNDK(); + + useEffect(() => { + if (currentUser) alert("you are now logged in as ", +currentUser.pubkey); + }, [currentUser]); + + return + + {#if open} + + {/if} + + + \ No newline at end of file diff --git a/ndk-svelte-components/src/lib/event/EventThread.svelte b/ndk-svelte-components/src/lib/event/EventThread.svelte new file mode 100644 index 00000000..fa9ec263 --- /dev/null +++ b/ndk-svelte-components/src/lib/event/EventThread.svelte @@ -0,0 +1,217 @@ + + +
+ {#if !skipEvent} +
+ {#each Array.from(threadIds.values()).sort(sortThread) as event (event.id)} + + {/each} +
+ {/if} + + {#if replyIds.size > 0 || $extraItems} +
+ {#each $extraItems??[] as item (item.props.key)} + + + + {/each} + + {#each Array.from(replyIds.values()).sort(sortReplies) as reply (reply.id)} + {#if !whitelistPubkeys || !useWhitelist || whitelistPubkeys.has(reply.pubkey)} + + + + {:else if whitelistPubkeys && useWhitelist && !whitelistPubkeys.has(reply.pubkey)} +
+
+ This reply was hidden + +
+
+ {/if} + {/each} +
+ {/if} +
+ + \ No newline at end of file diff --git a/ndk-svelte-components/src/lib/event/content/EventContent.svelte b/ndk-svelte-components/src/lib/event/content/EventContent.svelte new file mode 100644 index 00000000..e9411834 --- /dev/null +++ b/ndk-svelte-components/src/lib/event/content/EventContent.svelte @@ -0,0 +1,94 @@ + + +{#if event} + {#if event.kind === 1} + + {:else if event.kind === 40} + + {:else if event.kind === 1063} + + {:else if event.kind === 1985} + + {:else if event.kind === 9802} + + {:else if event.kind === 30000} + + {:else if event.kind === 30001} + + {:else if markdownKinds.includes(event.kind)} + + {:else} + + {/if} +{/if} diff --git a/ndk-svelte-components/src/lib/event/content/Kind1.svelte b/ndk-svelte-components/src/lib/event/content/Kind1.svelte new file mode 100644 index 00000000..e9a11e78 --- /dev/null +++ b/ndk-svelte-components/src/lib/event/content/Kind1.svelte @@ -0,0 +1,82 @@ + + +
+

+ {#each groupedContent as { type, value }, i} + {#if type === NEWLINE} + + {:else if type === TOPIC} + + {:else if type === LINK} + + {:else if type === LINKCOLLECTION} + {#if mediaCollectionComponent} + v.value.url)} /> + {:else} +

+ {#each value as {type: _type, value: _value}, j} + + {/each} +
+ {/if} + {:else if type.match(/^nostr:np(rofile|ub)$/)} + + {:else if type.startsWith('nostr:') && showMedia && isStartOrEnd(i) && value.id !== anchorId} + + {:else if type.startsWith('nostr:')} + + {:else} + {value} + {/if} + {' '} + {/each} +

+ +
diff --git a/ndk-svelte-components/src/lib/event/content/Kind1063.svelte b/ndk-svelte-components/src/lib/event/content/Kind1063.svelte new file mode 100644 index 00000000..4dfab226 --- /dev/null +++ b/ndk-svelte-components/src/lib/event/content/Kind1063.svelte @@ -0,0 +1,81 @@ + + +
+

File metadata

+
Description: {event.content}
+
+ File URL: + {truncatedFile} +
+
MIME type: {mimeType}
+
File size: {size}
+
Dimensions: {dim}
+ {#if showMedia && SUPPORTED_IMAGE_TYPES.includes(mimeType)} +
File preview:
+
+ {event.content} +
+ {/if} + {#if showMedia && SUPPORTED_VIDEO_TYPES.includes(mimeType)} +
File preview:
+
+ + +
+ {/if} +
+ + diff --git a/ndk-svelte-components/src/lib/event/content/Kind30000.svelte b/ndk-svelte-components/src/lib/event/content/Kind30000.svelte new file mode 100644 index 00000000..8b11f1c5 --- /dev/null +++ b/ndk-svelte-components/src/lib/event/content/Kind30000.svelte @@ -0,0 +1,29 @@ + + +{#each list.items as tag (tag[1])} +
+ + +
+{/each} + + \ No newline at end of file diff --git a/ndk-svelte-components/src/lib/event/content/Kind30001.svelte b/ndk-svelte-components/src/lib/event/content/Kind30001.svelte new file mode 100644 index 00000000..997fd0a3 --- /dev/null +++ b/ndk-svelte-components/src/lib/event/content/Kind30001.svelte @@ -0,0 +1,27 @@ + + +{#each list.items as tag (tag[1])} +
+ +
+{/each} + + \ No newline at end of file diff --git a/ndk-svelte-components/src/lib/event/content/Kind30023.svelte b/ndk-svelte-components/src/lib/event/content/Kind30023.svelte new file mode 100644 index 00000000..86c33fc5 --- /dev/null +++ b/ndk-svelte-components/src/lib/event/content/Kind30023.svelte @@ -0,0 +1,59 @@ + + +
+ +
+ + diff --git a/ndk-svelte-components/src/lib/event/content/Kind9802.svelte b/ndk-svelte-components/src/lib/event/content/Kind9802.svelte new file mode 100644 index 00000000..ae9960b6 --- /dev/null +++ b/ndk-svelte-components/src/lib/event/content/Kind9802.svelte @@ -0,0 +1,26 @@ + + +
+
+ + {@html sanitizeHtml(context || event.content)} +
+
+ +{#if ref} +
+ +
+{/if} diff --git a/ndk-svelte-components/src/lib/event/content/NoteContentLink.svelte b/ndk-svelte-components/src/lib/event/content/NoteContentLink.svelte new file mode 100644 index 00000000..1160cc62 --- /dev/null +++ b/ndk-svelte-components/src/lib/event/content/NoteContentLink.svelte @@ -0,0 +1,30 @@ + + +{#if showMedia && value.isMedia} + {#if !!isImage(value.url)} + {""} + {:else if isVideo(value.url)} + +