@uservibesos/web-component
v1.0.1
Published
Feature request widget as a Web Component
Maintainers
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-componentThen 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_hereStep 2: Add to .gitignore:
.env.localStep 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:
- Your backend securely holds your Secret Key (SK)
- Widget calls YOUR backend proxy endpoint (not UserVibeOS directly)
- Your backend exchanges SK for a short-lived JWT token (5 min expiry)
- Backend forwards request to UserVibeOS with JWT
- 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
- Go to your UserVibeOS Dashboard → Projects → [Your Project]
- Navigate to "Project Settings"
- Enable "JWT Authentication (Tier 2)"
- Select or create a Secret Key (SK)
- 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 modeproxy-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.comProduction 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:
- 📘 Next.js App Router Proxy Guide
- 📗 React + Express Proxy Guide
- 📕 Standalone Express Proxy Guide
- 📙 Migration Guide: Upgrading to Two-Tier Security
API Key Security Best Practices 🔒
Required Security Practices (Both Tiers)
✅ MUST DO:
- Store API keys in
.env.local(never in code) - Add
.env.localto.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
