@uservibesos/react-widget
v1.0.2
Published
React wrapper for the UserVibesOS feature request widget
Maintainers
Readme
@uservibeos/react-widget
Embeddable feature request widget for React and Next.js applications with two-tier security.
Installation
npm install @uservibeos/react-widgetRecommended Approach
For v1.0, we recommend using the iframe or web component versions with backend proxy for maximum security. The NPM package is best used with server-side rendering frameworks.
🔐 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
Limitations:
- Keys visible in browser's view source
- Relies on origin validation
- 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
Quick Start
Option 1: Iframe (Easiest)
Tier 1 (Public Key):
// app/feedback/page.tsx (Next.js Server Component)
export default function FeedbackPage() {
return (
<iframe
src={`https://app.uservibeos.com/widget/${projectSlug}?publicKey=${process.env.USERVIBE_PUBLIC_KEY}`}
width="100%"
height="600"
frameBorder="0"
title="Feature Requests"
/>
);
}Tier 2 (JWT Proxy):
// Requires backend proxy setup (see below)
export default function FeedbackPage() {
return (
<iframe
src="/widget-proxy/your-project-slug"
width="100%"
height="600"
frameBorder="0"
title="Feature Requests"
/>
);
}Option 2: Web Component
Tier 1 (Public Key):
// 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-project"
api-key={process.env.USERVIBE_PUBLIC_KEY}
theme="light"
/>
</div>
);
}Tier 2 (JWT Proxy):
export default function FeedbackPage() {
return (
<div>
<script src="https://app.uservibeos.com/widget-assets/v1.0.0/widget.js"></script>
<uservibe-widget
project="my-project"
jwt-mode="true"
proxy-url="/api/uservibe-proxy"
theme="light"
/>
</div>
);
}Tier 2 Implementation Guide
Step 1: Enable JWT in Project Settings
- Go to 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
Next.js App Router Example:
// 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 }
);
}
}Step 3: Environment Variables
# .env.local (Backend only - NEVER commit)
# Tier 1: Public Key (for origin-restricted access)
USERVIBE_PUBLIC_KEY=pk_live_your_public_key_here
# Tier 2: Secret Key (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
Token Caching for Tier 2
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
// 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;
}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
Security Best Practices
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 |
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
- Use different keys for dev and production
- Rotate keys if compromised
❌ NEVER DO:
- Hardcode keys in source code
- Commit keys to version control
- Expose keys in client environment variables
- Share keys in public repositories
Features
- ✅ Real-time feature request submission
- ✅ Voting system with email verification
- ✅ Comment threads
- ✅ Customizable branding (colors, fonts, logo)
- ✅ Mobile responsive
- ✅ Accessibility compliant
- ✅ Two-tier security model
- ✅ JWT proxy support
- ✅ Origin enforcement
- ✅ Rate limiting
Future NPM Package Support
Full React component NPM package with direct Convex client bundling is coming in v2.0. For now, the iframe and web component versions with backend proxy provide the best security and developer experience.
Support
Visit UserVibeOS Dashboard to manage your widget projects and API keys.
License
MIT
