UNPKG

10.2 kBJavaScriptView Raw
1this.workbox = this.workbox || {};
2this.workbox.streams = (function (exports, logger_mjs, assert_mjs) {
3 'use strict';
4
5 try {
6 self['workbox:streams:4.3.0'] && _();
7 } catch (e) {} // eslint-disable-line
8
9 /*
10 Copyright 2018 Google LLC
11
12 Use of this source code is governed by an MIT-style
13 license that can be found in the LICENSE file or at
14 https://opensource.org/licenses/MIT.
15 */
16 /**
17 * Takes either a Response, a ReadableStream, or a
18 * [BodyInit](https://fetch.spec.whatwg.org/#bodyinit) and returns the
19 * ReadableStreamReader object associated with it.
20 *
21 * @param {workbox.streams.StreamSource} source
22 * @return {ReadableStreamReader}
23 * @private
24 */
25
26 function _getReaderFromSource(source) {
27 if (source.body && source.body.getReader) {
28 return source.body.getReader();
29 }
30
31 if (source.getReader) {
32 return source.getReader();
33 } // TODO: This should be possible to do by constructing a ReadableStream, but
34 // I can't get it to work. As a hack, construct a new Response, and use the
35 // reader associated with its body.
36
37
38 return new Response(source).body.getReader();
39 }
40 /**
41 * Takes multiple source Promises, each of which could resolve to a Response, a
42 * ReadableStream, or a [BodyInit](https://fetch.spec.whatwg.org/#bodyinit).
43 *
44 * Returns an object exposing a ReadableStream with each individual stream's
45 * data returned in sequence, along with a Promise which signals when the
46 * stream is finished (useful for passing to a FetchEvent's waitUntil()).
47 *
48 * @param {Array<Promise<workbox.streams.StreamSource>>} sourcePromises
49 * @return {Object<{done: Promise, stream: ReadableStream}>}
50 *
51 * @memberof workbox.streams
52 */
53
54
55 function concatenate(sourcePromises) {
56 {
57 assert_mjs.assert.isArray(sourcePromises, {
58 moduleName: 'workbox-streams',
59 funcName: 'concatenate',
60 paramName: 'sourcePromises'
61 });
62 }
63
64 const readerPromises = sourcePromises.map(sourcePromise => {
65 return Promise.resolve(sourcePromise).then(source => {
66 return _getReaderFromSource(source);
67 });
68 });
69 let fullyStreamedResolve;
70 let fullyStreamedReject;
71 const done = new Promise((resolve, reject) => {
72 fullyStreamedResolve = resolve;
73 fullyStreamedReject = reject;
74 });
75 let i = 0;
76 const logMessages = [];
77 const stream = new ReadableStream({
78 pull(controller) {
79 return readerPromises[i].then(reader => reader.read()).then(result => {
80 if (result.done) {
81 {
82 logMessages.push(['Reached the end of source:', sourcePromises[i]]);
83 }
84
85 i++;
86
87 if (i >= readerPromises.length) {
88 // Log all the messages in the group at once in a single group.
89 {
90 logger_mjs.logger.groupCollapsed(`Concatenating ${readerPromises.length} sources.`);
91
92 for (const message of logMessages) {
93 if (Array.isArray(message)) {
94 logger_mjs.logger.log(...message);
95 } else {
96 logger_mjs.logger.log(message);
97 }
98 }
99
100 logger_mjs.logger.log('Finished reading all sources.');
101 logger_mjs.logger.groupEnd();
102 }
103
104 controller.close();
105 fullyStreamedResolve();
106 return;
107 }
108
109 return this.pull(controller);
110 } else {
111 controller.enqueue(result.value);
112 }
113 }).catch(error => {
114 {
115 logger_mjs.logger.error('An error occurred:', error);
116 }
117
118 fullyStreamedReject(error);
119 throw error;
120 });
121 },
122
123 cancel() {
124 {
125 logger_mjs.logger.warn('The ReadableStream was cancelled.');
126 }
127
128 fullyStreamedResolve();
129 }
130
131 });
132 return {
133 done,
134 stream
135 };
136 }
137
138 /*
139 Copyright 2018 Google LLC
140
141 Use of this source code is governed by an MIT-style
142 license that can be found in the LICENSE file or at
143 https://opensource.org/licenses/MIT.
144 */
145 /**
146 * This is a utility method that determines whether the current browser supports
147 * the features required to create streamed responses. Currently, it checks if
148 * [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/ReadableStream)
149 * is available.
150 *
151 * @param {HeadersInit} [headersInit] If there's no `Content-Type` specified,
152 * `'text/html'` will be used by default.
153 * @return {boolean} `true`, if the current browser meets the requirements for
154 * streaming responses, and `false` otherwise.
155 *
156 * @memberof workbox.streams
157 */
158
159 function createHeaders(headersInit = {}) {
160 // See https://github.com/GoogleChrome/workbox/issues/1461
161 const headers = new Headers(headersInit);
162
163 if (!headers.has('content-type')) {
164 headers.set('content-type', 'text/html');
165 }
166
167 return headers;
168 }
169
170 /*
171 Copyright 2018 Google LLC
172
173 Use of this source code is governed by an MIT-style
174 license that can be found in the LICENSE file or at
175 https://opensource.org/licenses/MIT.
176 */
177 /**
178 * Takes multiple source Promises, each of which could resolve to a Response, a
179 * ReadableStream, or a [BodyInit](https://fetch.spec.whatwg.org/#bodyinit),
180 * along with a
181 * [HeadersInit](https://fetch.spec.whatwg.org/#typedefdef-headersinit).
182 *
183 * Returns an object exposing a Response whose body consists of each individual
184 * stream's data returned in sequence, along with a Promise which signals when
185 * the stream is finished (useful for passing to a FetchEvent's waitUntil()).
186 *
187 * @param {Array<Promise<workbox.streams.StreamSource>>} sourcePromises
188 * @param {HeadersInit} [headersInit] If there's no `Content-Type` specified,
189 * `'text/html'` will be used by default.
190 * @return {Object<{done: Promise, response: Response}>}
191 *
192 * @memberof workbox.streams
193 */
194
195 function concatenateToResponse(sourcePromises, headersInit) {
196 const {
197 done,
198 stream
199 } = concatenate(sourcePromises);
200 const headers = createHeaders(headersInit);
201 const response = new Response(stream, {
202 headers
203 });
204 return {
205 done,
206 response
207 };
208 }
209
210 /*
211 Copyright 2018 Google LLC
212
213 Use of this source code is governed by an MIT-style
214 license that can be found in the LICENSE file or at
215 https://opensource.org/licenses/MIT.
216 */
217 let cachedIsSupported = undefined;
218 /**
219 * This is a utility method that determines whether the current browser supports
220 * the features required to create streamed responses. Currently, it checks if
221 * [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/ReadableStream)
222 * can be created.
223 *
224 * @return {boolean} `true`, if the current browser meets the requirements for
225 * streaming responses, and `false` otherwise.
226 *
227 * @memberof workbox.streams
228 */
229
230 function isSupported() {
231 if (cachedIsSupported === undefined) {
232 // See https://github.com/GoogleChrome/workbox/issues/1473
233 try {
234 new ReadableStream({
235 start() {}
236
237 });
238 cachedIsSupported = true;
239 } catch (error) {
240 cachedIsSupported = false;
241 }
242 }
243
244 return cachedIsSupported;
245 }
246
247 /*
248 Copyright 2018 Google LLC
249
250 Use of this source code is governed by an MIT-style
251 license that can be found in the LICENSE file or at
252 https://opensource.org/licenses/MIT.
253 */
254 /**
255 * A shortcut to create a strategy that could be dropped-in to Workbox's router.
256 *
257 * On browsers that do not support constructing new `ReadableStream`s, this
258 * strategy will automatically wait for all the `sourceFunctions` to complete,
259 * and create a final response that concatenates their values together.
260 *
261 * @param {
262 * Array<function(workbox.routing.Route~handlerCallback)>} sourceFunctions
263 * Each function should return a {@link workbox.streams.StreamSource} (or a
264 * Promise which resolves to one).
265 * @param {HeadersInit} [headersInit] If there's no `Content-Type` specified,
266 * `'text/html'` will be used by default.
267 * @return {workbox.routing.Route~handlerCallback}
268 *
269 * @memberof workbox.streams
270 */
271
272 function strategy(sourceFunctions, headersInit) {
273 return async ({
274 event,
275 url,
276 params
277 }) => {
278 if (isSupported()) {
279 const {
280 done,
281 response
282 } = concatenateToResponse(sourceFunctions.map(fn => fn({
283 event,
284 url,
285 params
286 })), headersInit);
287 event.waitUntil(done);
288 return response;
289 }
290
291 {
292 logger_mjs.logger.log(`The current browser doesn't support creating response ` + `streams. Falling back to non-streaming response instead.`);
293 } // Fallback to waiting for everything to finish, and concatenating the
294 // responses.
295
296
297 const parts = await Promise.all(sourceFunctions.map(sourceFunction => sourceFunction({
298 event,
299 url,
300 params
301 })).map(async responsePromise => {
302 const response = await responsePromise;
303
304 if (response instanceof Response) {
305 return response.blob();
306 } // Otherwise, assume it's something like a string which can be used
307 // as-is when constructing the final composite blob.
308
309
310 return response;
311 }));
312 const headers = createHeaders(headersInit); // Constructing a new Response from a Blob source is well-supported.
313 // So is constructing a new Blob from multiple source Blobs or strings.
314
315 return new Response(new Blob(parts), {
316 headers
317 });
318 };
319 }
320
321 /*
322 Copyright 2018 Google LLC
323
324 Use of this source code is governed by an MIT-style
325 license that can be found in the LICENSE file or at
326 https://opensource.org/licenses/MIT.
327 */
328
329 exports.concatenate = concatenate;
330 exports.concatenateToResponse = concatenateToResponse;
331 exports.isSupported = isSupported;
332 exports.strategy = strategy;
333
334 return exports;
335
336}({}, workbox.core._private, workbox.core._private));
337