UNPKG

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