UNPKG

9.26 kBJavaScriptView Raw
1this.workbox = this.workbox || {};
2this.workbox.rangeRequests = (function (exports, WorkboxError_js, assert_js, logger_js) {
3 'use strict';
4
5 try {
6 self['workbox:range-requests: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 * @param {Blob} blob A source blob.
18 * @param {number} [start] The offset to use as the start of the
19 * slice.
20 * @param {number} [end] The offset to use as the end of the slice.
21 * @return {Object} An object with `start` and `end` properties, reflecting
22 * the effective boundaries to use given the size of the blob.
23 *
24 * @private
25 */
26
27 function calculateEffectiveBoundaries(blob, start, end) {
28 {
29 assert_js.assert.isInstance(blob, Blob, {
30 moduleName: 'workbox-range-requests',
31 funcName: 'calculateEffectiveBoundaries',
32 paramName: 'blob'
33 });
34 }
35
36 const blobSize = blob.size;
37
38 if (end && end > blobSize || start && start < 0) {
39 throw new WorkboxError_js.WorkboxError('range-not-satisfiable', {
40 size: blobSize,
41 end,
42 start
43 });
44 }
45
46 let effectiveStart;
47 let effectiveEnd;
48
49 if (start !== undefined && end !== undefined) {
50 effectiveStart = start; // Range values are inclusive, so add 1 to the value.
51
52 effectiveEnd = end + 1;
53 } else if (start !== undefined && end === undefined) {
54 effectiveStart = start;
55 effectiveEnd = blobSize;
56 } else if (end !== undefined && start === undefined) {
57 effectiveStart = blobSize - end;
58 effectiveEnd = blobSize;
59 }
60
61 return {
62 start: effectiveStart,
63 end: effectiveEnd
64 };
65 }
66
67 /*
68 Copyright 2018 Google LLC
69
70 Use of this source code is governed by an MIT-style
71 license that can be found in the LICENSE file or at
72 https://opensource.org/licenses/MIT.
73 */
74 /**
75 * @param {string} rangeHeader A Range: header value.
76 * @return {Object} An object with `start` and `end` properties, reflecting
77 * the parsed value of the Range: header. If either the `start` or `end` are
78 * omitted, then `null` will be returned.
79 *
80 * @private
81 */
82
83 function parseRangeHeader(rangeHeader) {
84 {
85 assert_js.assert.isType(rangeHeader, 'string', {
86 moduleName: 'workbox-range-requests',
87 funcName: 'parseRangeHeader',
88 paramName: 'rangeHeader'
89 });
90 }
91
92 const normalizedRangeHeader = rangeHeader.trim().toLowerCase();
93
94 if (!normalizedRangeHeader.startsWith('bytes=')) {
95 throw new WorkboxError_js.WorkboxError('unit-must-be-bytes', {
96 normalizedRangeHeader
97 });
98 } // Specifying multiple ranges separate by commas is valid syntax, but this
99 // library only attempts to handle a single, contiguous sequence of bytes.
100 // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range#Syntax
101
102
103 if (normalizedRangeHeader.includes(',')) {
104 throw new WorkboxError_js.WorkboxError('single-range-only', {
105 normalizedRangeHeader
106 });
107 }
108
109 const rangeParts = /(\d*)-(\d*)/.exec(normalizedRangeHeader); // We need either at least one of the start or end values.
110
111 if (!rangeParts || !(rangeParts[1] || rangeParts[2])) {
112 throw new WorkboxError_js.WorkboxError('invalid-range-values', {
113 normalizedRangeHeader
114 });
115 }
116
117 return {
118 start: rangeParts[1] === '' ? undefined : Number(rangeParts[1]),
119 end: rangeParts[2] === '' ? undefined : Number(rangeParts[2])
120 };
121 }
122
123 /*
124 Copyright 2018 Google LLC
125
126 Use of this source code is governed by an MIT-style
127 license that can be found in the LICENSE file or at
128 https://opensource.org/licenses/MIT.
129 */
130 /**
131 * Given a `Request` and `Response` objects as input, this will return a
132 * promise for a new `Response`.
133 *
134 * If the original `Response` already contains partial content (i.e. it has
135 * a status of 206), then this assumes it already fulfills the `Range:`
136 * requirements, and will return it as-is.
137 *
138 * @param {Request} request A request, which should contain a Range:
139 * header.
140 * @param {Response} originalResponse A response.
141 * @return {Promise<Response>} Either a `206 Partial Content` response, with
142 * the response body set to the slice of content specified by the request's
143 * `Range:` header, or a `416 Range Not Satisfiable` response if the
144 * conditions of the `Range:` header can't be met.
145 *
146 * @memberof module:workbox-range-requests
147 */
148
149 async function createPartialResponse(request, originalResponse) {
150 try {
151 if ("dev" !== 'production') {
152 assert_js.assert.isInstance(request, Request, {
153 moduleName: 'workbox-range-requests',
154 funcName: 'createPartialResponse',
155 paramName: 'request'
156 });
157 assert_js.assert.isInstance(originalResponse, Response, {
158 moduleName: 'workbox-range-requests',
159 funcName: 'createPartialResponse',
160 paramName: 'originalResponse'
161 });
162 }
163
164 if (originalResponse.status === 206) {
165 // If we already have a 206, then just pass it through as-is;
166 // see https://github.com/GoogleChrome/workbox/issues/1720
167 return originalResponse;
168 }
169
170 const rangeHeader = request.headers.get('range');
171
172 if (!rangeHeader) {
173 throw new WorkboxError_js.WorkboxError('no-range-header');
174 }
175
176 const boundaries = parseRangeHeader(rangeHeader);
177 const originalBlob = await originalResponse.blob();
178 const effectiveBoundaries = calculateEffectiveBoundaries(originalBlob, boundaries.start, boundaries.end);
179 const slicedBlob = originalBlob.slice(effectiveBoundaries.start, effectiveBoundaries.end);
180 const slicedBlobSize = slicedBlob.size;
181 const slicedResponse = new Response(slicedBlob, {
182 // Status code 206 is for a Partial Content response.
183 // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/206
184 status: 206,
185 statusText: 'Partial Content',
186 headers: originalResponse.headers
187 });
188 slicedResponse.headers.set('Content-Length', String(slicedBlobSize));
189 slicedResponse.headers.set('Content-Range', `bytes ${effectiveBoundaries.start}-${effectiveBoundaries.end - 1}/` + originalBlob.size);
190 return slicedResponse;
191 } catch (error) {
192 {
193 logger_js.logger.warn(`Unable to construct a partial response; returning a ` + `416 Range Not Satisfiable response instead.`);
194 logger_js.logger.groupCollapsed(`View details here.`);
195 logger_js.logger.log(error);
196 logger_js.logger.log(request);
197 logger_js.logger.log(originalResponse);
198 logger_js.logger.groupEnd();
199 }
200
201 return new Response('', {
202 status: 416,
203 statusText: 'Range Not Satisfiable'
204 });
205 }
206 }
207
208 /*
209 Copyright 2018 Google LLC
210
211 Use of this source code is governed by an MIT-style
212 license that can be found in the LICENSE file or at
213 https://opensource.org/licenses/MIT.
214 */
215 /**
216 * The range request plugin makes it easy for a request with a 'Range' header to
217 * be fulfilled by a cached response.
218 *
219 * It does this by intercepting the `cachedResponseWillBeUsed` plugin callback
220 * and returning the appropriate subset of the cached response body.
221 *
222 * @memberof module:workbox-range-requests
223 */
224
225 class RangeRequestsPlugin {
226 constructor() {
227 /**
228 * @param {Object} options
229 * @param {Request} options.request The original request, which may or may not
230 * contain a Range: header.
231 * @param {Response} options.cachedResponse The complete cached response.
232 * @return {Promise<Response>} If request contains a 'Range' header, then a
233 * new response with status 206 whose body is a subset of `cachedResponse` is
234 * returned. Otherwise, `cachedResponse` is returned as-is.
235 *
236 * @private
237 */
238 this.cachedResponseWillBeUsed = async ({
239 request,
240 cachedResponse
241 }) => {
242 // Only return a sliced response if there's something valid in the cache,
243 // and there's a Range: header in the request.
244 if (cachedResponse && request.headers.has('range')) {
245 return await createPartialResponse(request, cachedResponse);
246 } // If there was no Range: header, or if cachedResponse wasn't valid, just
247 // pass it through as-is.
248
249
250 return cachedResponse;
251 };
252 }
253
254 }
255
256 exports.RangeRequestsPlugin = RangeRequestsPlugin;
257 exports.createPartialResponse = createPartialResponse;
258
259 return exports;
260
261}({}, workbox.core._private, workbox.core._private, workbox.core._private));
262