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

@uservibesos/web-component

v1.0.1

Published

Feature request widget as a Web Component

Readme

@uservibeos/web-component

Authenticated Feature Request Widget as a Web Component (Custom Element).

⚠️ API KEY REQUIRED - This widget requires authentication. Only compatible with server-rendered applications.

Installation

Via CDN (Recommended)

Add the script tag to your HTML:

<script src="https://app.uservibeos.com/widget-assets/v1.0.0/widget.js"></script>

Via NPM

npm install @uservibeos/web-component

Then import in your JavaScript:

import '@uservibeos/web-component';

Usage

Prerequisites

  • Server-rendered application (Next.js, Remix, SvelteKit, etc.)
  • UserVibeOS API key stored in environment variables

NOT compatible with:

  • Static HTML sites
  • Client-only React/Vue apps
  • WordPress (unless using custom server integration)

Required Attributes

| Attribute | Type | Required | Description | |-----------|------|----------|-------------| | project | string | YES | Your project slug from UserVibeOS dashboard | | api-key | string | YES | Your UserVibeOS API key from environment variables |

Optional Attributes

| Attribute | Type | Default | Description | |-----------|------|---------|-------------| | theme | 'light' \| 'dark' | 'light' | Color theme | | height | string | '600px' | Widget height (CSS value) | | base-url | string | Auto-detected | Custom base URL (for development/self-hosting) |

Security Requirements 🔒

API keys are MANDATORY:

  • ✅ Widgets will NOT load without a valid API key
  • ✅ API key must come from environment variables
  • ✅ Only use in server-rendered applications
  • ❌ Never hardcode API keys in HTML or JavaScript
  • ❌ Not compatible with static sites or client-only apps

Events

The widget emits custom events you can listen to:

const widget = document.querySelector('uservibe-widget');

widget.addEventListener('request-submitted', (event) => {
  console.log('New request submitted:', event.detail);
});

widget.addEventListener('vote-added', (event) => {
  console.log('Vote added:', event.detail);
});

Examples

Next.js App Router (Recommended)

Step 1: Add your API key to .env.local:

USERVIBE_API_KEY=uv_live_your_key_here

Step 2: Add to .gitignore:

.env.local

Step 3: Use in your server component:

// app/feedback/page.tsx (Next.js App Router Server Component)
export default function FeedbackPage() {
  return (
    <div>
      <h1>Feature Requests</h1>
      <script src="https://app.uservibeos.com/widget-assets/v1.0.0/widget.js"></script>
      <uservibe-widget
        project="my-app"
        api-key={process.env.USERVIBE_API_KEY}
        theme="light"
      />
    </div>
  );
}

With Custom Styling

<uservibe-widget
  project="my-app"
  api-key={process.env.USERVIBE_API_KEY}
  theme="dark"
  height="800px"
/>

### Development/Local Testing

When testing locally with UserVibeOS running on a different port:

```tsx
// Test app on localhost:3002, UserVibeOS on localhost:3000
export default function TestPage() {
  return (
    <div>
      <script src="http://localhost:3000/widget-assets/v1.0.0/widget.js"></script>
      <uservibe-widget
        project="my-app"
        base-url="http://localhost:3000"
        api-key={process.env.USERVIBE_API_KEY}
      />
    </div>
  );
}

Don't forget to add TypeScript types:

declare global {
  namespace JSX {
    interface IntrinsicElements {
      'uservibe-widget': {
        project: string;
        'api-key': string; // REQUIRED
        theme?: 'light' | 'dark';
        height?: string;
        'base-url'?: string;
      };
    }
  }
}

🔐 Two-Tier Security Model

UserVibeOS offers two security tiers for widget authentication. Choose based on your security requirements.


TIER 1: Public Keys (PK) - Recommended for Most Users

Best for: Standard web applications with good security needs.

How it works:

  • Create a Public Key (PK) in the API Keys dashboard
  • Configure allowed origins (up to 5 domains)
  • Use server-side rendering to inject the key
  • Widget validates origin against your whitelist

Security features:

  • ✅ Origin/referrer enforcement
  • ✅ Rate limiting (5 req/min per IP/fingerprint)
  • ✅ Advanced browser fingerprinting
  • ✅ Behavioral anomaly detection
  • ✅ Real-time security event logging

Setup:

# .env.local
USERVIBE_PUBLIC_KEY=pk_live_your_key_here
// app/feedback/page.tsx (Next.js Server Component)
export default function FeedbackPage() {
  return (
    <div>
      <script src="https://app.uservibeos.com/widget-assets/v1.0.0/widget.js"></script>
      <uservibe-widget
        project="my-app"
        api-key={process.env.USERVIBE_PUBLIC_KEY}
        theme="light"
      />
    </div>
  );
}

Limitations:

  • Keys visible in browser's view source
  • Relies on origin validation (can be bypassed by determined attackers)
  • Not suitable for high-security applications

TIER 2: JWT Proxy - Maximum Security

Best for: Financial apps, healthcare, or maximum security requirements.

How it works:

  1. Your backend securely holds your Secret Key (SK)
  2. Widget calls YOUR backend proxy endpoint (not UserVibeOS directly)
  3. Your backend exchanges SK for a short-lived JWT token (5 min expiry)
  4. Backend forwards request to UserVibeOS with JWT
  5. Secret Key never leaves your server

Security features:

  • ✅ All Tier 1 features PLUS:
  • ✅ Secret Key (SK) never exposed to client
  • ✅ JWT tokens expire in 5 minutes
  • ✅ One-time use tokens (replay protection)
  • ✅ Backend request validation
  • ✅ Full cryptographic signing
  • ✅ Complete audit trail

Tier 2 Implementation Guide

Step 1: Enable JWT in Project Settings

  1. Go to your UserVibeOS Dashboard → Projects → [Your Project]
  2. Navigate to "Project Settings"
  3. Enable "JWT Authentication (Tier 2)"
  4. Select or create a Secret Key (SK)
  5. Set token expiry (default: 5 minutes)

Step 2: Create Backend Proxy

Choose your framework:

Next.js App Router:

// app/api/uservibe-proxy/route.ts
import { NextRequest, NextResponse } from 'next/server';

// Token cache (use Redis in production for multi-instance)
let tokenCache: { token: string; expiresAt: number } | null = null;

async function getValidToken(projectId: string): Promise<string> {
  // Check cache
  if (tokenCache && tokenCache.expiresAt > Date.now() + 60000) {
    return tokenCache.token;
  }

  // Exchange Secret Key for JWT token
  const response = await fetch('https://app.uservibeos.com/api/token/exchange', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      secretKey: process.env.USERVIBE_SECRET_KEY, // SK never exposed
      projectId,
      origin: process.env.NEXT_PUBLIC_SITE_URL || 'https://yourapp.com',
    }),
  });

  if (!response.ok) {
    throw new Error('Token exchange failed');
  }

  const data = await response.json();

  // Cache token
  tokenCache = {
    token: data.token,
    expiresAt: data.expiresAt,
  };

  return data.token;
}

export async function POST(req: NextRequest) {
  try {
    const body = await req.json();
    const { projectId, action, ...payload } = body;

    // Get valid JWT token
    const jwtToken = await getValidToken(projectId);

    // Forward to Convex with JWT
    const convexUrl = process.env.CONVEX_URL || 'https://your-deployment.convex.cloud';
    const response = await fetch(`${convexUrl}/api/function/${action}`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${jwtToken}`,
      },
      body: JSON.stringify(payload),
    });

    const data = await response.json();
    return NextResponse.json(data);

  } catch (error) {
    console.error('Proxy error:', error);
    return NextResponse.json(
      { error: 'Proxy request failed' },
      { status: 500 }
    );
  }
}

Express.js:

// server.js
const express = require('express');
const app = express();

let tokenCache = null;

async function getValidToken(projectId) {
  if (tokenCache && tokenCache.expiresAt > Date.now() + 60000) {
    return tokenCache.token;
  }

  const response = await fetch('https://app.uservibeos.com/api/token/exchange', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      secretKey: process.env.USERVIBE_SECRET_KEY,
      projectId,
      origin: process.env.SITE_URL || 'https://yourapp.com',
    }),
  });

  const data = await response.json();
  tokenCache = { token: data.token, expiresAt: data.expiresAt };
  return data.token;
}

app.post('/api/uservibe-proxy', async (req, res) => {
  try {
    const { projectId, action, ...payload } = req.body;
    const token = await getValidToken(projectId);

    const convexUrl = process.env.CONVEX_URL;
    const response = await fetch(`${convexUrl}/api/function/${action}`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${token}`,
      },
      body: JSON.stringify(payload),
    });

    const data = await response.json();
    res.json(data);
  } catch (error) {
    console.error('Proxy error:', error);
    res.status(500).json({ error: 'Proxy request failed' });
  }
});

app.listen(3000);

Step 3: Configure Widget for JWT Mode

<script src="https://app.uservibeos.com/widget-assets/v1.0.0/widget.js"></script>
<uservibe-widget
  project="my-app"
  jwt-mode="true"
  proxy-url="/api/uservibe-proxy"
  theme="light"
></uservibe-widget>

Key attributes for Tier 2:

  • jwt-mode="true" - Enables JWT authentication mode
  • proxy-url="/api/uservibe-proxy" - Your backend proxy endpoint

Step 4: Environment Variables

# .env.local (Backend only - NEVER commit)

# Tier 2: Secret Key (SK) for JWT signing
USERVIBE_SECRET_KEY=sk_live_your_secret_key_here

# Your Convex deployment URL
CONVEX_URL=https://your-deployment.convex.cloud

# Your site URL (for origin validation)
NEXT_PUBLIC_SITE_URL=https://yourapp.com

Production Considerations for Tier 2

Token Caching

Single-instance deployments (Vercel Hobby, single server):

  • Use in-memory caching (as shown above)

Multi-instance deployments (Vercel Pro, load-balanced):

  • Use Redis or similar distributed cache
  • Share tokens across instances
// Example with Upstash Redis
import { Redis } from '@upstash/redis';

const redis = new Redis({
  url: process.env.UPSTASH_REDIS_URL!,
  token: process.env.UPSTASH_REDIS_TOKEN!,
});

async function getValidToken(projectId: string): Promise<string> {
  const cacheKey = `uservibe:jwt:${projectId}`;

  // Check cache
  const cached = await redis.get<{ token: string; expiresAt: number }>(cacheKey);
  if (cached && cached.expiresAt > Date.now() + 60000) {
    return cached.token;
  }

  // Exchange for new token
  const token = await exchangeToken(projectId);

  // Cache with TTL (4 min, tokens expire in 5)
  await redis.setex(cacheKey, 240, token);

  return token.token;
}

Error Handling

Always implement graceful error handling:

try {
  const token = await getValidToken(projectId);
  // Use token...
} catch (error) {
  console.error('JWT token exchange failed:', error);

  // Return user-friendly error
  return NextResponse.json(
    { error: 'Authentication failed. Please try again.' },
    { status: 503 }
  );
}

Complete Implementation Guides

For detailed, framework-specific guides:


API Key Security Best Practices 🔒

Required Security Practices (Both Tiers)

MUST DO:

  • Store API keys in .env.local (never in code)
  • Add .env.local to .gitignore
  • Use server-side rendering only (Next.js Server Components, Remix loaders, etc.)
  • Use different API keys for development and production
  • Rotate API keys if compromised

NEVER DO:

  • Hardcode API keys in HTML, JavaScript, or TSX files
  • Commit API keys to version control (GitHub, GitLab, etc.)
  • Use this widget in static HTML sites
  • Use this widget in client-only React/Vue apps
  • Expose API keys in client-side environment variables (NEXT_PUBLIC_, VITE_, etc.)
  • Share API keys in public repositories or screenshots

Tier 1 vs Tier 2: When to Use

| Use Case | Recommended Tier | Reason | |----------|-----------------|---------| | Standard web app | Tier 1 (PK) | Good security, easier setup | | Financial services | Tier 2 (JWT) | Regulatory compliance | | Healthcare (HIPAA) | Tier 2 (JWT) | Maximum security required | | E-commerce | Tier 1 or 2 | Depends on sensitivity | | Internal tools | Tier 1 (PK) | Lower risk, simpler | | Public-facing SaaS | Tier 2 (JWT) | Higher attack surface |

Important Notes

⚠️ Tier 1 Limitation: Public Keys visible in browser's view source (but origin-restricted)

⚠️ Tier 2 Requirement: Requires backend infrastructure to run proxy

⚠️ Not for Static Sites: Both tiers require server-side rendering

Browser Support

  • Chrome/Edge: ✅
  • Firefox: ✅
  • Safari: ✅
  • IE11: ❌ (Custom Elements not supported)

License

MIT