-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathapp.js
More file actions
191 lines (158 loc) · 6.6 KB
/
Copy pathapp.js
File metadata and controls
191 lines (158 loc) · 6.6 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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
const express = require("express");
const cors = require("cors");
const path = require("path");
const app = express();
const PORT = process.env.PORT || 3000;
const TARGET_BASE = "https://zamboni.gg";
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, "public")));
app.disable("x-powered-by");
app.get("/llms.txt", (req, res) => {
res.type("text/plain");
const llmsTxtContent = `# Zamboni
> Community-run private servers for classic NHL games on RPCS3.
## Core Pages
- [/](https://zamboni.gg): The main landing page for Zamboni private servers, including setup requirements and project information.
- [/statistics](https://zamboni.gg/statistics): Real-time server diagnostics, player counts, and network matchmaking statistics across all supported NHL iterations.
## Supported Titles
- **NHL 10**
- **NHL 11**
- **NHL 12**
- **NHL 13**
- **NHL 14**
- **NHL 15**
- **NHL Legacy Edition**`;
res.send(llmsTxtContent);
});
app.get("/robots.txt", (req, res) => {
res.type("text/plain");
res.send("User-agent: *\nAllow: /\nSitemap: https://zamboni.gg");
});
app.get("/sitemap.xml", (req, res) => {
res.type("application/xml");
const today = new Date().toISOString().split("T")[0];
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://sitemaps.org">
<url>
<loc>https://zamboni.gg</loc>
<lastmod>${today}</lastmod>
<changefreq>daily</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://zamboni.ggstatistics</loc>
<lastmod>${today}</lastmod>
<changefreq>hourly</changefreq>
<priority>0.8</priority>
</url>
</urlset>`;
res.send(sitemap);
});
app.get("/", (req, res) => {
res.sendFile(path.join(__dirname, "public", "index.html"));
});
app.get("/statistics", (req, res) => {
res.sendFile(path.join(__dirname, "public", "statistics-page.html"));
});
// LEGACY: THIS WILL GET REMOVED LATER BUT KEPT NOW TO WORK WITH LEGACY ROUTELINKS
app.get("/faq", (req, res) => {
res.sendFile(path.join(__dirname, "public", "index.html"));
});
// Temp server side status proxy fetches http game server endpoints and serves them ovr https to avoid mixed content browser errors
const TEMP_STATUS_TARGETS = {
nhl10: "https://zamboni.gg/nhl10/status",
nhl11: "http://zamboni.gg:8081/nhl11/status",
nhl12: "http://zamboni.gg:8085/nhl12/status",
nhl13: "http://zamboni.gg:8086/nhl13/status",
nhl14: "http://zamboni.gg:8082/nhl14/status",
nhl15: "http://zamboni.gg:8087/nhl15/status",
nhllegacy: "http://zamboni.gg:8083/nhllegacy/status"
};
app.get("/temp/status/:version", async (req, res) => {
const target = TEMP_STATUS_TARGETS[req.params.version];
if (!target) {
return res.status(404).json({ error: "Unknown version" });
}
const start = Date.now();
console.log(`-> [temp/status] ${req.params.version} → ${target}`);
try {
const response = await fetch(target, {
headers: { Accept: "application/json" },
redirect: "follow",
});
if (!response.ok) {
throw new Error(`Upstream HTTP ${response.status}`);
}
const data = await response.json();
console.log(`<- [temp/status] ${req.params.version} OK (${Date.now() - start}ms)`);
res.setHeader("Cache-Control", "no-store");
res.json(data);
} catch (err) {
console.error(`X [temp/status] ${req.params.version} failed (${Date.now() - start}ms):`, err.message);
res.status(502).json({ error: "Upstream unreachable", message: err.message });
}
});
// Proxy due to cors on development server. USE IF RUNNING LOCAL OR REMOTE FROM SERVBER
const isLocal = process.argv.includes("--local");
if (isLocal) {
const proxyPaths = [
/^\/nhl10\/.*/,
/^\/nhl11\/.*/,
/^\/nhl12\/.*/,
/^\/nhl13\/.*/,
/^\/nhl14\/.*/,
/^\/nhl15\/.*/,
/^\/nhllegacy\/.*/,
/^\/hut12\/.*/,
/^\/api\/.*/,
/^\/status\/.*/
];
app.use(proxyPaths, async (req, res) => {
const targetUrl = TARGET_BASE + req.originalUrl;
const start = Date.now();
console.log(`-> Proxying [${req.method}] ${targetUrl}`);
try {
const response = await fetch(targetUrl, {
method: req.method,
headers: {
...req.headers,
host: undefined
},
body: ["GET", "HEAD"].includes(req.method)
? undefined
: JSON.stringify(req.body)
});
const contentType = response.headers.get("content-type") || "";
res.status(response.status);
response.headers.forEach((value, key) => {
if (key.toLowerCase() !== "transfer-encoding") {
res.setHeader(key, value);
}
});
if (contentType.includes("application/json")) {
const data = await response.json();
res.json(data);
} else if (contentType.includes("text")) {
const text = await response.text();
res.send(text);
} else {
const buffer = Buffer.from(await response.arrayBuffer());
res.send(buffer);
}
console.log(`<- ${response.status} (${Date.now() - start}ms)`);
} catch (err) {
console.error(`X - Proxy failed (${Date.now() - start}ms):`, err.message);
res.status(500).json({
error: "Proxy failed",
message: err.message
});
}
});
}
app.listen(PORT, () => {
console.log(
`Server running on http://localhost:${PORT} (${isLocal ? "PROXY MODE" : "PRODUCTION MODE"})`
);
});