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

@sidub-inc/licensing-client

v1.5.1

Published

React/TypeScript runtime library for Sidub Licensing - License enforcement and consumption reporting

Readme

@sidub-inc/licensing-client

TypeScript/React runtime library for Sidub Licensing. Provides license enforcement, feature gating, consumption reporting, and cryptographic signature verification.

Installation

npm install @sidub-inc/licensing-client

Requirements:

  • Modern browsers with Web Crypto API support (Chrome, Firefox, Safari, Edge)
  • Node.js 18+ (or polyfill for fetch and crypto.subtle)
  • React 16.8+ (optional, for hooks)

Configuration

Using Encoded Credentials (Recommended)

Encoded credentials bundle all required values into a single portable string:

import { LicensingClient } from '@sidub-inc/licensing-client';

const client = new LicensingClient({
  licenseServiceUri: 'https://api.monaiq.com/licensing',
  encodedCredential: 'SIDUB_LIC_eyJ...'  // From your license portal
});

The encoded credential contains: licenseId, serviceKeyId, serviceKeyPublicMember, and apiAccessKey.

Manual Configuration

const client = new LicensingClient({
  licenseServiceUri: 'https://api.monaiq.com/licensing',
  apiKey: 'your-api-key',
  licenseId: 'your-license-uuid',                    // Optional if passed to getAuthorization()
  serviceKeyId: 'your-service-key-uuid',             // Required for signature validation
  serviceKeyPublicMember: 'base64-encoded-public-key' // Required for signature validation
});

Configuration Reference

| Property | Required | Description | |----------|----------|-------------| | licenseServiceUri | Yes | Base URI for the licensing API | | encodedCredential | No | Encoded credential string (SIDUB_LIC_...) | | apiKey | No | API authentication key | | licenseId | No | Default license ID | | serviceKeyId | No | Key ID for signature verification | | serviceKeyPublicMember | No | Base64 EC public key (P-256 SPKI) for signature verification | | consumptionServiceUri | No | Separate URI for consumption reporting | | timeout | No | Request timeout in ms (default: 30000) | | validateSignatures | No | Enable/disable signature validation (default: auto) | | cacheEnabled | No | Enable authorization caching (default: true) | | cacheMaxSize | No | Maximum cached authorizations (default: 100) | | contextProvider | No | Custom ILicensingContextProvider for credential resolution | | billableResourceId | No | Billable resource ID for consumption reporting | | billablePlanId | No | Billable plan ID for consumption reporting |

Core Operations

1. Get License Authorization

// Using configured license ID
const authorization = await client.getAuthorization();

// With explicit license ID
const authorization = await client.getAuthorization('license-uuid');

// Authorization includes:
// - licenseId, classification, features[], issuedAt, expiresAt
// - signature (if server-signed)
// - signatureValidated (true if cryptographic verification passed)

2. Assert License Conditions

import { ServiceAccessAssertion, ServiceAccessLevel } from '@sidub-inc/licensing-client';

const assertion = ServiceAccessAssertion.create('premium-feature', ServiceAccessLevel.Allowed);
const hasAccess = client.assertLicense(assertion, authorization);

3. Report Consumption

await client.performOperation({
  licenseId: 'license-uuid',
  feature: { featureId: 'api-calls' },
  operationType: 'increment',
  quantity: 1,
  timestamp: new Date()
});

4. Checkout Flow

Create embedded checkout sessions for purchasing offerings:

// Create a checkout session
const session = await client.createCheckoutSession({
  offeringId: 'offering-uuid',
  issuerClientId: 'issuer-uuid',
  correlationId: 'correlation-uuid',
  customerEmail: '[email protected]',
  successUrl: 'https://example.com/success',
  cancelUrl: 'https://example.com/cancel'
});

// For paid offerings, redirect to payment
if (session.sessionUrl) {
  window.location.href = session.sessionUrl;
}

// Poll for checkout result
const result = await client.getCheckoutResult(session.sessionId);
// result.status: 'pending' | 'completed' | 'failed'
// result.encodedCredential: SIDUB_LIC_... (on completion)

// Poll with exponential backoff (recommended for full lifecycle)
const result = await client.pollCheckoutResult(session.sessionId, {
  maxAttempts: 60,    // default: 60
  signal: abortController.signal  // optional: cancel polling
});
// result.status: 'completed' | 'failed'
// result.encodedCredential: SIDUB_LIC_... (on completion)
// Throws LicensingError if max attempts exceeded or signal aborted

5. Report Access Check (Telemetry)

await client.reportAccessCheck(authorization, {
  featureId: 'api-calls',
  featureKey: 'api-calls',
  IsBillable: true,
  Amount: 1
});

Authorization Caching

Authorization responses are cached in memory with TTL-based expiry (derived from expiresAt). Caching is enabled by default.

const client = new LicensingClient({
  licenseServiceUri: '...',
  encodedCredential: '...',
  cacheEnabled: true,   // default
  cacheMaxSize: 100     // default
});

// Manual cache management
client.clearCache();                    // Remove all cached authorizations
client.invalidateCache('license-uuid'); // Remove entries for a specific license

When caching is enabled, repeated calls to getAuthorization() with the same license ID return the cached result until it expires.

Context Providers

Context providers resolve licensing credentials dynamically, supporting multi-tenant scenarios.

ConfigurationContextProvider (Default)

Builds context from LicensingConfig fields. Used automatically when no custom provider is configured:

import { ConfigurationContextProvider } from '@sidub-inc/licensing-client';

const provider = new ConfigurationContextProvider(config);
const context = await provider.resolveContext();
// context: { licenseId, serviceKeyId, serviceKeyPublicMember, apiAccessKey, billableResourceId?, billablePlanId? }

Custom Context Provider

Implement ILicensingContextProvider for custom resolution strategies (e.g., per-tenant lookup):

import { ILicensingContextProvider, LicensingContextType } from '@sidub-inc/licensing-client';

class TenantContextProvider implements ILicensingContextProvider {
  async resolveContext(): Promise<LicensingContextType | null> {
    const tenantCreds = await fetchTenantCredentials();
    if (!tenantCreds) return null;
    return {
      licenseId: tenantCreds.licenseId,
      serviceKeyId: tenantCreds.serviceKeyId,
      serviceKeyPublicMember: tenantCreds.publicKey,
      apiAccessKey: tenantCreds.apiKey
    };
  }
}

Portable Encoded Credentials

import { licensingContextFromEncodedString, licensingContextToEncodedString } from '@sidub-inc/licensing-client';

// Decode an encoded credential string into a context
const context = licensingContextFromEncodedString('SIDUB_LIC_eyJ...', billableResourceId, billablePlanId);

// Encode a context back to a portable string (billable fields are not included)
const encoded = licensingContextToEncodedString(context);

Feature State (Rate Limiting)

Track local rate-limit consumption within time windows:

import { RateLimitFeatureState } from '@sidub-inc/licensing-client';

// Track consumption within a 60-second window
const state = new RateLimitFeatureState(60);
state.consumeRate(1);
state.consumeRate(1);
const current = state.getConsumption(); // 2 (entries older than 60s are pruned)

RateLimitAssertion consults local feature state before server-reported consumption:

// Store feature state on the client
client.setFeatureState('api-calls', state);
const stored = client.getFeatureState('api-calls');

Assertions

Assertions provide declarative license condition checking.

FeatureExistsAssertion

import { FeatureExistsAssertion } from '@sidub-inc/licensing-client';

const assertion = FeatureExistsAssertion.create('analytics');
const hasFeature = client.assertLicense(assertion, authorization);

ServiceAccessAssertion

import { ServiceAccessAssertion, ServiceAccessLevel } from '@sidub-inc/licensing-client';

const assertion = ServiceAccessAssertion.create('api-access', ServiceAccessLevel.Allowed);
const hasAccess = client.assertLicense(assertion, authorization);

RateLimitAssertion

import { RateLimitAssertion } from '@sidub-inc/licensing-client';

const assertion = RateLimitAssertion.create({
  featureId: 'api-calls',
  rateLimit: 1000,
  currentConsumption: 500
});
const withinLimit = client.assertLicense(assertion, authorization);

Composite Assertions

import { CompositeAssertion, FeatureExistsAssertion } from '@sidub-inc/licensing-client';

// AND: all must be satisfied
const both = CompositeAssertion.and(
  FeatureExistsAssertion.create('feature1'),
  FeatureExistsAssertion.create('feature2')
);

// OR: any must be satisfied
const either = CompositeAssertion.or(
  FeatureExistsAssertion.create('feature1'),
  FeatureExistsAssertion.create('feature2')
);

// NOT: invert result
import { NotAssertion } from '@sidub-inc/licensing-client';
const notBlocked = NotAssertion.create(FeatureExistsAssertion.create('blocked'));

React Integration

Provider Setup

import { LicensingProvider } from '@sidub-inc/licensing-client';

function App() {
  return (
    <LicensingProvider 
      config={{ 
        licenseServiceUri: 'https://api.monaiq.com/licensing',
        encodedCredential: process.env.REACT_APP_LICENSE_CREDENTIAL
      }}
    >
      <YourApp />
    </LicensingProvider>
  );
}

useLicenseAuthorization

import { useLicenseAuthorization } from '@sidub-inc/licensing-client';

function LicensedComponent() {
  const { 
    authorization,      // LicenseAuthorization | null
    loading,            // boolean
    error,              // LicensingError | null
    signatureValidated, // boolean
    fetchAuthorization, // (licenseId?: string) => Promise<LicenseAuthorization>
    clearAuthorization  // () => void
  } = useLicenseAuthorization();

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!authorization) return null;

  return <div>License loaded {signatureValidated && '✓'}</div>;
}

useAssertion

import { useLicenseAuthorization, useAssertion, ServiceAccessAssertion, ServiceAccessLevel } from '@sidub-inc/licensing-client';

function FeatureGate() {
  const { authorization } = useLicenseAuthorization();
  const assertion = ServiceAccessAssertion.create('premium', ServiceAccessLevel.Allowed);
  const hasPremium = useAssertion(assertion, authorization);

  return hasPremium ? <PremiumFeature /> : <UpgradePrompt />;
}

useLicenseFeature

import { useLicenseAuthorization, useLicenseFeature } from '@sidub-inc/licensing-client';

function Component() {
  const { authorization } = useLicenseAuthorization();
  const hasAnalytics = useLicenseFeature(authorization, 'analytics');

  return hasAnalytics ? <Analytics /> : null;
}

useLicenseValidity

import { useLicenseAuthorization, useLicenseValidity } from '@sidub-inc/licensing-client';

function LicenseStatus() {
  const { authorization } = useLicenseAuthorization();
  const isValid = useLicenseValidity(authorization);

  return <span>{isValid ? 'Active' : 'Expired'}</span>;
}

useReportConsumption

import { useLicensingContext, useReportConsumption } from '@sidub-inc/licensing-client';

function UsageTracker() {
  const client = useLicensingContext();
  const { reportConsumption, isReporting } = useReportConsumption(client);

  const handleUse = async () => {
    await reportConsumption({
      licenseId: 'license-uuid',
      feature: { featureId: 'api-calls' },
      operationType: 'increment',
      quantity: 1,
      timestamp: new Date()
    });
  };

  return <button onClick={handleUse} disabled={isReporting}>Track Usage</button>;
}

Checkout Lifecycle (pollCheckoutResult)

Use pollCheckoutResult() on LicensingClient for the full checkout lifecycle with exponential backoff:

import { useLicensingContext } from '@sidub-inc/licensing-client';

function PurchaseButton({ offeringId }: { offeringId: string }) {
  const client = useLicensingContext();
  const [status, setStatus] = useState<'idle' | 'processing' | 'completed' | 'failed'>('idle');
  const [result, setResult] = useState<CheckoutSessionResult | null>(null);
  const controllerRef = useRef<AbortController | null>(null);

  const handlePurchase = async () => {
    setStatus('processing');
    try {
      const session = await client.createCheckoutSession({
        offeringId,
        issuerClientId: 'issuer-uuid',
        correlationId: crypto.randomUUID(),
        customerEmail: '[email protected]',
        successUrl: window.location.origin + '/success',
        cancelUrl: window.location.origin + '/cancel'
      });

      // For paid offerings, redirect to payment
      if (session.sessionUrl) {
        window.location.href = session.sessionUrl;
        return;
      }

      // For free/trial offerings, poll for result
      controllerRef.current = new AbortController();
      const checkoutResult = await client.pollCheckoutResult(session.sessionId, {
        signal: controllerRef.current.signal,
        maxAttempts: 60  // ~15 min with exponential backoff (1s → 15s cap)
      });
      setResult(checkoutResult);
      setStatus(checkoutResult.status === 'completed' ? 'completed' : 'failed');
    } catch (error) {
      setStatus('failed');
    }
  };

  useEffect(() => {
    return () => controllerRef.current?.abort();
  }, []);

  if (status === 'completed') return <div>Purchase complete! License: {result?.licenseId}</div>;
  if (status === 'failed') return <div>Error occurred <button onClick={() => setStatus('idle')}>Retry</button></div>;

  return <button onClick={handlePurchase} disabled={status === 'processing'}>Purchase</button>;
}

pollCheckoutResult() uses exponential backoff (1s initial, doubling to 15s cap, 60 max attempts ~15 min total). Pass an AbortSignal to cancel polling. Throws LicensingError on timeout or cancellation.

Standalone Hook (without Provider)

import { useLicensing } from '@sidub-inc/licensing-client';

function Component() {
  const client = useLicensing({
    licenseServiceUri: 'https://api.monaiq.com/licensing',
    encodedCredential: 'SIDUB_LIC_...'
  });

  // Use client directly
}

Signature Validation

When serviceKeyId and serviceKeyPublicMember are configured (directly or via encoded credential), the library validates authorization signatures using ECDSA with P-256 (secp256r1) and SHA-256.

Validation flow:

  1. Server signs LicenseAuthorization with private EC key (P-256)
  2. Client receives authorization with signature field
  3. Client verifies signature using configured public key
  4. Invalid signatures throw CryptoError

Disable validation (not recommended):

const client = new LicensingClient({
  licenseServiceUri: '...',
  encodedCredential: '...',
  validateSignatures: false
});

Credential Encoding

import { encodeCredential, decodeCredential, LicensingCredential } from '@sidub-inc/licensing-client';

const credential: LicensingCredential = {
  licenseId: 'uuid',
  serviceKeyId: 'uuid',
  serviceKeyPublicMember: 'base64-key',
  apiAccessKey: 'api-key'
};

const encoded = encodeCredential(credential);  // "SIDUB_LIC_eyJ..."
const decoded = decodeCredential(encoded);     // LicensingCredential

Error Handling

| Error Type | Description | |-----------|-------------| | LicensingError | Base error for API, network, and timeout errors | | LicensingConfigurationException | Missing required configuration fields (extends LicensingError) | | CryptoError | Signature validation failures (KEY_IMPORT_FAILED, VERIFICATION_FAILED, INVALID_SIGNATURE) |

import { LicensingError, LicensingConfigurationException, CryptoError } from '@sidub-inc/licensing-client';

try {
  const authorization = await client.getAuthorization();
} catch (error) {
  if (error instanceof CryptoError) {
    // Signature validation failed
    console.error('Signature invalid:', error.code);
  } else if (error instanceof LicensingConfigurationException) {
    // Missing configuration
    console.error('Config error:', error.message);
  } else if (error instanceof LicensingError) {
    // API or network error
    console.error('Licensing error:', error.message, error.statusCode);
  }
}

TypeScript Types

import type {
  LicensingConfig,
  LicenseAuthorization,
  LicensingCredential,
  ILicenseFeature,
  LicenseClassificationType,
  ILicenseAssertion
} from '@sidub-inc/licensing-client';

Migrating from v1.0.x

See MIGRATION.md for detailed upgrade instructions. Key changes:

  • BillingIntervalUnit enum members renamed: DaysDay, MonthsMonth, YearsYear (+ new Hour, Week)
  • performOperation() payload structure changed (nested LicenseOperation)
  • Configuration validation now throws LicensingConfigurationException instead of LicensingError
  • Consumption requests use dub-apiKey header (was dub-issuerKey)

License

Proprietary licenses available at https://sidub.ca/ © Sidub Inc.