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 🙏

© 2026 – Pkg Stats / Ryan Hefner

subsafe

v1.0.1

Published

MOSIP WebSub event listener with message decryption capabilities

Readme

SubSafe

A flexible MOSIP WebSub event listener with message decryption capabilities.

Features

  • WebSub subscription management for MOSIP events
  • Automatic handling of webhook verification challenges
  • Data Share URI resolution and data retrieval
  • MOSIP partner credential decryption using P12 certificates
  • Configurable through environment variables or constructor options
  • Event-based notification processing
  • Advanced logging with configurable log levels

Installation

npm install subsafe

Usage as an npm Package

You can use SubSafe in two ways:

  1. As a standalone project (clone and run)
  2. As a dependency in your own project (npm install)

Using as a Dependency

To use SubSafe as an npm package in your project:

# Install the package
npm install subsafe

Then in your application:

// Import the SubSafe package
const SubSafe = require('subsafe');

// Create an instance with your configuration
const subSafe = new SubSafe({
  // Your configuration here
  port: 3000,
  // MOSIP configuration
  baseUrl: 'https://your-mosip-instance.com',
  clientId: 'your-client-id',
  clientSecret: 'your-client-secret',
  // WebSub configuration
  hubUrl: 'https://your-websub-hub.com',
  topic: 'your-websub-topic',
  // DataShare configuration
  dataSourceMode: 'datashare', // 'datashare' or 'direct'
  p12Path: './your-cert.p12',
  p12Password: 'your-p12-password'
});

// Register a notification handler
subSafe.onNotification((decryptedData, rawNotification) => {
  console.log('Received decrypted data:', decryptedData);
  // Your business logic here
});

// Start the server and subscribe to events
async function startListener() {
  // Start the server
  const callbackUrl = await subSafe.start();
  console.log(`Server started with callback URL: ${callbackUrl}`);
  
  // Subscribe to the WebSub topic
  const subscriptionResult = await subSafe.subscribe();
  console.log('Subscription result:', subscriptionResult);
}

startListener();

For more examples, see the examples directory.

Publishing Your Changes

If you've made improvements to SubSafe and want to publish your own version:

  1. Update the version in package.json
  2. Run tests to make sure everything works:
    npm run test:package
  3. Publish to npm:
    npm publish

Quick Start

const SubSafe = require('subsafe');
// The logger is directly available from the SubSafe package
const { logger } = SubSafe;

// Create a new SubSafe instance
const subsafe = new SubSafe({
  // Optional: Override environment variables with constructor options
  hubUrl: process.env.MOSIP_WEBSUB_URL,
  topic: process.env.MOSIP_TOPIC,
  callbackPath: process.env.CALLBACK_PATH,
  
  // Log level configuration
  logLevel: process.env.LOG_LEVEL || 'info',
  
  // Auth credentials
  baseUrl: process.env.MOSIP_BASE_URL,
  clientId: process.env.MOSIP_CLIENT_ID,
  clientSecret: process.env.MOSIP_SECRET_KEY,
  tokenUrl: process.env.MOSIP_TOKEN_URL,
  authEndpoint: process.env.MOSIP_AUTH_ENDPOINT,
  appId: process.env.MOSIP_APP_ID,
  
  // P12 configuration for decryption
  p12Path: process.env.P12_PATH,
  p12Password: process.env.P12_PASSWORD,
  
  // Data source configuration
  dataSourceMode: process.env.DATA_SOURCE_MODE || 'datashare', // 'direct' or 'datashare'
  directMessagePath: process.env.DIRECT_MESSAGE_PATH || 'event.data.credential',
  registrationIdField: process.env.REGISTRATION_ID_FIELD || 'event.data.registrationId',
  
  // Auth configuration
  baseUrl: process.env.MOSIP_BASE_URL,
  clientId: process.env.MOSIP_CLIENT_ID,
  clientSecret: process.env.MOSIP_SECRET_KEY,
  tokenUrl: process.env.MOSIP_TOKEN_URL,
  authEndpoint: process.env.MOSIP_AUTH_ENDPOINT,
  appId: process.env.MOSIP_APP_ID,
  
  // Data share configuration
  dataShareBaseUrl: process.env.DATASHARE_BASE_URL,
  
  // Decryption configuration
  p12Path: process.env.P12_PATH,
  p12Password: process.env.P12_PASSWORD,
  tempEncryptedFile: process.env.TEMP_ENCRYPTED_FILE,
  tempDecryptedFile: process.env.TEMP_DECRYPTED_FILE
});

// Register notification handler for incoming messages
subsafe.onNotification((data) => {
  logger.info('Registration ID:', { registrationId: data.registrationId });
  logger.debug('Decrypted data:', { data });
  
  // Your business logic here
});

// Start the server
subsafe.start(process.env.CALLBACK_URL)
  .then(async (server) => {
    logger.info('Server started!');
    
    // Optional: Subscribe to the WebSub hub
    await subsafe.subscribe();
  })
  .catch(error => {
    logger.error('Failed to start server:', error);
  });

Configuration

SubSafe can be configured using environment variables (recommended) or constructor options.

Environment Variables

Create a .env file in your project root:

# Server Configuration
PORT=3000
CALLBACK_URL=
CALLBACK_PATH=

# Logging Configuration
# Levels: error, warn, info, http, verbose, debug, silly
LOG_LEVEL=info

# MOSIP Authentication Configuration
MOSIP_BASE_URL=
MOSIP_CLIENT_ID=
MOSIP_SECRET_KEY=
MOSIP_TOKEN_URL=
MOSIP_AUTH_ENDPOINT=
MOSIP_APP_ID=

# MOSIP WebSub Configuration
MOSIP_WEBSUB_URL=
MOSIP_TOPIC=
MOSIP_WEBSUB_SECRET=

# Data Source Configuration
# Options: 'direct' or 'datashare'
DATA_SOURCE_MODE=datashare
# Path to find the encrypted data in direct messages (dot notation)
DIRECT_MESSAGE_PATH=event.data.credential
# Path to find the registration ID in messages (dot notation)
REGISTRATION_ID_FIELD=event.data.registrationId

# Data Share Configuration
DATASHARE_BASE_URL=

# Decryption Configuration
P12_PATH=
P12_PASSWORD=
TEMP_ENCRYPTED_FILE=
TEMP_DECRYPTED_FILE=

# Auto Subscription
AUTO_SUBSCRIBE=false

Constructor Options

Constructor options take precedence over environment variables.

const options = {
  // Server configuration
  port: process.env.PORT || 3000,
  
  // WebSub configuration
  hubUrl: process.env.MOSIP_WEBSUB_URL,
  topic: process.env.MOSIP_TOPIC,
  callbackPath: process.env.CALLBACK_PATH,
  secret: process.env.MOSIP_WEBSUB_SECRET,
  
  // Data source configuration
  dataSourceMode: process.env.DATA_SOURCE_MODE || 'datashare', // 'direct' or 'datashare'
  directMessagePath: process.env.DIRECT_MESSAGE_PATH || 'event.data.credential',
  registrationIdField: process.env.REGISTRATION_ID_FIELD || 'event.data.registrationId',
  
  // Auth configuration
  baseUrl: process.env.MOSIP_BASE_URL,
  clientId: process.env.MOSIP_CLIENT_ID,
  clientSecret: process.env.MOSIP_SECRET_KEY,
  tokenUrl: process.env.MOSIP_TOKEN_URL,
  authEndpoint: process.env.MOSIP_AUTH_ENDPOINT,
  appId: process.env.MOSIP_APP_ID,
  
  // Data share configuration
  dataShareBaseUrl: process.env.DATASHARE_BASE_URL,
  
  // Decryption configuration
  p12Path: process.env.P12_PATH,
  p12Password: process.env.P12_PASSWORD,
  tempEncryptedFile: process.env.TEMP_ENCRYPTED_FILE,
  tempDecryptedFile: process.env.TEMP_DECRYPTED_FILE
};

const subsafe = new SubSafe(options);

API Reference

SubSafe

Constructor

const subsafe = new SubSafe(options);

Methods

  • onNotification(handler) - Add an event handler for notification events
  • start(callbackUrl) - Start the server and set the callback URL
  • subscribe() - Subscribe to the WebSub hub
  • unsubscribe() - Unsubscribe from the WebSub hub

Event Handler

The notification handler receives two parameters:

subsafe.onNotification((data, rawEvent) => {
  // data: Decrypted and parsed data (if successful)
  // rawEvent: Original webhook payload from MOSIP
});

Data Source Modes

SubSafe supports two modes for receiving encrypted data:

1. DataShare Mode (Default)

In this mode, the notification contains a dataShareUri that points to an endpoint where the encrypted data can be retrieved. SubSafe fetches the data from this URI using the configured authentication.

Example notification in DataShare mode:

{
  "event": {
    "dataShareUri": "https://mosip.example.com/datashare/abc123",
    "timestamp": "2023-01-01T12:00:00Z"
  }
}

2. Direct Mode

In this mode, the encrypted data is included directly within the notification payload. The path to the encrypted data in the notification body is configurable via the directMessagePath option.

Example notification in Direct mode:

{
  "publisher": "CREDENTIAL_SERVICE",
  "topic": "patner-opencrvs-i1/CREDENTIAL_ISSUED",
  "publishedOn": "2025-03-28T11:33:26.355Z",
  "event": {
    "id": "1a61e56f-70c5-4369-9cb4-e602f0af328d",
    "transactionId": "adb5f231-cb59-487a-8895-dac71d584927",
    "type": {
      "namespace": "mosip",
      "name": "mosip"
    },
    "timestamp": "2025-03-28T11:33:26.355Z",
    "dataShareUri": null,
    "data": {
      "registrationId": "10007100070013220250319095337",
      "credential": "ENCRYPTED_DATA_STRING",
      "credentialType": "euin"
    }
  }
}

With the default directMessagePath of event.data.credential, SubSafe would extract and decrypt the value of ENCRYPTED_DATA_STRING in the above example.

Registration ID Tracking

SubSafe can extract and track registration IDs from incoming messages, allowing you to associate encrypted data with its source registration. The registration ID is extracted from the notification using a configurable field path and is included in the decrypted result.

To configure the registration ID field:

// Using constructor options
const subsafe = new SubSafe({
  registrationIdField: 'event.data.registrationId'
});

// Or using environment variables in .env file
// REGISTRATION_ID_FIELD=event.data.registrationId

In the notification handler, the registration ID will be included in the decrypted data:

subsafe.onNotification((data, rawEvent) => {
  logger.info('Registration ID:', { registrationId: data.registrationId });
  logger.debug('Decrypted data:', { data });
  
  // Your business logic here
});

The dashboard interface also displays the configured registration ID field path and the last received registration ID.

To configure the data source mode:

// Using constructor options
const subsafe = new SubSafe({
  dataSourceMode: 'direct', // or 'datashare'
  directMessagePath: 'event.data.credential'
});

// Or using environment variables in .env file
// DATA_SOURCE_MODE=direct
// DIRECT_MESSAGE_PATH=event.data.credential

Running Standalone

SubSafe can also be run as a standalone application:

# Clone the repository
git clone https://github.com/abdulbathish/subsafe.git
cd subsafe

# Install dependencies
npm install

# Configure environment variables
cp .env.example .env
# Edit the .env file with your configuration

# Start the server
npm start

Testing

SubSafe includes test scripts for different modes (subscription, datashare, direct). You can run these tests to verify the functionality:

# Run the default subscription test
npm test

# Run specific mode tests
npm run test:subscription  # Test subscription mode
npm run test:datashare     # Test datashare mode
npm run test:direct        # Test direct credential mode

# Run tests with debug logging level
npm run test:debug             # Subscription test with debug logging
npm run test:debug:datashare   # Datashare test with debug logging
npm run test:debug:direct      # Direct test with debug logging

Test Configuration

The tests use a separate .env.test file for configuration. Copy the .env.example file to .env.test and update the values:

cp .env.example .env.test
# Edit .env.test with your test configuration

Make sure to set the appropriate log level for your tests:

# In .env.test
LOG_LEVEL=info  # or debug, error, warn, etc.

License

MIT

Logging

SubSafe includes a comprehensive logging system using Winston. Logs are captured at different levels and can be configured according to your needs.

Log Levels

SubSafe supports the following log levels (from highest to lowest priority):

  • error: Error events that might still allow the application to continue running
  • warn: Warning events that might indicate potential issues
  • info: Informational messages highlighting the progress of the application (default)
  • http: HTTP request-specific messages
  • verbose: More detailed informational messages
  • debug: Detailed debugging information
  • silly: Extremely detailed debugging information

Configuration

You can configure the log level in two ways:

  1. Environment variable:
LOG_LEVEL=debug
  1. Constructor option:
const subsafe = new SubSafe({
  logLevel: 'debug'
});

Log Output

Logs are written to:

  • Console (with colors)
  • logs/combined.log - Contains all logs of the configured level and above
  • logs/error.log - Contains only error-level logs

Additionally, logs are captured for display in the dashboard interface.

Usage in Your Code

When using SubSafe as a module in your application, you can access the logger:

const SubSafe = require('subsafe');
// The logger is directly available from the SubSafe package
const { logger } = SubSafe;

const subsafe = new SubSafe();

logger.info('Application started');
logger.debug('Detailed debugging information', { 
  additionalData: 'Any JavaScript object' 
});

subsafe.onNotification((data) => {
  logger.info('Notification received', { registrationId: data.registrationId });
  // Process data...
});

Examples

SubSafe includes several examples to help you get started:

Basic Usage Example

See the examples/basic-usage.js file for a simple example of how to use SubSafe in your project.

DataShare Mode Example

The examples/datashare-mode.js example demonstrates how to use SubSafe in DataShare mode, where:

  • The notification contains a dataShareUri pointing to encrypted data
  • SubSafe resolves the URI, downloads the encrypted data, and decrypts it

Run this example with:

node examples/datashare-mode.js

Direct Mode Example

The examples/direct-mode.js example demonstrates how to use SubSafe in Direct mode, where:

  • The notification contains the encrypted data directly in the message body
  • SubSafe extracts and decrypts the data

Run this example with:

node examples/direct-mode.js