alf: the atproto Latency Fabric
alf.fly.dev/
1// ABOUTME: Minimal static file server for the ALF demo. All OAuth and API
2// logic runs in the browser. This server only serves static files and
3// exposes a single /api/config endpoint.
4
5import * as dotenv from 'dotenv';
6dotenv.config();
7
8import express from 'express';
9import path from 'path';
10
11const ALF_URL = process.env.ALF_URL || 'http://localhost:1986';
12const SERVICE_URL = process.env.SERVICE_URL || 'http://localhost:1756';
13const PORT = parseInt(process.env.PORT || '1756', 10);
14
15const app = express();
16app.use(express.static(path.join(__dirname, 'public')));
17app.get('/api/config', (_req, res) => res.json({ alfUrl: ALF_URL, serviceUrl: SERVICE_URL }));
18// OAuth client metadata for deployed (non-loopback) environments
19app.get('/oauth/client-metadata.json', (_req, res) => {
20 res.json({
21 client_id: `${SERVICE_URL}/oauth/client-metadata.json`,
22 client_name: 'ALF Demo',
23 client_uri: SERVICE_URL,
24 redirect_uris: [`${SERVICE_URL}/`],
25 scope: 'atproto',
26 grant_types: ['authorization_code', 'refresh_token'],
27 response_types: ['code'],
28 token_endpoint_auth_method: 'none',
29 dpop_bound_access_tokens: true,
30 application_type: 'web',
31 });
32});
33// SPA fallback
34app.get('*', (_req, res) => res.sendFile(path.join(__dirname, 'public', 'index.html')));
35
36app.listen(PORT, () => {
37 console.log(`ALF Demo running at http://localhost:${PORT}`);
38 console.log(`ALF backend: ${ALF_URL}`);
39});