workbox-range-requests.dev.js 9.1 KB

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