Repository navigation
Expand file tree
/
Copy pathserver.ts
More file actions
107 lines (96 loc) · 3.36 KB
/
Copy pathserver.ts
File metadata and controls
107 lines (96 loc) · 3.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// Load environment variables from .env only in local/dev, not on Deno Deploy
if (!Deno.env.get("DENO_DEPLOYMENT_ID")) {
await import("https://deno.land/std@0.224.0/dotenv/load.ts");
}
import { serveDir, serveFile } from "https://deno.land/std@0.224.0/http/file_server.ts";
const requestedPort = Number(Deno.env.get("PORT"));
const port = Number.isFinite(requestedPort) ? requestedPort : 8080;
const serverPortFile = Deno.env.get("SERVER_PORT_FILE") ?? "logs/server.port";
const serverPidFile = Deno.env.get("SERVER_PID_FILE") ?? "logs/server.pid";
const kv = await Deno.openKv();
async function handler(request: Request): Promise<Response> {
const url = new URL(request.url);
const pathname = url.pathname;
console.log(`REQ ${request.method} ${pathname}`);
// Handle CORS preflight globally to avoid 405 from static server
if (request.method === "OPTIONS") {
return new Response(null, {
status: 204,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
},
});
}
// API routes
if (pathname === "/issue-scraper") {
const { handleIssueScraper } = await import("./functions/issue-scraper.ts");
return await handleIssueScraper(request);
}
if (pathname === "/referral-manager") {
const { handleReferralManager } = await import("./functions/referral-manager.ts");
return await handleReferralManager(request, kv);
}
// Static file serving from ./static with SPA fallback
const res = await serveDir(request, {
fsRoot: "static",
// urlRoot intentionally omitted to serve from site root
quiet: true,
});
// If file not found, fallback to index.html for SPA routes (GET only)
if (res.status === 404 && request.method === "GET") {
// Only fallback when expecting HTML
const accept = request.headers.get("accept") || "";
if (accept.includes("text/html")) {
return await serveFile(request, "static/index.html");
}
}
return res;
}
async function onListenCommon(hostname: string | undefined, port: number) {
console.log(`Deno server listening on http://${hostname || "localhost"}:${port}`);
try {
await Deno.mkdir("logs", { recursive: true });
} catch (_) {
console.warn("Failed creating logs directory:", String(_));
}
try {
await Deno.writeTextFile(serverPortFile, String(port));
} catch (err) {
console.warn("Failed writing server port file:", String(err));
}
try {
await Deno.writeTextFile(serverPidFile, String(Deno.pid));
} catch (err) {
console.warn("Failed writing server pid file:", String(err));
}
}
function start(portToUse: number) {
try {
Deno.serve(
{
port: portToUse,
onListen: ({ hostname, port }) => onListenCommon(hostname, port),
},
handler
);
} catch (err) {
// If the port is in use, retry with an ephemeral port
const msg = (err && (err as Error).message) || "";
const isAddrInUse = msg.includes("AddrInUse") || msg.includes("address already in use");
if (isAddrInUse && portToUse !== 0) {
console.warn(`Port ${portToUse} in use; retrying on an open port...`);
Deno.serve(
{
port: 0,
onListen: ({ hostname, port }) => onListenCommon(hostname, port),
},
handler
);
return;
}
throw err;
}
}
start(port);