1import { updateLastPosts } from '$lib/bluesky';
2import { getLastTrack, updateNowPlayingTrack } from '$lib/lastfm';
3import { steamReadLastGame, steamUpdateNowPlaying } from '$lib/steam';
4import { updateCommits } from '$lib/activity';
5import { ToadScheduler, SimpleIntervalJob, Task, AsyncTask } from 'toad-scheduler';
6import {
7 incrementFakeVisitCount,
8 incrementLegitVisitCount,
9 pushMetric,
10 sendAllMetrics
11} from '$lib/metrics';
12import {
13 addLastVisitor,
14 decrementVisitCount,
15 incrementVisitCount,
16 notifyDarkVisitors,
17 removeLastVisitor
18} from '$lib/visits';
19import { testUa } from '$lib/robots';
20import { error, type Handle } from '@sveltejs/kit';
21import { _fetchEntries } from './routes/(site)/guestbook/+page.server';
22import { sequence } from '@sveltejs/kit/hooks';
23
24const updateNowPlaying = async () => {
25 try {
26 await Promise.all([steamUpdateNowPlaying(), updateNowPlayingTrack()]);
27 } catch (err) {
28 console.log(`error while updating: ${err}`);
29 }
30};
31const refreshContent = async () => {
32 try {
33 await Promise.all([updateLastPosts(), _fetchEntries(), updateCommits(), sendAllMetrics()]);
34 } catch (err) {
35 console.log(`error while updating: ${err}`);
36 }
37};
38
39await Promise.all([updateNowPlaying(), refreshContent()]);
40
41const scheduler = new ToadScheduler();
42scheduler.addSimpleIntervalJob(
43 new SimpleIntervalJob(
44 { seconds: 5 },
45 new AsyncTask('updateNowPlaying task', updateNowPlaying, (err) =>
46 console.log(`error while updateNowPlaying: ${err}`)
47 )
48 )
49);
50scheduler.addSimpleIntervalJob(
51 new SimpleIntervalJob(
52 { seconds: 30 },
53 new AsyncTask('refreshContent task', refreshContent, (err) =>
54 console.log(`error while refreshContent: ${err}`)
55 )
56 )
57);
58
59const corsHandler = (allowedOrigins = ['*']) => {
60 return async ({ event, resolve }: Parameters<Handle>[0]) => {
61 const origin = event.request.headers.get('origin');
62
63 const corsHeaders: Record<string, string> = {
64 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
65 'Access-Control-Allow-Headers': 'Content-Type, Authorization',
66 };
67
68 if (allowedOrigins.includes('*'))
69 corsHeaders['Access-Control-Allow-Origin'] = '*';
70 else if (origin && allowedOrigins.includes(origin)) {
71 corsHeaders['Access-Control-Allow-Origin'] = origin;
72 corsHeaders['Access-Control-Allow-Credentials'] = 'true';
73 }
74
75 if (event.request.method === 'OPTIONS')
76 return new Response(null, { headers: corsHeaders });
77
78 const response = await resolve(event);
79
80 Object.entries(corsHeaders).forEach(([key, value]) => {
81 response.headers.set(key, value);
82 });
83
84 return response;
85 };
86}
87
88const handler = async ({ event, resolve }: Parameters<Handle>[0]) => {
89 notifyDarkVisitors(event.url, event.request); // no await so it doesnt block
90
91 const isPrefetch = () => {
92 return (
93 event.request.headers.get('Sec-Purpose')?.includes('prefetch') ||
94 event.request.headers.get('Purpose')?.includes('prefetch') ||
95 event.request.headers.get('x-purpose')?.includes('preview') ||
96 event.request.headers.get('x-moz')?.includes('prefetch')
97 );
98 };
99 const isApi = () => {
100 return event.url.pathname.startsWith('/_api');
101 };
102 const isRss = () => {
103 return event.url.pathname.endsWith('/_rss');
104 };
105
106 // block any requests if the user agent is disallowed by our robots txt
107 const isFakeVisit =
108 (await testUa(event.url.toString(), event.request.headers.get('user-agent') ?? '')) === false;
109 if (isFakeVisit) {
110 pushMetric({ gazesys_visit_fake_total: await incrementFakeVisitCount() });
111 throw error(403, 'get a better user agent silly');
112 }
113
114 // only push metric if legit page visit (still want rss to count here though)
115 const isPageVisit = !isApi() && !isPrefetch();
116 if (isPageVisit) pushMetric({ gazesys_visit_real_total: await incrementLegitVisitCount() });
117
118 // only add visitors if its a "legit" page visit
119 let id = null;
120 let valid = false;
121 if (isPageVisit && !isRss()) {
122 id = addLastVisitor(event.request, event.cookies);
123 valid = await incrementVisitCount(event.request, event.cookies);
124 }
125
126 // actually resolve event
127 const resp = await resolve(event);
128 // remove visitors if it was a 404
129 if (resp.status === 404) {
130 if (id !== null) removeLastVisitor(id);
131 if (valid) decrementVisitCount();
132 }
133
134 return resp;
135};
136
137const allowedOrigins = [
138 "https://gaze.systems",
139 "https://ptr.pet",
140 "https://poor.dog",
141];
142export const handle = sequence(corsHandler(allowedOrigins), handler);