1 | this.workbox = this.workbox || {};
|
2 | this.workbox.rangeRequests = (function (exports, WorkboxError_mjs, assert_mjs, logger_mjs) {
|
3 | ;
|
4 |
|
5 | try {
|
6 | self['workbox:range-requests:4.1.1'] && _();
|
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 | * @param {Blob} blob A source blob.
|
18 | * @param {number|null} start The offset to use as the start of the
|
19 | * slice.
|
20 | * @param {number|null} 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_mjs.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 > blobSize || start < 0) {
|
39 | throw new WorkboxError_mjs.WorkboxError('range-not-satisfiable', {
|
40 | size: blobSize,
|
41 | end,
|
42 | start
|
43 | });
|
44 | }
|
45 |
|
46 | let effectiveStart;
|
47 | let effectiveEnd;
|
48 |
|
49 | if (start === null) {
|
50 | effectiveStart = blobSize - end;
|
51 | effectiveEnd = blobSize;
|
52 | } else if (end === null) {
|
53 | effectiveStart = start;
|
54 | effectiveEnd = blobSize;
|
55 | } else {
|
56 | effectiveStart = start; // Range values are inclusive, so add 1 to the value.
|
57 |
|
58 | effectiveEnd = end + 1;
|
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_mjs.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_mjs.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_mjs.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 === null || !(rangeParts[1] || rangeParts[2])) {
|
112 | throw new WorkboxError_mjs.WorkboxError('invalid-range-values', {
|
113 | normalizedRangeHeader
|
114 | });
|
115 | }
|
116 |
|
117 | return {
|
118 | start: rangeParts[1] === '' ? null : Number(rangeParts[1]),
|
119 | end: rangeParts[2] === '' ? null : 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 workbox.rangeRequests
|
147 | */
|
148 |
|
149 | async function createPartialResponse(request, originalResponse) {
|
150 | try {
|
151 | {
|
152 | assert_mjs.assert.isInstance(request, Request, {
|
153 | moduleName: 'workbox-range-requests',
|
154 | funcName: 'createPartialResponse',
|
155 | paramName: 'request'
|
156 | });
|
157 | assert_mjs.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_mjs.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', slicedBlobSize);
|
189 | slicedResponse.headers.set('Content-Range', `bytes ${effectiveBoundaries.start}-${effectiveBoundaries.end - 1}/` + originalBlob.size);
|
190 | return slicedResponse;
|
191 | } catch (error) {
|
192 | {
|
193 | logger_mjs.logger.warn(`Unable to construct a partial response; returning a ` + `416 Range Not Satisfiable response instead.`);
|
194 | logger_mjs.logger.groupCollapsed(`View details here.`);
|
195 | logger_mjs.logger.log(error);
|
196 | logger_mjs.logger.log(request);
|
197 | logger_mjs.logger.log(originalResponse);
|
198 | logger_mjs.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 workbox.rangeRequests
|
223 | */
|
224 |
|
225 | class Plugin {
|
226 | /**
|
227 | * @param {Object} options
|
228 | * @param {Request} options.request The original request, which may or may not
|
229 | * contain a Range: header.
|
230 | * @param {Response} options.cachedResponse The complete cached response.
|
231 | * @return {Promise<Response>} If request contains a 'Range' header, then a
|
232 | * new response with status 206 whose body is a subset of `cachedResponse` is
|
233 | * returned. Otherwise, `cachedResponse` is returned as-is.
|
234 | *
|
235 | * @private
|
236 | */
|
237 | async cachedResponseWillBeUsed({
|
238 | request,
|
239 | cachedResponse
|
240 | }) {
|
241 | // Only return a sliced response if there's something valid in the cache,
|
242 | // and there's a Range: header in the request.
|
243 | if (cachedResponse && request.headers.has('range')) {
|
244 | return await createPartialResponse(request, cachedResponse);
|
245 | } // If there was no Range: header, or if cachedResponse wasn't valid, just
|
246 | // pass it through as-is.
|
247 |
|
248 |
|
249 | return cachedResponse;
|
250 | }
|
251 |
|
252 | }
|
253 |
|
254 | /*
|
255 | Copyright 2018 Google LLC
|
256 |
|
257 | Use of this source code is governed by an MIT-style
|
258 | license that can be found in the LICENSE file or at
|
259 | https://opensource.org/licenses/MIT.
|
260 | */
|
261 |
|
262 | exports.createPartialResponse = createPartialResponse;
|
263 | exports.Plugin = Plugin;
|
264 |
|
265 | return exports;
|
266 |
|
267 | }({}, workbox.core._private, workbox.core._private, workbox.core._private));
|
268 |
|