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

@softlock/sdk

v0.0.11

Published

Official Softlock SDK for access key validation and management

Readme

Softlock SDK

Official SDK for integrating Softlock access key validation into your applications.

Installation

npm install @softlock/sdk
# or
yarn add @softlock/sdk
# or
pnpm add @softlock/sdk

Architecture

The SDK is split into client and server modules to ensure compatibility with Next.js App Router and React Server Components:

  • @softlock/sdk/client - React hooks and components (client-side only)
  • @softlock/sdk/server - Validation utilities and middleware (server-safe)
  • @softlock/sdk - Core client and server-safe utilities

Quick Start

Server-Side Validation

// Server components, API routes, middleware
import { initSoftlock, validateKey } from "@softlock/sdk/server";

// Initialize with your tenant ID
initSoftlock({
  tenantId: "your-tenant-id", // Your Discord server ID or tenant UUID
  baseUrl: "https://your-softlock-instance.com", // Optional: custom instance
  cacheTtl: 300000, // Optional: cache TTL in ms (default: 5 minutes)
  debug: true, // Optional: enable debug logging
});

async function checkAccess(userKey: string) {
  const result = await validateKey(userKey);

  if (result.valid) {
    console.log("Access granted!", result.key);
  } else {
    console.log("Access denied:", result.error);
  }
}

Client-Side Usage

// Client components only
import { initSoftlock } from "@softlock/sdk/client";

// Initialize on client-side (typically in a provider or root component)
initSoftlock({
  tenantId: "your-tenant-id",
  baseUrl: "https://your-softlock-instance.com",
});

React Integration

Using Hooks

"use client"; // Required for client components

import { useAccessKey } from "@softlock/sdk/client";
import { useState } from "react";

function AccessChecker() {
  const { result, loading, error, validate } = useAccessKey();
  const [keyInput, setKeyInput] = useState("");

  const handleValidate = () => {
    validate(keyInput);
  };

  return (
    <div>
      <input
        value={keyInput}
        onChange={(e) => setKeyInput(e.target.value)}
        placeholder="Enter your access key"
      />
      <button onClick={handleValidate} disabled={loading}>
        {loading ? "Validating..." : "Validate"}
      </button>

      {result && <div>{result.valid ? "✅ Valid" : "❌ Invalid"}</div>}
    </div>
  );
}

Using Components

"use client"; // Required for client components

import {
  AccessKeyValidator,
  AccessGuard,
  AccessStatus,
} from "@softlock/sdk/client";
import { useState } from "react";

function App() {
  const [userKey, setUserKey] = useState("");

  return (
    <div>
      {/* Built-in validator component */}
      <AccessKeyValidator
        placeholder="Enter your access key..."
        onValidation={(result) => {
          if (result.valid) {
            setUserKey(result.key?.key_value || "");
          }
        }}
        autoValidate={true}
      />

      {/* Protected content */}
      <AccessGuard
        keyValue={userKey}
        fallback={<div>You need a valid access key to see this content.</div>}
      >
        <h1>Secret Content!</h1>
        <p>This content is only visible to users with valid access keys.</p>
      </AccessGuard>

      {/* Show access status */}
      <AccessStatus
        keyValue={userKey}
        showDetails={true}
        refreshInterval={60000} // Refresh every minute
      />
    </div>
  );
}

Middleware Protection

Express.js

import express from "express";
import { createExpressMiddleware, initSoftlock } from "@softlock/sdk/server";

const app = express();

// Initialize Softlock first
initSoftlock({ tenantId: "your-tenant-id" });

// Protect routes
const softlockAuth = createExpressMiddleware({
  extractKey: (req) => req.headers["x-api-key"], // Custom extraction
  onUnauthorized: (req, res) => {
    res.status(403).json({ error: "Access denied" });
  },
});

app.get("/protected", softlockAuth, (req, res) => {
  // Access granted! User data available in req.softlock
  res.json({
    message: "Hello!",
    user: req.softlock.user,
  });
});

Next.js API Routes

import { withSoftlockAuth } from "@softlock/sdk/server";

async function handler(req: NextApiRequest, res: NextApiResponse) {
  // This handler only runs if access key is valid
  // User data available in req.softlock
  res.json({
    message: "Protected data",
    user: req.softlock?.user,
  });
}

export default withSoftlockAuth(handler);

Next.js Middleware

Basic Protection

// middleware.ts
import { NextRequest } from "next/server";
import { createNextMiddleware } from "@softlock/sdk/server";

export async function middleware(request: NextRequest) {
  if (request.nextUrl.pathname.startsWith("/protected")) {
    return createNextMiddleware()(request);
  }
}

export const config = {
  matcher: "/protected/:path*",
};

Advanced Protection with Redirects

// middleware.ts
import { NextRequest, NextResponse } from "next/server";
import { createNextMiddleware, initSoftlock } from "@softlock/sdk/server";

initSoftlock({
  tenantId: "your-tenant-id",
  baseUrl: "https://your-softlock-instance.com",
  debug: true,
});

export async function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;

  // Skip beta-access page to avoid redirect loops
  if (pathname === "/beta-access") {
    return NextResponse.next();
  }

  // Check if user has valid access key
  const validKey = request.cookies.get("softlock_valid_key")?.value;

  // If no valid key, redirect to beta access page
  if (!validKey) {
    return NextResponse.redirect(new URL("/beta-access", request.url));
  }

  // Use Softlock middleware for additional validation
  return createNextMiddleware({
    extractKey: (req) => {
      return req.cookies.get("softlock_valid_key")?.value || "";
    },
  })(request);
}

export const config = {
  matcher: ["/dashboard/:path*", "/premium/:path*"],
};

Advanced Usage

Custom Client Configuration

// Server-side
import { SoftlockClient } from "@softlock/sdk/server";

const client = new SoftlockClient({
  tenantId: "your-tenant-id",
  baseUrl: "https://api.yourapp.com",
  apiKey: "your-api-key", // For server-side validation
  cacheTtl: 600000, // 10 minutes
  debug: false,
});

// Use client directly
const result = await client.validateKey("ak_...");

Access Management Hook

"use client";

import { useAccessKey, useUserAccess } from "@softlock/sdk/client";
import { useState, useEffect } from "react";

function AccessManager() {
  const [userKey, setUserKey] = useState("");
  const { result: keyResult } = useAccessKey(userKey, { autoValidate: true });
  const { user, loading, error, refreshAccess } = useUserAccess();

  useEffect(() => {
    if (keyResult?.valid) {
      // Store valid key
      localStorage.setItem("access_key", userKey);
    }
  }, [keyResult, userKey]);

  return (
    <div>
      <input
        value={userKey}
        onChange={(e) => setUserKey(e.target.value)}
        placeholder="Access key"
      />

      {keyResult?.valid && (
        <div>
          <h3>Access Granted</h3>
          <p>User: {user?.discord_tag}</p>
          <button onClick={refreshAccess}>Refresh Access</button>
        </div>
      )}
    </div>
  );
}

Next.js App Router Integration

Provider Setup

// app/providers.tsx
"use client";

import { initSoftlock } from "@softlock/sdk/client";
import { useEffect } from "react";

export function SoftlockProvider({ children }: { children: React.ReactNode }) {
  useEffect(() => {
    initSoftlock({
      tenantId: process.env.NEXT_PUBLIC_TENANT_ID!,
      baseUrl: process.env.NEXT_PUBLIC_SOFTLOCK_URL,
      debug: process.env.NODE_ENV === "development",
    });
  }, []);

  return <>{children}</>;
}
// app/layout.tsx
import { SoftlockProvider } from "./providers";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <SoftlockProvider>{children}</SoftlockProvider>
      </body>
    </html>
  );
}

Server Components with Validation

// app/protected/page.tsx
import { validateKey } from "@softlock/sdk/server";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";

export default async function ProtectedPage() {
  const cookieStore = cookies();
  const accessKey = cookieStore.get("access_key")?.value;

  if (!accessKey) {
    redirect("/beta-access");
  }

  const result = await validateKey(accessKey);

  if (!result.valid) {
    redirect("/beta-access");
  }

  return (
    <div>
      <h1>Protected Content</h1>
      <p>Welcome, {result.key?.discord_tag}!</p>
    </div>
  );
}

import { useUserAccess } from "@softlock/sdk";

function UserDashboard({ userId }: { userId: string }) { const { hasAccess, loading, checkAccess, revokeAccess } = useUserAccess(userId);

return (

  {hasAccess === true && (
    <div>
      <h2>Welcome! You have access.</h2>
      <button onClick={revokeAccess}>Revoke Access</button>
    </div>
  )}

  {hasAccess === false && (
    <div>
      <h2>Access Required</h2>
      <button onClick={() => checkAccess("ak_...")}>
        Check Access Key
      </button>
    </div>
  )}
</div>

); }


### Access Guard with Redirect

```tsx
import { AccessGuard } from "@softlock/sdk";

function ProtectedPage() {
  return (
    <AccessGuard
      keyValue={getUserKey()} // Your function to get user's key
      redirectUrl="/login"
      onUnauthorized={() => {
        console.log("User tried to access protected content");
        // Track analytics, show notification, etc.
      }}
      loadingComponent={<div>Verifying access...</div>}
    >
      <YourProtectedContent />
    </AccessGuard>
  );
}

Accessing Discord User Information

The SDK provides multiple ways to retrieve the Discord ID and other user information associated with an access key:

1. From Validation Result

import { validateKey } from "@softlock/sdk";

async function getUserInfo(accessKey: string) {
  const result = await validateKey(accessKey);

  if (result.valid && result.key) {
    const discordId = result.key.discord_user_id;
    const discordTag = result.key.discord_tag; // username#discriminator

    console.log("Discord User ID:", discordId);
    console.log("Discord Tag:", discordTag);

    return {
      discordId,
      discordTag,
      keyId: result.key.id,
      status: result.key.status,
    };
  }

  return null;
}

2. In React Components (Hooks)

import { useAccessKey } from "@softlock/sdk";

function UserProfile() {
  const { result } = useAccessKey();

  if (result?.valid && result.key) {
    return (
      <div>
        <h3>User Information</h3>
        <p>Discord ID: {result.key.discord_user_id}</p>
        <p>Discord Tag: {result.key.discord_tag}</p>
        <p>Key Status: {result.key.status}</p>
        <p>
          Key Created: {new Date(result.key.created_at).toLocaleDateString()}
        </p>
      </div>
    );
  }

  return <div>No valid access key found</div>;
}

3. In API Routes/Middleware

// Express.js
app.get("/user-info", softlockAuth, (req, res) => {
  const discordId = req.softlock?.user?.discordId;
  const discordTag = req.softlock?.user?.discordTag;

  res.json({
    discordId,
    discordTag,
    keyData: req.softlock?.key,
  });
});

// Next.js API Route
export default withSoftlockAuth((req, res) => {
  const userInfo = {
    discordId: req.softlock?.user?.discordId,
    discordTag: req.softlock?.user?.discordTag,
    keyId: req.softlock?.key?.id,
  };

  res.json({ user: userInfo });
});

4. In Next.js Middleware (Headers)

// middleware.ts
export async function middleware(request: NextRequest) {
  // ... validation logic ...

  if (validationResult.valid && validationResult.key) {
    const requestHeaders = new Headers(request.headers);

    // Add Discord ID to headers for downstream use
    requestHeaders.set(
      "x-discord-user-id",
      validationResult.key.discord_user_id || ""
    );
    requestHeaders.set("x-discord-tag", validationResult.key.discord_tag || "");

    return NextResponse.next({
      request: {
        headers: requestHeaders,
      },
    });
  }
}

// In your page/component, access via headers
export async function getServerSideProps({ req }) {
  const discordId = req.headers["x-discord-user-id"];
  const discordTag = req.headers["x-discord-tag"];

  return {
    props: { discordId, discordTag },
  };
}

5. Using the Client Directly

import { SoftlockClient } from "@softlock/sdk";

const client = new SoftlockClient({
  tenantId: "your-tenant-id",
});

async function lookupUser(accessKey: string) {
  try {
    const validation = await client.validateKey(accessKey);

    if (validation.valid && validation.key) {
      return {
        discordId: validation.key.discord_user_id,
        discordTag: validation.key.discord_tag,
        keyDetails: {
          id: validation.key.id,
          status: validation.key.status,
          createdAt: validation.key.created_at,
          expiresAt: validation.key.expires_at,
          lastUsed: validation.key.used_at,
        },
      };
    }
  } catch (error) {
    console.error("Failed to validate key:", error);
  }

  return null;
}

API Reference

Configuration

interface SoftlockConfig {
  tenantId: string; // Required: Your tenant/server ID
  baseUrl?: string; // Optional: Custom API base URL
  apiKey?: string; // Optional: API key for server-side
  cacheTtl?: number; // Optional: Cache TTL in ms
  debug?: boolean; // Optional: Enable debug logging
}

Validation Result

interface ValidationResult {
  valid: boolean; // Whether the key is valid
  key?: AccessKey; // Key details if valid
  error?: string; // Error message if invalid
  cached?: boolean; // Whether result came from cache
}

Access Key

interface AccessKey {
  id: string; // Unique key ID
  key_value: string; // The actual key value
  status: "active" | "revoked" | "expired";
  discord_user_id?: string; // Associated Discord user
  discord_tag?: string; // Discord username#discriminator
  created_at: string; // ISO timestamp
  expires_at?: string; // ISO timestamp (if expires)
  used_at?: string; // ISO timestamp (when first used)
}

Error Handling

The SDK provides specific error types for better error handling:

import {
  SoftlockError,
  ValidationError,
  NetworkError,
  ConfigurationError,
} from "@softlock/sdk";

try {
  const result = await validateKey("invalid-key");
} catch (error) {
  if (error instanceof ValidationError) {
    console.log("Key validation failed:", error.message);
  } else if (error instanceof NetworkError) {
    console.log("Network issue:", error.message);
  } else if (error instanceof ConfigurationError) {
    console.log("Configuration issue:", error.message);
  }
}

Best Practices

  1. Initialize Early: Call initSoftlock() at the top level of your app
  2. Cache Wisely: Use appropriate cache TTL based on your security needs
  3. Handle Errors: Always handle validation errors gracefully
  4. Secure Keys: Never expose API keys in client-side code
  5. Monitor Usage: Use debug mode during development

TypeScript Support

The SDK is written in TypeScript and provides full type safety:

import type {
  ValidationResult,
  AccessKey,
  SoftlockConfig,
} from "@softlock/sdk";

// All types are exported and ready to use
const config: SoftlockConfig = {
  tenantId: "your-tenant-id",
};

Contributing

See the main repository for contribution guidelines.

License

MIT