mirror of
https://github.com/LukeHagar/relay.git
synced 2025-12-06 04:21:14 +00:00
Refactor SvelteKit app to unified webhook relay with WebSocket support
Co-authored-by: lukeslakemail <lukeslakemail@gmail.com>
This commit is contained in:
@@ -7,10 +7,12 @@ GITHUB_ID="your-github-oauth-app-id"
|
||||
GITHUB_SECRET="your-github-oauth-app-secret"
|
||||
REDIRECT_URL="http://localhost:3000"
|
||||
|
||||
# Backend Servers
|
||||
INGEST_SERVER_URL="http://localhost:4000"
|
||||
RELAY_SERVER_URL="http://localhost:4200"
|
||||
WEBSOCKET_URL="ws://localhost:4200/api/relay"
|
||||
# Webhook Configuration
|
||||
WEBHOOK_DOMAIN="yourdomain.com"
|
||||
WEBHOOK_SUBDOMAIN_PATTERN="*.yourdomain.com"
|
||||
|
||||
# Optional: Custom webhook path prefix
|
||||
# WEBHOOK_PATH_PREFIX="/webhook"
|
||||
|
||||
# Optional: Custom domain for production
|
||||
# DOMAIN="yourdomain.com"
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
# Webhook Relay - SvelteKit Integration
|
||||
# Webhook Relay - SvelteKit Fullstack Application
|
||||
|
||||
A fullstack SvelteKit application that provides a modern web interface for the webhook relay system. This application integrates with the existing Bun-based webhook relay backend to provide real-time webhook management, forwarding, and monitoring.
|
||||
A complete webhook relay system built entirely with SvelteKit, providing webhook ingestion, relay functionality, and a modern web interface all in one application.
|
||||
|
||||
## Features
|
||||
|
||||
### 🚀 Real-time Webhook Management
|
||||
- **Live Dashboard**: Real-time webhook event monitoring with WebSocket connections
|
||||
### 🚀 Complete Webhook Management
|
||||
- **Webhook Ingestion**: Receive webhooks via subdomain routing
|
||||
- **Real-time Dashboard**: Live webhook event monitoring with WebSocket connections
|
||||
- **Event History**: Complete webhook event history with search and filtering
|
||||
- **Event Details**: View full request bodies, headers, and metadata
|
||||
- **Relay System**: Forward webhooks to multiple configured targets
|
||||
|
||||
### 🔄 Webhook Relay System
|
||||
- **Multiple Targets**: Configure multiple forwarding destinations
|
||||
- **Target Management**: Add, edit, delete, and toggle relay targets
|
||||
- **Automatic Forwarding**: Incoming webhooks are automatically forwarded to all active targets
|
||||
- **Relay Analytics**: Track success rates and response times
|
||||
|
||||
### 🔐 Authentication & Security
|
||||
- **GitHub OAuth**: Secure authentication using GitHub
|
||||
@@ -26,38 +28,39 @@ A fullstack SvelteKit application that provides a modern web interface for the w
|
||||
|
||||
## Architecture
|
||||
|
||||
### Frontend (SvelteKit)
|
||||
- **Port**: 3000
|
||||
- **Framework**: SvelteKit with TypeScript
|
||||
- **Styling**: Tailwind CSS
|
||||
- **Icons**: Lucide Svelte
|
||||
- **State Management**: Svelte stores for real-time updates
|
||||
|
||||
### Backend Integration
|
||||
- **Ingest Server**: Receives webhooks via subdomains (port 4000)
|
||||
- **Relay Server**: Handles authentication and WebSocket connections (port 4200)
|
||||
### Single SvelteKit Application
|
||||
- **Webhook Ingestion**: `/webhook/[...path]` - Handles all incoming webhooks
|
||||
- **WebSocket Server**: `/api/ws` - Real-time updates for authenticated users
|
||||
- **Web Interface**: Modern dashboard and management UI
|
||||
- **Database**: PostgreSQL with Prisma ORM
|
||||
|
||||
### Key Components
|
||||
|
||||
1. **WebSocket Client** (`src/lib/websocket.ts`)
|
||||
- Manages real-time connections to the relay server
|
||||
- Handles automatic reconnection
|
||||
- Provides reactive stores for webhook events
|
||||
1. **Webhook Handler** (`src/routes/webhook/[...path]/+server.ts`)
|
||||
- Receives webhooks via subdomain routing
|
||||
- Stores events in database
|
||||
- Forwards to configured relay targets
|
||||
- Broadcasts real-time updates
|
||||
|
||||
2. **Authentication** (`src/lib/auth.ts`)
|
||||
2. **WebSocket Handler** (`src/routes/api/ws/+server.ts`)
|
||||
- Manages authenticated WebSocket connections
|
||||
- Provides real-time webhook event updates
|
||||
- Handles connection lifecycle
|
||||
|
||||
3. **Relay Service** (`src/lib/relay.ts`)
|
||||
- Forwards webhooks to configured targets
|
||||
- Tracks relay success/failure
|
||||
- Handles timeouts and errors
|
||||
|
||||
4. **Authentication** (`src/lib/auth.ts`)
|
||||
- GitHub OAuth integration
|
||||
- Session management with Auth.js
|
||||
- Session management
|
||||
- User data persistence
|
||||
|
||||
3. **Database Layer** (`src/lib/db.ts`)
|
||||
- Prisma client configuration
|
||||
- Connection pooling and optimization
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
- Node.js 18+ or Bun
|
||||
- Node.js 18+
|
||||
- PostgreSQL database
|
||||
- GitHub OAuth application
|
||||
|
||||
@@ -81,6 +84,7 @@ A fullstack SvelteKit application that provides a modern web interface for the w
|
||||
GITHUB_ID="your-github-oauth-app-id"
|
||||
GITHUB_SECRET="your-github-oauth-app-secret"
|
||||
REDIRECT_URL="http://localhost:3000"
|
||||
WEBHOOK_DOMAIN="yourdomain.com"
|
||||
```
|
||||
|
||||
3. **Set up the database**:
|
||||
@@ -94,15 +98,6 @@ A fullstack SvelteKit application that provides a modern web interface for the w
|
||||
npm run dev
|
||||
```
|
||||
|
||||
5. **Start the backend servers** (in separate terminals):
|
||||
```bash
|
||||
# Terminal 1 - Ingest Server
|
||||
bun run server/index.ts
|
||||
|
||||
# Terminal 2 - Relay Server
|
||||
bun run server/relay.ts
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
1. **Access the application**: http://localhost:3000
|
||||
@@ -111,10 +106,36 @@ A fullstack SvelteKit application that provides a modern web interface for the w
|
||||
4. **Add relay targets**: Configure where webhooks should be forwarded
|
||||
5. **Monitor webhooks**: View real-time webhook events on the dashboard
|
||||
|
||||
## Webhook Flow
|
||||
|
||||
### 1. Webhook Reception
|
||||
```
|
||||
External Service → https://{subdomain}.yourdomain.com/webhook → SvelteKit Handler
|
||||
```
|
||||
|
||||
### 2. Processing Pipeline
|
||||
1. **Subdomain Extraction**: Extract user subdomain from hostname
|
||||
2. **User Validation**: Verify subdomain belongs to authenticated user
|
||||
3. **Event Storage**: Store webhook data in database
|
||||
4. **Target Relay**: Forward to all active relay targets
|
||||
5. **Real-time Broadcast**: Send updates to connected WebSocket clients
|
||||
|
||||
### 3. Real-time Updates
|
||||
```
|
||||
Webhook Event → Database → WebSocket Broadcast → SvelteKit UI Updates
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Webhook Statistics
|
||||
### Webhook Ingestion
|
||||
- `ANY /webhook/[...path]` - Receive webhooks (subdomain-based routing)
|
||||
|
||||
### WebSocket
|
||||
- `GET /api/ws` - WebSocket connection for real-time updates
|
||||
|
||||
### Webhook Management
|
||||
- `GET /api/webhooks/stats` - Get webhook statistics for dashboard
|
||||
- `POST /api/test-webhook` - Create test webhook events
|
||||
|
||||
### Relay Targets
|
||||
- `POST /api/targets` - Create a new relay target
|
||||
@@ -122,13 +143,8 @@ A fullstack SvelteKit application that provides a modern web interface for the w
|
||||
- `DELETE /api/targets/[id]` - Delete a relay target
|
||||
- `PUT /api/targets/[id]/toggle` - Toggle target active status
|
||||
|
||||
## Webhook Flow
|
||||
|
||||
1. **Webhook Reception**: Incoming webhooks are received at `{subdomain}.yourdomain.com`
|
||||
2. **Event Storage**: Webhook data is stored in the database
|
||||
3. **Real-time Updates**: WebSocket clients receive immediate updates
|
||||
4. **Target Forwarding**: Webhooks are forwarded to all active relay targets
|
||||
5. **UI Updates**: SvelteKit interface updates in real-time
|
||||
### User Settings
|
||||
- `PUT /api/settings/subdomain` - Update user subdomain
|
||||
|
||||
## Development
|
||||
|
||||
@@ -136,73 +152,120 @@ A fullstack SvelteKit application that provides a modern web interface for the w
|
||||
```
|
||||
sveltekit-app/
|
||||
├── src/
|
||||
│ ├── lib/ # Shared utilities and configurations
|
||||
│ ├── routes/ # SvelteKit routes and API endpoints
|
||||
│ └── app.css # Global styles
|
||||
├── prisma/ # Database schema and migrations
|
||||
├── static/ # Static assets
|
||||
└── package.json # Dependencies and scripts
|
||||
│ ├── lib/ # Shared utilities and services
|
||||
│ │ ├── auth.ts # Authentication configuration
|
||||
│ │ ├── db.ts # Database connection
|
||||
│ │ ├── relay.ts # Webhook relay service
|
||||
│ │ └── websocket.ts # WebSocket client utilities
|
||||
│ ├── routes/
|
||||
│ │ ├── webhook/ # Webhook ingestion endpoints
|
||||
│ │ ├── api/ # API endpoints
|
||||
│ │ ├── +page.svelte # Dashboard
|
||||
│ │ ├── webhooks/ # Webhook management
|
||||
│ │ ├── targets/ # Relay target management
|
||||
│ │ └── settings/ # User settings
|
||||
│ └── app.css # Global styles
|
||||
├── prisma/ # Database schema and migrations
|
||||
└── package.json # Dependencies and scripts
|
||||
```
|
||||
|
||||
### Key Technologies
|
||||
- **SvelteKit**: Fullstack framework for the web interface
|
||||
- **SvelteKit**: Fullstack framework for web interface and API
|
||||
- **TypeScript**: Type-safe development
|
||||
- **Tailwind CSS**: Utility-first styling
|
||||
- **Prisma**: Database ORM and migrations
|
||||
- **Auth.js**: Authentication and session management
|
||||
- **WebSocket**: Real-time communication
|
||||
|
||||
### Customization
|
||||
## Production Deployment
|
||||
|
||||
#### Adding New Webhook Providers
|
||||
1. Extend the webhook event schema in `prisma/schema.prisma`
|
||||
2. Add provider-specific parsing in the ingest server
|
||||
3. Update the UI components to display new fields
|
||||
### 1. Build the Application
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
#### Custom Relay Logic
|
||||
1. Modify the relay server to add custom forwarding logic
|
||||
2. Add conditional forwarding based on webhook content
|
||||
3. Implement retry mechanisms and error handling
|
||||
|
||||
#### UI Enhancements
|
||||
1. Add new dashboard widgets for specific metrics
|
||||
2. Create custom event visualizations
|
||||
3. Implement advanced filtering and search
|
||||
|
||||
## Deployment
|
||||
|
||||
### Production Setup
|
||||
1. **Build the application**:
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
2. **Set up production environment**:
|
||||
- Configure production database
|
||||
- Set up proper domain and SSL certificates
|
||||
- Configure reverse proxy for subdomain routing
|
||||
|
||||
3. **Deploy with adapter**:
|
||||
```bash
|
||||
npm run preview
|
||||
```
|
||||
|
||||
### Environment Variables for Production
|
||||
### 2. Environment Configuration
|
||||
```env
|
||||
DATABASE_URL="postgresql://..."
|
||||
AUTH_SECRET="production-secret"
|
||||
GITHUB_ID="production-github-id"
|
||||
GITHUB_SECRET="production-github-secret"
|
||||
REDIRECT_URL="https://yourdomain.com"
|
||||
WEBHOOK_DOMAIN="yourdomain.com"
|
||||
```
|
||||
|
||||
## Contributing
|
||||
### 3. Domain Configuration
|
||||
- Set up wildcard DNS records (`*.yourdomain.com`)
|
||||
- Configure reverse proxy (nginx/traefik) for subdomain routing
|
||||
- Set up SSL certificates for secure webhook reception
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Make your changes
|
||||
4. Add tests if applicable
|
||||
5. Submit a pull request
|
||||
### 4. Start Production Server
|
||||
```bash
|
||||
npm run preview
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Test Webhook Creation
|
||||
```bash
|
||||
curl -X POST http://localhost:3000/api/test-webhook \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"subdomain": "your-subdomain", "method": "POST", "path": "/test", "body": {"test": "data"}}'
|
||||
```
|
||||
|
||||
### Send Webhook to Your Endpoint
|
||||
```bash
|
||||
curl -X POST https://your-subdomain.yourdomain.com/webhook/test \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"event": "test", "data": "example"}'
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### 1. Authentication & Authorization
|
||||
- GitHub OAuth for user authentication
|
||||
- Session-based authentication with secure cookies
|
||||
- User data isolation in database queries
|
||||
- CSRF protection via SvelteKit
|
||||
|
||||
### 2. Data Protection
|
||||
- Input validation on all endpoints
|
||||
- SQL injection prevention via Prisma ORM
|
||||
- XSS protection via SvelteKit's built-in sanitization
|
||||
- Secure WebSocket connections
|
||||
|
||||
### 3. Rate Limiting
|
||||
- Consider implementing rate limiting for webhook endpoints
|
||||
- WebSocket connection limits
|
||||
- Database query optimization
|
||||
|
||||
## Monitoring & Analytics
|
||||
|
||||
### 1. Real-time Metrics
|
||||
- WebSocket connection status
|
||||
- Webhook event counts
|
||||
- Relay target success rates
|
||||
- User activity tracking
|
||||
|
||||
### 2. Error Tracking
|
||||
- WebSocket connection failures
|
||||
- API endpoint errors
|
||||
- Database connection issues
|
||||
- Relay target failures
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
### 1. Advanced Features
|
||||
- **Webhook Templates**: Pre-configured webhook formats
|
||||
- **Conditional Relay**: Forward based on webhook content
|
||||
- **Retry Mechanisms**: Automatic retry for failed relays
|
||||
- **Webhook Signatures**: Security verification
|
||||
|
||||
### 2. Enterprise Features
|
||||
- **Team Management**: Multi-user organizations
|
||||
- **API Keys**: Programmatic access
|
||||
- **Audit Logs**: Complete activity tracking
|
||||
- **Advanced Analytics**: Detailed performance metrics
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^3.0.0",
|
||||
"@sveltejs/adapter-node": "^3.0.0",
|
||||
"@sveltejs/kit": "^2.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
|
||||
127
sveltekit-app/src/lib/relay.ts
Normal file
127
sveltekit-app/src/lib/relay.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import { prisma } from '$lib/db';
|
||||
|
||||
export interface RelayResult {
|
||||
success: boolean;
|
||||
target: string;
|
||||
statusCode?: number;
|
||||
error?: string;
|
||||
responseTime?: number;
|
||||
}
|
||||
|
||||
export async function relayWebhookToTargets(
|
||||
userId: string,
|
||||
webhookData: {
|
||||
method: string;
|
||||
path: string;
|
||||
query: string;
|
||||
body: any;
|
||||
headers: Record<string, string>;
|
||||
}
|
||||
): Promise<RelayResult[]> {
|
||||
try {
|
||||
// Get all active relay targets for the user
|
||||
const targets = await prisma.relayTarget.findMany({
|
||||
where: {
|
||||
userId,
|
||||
active: true
|
||||
}
|
||||
});
|
||||
|
||||
if (targets.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Forward webhook to all active targets
|
||||
const results = await Promise.allSettled(
|
||||
targets.map(async (target) => {
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
// Prepare headers for forwarding
|
||||
const forwardHeaders: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': 'WebhookRelay/1.0',
|
||||
'X-Webhook-Relay-Source': 'webhook-relay',
|
||||
'X-Webhook-Relay-Target': target.nickname || target.id,
|
||||
...webhookData.headers
|
||||
};
|
||||
|
||||
// Remove headers that shouldn't be forwarded
|
||||
delete forwardHeaders['host'];
|
||||
delete forwardHeaders['authorization'];
|
||||
delete forwardHeaders['cookie'];
|
||||
|
||||
// Forward the webhook
|
||||
const response = await fetch(target.target, {
|
||||
method: webhookData.method,
|
||||
headers: forwardHeaders,
|
||||
body: webhookData.method !== 'GET' ? JSON.stringify(webhookData.body) : undefined,
|
||||
signal: AbortSignal.timeout(30000) // 30 second timeout
|
||||
});
|
||||
|
||||
const responseTime = Date.now() - startTime;
|
||||
|
||||
return {
|
||||
success: response.ok,
|
||||
target: target.target,
|
||||
statusCode: response.status,
|
||||
responseTime
|
||||
} as RelayResult;
|
||||
|
||||
} catch (error) {
|
||||
const responseTime = Date.now() - startTime;
|
||||
|
||||
return {
|
||||
success: false,
|
||||
target: target.target,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
responseTime
|
||||
} as RelayResult;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// Process results
|
||||
return results.map((result, index) => {
|
||||
if (result.status === 'fulfilled') {
|
||||
return result.value;
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
target: targets[index]?.target || 'unknown',
|
||||
error: result.reason?.message || 'Unknown error'
|
||||
} as RelayResult;
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error relaying webhook to targets:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Optional: Store relay results for analytics
|
||||
export async function storeRelayResults(
|
||||
webhookEventId: string,
|
||||
results: RelayResult[]
|
||||
): Promise<void> {
|
||||
try {
|
||||
// You could create a new table for relay results if needed
|
||||
// For now, we'll just log them
|
||||
console.log('Relay results for webhook:', webhookEventId, results);
|
||||
|
||||
// Example of storing results (if you add a RelayResult table):
|
||||
// await prisma.relayResult.createMany({
|
||||
// data: results.map(result => ({
|
||||
// webhookEventId,
|
||||
// target: result.target,
|
||||
// success: result.success,
|
||||
// statusCode: result.statusCode,
|
||||
// error: result.error,
|
||||
// responseTime: result.responseTime
|
||||
// }))
|
||||
// });
|
||||
} catch (error) {
|
||||
console.error('Error storing relay results:', error);
|
||||
}
|
||||
}
|
||||
@@ -136,6 +136,9 @@ class WebSocketClient {
|
||||
}
|
||||
}
|
||||
|
||||
export function createWebSocketClient(url: string) {
|
||||
return new WebSocketClient(url);
|
||||
export function createWebSocketClient() {
|
||||
// Use the SvelteKit WebSocket endpoint
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const host = window.location.host;
|
||||
return new WebSocketClient(`${protocol}//${host}/api/ws`);
|
||||
}
|
||||
@@ -17,8 +17,8 @@
|
||||
|
||||
onMount(() => {
|
||||
if (data.session?.user) {
|
||||
// Connect to WebSocket for real-time updates
|
||||
wsClient = createWebSocketClient(`ws://localhost:4200/api/relay`);
|
||||
// Connect to SvelteKit WebSocket endpoint
|
||||
wsClient = createWebSocketClient();
|
||||
|
||||
wsClient.events.subscribe((newEvents) => {
|
||||
events = newEvents;
|
||||
|
||||
63
sveltekit-app/src/routes/api/test-webhook/+server.ts
Normal file
63
sveltekit-app/src/routes/api/test-webhook/+server.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { prisma } from '$lib/db';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
export const POST: RequestHandler = async ({ request, locals }) => {
|
||||
const session = await locals.getSession();
|
||||
|
||||
if (!session?.user?.id) {
|
||||
return json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const { subdomain, method = 'POST', path = '/test', body = { test: true } } = await request.json();
|
||||
|
||||
if (!subdomain) {
|
||||
return json({ error: 'Subdomain is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Verify the subdomain belongs to the user
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: session.user.id,
|
||||
subdomain: subdomain
|
||||
}
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return json({ error: 'Invalid subdomain for user' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Create a test webhook event
|
||||
const webhookEvent = {
|
||||
userId: user.id,
|
||||
method: method,
|
||||
path: path,
|
||||
query: '',
|
||||
body: JSON.stringify(body),
|
||||
headers: JSON.stringify({
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': 'Test-Webhook/1.0',
|
||||
'X-Test-Webhook': 'true'
|
||||
}),
|
||||
createdAt: new Date(),
|
||||
};
|
||||
|
||||
// Store in database
|
||||
const storedEvent = await prisma.webhookEvent.create({
|
||||
data: webhookEvent,
|
||||
});
|
||||
|
||||
return json({
|
||||
success: true,
|
||||
message: 'Test webhook created successfully',
|
||||
eventId: storedEvent.id,
|
||||
webhookUrl: `https://${subdomain}.yourdomain.com${path}`,
|
||||
timestamp: storedEvent.createdAt
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error creating test webhook:', error);
|
||||
return json({ error: 'Failed to create test webhook' }, { status: 500 });
|
||||
}
|
||||
};
|
||||
85
sveltekit-app/src/routes/api/ws/+server.ts
Normal file
85
sveltekit-app/src/routes/api/ws/+server.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { prisma } from '$lib/db';
|
||||
import { clients } from '../../webhook/[...path]/+server.js';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
export const GET: RequestHandler = async ({ request, locals }) => {
|
||||
const session = await locals.getSession();
|
||||
|
||||
if (!session?.user?.id) {
|
||||
return new Response('Unauthorized', { status: 401 });
|
||||
}
|
||||
|
||||
// Get user data
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: session.user.id },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return new Response('Unauthorized', { status: 401 });
|
||||
}
|
||||
|
||||
// Upgrade to WebSocket
|
||||
const upgrade = request.headers.get('upgrade');
|
||||
if (upgrade !== 'websocket') {
|
||||
return new Response('Expected websocket', { status: 426 });
|
||||
}
|
||||
|
||||
const { socket, response } = Deno.upgradeWebSocket(request);
|
||||
|
||||
// Add client to the map
|
||||
if (!clients.has(user.subdomain)) {
|
||||
clients.set(user.subdomain, []);
|
||||
}
|
||||
clients.get(user.subdomain)!.push(socket);
|
||||
|
||||
// Send welcome message
|
||||
socket.send(JSON.stringify({
|
||||
message: `Connected to WebSocket server as ${user.name} with subdomain ${user.subdomain}`,
|
||||
type: 'connection'
|
||||
}));
|
||||
|
||||
// Handle WebSocket events
|
||||
socket.onopen = () => {
|
||||
console.log(`WebSocket connected for user: ${user.subdomain}`);
|
||||
};
|
||||
|
||||
socket.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
console.log('Received message:', data);
|
||||
|
||||
// Echo back for testing
|
||||
socket.send(JSON.stringify({
|
||||
message: 'Message received',
|
||||
type: 'echo',
|
||||
data: data
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Error parsing WebSocket message:', error);
|
||||
}
|
||||
};
|
||||
|
||||
socket.onclose = () => {
|
||||
console.log(`WebSocket disconnected for user: ${user.subdomain}`);
|
||||
|
||||
// Remove client from the map
|
||||
const userClients = clients.get(user.subdomain);
|
||||
if (userClients) {
|
||||
const index = userClients.indexOf(socket);
|
||||
if (index > -1) {
|
||||
userClients.splice(index, 1);
|
||||
}
|
||||
|
||||
// Remove empty arrays
|
||||
if (userClients.length === 0) {
|
||||
clients.delete(user.subdomain);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
socket.onerror = (error) => {
|
||||
console.error(`WebSocket error for user ${user.subdomain}:`, error);
|
||||
};
|
||||
|
||||
return response;
|
||||
};
|
||||
152
sveltekit-app/src/routes/webhook/[...path]/+server.ts
Normal file
152
sveltekit-app/src/routes/webhook/[...path]/+server.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { prisma } from '$lib/db';
|
||||
import { relayWebhookToTargets, storeRelayResults } from '$lib/relay';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
// Store connected WebSocket clients for real-time updates
|
||||
const clients: Map<string, any[]> = new Map();
|
||||
|
||||
export const GET: RequestHandler = async ({ request, params, url }) => {
|
||||
return handleWebhook(request, params, url);
|
||||
};
|
||||
|
||||
export const POST: RequestHandler = async ({ request, params, url }) => {
|
||||
return handleWebhook(request, params, url);
|
||||
};
|
||||
|
||||
export const PUT: RequestHandler = async ({ request, params, url }) => {
|
||||
return handleWebhook(request, params, url);
|
||||
};
|
||||
|
||||
export const DELETE: RequestHandler = async ({ request, params, url }) => {
|
||||
return handleWebhook(request, params, url);
|
||||
};
|
||||
|
||||
export const PATCH: RequestHandler = async ({ request, params, url }) => {
|
||||
return handleWebhook(request, params, url);
|
||||
};
|
||||
|
||||
async function handleWebhook(request: Request, params: any, url: URL) {
|
||||
try {
|
||||
// Extract subdomain from hostname
|
||||
const hostname = request.headers.get('host') || '';
|
||||
const urlParts = hostname.split('.');
|
||||
let subdomain = '';
|
||||
|
||||
if (urlParts.length > 1) {
|
||||
subdomain = urlParts[0];
|
||||
}
|
||||
|
||||
if (!subdomain) {
|
||||
return json({ error: 'Missing Subdomain' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Find user by subdomain
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { subdomain },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return json({ error: 'Invalid Subdomain' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Get request body
|
||||
let body: any = null;
|
||||
const contentType = request.headers.get('content-type');
|
||||
|
||||
if (contentType?.includes('application/json')) {
|
||||
try {
|
||||
body = await request.json();
|
||||
} catch {
|
||||
body = null;
|
||||
}
|
||||
} else if (contentType?.includes('application/x-www-form-urlencoded')) {
|
||||
const formData = await request.formData();
|
||||
body = Object.fromEntries(formData);
|
||||
} else {
|
||||
// Try to get raw body as text
|
||||
try {
|
||||
body = await request.text();
|
||||
} catch {
|
||||
body = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Get headers (excluding sensitive ones)
|
||||
const headers: Record<string, string> = {};
|
||||
for (const [key, value] of request.headers.entries()) {
|
||||
if (!['authorization', 'cookie', 'x-forwarded-for'].includes(key.toLowerCase())) {
|
||||
headers[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Build webhook event
|
||||
const webhookEvent = {
|
||||
userId: user.id,
|
||||
method: request.method,
|
||||
path: url.pathname,
|
||||
query: url.search,
|
||||
body: JSON.stringify(body),
|
||||
headers: JSON.stringify(headers),
|
||||
createdAt: new Date(),
|
||||
};
|
||||
|
||||
// Store in database
|
||||
const storedEvent = await prisma.webhookEvent.create({
|
||||
data: webhookEvent,
|
||||
});
|
||||
|
||||
// Relay to configured targets (async, don't wait for completion)
|
||||
const relayResults = await relayWebhookToTargets(user.id, {
|
||||
method: webhookData.method,
|
||||
path: webhookData.path,
|
||||
query: webhookData.query,
|
||||
body: body,
|
||||
headers: headers
|
||||
});
|
||||
|
||||
// Store relay results for analytics
|
||||
storeRelayResults(storedEvent.id, relayResults);
|
||||
|
||||
// Broadcast to WebSocket clients
|
||||
let messageSent = false;
|
||||
if (clients.has(subdomain)) {
|
||||
try {
|
||||
const userClients = clients.get(subdomain) || [];
|
||||
userClients.forEach((client) => {
|
||||
if (client.readyState === 1) { // WebSocket.OPEN
|
||||
client.send(JSON.stringify({
|
||||
...storedEvent,
|
||||
user: {
|
||||
name: user.name,
|
||||
subdomain: user.subdomain
|
||||
},
|
||||
relayResults: relayResults
|
||||
}));
|
||||
}
|
||||
});
|
||||
messageSent = true;
|
||||
} catch (error) {
|
||||
console.error('Error broadcasting to WebSocket clients:', error);
|
||||
messageSent = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Return success response
|
||||
return json({
|
||||
success: true,
|
||||
logged: true,
|
||||
forwarded: messageSent,
|
||||
subdomain,
|
||||
eventId: storedEvent.id,
|
||||
timestamp: storedEvent.createdAt
|
||||
}, { status: 200 });
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error handling webhook:', error);
|
||||
return json({ error: 'Internal Server Error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// Export the clients map for WebSocket handler
|
||||
export { clients };
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
onMount(() => {
|
||||
if (data.session?.user) {
|
||||
wsClient = createWebSocketClient(`ws://localhost:4200/api/relay`);
|
||||
wsClient = createWebSocketClient();
|
||||
|
||||
wsClient.events.subscribe((newEvents) => {
|
||||
// Merge new events with existing ones
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import adapter from '@sveltejs/adapter-auto';
|
||||
import adapter from '@sveltejs/adapter-node';
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
@@ -11,7 +11,12 @@ const config = {
|
||||
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
|
||||
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
|
||||
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
|
||||
adapter: adapter()
|
||||
adapter: adapter(),
|
||||
|
||||
// Enable WebSocket support
|
||||
alias: {
|
||||
$lib: './src/lib'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user