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