11
11
12
12
/** Class to wrap all module constants, reused in tests
13
13
*
14
- * This is a slightly silly hack because Workers can't export consts , though
15
- * exported classes seem to be fine. This should really be a separate import,
16
- * but this complicates the Worker deployment a little.
14
+ * This is a slightly silly hack because Workers can't export strings , though
15
+ * exported classes seem to be fine. These should really be strings in a
16
+ * separate import, but this complicates the Worker deployment a little.
17
17
**/
18
18
export class AddonsConstants {
19
19
// Add "readthedocs-addons.js" inside the "<head>"
@@ -91,59 +91,69 @@ async function onFetch(request, env, context) {
91
91
return response ;
92
92
}
93
93
94
- const responseFallback = response . clone ( ) ;
95
-
96
94
try {
97
95
const transformed = await transformResponse ( response ) ;
98
- // Wait on the transformed text for force errors to evaluate. Timeout errors
99
- // and errors thrown during transform aren't actually raised until the body
100
- // is read
101
- return new Response ( await transformed . text ( ) , transformed ) ;
96
+ // Wait on the transformed body for errors to evaluate. Timeout errors and
97
+ // errors thrown during transform aren't actually raised until the body is
98
+ // read. If there was no return, we should return the original response.
99
+ if ( transformed !== undefined ) {
100
+ return new Response ( await transformed . arrayBuffer ( ) , transformed ) ;
101
+ }
102
102
} catch ( error ) {
103
103
console . error ( "Discarding error:" , error ) ;
104
+ console . debug ( "Returning original response to avoid blank page" ) ;
104
105
}
105
106
106
- console . debug ( "Returning original response to avoid blank page" ) ;
107
- return responseFallback ;
107
+ return response ;
108
108
}
109
109
110
110
export default {
111
111
fetch : onFetch ,
112
112
} ;
113
113
114
- async function transformResponse ( originalResponse ) {
115
- const { headers } = originalResponse ;
114
+ /** Transform HTTP reponses
115
+ *
116
+ * This supports several versions of element removal and transformation of some
117
+ * content that was previously injected into project builds.
118
+ *
119
+ * Important: wait until the last minute to use a `response.clone()`, as if you
120
+ * create a clone and do not use the body, this consumes extra memory in the
121
+ * worker.
122
+ *
123
+ * @param response {Response} - The origin response
124
+ * @returns {Response } - If transformed, return a response, otherwise `null`.
125
+ **/
126
+ async function transformResponse ( response ) {
127
+ const { headers } = response ;
116
128
117
129
// Get the content type of the response to manipulate the content only if it's HTML
118
130
const contentType = headers . get ( "content-type" ) || "" ;
119
131
const injectHostingIntegrations =
120
132
headers . get ( "x-rtd-hosting-integrations" ) || "false" ;
121
133
const forceAddons = headers . get ( "x-rtd-force-addons" ) || "false" ;
122
- const httpStatus = originalResponse . status ;
134
+ const httpStatus = response . status ;
123
135
124
136
// Log some debugging data
125
137
console . log ( `ContentType: ${ contentType } ` ) ;
126
138
console . log ( `X-RTD-Force-Addons: ${ forceAddons } ` ) ;
127
139
console . log ( `X-RTD-Hosting-Integrations: ${ injectHostingIntegrations } ` ) ;
128
140
console . log ( `HTTP status: ${ httpStatus } ` ) ;
129
141
130
- // Debug mode test operations
142
+ // Debug mode for some test cases. This is just for triggering an exception
143
+ // from tests. There might be a way to conditionally enable this, but I had
144
+ // trouble getting Wrangler vars to work at all.
131
145
const throwError = headers . get ( "X-RTD-Throw-Error" ) ;
132
146
if ( throwError ) {
133
147
console . log ( `Throw error: ${ throwError } ` ) ;
134
148
}
135
149
136
- // get project/version slug from headers inject by El Proxito
137
- const projectSlug = headers . get ( "x-rtd-project" ) || "" ;
138
- const versionSlug = headers . get ( "x-rtd-version" ) || "" ;
139
- const resolverFilename = headers . get ( "x-rtd-resolver-filename" ) || "" ;
140
-
141
- // Check to decide whether or not inject the new beta addons:
142
- //
143
- // - content type has to be "text/html"
144
- // when all these conditions are met, we remove all the old JS/CSS files and inject the new beta flyout JS
150
+ // Get project/version slug from headers inject by El Proxito
151
+ const projectSlug = headers . get ( "X-RTD-Project" ) || "" ;
152
+ const versionSlug = headers . get ( "X-RTD-Version" ) || "" ;
153
+ const resolverFilename = headers . get ( "X-RTD-Resolver-Filename" ) || "" ;
145
154
146
- // check if the Content-Type is HTML, otherwise do nothing
155
+ // Check to decide whether or not inject Addons library. We only do this for
156
+ // `text/html` content types.
147
157
if ( contentType . includes ( "text/html" ) ) {
148
158
// Remove old implementation of our flyout and inject the new addons if the following conditions are met:
149
159
//
@@ -153,7 +163,7 @@ async function transformResponse(originalResponse) {
153
163
if ( forceAddons === "true" && injectHostingIntegrations === "false" ) {
154
164
let rewriter = new HTMLRewriter ( ) ;
155
165
156
- // Remove by selectors
166
+ // Remove by selector lookup
157
167
for ( const script of AddonsConstants . removalScripts ) {
158
168
rewriter . on ( script , new removeElement ( ) ) ;
159
169
}
@@ -183,20 +193,21 @@ async function transformResponse(originalResponse) {
183
193
) ,
184
194
)
185
195
. on ( "*" , {
186
- // This mimics a number of exceptions that can occur in the inner
187
- // request to origin, but mostly a hard to replicate timeout due to
188
- // a large page size.
196
+ // This is only used for testing purposes. This is used to mimic an
197
+ // error being thrown during the request to origin. There are cases
198
+ // that are difficult to model here, like a large page size timing out
199
+ // the request.
189
200
element ( element ) {
190
201
if ( throwError ) {
191
202
throw new Error ( "Manually triggered error in transform" ) ;
192
203
}
193
204
} ,
194
205
} )
195
- . transform ( originalResponse ) ;
206
+ . transform ( await response . clone ( ) ) ;
196
207
}
197
208
}
198
209
199
- // Inject the new addons if the following conditions are met:
210
+ // Inject Addons if the following conditions are met:
200
211
//
201
212
// - header `X-RTD-Hosting-Integrations` is present (added automatically when using `build.commands`)
202
213
// - header `X-RTD-Force-Addons` is not present (user opted-in into new beta addons)
@@ -208,23 +219,24 @@ async function transformResponse(originalResponse) {
208
219
"head" ,
209
220
new addMetaTags ( projectSlug , versionSlug , resolverFilename , httpStatus ) ,
210
221
)
211
- . transform ( originalResponse ) ;
222
+ . transform ( await response . clone ( ) ) ;
212
223
}
213
224
214
225
// Modify `_static/searchtools.js` to re-enable Sphinx's default search
215
226
if (
216
227
( contentType . includes ( "text/javascript" ) ||
217
228
contentType . includes ( "application/javascript" ) ) &&
218
229
( injectHostingIntegrations === "true" || forceAddons === "true" ) &&
219
- originalResponse . url . endsWith ( "_static/searchtools.js" )
230
+ response . url . endsWith ( "_static/searchtools.js" )
220
231
) {
221
- console . log ( "Modifying _static/searchtools.js" ) ;
222
- return handleSearchToolsJSRequest ( originalResponse ) ;
232
+ console . debug ( "Modifying _static/searchtools.js" ) ;
233
+ // Note, not a `clone()` call here on purpose, as we create a new Response
234
+ // in the function already.
235
+ return handleSearchToolsJSRequest ( response ) ;
223
236
}
224
237
225
- // if none of the previous conditions are met,
226
- // we return the response without modifying it
227
- return originalResponse ;
238
+ // Return null response, don't continue transforming this response
239
+ return ;
228
240
}
229
241
230
242
class removeElement {
@@ -287,11 +299,11 @@ class addMetaTags {
287
299
* initialization of the default Sphinx search. This reverts manipulation and
288
300
* restores the original Sphinx search initialization.
289
301
*
290
- * @param originalResponse {Response} - Response from origin
302
+ * @param response {Response} - Response from origin
291
303
* @returns {Response }
292
304
**/
293
- async function handleSearchToolsJSRequest ( originalResponse ) {
305
+ async function handleSearchToolsJSRequest ( response ) {
294
306
const { pattern, replacement } = AddonsConstants . replacements . searchtools ;
295
- const content = await originalResponse . text ( ) ;
296
- return new Response ( content . replace ( pattern , replacement ) , originalResponse ) ;
307
+ const content = await response . text ( ) ;
308
+ return new Response ( content . replace ( pattern , replacement ) , response ) ;
297
309
}
0 commit comments