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 🙏

© 2025 – Pkg Stats / Ryan Hefner

portal-sdk

v0.1.4

Published

TypeScript client for Portal WebSocket Server - Nostr-based authentication, Lightning Network payment processing and more

Readme

Portal SDK - TypeScript Client

A TypeScript client for the Portal WebSocket Server, providing Nostr-based authentication and Lightning Network payment processing capabilities.

Installation

npm install portal-sdk

Quick Start

import { PortalSDK, Currency, Timestamp } from 'portal-sdk';

// Initialize the client
const client = new PortalSDK({
  serverUrl: 'ws://localhost:3000/ws',
  connectTimeout: 10000
});

// Connect to the server
await client.connect();

// Authenticate with your token
await client.authenticate('your-auth-token');

// Generate authentication URL for users
const url = await client.newKeyHandshakeUrl((mainKey) => {
  console.log('Received key handshake from:', mainKey);
});

console.log('Authentication URL:', url);

Features

  • Nostr Authentication: Secure user authentication using Nostr protocol
  • Lightning Payments: Single and recurring payment processing
  • Profile Management: Fetch and update user profiles
  • JWT Support: Issue and verify JWT tokens
  • Relay Management: Add and remove Nostr relays dynamically
  • Real-time Updates: WebSocket-based real-time notifications
  • TypeScript Support: Full TypeScript definitions included

API Reference

PortalSDK Class

The main client class for interacting with the Portal server.

Constructor

new PortalSDK(config: ClientConfig)

Parameters:

  • config.serverUrl (string): WebSocket server URL
  • config.connectTimeout (number, optional): Connection timeout in milliseconds (default: 10000)

Methods

connect(): Promise<void>

Establishes a WebSocket connection to the server.

await client.connect();
disconnect(): void

Closes the WebSocket connection and cleans up resources.

client.disconnect();
authenticate(token: string): Promise<void>

Authenticates with the server using a token.

await client.authenticate('your-auth-token');
newKeyHandshakeUrl(onKeyHandshake: (mainKey: string, preferredRelays: string[]) => void, staticToken?: string): Promise<string>

Generates a new authentication URL for user key handshake.

const url = await client.newKeyHandshakeUrl((mainKey, preferredRelays) => {
  console.log('Recevied key handshake from:', mainKey);
  console.log('User wants to talk at:', preferredRelays);
});
authenticateKey(mainKey: string, subkeys?: string[]): Promise<AuthResponseData>

Authenticates a user's key with optional subkeys.

const authResponse = await client.authenticateKey('user-pubkey', ['subkey1', 'subkey2']);
requestRecurringPayment(mainKey: string, subkeys: string[], paymentRequest: RecurringPaymentRequestContent): Promise<RecurringPaymentResponseContent>

Requests a recurring payment subscription.

const paymentRequest: RecurringPaymentRequestContent = {
  amount: 10000, // 10 sats
  currency: Currency.Millisats,
  recurrence: {
    calendar: "monthly",
    first_payment_due: Timestamp.fromNow(86400), // 24 hours from now
    max_payments: 12
  },
  expires_at: Timestamp.fromNow(3600) // 1 hour from now
};

const result = await client.requestRecurringPayment('user-pubkey', [], paymentRequest);
requestSinglePayment(mainKey: string, subkeys: string[], paymentRequest: SinglePaymentRequestContent, onStatusChange: (status: InvoiceStatus) => void): Promise<void>

Requests a single payment with the NWC embedded wallet.

const paymentRequest: SinglePaymentRequestContent = {
  amount: 5000, // 5 sats
  currency: Currency.Millisats,
  description: "Product purchase"
};

await client.requestSinglePayment('user-pubkey', [], paymentRequest, (status) => {
  console.log('Payment status:', status);
});
requestInvoicePayment(mainKey: string, subkeys: string[], paymentRequest: InvoicePaymentRequestContent, onStatusChange: (status: InvoiceStatus) => void): Promise<void>

Requests payment for a specific invoice (generated outside of the NWC wallet).

const paymentRequest: InvoicePaymentRequestContent = {
  amount: 1000,
  currency: Currency.Millisats,
  description: "Invoice payment",
  invoice: "lnbc..." // Lightning invoice
};

await client.requestInvoicePayment('user-pubkey', [], paymentRequest, (status) => {
  console.log('Invoice payment status:', status);
});
fetchProfile(mainKey: string): Promise<Profile | null>

Fetches a user's profile.

const profile = await client.fetchProfile('user-pubkey');
console.log('User profile:', profile);
setProfile(profile: Profile): Promise<void>

Updates the service profile.

const profile: Profile = {
  id: 'user-id',
  pubkey: 'user-pubkey',
  name: 'John Doe',
  display_name: 'John',
  picture: 'https://example.com/avatar.jpg',
  about: 'Software developer',
  nip05: '[email protected]' // Read NIP-05 for the spec of /.well-known/nostr.json
};

await client.setProfile(profile);
closeRecurringPayment(mainKey: string, subkeys: string[], subscriptionId: string): Promise<string>

Closes a recurring payment subscription.

const message = await client.closeRecurringPayment('user-pubkey', [], 'subscription-id');
console.log('Subscription closed:', message);
listenClosedRecurringPayment(onClosed: (data: CloseRecurringPaymentNotification) => void): Promise<void>

Listens for closed recurring payment notifications.

await client.listenClosedRecurringPayment((data) => {
  console.log('Payment closed:', data);
});
requestInvoice(recipientKey: string, content: InvoicePaymentRequestContent): Promise<InvoiceResponseContent>

Requests an invoice for payment.

const content: InvoicePaymentRequestContent = {
  amount: 1000,
  currency: Currency.Millisats,
  description: "Invoice request"
};

const invoice = await client.requestInvoice('recipient-pubkey', content);
console.log('Invoice:', invoice.invoice);
issueJwt(targetKey: string, durationHours: number): Promise<string>

Issues a JWT token for a target key.

const token = await client.issueJwt('target-pubkey', 24); // 24 hours
console.log('JWT token:', token);
verifyJwt(publicKey: string, token: string): Promise<{ targetKey: string }>

Verifies a JWT token.

const claims = await client.verifyJwt('public-key', 'jwt-token');
console.log('Token target key:', claims.targetKey);
addRelay(relay: string): Promise<string>

Adds a relay to the relay pool.

const relayUrl = await client.addRelay('wss://relay.damus.io');
console.log('Added relay:', relayUrl);
removeRelay(relay: string): Promise<string>

Removes a relay from the relay pool.

const relayUrl = await client.removeRelay('wss://relay.damus.io');
console.log('Removed relay:', relayUrl);
on(eventType: string | EventCallbacks, callback?: (data: any) => void): void

Registers event listeners.

// Single event
client.on('connected', () => {
  console.log('Connected to server');
});

// Multiple events
client.on({
  onConnected: () => console.log('Connected'),
  onDisconnected: () => console.log('Disconnected'),
  onError: (error) => console.error('Error:', error)
});
off(eventType: string, callback: (data: any) => void): void

Removes an event listener.

const callback = (data) => console.log(data);
client.on('event', callback);
client.off('event', callback);

Types

Currency

enum Currency {
  Millisats = "Millisats"
}

Timestamp

class Timestamp {
  static fromDate(date: Date): Timestamp
  static fromNow(seconds: number): Timestamp
  toJSON(): string
  toString(): string
  valueOf(): bigint
}

Profile

interface Profile {
  id: string;
  pubkey: string;
  name?: string;
  display_name?: string;
  picture?: string;
  about?: string;
  nip05?: string;
}

Payment Types

interface RecurringPaymentRequestContent {
  amount: number;
  currency: Currency;
  recurrence: RecurrenceInfo;
  current_exchange_rate?: any;
  expires_at: Timestamp;
  auth_token?: string;
}

interface SinglePaymentRequestContent {
  description: string;
  amount: number;
  currency: Currency;
  subscription_id?: string;
  auth_token?: string;
}

interface InvoicePaymentRequestContent {
  amount: number;
  currency: Currency;
  description: string;
  subscription_id?: string;
  auth_token?: string;
  current_exchange_rate?: any;
  expires_at?: Timestamp;
  invoice?: string;
}

Response Types

interface AuthResponseData {
  user_key: string;
  recipient: string;
  challenge: string;
  status: AuthResponseStatus;
}

interface InvoiceStatus {
  status: 'paid' | 'timeout' | 'error' | 'user_approved' | 'user_success' | 'user_failed' | 'user_rejected';
  preimage?: string;
  reason?: string;
}

Examples

Complete Authentication Flow

import { PortalSDK } from 'portal-sdk';

const client = new PortalSDK({
  serverUrl: 'ws://localhost:3000/ws'
});

try {
  await client.connect();
  await client.authenticate('your-auth-token');
  
  const url = await client.newKeyHandshakeUrl((mainKey) => {
    console.log('Received key handshake from:', mainKey);
    const authResponse = await client.authenticateKey(mainKey, []);
    console.log('Auth response:', authResponse);
  });
  
  console.log('Share this URL with the user:', url);
} catch (error) {
  console.error('Authentication failed:', error);
} finally {
  client.disconnect();
}

Payment Processing

import { PortalSDK, Currency, Timestamp } from 'portal-sdk';

const client = new PortalSDK({
  serverUrl: 'ws://localhost:3000/ws'
});

await client.connect();
await client.authenticate('your-auth-token');

// Request a single payment
await client.requestSinglePayment(
  'user-pubkey',
  [],
  {
    amount: 1000,
    currency: Currency.Millisats,
    description: "Product purchase"
  },
  (status) => {
    if (status.status === 'paid') {
      console.log('Payment completed!');
    } else if (status.status === 'timeout') {
      console.log('Payment timed out');
    }
  }
);

Profile Management

import { PortalSDK } from 'portal-sdk';

const client = new PortalSDK({
  serverUrl: 'ws://localhost:3000/ws'
});

await client.connect();
await client.authenticate('your-auth-token');

// Fetch user profile
const profile = await client.fetchProfile('user-pubkey');
console.log('User profile:', profile);

Relay Management

import { PortalSDK } from 'portal-sdk';

const client = new PortalSDK({
  serverUrl: 'ws://localhost:3000/ws'
});

await client.connect();
await client.authenticate('your-auth-token');

// Add a relay to the relay pool
const addedRelay = await client.addRelay('wss://relay.damus.io');
console.log('Added relay:', addedRelay);

// Remove a relay from the relay pool
const removedRelay = await client.removeRelay('wss://relay.damus.io');
console.log('Removed relay:', removedRelay);

Error Handling

The client throws errors for various scenarios:

try {
  await client.connect();
  await client.authenticate('invalid-token');
} catch (error) {
  if (error.message.includes('Authentication failed')) {
    console.error('Invalid authentication token');
  } else if (error.message.includes('Connection timeout')) {
    console.error('Server connection timeout');
  }
}

Browser Support

This client works in both Node.js and browser environments. For browser usage, the WebSocket implementation is automatically handled by the isomorphic-ws package.

License

MIT License - see LICENSE file for details.