@@ -2,167 +2,172 @@ import Cloudflare from "cloudflare";
22import { z } from "zod" ;
33
44interface CaptureBody {
5- url : string ;
6- tags : string [ ] ;
7- zone : string ;
5+ url : string ;
6+ tags : string [ ] ;
7+ zone : string ;
88}
99
1010const Purge = z . object ( {
11- tags : z . array ( z . string ( ) ) ,
11+ tags : z . array ( z . string ( ) ) ,
1212} ) ;
1313
1414const Capture = z . object ( {
15- url : z . string ( ) . url ( ) ,
16- tags : z . array ( z . string ( ) ) ,
15+ url : z . string ( ) . url ( ) ,
16+ tags : z . array ( z . string ( ) ) ,
1717} ) ;
1818
1919function * chunks < T > ( arr : T [ ] , n : number ) {
20- for ( let i = 0 ; i < arr . length ; i += n ) {
21- yield arr . slice ( i , i + n ) ;
22- }
20+ for ( let i = 0 ; i < arr . length ; i += n ) {
21+ yield arr . slice ( i , i + n ) ;
22+ }
2323}
2424
2525interface PurgeBody {
26- tag : string ;
27- zone ?: string | undefined ;
26+ tag : string ;
27+ zone ?: string ;
2828}
2929
3030function apiToken ( request : Request , env : Env ) : string {
31- const auth = request . headers . get ( "Authorization" ) ;
32- if ( ! auth ) {
33- throw new Error ( "Missing Authorization header" ) ;
34- }
35- const [ scheme , token ] = auth . split ( " " ) ;
36- if ( scheme !== "Bearer" ) {
37- throw new Error ( "Authorization scheme is not Bearer" ) ;
38- }
39-
40- // Needs at least `Cache Purge:Purge, Zone:Read" permissions.
41- if ( token !== env . API_TOKEN ) {
42- throw new Error ( "Provided token does not match the `API_TOKEN` secret." ) ;
43- }
44-
45- return token ;
31+ const auth = request . headers . get ( "Authorization" ) ;
32+ if ( ! auth ) {
33+ throw new Error ( "Missing Authorization header" ) ;
34+ }
35+ const [ scheme , token ] = auth . split ( " " ) ;
36+ if ( scheme !== "Bearer" ) {
37+ throw new Error ( "Authorization scheme is not Bearer" ) ;
38+ }
39+
40+ // Needs at least `Cache Purge:Purge, Zone:Read" permissions.
41+ if ( token !== env . API_TOKEN ) {
42+ throw new Error ( "Provided token does not match the `API_TOKEN` secret." ) ;
43+ }
44+
45+ return token ;
4646}
4747
4848async function handlePurgeRequest (
49- request : Request ,
50- env : Env ,
49+ request : Request ,
50+ env : Env ,
5151) : Promise < Response > {
52- let token : string ;
53- try {
54- token = apiToken ( request , env ) ;
55- } catch ( e ) {
56- return Response . json (
57- {
58- error : String ( e ) ,
59- } ,
60- { status : 401 } ,
61- ) ;
62- }
63-
64- const client = new Cloudflare ( {
65- apiToken : token ,
66- } ) ;
67-
68- const { status } = await client . user . tokens . verify ( ) ;
69-
70- if ( status !== "active" ) {
71- return Response . json (
72- {
73- error : "Authentication token is not active." ,
74- } ,
75- { status : 401 } ,
76- ) ;
77- }
78-
79- const { tags } = Purge . parse ( await request . json ( ) ) ;
80- console . debug ( "[Cache Purge Request] Purge Tags" , tags ) ;
81-
82- // If no zone is present, then all zones will be purged.
83- const zone = request . headers . get ( "CF-Worker" ) ?? undefined ;
84-
85- const messages = tags . map < MessageSendRequest < PurgeBody > > ( ( tag ) => ( {
86- body : {
87- tag,
88- zone,
89- } ,
90- contentType : "json" ,
91- } ) ) ;
92-
93- // sendBatch only allows for a maximum of 100 messages.
94- const promises : ReturnType < typeof env . CACHE_PURGE_TAG . sendBatch > [ ] = [ ] ;
95- for ( const messageChunks of chunks ( messages , 100 ) ) {
96- console . debug (
97- "[Cache Purge Request] Send Batch" ,
98- messageChunks . map ( ( { body } ) => body ) ,
99- ) ;
100- promises . push ( env . CACHE_PURGE_TAG . sendBatch ( messageChunks ) ) ;
101- }
102-
103- await Promise . all ( promises ) ;
104-
105- return new Response ( "" , { status : 202 } ) ;
52+ let token : string ;
53+ try {
54+ token = apiToken ( request , env ) ;
55+ } catch ( e ) {
56+ return Response . json (
57+ {
58+ error : String ( e ) ,
59+ } ,
60+ { status : 401 } ,
61+ ) ;
62+ }
63+
64+ const client = new Cloudflare ( {
65+ apiToken : token ,
66+ } ) ;
67+
68+ const { status } = await client . user . tokens . verify ( ) ;
69+
70+ if ( status !== "active" ) {
71+ return Response . json (
72+ {
73+ error : "Authentication token is not active." ,
74+ } ,
75+ { status : 401 } ,
76+ ) ;
77+ }
78+
79+ const { tags } = Purge . parse ( await request . json ( ) ) ;
80+ console . debug ( "[Cache Purge Request] Purge Tags" , tags ) ;
81+
82+ // If no zone is present, then all zones will be purged.
83+ const zone = request . headers . get ( "CF-Worker" ) ?? undefined ;
84+
85+ const messages = tags . map < MessageSendRequest < PurgeBody > > ( ( tag ) => {
86+ const body : PurgeBody = {
87+ tag,
88+ } ;
89+ if ( zone ) {
90+ body . zone = zone ;
91+ }
92+ return {
93+ body,
94+ contentType : "json" ,
95+ } ;
96+ } ) ;
97+
98+ // sendBatch only allows for a maximum of 100 messages.
99+ const promises : ReturnType < typeof env . CACHE_PURGE_TAG . sendBatch > [ ] = [ ] ;
100+ for ( const messageChunks of chunks ( messages , 100 ) ) {
101+ console . debug (
102+ "[Cache Purge Request] Send Batch" ,
103+ messageChunks . map ( ( { body } ) => body ) ,
104+ ) ;
105+ promises . push ( env . CACHE_PURGE_TAG . sendBatch ( messageChunks ) ) ;
106+ }
107+
108+ await Promise . all ( promises ) ;
109+
110+ return new Response ( "" , { status : 202 } ) ;
106111}
107112
108113async function handleCaptureRequest (
109- request : Request ,
110- env : Env ,
114+ request : Request ,
115+ env : Env ,
111116) : Promise < Response > {
112- // Since this worker can be called over the internet, we must at least verify that the token matches the secret,
113- // but we don't need to verify that it's usable right now.
114- try {
115- apiToken ( request , env ) ;
116- } catch ( e ) {
117- return Response . json (
118- {
119- error : String ( e ) ,
120- } ,
121- { status : 401 } ,
122- ) ;
123- }
124-
125- // If there is no zone on the request,
126- // then we wont know how to purge the response later.
127- const zone = request . headers . get ( "CF-Worker" ) ;
128- if ( ! zone ) {
129- return Response . json (
130- {
131- error : "Missing CF-Worker Header" ,
132- } ,
133- { status : 400 } ,
134- ) ;
135- }
136-
137- const { url, tags } = Capture . parse ( await request . json ( ) ) ;
138-
139- const capture : CaptureBody = {
140- url,
141- zone,
142- tags,
143- } ;
144-
145- await env . CACHE_CAPTURE . send ( capture , { contentType : "json" } ) ;
146-
147- return new Response ( "" , { status : 202 } ) ;
117+ // Since this worker can be called over the internet, we must at least verify that the token matches the secret,
118+ // but we don't need to verify that it's usable right now.
119+ try {
120+ apiToken ( request , env ) ;
121+ } catch ( e ) {
122+ return Response . json (
123+ {
124+ error : String ( e ) ,
125+ } ,
126+ { status : 401 } ,
127+ ) ;
128+ }
129+
130+ // If there is no zone on the request,
131+ // then we wont know how to purge the response later.
132+ const zone = request . headers . get ( "CF-Worker" ) ;
133+ if ( ! zone ) {
134+ return Response . json (
135+ {
136+ error : "Missing CF-Worker Header" ,
137+ } ,
138+ { status : 400 } ,
139+ ) ;
140+ }
141+
142+ const { url, tags } = Capture . parse ( await request . json ( ) ) ;
143+
144+ const capture : CaptureBody = {
145+ url,
146+ zone,
147+ tags,
148+ } ;
149+
150+ await env . CACHE_CAPTURE . send ( capture , { contentType : "json" } ) ;
151+
152+ return new Response ( "" , { status : 202 } ) ;
148153}
149154
150155export default {
151- async fetch ( request , env ) {
152- // Remove any tracking params to increase the cache hit rate.
153- const url = new URL ( request . url ) ;
154-
155- if ( request . method !== "POST" ) {
156- return new Response ( "" , { status : 404 } ) ;
157- }
158-
159- switch ( url . pathname ) {
160- case "/purge" :
161- return handlePurgeRequest ( request , env ) ;
162- case "/capture" :
163- return handleCaptureRequest ( request , env ) ;
164- default :
165- return new Response ( "" , { status : 404 } ) ;
166- }
167- } ,
156+ async fetch ( request , env ) {
157+ // Remove any tracking params to increase the cache hit rate.
158+ const url = new URL ( request . url ) ;
159+
160+ if ( request . method !== "POST" ) {
161+ return new Response ( "" , { status : 404 } ) ;
162+ }
163+
164+ switch ( url . pathname ) {
165+ case "/purge" :
166+ return handlePurgeRequest ( request , env ) ;
167+ case "/capture" :
168+ return handleCaptureRequest ( request , env ) ;
169+ default :
170+ return new Response ( "" , { status : 404 } ) ;
171+ }
172+ } ,
168173} satisfies ExportedHandler < Env > ;
0 commit comments