Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
amethystnetwork-dev
GitHub Repository: amethystnetwork-dev/Incognito
Path: blob/main/src/analytics.js
917 views
1
/**
2
* Incognito
3
*
4
* This program is free software: you can redistribute it and/or modify
5
* it under the terms of the GNU General Public License as published by
6
* the Free Software Foundation, either version 3 of the License, or
7
* (at your option) any later version.
8
*
9
* This program is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
* GNU General Public License for more details.
13
*
14
* You should have received a copy of the GNU General Public License
15
* along with this program. If not, see <https://www.gnu.org/licenses/>.
16
*/
17
18
import crypto from "node:crypto";
19
import bodyParser from "body-parser";
20
import { METHODS } from "node:http";
21
22
function shouldRoute(req, path) {
23
return req.originalUrl.endsWith("/") && !path.endsWith("/")
24
? req.originalUrl.slice(0, req.originalUrl.length - 1)
25
: req.originalUrl
26
=== path;
27
}
28
29
function mnt(app, method, path, handler) {
30
app.use(path, (req, res, next) => {
31
if(!req.method === method.toUpperCase()) return next();
32
if(shouldRoute(req, path)) {
33
try {
34
handler(req, res, next);
35
} catch(err) {
36
next(err);
37
}
38
} else next();
39
});
40
};
41
42
const visitors = new Map();
43
let visits = 0;
44
let peak = 0;
45
46
export default function(app) {
47
for(const method of METHODS) {
48
app[method] = (path, handler) => mnt(app, method, path, handler);
49
};
50
app.use("/data", bodyParser.text());
51
52
app.GET("/data/data", (req, res) => {
53
res.writeHead(200, { "Content-Type": "application/json" })
54
res.end(JSON.stringify({
55
live: visitors.size,
56
peak,
57
visits
58
}));
59
});
60
61
app.GET("/data/debug", (req, res) => {
62
res.writeHead(200, { "Content-Type": "application/json" });
63
res.end(JSON.stringify(Object.fromEntries(visitors)));
64
});
65
66
app.GET("/data/create-id", (req, res) => {
67
const id = crypto.randomUUID();
68
visitors.set(id, {
69
ut: Date.now(),
70
creation: Date.now()
71
});
72
res.end(id);
73
});
74
75
app.POST("/data/visit", (req, res) => {
76
visits++;
77
res.end("OK");
78
});
79
80
app.POST("/data/check-id", (req, res) => {
81
res.end(visitors.has(req.body).toString());
82
});
83
84
app.POST("/data/keep-alive", (req, res) => {
85
if(!visitors.has(req.body)) return res.end("Invalid ID");
86
if(peak < visitors.size) peak = visitors.size;
87
visitors.set(req.body, {
88
ut: Date.now(),
89
...visitors.get(req.body)
90
});
91
res.end("OK");
92
});
93
94
app.POST("/data/destroy", (req, res) => {
95
if(!visitors.has(req.body)) return res.end("Invalid ID");
96
visitors.delete(req.body);
97
res.end("Deleted");
98
});
99
};
100
101
setInterval(() => {
102
const now = Date.now();
103
for(const [uuid, data] of [...visitors]) {
104
if(now - data.ut > 60000) {
105
visitors.delete(uuid);
106
}
107
}
108
}, 60000);
109