-
Notifications
You must be signed in to change notification settings - Fork 3
/
index.js
124 lines (105 loc) · 3.14 KB
/
index.js
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
// URL to fetch upstream assets from
const ORIGIN_URL = 'https://github.com/ProtonAOSP'
// Whether to cache assets from GitHub
// Disable to conserve Workers cache quota
const USE_CACHE = false
// Browser cache TTL, in seconds
const CACHE_TTL = 5259488 // 2 months
// Regex patterns for allowed CORS origins
const CORS_ALLOWED_ORIGINS = [
/^protonaosp\.org$/,
/^android-webinstall-kdrag0n\.vercel\.app$/,
/^android-webinstall-git-[a-z0-9\-_/]-kdrag0n\.vercel\.app$/,
/^android-webinstall-[a-z0-9]+-kdrag0n\.vercel\.app$/,
]
class RequestError extends Error {
constructor(status, message) {
super(`HTTP ${status}: ${message}`)
this.status = status
this.messageText = message
this.name = this.constructor.name
}
}
function validateCors(origin) {
let originUrl = new URL(origin)
for (let pattern of CORS_ALLOWED_ORIGINS) {
if (pattern.test(originUrl.host)) {
return true
}
}
return false
}
function parseRequest(request) {
if (request.method !== 'GET') {
throw new RequestError(405, 'Invalid request method')
}
// Validate CORS, if necessary
let origin = request.headers.get('Origin')
if (origin !== null && !validateCors(origin)) {
throw new RequestError(403, 'Origin not allowed')
}
let reqUrl = new URL(request.url)
let reqParts = reqUrl.pathname.split('/').slice(1)
// repo, tag, filename
if (reqParts.length !== 3) {
throw new RequestError(404, 'Invalid request URL')
}
let [repo, tag, filename] = reqParts
return {
url: `${ORIGIN_URL}/${repo}/releases/download/${tag}/${filename}`,
corsOrigin: origin,
}
}
function modifyResponse(response) {
response.headers.set('Cache-Control', `public, max-age=${CACHE_TTL}`)
}
async function respondAsset(request) {
const cache = caches.default
let { url, corsOrigin } = parseRequest(request)
let originResp = undefined
if (USE_CACHE) {
// Try cache first
originResp = await cache.match(request)
}
if (originResp === undefined) {
// Fetch from origin
originResp = await fetch(url)
if (USE_CACHE) {
// Tee the stream for both the client and caching readers to use
let bodies = originResp.body.tee()
originResp = new Response(bodies[0], originResp)
let cacheResp = new Response(bodies[1], originResp)
// Set TTL for caching purposes
modifyResponse(cacheResp)
await cache.put(request, cacheResp)
}
}
// Copy response and modify headers
let clientResp = new Response(originResp.body, originResp)
modifyResponse(clientResp)
if (corsOrigin !== null) {
clientResp.headers.set('Access-Control-Allow-Origin', corsOrigin)
}
return clientResp
}
async function handleRequest(request) {
try {
return await respondAsset(request)
} catch (e) {
// Handle RequestError exceptions with proper error responses
if (e instanceof RequestError) {
return new Response(e.messageText, {
status: e.status,
headers: {
'Content-Type': 'text/plain;charset=UTF-8',
},
})
} else {
throw e
}
}
}
addEventListener('fetch', function (event) {
let resp = handleRequest(event.request)
return event.respondWith(resp)
})