Compare commits

..
Author SHA1 Message Date
ohif-bot ad2f038817 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.11.0
 - @ohif/extension-vtk@1.11.17
 - @ohif/core@2.14.0
 - @ohif/ui@1.10.0
 - @ohif/viewer@4.10.0
2021-09-09 08:02:24 +00:00
Igor OctavianoandAlireza 8f2604509d feat: OHIF-1000: Study prefetcher (#2466)
* Enable study prefetcher and expose configs

* Fix progress for all images

* Enable study loading listener

* Add new default for displaying progress bar for series

* Use events instead of redux to improve performance

* Add documentation, resolve issues with different datasets

* CR Updates

* CR Updates

* CR Updates & Resolve issue when activating both stack and study prefetch

* Add upward and update display set logic

* Add new behavior to prefetching orders

* Rollback display set check and activate cache check

* Remove prefetch check

* Check if displayset has images

* Debounce check cache events

* Remove usued debounce flags

* Ignore prefetching same image

* Use new image instead of image rendered

* Continue prefetch after cache check

* Update prefetch logic after checking cache

* Remove log

* Update displayset count default to 3

* Update netlify config

Co-authored-by: Alireza <ar.sedghi@gmail.com>
2021-09-09 09:58:24 +02:00
ohif-bot ea0963d75f chore(release): publish [skip ci]
- @ohif/extension-dicom-rt@0.6.14
 - @ohif/extension-dicom-segmentation@0.6.11
 - @ohif/extension-vtk@1.11.16
 - @ohif/core@2.13.15
 - @ohif/ui@1.9.3
 - @ohif/viewer@4.9.24
2021-09-03 21:01:01 +00:00
Igor Octaviano 46d5718011 Merge pull request #2491 from OHIF/feat/ohif-2482
OHIF-2482: Add badge numbers for seg and rtstruct
2021-09-03 17:57:42 -03:00
Davide Punzo f8740e04f3 Merge branch 'master' into feat/ohif-2482 2021-09-02 16:08:37 +02:00
ohif-bot 669717d27b chore(release): publish [skip ci]
- @ohif/extension-vtk@1.11.15
 - @ohif/core@2.13.14
 - @ohif/viewer@4.9.23
2021-08-30 13:04:58 +00:00
Alireza 715dfc556a fix: MPR metadata parsing for spacings (#2517)
* fix: MPR metadata parsing for spacings

* fix: OHIF Metadata provider for validNumbers for imagePlaneModule
2021-08-30 09:01:46 -04:00
ohif-bot cafb7d3f7b chore(release): publish [skip ci]
- @ohif/viewer@4.9.22
2021-08-19 13:05:04 +00:00
matthiasg 5f92f6ec7f fix: Remove internet dependency for workbox (addressing #2109) (#2151)
* Ignoring *.js.map files as generated in the extensions directories for example

* Fix for #2109. Removed internet dependency to google workbox
2021-08-19 15:00:20 +02:00
Matthias SchmiederandMatthias Schmieder eea610dfe8 fix CVE-2019-5021 caused by nginx-apline (#2514)
Co-authored-by: Matthias Schmieder <matthias.schmieder@stryker.com>
2021-08-19 13:42:04 +02:00
Erik Ziegler e8733a9bd9 Update CITATION.cff (#2504) 2021-08-04 17:16:35 +02:00
Erik Ziegler d292931073 Create CITATION.cff (#2503) 2021-08-04 15:43:10 +02:00
igoroctaviano f6a0db753a Merge branch 'master' of github.com:OHIF/Viewers into feat/ohif-2482 2021-07-29 09:19:42 -03:00
igoroctaviano 8518e179ee Extract event 2021-07-29 09:18:31 -03:00
igoroctaviano 3f40cdced7 Add badge numbers for seg and rtstruct 2021-07-28 22:15:05 -03:00
ohif-bot 4cf17185da chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.10.10
 - @ohif/viewer@4.9.21
2021-07-26 11:44:06 +00:00
b8ae92c2d4 fix: changed new image handler conditional for 0 edge case (#2338)
Co-authored-by: Michael Andersen <Michael.Andersen@rmp.uhn.ca>
Co-authored-by: Erik Ziegler <erik.sweed@gmail.com>
2021-07-26 13:35:37 +02:00
Woonchan Cho bad67020cc chore(#2483) make client-side code debuggable right from the source code (#2484) 2021-07-26 13:34:55 +02:00
120 changed files with 12496 additions and 430 deletions

No files matched your search

+2
View File
@@ -9,6 +9,7 @@ src/version.js
junit.xml
coverage/
.docz/
*.js.map
# YALC (for Erik)
.yalc
@@ -34,3 +35,4 @@ screenshots/
# Locize settings
.locize
+15
View File
@@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "pwa-chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}"
}
]
}
+24
View File
@@ -0,0 +1,24 @@
cff-version: 1.2.0
message: "If you use this software, please cite it as below."
authors:
- family-names: "Ziegler"
given-names: "Erik"
orcid: "https://orcid.org/0000-0003-1857-8129"
- family-names: "Urban"
given-names: "Trinity"
- family-names: "Brown"
given-names: "Danny"
- family-names: "Petts"
given-names: "James"
- family-names: "Pieper"
given-names: "Steve D"
- family-names: "Lewis"
given-names: "Rob"
- family-names: "Hafey"
given-names: "Chris"
- family-names: "Harris"
given-names: "Gordon J"
title: "Open Health Imaging Foundation Viewer: An Extensible Open-Source Framework for Building Web-Based Imaging Applications to Support Cancer Research"
doi: 10.1200/CCI.19.00131
version: 1.0.0
url: "https://github.com/OHIF/Viewers"
+1 -1
View File
@@ -53,7 +53,7 @@ RUN yarn run build
# Stage 2: Bundle the built application into a Docker container
# which runs Nginx using Alpine Linux
FROM nginx:1.15.5-alpine
FROM nginx:1.21.1-alpine
RUN apk add --no-cache bash
RUN rm -rf /etc/nginx/conf.d
COPY .docker/Viewer-v2.x /etc/nginx/conf.d
+39 -3
View File
@@ -67,6 +67,33 @@ window.config = {
extensions: [],
showStudyList: true,
filterQueryParam: false,
/**
* Disable caching of servers configuration.
*
* There will be no effect if you update the servers property of this config
* while your application is running because this property is cached in local storage.
*/
disableServersCache: false,
/**
* OHIF's study prefetcher configuration.
*
* @param {boolean} enabled Whether to enable/disable OHIF's study prefetcher
* @param {('all'|'closest'|'downward'|'upward'|'topdown')} order Fetching order: all display sets, the closest ones, downward or top down fashion based on the currently selected display set
* @param {number} displaySetCount How much display sets should be prefetched at once (note: this attribute is ignored if order was set to 'all')
* @param {boolean} preventCache Prevent images to be cached in Cornerstone Tools's request pool manager
* @param {number} prefetchDisplaySetsTimeout Prefetch timeout
* @param {boolean} displayProgress Whether to display or not the progress bar in the display set
* @param {boolean} includeActiveDisplaySet Include or not the active display set while prefetching
*/
studyPrefetcher: {
enabled: true,
order: 'all',
displaySetCount: 1,
preventCache: false,
prefetchDisplaySetsTimeout: 300,
displayProgress: false,
includeActiveDisplaySet: true,
},
servers: {
dicomWeb: [
{
@@ -81,7 +108,14 @@ window.config = {
},
],
},
// Supported Keys: https://craig.is/killing/mice
/**
* Hotkey definitions.
* Supported Keys: https://craig.is/killing/mice
*
* @param {string} commandName
* @param {string} label
* @param {array} keys
*/
hotkeys: [
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
@@ -97,14 +131,16 @@ window.config = {
keys: ['v'],
},
],
/* Configuration passed to the bundled cornerstone extension
/**
* Configuration passed to the bundled cornerstone extension
*
* The cornerstone extension is currently tightly coupled to the platform.
* Until we're able to decouple it, this key will serve as a workaround to
* pass it configuration.
*
* @param {boolean} hideHandles Whether to show/hide annotation "handles"
*/
cornerstoneExtensionConfig: {
/* Whether to show/hide annotation "handles" */
hideHandles: true,
},
};
+37
View File
@@ -0,0 +1,37 @@
# Viewer: Study Prefetcher & Stack Prefetch
Prefetching the stack of images of the active viewport is enabled by default for
cornerstone viewports via cornerstone tools's stack prefetch functionality.
In order to have a more customizable alternative for prefetching, OHIF provides
a built-in study prefetcher functionality which allows the user to set the order
in which the display sets are prefetched, the granularity, the timeout and
whether or not display a progress bar in the display set (close to the thumbnail
image) in the study browser.
You can customize these options via the viewer's configuration:
```js
window.config = {
/**
* OHIF's study prefetcher configuration.
*
* @param {boolean} enabled Whether to enable/disable OHIF's study prefetcher
* @param {('all'|'closest'|'downward'|'upward'|'topdown')} order Fetching order: all display sets, the closest ones, downward or top down fashion based on the currently selected display set
* @param {number} displaySetCount How much display sets should be prefetched at once (note: this attribute is ignored if order was set to 'all')
* @param {boolean} preventCache Prevent images to be cached in Cornerstone Tools's request pool manager
* @param {number} prefetchDisplaySetsTimeout Prefetch timeout
* @param {boolean} displayProgress Whether to display or not the progress bar in the display set
* @param {boolean} includeActiveDisplaySet Include or not the active display set while prefetching
*/
studyPrefetcher: {
enabled: true,
order: 'all',
displaySetCount: 1,
preventCache: false,
prefetchDisplaySetsTimeout: 300,
displayProgress: false,
includeActiveDisplaySet: true,
},
};
```
+22
View File
@@ -3,6 +3,28 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.11.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.10.10...@ohif/extension-cornerstone@2.11.0) (2021-09-09)
### Features
* OHIF-1000: Study prefetcher ([#2466](https://github.com/OHIF/Viewers/issues/2466)) ([8f26045](https://github.com/OHIF/Viewers/commit/8f2604509d24092b721f1333289d838820d68cac))
## [2.10.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.10.9...@ohif/extension-cornerstone@2.10.10) (2021-07-26)
### Bug Fixes
* changed new image handler conditional for 0 edge case ([#2338](https://github.com/OHIF/Viewers/issues/2338)) ([b8ae92c](https://github.com/OHIF/Viewers/commit/b8ae92c2d4f733b3ca89826118029685650b396a))
## [2.10.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.10.8...@ohif/extension-cornerstone@2.10.9) (2021-06-02)
+23 -2
View File
@@ -85,12 +85,32 @@ Tools can be configured through extension configuration using the tools key:
...
```
## Stack prefetch configuration
Stack prefetch can be configured through extension configuration using the
stackPrefetch key:
```js
...
cornerstoneExtensionConfig: {
stackPrefetch: {
enabled: true,
maxImagesToPrefetch: Infinity,
preserveExistingPool: false,
maxSimultaneousRequests: 20,
},
},
...
```
## Annotate Tools Configuration
*We currently support one property for annotation tools.*
_We currently support one property for annotation tools._
### Hide handles
This extension configuration allows you to toggle on/off handle rendering for all annotate tools:
This extension configuration allows you to toggle on/off handle rendering for
all annotate tools:
```js
...
@@ -119,3 +139,4 @@ This extension configuration allows you to toggle on/off handle rendering for al
[cornerstone-tools]: https://github.com/cornerstonejs/cornerstoneTools
[cornerstone]: https://github.com/cornerstonejs/cornerstone
<!-- prettier-ignore-end -->
```
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone",
"version": "2.10.9",
"version": "2.11.0",
"description": "OHIF extension for Cornerstone",
"author": "OHIF",
"license": "MIT",
@@ -53,7 +53,11 @@ const mapStateToProps = (state, ownProps) => {
// Currently justing using escape hatch + commands
// activeTool: activeButton && activeButton.command,
...dataFromStore,
isStackPrefetchEnabled: isActive,
isStackPrefetchEnabled: ownProps.hasOwnProperty('isStackPrefetchEnabled')
? ownProps.isStackPrefetchEnabled
: ownProps.stackPrefetch
? ownProps.stackPrefetch.enabled
: isActive,
isPlaying,
frameRate,
//stack: viewportSpecificData.stack,
@@ -1,6 +1,6 @@
import React, { Component } from 'react';
import OHIFCornerstoneViewportOverlay from './components/OHIFCornerstoneViewportOverlay'
import OHIFCornerstoneViewportOverlay from './components/OHIFCornerstoneViewportOverlay';
import ConnectedCornerstoneViewport from './ConnectedCornerstoneViewport';
import OHIF from '@ohif/core';
import PropTypes from 'prop-types';
@@ -15,6 +15,7 @@ class OHIFCornerstoneViewport extends Component {
static defaultProps = {
customProps: {},
isStackPrefetchEnabled: true,
};
static propTypes = {
@@ -23,6 +24,8 @@ class OHIFCornerstoneViewport extends Component {
viewportIndex: PropTypes.number,
children: PropTypes.node,
customProps: PropTypes.object,
stackPrefetch: PropTypes.object,
isStackPrefetchEnabled: PropTypes.bool,
};
static id = 'OHIFCornerstoneViewport';
@@ -221,7 +224,7 @@ class OHIFCornerstoneViewport extends Component {
const { displaySet } = this.props.viewportData;
const { StudyInstanceUID } = displaySet;
if (currentImageIdIndex > 0) {
if (currentImageIdIndex >= 0) {
this.props.onNewImage({
StudyInstanceUID,
SOPInstanceUID: sopInstanceUid,
@@ -232,7 +235,12 @@ class OHIFCornerstoneViewport extends Component {
};
const warningsOverlay = props => {
return <OHIFCornerstoneViewportOverlay {...props} inconsistencyWarnings={inconsistencyWarnings} />
return (
<OHIFCornerstoneViewportOverlay
{...props}
inconsistencyWarnings={inconsistencyWarnings}
/>
);
};
return (
@@ -244,10 +252,11 @@ class OHIFCornerstoneViewport extends Component {
onNewImageDebounced={newImageHandler}
onNewImageDebounceTime={300}
viewportOverlayComponent={warningsOverlay}
stackPrefetch={this.props.stackPrefetch}
isStackPrefetchEnabled={this.props.isStackPrefetchEnabled}
// ~~ Connected (From REDUX)
// frameRate={frameRate}
// isPlaying={false}
// isStackPrefetchEnabled={true}
// onElementEnabled={() => {}}
// setViewportActive{() => {}}
{...this.props.customProps}
+11 -2
View File
@@ -36,7 +36,7 @@ export default {
preRegistration({ servicesManager, configuration = {} }) {
init({ servicesManager, configuration });
},
getViewportModule({ commandsManager }) {
getViewportModule({ commandsManager, appConfig }) {
const ExtendedOHIFCornerstoneViewport = props => {
/**
* TODO: This appears to be used to set the redux parameters for
@@ -48,8 +48,17 @@ export default {
jumpData.refreshViewports = false;
commandsManager.runCommand('jumpToImage', jumpData);
};
const { studyPrefetcher } = appConfig;
const isStackPrefetchEnabled =
studyPrefetcher && !studyPrefetcher.enabled;
return (
<OHIFCornerstoneViewport {...props} onNewImage={onNewImageHandler} />
<OHIFCornerstoneViewport
{...props}
onNewImage={onNewImageHandler}
isStackPrefetchEnabled={isStackPrefetchEnabled}
/>
);
};
+9 -2
View File
@@ -37,7 +37,14 @@ export default function init({ servicesManager, configuration }) {
}
};
const { csToolsConfig } = configuration;
const {
csToolsConfig,
stackPrefetch = {
maxImagesToPrefetch: Infinity,
preserveExistingPool: false,
maxSimultaneousRequests: 20,
},
} = configuration;
const metadataProvider = OHIF.cornerstone.metadataProvider;
cornerstone.metaData.addProvider(
@@ -52,7 +59,7 @@ export default function init({ servicesManager, configuration }) {
autoResizeViewports: false,
};
initCornerstoneTools(defaultCsToolsConfig);
initCornerstoneTools({ ...defaultCsToolsConfig, ...stackPrefetch });
const toolsGroupedByType = {
touch: [csTools.PanMultiTouchTool, csTools.ZoomTouchPinchTool],
@@ -37,8 +37,8 @@ export default function(configuration = {}) {
// Configure stack prefetch
cornerstoneTools.stackPrefetch.setConfiguration({
maxImagesToPrefetch: Infinity,
preserveExistingPool: false,
maxSimultaneousRequests: 20,
maxImagesToPrefetch: configuration.maxImagesToPrefetch,
preserveExistingPool: configuration.preserveExistingPool,
maxSimultaneousRequests: configuration.maxSimultaneousRequests,
});
}
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.6.14](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.13...@ohif/extension-dicom-rt@0.6.14) (2021-09-03)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.6.13](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.12...@ohif/extension-dicom-rt@0.6.13) (2021-06-02)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-rt",
"version": "0.6.13",
"version": "0.6.14",
"description": "OHIF extension for rendering DICOM RTSTRUCTs on top of cornerstone images.",
"author": "OHIF",
"license": "MIT",
+38 -1
View File
@@ -1,11 +1,12 @@
import React from 'react';
import { utils } from '@ohif/core';
import init from './init.js';
import sopClassHandlerModule from './OHIFDicomRTStructSopClassHandler';
import id from './id.js';
import RTPanel from './components/RTPanel/RTPanel';
import { version } from '../package.json';
import { utils } from '@ohif/core';
const { studyMetadataManager } = utils;
export default {
@@ -42,12 +43,48 @@ export default {
);
};
const RTPanelTabChangedEvent = 'rt-panel-tab-updated';
/**
* Trigger's an event to update the state of the panel's RoundedButtonGroup.
*
* This is required to avoid extension state
* coupling with the viewer's ToolbarRow component.
*
* @param {object} data
*/
const triggerRTPanelUpdatedEvent = data => {
const event = new CustomEvent(RTPanelTabChangedEvent, {
detail: data,
});
document.dispatchEvent(event);
};
const onRTStructsLoaded = ({ detail }) => {
const { rtStructDisplaySet, referencedDisplaySet } = detail;
const studyMetadata = studyMetadataManager.get(
rtStructDisplaySet.StudyInstanceUID
);
const referencedDisplaysets = studyMetadata.getDerivedDatasets({
referencedSeriesInstanceUID: referencedDisplaySet.SeriesInstanceUID,
Modality: 'RTSTRUCT',
});
triggerRTPanelUpdatedEvent({
badgeNumber: referencedDisplaysets.length,
target: 'rt-panel',
});
};
document.addEventListener('extensiondicomrtrtloaded', onRTStructsLoaded);
return {
menuOptions: [
{
icon: 'list',
label: 'RTSTRUCT',
target: 'rt-panel',
stateEvent: RTPanelTabChangedEvent,
isDisabled: (studies, activeViewport) => {
if (!studies) {
return true;
+15 -7
View File
@@ -90,11 +90,11 @@ export default async function loadRTStruct(
const sopInstanceUID = ContourImageSequence
? ContourImageSequence.ReferencedSOPInstanceUID
: _getClosestSOPInstanceUID(
ContourData,
ContourGeometricType,
NumberOfContourPoints,
imageIdSopInstanceUidPairs
);
ContourData,
ContourGeometricType,
NumberOfContourPoints,
imageIdSopInstanceUidPairs
);
const imageId = _getImageId(imageIdSopInstanceUidPairs, sopInstanceUID);
if (!imageId) {
@@ -171,7 +171,13 @@ export default async function loadRTStruct(
* allows us to easily watch the module or the rtstruct loading process in any other component
* without subscribing to external events.
*/
const event = new CustomEvent('extensiondicomrtrtloaded');
const event = new CustomEvent('extensiondicomrtrtloaded', {
detail: {
rtStructDisplaySet,
referencedDisplaySet,
studies,
},
});
document.dispatchEvent(event);
}
@@ -299,7 +305,9 @@ const _getImageId = (imageIdSopInstanceUidPairs, sopInstanceUID) => {
imageIdSopInstanceUidPairsEntry.sopInstanceUID === sopInstanceUID
);
return imageIdSopInstanceUidPairsEntry ? imageIdSopInstanceUidPairsEntry.imageId : null;
return imageIdSopInstanceUidPairsEntry
? imageIdSopInstanceUidPairsEntry.imageId
: null;
};
function _getImageIdSopInstanceUidPairsForDisplaySet(
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.6.11](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.6.10...@ohif/extension-dicom-segmentation@0.6.11) (2021-09-03)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.6.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.6.9...@ohif/extension-dicom-segmentation@0.6.10) (2021-06-02)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-segmentation",
"version": "0.6.10",
"version": "0.6.11",
"description": "OHIF extension for viewing segmentations in the 2D MPR view",
"author": "OHIF",
"license": "MIT",
@@ -224,16 +224,20 @@ const SegmentationPanel = ({
)
);
};
}, [activeIndex, viewports]);
}, [
activeIndex,
updateSegmentationComboBox,
viewports,
]);
const updateSegmentationComboBox = (e) => {
const updateSegmentationComboBox = e => {
const index = e.detail.activatedLabelmapIndex;
if (index !== -1) {
setState(state => ({ ...state, selectedSegmentation: index }));
} else {
cleanSegmentationComboBox();
}
}
};
const cleanSegmentationComboBox = () => {
setState(state => ({
@@ -245,7 +249,7 @@ const SegmentationPanel = ({
isDisabled: true,
selectedSegmentation: -1,
}));
}
};
const refreshSegmentations = () => {
const activeViewport = getActiveViewport();
@@ -309,7 +313,8 @@ const SegmentationPanel = ({
);
const filteredReferencedSegDisplaysets = referencedSegDisplaysets.filter(
(segDisplay => segDisplay.loadError !== true));
segDisplay => segDisplay.loadError !== true
);
return filteredReferencedSegDisplaysets.map((displaySet, index) => {
const {
@@ -1,9 +1,12 @@
import React from 'react';
import OHIF from '@ohif/core';
import init from './init.js';
import toolbarModule from './toolbarModule.js';
import getSopClassHandlerModule from './getOHIFDicomSegSopClassHandler.js';
import SegmentationPanel from './components/SegmentationPanel/SegmentationPanel.js';
import { version } from '../package.json';
const { studyMetadataManager } = OHIF.utils;
export default {
/**
@@ -79,12 +82,50 @@ export default {
);
};
const SegmentationPanelTabUpdatedEvent = 'segmentation-panel-tab-updated';
/**
* Trigger's an event to update the state of the panel's RoundedButtonGroup.
*
* This is required to avoid extension state
* coupling with the viewer's ToolbarRow component.
*
* @param {object} data
*/
const triggerSegmentationPanelTabUpdatedEvent = data => {
const event = new CustomEvent(SegmentationPanelTabUpdatedEvent, {
detail: data,
});
document.dispatchEvent(event);
};
const onSegmentationsLoaded = ({ detail }) => {
const { segDisplaySet, segMetadata } = detail;
const studyMetadata = studyMetadataManager.get(
segDisplaySet.StudyInstanceUID
);
const referencedDisplaysets = studyMetadata.getDerivedDatasets({
referencedSeriesInstanceUID: segMetadata.seriesInstanceUid,
Modality: 'SEG',
});
triggerSegmentationPanelTabUpdatedEvent({
badgeNumber: referencedDisplaysets.length,
target: 'segmentation-panel',
});
};
document.addEventListener(
'extensiondicomsegmentationsegloaded',
onSegmentationsLoaded
);
return {
menuOptions: [
{
icon: 'list',
label: 'Segmentations',
target: 'segmentation-panel',
stateEvent: SegmentationPanelTabUpdatedEvent,
isDisabled: studies => {
if (!studies) {
return true;
@@ -59,7 +59,16 @@ export default async function loadSegmentation(
* without subscribing to external events.
*/
console.log('Segmentation loaded.');
const event = new CustomEvent('extensiondicomsegmentationsegloaded');
const event = new CustomEvent('extensiondicomsegmentationsegloaded', {
detail: {
imageIds,
segDisplaySet,
labelmapBuffer,
segMetadata,
segmentsOnFrame,
labelmapSegments,
},
});
document.dispatchEvent(event);
return labelmapIndex;
+27
View File
@@ -3,6 +3,33 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.11.17](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.16...@ohif/extension-vtk@1.11.17) (2021-09-09)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.11.16](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.15...@ohif/extension-vtk@1.11.16) (2021-09-03)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.11.15](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.14...@ohif/extension-vtk@1.11.15) (2021-08-30)
### Bug Fixes
* MPR metadata parsing for spacings ([#2517](https://github.com/OHIF/Viewers/issues/2517)) ([715dfc5](https://github.com/OHIF/Viewers/commit/715dfc556a3883de31d74012dcc99e431d91f57d))
## [1.11.14](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.13...@ohif/extension-vtk@1.11.14) (2021-06-03)
**Note:** Version bump only for package @ohif/extension-vtk
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-vtk",
"version": "1.11.14",
"version": "1.11.17",
"description": "OHIF extension for VTK.js",
"author": "OHIF",
"license": "MIT",
@@ -53,8 +53,8 @@
"react-vtkjs-viewport": "^0.14.2"
},
"devDependencies": {
"@ohif/core": "^2.13.13",
"@ohif/ui": "^1.9.2",
"@ohif/core": "^2.14.0",
"@ohif/ui": "^1.10.0",
"cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0",
"dicom-parser": "^1.8.3",
+3 -3
View File
@@ -72,7 +72,7 @@ class OHIFVTKViewport extends Component {
};
static defaultProps = {
onScroll: () => {},
onScroll: () => { },
};
static id = 'OHIFVTKViewport';
@@ -271,7 +271,7 @@ class OHIFVTKViewport extends Component {
volumeMapper.setSampleDistance(sampleDistance);
// Be generous to surpress warnings, as the logging really hurts performance.
// Be generous to suppress warnings, as the logging really hurts performance.
// TODO: maybe we should auto adjust samples to 1000.
volumeMapper.setMaximumSamplesPerRay(4000);
@@ -398,7 +398,7 @@ class OHIFVTKViewport extends Component {
if (
displaySet.displaySetInstanceUID !==
prevDisplaySet.displaySetInstanceUID ||
prevDisplaySet.displaySetInstanceUID ||
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
displaySet.frameIndex !== prevDisplaySet.frameIndex
) {
+30
View File
@@ -3,6 +3,36 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.14.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.13.15...@ohif/core@2.14.0) (2021-09-09)
### Features
* OHIF-1000: Study prefetcher ([#2466](https://github.com/OHIF/Viewers/issues/2466)) ([8f26045](https://github.com/OHIF/Viewers/commit/8f2604509d24092b721f1333289d838820d68cac))
## [2.13.15](https://github.com/OHIF/Viewers/compare/@ohif/core@2.13.14...@ohif/core@2.13.15) (2021-09-03)
**Note:** Version bump only for package @ohif/core
## [2.13.14](https://github.com/OHIF/Viewers/compare/@ohif/core@2.13.13...@ohif/core@2.13.14) (2021-08-30)
### Bug Fixes
* MPR metadata parsing for spacings ([#2517](https://github.com/OHIF/Viewers/issues/2517)) ([715dfc5](https://github.com/OHIF/Viewers/commit/715dfc556a3883de31d74012dcc99e431d91f57d))
## [2.13.13](https://github.com/OHIF/Viewers/compare/@ohif/core@2.13.12...@ohif/core@2.13.13) (2021-06-03)
**Note:** Version bump only for package @ohif/core
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/core",
"version": "2.13.13",
"version": "2.14.0",
"description": "Generic business logic for web-based medical imaging applications",
"author": "OHIF Core Team",
"license": "MIT",
@@ -231,26 +231,26 @@ class MetadataProvider {
let columnCosines;
if (PixelSpacing) {
rowPixelSpacing = PixelSpacing[0];
columnPixelSpacing = PixelSpacing[1];
rowPixelSpacing = validNumber(PixelSpacing[0]);
columnPixelSpacing = validNumber(PixelSpacing[1]);
}
if (ImageOrientationPatient) {
rowCosines = ImageOrientationPatient.slice(0, 3);
columnCosines = ImageOrientationPatient.slice(3, 6);
rowCosines = validNumber(ImageOrientationPatient.slice(0, 3));
columnCosines = validNumber(ImageOrientationPatient.slice(3, 6));
}
metadata = {
frameOfReferenceUID: instance.FrameOfReferenceUID,
rows: instance.Rows,
columns: instance.Columns,
imageOrientationPatient: ImageOrientationPatient,
imageOrientationPatient: validNumber(ImageOrientationPatient),
rowCosines,
columnCosines,
imagePositionPatient: instance.ImagePositionPatient,
sliceThickness: instance.SliceThickness,
sliceLocation: instance.SliceLocation,
pixelSpacing: PixelSpacing,
imagePositionPatient: validNumber(instance.ImagePositionPatient),
sliceThickness: validNumber(instance.SliceThickness),
sliceLocation: validNumber(instance.SliceLocation),
pixelSpacing: validNumber(PixelSpacing),
rowPixelSpacing,
columnPixelSpacing,
};
+118 -31
View File
@@ -1,16 +1,14 @@
import cornerstone from 'cornerstone-core';
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
import {
clearStudyLoadingProgress,
setStudyLoadingProgress,
} from '../redux/actions';
import debounce from 'lodash.debounce';
import StackManager from '../utils/StackManager';
import { StudyPrefetcher } from './StudyPrefetcher';
class BaseLoadingListener {
constructor(stack, options = {}) {
this.id = BaseLoadingListener.getNewId();
this.stack = stack;
this.startListening();
this.statsItemsLimit = options.statsItemsLimit || 2;
this.stats = {
items: [],
@@ -60,6 +58,9 @@ class BaseLoadingListener {
}
_getProgressId() {
/**
* TODO: The id key should be configurable.
*/
const displaySetInstanceUID = this.stack.displaySetInstanceUID;
return 'StackProgress:' + displaySetInstanceUID;
}
@@ -96,11 +97,18 @@ class BaseLoadingListener {
class DICOMFileLoadingListener extends BaseLoadingListener {
constructor(stack, options) {
super(stack, options);
this.imageLoadProgressEventHandler = this._imageLoadProgressEventHandle.bind(
this
);
this._dataSetUrl = this._getDataSetUrl(stack);
this._lastLoaded = 0;
// Check how many instances has already been download (cached)
this._checkCachedData();
this.startListening();
}
_checkCachedData() {
@@ -127,10 +135,6 @@ class DICOMFileLoadingListener extends BaseLoadingListener {
startListening() {
const imageLoadProgressEventName = this._getImageLoadProgressEventName();
this.imageLoadProgressEventHandler = this._imageLoadProgressEventHandle.bind(
this
);
this.stopListening();
cornerstone.events.addEventListener(
@@ -200,17 +204,57 @@ class DICOMFileLoadingListener extends BaseLoadingListener {
}
}
const StudyLoadingListenerEvents = {
OnProgress: 'StudyLoadingListenerEvents.OnProgress',
};
function promiseState(promise, callback) {
// Symbols and RegExps are never content-equal
var uniqueValue = window['Symbol'] ? Symbol('unique') : /unique/;
function notifyPendingOrResolved(value) {
if (value === uniqueValue) {
return callback('pending');
} else {
return callback('fulfilled');
}
}
function notifyRejected(reason) {
return callback('rejected');
}
var race = [promise, Promise.resolve(uniqueValue)];
Promise.race(race).then(notifyPendingOrResolved, notifyRejected);
}
class StackLoadingListener extends BaseLoadingListener {
constructor(stack, options = {}) {
options.statsItemsLimit = 20;
super(stack, options);
this.imageLoadedEventHandler = this._imageLoadedEventHandler.bind(this);
this.imageCachePromiseRemovedEventHandler = this._imageCachePromiseRemovedEventHandler.bind(
this
);
this.imageDataMap = this._convertImageIdsArrayToMap(stack.imageIds);
this.framesStatus = this._createArray(stack.imageIds.length, false);
this.loadedCount = 0;
// Check how many instances has already been download (cached)
this._checkCachedData();
this._debouncedSetProgressData = debounce((...args) => {
this._setProgressData(...args);
/** After checking cache, continue prefetch */
const studyPrefetcher = StudyPrefetcher.getInstance();
studyPrefetcher.prefetch(studyPrefetcher.getElement());
}, 300);
const debounced = true;
this._checkCachedData(debounced);
this.startListening();
}
_convertImageIdsArrayToMap(imageIds) {
@@ -238,18 +282,37 @@ class StackLoadingListener extends BaseLoadingListener {
return array;
}
_checkCachedData() {
// const imageIds = this.stack.imageIds;
// TODO: No way to check status of Promise.
/*for(let i = 0; i < imageIds.length; i++) {
const imageId = imageIds[i];
/**
* Check if image id is cached in cornerstone.
*
* @param {string} imageId
* @returns
*/
isImageCached(imageId) {
const image = cornerstone.imageCache.imageCache[imageId];
return image && image.sizeInBytes;
}
const imagePromise = cornerstone.imageCache.getImageLoadObject(imageId).promise;
_checkCachedData(debounced = false) {
const imageIds = this.stack.imageIds;
if (imagePromise && (imagePromise.state() === 'resolved')) {
this._updateFrameStatus(imageId, true);
}
}*/
for (let i = 0; i < imageIds.length; i++) {
const imageId = imageIds[i];
const imageObject = cornerstone.imageCache.getImageLoadObject(imageId);
if (this.isImageCached(imageId)) {
this._updateFrameStatus(imageId, true, debounced);
}
if (imageObject && imageObject.promise) {
promiseState(imageObject.promise, state => {
if (state === 'fulfilled') {
this._updateFrameStatus(imageId, true, debounced);
}
});
}
}
}
_getImageLoadedEventName() {
@@ -272,11 +335,6 @@ class StackLoadingListener extends BaseLoadingListener {
const imageLoadedEventName = this._getImageLoadedEventName();
const imageCachePromiseRemovedEventName = this._getImageCachePromiseRemoveEventName();
this.imageLoadedEventHandler = this._imageLoadedEventHandler.bind(this);
this.imageCachePromiseRemovedEventHandler = this._imageCachePromiseRemovedEventHandler.bind(
this
);
this.stopListening();
cornerstone.events.addEventListener(
@@ -303,7 +361,7 @@ class StackLoadingListener extends BaseLoadingListener {
);
}
_updateFrameStatus(imageId, loaded) {
_updateFrameStatus(imageId, loaded, debounced) {
const imageData = this.imageDataMap.get(imageId);
if (!imageData || imageData.loaded === loaded) {
@@ -318,7 +376,7 @@ class StackLoadingListener extends BaseLoadingListener {
imageData.loaded = loaded;
this.framesStatus[imageData.index] = loaded;
this.loadedCount += loaded ? 1 : -1;
this._updateProgress();
this._updateProgress(debounced);
}
_setProgressData(progressId, progressData) {
@@ -339,7 +397,7 @@ class StackLoadingListener extends BaseLoadingListener {
);
}
_updateProgress() {
_updateProgress(debounced) {
const totalFramesCount = this.stack.imageIds.length;
const loadedFramesCount = this.loadedCount;
const loadingFramesCount = totalFramesCount - loadedFramesCount;
@@ -357,6 +415,11 @@ class StackLoadingListener extends BaseLoadingListener {
framesStatus: this.framesStatus,
};
if (debounced) {
this._debouncedSetProgressData(progressId, progressData);
return;
}
this._setProgressData(progressId, progressData);
}
@@ -371,11 +434,13 @@ class StackLoadingListener extends BaseLoadingListener {
}
progressBar += ']';
log.info(`${displaySetInstanceUID}: ${progressBar}`);
console.info(`${displaySetInstanceUID}: ${progressBar}`);
}
}
class StudyLoadingListener {
static events = StudyLoadingListenerEvents;
constructor(options) {
this.listeners = {};
this.options = options;
@@ -454,14 +519,36 @@ class StudyLoadingListener {
_getSchema(stack) {
const imageId = stack.imageIds[0];
if (!imageId) {
return;
}
const colonIndex = imageId.indexOf(':');
return imageId.substring(0, colonIndex);
}
// Singleton
static getInstance(options) {
/**
* TODO: Use a different alternative without the use of events.
*/
const DEFAULT_OPTIONS = {
_setProgressData: (progressId, progressData) => {
const event = new CustomEvent(StudyLoadingListenerEvents.OnProgress, {
detail: { progressId, progressData },
});
document.dispatchEvent(event);
},
_clearProgressById: progressId => {
const event = new CustomEvent(StudyLoadingListenerEvents.OnProgress, {
detail: { progressId, percentComplete: 0 },
});
document.dispatchEvent(event);
},
};
if (!StudyLoadingListener._instance) {
StudyLoadingListener._instance = new StudyLoadingListener(options);
StudyLoadingListener._instance = new StudyLoadingListener(
options || DEFAULT_OPTIONS
);
}
return StudyLoadingListener._instance;
+310 -47
View File
@@ -1,14 +1,28 @@
import log from '../log.js';
import OHIFError from './OHIFError';
import cornerstone from 'cornerstone-core';
import cornerstoneTools from 'cornerstone-tools';
import getImageId from '../utils/getImageId.js';
const noop = () => {};
export class StudyPrefetcher {
constructor(studies) {
options = {
order: 'closest',
displaySetCount: 1,
onImageCached: noop,
requestType: 'prefetch',
preventCache: false,
prefetchDisplaySetsTimeout: 300,
includeActiveDisplaySet: false,
};
constructor(studies, options) {
this.studies = studies || [];
this.prefetchDisplaySetsTimeout = 300;
this.lastActiveViewportElement = null;
if (options) {
this.options = { ...this.options, ...options };
this.options.requestType = 'prefetch';
}
cornerstone.events.addEventListener(
'cornerstoneimagecachefull.StudyPrefetcher',
@@ -16,6 +30,9 @@ export class StudyPrefetcher {
);
}
/**
* Remove previously set event listeners and stop prefetching.
*/
destroy() {
this.stopPrefetching();
cornerstone.events.removeEventListener(
@@ -24,68 +41,117 @@ export class StudyPrefetcher {
);
}
static getInstance() {
/**
* Get StudyPrefetcher singleton instance.
*
* @param {array} studies
* @param {object} options
* @returns
*/
static getInstance(studies = [], options) {
if (!StudyPrefetcher.instance) {
StudyPrefetcher.instance = new StudyPrefetcher([]);
StudyPrefetcher.instance = new StudyPrefetcher(studies, options);
}
if (options) {
this.options = { ...this.options, ...options };
this.options.requestType = 'prefetch';
}
return StudyPrefetcher.instance;
}
/**
* OHIF study metadata instances.
*
* @param {array} studies
*/
setStudies(studies) {
this.stopPrefetching();
this.studies = studies;
}
prefetch() {
/**
* Get previously prefetched element.
*
* @returns {HTMLElement} Previously prefetched element.
*/
getElement() {
return this.element;
}
/**
* Prefetch related display sets based on cornerstone viewport element
* with previously set options.
*
* @param {*} element
* @param {string} displaySetInstanceUID the display set instance uid
* @returns
*/
prefetch(element, displaySetInstanceUID) {
if (!this.studies || !this.studies.length) {
return;
}
this.element = element;
this.enabledElement = cornerstone.getEnabledElement(element);
this.stopPrefetching();
this.prefetchDisplaySets();
this.prefetchDisplaySets(displaySetInstanceUID);
}
/**
* Stop prefetching images.
*/
stopPrefetching() {
cornerstoneTools.requestPoolManager.clearRequestStack('prefetch');
}
prefetchDisplaySetsAsync(timeout) {
timeout = timeout || this.prefetchDisplaySetsTimeout;
/**
* Prefetch display sets async.
*
* @param {HTMLElement} element cornerstone viewport element
* @param {number} timeout
*/
prefetchDisplaySetsAsync(element, timeout) {
this.enabledElement = cornerstone.getEnabledElement(element);
timeout = timeout || this.options.prefetchDisplaySetsTimeout;
clearTimeout(this.prefetchDisplaySetsHandler);
this.prefetchDisplaySetsHandler = setTimeout(() => {
this.prefetchDisplaySets();
this.prefetchDisplaySets(element);
}, timeout);
}
prefetchDisplaySets() {
// TODO: Allow passing in config
let config = {
order: 'closest',
displaySetCount: 1,
};
const displaySetsToPrefetch = this.getDisplaySetsToPrefetch(config);
/**
* Extract all image ids from all display sets to be fetched and
* call method to add images to request pool manager.
*
* @param {string} displaySetInstanceUID the display set instance uid
*/
prefetchDisplaySets(displaySetInstanceUID) {
const displaySetsToPrefetch = this.getDisplaySetsToPrefetch(
displaySetInstanceUID
);
const imageIds = this.getImageIdsFromDisplaySets(displaySetsToPrefetch);
this.prefetchImageIds(imageIds);
}
/**
* Add image ids to request pool manager.
*
* @param {array} imageIds
*/
prefetchImageIds(imageIds) {
const nonCachedImageIds = this.filterCachedImageIds(imageIds);
const requestPoolManager = cornerstoneTools.requestPoolManager;
const requestType = 'prefetch';
const preventCache = false;
const noop = () => {};
nonCachedImageIds.forEach(imageId => {
requestPoolManager.addRequest(
{},
imageId,
requestType,
preventCache,
noop,
this.options.requestType,
this.options.preventCache,
() => {},
noop
);
});
@@ -93,26 +159,45 @@ export class StudyPrefetcher {
requestPoolManager.startGrabbing();
}
/**
* Get study by cornerstone image instance.
*
* @param {object} image
* @returns
*/
getStudy(image) {
const StudyInstanceUID = cornerstone.metaData.get(
'StudyInstanceUID',
image.imageId
);
return OHIF.viewer.Studies.find(
study => study.StudyInstanceUID === StudyInstanceUID
const studies = this.studies;
return studies.find(
study => study.getData().StudyInstanceUID === StudyInstanceUID
);
}
/**
* Get study series by cornerstone image instance.
*
* @param {object} study OHIF study instance
* @param {object} image cornerstone image instance object
* @returns
*/
getSeries(study, image) {
const SeriesInstanceUID = cornerstone.metaData.get(
'SeriesInstanceUID',
image.imageId
);
const studyMetadata = OHIF.viewerbase.getStudyMetadata(study);
return studyMetadata.getSeriesByUID(SeriesInstanceUID);
return study.getSeriesByUID(SeriesInstanceUID);
}
/**
* Get sop instance by cornerstone image instance.
*
* @param {array} series
* @param {object} image
* @returns
*/
getInstance(series, image) {
const instanceMetadata = cornerstone.metaData.get(
'instance',
@@ -121,7 +206,33 @@ export class StudyPrefetcher {
return series.getInstanceByUID(instanceMetadata.SOPInstanceUID);
}
getActiveDisplaySet(displaySets, instance) {
/**
* Returns the display set with given uid.
*
* @param {string} displaySetInstanceUID the display set instance uid
* @returns {object} displaySet
*/
getDisplaySetByUID(displaySetInstanceUID) {
let displaySet;
this.studies.forEach(study => {
const ds = study.displaySets.find(
ds => ds.displaySetInstanceUID === displaySetInstanceUID
);
if (ds) {
displaySet = ds;
}
});
return displaySet;
}
/**
* Get display set by SOPInstanceUID.
*
* @param {array} displaySets
* @param {object} instance
* @returns
*/
getDisplaySetBySOPInstanceUID(displaySets, instance) {
return displaySets.find(displaySet => {
return displaySet.images.some(displaySetImage => {
return displaySetImage.SOPInstanceUID === instance.SOPInstanceUID;
@@ -129,25 +240,48 @@ export class StudyPrefetcher {
});
}
getDisplaySetsToPrefetch(config) {
/**
* Get active viewport image based on cornerstone viewport element.
* @returns
*/
getActiveViewportImage() {
if (!this.enabledElement) {
return;
}
return this.enabledElement.image;
}
/**
* Prefetch display sets based on cornerstone viewport element image.
*
* @param {string} displaySetInstanceUID the display set instance uid
* @returns {array} displaySets
*/
getDisplaySetsToPrefetch(displaySetInstanceUID) {
const image = this.getActiveViewportImage();
if (!image || !config || !config.displaySetCount) {
if (!image) {
return [];
}
/*const study = this.getStudy(image);
const study = this.getStudy(image);
const series = this.getSeries(study, image);
const instance = this.getInstance(series, image);*/
const instance = this.getInstance(series, image);
const displaySets = study.displaySets;
const activeDisplaySet = null; //this.getActiveDisplaySet(displaySets, instance);
const activeDisplaySet = displaySetInstanceUID
? this.getDisplaySetByUID(displaySetInstanceUID)
: this.getDisplaySetBySOPInstanceUID(displaySets, instance);
const prefetchMethodMap = {
topdown: 'getFirstDisplaySets',
downward: 'getNextDisplaySets',
upward: 'getPreviousDisplaySets',
closest: 'getClosestDisplaySets',
all: 'getAllDisplaySets',
};
const prefetchOrder = config.order;
const prefetchOrder = this.options.order;
const methodName = prefetchMethodMap[prefetchOrder];
const getDisplaySets = this[methodName];
@@ -163,18 +297,59 @@ export class StudyPrefetcher {
this,
displaySets,
activeDisplaySet,
config.displaySetCount
this.options.displaySetCount,
this.options.includeActiveDisplaySet
);
}
getFirstDisplaySets(displaySets, activeDisplaySet, displaySetCount) {
/**
* Get all display sets.
*
* @param {array} displaySets
* @param {object} activeDisplaySet
* @param {number} displaySetCount
* @param {boolean} includeActiveDisplaySet
* @returns
*/
getAllDisplaySets(
displaySets,
activeDisplaySet,
displaySetCount,
includeActiveDisplaySet
) {
const length = displaySets.length;
const selectedDisplaySets = [];
for (let i = 0; i < length; i++) {
const displaySet = displaySets[i];
selectedDisplaySets.push(displaySet);
}
return selectedDisplaySets;
}
/**
* Get all display sets in order after the active display set.
*
* @param {array} displaySets
* @param {object} activeDisplaySet
* @param {number} displaySetCount
* @param {boolean} includeActiveDisplaySet
* @returns
*/
getFirstDisplaySets(
displaySets,
activeDisplaySet,
displaySetCount,
includeActiveDisplaySet
) {
const length = displaySets.length;
const selectedDisplaySets = [];
for (let i = 0; i < length && displaySetCount; i++) {
const displaySet = displaySets[i];
if (displaySet !== activeDisplaySet) {
if (includeActiveDisplaySet || displaySet !== activeDisplaySet) {
selectedDisplaySets.push(displaySet);
displaySetCount--;
}
@@ -183,21 +358,78 @@ export class StudyPrefetcher {
return selectedDisplaySets;
}
getNextDisplaySets(displaySets, activeDisplaySet, displaySetCount) {
/**
* Get all display sets before the active display set.
*
* @param {array} displaySets
* @param {object} activeDisplaySet
* @param {number} displaySetCount
* @param {boolean} includeActiveDisplaySet
* @returns
*/
getPreviousDisplaySets(
displaySets,
activeDisplaySet,
displaySetCount,
includeActiveDisplaySet
) {
const activeDisplaySetIndex = displaySets.indexOf(activeDisplaySet);
const begin = activeDisplaySetIndex + 1;
const end = Math.min(begin + displaySetCount, displaySets.length);
const end = includeActiveDisplaySet
? activeDisplaySetIndex + 1
: activeDisplaySetIndex;
const previousDisplaySets = displaySets.slice(0, end);
return previousDisplaySets.reverse().slice(0, displaySetCount);
}
/**
* Get all display sets after the active display set.
*
* @param {array} displaySets
* @param {object} activeDisplaySet
* @param {number} displaySetCount
* @param {boolean} includeActiveDisplaySet
* @returns
*/
getNextDisplaySets(
displaySets,
activeDisplaySet,
displaySetCount,
includeActiveDisplaySet
) {
const activeDisplaySetIndex = displaySets.indexOf(activeDisplaySet);
const begin = includeActiveDisplaySet
? activeDisplaySetIndex
: activeDisplaySetIndex + 1;
const end = Math.min(begin + displaySetCount, displaySets.length);
return displaySets.slice(begin, end);
}
getClosestDisplaySets(displaySets, activeDisplaySet, displaySetCount) {
/**
* Get all display set closest to the active display set.
*
* @param {array} displaySets
* @param {object} activeDisplaySet
* @param {number} displaySetCount
* @param {boolean} includeActiveDisplaySet
* @returns
*/
getClosestDisplaySets(
displaySets,
activeDisplaySet,
displaySetCount,
includeActiveDisplaySet
) {
const activeDisplaySetIndex = displaySets.indexOf(activeDisplaySet);
const length = displaySets.length;
const selectedDisplaySets = [];
let left = activeDisplaySetIndex - 1;
let right = activeDisplaySetIndex + 1;
if (includeActiveDisplaySet) {
selectedDisplaySets.push(displaySets[activeDisplaySetIndex]);
displaySetCount--;
}
while ((left >= 0 || right < length) && displaySetCount) {
if (left >= 0) {
selectedDisplaySets.push(displaySets[left]);
@@ -215,6 +447,12 @@ export class StudyPrefetcher {
return selectedDisplaySets;
}
/**
* Get all image ids from display sets.
*
* @param {array} displaySets
* @returns {array} image ids
*/
getImageIdsFromDisplaySets(displaySets) {
let imageIds = [];
@@ -225,9 +463,19 @@ export class StudyPrefetcher {
return imageIds;
}
/**
* Get all image ids from a given display set.
*
* @param {array} displaySet
* @returns
*/
getImageIdsFromDisplaySet(displaySet) {
const imageIds = [];
if (!displaySet.images || displaySet.images.length < 1) {
return [];
}
// TODO: This duplicates work done by the stack manager
displaySet.images.forEach(image => {
const numFrames = image.numFrames;
@@ -245,15 +493,30 @@ export class StudyPrefetcher {
return imageIds;
}
/**
* Filter cached image ids from a set of image ids.
*
* @param {array} imageIds
* @returns {array} images not cached
*/
filterCachedImageIds(imageIds) {
return imageIds.filter(imageId => !this.isImageCached(imageId));
}
/**
* Check if image id is cached in cornerstone.
*
* @param {string} imageId
* @returns
*/
isImageCached(imageId) {
const image = cornerstone.imageCache.imageCache[imageId];
return image && image.sizeInBytes;
}
/**
* Warns that cache is full and stops prefetching.
*/
cacheFullHandler = () => {
log.warn('Cache full');
this.stopPrefetching();
@@ -497,6 +497,12 @@ class StudyMetadata extends Metadata {
}
}
if (this._displaySets.some(ds =>
ds.displaySetInstanceUID === displaySet.displaySetInstanceUID)
) {
return;
}
this._displaySets.splice(insertIndex, 0, displaySet);
this.displaySets = this._displaySets;
}
-13
View File
@@ -78,17 +78,6 @@ export const setActiveViewportSpecificData = viewportSpecificData => ({
/**
* NOT-VIEWPORT
*/
export const setStudyLoadingProgress = (progressId, progressData) => ({
type: 'SET_STUDY_LOADING_PROGRESS',
progressId,
progressData,
});
export const clearStudyLoadingProgress = progressId => ({
type: 'CLEAR_STUDY_LOADING_PROGRESS',
progressId,
});
export const setUserPreferences = state => ({
type: SET_USER_PREFERENCES,
state,
@@ -134,8 +123,6 @@ const actions = {
/**
* NOT-VIEWPORT
*/
setStudyLoadingProgress,
clearStudyLoadingProgress,
setUserPreferences,
setExtensionData,
setTimepoints,
-2
View File
@@ -11,8 +11,6 @@ describe('actions', () => {
'setLayout',
'clearViewportSpecificData',
'setActiveViewportSpecificData',
'setStudyLoadingProgress',
'clearStudyLoadingProgress',
'setUserPreferences',
'setExtensionData',
'setTimepoints',
@@ -1,5 +1,4 @@
import extensions from './extensions';
import loading from './loading';
import preferences from './preferences';
import servers from './servers';
import studies from './studies';
@@ -8,7 +7,6 @@ import viewports from './viewports';
const reducers = {
extensions,
loading,
preferences,
servers,
studies,
@@ -1,33 +0,0 @@
import cloneDeep from 'lodash.clonedeep';
const defaultState = {
progress: {},
lastUpdated: null,
};
const loading = (state = defaultState, action) => {
let progress;
let lastUpdated;
switch (action.type) {
case 'SET_STUDY_LOADING_PROGRESS':
progress = cloneDeep(state).progress;
progress[action.progressId] = action.progressData;
// This is a workaround so we can easily identify changes
// to the progress object without doing deep comparison.
lastUpdated = new Date().getTime();
return Object.assign({}, state, { progress, lastUpdated });
case 'CLEAR_STUDY_LOADING_PROGRESS':
progress = cloneDeep(state).progress;
delete progress[action.progressId];
lastUpdated = new Date().getTime();
return Object.assign({}, state, { progress, lastUpdated });
default:
return state;
}
};
export default loading;
@@ -260,7 +260,6 @@ class MeasurementService {
const sourceInfo = this._getSourceInfo(source);
if (!definition) {
console.log('TEST');
log.warn('No source definition provided. Exiting early.');
return;
}
+19
View File
@@ -3,6 +3,25 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.10.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.9.3...@ohif/ui@1.10.0) (2021-09-09)
### Features
* OHIF-1000: Study prefetcher ([#2466](https://github.com/OHIF/Viewers/issues/2466)) ([8f26045](https://github.com/OHIF/Viewers/commit/8f2604509d24092b721f1333289d838820d68cac))
## [1.9.3](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.9.2...@ohif/ui@1.9.3) (2021-09-03)
**Note:** Version bump only for package @ohif/ui
## [1.9.2](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.9.1...@ohif/ui@1.9.2) (2021-05-17)
**Note:** Version bump only for package @ohif/ui
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/ui",
"version": "1.9.2",
"version": "1.10.0",
"description": "A set of React components for Medical Imaging Viewers",
"author": "OHIF Contributors",
"license": "MIT",
@@ -4,6 +4,29 @@
z-index: 0;
}
.RoundedButtonGroup .badgeNumber-container {
background-color: var(--ui-border-color-dark);
width: 21px;
height: 21px;
justify-content: center;
align-items: center;
display: flex;
border-radius: 50%;
font-size: 12px;
font-weight: bold;
bottom: 10px;
left: 5px;
color: white;
position: absolute;
}
.RoundedButtonGroup .badgeNumber {
font-size: 10px;
font-weight: bold;
display: block;
width: 25px;
}
.roundedButtonWrapper {
cursor: pointer;
display: inline-block;
@@ -36,7 +59,12 @@
z-index: 1;
}
.roundedButtonWrapper .roundedButton svg .roundedButtonWrapper .roundedButton span {
.roundedButtonWrapper
.roundedButton
svg
.roundedButtonWrapper
.roundedButton
span {
margin: 0 2px;
}
@@ -13,6 +13,7 @@ class RoundedButtonGroup extends Component {
PropTypes.shape({
value: PropTypes.any,
label: PropTypes.string,
stateEvent: PropTypes.string,
icon: PropTypes.oneOfType([
PropTypes.string,
PropTypes.shape({
@@ -30,6 +31,15 @@ class RoundedButtonGroup extends Component {
value: null,
};
state = {
badgeNumbers: [],
};
constructor() {
super();
this.onStateEvent = this.onStateEvent.bind(this);
}
onClickOption = value => {
let newValue = value;
if (this.props.value === value) {
@@ -41,6 +51,48 @@ class RoundedButtonGroup extends Component {
}
};
onStateEvent(event) {
const optionIndex = this.props.options.findIndex(
o => o.value === event.detail.target
);
if (optionIndex > -1) {
const badgeNumbers = this.state.badgeNumbers;
badgeNumbers[optionIndex] = event.detail.badgeNumber;
this.setState({ badgeNumbers });
}
}
componentDidMount() {
this.props.options.forEach(option => {
if (option.stateEvent) {
document.addEventListener(option.stateEvent, this.onStateEvent);
}
});
}
componentDidUpdate(prevProps, prevState) {
this.props.options.forEach((option, index) => {
if (
option.stateEvent &&
option.stateEvent !==
(prevProps.options[index]
? prevProps.options[index].stateEvent
: null)
) {
document.removeEventListener(option.stateEvent, this.onStateEvent);
document.addEventListener(option.stateEvent, this.onStateEvent);
}
});
}
componentWillUnmount() {
this.props.options.forEach(option => {
if (option.stateEvent) {
document.removeEventListener(option.stateEvent, this.onStateEvent);
}
});
}
render() {
let className = classnames(
RoundedButtonGroup.className,
@@ -62,6 +114,14 @@ class RoundedButtonGroup extends Component {
<div className="bottomLabel">{option.bottomLabel}</div>
);
let badgeNumber = this.state.badgeNumbers[index];
const badgeNumberOverflow = String(badgeNumber).length > 2;
badgeNumber = badgeNumber
? badgeNumberOverflow
? 99
: badgeNumber
: null;
return (
<div
key={index}
@@ -70,6 +130,14 @@ class RoundedButtonGroup extends Component {
>
<div className="roundedButton">
{optionText}
{badgeNumber && (
<div className="badgeNumber-container">
<span className="badgeNumber">
{badgeNumber}
{badgeNumberOverflow && '+'}
</span>
</div>
)}
{iconProps && <Icon {...iconProps} />}
</div>
{bottomLabel}
@@ -2,7 +2,7 @@
import './ImageThumbnail.styl';
import { utils } from '@ohif/core';
import React, { useState, useEffect, createRef } from 'react';
import React, { useState, useEffect, createRef, useCallback } from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types';
@@ -23,6 +23,7 @@ function ImageThumbnail(props) {
imageId,
stackPercentComplete,
error: propsError,
showProgressBar,
} = props;
const [isLoading, setLoading] = useState(false);
@@ -39,7 +40,8 @@ function ImageThumbnail(props) {
loadingOrError = <ViewportLoadingIndicator />;
}
const showStackLoadingProgressBar = stackPercentComplete !== undefined;
const showStackLoadingProgressBar =
showProgressBar && stackPercentComplete !== undefined;
const shouldRenderToCanvas = () => {
return imageId && !imageSrc;
@@ -71,11 +73,11 @@ function ImageThumbnail(props) {
}
};
const purgeCancelablePromise = () => {
const purgeCancelablePromise = useCallback(() => {
if (cancelablePromise) {
cancelablePromise.cancel();
}
};
});
useEffect(() => {
return () => {
@@ -96,7 +98,13 @@ function ImageThumbnail(props) {
setImagePromise();
fetchImagePromise();
}
}, [fetchImagePromise, image.imageId, imageId, purgeCancelablePromise, setImagePromise]);
}, [
fetchImagePromise,
image.imageId,
imageId,
purgeCancelablePromise,
setImagePromise,
]);
return (
<div className={classNames('ImageThumbnail', { active: active })}>
@@ -104,14 +112,14 @@ function ImageThumbnail(props) {
{shouldRenderToCanvas() ? (
<canvas ref={canvasRef} width={width} height={height} />
) : (
<img
className="static-image"
src={imageSrc}
//width={this.props.width}
height={height}
alt={''}
/>
)}
<img
className="static-image"
src={imageSrc}
//width={this.props.width}
height={height}
alt={''}
/>
)}
</div>
{loadingOrError}
{showStackLoadingProgressBar && (
@@ -135,6 +143,7 @@ ImageThumbnail.propTypes = {
width: PropTypes.number,
height: PropTypes.number,
stackPercentComplete: PropTypes.number.isRequired,
showProgressBar: PropTypes.bool,
};
ImageThumbnail.defaultProps = {
@@ -143,6 +152,7 @@ ImageThumbnail.defaultProps = {
stackPercentComplete: 0,
width: 217,
height: 123,
showProgressBar: true,
};
export default ImageThumbnail;
@@ -9,6 +9,7 @@ function StudyBrowser(props) {
onThumbnailClick,
onThumbnailDoubleClick,
supportsDrag,
showThumbnailProgressBar,
} = props;
return (
@@ -28,7 +29,6 @@ function StudyBrowser(props) {
numImageFrames,
SeriesDescription,
SeriesNumber,
stackPercentComplete,
hasWarnings,
} = thumb;
@@ -54,13 +54,13 @@ function StudyBrowser(props) {
SeriesDescription={SeriesDescription}
SeriesNumber={SeriesNumber}
hasWarnings={hasWarnings}
stackPercentComplete={stackPercentComplete}
// Events
onClick={onThumbnailClick.bind(
undefined,
displaySetInstanceUID
)}
onDoubleClick={onThumbnailDoubleClick}
showProgressBar={showThumbnailProgressBar}
/>
</div>
);
@@ -95,6 +95,7 @@ StudyBrowser.propTypes = {
supportsDrag: PropTypes.bool,
onThumbnailClick: PropTypes.func,
onThumbnailDoubleClick: PropTypes.func,
showThumbnailProgressBar: PropTypes.bool,
};
StudyBrowser.defaultProps = {
@@ -102,6 +103,7 @@ StudyBrowser.defaultProps = {
supportsDrag: true,
onThumbnailClick: noop,
onThumbnailDoubleClick: noop,
showThumbnailProgressBar: true,
};
export { StudyBrowser };
@@ -1,34 +1,36 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { useDrag } from 'react-dnd';
import { classes } from '@ohif/core';
import ImageThumbnail from './ImageThumbnail';
import classNames from 'classnames';
import { Icon } from './../../elements/Icon';
import { Tooltip } from './../tooltip';
import { OverlayTrigger } from './../overlayTrigger';
import './Thumbnail.styl';
const StudyLoadingListener = classes.StudyLoadingListener;
function ThumbnailFooter({
SeriesDescription,
SeriesNumber,
InstanceNumber,
numImageFrames,
hasWarnings
hasWarnings,
}) {
const [inconsistencyWarnings, inconsistencyWarningsSet] = useState([]);
useEffect(() => {
let unmounted = false
let unmounted = false;
hasWarnings.then(response => {
if (!unmounted) {
inconsistencyWarningsSet(response)
inconsistencyWarningsSet(response);
}
})
});
return () => {
unmounted = true
}
}, [])
unmounted = true;
};
}, [hasWarnings]);
const infoOnly = !SeriesDescription;
@@ -41,7 +43,7 @@ function ThumbnailFooter({
);
};
const getWarningContent = (inconsistencyWarnings) => {
const getWarningContent = inconsistencyWarnings => {
if (Array.isArray(inconsistencyWarnings)) {
const listedWarnings = inconsistencyWarnings.map((warn, index) => {
return <li key={index}>{warn}</li>;
@@ -54,8 +56,8 @@ function ThumbnailFooter({
};
const getWarningInfo = (SeriesNumber, inconsistencyWarnings) => {
return(
<React.Fragment>
return (
<React.Fragment>
{inconsistencyWarnings && inconsistencyWarnings.length != 0 ? (
<OverlayTrigger
key={SeriesNumber}
@@ -67,7 +69,9 @@ function ThumbnailFooter({
id="tooltip-left"
>
<div className="warningTitle">Series Inconsistencies</div>
<div className="warningContent">{getWarningContent(inconsistencyWarnings)}</div>
<div className="warningContent">
{getWarningContent(inconsistencyWarnings)}
</div>
</Tooltip>
}
>
@@ -79,9 +83,9 @@ function ThumbnailFooter({
</OverlayTrigger>
) : (
<React.Fragment></React.Fragment>
)}
)}
</React.Fragment>
);
);
};
const getSeriesInformation = (
SeriesNumber,
@@ -92,21 +96,27 @@ function ThumbnailFooter({
if (!SeriesNumber && !InstanceNumber && !numImageFrames) {
return;
}
const seriesInformation =
const seriesInformation = (
<div className="series-information">
{getInfo(SeriesNumber, 'S:')}
{getInfo(InstanceNumber, 'I:')}
{getInfo(numImageFrames, '', 'image-frames')}
{getWarningInfo(SeriesNumber, inconsistencyWarnings)}
</div>
);
return (seriesInformation);
return seriesInformation;
};
return (
<div className={classNames('series-details', { 'info-only': infoOnly })}>
<div className="series-description">{SeriesDescription}</div>
{getSeriesInformation(SeriesNumber, InstanceNumber, numImageFrames, inconsistencyWarnings)}
{getSeriesInformation(
SeriesNumber,
InstanceNumber,
numImageFrames,
inconsistencyWarnings
)}
</div>
);
}
@@ -124,14 +134,37 @@ function Thumbnail(props) {
SeriesDescription,
SeriesNumber,
hasWarnings,
stackPercentComplete,
StudyInstanceUID,
onClick,
onDoubleClick,
onMouseDown,
supportsDrag,
showProgressBar,
} = props;
const [stackPercentComplete, setStackPercentComplete] = useState(0);
useEffect(() => {
const onProgressChange = ({ detail }) => {
const { progressId, progressData } = detail;
if (`StackProgress:${displaySetInstanceUID}` === progressId) {
const percent = progressData ? progressData.percentComplete : 0;
setStackPercentComplete(percent);
}
};
document.addEventListener(
StudyLoadingListener.events.OnProgress,
onProgressChange
);
return () => {
document.removeEventListener(
StudyLoadingListener.events.OnProgress,
onProgressChange
);
};
}, [displaySetInstanceUID]);
const [collectedProps, drag, dragPreview] = useDrag({
// `droppedItem` in `dropTarget`
// The only data it will have access to
@@ -164,6 +197,7 @@ function Thumbnail(props) {
imageId={imageId}
error={error}
stackPercentComplete={stackPercentComplete}
showProgressBar={showProgressBar}
/>
)}
{/* SHOW TEXT ALTERNATIVE */}
@@ -203,6 +237,7 @@ Thumbnail.propTypes = {
onDoubleClick: PropTypes.func,
onClick: PropTypes.func,
onMouseDown: PropTypes.func,
showProgressBar: PropTypes.bool,
};
Thumbnail.defaultProps = {
+7
View File
@@ -41,6 +41,13 @@ module.exports = (env, argv) => {
path: DIST_DIR,
filename: isProdBuild ? '[name].bundle.[chunkhash].js' : '[name].js',
publicPath: PUBLIC_URL, // Used by HtmlWebPackPlugin for asset prefix
devtoolModuleFilenameTemplate: function(info) {
if (isProdBuild) {
return `webpack:///${info.resourcePath}`;
} else {
return 'file:///' + encodeURI(info.absoluteResourcePath);
}
},
},
module: {
rules: [...extractStyleChunksRule(isProdBuild)],
+49
View File
@@ -3,6 +3,55 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [4.10.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.24...@ohif/viewer@4.10.0) (2021-09-09)
### Features
* OHIF-1000: Study prefetcher ([#2466](https://github.com/OHIF/Viewers/issues/2466)) ([8f26045](https://github.com/OHIF/Viewers/commit/8f2604509d24092b721f1333289d838820d68cac))
## [4.9.24](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.23...@ohif/viewer@4.9.24) (2021-09-03)
**Note:** Version bump only for package @ohif/viewer
## [4.9.23](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.22...@ohif/viewer@4.9.23) (2021-08-30)
### Bug Fixes
* MPR metadata parsing for spacings ([#2517](https://github.com/OHIF/Viewers/issues/2517)) ([715dfc5](https://github.com/OHIF/Viewers/commit/715dfc556a3883de31d74012dcc99e431d91f57d))
## [4.9.22](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.21...@ohif/viewer@4.9.22) (2021-08-19)
### Bug Fixes
* Remove internet dependency for workbox (addressing [#2109](https://github.com/OHIF/Viewers/issues/2109)) ([#2151](https://github.com/OHIF/Viewers/issues/2151)) ([5f92f6e](https://github.com/OHIF/Viewers/commit/5f92f6ec7faa11d3fdc33ef8f22971a0bab87a36))
## [4.9.21](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.20...@ohif/viewer@4.9.21) (2021-07-26)
**Note:** Version bump only for package @ohif/viewer
## [4.9.20](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.19...@ohif/viewer@4.9.20) (2021-06-03)
**Note:** Version bump only for package @ohif/viewer
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/viewer",
"version": "4.9.20",
"version": "4.10.0",
"description": "OHIF Viewer",
"author": "OHIF Contributors",
"license": "MIT",
@@ -49,18 +49,18 @@
},
"dependencies": {
"@babel/runtime": "^7.5.5",
"@ohif/core": "^2.13.13",
"@ohif/extension-cornerstone": "^2.10.9",
"@ohif/core": "^2.14.0",
"@ohif/extension-cornerstone": "^2.11.0",
"@ohif/extension-debugging": "^0.2.2",
"@ohif/extension-dicom-html": "^1.3.10",
"@ohif/extension-dicom-microscopy": "^0.52.0",
"@ohif/extension-dicom-pdf": "^1.0.6",
"@ohif/extension-dicom-rt": "^0.6.13",
"@ohif/extension-dicom-segmentation": "^0.6.10",
"@ohif/extension-dicom-rt": "^0.6.14",
"@ohif/extension-dicom-segmentation": "^0.6.11",
"@ohif/extension-lesion-tracker": "^0.2.1",
"@ohif/extension-vtk": "^1.11.14",
"@ohif/extension-vtk": "^1.11.17",
"@ohif/i18n": "^0.53.0",
"@ohif/ui": "^1.9.2",
"@ohif/ui": "^1.10.0",
"@tanem/react-nprogress": "^1.1.25",
"classnames": "^2.2.6",
"core-js": "^3.2.1",
+10 -1
View File
@@ -5,6 +5,15 @@ window.config = {
showStudyList: true,
filterQueryParam: false,
disableServersCache: false,
studyPrefetcher: {
enabled: true,
order: 'closest',
displaySetCount: 3,
preventCache: false,
prefetchDisplaySetsTimeout: 300,
displayProgress: true,
includeActiveDisplaySet: true,
},
servers: {
dicomWeb: [
{
@@ -124,6 +133,6 @@ window.config = {
// If the server is particularly slow to respond to series metadata
// requests as it extracts the metadata from raw files everytime,
// try setting this to even lower value
// Leave it undefined for no limit, sutiable for HTTP/2 enabled servers
// Leave it undefined for no limit, suitable for HTTP/2 enabled servers
// maxConcurrentMetadataRequests: 5,
};
+9
View File
@@ -1,6 +1,15 @@
window.config = {
routerBasename: '/pwa',
showStudyList: true,
studyPrefetcher: {
enabled: true,
order: 'closest',
displaySetCount: 3,
preventCache: false,
prefetchDisplaySetsTimeout: 300,
displayProgress: true,
includeActiveDisplaySet: true,
},
servers: {
dicomWeb: [
{
+12 -1
View File
@@ -3,7 +3,17 @@
// modules, so it's perfectly fine to serve this code to any browsers
// (older browsers will just ignore it)
//
import { Workbox } from 'https://storage.googleapis.com/workbox-cdn/releases/5.0.0-beta.1/workbox-window.prod.mjs';
// The workbox code is locally stored and can be updated
// by running `npx workbox-cli copyLibraries platform/viewer/public/third_party/workbox`
// from the root directory.
// Since the workbox version may change with copyLibraries ensure that the
// right version is used here and in `service-worker.js`
import { Workbox } from './third_party/workbox/workbox-v5.1.4/workbox-window.dev.mjs';
// Alternatively use the internet version by commenting out the above import and
// replace with this line and do the corresponding change in service-worker.js
// import { Workbox } from 'https://storage.googleapis.com/workbox-cdn/releases/5.0.0-beta.1/workbox-window.prod.mjs';
var supportsServiceWorker = 'serviceWorker' in navigator;
var isNotLocalDevelopment =
@@ -11,6 +21,7 @@ var isNotLocalDevelopment =
if (supportsServiceWorker && isNotLocalDevelopment) {
const swFileLocation = (window.PUBLIC_URL || '/') + 'sw.js';
const wb = new Workbox(swFileLocation);
// Add an event listener to detect when the registered
@@ -0,0 +1,818 @@
this.workbox = this.workbox || {};
this.workbox.backgroundSync = (function (exports, WorkboxError_js, logger_js, assert_js, getFriendlyURL_js, DBWrapper_js) {
'use strict';
try {
self['workbox:background-sync:5.1.4'] && _();
} catch (e) {}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
const DB_VERSION = 3;
const DB_NAME = 'workbox-background-sync';
const OBJECT_STORE_NAME = 'requests';
const INDEXED_PROP = 'queueName';
/**
* A class to manage storing requests from a Queue in IndexedDB,
* indexed by their queue name for easier access.
*
* @private
*/
class QueueStore {
/**
* Associates this instance with a Queue instance, so entries added can be
* identified by their queue name.
*
* @param {string} queueName
* @private
*/
constructor(queueName) {
this._queueName = queueName;
this._db = new DBWrapper_js.DBWrapper(DB_NAME, DB_VERSION, {
onupgradeneeded: this._upgradeDb
});
}
/**
* Append an entry last in the queue.
*
* @param {Object} entry
* @param {Object} entry.requestData
* @param {number} [entry.timestamp]
* @param {Object} [entry.metadata]
* @private
*/
async pushEntry(entry) {
{
assert_js.assert.isType(entry, 'object', {
moduleName: 'workbox-background-sync',
className: 'QueueStore',
funcName: 'pushEntry',
paramName: 'entry'
});
assert_js.assert.isType(entry.requestData, 'object', {
moduleName: 'workbox-background-sync',
className: 'QueueStore',
funcName: 'pushEntry',
paramName: 'entry.requestData'
});
} // Don't specify an ID since one is automatically generated.
delete entry.id;
entry.queueName = this._queueName;
await this._db.add(OBJECT_STORE_NAME, entry);
}
/**
* Prepend an entry first in the queue.
*
* @param {Object} entry
* @param {Object} entry.requestData
* @param {number} [entry.timestamp]
* @param {Object} [entry.metadata]
* @private
*/
async unshiftEntry(entry) {
{
assert_js.assert.isType(entry, 'object', {
moduleName: 'workbox-background-sync',
className: 'QueueStore',
funcName: 'unshiftEntry',
paramName: 'entry'
});
assert_js.assert.isType(entry.requestData, 'object', {
moduleName: 'workbox-background-sync',
className: 'QueueStore',
funcName: 'unshiftEntry',
paramName: 'entry.requestData'
});
}
const [firstEntry] = await this._db.getAllMatching(OBJECT_STORE_NAME, {
count: 1
});
if (firstEntry) {
// Pick an ID one less than the lowest ID in the object store.
entry.id = firstEntry.id - 1;
} else {
// Otherwise let the auto-incrementor assign the ID.
delete entry.id;
}
entry.queueName = this._queueName;
await this._db.add(OBJECT_STORE_NAME, entry);
}
/**
* Removes and returns the last entry in the queue matching the `queueName`.
*
* @return {Promise<Object>}
* @private
*/
async popEntry() {
return this._removeEntry({
direction: 'prev'
});
}
/**
* Removes and returns the first entry in the queue matching the `queueName`.
*
* @return {Promise<Object>}
* @private
*/
async shiftEntry() {
return this._removeEntry({
direction: 'next'
});
}
/**
* Returns all entries in the store matching the `queueName`.
*
* @param {Object} options See {@link module:workbox-background-sync.Queue~getAll}
* @return {Promise<Array<Object>>}
* @private
*/
async getAll() {
return await this._db.getAllMatching(OBJECT_STORE_NAME, {
index: INDEXED_PROP,
query: IDBKeyRange.only(this._queueName)
});
}
/**
* Deletes the entry for the given ID.
*
* WARNING: this method does not ensure the deleted enry belongs to this
* queue (i.e. matches the `queueName`). But this limitation is acceptable
* as this class is not publicly exposed. An additional check would make
* this method slower than it needs to be.
*
* @private
* @param {number} id
*/
async deleteEntry(id) {
await this._db.delete(OBJECT_STORE_NAME, id);
}
/**
* Removes and returns the first or last entry in the queue (based on the
* `direction` argument) matching the `queueName`.
*
* @return {Promise<Object>}
* @private
*/
async _removeEntry({
direction
}) {
const [entry] = await this._db.getAllMatching(OBJECT_STORE_NAME, {
direction,
index: INDEXED_PROP,
query: IDBKeyRange.only(this._queueName),
count: 1
});
if (entry) {
await this.deleteEntry(entry.id);
return entry;
}
}
/**
* Upgrades the database given an `upgradeneeded` event.
*
* @param {Event} event
* @private
*/
_upgradeDb(event) {
const db = event.target.result;
if (event.oldVersion > 0 && event.oldVersion < DB_VERSION) {
if (db.objectStoreNames.contains(OBJECT_STORE_NAME)) {
db.deleteObjectStore(OBJECT_STORE_NAME);
}
}
const objStore = db.createObjectStore(OBJECT_STORE_NAME, {
autoIncrement: true,
keyPath: 'id'
});
objStore.createIndex(INDEXED_PROP, INDEXED_PROP, {
unique: false
});
}
}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
const serializableProperties = ['method', 'referrer', 'referrerPolicy', 'mode', 'credentials', 'cache', 'redirect', 'integrity', 'keepalive'];
/**
* A class to make it easier to serialize and de-serialize requests so they
* can be stored in IndexedDB.
*
* @private
*/
class StorableRequest {
/**
* Accepts an object of request data that can be used to construct a
* `Request` but can also be stored in IndexedDB.
*
* @param {Object} requestData An object of request data that includes the
* `url` plus any relevant properties of
* [requestInit]{@link https://fetch.spec.whatwg.org/#requestinit}.
* @private
*/
constructor(requestData) {
{
assert_js.assert.isType(requestData, 'object', {
moduleName: 'workbox-background-sync',
className: 'StorableRequest',
funcName: 'constructor',
paramName: 'requestData'
});
assert_js.assert.isType(requestData.url, 'string', {
moduleName: 'workbox-background-sync',
className: 'StorableRequest',
funcName: 'constructor',
paramName: 'requestData.url'
});
} // If the request's mode is `navigate`, convert it to `same-origin` since
// navigation requests can't be constructed via script.
if (requestData['mode'] === 'navigate') {
requestData['mode'] = 'same-origin';
}
this._requestData = requestData;
}
/**
* Converts a Request object to a plain object that can be structured
* cloned or JSON-stringified.
*
* @param {Request} request
* @return {Promise<StorableRequest>}
*
* @private
*/
static async fromRequest(request) {
const requestData = {
url: request.url,
headers: {}
}; // Set the body if present.
if (request.method !== 'GET') {
// Use ArrayBuffer to support non-text request bodies.
// NOTE: we can't use Blobs becuse Safari doesn't support storing
// Blobs in IndexedDB in some cases:
// https://github.com/dfahlander/Dexie.js/issues/618#issuecomment-398348457
requestData.body = await request.clone().arrayBuffer();
} // Convert the headers from an iterable to an object.
for (const [key, value] of request.headers.entries()) {
requestData.headers[key] = value;
} // Add all other serializable request properties
for (const prop of serializableProperties) {
if (request[prop] !== undefined) {
requestData[prop] = request[prop];
}
}
return new StorableRequest(requestData);
}
/**
* Returns a deep clone of the instances `_requestData` object.
*
* @return {Object}
*
* @private
*/
toObject() {
const requestData = Object.assign({}, this._requestData);
requestData.headers = Object.assign({}, this._requestData.headers);
if (requestData.body) {
requestData.body = requestData.body.slice(0);
}
return requestData;
}
/**
* Converts this instance to a Request.
*
* @return {Request}
*
* @private
*/
toRequest() {
return new Request(this._requestData.url, this._requestData);
}
/**
* Creates and returns a deep clone of the instance.
*
* @return {StorableRequest}
*
* @private
*/
clone() {
return new StorableRequest(this.toObject());
}
}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
const TAG_PREFIX = 'workbox-background-sync';
const MAX_RETENTION_TIME = 60 * 24 * 7; // 7 days in minutes
const queueNames = new Set();
/**
* Converts a QueueStore entry into the format exposed by Queue. This entails
* converting the request data into a real request and omitting the `id` and
* `queueName` properties.
*
* @param {Object} queueStoreEntry
* @return {Object}
* @private
*/
const convertEntry = queueStoreEntry => {
const queueEntry = {
request: new StorableRequest(queueStoreEntry.requestData).toRequest(),
timestamp: queueStoreEntry.timestamp
};
if (queueStoreEntry.metadata) {
queueEntry.metadata = queueStoreEntry.metadata;
}
return queueEntry;
};
/**
* A class to manage storing failed requests in IndexedDB and retrying them
* later. All parts of the storing and replaying process are observable via
* callbacks.
*
* @memberof module:workbox-background-sync
*/
class Queue {
/**
* Creates an instance of Queue with the given options
*
* @param {string} name The unique name for this queue. This name must be
* unique as it's used to register sync events and store requests
* in IndexedDB specific to this instance. An error will be thrown if
* a duplicate name is detected.
* @param {Object} [options]
* @param {Function} [options.onSync] A function that gets invoked whenever
* the 'sync' event fires. The function is invoked with an object
* containing the `queue` property (referencing this instance), and you
* can use the callback to customize the replay behavior of the queue.
* When not set the `replayRequests()` method is called.
* Note: if the replay fails after a sync event, make sure you throw an
* error, so the browser knows to retry the sync event later.
* @param {number} [options.maxRetentionTime=7 days] The amount of time (in
* minutes) a request may be retried. After this amount of time has
* passed, the request will be deleted from the queue.
*/
constructor(name, {
onSync,
maxRetentionTime
} = {}) {
this._syncInProgress = false;
this._requestsAddedDuringSync = false; // Ensure the store name is not already being used
if (queueNames.has(name)) {
throw new WorkboxError_js.WorkboxError('duplicate-queue-name', {
name
});
} else {
queueNames.add(name);
}
this._name = name;
this._onSync = onSync || this.replayRequests;
this._maxRetentionTime = maxRetentionTime || MAX_RETENTION_TIME;
this._queueStore = new QueueStore(this._name);
this._addSyncListener();
}
/**
* @return {string}
*/
get name() {
return this._name;
}
/**
* Stores the passed request in IndexedDB (with its timestamp and any
* metadata) at the end of the queue.
*
* @param {Object} entry
* @param {Request} entry.request The request to store in the queue.
* @param {Object} [entry.metadata] Any metadata you want associated with the
* stored request. When requests are replayed you'll have access to this
* metadata object in case you need to modify the request beforehand.
* @param {number} [entry.timestamp] The timestamp (Epoch time in
* milliseconds) when the request was first added to the queue. This is
* used along with `maxRetentionTime` to remove outdated requests. In
* general you don't need to set this value, as it's automatically set
* for you (defaulting to `Date.now()`), but you can update it if you
* don't want particular requests to expire.
*/
async pushRequest(entry) {
{
assert_js.assert.isType(entry, 'object', {
moduleName: 'workbox-background-sync',
className: 'Queue',
funcName: 'pushRequest',
paramName: 'entry'
});
assert_js.assert.isInstance(entry.request, Request, {
moduleName: 'workbox-background-sync',
className: 'Queue',
funcName: 'pushRequest',
paramName: 'entry.request'
});
}
await this._addRequest(entry, 'push');
}
/**
* Stores the passed request in IndexedDB (with its timestamp and any
* metadata) at the beginning of the queue.
*
* @param {Object} entry
* @param {Request} entry.request The request to store in the queue.
* @param {Object} [entry.metadata] Any metadata you want associated with the
* stored request. When requests are replayed you'll have access to this
* metadata object in case you need to modify the request beforehand.
* @param {number} [entry.timestamp] The timestamp (Epoch time in
* milliseconds) when the request was first added to the queue. This is
* used along with `maxRetentionTime` to remove outdated requests. In
* general you don't need to set this value, as it's automatically set
* for you (defaulting to `Date.now()`), but you can update it if you
* don't want particular requests to expire.
*/
async unshiftRequest(entry) {
{
assert_js.assert.isType(entry, 'object', {
moduleName: 'workbox-background-sync',
className: 'Queue',
funcName: 'unshiftRequest',
paramName: 'entry'
});
assert_js.assert.isInstance(entry.request, Request, {
moduleName: 'workbox-background-sync',
className: 'Queue',
funcName: 'unshiftRequest',
paramName: 'entry.request'
});
}
await this._addRequest(entry, 'unshift');
}
/**
* Removes and returns the last request in the queue (along with its
* timestamp and any metadata). The returned object takes the form:
* `{request, timestamp, metadata}`.
*
* @return {Promise<Object>}
*/
async popRequest() {
return this._removeRequest('pop');
}
/**
* Removes and returns the first request in the queue (along with its
* timestamp and any metadata). The returned object takes the form:
* `{request, timestamp, metadata}`.
*
* @return {Promise<Object>}
*/
async shiftRequest() {
return this._removeRequest('shift');
}
/**
* Returns all the entries that have not expired (per `maxRetentionTime`).
* Any expired entries are removed from the queue.
*
* @return {Promise<Array<Object>>}
*/
async getAll() {
const allEntries = await this._queueStore.getAll();
const now = Date.now();
const unexpiredEntries = [];
for (const entry of allEntries) {
// Ignore requests older than maxRetentionTime. Call this function
// recursively until an unexpired request is found.
const maxRetentionTimeInMs = this._maxRetentionTime * 60 * 1000;
if (now - entry.timestamp > maxRetentionTimeInMs) {
await this._queueStore.deleteEntry(entry.id);
} else {
unexpiredEntries.push(convertEntry(entry));
}
}
return unexpiredEntries;
}
/**
* Adds the entry to the QueueStore and registers for a sync event.
*
* @param {Object} entry
* @param {Request} entry.request
* @param {Object} [entry.metadata]
* @param {number} [entry.timestamp=Date.now()]
* @param {string} operation ('push' or 'unshift')
* @private
*/
async _addRequest({
request,
metadata,
timestamp = Date.now()
}, operation) {
const storableRequest = await StorableRequest.fromRequest(request.clone());
const entry = {
requestData: storableRequest.toObject(),
timestamp
}; // Only include metadata if it's present.
if (metadata) {
entry.metadata = metadata;
}
await this._queueStore[`${operation}Entry`](entry);
{
logger_js.logger.log(`Request for '${getFriendlyURL_js.getFriendlyURL(request.url)}' has ` + `been added to background sync queue '${this._name}'.`);
} // Don't register for a sync if we're in the middle of a sync. Instead,
// we wait until the sync is complete and call register if
// `this._requestsAddedDuringSync` is true.
if (this._syncInProgress) {
this._requestsAddedDuringSync = true;
} else {
await this.registerSync();
}
}
/**
* Removes and returns the first or last (depending on `operation`) entry
* from the QueueStore that's not older than the `maxRetentionTime`.
*
* @param {string} operation ('pop' or 'shift')
* @return {Object|undefined}
* @private
*/
async _removeRequest(operation) {
const now = Date.now();
const entry = await this._queueStore[`${operation}Entry`]();
if (entry) {
// Ignore requests older than maxRetentionTime. Call this function
// recursively until an unexpired request is found.
const maxRetentionTimeInMs = this._maxRetentionTime * 60 * 1000;
if (now - entry.timestamp > maxRetentionTimeInMs) {
return this._removeRequest(operation);
}
return convertEntry(entry);
} else {
return undefined;
}
}
/**
* Loops through each request in the queue and attempts to re-fetch it.
* If any request fails to re-fetch, it's put back in the same position in
* the queue (which registers a retry for the next sync event).
*/
async replayRequests() {
let entry;
while (entry = await this.shiftRequest()) {
try {
await fetch(entry.request.clone());
if ("dev" !== 'production') {
logger_js.logger.log(`Request for '${getFriendlyURL_js.getFriendlyURL(entry.request.url)}'` + `has been replayed in queue '${this._name}'`);
}
} catch (error) {
await this.unshiftRequest(entry);
{
logger_js.logger.log(`Request for '${getFriendlyURL_js.getFriendlyURL(entry.request.url)}'` + `failed to replay, putting it back in queue '${this._name}'`);
}
throw new WorkboxError_js.WorkboxError('queue-replay-failed', {
name: this._name
});
}
}
{
logger_js.logger.log(`All requests in queue '${this.name}' have successfully ` + `replayed; the queue is now empty!`);
}
}
/**
* Registers a sync event with a tag unique to this instance.
*/
async registerSync() {
if ('sync' in self.registration) {
try {
await self.registration.sync.register(`${TAG_PREFIX}:${this._name}`);
} catch (err) {
// This means the registration failed for some reason, possibly due to
// the user disabling it.
{
logger_js.logger.warn(`Unable to register sync event for '${this._name}'.`, err);
}
}
}
}
/**
* In sync-supporting browsers, this adds a listener for the sync event.
* In non-sync-supporting browsers, this will retry the queue on service
* worker startup.
*
* @private
*/
_addSyncListener() {
if ('sync' in self.registration) {
self.addEventListener('sync', event => {
if (event.tag === `${TAG_PREFIX}:${this._name}`) {
{
logger_js.logger.log(`Background sync for tag '${event.tag}'` + `has been received`);
}
const syncComplete = async () => {
this._syncInProgress = true;
let syncError;
try {
await this._onSync({
queue: this
});
} catch (error) {
syncError = error; // Rethrow the error. Note: the logic in the finally clause
// will run before this gets rethrown.
throw syncError;
} finally {
// New items may have been added to the queue during the sync,
// so we need to register for a new sync if that's happened...
// Unless there was an error during the sync, in which
// case the browser will automatically retry later, as long
// as `event.lastChance` is not true.
if (this._requestsAddedDuringSync && !(syncError && !event.lastChance)) {
await this.registerSync();
}
this._syncInProgress = false;
this._requestsAddedDuringSync = false;
}
};
event.waitUntil(syncComplete());
}
});
} else {
{
logger_js.logger.log(`Background sync replaying without background sync event`);
} // If the browser doesn't support background sync, retry
// every time the service worker starts up as a fallback.
this._onSync({
queue: this
});
}
}
/**
* Returns the set of queue names. This is primarily used to reset the list
* of queue names in tests.
*
* @return {Set}
*
* @private
*/
static get _queueNames() {
return queueNames;
}
}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* A class implementing the `fetchDidFail` lifecycle callback. This makes it
* easier to add failed requests to a background sync Queue.
*
* @memberof module:workbox-background-sync
*/
class BackgroundSyncPlugin {
/**
* @param {string} name See the [Queue]{@link module:workbox-background-sync.Queue}
* documentation for parameter details.
* @param {Object} [options] See the
* [Queue]{@link module:workbox-background-sync.Queue} documentation for
* parameter details.
*/
constructor(name, options) {
/**
* @param {Object} options
* @param {Request} options.request
* @private
*/
this.fetchDidFail = async ({
request
}) => {
await this._queue.pushRequest({
request
});
};
this._queue = new Queue(name, options);
}
}
exports.BackgroundSyncPlugin = BackgroundSyncPlugin;
exports.Queue = Queue;
return exports;
}({}, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private));
//# sourceMappingURL=workbox-background-sync.dev.js.map
@@ -0,0 +1 @@
{"version":3,"file":"workbox-background-sync.dev.js","sources":["../_version.js","../lib/QueueStore.js","../lib/StorableRequest.js","../Queue.js","../BackgroundSyncPlugin.js"],"sourcesContent":["\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:background-sync:5.1.4'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { DBWrapper } from 'workbox-core/_private/DBWrapper.js';\nimport '../_version.js';\nconst DB_VERSION = 3;\nconst DB_NAME = 'workbox-background-sync';\nconst OBJECT_STORE_NAME = 'requests';\nconst INDEXED_PROP = 'queueName';\n/**\n * A class to manage storing requests from a Queue in IndexedDB,\n * indexed by their queue name for easier access.\n *\n * @private\n */\nexport class QueueStore {\n /**\n * Associates this instance with a Queue instance, so entries added can be\n * identified by their queue name.\n *\n * @param {string} queueName\n * @private\n */\n constructor(queueName) {\n this._queueName = queueName;\n this._db = new DBWrapper(DB_NAME, DB_VERSION, {\n onupgradeneeded: this._upgradeDb,\n });\n }\n /**\n * Append an entry last in the queue.\n *\n * @param {Object} entry\n * @param {Object} entry.requestData\n * @param {number} [entry.timestamp]\n * @param {Object} [entry.metadata]\n * @private\n */\n async pushEntry(entry) {\n if (process.env.NODE_ENV !== 'production') {\n assert.isType(entry, 'object', {\n moduleName: 'workbox-background-sync',\n className: 'QueueStore',\n funcName: 'pushEntry',\n paramName: 'entry',\n });\n assert.isType(entry.requestData, 'object', {\n moduleName: 'workbox-background-sync',\n className: 'QueueStore',\n funcName: 'pushEntry',\n paramName: 'entry.requestData',\n });\n }\n // Don't specify an ID since one is automatically generated.\n delete entry.id;\n entry.queueName = this._queueName;\n await this._db.add(OBJECT_STORE_NAME, entry);\n }\n /**\n * Prepend an entry first in the queue.\n *\n * @param {Object} entry\n * @param {Object} entry.requestData\n * @param {number} [entry.timestamp]\n * @param {Object} [entry.metadata]\n * @private\n */\n async unshiftEntry(entry) {\n if (process.env.NODE_ENV !== 'production') {\n assert.isType(entry, 'object', {\n moduleName: 'workbox-background-sync',\n className: 'QueueStore',\n funcName: 'unshiftEntry',\n paramName: 'entry',\n });\n assert.isType(entry.requestData, 'object', {\n moduleName: 'workbox-background-sync',\n className: 'QueueStore',\n funcName: 'unshiftEntry',\n paramName: 'entry.requestData',\n });\n }\n const [firstEntry] = await this._db.getAllMatching(OBJECT_STORE_NAME, {\n count: 1,\n });\n if (firstEntry) {\n // Pick an ID one less than the lowest ID in the object store.\n entry.id = firstEntry.id - 1;\n }\n else {\n // Otherwise let the auto-incrementor assign the ID.\n delete entry.id;\n }\n entry.queueName = this._queueName;\n await this._db.add(OBJECT_STORE_NAME, entry);\n }\n /**\n * Removes and returns the last entry in the queue matching the `queueName`.\n *\n * @return {Promise<Object>}\n * @private\n */\n async popEntry() {\n return this._removeEntry({ direction: 'prev' });\n }\n /**\n * Removes and returns the first entry in the queue matching the `queueName`.\n *\n * @return {Promise<Object>}\n * @private\n */\n async shiftEntry() {\n return this._removeEntry({ direction: 'next' });\n }\n /**\n * Returns all entries in the store matching the `queueName`.\n *\n * @param {Object} options See {@link module:workbox-background-sync.Queue~getAll}\n * @return {Promise<Array<Object>>}\n * @private\n */\n async getAll() {\n return await this._db.getAllMatching(OBJECT_STORE_NAME, {\n index: INDEXED_PROP,\n query: IDBKeyRange.only(this._queueName),\n });\n }\n /**\n * Deletes the entry for the given ID.\n *\n * WARNING: this method does not ensure the deleted enry belongs to this\n * queue (i.e. matches the `queueName`). But this limitation is acceptable\n * as this class is not publicly exposed. An additional check would make\n * this method slower than iLine truncated
@@ -0,0 +1,2 @@
this.workbox=this.workbox||{},this.workbox.backgroundSync=function(t,e,s,i,n){"use strict";try{self["workbox:background-sync:5.1.4"]&&_()}catch(t){}class a{constructor(t){this.t=t,this.s=new n.DBWrapper("workbox-background-sync",3,{onupgradeneeded:this.i})}async pushEntry(t){delete t.id,t.queueName=this.t,await this.s.add("requests",t)}async unshiftEntry(t){const[e]=await this.s.getAllMatching("requests",{count:1});e?t.id=e.id-1:delete t.id,t.queueName=this.t,await this.s.add("requests",t)}async popEntry(){return this.h({direction:"prev"})}async shiftEntry(){return this.h({direction:"next"})}async getAll(){return await this.s.getAllMatching("requests",{index:"queueName",query:IDBKeyRange.only(this.t)})}async deleteEntry(t){await this.s.delete("requests",t)}async h({direction:t}){const[e]=await this.s.getAllMatching("requests",{direction:t,index:"queueName",query:IDBKeyRange.only(this.t),count:1});if(e)return await this.deleteEntry(e.id),e}i(t){const e=t.target.result;t.oldVersion>0&&t.oldVersion<3&&e.objectStoreNames.contains("requests")&&e.deleteObjectStore("requests");e.createObjectStore("requests",{autoIncrement:!0,keyPath:"id"}).createIndex("queueName","queueName",{unique:!1})}}const r=["method","referrer","referrerPolicy","mode","credentials","cache","redirect","integrity","keepalive"];class c{constructor(t){"navigate"===t.mode&&(t.mode="same-origin"),this.u=t}static async fromRequest(t){const e={url:t.url,headers:{}};"GET"!==t.method&&(e.body=await t.clone().arrayBuffer());for(const[s,i]of t.headers.entries())e.headers[s]=i;for(const s of r)void 0!==t[s]&&(e[s]=t[s]);return new c(e)}toObject(){const t=Object.assign({},this.u);return t.headers=Object.assign({},this.u.headers),t.body&&(t.body=t.body.slice(0)),t}toRequest(){return new Request(this.u.url,this.u)}clone(){return new c(this.toObject())}}const h=new Set,u=t=>{const e={request:new c(t.requestData).toRequest(),timestamp:t.timestamp};return t.metadata&&(e.metadata=t.metadata),e};class o{constructor(t,{onSync:s,maxRetentionTime:i}={}){if(this.o=!1,this.q=!1,h.has(t))throw new e.WorkboxError("duplicate-queue-name",{name:t});h.add(t),this.l=t,this.m=s||this.replayRequests,this.p=i||10080,this.g=new a(this.l),this.R()}get name(){return this.l}async pushRequest(t){await this.k(t,"push")}async unshiftRequest(t){await this.k(t,"unshift")}async popRequest(){return this.D("pop")}async shiftRequest(){return this.D("shift")}async getAll(){const t=await this.g.getAll(),e=Date.now(),s=[];for(const i of t){const t=60*this.p*1e3;e-i.timestamp>t?await this.g.deleteEntry(i.id):s.push(u(i))}return s}async k({request:t,metadata:e,timestamp:s=Date.now()},i){const n={requestData:(await c.fromRequest(t.clone())).toObject(),timestamp:s};e&&(n.metadata=e),await this.g[i+"Entry"](n),this.o?this.q=!0:await this.registerSync()}async D(t){const e=Date.now(),s=await this.g[t+"Entry"]();if(s){const i=60*this.p*1e3;return e-s.timestamp>i?this.D(t):u(s)}}async replayRequests(){let t;for(;t=await this.shiftRequest();)try{await fetch(t.request.clone())}catch(s){throw await this.unshiftRequest(t),new e.WorkboxError("queue-replay-failed",{name:this.l})}}async registerSync(){if("sync"in self.registration)try{await self.registration.sync.register("workbox-background-sync:"+this.l)}catch(t){}}R(){"sync"in self.registration?self.addEventListener("sync",t=>{if(t.tag==="workbox-background-sync:"+this.l){const e=async()=>{let e;this.o=!0;try{await this.m({queue:this})}catch(t){throw e=t,e}finally{!this.q||e&&!t.lastChance||await this.registerSync(),this.o=!1,this.q=!1}};t.waitUntil(e())}}):this.m({queue:this})}static get _(){return h}}return t.BackgroundSyncPlugin=class{constructor(t,e){this.fetchDidFail=async({request:t})=>{await this.v.pushRequest({request:t})},this.v=new o(t,e)}},t.Queue=o,t}({},workbox.core._private,workbox.core._private,workbox.core._private,workbox.core._private);
//# sourceMappingURL=workbox-background-sync.prod.js.map
@@ -0,0 +1 @@
{"version":3,"file":"workbox-background-sync.prod.js","sources":["../_version.js","../lib/QueueStore.js","../lib/StorableRequest.js","../Queue.js","../BackgroundSyncPlugin.js"],"sourcesContent":["\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:background-sync:5.1.4'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { DBWrapper } from 'workbox-core/_private/DBWrapper.js';\nimport '../_version.js';\nconst DB_VERSION = 3;\nconst DB_NAME = 'workbox-background-sync';\nconst OBJECT_STORE_NAME = 'requests';\nconst INDEXED_PROP = 'queueName';\n/**\n * A class to manage storing requests from a Queue in IndexedDB,\n * indexed by their queue name for easier access.\n *\n * @private\n */\nexport class QueueStore {\n /**\n * Associates this instance with a Queue instance, so entries added can be\n * identified by their queue name.\n *\n * @param {string} queueName\n * @private\n */\n constructor(queueName) {\n this._queueName = queueName;\n this._db = new DBWrapper(DB_NAME, DB_VERSION, {\n onupgradeneeded: this._upgradeDb,\n });\n }\n /**\n * Append an entry last in the queue.\n *\n * @param {Object} entry\n * @param {Object} entry.requestData\n * @param {number} [entry.timestamp]\n * @param {Object} [entry.metadata]\n * @private\n */\n async pushEntry(entry) {\n if (process.env.NODE_ENV !== 'production') {\n assert.isType(entry, 'object', {\n moduleName: 'workbox-background-sync',\n className: 'QueueStore',\n funcName: 'pushEntry',\n paramName: 'entry',\n });\n assert.isType(entry.requestData, 'object', {\n moduleName: 'workbox-background-sync',\n className: 'QueueStore',\n funcName: 'pushEntry',\n paramName: 'entry.requestData',\n });\n }\n // Don't specify an ID since one is automatically generated.\n delete entry.id;\n entry.queueName = this._queueName;\n await this._db.add(OBJECT_STORE_NAME, entry);\n }\n /**\n * Prepend an entry first in the queue.\n *\n * @param {Object} entry\n * @param {Object} entry.requestData\n * @param {number} [entry.timestamp]\n * @param {Object} [entry.metadata]\n * @private\n */\n async unshiftEntry(entry) {\n if (process.env.NODE_ENV !== 'production') {\n assert.isType(entry, 'object', {\n moduleName: 'workbox-background-sync',\n className: 'QueueStore',\n funcName: 'unshiftEntry',\n paramName: 'entry',\n });\n assert.isType(entry.requestData, 'object', {\n moduleName: 'workbox-background-sync',\n className: 'QueueStore',\n funcName: 'unshiftEntry',\n paramName: 'entry.requestData',\n });\n }\n const [firstEntry] = await this._db.getAllMatching(OBJECT_STORE_NAME, {\n count: 1,\n });\n if (firstEntry) {\n // Pick an ID one less than the lowest ID in the object store.\n entry.id = firstEntry.id - 1;\n }\n else {\n // Otherwise let the auto-incrementor assign the ID.\n delete entry.id;\n }\n entry.queueName = this._queueName;\n await this._db.add(OBJECT_STORE_NAME, entry);\n }\n /**\n * Removes and returns the last entry in the queue matching the `queueName`.\n *\n * @return {Promise<Object>}\n * @private\n */\n async popEntry() {\n return this._removeEntry({ direction: 'prev' });\n }\n /**\n * Removes and returns the first entry in the queue matching the `queueName`.\n *\n * @return {Promise<Object>}\n * @private\n */\n async shiftEntry() {\n return this._removeEntry({ direction: 'next' });\n }\n /**\n * Returns all entries in the store matching the `queueName`.\n *\n * @param {Object} options See {@link module:workbox-background-sync.Queue~getAll}\n * @return {Promise<Array<Object>>}\n * @private\n */\n async getAll() {\n return await this._db.getAllMatching(OBJECT_STORE_NAME, {\n index: INDEXED_PROP,\n query: IDBKeyRange.only(this._queueName),\n });\n }\n /**\n * Deletes the entry for the given ID.\n *\n * WARNING: this method does not ensure the deleted enry belongs to this\n * queue (i.e. matches the `queueName`). But this limitation is acceptable\n * as this class is not publicly exposed. An additional check would make\n * this method slower than Line truncated
@@ -0,0 +1,288 @@
this.workbox = this.workbox || {};
this.workbox.broadcastUpdate = (function (exports, assert_js, timeout_js, resultingClientExists_js, logger_js, WorkboxError_js, dontWaitFor_js) {
'use strict';
try {
self['workbox:broadcast-update:5.1.4'] && _();
} catch (e) {}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* Given two `Response's`, compares several header values to see if they are
* the same or not.
*
* @param {Response} firstResponse
* @param {Response} secondResponse
* @param {Array<string>} headersToCheck
* @return {boolean}
*
* @memberof module:workbox-broadcast-update
*/
const responsesAreSame = (firstResponse, secondResponse, headersToCheck) => {
{
if (!(firstResponse instanceof Response && secondResponse instanceof Response)) {
throw new WorkboxError_js.WorkboxError('invalid-responses-are-same-args');
}
}
const atLeastOneHeaderAvailable = headersToCheck.some(header => {
return firstResponse.headers.has(header) && secondResponse.headers.has(header);
});
if (!atLeastOneHeaderAvailable) {
{
logger_js.logger.warn(`Unable to determine where the response has been updated ` + `because none of the headers that would be checked are present.`);
logger_js.logger.debug(`Attempting to compare the following: `, firstResponse, secondResponse, headersToCheck);
} // Just return true, indicating the that responses are the same, since we
// can't determine otherwise.
return true;
}
return headersToCheck.every(header => {
const headerStateComparison = firstResponse.headers.has(header) === secondResponse.headers.has(header);
const headerValueComparison = firstResponse.headers.get(header) === secondResponse.headers.get(header);
return headerStateComparison && headerValueComparison;
});
};
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
const CACHE_UPDATED_MESSAGE_TYPE = 'CACHE_UPDATED';
const CACHE_UPDATED_MESSAGE_META = 'workbox-broadcast-update';
const DEFAULT_HEADERS_TO_CHECK = ['content-length', 'etag', 'last-modified'];
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
// TODO(philipwalton): remove once this Safari bug fix has been released.
// https://bugs.webkit.org/show_bug.cgi?id=201169
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
/**
* Generates the default payload used in update messages. By default the
* payload includes the `cacheName` and `updatedURL` fields.
*
* @return Object
* @private
*/
function defaultPayloadGenerator(data) {
return {
cacheName: data.cacheName,
updatedURL: data.request.url
};
}
/**
* Uses the `postMessage()` API to inform any open windows/tabs when a cached
* response has been updated.
*
* For efficiency's sake, the underlying response bodies are not compared;
* only specific response headers are checked.
*
* @memberof module:workbox-broadcast-update
*/
class BroadcastCacheUpdate {
/**
* Construct a BroadcastCacheUpdate instance with a specific `channelName` to
* broadcast messages on
*
* @param {Object} options
* @param {Array<string>} [options.headersToCheck=['content-length', 'etag', 'last-modified']]
* A list of headers that will be used to determine whether the responses
* differ.
* @param {string} [options.generatePayload] A function whose return value
* will be used as the `payload` field in any cache update messages sent
* to the window clients.
*/
constructor({
headersToCheck,
generatePayload
} = {}) {
this._headersToCheck = headersToCheck || DEFAULT_HEADERS_TO_CHECK;
this._generatePayload = generatePayload || defaultPayloadGenerator;
}
/**
* Compares two [Responses](https://developer.mozilla.org/en-US/docs/Web/API/Response)
* and sends a message (via `postMessage()`) to all window clients if the
* responses differ (note: neither of the Responses can be
* {@link http://stackoverflow.com/questions/39109789|opaque}).
*
* The message that's posted has the following format (where `payload` can
* be customized via the `generatePayload` option the instance is created
* with):
*
* ```
* {
* type: 'CACHE_UPDATED',
* meta: 'workbox-broadcast-update',
* payload: {
* cacheName: 'the-cache-name',
* updatedURL: 'https://example.com/'
* }
* }
* ```
*
* @param {Object} options
* @param {Response} [options.oldResponse] Cached response to compare.
* @param {Response} options.newResponse Possibly updated response to compare.
* @param {Request} options.request The request.
* @param {string} options.cacheName Name of the cache the responses belong
* to. This is included in the broadcast message.
* @param {Event} [options.event] event An optional event that triggered
* this possible cache update.
* @return {Promise} Resolves once the update is sent.
*/
async notifyIfUpdated(options) {
{
assert_js.assert.isType(options.cacheName, 'string', {
moduleName: 'workbox-broadcast-update',
className: 'BroadcastCacheUpdate',
funcName: 'notifyIfUpdated',
paramName: 'cacheName'
});
assert_js.assert.isInstance(options.newResponse, Response, {
moduleName: 'workbox-broadcast-update',
className: 'BroadcastCacheUpdate',
funcName: 'notifyIfUpdated',
paramName: 'newResponse'
});
assert_js.assert.isInstance(options.request, Request, {
moduleName: 'workbox-broadcast-update',
className: 'BroadcastCacheUpdate',
funcName: 'notifyIfUpdated',
paramName: 'request'
});
} // Without two responses there is nothing to compare.
if (!options.oldResponse) {
return;
}
if (!responsesAreSame(options.oldResponse, options.newResponse, this._headersToCheck)) {
{
logger_js.logger.log(`Newer response found (and cached) for:`, options.request.url);
}
const messageData = {
type: CACHE_UPDATED_MESSAGE_TYPE,
meta: CACHE_UPDATED_MESSAGE_META,
payload: this._generatePayload(options)
}; // For navigation requests, wait until the new window client exists
// before sending the message
if (options.request.mode === 'navigate') {
let resultingClientId;
if (options.event instanceof FetchEvent) {
resultingClientId = options.event.resultingClientId;
}
const resultingWin = await resultingClientExists_js.resultingClientExists(resultingClientId); // Safari does not currently implement postMessage buffering and
// there's no good way to feature detect that, so to increase the
// chances of the message being delivered in Safari, we add a timeout.
// We also do this if `resultingClientExists()` didn't return a client,
// which means it timed out, so it's worth waiting a bit longer.
if (!resultingWin || isSafari) {
// 3500 is chosen because (according to CrUX data) 80% of mobile
// websites hit the DOMContentLoaded event in less than 3.5 seconds.
// And presumably sites implementing service worker are on the
// higher end of the performance spectrum.
await timeout_js.timeout(3500);
}
}
const windows = await self.clients.matchAll({
type: 'window'
});
for (const win of windows) {
win.postMessage(messageData);
}
}
}
}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* This plugin will automatically broadcast a message whenever a cached response
* is updated.
*
* @memberof module:workbox-broadcast-update
*/
class BroadcastUpdatePlugin {
/**
* Construct a BroadcastCacheUpdate instance with the passed options and
* calls its [`notifyIfUpdated()`]{@link module:workbox-broadcast-update.BroadcastCacheUpdate~notifyIfUpdated}
* method whenever the plugin's `cacheDidUpdate` callback is invoked.
*
* @param {Object} options
* @param {Array<string>} [options.headersToCheck=['content-length', 'etag', 'last-modified']]
* A list of headers that will be used to determine whether the responses
* differ.
* @param {string} [options.generatePayload] A function whose return value
* will be used as the `payload` field in any cache update messages sent
* to the window clients.
*/
constructor(options) {
/**
* A "lifecycle" callback that will be triggered automatically by the
* `workbox-sw` and `workbox-runtime-caching` handlers when an entry is
* added to a cache.
*
* @private
* @param {Object} options The input object to this function.
* @param {string} options.cacheName Name of the cache being updated.
* @param {Response} [options.oldResponse] The previous cached value, if any.
* @param {Response} options.newResponse The new value in the cache.
* @param {Request} options.request The request that triggered the update.
* @param {Request} [options.event] The event that triggered the update.
*/
this.cacheDidUpdate = async options => {
dontWaitFor_js.dontWaitFor(this._broadcastUpdate.notifyIfUpdated(options));
};
this._broadcastUpdate = new BroadcastCacheUpdate(options);
}
}
exports.BroadcastCacheUpdate = BroadcastCacheUpdate;
exports.BroadcastUpdatePlugin = BroadcastUpdatePlugin;
exports.responsesAreSame = responsesAreSame;
return exports;
}({}, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private));
//# sourceMappingURL=workbox-broadcast-update.dev.js.map
@@ -0,0 +1 @@
{"version":3,"file":"workbox-broadcast-update.dev.js","sources":["../_version.js","../responsesAreSame.js","../utils/constants.js","../BroadcastCacheUpdate.js","../BroadcastUpdatePlugin.js"],"sourcesContent":["\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:broadcast-update:5.1.4'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport './_version.js';\n/**\n * Given two `Response's`, compares several header values to see if they are\n * the same or not.\n *\n * @param {Response} firstResponse\n * @param {Response} secondResponse\n * @param {Array<string>} headersToCheck\n * @return {boolean}\n *\n * @memberof module:workbox-broadcast-update\n */\nconst responsesAreSame = (firstResponse, secondResponse, headersToCheck) => {\n if (process.env.NODE_ENV !== 'production') {\n if (!(firstResponse instanceof Response &&\n secondResponse instanceof Response)) {\n throw new WorkboxError('invalid-responses-are-same-args');\n }\n }\n const atLeastOneHeaderAvailable = headersToCheck.some((header) => {\n return firstResponse.headers.has(header) &&\n secondResponse.headers.has(header);\n });\n if (!atLeastOneHeaderAvailable) {\n if (process.env.NODE_ENV !== 'production') {\n logger.warn(`Unable to determine where the response has been updated ` +\n `because none of the headers that would be checked are present.`);\n logger.debug(`Attempting to compare the following: `, firstResponse, secondResponse, headersToCheck);\n }\n // Just return true, indicating the that responses are the same, since we\n // can't determine otherwise.\n return true;\n }\n return headersToCheck.every((header) => {\n const headerStateComparison = firstResponse.headers.has(header) ===\n secondResponse.headers.has(header);\n const headerValueComparison = firstResponse.headers.get(header) ===\n secondResponse.headers.get(header);\n return headerStateComparison && headerValueComparison;\n });\n};\nexport { responsesAreSame };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\nexport const CACHE_UPDATED_MESSAGE_TYPE = 'CACHE_UPDATED';\nexport const CACHE_UPDATED_MESSAGE_META = 'workbox-broadcast-update';\nexport const DEFAULT_HEADERS_TO_CHECK = [\n 'content-length',\n 'etag',\n 'last-modified',\n];\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { timeout } from 'workbox-core/_private/timeout.js';\nimport { resultingClientExists } from 'workbox-core/_private/resultingClientExists.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { responsesAreSame } from './responsesAreSame.js';\nimport { CACHE_UPDATED_MESSAGE_TYPE, CACHE_UPDATED_MESSAGE_META, DEFAULT_HEADERS_TO_CHECK } from './utils/constants.js';\nimport './_version.js';\n// UA-sniff Safari: https://stackoverflow.com/questions/7944460/detect-safari-browser\n// TODO(philipwalton): remove once this Safari bug fix has been released.\n// https://bugs.webkit.org/show_bug.cgi?id=201169\nconst isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);\n/**\n * Generates the default payload used in update messages. By default the\n * payload includes the `cacheName` and `updatedURL` fields.\n *\n * @return Object\n * @private\n */\nfunction defaultPayloadGenerator(data) {\n return {\n cacheName: data.cacheName,\n updatedURL: data.request.url,\n };\n}\n/**\n * Uses the `postMessage()` API to inform any open windows/tabs when a cached\n * response has been updated.\n *\n * For efficiency's sake, the underlying response bodies are not compared;\n * only specific response headers are checked.\n *\n * @memberof module:workbox-broadcast-update\n */\nclass BroadcastCacheUpdate {\n /**\n * Construct a BroadcastCacheUpdate instance with a specific `channelName` to\n * broadcast messages on\n *\n * @param {Object} options\n * @param {Array<string>} [options.headersToCheck=['content-length', 'etag', 'last-modified']]\n * A list of headers that will be used to determine whether the responses\n * differ.\n * @param {string} [options.generatePayload] A function whose return value\n * will be used as the `payload` field in any cache update messaLine truncated
@@ -0,0 +1,2 @@
this.workbox=this.workbox||{},this.workbox.broadcastUpdate=function(t,a,o,s){"use strict";try{self["workbox:broadcast-update:5.1.4"]&&_()}catch(t){}const e=(t,a,o)=>!o.some(o=>t.headers.has(o)&&a.headers.has(o))||o.every(o=>{const s=t.headers.has(o)===a.headers.has(o),e=t.headers.get(o)===a.headers.get(o);return s&&e}),n=["content-length","etag","last-modified"],i=/^((?!chrome|android).)*safari/i.test(navigator.userAgent);function c(t){return{cacheName:t.cacheName,updatedURL:t.request.url}}class r{constructor({headersToCheck:t,generatePayload:a}={}){this.t=t||n,this.o=a||c}async notifyIfUpdated(t){if(t.oldResponse&&!e(t.oldResponse,t.newResponse,this.t)){const s={type:"CACHE_UPDATED",meta:"workbox-broadcast-update",payload:this.o(t)};if("navigate"===t.request.mode){let s;t.event instanceof FetchEvent&&(s=t.event.resultingClientId);await o.resultingClientExists(s)&&!i||await a.timeout(3500)}const e=await self.clients.matchAll({type:"window"});for(const t of e)t.postMessage(s)}}}return t.BroadcastCacheUpdate=r,t.BroadcastUpdatePlugin=class{constructor(t){this.cacheDidUpdate=async t=>{s.dontWaitFor(this.s.notifyIfUpdated(t))},this.s=new r(t)}},t.responsesAreSame=e,t}({},workbox.core._private,workbox.core._private,workbox.core._private);
//# sourceMappingURL=workbox-broadcast-update.prod.js.map
@@ -0,0 +1 @@
{"version":3,"file":"workbox-broadcast-update.prod.js","sources":["../_version.js","../responsesAreSame.js","../utils/constants.js","../BroadcastCacheUpdate.js","../BroadcastUpdatePlugin.js"],"sourcesContent":["\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:broadcast-update:5.1.4'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport './_version.js';\n/**\n * Given two `Response's`, compares several header values to see if they are\n * the same or not.\n *\n * @param {Response} firstResponse\n * @param {Response} secondResponse\n * @param {Array<string>} headersToCheck\n * @return {boolean}\n *\n * @memberof module:workbox-broadcast-update\n */\nconst responsesAreSame = (firstResponse, secondResponse, headersToCheck) => {\n if (process.env.NODE_ENV !== 'production') {\n if (!(firstResponse instanceof Response &&\n secondResponse instanceof Response)) {\n throw new WorkboxError('invalid-responses-are-same-args');\n }\n }\n const atLeastOneHeaderAvailable = headersToCheck.some((header) => {\n return firstResponse.headers.has(header) &&\n secondResponse.headers.has(header);\n });\n if (!atLeastOneHeaderAvailable) {\n if (process.env.NODE_ENV !== 'production') {\n logger.warn(`Unable to determine where the response has been updated ` +\n `because none of the headers that would be checked are present.`);\n logger.debug(`Attempting to compare the following: `, firstResponse, secondResponse, headersToCheck);\n }\n // Just return true, indicating the that responses are the same, since we\n // can't determine otherwise.\n return true;\n }\n return headersToCheck.every((header) => {\n const headerStateComparison = firstResponse.headers.has(header) ===\n secondResponse.headers.has(header);\n const headerValueComparison = firstResponse.headers.get(header) ===\n secondResponse.headers.get(header);\n return headerStateComparison && headerValueComparison;\n });\n};\nexport { responsesAreSame };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\nexport const CACHE_UPDATED_MESSAGE_TYPE = 'CACHE_UPDATED';\nexport const CACHE_UPDATED_MESSAGE_META = 'workbox-broadcast-update';\nexport const DEFAULT_HEADERS_TO_CHECK = [\n 'content-length',\n 'etag',\n 'last-modified',\n];\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { timeout } from 'workbox-core/_private/timeout.js';\nimport { resultingClientExists } from 'workbox-core/_private/resultingClientExists.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { responsesAreSame } from './responsesAreSame.js';\nimport { CACHE_UPDATED_MESSAGE_TYPE, CACHE_UPDATED_MESSAGE_META, DEFAULT_HEADERS_TO_CHECK } from './utils/constants.js';\nimport './_version.js';\n// UA-sniff Safari: https://stackoverflow.com/questions/7944460/detect-safari-browser\n// TODO(philipwalton): remove once this Safari bug fix has been released.\n// https://bugs.webkit.org/show_bug.cgi?id=201169\nconst isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);\n/**\n * Generates the default payload used in update messages. By default the\n * payload includes the `cacheName` and `updatedURL` fields.\n *\n * @return Object\n * @private\n */\nfunction defaultPayloadGenerator(data) {\n return {\n cacheName: data.cacheName,\n updatedURL: data.request.url,\n };\n}\n/**\n * Uses the `postMessage()` API to inform any open windows/tabs when a cached\n * response has been updated.\n *\n * For efficiency's sake, the underlying response bodies are not compared;\n * only specific response headers are checked.\n *\n * @memberof module:workbox-broadcast-update\n */\nclass BroadcastCacheUpdate {\n /**\n * Construct a BroadcastCacheUpdate instance with a specific `channelName` to\n * broadcast messages on\n *\n * @param {Object} options\n * @param {Array<string>} [options.headersToCheck=['content-length', 'etag', 'last-modified']]\n * A list of headers that will be used to determine whether the responses\n * differ.\n * @param {string} [options.generatePayload] A function whose return value\n * will be used as the `payload` field in any cache update messLine truncated
@@ -0,0 +1,191 @@
this.workbox = this.workbox || {};
this.workbox.cacheableResponse = (function (exports, assert_js, WorkboxError_js, getFriendlyURL_js, logger_js) {
'use strict';
try {
self['workbox:cacheable-response:5.1.4'] && _();
} catch (e) {}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* This class allows you to set up rules determining what
* status codes and/or headers need to be present in order for a
* [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
* to be considered cacheable.
*
* @memberof module:workbox-cacheable-response
*/
class CacheableResponse {
/**
* To construct a new CacheableResponse instance you must provide at least
* one of the `config` properties.
*
* If both `statuses` and `headers` are specified, then both conditions must
* be met for the `Response` to be considered cacheable.
*
* @param {Object} config
* @param {Array<number>} [config.statuses] One or more status codes that a
* `Response` can have and be considered cacheable.
* @param {Object<string,string>} [config.headers] A mapping of header names
* and expected values that a `Response` can have and be considered cacheable.
* If multiple headers are provided, only one needs to be present.
*/
constructor(config = {}) {
{
if (!(config.statuses || config.headers)) {
throw new WorkboxError_js.WorkboxError('statuses-or-headers-required', {
moduleName: 'workbox-cacheable-response',
className: 'CacheableResponse',
funcName: 'constructor'
});
}
if (config.statuses) {
assert_js.assert.isArray(config.statuses, {
moduleName: 'workbox-cacheable-response',
className: 'CacheableResponse',
funcName: 'constructor',
paramName: 'config.statuses'
});
}
if (config.headers) {
assert_js.assert.isType(config.headers, 'object', {
moduleName: 'workbox-cacheable-response',
className: 'CacheableResponse',
funcName: 'constructor',
paramName: 'config.headers'
});
}
}
this._statuses = config.statuses;
this._headers = config.headers;
}
/**
* Checks a response to see whether it's cacheable or not, based on this
* object's configuration.
*
* @param {Response} response The response whose cacheability is being
* checked.
* @return {boolean} `true` if the `Response` is cacheable, and `false`
* otherwise.
*/
isResponseCacheable(response) {
{
assert_js.assert.isInstance(response, Response, {
moduleName: 'workbox-cacheable-response',
className: 'CacheableResponse',
funcName: 'isResponseCacheable',
paramName: 'response'
});
}
let cacheable = true;
if (this._statuses) {
cacheable = this._statuses.includes(response.status);
}
if (this._headers && cacheable) {
cacheable = Object.keys(this._headers).some(headerName => {
return response.headers.get(headerName) === this._headers[headerName];
});
}
{
if (!cacheable) {
logger_js.logger.groupCollapsed(`The request for ` + `'${getFriendlyURL_js.getFriendlyURL(response.url)}' returned a response that does ` + `not meet the criteria for being cached.`);
logger_js.logger.groupCollapsed(`View cacheability criteria here.`);
logger_js.logger.log(`Cacheable statuses: ` + JSON.stringify(this._statuses));
logger_js.logger.log(`Cacheable headers: ` + JSON.stringify(this._headers, null, 2));
logger_js.logger.groupEnd();
const logFriendlyHeaders = {};
response.headers.forEach((value, key) => {
logFriendlyHeaders[key] = value;
});
logger_js.logger.groupCollapsed(`View response status and headers here.`);
logger_js.logger.log(`Response status: ` + response.status);
logger_js.logger.log(`Response headers: ` + JSON.stringify(logFriendlyHeaders, null, 2));
logger_js.logger.groupEnd();
logger_js.logger.groupCollapsed(`View full response details here.`);
logger_js.logger.log(response.headers);
logger_js.logger.log(response);
logger_js.logger.groupEnd();
logger_js.logger.groupEnd();
}
}
return cacheable;
}
}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* A class implementing the `cacheWillUpdate` lifecycle callback. This makes it
* easier to add in cacheability checks to requests made via Workbox's built-in
* strategies.
*
* @memberof module:workbox-cacheable-response
*/
class CacheableResponsePlugin {
/**
* To construct a new CacheableResponsePlugin instance you must provide at
* least one of the `config` properties.
*
* If both `statuses` and `headers` are specified, then both conditions must
* be met for the `Response` to be considered cacheable.
*
* @param {Object} config
* @param {Array<number>} [config.statuses] One or more status codes that a
* `Response` can have and be considered cacheable.
* @param {Object<string,string>} [config.headers] A mapping of header names
* and expected values that a `Response` can have and be considered cacheable.
* If multiple headers are provided, only one needs to be present.
*/
constructor(config) {
/**
* @param {Object} options
* @param {Response} options.response
* @return {Response|null}
* @private
*/
this.cacheWillUpdate = async ({
response
}) => {
if (this._cacheableResponse.isResponseCacheable(response)) {
return response;
}
return null;
};
this._cacheableResponse = new CacheableResponse(config);
}
}
exports.CacheableResponse = CacheableResponse;
exports.CacheableResponsePlugin = CacheableResponsePlugin;
return exports;
}({}, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private));
//# sourceMappingURL=workbox-cacheable-response.dev.js.map
@@ -0,0 +1 @@
{"version":3,"file":"workbox-cacheable-response.dev.js","sources":["../_version.js","../CacheableResponse.js","../CacheableResponsePlugin.js"],"sourcesContent":["\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:cacheable-response:5.1.4'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport { getFriendlyURL } from 'workbox-core/_private/getFriendlyURL.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport './_version.js';\n/**\n * This class allows you to set up rules determining what\n * status codes and/or headers need to be present in order for a\n * [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)\n * to be considered cacheable.\n *\n * @memberof module:workbox-cacheable-response\n */\nclass CacheableResponse {\n /**\n * To construct a new CacheableResponse instance you must provide at least\n * one of the `config` properties.\n *\n * If both `statuses` and `headers` are specified, then both conditions must\n * be met for the `Response` to be considered cacheable.\n *\n * @param {Object} config\n * @param {Array<number>} [config.statuses] One or more status codes that a\n * `Response` can have and be considered cacheable.\n * @param {Object<string,string>} [config.headers] A mapping of header names\n * and expected values that a `Response` can have and be considered cacheable.\n * If multiple headers are provided, only one needs to be present.\n */\n constructor(config = {}) {\n if (process.env.NODE_ENV !== 'production') {\n if (!(config.statuses || config.headers)) {\n throw new WorkboxError('statuses-or-headers-required', {\n moduleName: 'workbox-cacheable-response',\n className: 'CacheableResponse',\n funcName: 'constructor',\n });\n }\n if (config.statuses) {\n assert.isArray(config.statuses, {\n moduleName: 'workbox-cacheable-response',\n className: 'CacheableResponse',\n funcName: 'constructor',\n paramName: 'config.statuses',\n });\n }\n if (config.headers) {\n assert.isType(config.headers, 'object', {\n moduleName: 'workbox-cacheable-response',\n className: 'CacheableResponse',\n funcName: 'constructor',\n paramName: 'config.headers',\n });\n }\n }\n this._statuses = config.statuses;\n this._headers = config.headers;\n }\n /**\n * Checks a response to see whether it's cacheable or not, based on this\n * object's configuration.\n *\n * @param {Response} response The response whose cacheability is being\n * checked.\n * @return {boolean} `true` if the `Response` is cacheable, and `false`\n * otherwise.\n */\n isResponseCacheable(response) {\n if (process.env.NODE_ENV !== 'production') {\n assert.isInstance(response, Response, {\n moduleName: 'workbox-cacheable-response',\n className: 'CacheableResponse',\n funcName: 'isResponseCacheable',\n paramName: 'response',\n });\n }\n let cacheable = true;\n if (this._statuses) {\n cacheable = this._statuses.includes(response.status);\n }\n if (this._headers && cacheable) {\n cacheable = Object.keys(this._headers).some((headerName) => {\n return response.headers.get(headerName) === this._headers[headerName];\n });\n }\n if (process.env.NODE_ENV !== 'production') {\n if (!cacheable) {\n logger.groupCollapsed(`The request for ` +\n `'${getFriendlyURL(response.url)}' returned a response that does ` +\n `not meet the criteria for being cached.`);\n logger.groupCollapsed(`View cacheability criteria here.`);\n logger.log(`Cacheable statuses: ` +\n JSON.stringify(this._statuses));\n logger.log(`Cacheable headers: ` +\n JSON.stringify(this._headers, null, 2));\n logger.groupEnd();\n const logFriendlyHeaders = {};\n response.headers.forEach((value, key) => {\n logFriendlyHeaders[key] = value;\n });\n logger.groupCollapsed(`View response status and headers here.`);\n logger.log(`Response status: ` + responsLine truncated
@@ -0,0 +1,2 @@
this.workbox=this.workbox||{},this.workbox.cacheableResponse=function(s){"use strict";try{self["workbox:cacheable-response:5.1.4"]&&_()}catch(s){}class t{constructor(s={}){this.s=s.statuses,this.t=s.headers}isResponseCacheable(s){let t=!0;return this.s&&(t=this.s.includes(s.status)),this.t&&t&&(t=Object.keys(this.t).some(t=>s.headers.get(t)===this.t[t])),t}}return s.CacheableResponse=t,s.CacheableResponsePlugin=class{constructor(s){this.cacheWillUpdate=async({response:s})=>this.i.isResponseCacheable(s)?s:null,this.i=new t(s)}},s}({});
//# sourceMappingURL=workbox-cacheable-response.prod.js.map
@@ -0,0 +1 @@
{"version":3,"file":"workbox-cacheable-response.prod.js","sources":["../_version.js","../CacheableResponse.js","../CacheableResponsePlugin.js"],"sourcesContent":["\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:cacheable-response:5.1.4'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport { getFriendlyURL } from 'workbox-core/_private/getFriendlyURL.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport './_version.js';\n/**\n * This class allows you to set up rules determining what\n * status codes and/or headers need to be present in order for a\n * [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)\n * to be considered cacheable.\n *\n * @memberof module:workbox-cacheable-response\n */\nclass CacheableResponse {\n /**\n * To construct a new CacheableResponse instance you must provide at least\n * one of the `config` properties.\n *\n * If both `statuses` and `headers` are specified, then both conditions must\n * be met for the `Response` to be considered cacheable.\n *\n * @param {Object} config\n * @param {Array<number>} [config.statuses] One or more status codes that a\n * `Response` can have and be considered cacheable.\n * @param {Object<string,string>} [config.headers] A mapping of header names\n * and expected values that a `Response` can have and be considered cacheable.\n * If multiple headers are provided, only one needs to be present.\n */\n constructor(config = {}) {\n if (process.env.NODE_ENV !== 'production') {\n if (!(config.statuses || config.headers)) {\n throw new WorkboxError('statuses-or-headers-required', {\n moduleName: 'workbox-cacheable-response',\n className: 'CacheableResponse',\n funcName: 'constructor',\n });\n }\n if (config.statuses) {\n assert.isArray(config.statuses, {\n moduleName: 'workbox-cacheable-response',\n className: 'CacheableResponse',\n funcName: 'constructor',\n paramName: 'config.statuses',\n });\n }\n if (config.headers) {\n assert.isType(config.headers, 'object', {\n moduleName: 'workbox-cacheable-response',\n className: 'CacheableResponse',\n funcName: 'constructor',\n paramName: 'config.headers',\n });\n }\n }\n this._statuses = config.statuses;\n this._headers = config.headers;\n }\n /**\n * Checks a response to see whether it's cacheable or not, based on this\n * object's configuration.\n *\n * @param {Response} response The response whose cacheability is being\n * checked.\n * @return {boolean} `true` if the `Response` is cacheable, and `false`\n * otherwise.\n */\n isResponseCacheable(response) {\n if (process.env.NODE_ENV !== 'production') {\n assert.isInstance(response, Response, {\n moduleName: 'workbox-cacheable-response',\n className: 'CacheableResponse',\n funcName: 'isResponseCacheable',\n paramName: 'response',\n });\n }\n let cacheable = true;\n if (this._statuses) {\n cacheable = this._statuses.includes(response.status);\n }\n if (this._headers && cacheable) {\n cacheable = Object.keys(this._headers).some((headerName) => {\n return response.headers.get(headerName) === this._headers[headerName];\n });\n }\n if (process.env.NODE_ENV !== 'production') {\n if (!cacheable) {\n logger.groupCollapsed(`The request for ` +\n `'${getFriendlyURL(response.url)}' returned a response that does ` +\n `not meet the criteria for being cached.`);\n logger.groupCollapsed(`View cacheability criteria here.`);\n logger.log(`Cacheable statuses: ` +\n JSON.stringify(this._statuses));\n logger.log(`Cacheable headers: ` +\n JSON.stringify(this._headers, null, 2));\n logger.groupEnd();\n const logFriendlyHeaders = {};\n response.headers.forEach((value, key) => {\n logFriendlyHeaders[key] = value;\n });\n logger.groupCollapsed(`View response status and headers here.`);\n logger.log(`Response status: ` + responLine truncated
File diff suppressed because it is too large. Load diff
@@ -0,0 +1 @@
{"version":3,"file":"workbox-core.dev.js","sources":["../_version.js","../_private/logger.js","../models/messages/messages.js","../models/messages/messageGenerator.js","../_private/WorkboxError.js","../_private/assert.js","../models/quotaErrorCallbacks.js","../registerQuotaErrorCallback.js","../_private/cacheNames.js","../_private/executeQuotaErrorCallbacks.js","../_private/getFriendlyURL.js","../utils/pluginUtils.js","../_private/cacheWrapper.js","../_private/canConstructReadableStream.js","../_private/canConstructResponseFromBodyStream.js","../_private/dontWaitFor.js","../_private/DBWrapper.js","../_private/Deferred.js","../_private/deleteDatabase.js","../_private/fetchWrapper.js","../_private/timeout.js","../_private/resultingClientExists.js","../_private.js","../cacheNames.js","../copyResponse.js","../clientsClaim.js","../setCacheNameDetails.js","../skipWaiting.js"],"sourcesContent":["\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:core:5.1.4'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2019 Google LLC\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\nconst logger = (process.env.NODE_ENV === 'production' ? null : (() => {\n // Don't overwrite this value if it's already set.\n // See https://github.com/GoogleChrome/workbox/pull/2284#issuecomment-560470923\n if (!('__WB_DISABLE_DEV_LOGS' in self)) {\n self.__WB_DISABLE_DEV_LOGS = false;\n }\n let inGroup = false;\n const methodToColorMap = {\n debug: `#7f8c8d`,\n log: `#2ecc71`,\n warn: `#f39c12`,\n error: `#c0392b`,\n groupCollapsed: `#3498db`,\n groupEnd: null,\n };\n const print = function (method, args) {\n if (self.__WB_DISABLE_DEV_LOGS) {\n return;\n }\n if (method === 'groupCollapsed') {\n // Safari doesn't print all console.groupCollapsed() arguments:\n // https://bugs.webkit.org/show_bug.cgi?id=182754\n if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) {\n console[method](...args);\n return;\n }\n }\n const styles = [\n `background: ${methodToColorMap[method]}`,\n `border-radius: 0.5em`,\n `color: white`,\n `font-weight: bold`,\n `padding: 2px 0.5em`,\n ];\n // When in a group, the workbox prefix is not displayed.\n const logPrefix = inGroup ? [] : ['%cworkbox', styles.join(';')];\n console[method](...logPrefix, ...args);\n if (method === 'groupCollapsed') {\n inGroup = true;\n }\n if (method === 'groupEnd') {\n inGroup = false;\n }\n };\n const api = {};\n const loggerMethods = Object.keys(methodToColorMap);\n for (const key of loggerMethods) {\n const method = key;\n api[method] = (...args) => {\n print(method, args);\n };\n }\n return api;\n})());\nexport { logger };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../../_version.js';\nexport const messages = {\n 'invalid-value': ({ paramName, validValueDescription, value }) => {\n if (!paramName || !validValueDescription) {\n throw new Error(`Unexpected input to 'invalid-value' error.`);\n }\n return `The '${paramName}' parameter was given a value with an ` +\n `unexpected value. ${validValueDescription} Received a value of ` +\n `${JSON.stringify(value)}.`;\n },\n 'not-an-array': ({ moduleName, className, funcName, paramName }) => {\n if (!moduleName || !className || !funcName || !paramName) {\n throw new Error(`Unexpected input to 'not-an-array' error.`);\n }\n return `The parameter '${paramName}' passed into ` +\n `'${moduleName}.${className}.${funcName}()' must be an array.`;\n },\n 'incorrect-type': ({ expectedType, paramName, moduleName, className, funcName }) => {\n if (!expectedType || !paramName || !moduleName || !funcName) {\n throw new Error(`Unexpected input to 'incorrect-type' error.`);\n }\n return `The parameter '${paramName}' passed into ` +\n `'${moduleName}.${className ? (className + '.') : ''}` +\n `${funcName}()' must be of type ${expectedType}.`;\n },\n 'incorrect-class': ({ expectedClass, paramName, moduleName, className, funcName, isReturnValueProblem }) => {\n if (!expectedClass || !moduleName || !funcName) {\n throw new Error(`Unexpected input to 'incorrect-class' error.`);\n }\n if (isReturnValueProblem) {\n return `The return value from ` +\n `'${moduleName}.${classNaLine truncated
@@ -0,0 +1,2 @@
this.workbox=this.workbox||{},this.workbox.core=function(e){"use strict";try{self["workbox:core:5.1.4"]&&_()}catch(e){}const t=(e,...t)=>{let n=e;return t.length>0&&(n+=" :: "+JSON.stringify(t)),n};class n extends Error{constructor(e,n){super(t(e,n)),this.name=e,this.details=n}}const s=new Set;const r={googleAnalytics:"googleAnalytics",precache:"precache-v2",prefix:"workbox",runtime:"runtime",suffix:"undefined"!=typeof registration?registration.scope:""},i=e=>[r.prefix,e,r.suffix].filter(e=>e&&e.length>0).join("-"),o={updateDetails:e=>{(e=>{for(const t of Object.keys(r))e(t)})(t=>{"string"==typeof e[t]&&(r[t]=e[t])})},getGoogleAnalyticsName:e=>e||i(r.googleAnalytics),getPrecacheName:e=>e||i(r.precache),getPrefix:()=>r.prefix,getRuntimeName:e=>e||i(r.runtime),getSuffix:()=>r.suffix};async function a(){for(const e of s)await e()}const c=e=>new URL(String(e),location.href).href.replace(new RegExp("^"+location.origin),""),u=(e,t)=>e.filter(e=>t in e),l=async({request:e,mode:t,plugins:n=[]})=>{const s=u(n,"cacheKeyWillBeUsed");let r=e;for(const e of s)r=await e.cacheKeyWillBeUsed.call(e,{mode:t,request:r}),"string"==typeof r&&(r=new Request(r));return r},f=async({cacheName:e,request:t,event:n,matchOptions:s,plugins:r=[]})=>{const i=await self.caches.open(e),o=await l({plugins:r,request:t,mode:"read"});let a=await i.match(o,s);for(const t of r)if("cachedResponseWillBeUsed"in t){const r=t.cachedResponseWillBeUsed;a=await r.call(t,{cacheName:e,event:n,matchOptions:s,cachedResponse:a,request:o})}return a},h={put:async({cacheName:e,request:t,response:s,event:r,plugins:i=[],matchOptions:o})=>{const h=await l({plugins:i,request:t,mode:"write"});if(!s)throw new n("cache-put-with-no-response",{url:c(h.url)});const w=await(async({request:e,response:t,event:n,plugins:s=[]})=>{let r=t,i=!1;for(const t of s)if("cacheWillUpdate"in t){i=!0;const s=t.cacheWillUpdate;if(r=await s.call(t,{request:e,response:r,event:n}),!r)break}return i||(r=r&&200===r.status?r:void 0),r||null})({event:r,plugins:i,response:s,request:h});if(!w)return;const p=await self.caches.open(e),d=u(i,"cacheDidUpdate"),g=d.length>0?await f({cacheName:e,matchOptions:o,request:h}):null;try{await p.put(h,w)}catch(e){throw"QuotaExceededError"===e.name&&await a(),e}for(const t of d)await t.cacheDidUpdate.call(t,{cacheName:e,event:r,oldResponse:g,newResponse:w,request:h})},match:f};let w,p;function d(){if(void 0===p){const e=new Response("");if("body"in e)try{new Response(e.body),p=!0}catch(e){p=!1}p=!1}return p}class g{constructor(e,t,{onupgradeneeded:n,onversionchange:s}={}){this.t=null,this.s=e,this.i=t,this.o=n,this.u=s||(()=>this.close())}get db(){return this.t}async open(){if(!this.t)return this.t=await new Promise((e,t)=>{let n=!1;setTimeout(()=>{n=!0,t(new Error("The open request was blocked and timed out"))},this.OPEN_TIMEOUT);const s=indexedDB.open(this.s,this.i);s.onerror=()=>t(s.error),s.onupgradeneeded=e=>{n?(s.transaction.abort(),s.result.close()):"function"==typeof this.o&&this.o(e)},s.onsuccess=()=>{const t=s.result;n?t.close():(t.onversionchange=this.u.bind(this),e(t))}}),this}async getKey(e,t){return(await this.getAllKeys(e,t,1))[0]}async getAll(e,t,n){return await this.getAllMatching(e,{query:t,count:n})}async getAllKeys(e,t,n){return(await this.getAllMatching(e,{query:t,count:n,includeKeys:!0})).map(e=>e.key)}async getAllMatching(e,{index:t,query:n=null,direction:s="next",count:r,includeKeys:i=!1}={}){return await this.transaction([e],"readonly",(o,a)=>{const c=o.objectStore(e),u=t?c.index(t):c,l=[],f=u.openCursor(n,s);f.onsuccess=()=>{const e=f.result;e?(l.push(i?e:e.value),r&&l.length>=r?a(l):e.continue()):a(l)}})}async transaction(e,t,n){return await this.open(),await new Promise((s,r)=>{const i=this.t.transaction(e,t);i.onabort=()=>r(i.error),i.oncomplete=()=>s(),n(i,e=>s(e))})}async l(e,t,n,...s){return await this.transaction([t],n,(n,r)=>{const i=n.objectStore(t),o=i[e].apply(i,s);o.onsuccess=()=>r(o.result)})}close(){this.t&&(this.t.close(),this.t=null)}}g.prototype.OPEN_TIMEOUT=2e3;const y={readonly:["get","count","getKey","getAll","getAllKeys"],readwrite:["add","put","clear","delete"]};for(const[e,t]of Object.entries(y))for(const n of t)n in IDBObjectStore.prototype&&(g.prototype[n]=async function(t,...s){return await this.l(n,t,e,...s)});const m={fetch:async({request:e,fetchOptions:t,event:s,plugins:r=[]})=>{if("string"==typeof e&&(e=new Request(e)),s instanceof FetchEvent&&s.preloadResponse){const e=await s.preloadResponse;if(e)return e}const i=u(r,"fetchDidFail"),o=i.length>0?e.clone():null;try{for(const t of r)if("requestWillFetch"in t){const n=t.requestWillFetch,r=e.clone();e=await n.call(t,{request:r,event:s})}}catch(e){throw new n("plugin-error-request-will-fetch",{thrownError:e})}const a=e.clone();try{let n;n="navigate"===e.mode?await fetch(e):await fetch(e,t);for(const e of r)"fetchDidSucceed"in e&&(n=await e.fetchDidSucceed.call(e,{event:s,request:a,response:n}));return n}catch(e){for(const t of i)await t.fetchDidFail.call(t,{Line truncated
//# sourceMappingURL=workbox-core.prod.js.map
@@ -0,0 +1 @@
{"version":3,"file":"workbox-core.prod.js","sources":["../_version.js","../_private/logger.js","../models/messages/messageGenerator.js","../_private/WorkboxError.js","../_private/assert.js","../models/quotaErrorCallbacks.js","../_private/cacheNames.js","../_private/executeQuotaErrorCallbacks.js","../_private/getFriendlyURL.js","../utils/pluginUtils.js","../_private/cacheWrapper.js","../_private/canConstructReadableStream.js","../_private/canConstructResponseFromBodyStream.js","../_private/DBWrapper.js","../_private/deleteDatabase.js","../_private/fetchWrapper.js","../_private/timeout.js","../_private/dontWaitFor.js","../_private/Deferred.js","../_private/resultingClientExists.js","../cacheNames.js","../clientsClaim.js","../copyResponse.js","../registerQuotaErrorCallback.js","../setCacheNameDetails.js","../skipWaiting.js"],"sourcesContent":["\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:core:5.1.4'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2019 Google LLC\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\nconst logger = (process.env.NODE_ENV === 'production' ? null : (() => {\n // Don't overwrite this value if it's already set.\n // See https://github.com/GoogleChrome/workbox/pull/2284#issuecomment-560470923\n if (!('__WB_DISABLE_DEV_LOGS' in self)) {\n self.__WB_DISABLE_DEV_LOGS = false;\n }\n let inGroup = false;\n const methodToColorMap = {\n debug: `#7f8c8d`,\n log: `#2ecc71`,\n warn: `#f39c12`,\n error: `#c0392b`,\n groupCollapsed: `#3498db`,\n groupEnd: null,\n };\n const print = function (method, args) {\n if (self.__WB_DISABLE_DEV_LOGS) {\n return;\n }\n if (method === 'groupCollapsed') {\n // Safari doesn't print all console.groupCollapsed() arguments:\n // https://bugs.webkit.org/show_bug.cgi?id=182754\n if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) {\n console[method](...args);\n return;\n }\n }\n const styles = [\n `background: ${methodToColorMap[method]}`,\n `border-radius: 0.5em`,\n `color: white`,\n `font-weight: bold`,\n `padding: 2px 0.5em`,\n ];\n // When in a group, the workbox prefix is not displayed.\n const logPrefix = inGroup ? [] : ['%cworkbox', styles.join(';')];\n console[method](...logPrefix, ...args);\n if (method === 'groupCollapsed') {\n inGroup = true;\n }\n if (method === 'groupEnd') {\n inGroup = false;\n }\n };\n const api = {};\n const loggerMethods = Object.keys(methodToColorMap);\n for (const key of loggerMethods) {\n const method = key;\n api[method] = (...args) => {\n print(method, args);\n };\n }\n return api;\n})());\nexport { logger };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { messages } from './messages.js';\nimport '../../_version.js';\nconst fallback = (code, ...args) => {\n let msg = code;\n if (args.length > 0) {\n msg += ` :: ${JSON.stringify(args)}`;\n }\n return msg;\n};\nconst generatorFunction = (code, details = {}) => {\n const message = messages[code];\n if (!message) {\n throw new Error(`Unable to find message for code '${code}'.`);\n }\n return message(details);\n};\nexport const messageGenerator = (process.env.NODE_ENV === 'production') ?\n fallback : generatorFunction;\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { messageGenerator } from '../models/messages/messageGenerator.js';\nimport '../_version.js';\n/**\n * Workbox errors should be thrown with this class.\n * This allows use to ensure the type easily in tests,\n * helps developers identify errors from workbox\n * easily and allows use to optimise error\n * messages correctly.\n *\n * @private\n */\nclass WorkboxError extends Error {\n /**\n *\n * @param {string} errorCode The error code that\n * identifies this particular error.\n * @param {Object=} details Any relevant arguments\n * that will help developers identify issues should\n * be added as a key on the context object.\n */\n constructor(errorCode, details) {\n const message = messageGenerator(errorCode, details);\n super(message);\n this.name = errorCode;\n this.details = details;\n }\n}\nexport { WorkboxError };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by aLine truncated
@@ -0,0 +1,649 @@
this.workbox = this.workbox || {};
this.workbox.expiration = (function (exports, assert_js, dontWaitFor_js, logger_js, WorkboxError_js, DBWrapper_js, deleteDatabase_js, cacheNames_js, getFriendlyURL_js, registerQuotaErrorCallback_js) {
'use strict';
try {
self['workbox:expiration:5.1.4'] && _();
} catch (e) {}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
const DB_NAME = 'workbox-expiration';
const OBJECT_STORE_NAME = 'cache-entries';
const normalizeURL = unNormalizedUrl => {
const url = new URL(unNormalizedUrl, location.href);
url.hash = '';
return url.href;
};
/**
* Returns the timestamp model.
*
* @private
*/
class CacheTimestampsModel {
/**
*
* @param {string} cacheName
*
* @private
*/
constructor(cacheName) {
this._cacheName = cacheName;
this._db = new DBWrapper_js.DBWrapper(DB_NAME, 1, {
onupgradeneeded: event => this._handleUpgrade(event)
});
}
/**
* Should perform an upgrade of indexedDB.
*
* @param {Event} event
*
* @private
*/
_handleUpgrade(event) {
const db = event.target.result; // TODO(philipwalton): EdgeHTML doesn't support arrays as a keyPath, so we
// have to use the `id` keyPath here and create our own values (a
// concatenation of `url + cacheName`) instead of simply using
// `keyPath: ['url', 'cacheName']`, which is supported in other browsers.
const objStore = db.createObjectStore(OBJECT_STORE_NAME, {
keyPath: 'id'
}); // TODO(philipwalton): once we don't have to support EdgeHTML, we can
// create a single index with the keyPath `['cacheName', 'timestamp']`
// instead of doing both these indexes.
objStore.createIndex('cacheName', 'cacheName', {
unique: false
});
objStore.createIndex('timestamp', 'timestamp', {
unique: false
}); // Previous versions of `workbox-expiration` used `this._cacheName`
// as the IDBDatabase name.
deleteDatabase_js.deleteDatabase(this._cacheName);
}
/**
* @param {string} url
* @param {number} timestamp
*
* @private
*/
async setTimestamp(url, timestamp) {
url = normalizeURL(url);
const entry = {
url,
timestamp,
cacheName: this._cacheName,
// Creating an ID from the URL and cache name won't be necessary once
// Edge switches to Chromium and all browsers we support work with
// array keyPaths.
id: this._getId(url)
};
await this._db.put(OBJECT_STORE_NAME, entry);
}
/**
* Returns the timestamp stored for a given URL.
*
* @param {string} url
* @return {number}
*
* @private
*/
async getTimestamp(url) {
const entry = await this._db.get(OBJECT_STORE_NAME, this._getId(url));
return entry.timestamp;
}
/**
* Iterates through all the entries in the object store (from newest to
* oldest) and removes entries once either `maxCount` is reached or the
* entry's timestamp is less than `minTimestamp`.
*
* @param {number} minTimestamp
* @param {number} maxCount
* @return {Array<string>}
*
* @private
*/
async expireEntries(minTimestamp, maxCount) {
const entriesToDelete = await this._db.transaction(OBJECT_STORE_NAME, 'readwrite', (txn, done) => {
const store = txn.objectStore(OBJECT_STORE_NAME);
const request = store.index('timestamp').openCursor(null, 'prev');
const entriesToDelete = [];
let entriesNotDeletedCount = 0;
request.onsuccess = () => {
const cursor = request.result;
if (cursor) {
const result = cursor.value; // TODO(philipwalton): once we can use a multi-key index, we
// won't have to check `cacheName` here.
if (result.cacheName === this._cacheName) {
// Delete an entry if it's older than the max age or
// if we already have the max number allowed.
if (minTimestamp && result.timestamp < minTimestamp || maxCount && entriesNotDeletedCount >= maxCount) {
// TODO(philipwalton): we should be able to delete the
// entry right here, but doing so causes an iteration
// bug in Safari stable (fixed in TP). Instead we can
// store the keys of the entries to delete, and then
// delete the separate transactions.
// https://github.com/GoogleChrome/workbox/issues/1978
// cursor.delete();
// We only need to return the URL, not the whole entry.
entriesToDelete.push(cursor.value);
} else {
entriesNotDeletedCount++;
}
}
cursor.continue();
} else {
done(entriesToDelete);
}
};
}); // TODO(philipwalton): once the Safari bug in the following issue is fixed,
// we should be able to remove this loop and do the entry deletion in the
// cursor loop above:
// https://github.com/GoogleChrome/workbox/issues/1978
const urlsDeleted = [];
for (const entry of entriesToDelete) {
await this._db.delete(OBJECT_STORE_NAME, entry.id);
urlsDeleted.push(entry.url);
}
return urlsDeleted;
}
/**
* Takes a URL and returns an ID that will be unique in the object store.
*
* @param {string} url
* @return {string}
*
* @private
*/
_getId(url) {
// Creating an ID from the URL and cache name won't be necessary once
// Edge switches to Chromium and all browsers we support work with
// array keyPaths.
return this._cacheName + '|' + normalizeURL(url);
}
}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* The `CacheExpiration` class allows you define an expiration and / or
* limit on the number of responses stored in a
* [`Cache`](https://developer.mozilla.org/en-US/docs/Web/API/Cache).
*
* @memberof module:workbox-expiration
*/
class CacheExpiration {
/**
* To construct a new CacheExpiration instance you must provide at least
* one of the `config` properties.
*
* @param {string} cacheName Name of the cache to apply restrictions to.
* @param {Object} config
* @param {number} [config.maxEntries] The maximum number of entries to cache.
* Entries used the least will be removed as the maximum is reached.
* @param {number} [config.maxAgeSeconds] The maximum age of an entry before
* it's treated as stale and removed.
*/
constructor(cacheName, config = {}) {
this._isRunning = false;
this._rerunRequested = false;
{
assert_js.assert.isType(cacheName, 'string', {
moduleName: 'workbox-expiration',
className: 'CacheExpiration',
funcName: 'constructor',
paramName: 'cacheName'
});
if (!(config.maxEntries || config.maxAgeSeconds)) {
throw new WorkboxError_js.WorkboxError('max-entries-or-age-required', {
moduleName: 'workbox-expiration',
className: 'CacheExpiration',
funcName: 'constructor'
});
}
if (config.maxEntries) {
assert_js.assert.isType(config.maxEntries, 'number', {
moduleName: 'workbox-expiration',
className: 'CacheExpiration',
funcName: 'constructor',
paramName: 'config.maxEntries'
}); // TODO: Assert is positive
}
if (config.maxAgeSeconds) {
assert_js.assert.isType(config.maxAgeSeconds, 'number', {
moduleName: 'workbox-expiration',
className: 'CacheExpiration',
funcName: 'constructor',
paramName: 'config.maxAgeSeconds'
}); // TODO: Assert is positive
}
}
this._maxEntries = config.maxEntries;
this._maxAgeSeconds = config.maxAgeSeconds;
this._cacheName = cacheName;
this._timestampModel = new CacheTimestampsModel(cacheName);
}
/**
* Expires entries for the given cache and given criteria.
*/
async expireEntries() {
if (this._isRunning) {
this._rerunRequested = true;
return;
}
this._isRunning = true;
const minTimestamp = this._maxAgeSeconds ? Date.now() - this._maxAgeSeconds * 1000 : 0;
const urlsExpired = await this._timestampModel.expireEntries(minTimestamp, this._maxEntries); // Delete URLs from the cache
const cache = await self.caches.open(this._cacheName);
for (const url of urlsExpired) {
await cache.delete(url);
}
{
if (urlsExpired.length > 0) {
logger_js.logger.groupCollapsed(`Expired ${urlsExpired.length} ` + `${urlsExpired.length === 1 ? 'entry' : 'entries'} and removed ` + `${urlsExpired.length === 1 ? 'it' : 'them'} from the ` + `'${this._cacheName}' cache.`);
logger_js.logger.log(`Expired the following ${urlsExpired.length === 1 ? 'URL' : 'URLs'}:`);
urlsExpired.forEach(url => logger_js.logger.log(` ${url}`));
logger_js.logger.groupEnd();
} else {
logger_js.logger.debug(`Cache expiration ran and found no entries to remove.`);
}
}
this._isRunning = false;
if (this._rerunRequested) {
this._rerunRequested = false;
dontWaitFor_js.dontWaitFor(this.expireEntries());
}
}
/**
* Update the timestamp for the given URL. This ensures the when
* removing entries based on maximum entries, most recently used
* is accurate or when expiring, the timestamp is up-to-date.
*
* @param {string} url
*/
async updateTimestamp(url) {
{
assert_js.assert.isType(url, 'string', {
moduleName: 'workbox-expiration',
className: 'CacheExpiration',
funcName: 'updateTimestamp',
paramName: 'url'
});
}
await this._timestampModel.setTimestamp(url, Date.now());
}
/**
* Can be used to check if a URL has expired or not before it's used.
*
* This requires a look up from IndexedDB, so can be slow.
*
* Note: This method will not remove the cached entry, call
* `expireEntries()` to remove indexedDB and Cache entries.
*
* @param {string} url
* @return {boolean}
*/
async isURLExpired(url) {
if (!this._maxAgeSeconds) {
{
throw new WorkboxError_js.WorkboxError(`expired-test-without-max-age`, {
methodName: 'isURLExpired',
paramName: 'maxAgeSeconds'
});
}
} else {
const timestamp = await this._timestampModel.getTimestamp(url);
const expireOlderThan = Date.now() - this._maxAgeSeconds * 1000;
return timestamp < expireOlderThan;
}
}
/**
* Removes the IndexedDB object store used to keep track of cache expiration
* metadata.
*/
async delete() {
// Make sure we don't attempt another rerun if we're called in the middle of
// a cache expiration.
this._rerunRequested = false;
await this._timestampModel.expireEntries(Infinity); // Expires all.
}
}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* This plugin can be used in the Workbox APIs to regularly enforce a
* limit on the age and / or the number of cached requests.
*
* Whenever a cached request is used or updated, this plugin will look
* at the used Cache and remove any old or extra requests.
*
* When using `maxAgeSeconds`, requests may be used *once* after expiring
* because the expiration clean up will not have occurred until *after* the
* cached request has been used. If the request has a "Date" header, then
* a light weight expiration check is performed and the request will not be
* used immediately.
*
* When using `maxEntries`, the entry least-recently requested will be removed
* from the cache first.
*
* @memberof module:workbox-expiration
*/
class ExpirationPlugin {
/**
* @param {Object} config
* @param {number} [config.maxEntries] The maximum number of entries to cache.
* Entries used the least will be removed as the maximum is reached.
* @param {number} [config.maxAgeSeconds] The maximum age of an entry before
* it's treated as stale and removed.
* @param {boolean} [config.purgeOnQuotaError] Whether to opt this cache in to
* automatic deletion if the available storage quota has been exceeded.
*/
constructor(config = {}) {
/**
* A "lifecycle" callback that will be triggered automatically by the
* `workbox-strategies` handlers when a `Response` is about to be returned
* from a [Cache](https://developer.mozilla.org/en-US/docs/Web/API/Cache) to
* the handler. It allows the `Response` to be inspected for freshness and
* prevents it from being used if the `Response`'s `Date` header value is
* older than the configured `maxAgeSeconds`.
*
* @param {Object} options
* @param {string} options.cacheName Name of the cache the response is in.
* @param {Response} options.cachedResponse The `Response` object that's been
* read from a cache and whose freshness should be checked.
* @return {Response} Either the `cachedResponse`, if it's
* fresh, or `null` if the `Response` is older than `maxAgeSeconds`.
*
* @private
*/
this.cachedResponseWillBeUsed = async ({
event,
request,
cacheName,
cachedResponse
}) => {
if (!cachedResponse) {
return null;
}
const isFresh = this._isResponseDateFresh(cachedResponse); // Expire entries to ensure that even if the expiration date has
// expired, it'll only be used once.
const cacheExpiration = this._getCacheExpiration(cacheName);
dontWaitFor_js.dontWaitFor(cacheExpiration.expireEntries()); // Update the metadata for the request URL to the current timestamp,
// but don't `await` it as we don't want to block the response.
const updateTimestampDone = cacheExpiration.updateTimestamp(request.url);
if (event) {
try {
event.waitUntil(updateTimestampDone);
} catch (error) {
{
// The event may not be a fetch event; only log the URL if it is.
if ('request' in event) {
logger_js.logger.warn(`Unable to ensure service worker stays alive when ` + `updating cache entry for ` + `'${getFriendlyURL_js.getFriendlyURL(event.request.url)}'.`);
}
}
}
}
return isFresh ? cachedResponse : null;
};
/**
* A "lifecycle" callback that will be triggered automatically by the
* `workbox-strategies` handlers when an entry is added to a cache.
*
* @param {Object} options
* @param {string} options.cacheName Name of the cache that was updated.
* @param {string} options.request The Request for the cached entry.
*
* @private
*/
this.cacheDidUpdate = async ({
cacheName,
request
}) => {
{
assert_js.assert.isType(cacheName, 'string', {
moduleName: 'workbox-expiration',
className: 'Plugin',
funcName: 'cacheDidUpdate',
paramName: 'cacheName'
});
assert_js.assert.isInstance(request, Request, {
moduleName: 'workbox-expiration',
className: 'Plugin',
funcName: 'cacheDidUpdate',
paramName: 'request'
});
}
const cacheExpiration = this._getCacheExpiration(cacheName);
await cacheExpiration.updateTimestamp(request.url);
await cacheExpiration.expireEntries();
};
{
if (!(config.maxEntries || config.maxAgeSeconds)) {
throw new WorkboxError_js.WorkboxError('max-entries-or-age-required', {
moduleName: 'workbox-expiration',
className: 'Plugin',
funcName: 'constructor'
});
}
if (config.maxEntries) {
assert_js.assert.isType(config.maxEntries, 'number', {
moduleName: 'workbox-expiration',
className: 'Plugin',
funcName: 'constructor',
paramName: 'config.maxEntries'
});
}
if (config.maxAgeSeconds) {
assert_js.assert.isType(config.maxAgeSeconds, 'number', {
moduleName: 'workbox-expiration',
className: 'Plugin',
funcName: 'constructor',
paramName: 'config.maxAgeSeconds'
});
}
}
this._config = config;
this._maxAgeSeconds = config.maxAgeSeconds;
this._cacheExpirations = new Map();
if (config.purgeOnQuotaError) {
registerQuotaErrorCallback_js.registerQuotaErrorCallback(() => this.deleteCacheAndMetadata());
}
}
/**
* A simple helper method to return a CacheExpiration instance for a given
* cache name.
*
* @param {string} cacheName
* @return {CacheExpiration}
*
* @private
*/
_getCacheExpiration(cacheName) {
if (cacheName === cacheNames_js.cacheNames.getRuntimeName()) {
throw new WorkboxError_js.WorkboxError('expire-custom-caches-only');
}
let cacheExpiration = this._cacheExpirations.get(cacheName);
if (!cacheExpiration) {
cacheExpiration = new CacheExpiration(cacheName, this._config);
this._cacheExpirations.set(cacheName, cacheExpiration);
}
return cacheExpiration;
}
/**
* @param {Response} cachedResponse
* @return {boolean}
*
* @private
*/
_isResponseDateFresh(cachedResponse) {
if (!this._maxAgeSeconds) {
// We aren't expiring by age, so return true, it's fresh
return true;
} // Check if the 'date' header will suffice a quick expiration check.
// See https://github.com/GoogleChromeLabs/sw-toolbox/issues/164 for
// discussion.
const dateHeaderTimestamp = this._getDateHeaderTimestamp(cachedResponse);
if (dateHeaderTimestamp === null) {
// Unable to parse date, so assume it's fresh.
return true;
} // If we have a valid headerTime, then our response is fresh iff the
// headerTime plus maxAgeSeconds is greater than the current time.
const now = Date.now();
return dateHeaderTimestamp >= now - this._maxAgeSeconds * 1000;
}
/**
* This method will extract the data header and parse it into a useful
* value.
*
* @param {Response} cachedResponse
* @return {number|null}
*
* @private
*/
_getDateHeaderTimestamp(cachedResponse) {
if (!cachedResponse.headers.has('date')) {
return null;
}
const dateHeader = cachedResponse.headers.get('date');
const parsedDate = new Date(dateHeader);
const headerTime = parsedDate.getTime(); // If the Date header was invalid for some reason, parsedDate.getTime()
// will return NaN.
if (isNaN(headerTime)) {
return null;
}
return headerTime;
}
/**
* This is a helper method that performs two operations:
*
* - Deletes *all* the underlying Cache instances associated with this plugin
* instance, by calling caches.delete() on your behalf.
* - Deletes the metadata from IndexedDB used to keep track of expiration
* details for each Cache instance.
*
* When using cache expiration, calling this method is preferable to calling
* `caches.delete()` directly, since this will ensure that the IndexedDB
* metadata is also cleanly removed and open IndexedDB instances are deleted.
*
* Note that if you're *not* using cache expiration for a given cache, calling
* `caches.delete()` and passing in the cache's name should be sufficient.
* There is no Workbox-specific method needed for cleanup in that case.
*/
async deleteCacheAndMetadata() {
// Do this one at a time instead of all at once via `Promise.all()` to
// reduce the chance of inconsistency if a promise rejects.
for (const [cacheName, cacheExpiration] of this._cacheExpirations) {
await self.caches.delete(cacheName);
await cacheExpiration.delete();
} // Reset this._cacheExpirations to its initial state.
this._cacheExpirations = new Map();
}
}
exports.CacheExpiration = CacheExpiration;
exports.ExpirationPlugin = ExpirationPlugin;
return exports;
}({}, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core));
//# sourceMappingURL=workbox-expiration.dev.js.map
@@ -0,0 +1 @@
{"version":3,"file":"workbox-expiration.dev.js","sources":["../_version.js","../models/CacheTimestampsModel.js","../CacheExpiration.js","../ExpirationPlugin.js"],"sourcesContent":["\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:expiration:5.1.4'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { DBWrapper } from 'workbox-core/_private/DBWrapper.js';\nimport { deleteDatabase } from 'workbox-core/_private/deleteDatabase.js';\nimport '../_version.js';\nconst DB_NAME = 'workbox-expiration';\nconst OBJECT_STORE_NAME = 'cache-entries';\nconst normalizeURL = (unNormalizedUrl) => {\n const url = new URL(unNormalizedUrl, location.href);\n url.hash = '';\n return url.href;\n};\n/**\n * Returns the timestamp model.\n *\n * @private\n */\nclass CacheTimestampsModel {\n /**\n *\n * @param {string} cacheName\n *\n * @private\n */\n constructor(cacheName) {\n this._cacheName = cacheName;\n this._db = new DBWrapper(DB_NAME, 1, {\n onupgradeneeded: (event) => this._handleUpgrade(event),\n });\n }\n /**\n * Should perform an upgrade of indexedDB.\n *\n * @param {Event} event\n *\n * @private\n */\n _handleUpgrade(event) {\n const db = event.target.result;\n // TODO(philipwalton): EdgeHTML doesn't support arrays as a keyPath, so we\n // have to use the `id` keyPath here and create our own values (a\n // concatenation of `url + cacheName`) instead of simply using\n // `keyPath: ['url', 'cacheName']`, which is supported in other browsers.\n const objStore = db.createObjectStore(OBJECT_STORE_NAME, { keyPath: 'id' });\n // TODO(philipwalton): once we don't have to support EdgeHTML, we can\n // create a single index with the keyPath `['cacheName', 'timestamp']`\n // instead of doing both these indexes.\n objStore.createIndex('cacheName', 'cacheName', { unique: false });\n objStore.createIndex('timestamp', 'timestamp', { unique: false });\n // Previous versions of `workbox-expiration` used `this._cacheName`\n // as the IDBDatabase name.\n deleteDatabase(this._cacheName);\n }\n /**\n * @param {string} url\n * @param {number} timestamp\n *\n * @private\n */\n async setTimestamp(url, timestamp) {\n url = normalizeURL(url);\n const entry = {\n url,\n timestamp,\n cacheName: this._cacheName,\n // Creating an ID from the URL and cache name won't be necessary once\n // Edge switches to Chromium and all browsers we support work with\n // array keyPaths.\n id: this._getId(url),\n };\n await this._db.put(OBJECT_STORE_NAME, entry);\n }\n /**\n * Returns the timestamp stored for a given URL.\n *\n * @param {string} url\n * @return {number}\n *\n * @private\n */\n async getTimestamp(url) {\n const entry = await this._db.get(OBJECT_STORE_NAME, this._getId(url));\n return entry.timestamp;\n }\n /**\n * Iterates through all the entries in the object store (from newest to\n * oldest) and removes entries once either `maxCount` is reached or the\n * entry's timestamp is less than `minTimestamp`.\n *\n * @param {number} minTimestamp\n * @param {number} maxCount\n * @return {Array<string>}\n *\n * @private\n */\n async expireEntries(minTimestamp, maxCount) {\n const entriesToDelete = await this._db.transaction(OBJECT_STORE_NAME, 'readwrite', (txn, done) => {\n const store = txn.objectStore(OBJECT_STORE_NAME);\n const request = store.index('timestamp').openCursor(null, 'prev');\n const entriesToDelete = [];\n let entriesNotDeletedCount = 0;\n request.onsuccess = () => {\n const cursor = request.result;\n if (cursor) {\n const result = cursor.value;\n // TODO(philipwalton): once we can use a multi-key index, we\n // won't have to check `cacheName` here.\n if (result.cacheName === this._cacheName) {\n // Delete an entry if it's older than the max age or\n // if we already have the max number allowed.\n if ((minTimestamp && result.timestamp < minTimestamp) ||\n (maxCount && entriesNotDeletedCount >= maxCount)) {\n // TODO(philipwalton): we should be able to delete the\n // entry right here, but doing so causes an iteration\n // bug in Safari stable (fixed in TP). Instead we can\n //Line truncated
@@ -0,0 +1,2 @@
this.workbox=this.workbox||{},this.workbox.expiration=function(t,e,s,i,a,n,h){"use strict";try{self["workbox:expiration:5.1.4"]&&_()}catch(t){}const r=t=>{const e=new URL(t,location.href);return e.hash="",e.href};class c{constructor(t){this.t=t,this.s=new i.DBWrapper("workbox-expiration",1,{onupgradeneeded:t=>this.i(t)})}i(t){const e=t.target.result.createObjectStore("cache-entries",{keyPath:"id"});e.createIndex("cacheName","cacheName",{unique:!1}),e.createIndex("timestamp","timestamp",{unique:!1}),a.deleteDatabase(this.t)}async setTimestamp(t,e){const s={url:t=r(t),timestamp:e,cacheName:this.t,id:this.h(t)};await this.s.put("cache-entries",s)}async getTimestamp(t){return(await this.s.get("cache-entries",this.h(t))).timestamp}async expireEntries(t,e){const s=await this.s.transaction("cache-entries","readwrite",(s,i)=>{const a=s.objectStore("cache-entries").index("timestamp").openCursor(null,"prev"),n=[];let h=0;a.onsuccess=()=>{const s=a.result;if(s){const i=s.value;i.cacheName===this.t&&(t&&i.timestamp<t||e&&h>=e?n.push(s.value):h++),s.continue()}else i(n)}}),i=[];for(const t of s)await this.s.delete("cache-entries",t.id),i.push(t.url);return i}h(t){return this.t+"|"+r(t)}}class o{constructor(t,e={}){this.o=!1,this.u=!1,this.l=e.maxEntries,this.m=e.maxAgeSeconds,this.t=t,this.p=new c(t)}async expireEntries(){if(this.o)return void(this.u=!0);this.o=!0;const t=this.m?Date.now()-1e3*this.m:0,s=await this.p.expireEntries(t,this.l),i=await self.caches.open(this.t);for(const t of s)await i.delete(t);this.o=!1,this.u&&(this.u=!1,e.dontWaitFor(this.expireEntries()))}async updateTimestamp(t){await this.p.setTimestamp(t,Date.now())}async isURLExpired(t){if(this.m){return await this.p.getTimestamp(t)<Date.now()-1e3*this.m}return!1}async delete(){this.u=!1,await this.p.expireEntries(1/0)}}return t.CacheExpiration=o,t.ExpirationPlugin=class{constructor(t={}){this.cachedResponseWillBeUsed=async({event:t,request:s,cacheName:i,cachedResponse:a})=>{if(!a)return null;const n=this.k(a),h=this.D(i);e.dontWaitFor(h.expireEntries());const r=h.updateTimestamp(s.url);if(t)try{t.waitUntil(r)}catch(t){}return n?a:null},this.cacheDidUpdate=async({cacheName:t,request:e})=>{const s=this.D(t);await s.updateTimestamp(e.url),await s.expireEntries()},this.N=t,this.m=t.maxAgeSeconds,this.g=new Map,t.purgeOnQuotaError&&h.registerQuotaErrorCallback(()=>this.deleteCacheAndMetadata())}D(t){if(t===n.cacheNames.getRuntimeName())throw new s.WorkboxError("expire-custom-caches-only");let e=this.g.get(t);return e||(e=new o(t,this.N),this.g.set(t,e)),e}k(t){if(!this.m)return!0;const e=this._(t);if(null===e)return!0;return e>=Date.now()-1e3*this.m}_(t){if(!t.headers.has("date"))return null;const e=t.headers.get("date"),s=new Date(e).getTime();return isNaN(s)?null:s}async deleteCacheAndMetadata(){for(const[t,e]of this.g)await self.caches.delete(t),await e.delete();this.g=new Map}},t}({},workbox.core._private,workbox.core._private,workbox.core._private,workbox.core._private,workbox.core._private,workbox.core);
//# sourceMappingURL=workbox-expiration.prod.js.map
@@ -0,0 +1 @@
{"version":3,"file":"workbox-expiration.prod.js","sources":["../_version.js","../models/CacheTimestampsModel.js","../CacheExpiration.js","../ExpirationPlugin.js"],"sourcesContent":["\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:expiration:5.1.4'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { DBWrapper } from 'workbox-core/_private/DBWrapper.js';\nimport { deleteDatabase } from 'workbox-core/_private/deleteDatabase.js';\nimport '../_version.js';\nconst DB_NAME = 'workbox-expiration';\nconst OBJECT_STORE_NAME = 'cache-entries';\nconst normalizeURL = (unNormalizedUrl) => {\n const url = new URL(unNormalizedUrl, location.href);\n url.hash = '';\n return url.href;\n};\n/**\n * Returns the timestamp model.\n *\n * @private\n */\nclass CacheTimestampsModel {\n /**\n *\n * @param {string} cacheName\n *\n * @private\n */\n constructor(cacheName) {\n this._cacheName = cacheName;\n this._db = new DBWrapper(DB_NAME, 1, {\n onupgradeneeded: (event) => this._handleUpgrade(event),\n });\n }\n /**\n * Should perform an upgrade of indexedDB.\n *\n * @param {Event} event\n *\n * @private\n */\n _handleUpgrade(event) {\n const db = event.target.result;\n // TODO(philipwalton): EdgeHTML doesn't support arrays as a keyPath, so we\n // have to use the `id` keyPath here and create our own values (a\n // concatenation of `url + cacheName`) instead of simply using\n // `keyPath: ['url', 'cacheName']`, which is supported in other browsers.\n const objStore = db.createObjectStore(OBJECT_STORE_NAME, { keyPath: 'id' });\n // TODO(philipwalton): once we don't have to support EdgeHTML, we can\n // create a single index with the keyPath `['cacheName', 'timestamp']`\n // instead of doing both these indexes.\n objStore.createIndex('cacheName', 'cacheName', { unique: false });\n objStore.createIndex('timestamp', 'timestamp', { unique: false });\n // Previous versions of `workbox-expiration` used `this._cacheName`\n // as the IDBDatabase name.\n deleteDatabase(this._cacheName);\n }\n /**\n * @param {string} url\n * @param {number} timestamp\n *\n * @private\n */\n async setTimestamp(url, timestamp) {\n url = normalizeURL(url);\n const entry = {\n url,\n timestamp,\n cacheName: this._cacheName,\n // Creating an ID from the URL and cache name won't be necessary once\n // Edge switches to Chromium and all browsers we support work with\n // array keyPaths.\n id: this._getId(url),\n };\n await this._db.put(OBJECT_STORE_NAME, entry);\n }\n /**\n * Returns the timestamp stored for a given URL.\n *\n * @param {string} url\n * @return {number}\n *\n * @private\n */\n async getTimestamp(url) {\n const entry = await this._db.get(OBJECT_STORE_NAME, this._getId(url));\n return entry.timestamp;\n }\n /**\n * Iterates through all the entries in the object store (from newest to\n * oldest) and removes entries once either `maxCount` is reached or the\n * entry's timestamp is less than `minTimestamp`.\n *\n * @param {number} minTimestamp\n * @param {number} maxCount\n * @return {Array<string>}\n *\n * @private\n */\n async expireEntries(minTimestamp, maxCount) {\n const entriesToDelete = await this._db.transaction(OBJECT_STORE_NAME, 'readwrite', (txn, done) => {\n const store = txn.objectStore(OBJECT_STORE_NAME);\n const request = store.index('timestamp').openCursor(null, 'prev');\n const entriesToDelete = [];\n let entriesNotDeletedCount = 0;\n request.onsuccess = () => {\n const cursor = request.result;\n if (cursor) {\n const result = cursor.value;\n // TODO(philipwalton): once we can use a multi-key index, we\n // won't have to check `cacheName` here.\n if (result.cacheName === this._cacheName) {\n // Delete an entry if it's older than the max age or\n // if we already have the max number allowed.\n if ((minTimestamp && result.timestamp < minTimestamp) ||\n (maxCount && entriesNotDeletedCount >= maxCount)) {\n // TODO(philipwalton): we should be able to delete the\n // entry right here, but doing so causes an iteration\n // bug in Safari stable (fixed in TP). Instead we can\n /Line truncated
@@ -0,0 +1,102 @@
this.workbox = this.workbox || {};
this.workbox.navigationPreload = (function (exports, logger_js) {
'use strict';
try {
self['workbox:navigation-preload:5.1.4'] && _();
} catch (e) {}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* @return {boolean} Whether or not the current browser supports enabling
* navigation preload.
*
* @memberof module:workbox-navigation-preload
*/
function isSupported() {
return Boolean(self.registration && self.registration.navigationPreload);
}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* If the browser supports Navigation Preload, then this will disable it.
*
* @memberof module:workbox-navigation-preload
*/
function disable() {
if (isSupported()) {
self.addEventListener('activate', event => {
event.waitUntil(self.registration.navigationPreload.disable().then(() => {
{
logger_js.logger.log(`Navigation preload is disabled.`);
}
}));
});
} else {
{
logger_js.logger.log(`Navigation preload is not supported in this browser.`);
}
}
}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* If the browser supports Navigation Preload, then this will enable it.
*
* @param {string} [headerValue] Optionally, allows developers to
* [override](https://developers.google.com/web/updates/2017/02/navigation-preload#changing_the_header)
* the value of the `Service-Worker-Navigation-Preload` header which will be
* sent to the server when making the navigation request.
*
* @memberof module:workbox-navigation-preload
*/
function enable(headerValue) {
if (isSupported()) {
self.addEventListener('activate', event => {
event.waitUntil(self.registration.navigationPreload.enable().then(() => {
// Defaults to Service-Worker-Navigation-Preload: true if not set.
if (headerValue) {
self.registration.navigationPreload.setHeaderValue(headerValue);
}
{
logger_js.logger.log(`Navigation preload is enabled.`);
}
}));
});
} else {
{
logger_js.logger.log(`Navigation preload is not supported in this browser.`);
}
}
}
exports.disable = disable;
exports.enable = enable;
exports.isSupported = isSupported;
return exports;
}({}, workbox.core._private));
//# sourceMappingURL=workbox-navigation-preload.dev.js.map
@@ -0,0 +1 @@
{"version":3,"file":"workbox-navigation-preload.dev.js","sources":["../_version.js","../isSupported.js","../disable.js","../enable.js"],"sourcesContent":["\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:navigation-preload:5.1.4'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport './_version.js';\n/**\n * @return {boolean} Whether or not the current browser supports enabling\n * navigation preload.\n *\n * @memberof module:workbox-navigation-preload\n */\nfunction isSupported() {\n return Boolean(self.registration && self.registration.navigationPreload);\n}\nexport { isSupported };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { isSupported } from './isSupported.js';\nimport './_version.js';\n/**\n * If the browser supports Navigation Preload, then this will disable it.\n *\n * @memberof module:workbox-navigation-preload\n */\nfunction disable() {\n if (isSupported()) {\n self.addEventListener('activate', (event) => {\n event.waitUntil(self.registration.navigationPreload.disable().then(() => {\n if (process.env.NODE_ENV !== 'production') {\n logger.log(`Navigation preload is disabled.`);\n }\n }));\n });\n }\n else {\n if (process.env.NODE_ENV !== 'production') {\n logger.log(`Navigation preload is not supported in this browser.`);\n }\n }\n}\nexport { disable };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { isSupported } from './isSupported.js';\nimport './_version.js';\n/**\n * If the browser supports Navigation Preload, then this will enable it.\n *\n * @param {string} [headerValue] Optionally, allows developers to\n * [override](https://developers.google.com/web/updates/2017/02/navigation-preload#changing_the_header)\n * the value of the `Service-Worker-Navigation-Preload` header which will be\n * sent to the server when making the navigation request.\n *\n * @memberof module:workbox-navigation-preload\n */\nfunction enable(headerValue) {\n if (isSupported()) {\n self.addEventListener('activate', (event) => {\n event.waitUntil(self.registration.navigationPreload.enable().then(() => {\n // Defaults to Service-Worker-Navigation-Preload: true if not set.\n if (headerValue) {\n self.registration.navigationPreload.setHeaderValue(headerValue);\n }\n if (process.env.NODE_ENV !== 'production') {\n logger.log(`Navigation preload is enabled.`);\n }\n }));\n });\n }\n else {\n if (process.env.NODE_ENV !== 'production') {\n logger.log(`Navigation preload is not supported in this browser.`);\n }\n }\n}\nexport { enable };\n"],"names":["self","_","e","isSupported","Boolean","registration","navigationPreload","disable","addEventListener","event","waitUntil","then","logger","log","enable","headerValue","setHeaderValue"],"mappings":";;;;IAEA,IAAI;IACAA,EAAAA,IAAI,CAAC,kCAAD,CAAJ,IAA4CC,CAAC,EAA7C;IACH,CAFD,CAGA,OAAOC,CAAP,EAAU;;ICLV;;;;;;;AAOA,IACA;;;;;;;IAMA,SAASC,WAAT,GAAuB;IACnB,SAAOC,OAAO,CAACJ,IAAI,CAACK,YAAL,IAAqBL,IAAI,CAACK,YAAL,CAAkBC,iBAAxC,CAAd;IACH;;IChBD;;;;;;;AAOA,IAGA;;;;;;IAKA,SAASC,OAAT,GAAmB;IACf,MAAIJ,WAAW,EAAf,EAAmB;IACfH,IAAAA,IAAI,CAACQ,gBAAL,CAAsB,UAAtB,EAAmCC,KAAD,IAAW;IACzCA,MAAAA,KAAK,CAACC,SAAN,CAAgBV,IAAI,CAACK,YAAL,CAAkBC,iBAAlB,CAAoCC,OAApC,GAA8CI,IAA9C,CAAmD,MAAM;IACrE,QAA2C;IACvCC,UAAAA,gBAAM,CAACC,GAAP,CAAY,iCAAZ;IACH;IACJ,OAJe,CAAhB;IAKH,KAND;IAOH,GARD,MASK;IACD,IAA2C;IACvCD,MAAAA,gBAAM,CAACC,GAAP,CAAY,sDAAZ;IACH;IACJ;IACJ;;IC9BD;;;;;;;AAOA,IAGA;;;;;;;;;;;IAUA,SAASC,MAAT,CAAgBC,WAAhB,EAA6B;IACzB,MAAIZ,WAAW,EAAf,EAAmB;IACfH,IAAAA,IAAI,CAACQ,gBAAL,CAAsB,UAAtB,EAAmCC,KAAD,IAAW;IACzCA,MAAAA,KAAK,CAACC,SAAN,CAAgBV,IAAI,CAACK,YAAL,CAAkBC,iBAAlB,CAAoCQ,MAApC,GAA6CH,IAA7C,CAAkD,MAAM;IACpE;IACA,YAAII,WAAJ,EAAiB;IACbf,UAAAA,IAAI,CAACK,YAAL,CAAkBC,iBAAlB,CAAoCU,cAApC,CAAmDD,WAAnD;IACH;;IACD,QAA2C;IACvCH,UAAAA,gBAAM,CAACC,GAAP,CAAY,gCAAZ;IACH;IACJ,OARe,CAAhB;IASH,KAVD;IAWH,GAZD,MAaK;IACD,IAA2C;IACvCD,MAAAA,gBAAM,CAACC,GAAP,CAAY,sDAAZ;IACH;IACJ;IACJ;;;;;;;;;;;;"}
@@ -0,0 +1,2 @@
this.workbox=this.workbox||{},this.workbox.navigationPreload=function(t){"use strict";try{self["workbox:navigation-preload:5.1.4"]&&_()}catch(t){}function e(){return Boolean(self.registration&&self.registration.navigationPreload)}return t.disable=function(){e()&&self.addEventListener("activate",t=>{t.waitUntil(self.registration.navigationPreload.disable().then(()=>{}))})},t.enable=function(t){e()&&self.addEventListener("activate",e=>{e.waitUntil(self.registration.navigationPreload.enable().then(()=>{t&&self.registration.navigationPreload.setHeaderValue(t)}))})},t.isSupported=e,t}({});
//# sourceMappingURL=workbox-navigation-preload.prod.js.map
@@ -0,0 +1 @@
{"version":3,"file":"workbox-navigation-preload.prod.js","sources":["../_version.js","../isSupported.js","../disable.js","../enable.js"],"sourcesContent":["\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:navigation-preload:5.1.4'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport './_version.js';\n/**\n * @return {boolean} Whether or not the current browser supports enabling\n * navigation preload.\n *\n * @memberof module:workbox-navigation-preload\n */\nfunction isSupported() {\n return Boolean(self.registration && self.registration.navigationPreload);\n}\nexport { isSupported };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { isSupported } from './isSupported.js';\nimport './_version.js';\n/**\n * If the browser supports Navigation Preload, then this will disable it.\n *\n * @memberof module:workbox-navigation-preload\n */\nfunction disable() {\n if (isSupported()) {\n self.addEventListener('activate', (event) => {\n event.waitUntil(self.registration.navigationPreload.disable().then(() => {\n if (process.env.NODE_ENV !== 'production') {\n logger.log(`Navigation preload is disabled.`);\n }\n }));\n });\n }\n else {\n if (process.env.NODE_ENV !== 'production') {\n logger.log(`Navigation preload is not supported in this browser.`);\n }\n }\n}\nexport { disable };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { isSupported } from './isSupported.js';\nimport './_version.js';\n/**\n * If the browser supports Navigation Preload, then this will enable it.\n *\n * @param {string} [headerValue] Optionally, allows developers to\n * [override](https://developers.google.com/web/updates/2017/02/navigation-preload#changing_the_header)\n * the value of the `Service-Worker-Navigation-Preload` header which will be\n * sent to the server when making the navigation request.\n *\n * @memberof module:workbox-navigation-preload\n */\nfunction enable(headerValue) {\n if (isSupported()) {\n self.addEventListener('activate', (event) => {\n event.waitUntil(self.registration.navigationPreload.enable().then(() => {\n // Defaults to Service-Worker-Navigation-Preload: true if not set.\n if (headerValue) {\n self.registration.navigationPreload.setHeaderValue(headerValue);\n }\n if (process.env.NODE_ENV !== 'production') {\n logger.log(`Navigation preload is enabled.`);\n }\n }));\n });\n }\n else {\n if (process.env.NODE_ENV !== 'production') {\n logger.log(`Navigation preload is not supported in this browser.`);\n }\n }\n}\nexport { enable };\n"],"names":["self","_","e","isSupported","Boolean","registration","navigationPreload","addEventListener","event","waitUntil","disable","then","headerValue","enable","setHeaderValue"],"mappings":"sFAEA,IACIA,KAAK,qCAAuCC,IAEhD,MAAOC,ICSP,SAASC,WACEC,QAAQJ,KAAKK,cAAgBL,KAAKK,aAAaC,oCCA1D,WACQH,KACAH,KAAKO,iBAAiB,WAAaC,IAC/BA,EAAMC,UAAUT,KAAKK,aAAaC,kBAAkBI,UAAUC,KAAK,qBCE/E,SAAgBC,GACRT,KACAH,KAAKO,iBAAiB,WAAaC,IAC/BA,EAAMC,UAAUT,KAAKK,aAAaC,kBAAkBO,SAASF,KAAK,KAE1DC,GACAZ,KAAKK,aAAaC,kBAAkBQ,eAAeF"}
@@ -0,0 +1,235 @@
this.workbox = this.workbox || {};
this.workbox.googleAnalytics = (function (exports, BackgroundSyncPlugin_js, cacheNames_js, getFriendlyURL_js, logger_js, Route_js, Router_js, NetworkFirst_js, NetworkOnly_js) {
'use strict';
try {
self['workbox:google-analytics:5.1.4'] && _();
} catch (e) {}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
const QUEUE_NAME = 'workbox-google-analytics';
const MAX_RETENTION_TIME = 60 * 48; // Two days in minutes
const GOOGLE_ANALYTICS_HOST = 'www.google-analytics.com';
const GTM_HOST = 'www.googletagmanager.com';
const ANALYTICS_JS_PATH = '/analytics.js';
const GTAG_JS_PATH = '/gtag/js';
const GTM_JS_PATH = '/gtm.js';
// endpoints. Most of the time the default path (/collect) is used, but
// occasionally an experimental endpoint is used when testing new features,
// (e.g. /r/collect or /j/collect)
const COLLECT_PATHS_REGEX = /^\/(\w+\/)?collect/;
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* Creates the requestWillDequeue callback to be used with the background
* sync plugin. The callback takes the failed request and adds the
* `qt` param based on the current time, as well as applies any other
* user-defined hit modifications.
*
* @param {Object} config See {@link module:workbox-google-analytics.initialize}.
* @return {Function} The requestWillDequeue callback function.
*
* @private
*/
const createOnSyncCallback = config => {
return async ({
queue
}) => {
let entry;
while (entry = await queue.shiftRequest()) {
const {
request,
timestamp
} = entry;
const url = new URL(request.url);
try {
// Measurement protocol requests can set their payload parameters in
// either the URL query string (for GET requests) or the POST body.
const params = request.method === 'POST' ? new URLSearchParams(await request.clone().text()) : url.searchParams; // Calculate the qt param, accounting for the fact that an existing
// qt param may be present and should be updated rather than replaced.
const originalHitTime = timestamp - (Number(params.get('qt')) || 0);
const queueTime = Date.now() - originalHitTime; // Set the qt param prior to applying hitFilter or parameterOverrides.
params.set('qt', String(queueTime)); // Apply `parameterOverrides`, if set.
if (config.parameterOverrides) {
for (const param of Object.keys(config.parameterOverrides)) {
const value = config.parameterOverrides[param];
params.set(param, value);
}
} // Apply `hitFilter`, if set.
if (typeof config.hitFilter === 'function') {
config.hitFilter.call(null, params);
} // Retry the fetch. Ignore URL search params from the URL as they're
// now in the post body.
await fetch(new Request(url.origin + url.pathname, {
body: params.toString(),
method: 'POST',
mode: 'cors',
credentials: 'omit',
headers: {
'Content-Type': 'text/plain'
}
}));
if ("dev" !== 'production') {
logger_js.logger.log(`Request for '${getFriendlyURL_js.getFriendlyURL(url.href)}'` + `has been replayed`);
}
} catch (err) {
await queue.unshiftRequest(entry);
{
logger_js.logger.log(`Request for '${getFriendlyURL_js.getFriendlyURL(url.href)}'` + `failed to replay, putting it back in the queue.`);
}
throw err;
}
}
{
logger_js.logger.log(`All Google Analytics request successfully replayed; ` + `the queue is now empty!`);
}
};
};
/**
* Creates GET and POST routes to catch failed Measurement Protocol hits.
*
* @param {BackgroundSyncPlugin} bgSyncPlugin
* @return {Array<Route>} The created routes.
*
* @private
*/
const createCollectRoutes = bgSyncPlugin => {
const match = ({
url
}) => url.hostname === GOOGLE_ANALYTICS_HOST && COLLECT_PATHS_REGEX.test(url.pathname);
const handler = new NetworkOnly_js.NetworkOnly({
plugins: [bgSyncPlugin]
});
return [new Route_js.Route(match, handler, 'GET'), new Route_js.Route(match, handler, 'POST')];
};
/**
* Creates a route with a network first strategy for the analytics.js script.
*
* @param {string} cacheName
* @return {Route} The created route.
*
* @private
*/
const createAnalyticsJsRoute = cacheName => {
const match = ({
url
}) => url.hostname === GOOGLE_ANALYTICS_HOST && url.pathname === ANALYTICS_JS_PATH;
const handler = new NetworkFirst_js.NetworkFirst({
cacheName
});
return new Route_js.Route(match, handler, 'GET');
};
/**
* Creates a route with a network first strategy for the gtag.js script.
*
* @param {string} cacheName
* @return {Route} The created route.
*
* @private
*/
const createGtagJsRoute = cacheName => {
const match = ({
url
}) => url.hostname === GTM_HOST && url.pathname === GTAG_JS_PATH;
const handler = new NetworkFirst_js.NetworkFirst({
cacheName
});
return new Route_js.Route(match, handler, 'GET');
};
/**
* Creates a route with a network first strategy for the gtm.js script.
*
* @param {string} cacheName
* @return {Route} The created route.
*
* @private
*/
const createGtmJsRoute = cacheName => {
const match = ({
url
}) => url.hostname === GTM_HOST && url.pathname === GTM_JS_PATH;
const handler = new NetworkFirst_js.NetworkFirst({
cacheName
});
return new Route_js.Route(match, handler, 'GET');
};
/**
* @param {Object=} [options]
* @param {Object} [options.cacheName] The cache name to store and retrieve
* analytics.js. Defaults to the cache names provided by `workbox-core`.
* @param {Object} [options.parameterOverrides]
* [Measurement Protocol parameters](https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters),
* expressed as key/value pairs, to be added to replayed Google Analytics
* requests. This can be used to, e.g., set a custom dimension indicating
* that the request was replayed.
* @param {Function} [options.hitFilter] A function that allows you to modify
* the hit parameters prior to replaying
* the hit. The function is invoked with the original hit's URLSearchParams
* object as its only argument.
*
* @memberof module:workbox-google-analytics
*/
const initialize = (options = {}) => {
const cacheName = cacheNames_js.cacheNames.getGoogleAnalyticsName(options.cacheName);
const bgSyncPlugin = new BackgroundSyncPlugin_js.BackgroundSyncPlugin(QUEUE_NAME, {
maxRetentionTime: MAX_RETENTION_TIME,
onSync: createOnSyncCallback(options)
});
const routes = [createGtmJsRoute(cacheName), createAnalyticsJsRoute(cacheName), createGtagJsRoute(cacheName), ...createCollectRoutes(bgSyncPlugin)];
const router = new Router_js.Router();
for (const route of routes) {
router.registerRoute(route);
}
router.addFetchListener();
};
exports.initialize = initialize;
return exports;
}({}, workbox.backgroundSync, workbox.core._private, workbox.core._private, workbox.core._private, workbox.routing, workbox.routing, workbox.strategies, workbox.strategies));
//# sourceMappingURL=workbox-offline-ga.dev.js.map
@@ -0,0 +1 @@
{"version":3,"file":"workbox-offline-ga.dev.js","sources":["../_version.js","../utils/constants.js","../initialize.js"],"sourcesContent":["\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:google-analytics:5.1.4'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\nexport const QUEUE_NAME = 'workbox-google-analytics';\nexport const MAX_RETENTION_TIME = 60 * 48; // Two days in minutes\nexport const GOOGLE_ANALYTICS_HOST = 'www.google-analytics.com';\nexport const GTM_HOST = 'www.googletagmanager.com';\nexport const ANALYTICS_JS_PATH = '/analytics.js';\nexport const GTAG_JS_PATH = '/gtag/js';\nexport const GTM_JS_PATH = '/gtm.js';\nexport const COLLECT_DEFAULT_PATH = '/collect';\n// This RegExp matches all known Measurement Protocol single-hit collect\n// endpoints. Most of the time the default path (/collect) is used, but\n// occasionally an experimental endpoint is used when testing new features,\n// (e.g. /r/collect or /j/collect)\nexport const COLLECT_PATHS_REGEX = /^\\/(\\w+\\/)?collect/;\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { BackgroundSyncPlugin } from 'workbox-background-sync/BackgroundSyncPlugin.js';\nimport { cacheNames } from 'workbox-core/_private/cacheNames.js';\nimport { getFriendlyURL } from 'workbox-core/_private/getFriendlyURL.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { Route } from 'workbox-routing/Route.js';\nimport { Router } from 'workbox-routing/Router.js';\nimport { NetworkFirst } from 'workbox-strategies/NetworkFirst.js';\nimport { NetworkOnly } from 'workbox-strategies/NetworkOnly.js';\nimport { QUEUE_NAME, MAX_RETENTION_TIME, GOOGLE_ANALYTICS_HOST, GTM_HOST, ANALYTICS_JS_PATH, GTAG_JS_PATH, GTM_JS_PATH, COLLECT_PATHS_REGEX, } from './utils/constants.js';\nimport './_version.js';\n/**\n * Creates the requestWillDequeue callback to be used with the background\n * sync plugin. The callback takes the failed request and adds the\n * `qt` param based on the current time, as well as applies any other\n * user-defined hit modifications.\n *\n * @param {Object} config See {@link module:workbox-google-analytics.initialize}.\n * @return {Function} The requestWillDequeue callback function.\n *\n * @private\n */\nconst createOnSyncCallback = (config) => {\n return async ({ queue }) => {\n let entry;\n while (entry = await queue.shiftRequest()) {\n const { request, timestamp } = entry;\n const url = new URL(request.url);\n try {\n // Measurement protocol requests can set their payload parameters in\n // either the URL query string (for GET requests) or the POST body.\n const params = request.method === 'POST' ?\n new URLSearchParams(await request.clone().text()) :\n url.searchParams;\n // Calculate the qt param, accounting for the fact that an existing\n // qt param may be present and should be updated rather than replaced.\n const originalHitTime = timestamp - (Number(params.get('qt')) || 0);\n const queueTime = Date.now() - originalHitTime;\n // Set the qt param prior to applying hitFilter or parameterOverrides.\n params.set('qt', String(queueTime));\n // Apply `parameterOverrides`, if set.\n if (config.parameterOverrides) {\n for (const param of Object.keys(config.parameterOverrides)) {\n const value = config.parameterOverrides[param];\n params.set(param, value);\n }\n }\n // Apply `hitFilter`, if set.\n if (typeof config.hitFilter === 'function') {\n config.hitFilter.call(null, params);\n }\n // Retry the fetch. Ignore URL search params from the URL as they're\n // now in the post body.\n await fetch(new Request(url.origin + url.pathname, {\n body: params.toString(),\n method: 'POST',\n mode: 'cors',\n credentials: 'omit',\n headers: { 'Content-Type': 'text/plain' },\n }));\n if (process.env.NODE_ENV !== 'production') {\n logger.log(`Request for '${getFriendlyURL(url.href)}'` +\n `has been replayed`);\n }\n }\n catch (err) {\n await queue.unshiftRequest(entry);\n if (process.env.NODE_ENV !== 'production') {\n Line truncated
@@ -0,0 +1,2 @@
this.workbox=this.workbox||{},this.workbox.googleAnalytics=function(t,o,e,n,a,c,r,w,s){"use strict";try{self["workbox:google-analytics:5.1.4"]&&_()}catch(t){}const i=/^\/(\w+\/)?collect/,l=t=>{const o=({url:t})=>"www.google-analytics.com"===t.hostname&&i.test(t.pathname),e=new s.NetworkOnly({plugins:[t]});return[new c.Route(o,e,"GET"),new c.Route(o,e,"POST")]},g=t=>{const o=new w.NetworkFirst({cacheName:t});return new c.Route(({url:t})=>"www.google-analytics.com"===t.hostname&&"/analytics.js"===t.pathname,o,"GET")},m=t=>{const o=new w.NetworkFirst({cacheName:t});return new c.Route(({url:t})=>"www.googletagmanager.com"===t.hostname&&"/gtag/js"===t.pathname,o,"GET")},u=t=>{const o=new w.NetworkFirst({cacheName:t});return new c.Route(({url:t})=>"www.googletagmanager.com"===t.hostname&&"/gtm.js"===t.pathname,o,"GET")};return t.initialize=(t={})=>{const n=e.cacheNames.getGoogleAnalyticsName(t.cacheName),a=new o.BackgroundSyncPlugin("workbox-google-analytics",{maxRetentionTime:2880,onSync:(c=t,async({queue:t})=>{let o;for(;o=await t.shiftRequest();){const{request:e,timestamp:n}=o,a=new URL(e.url);try{const t="POST"===e.method?new URLSearchParams(await e.clone().text()):a.searchParams,o=n-(Number(t.get("qt"))||0),r=Date.now()-o;if(t.set("qt",String(r)),c.parameterOverrides)for(const o of Object.keys(c.parameterOverrides)){const e=c.parameterOverrides[o];t.set(o,e)}"function"==typeof c.hitFilter&&c.hitFilter.call(null,t),await fetch(new Request(a.origin+a.pathname,{body:t.toString(),method:"POST",mode:"cors",credentials:"omit",headers:{"Content-Type":"text/plain"}}))}catch(e){throw await t.unshiftRequest(o),e}}})});var c;const w=[u(n),g(n),m(n),...l(a)],s=new r.Router;for(const t of w)s.registerRoute(t);s.addFetchListener()},t}({},workbox.backgroundSync,workbox.core._private,workbox.core._private,workbox.core._private,workbox.routing,workbox.routing,workbox.strategies,workbox.strategies);
//# sourceMappingURL=workbox-offline-ga.prod.js.map
@@ -0,0 +1 @@
{"version":3,"file":"workbox-offline-ga.prod.js","sources":["../_version.js","../utils/constants.js","../initialize.js"],"sourcesContent":["\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:google-analytics:5.1.4'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\nexport const QUEUE_NAME = 'workbox-google-analytics';\nexport const MAX_RETENTION_TIME = 60 * 48; // Two days in minutes\nexport const GOOGLE_ANALYTICS_HOST = 'www.google-analytics.com';\nexport const GTM_HOST = 'www.googletagmanager.com';\nexport const ANALYTICS_JS_PATH = '/analytics.js';\nexport const GTAG_JS_PATH = '/gtag/js';\nexport const GTM_JS_PATH = '/gtm.js';\nexport const COLLECT_DEFAULT_PATH = '/collect';\n// This RegExp matches all known Measurement Protocol single-hit collect\n// endpoints. Most of the time the default path (/collect) is used, but\n// occasionally an experimental endpoint is used when testing new features,\n// (e.g. /r/collect or /j/collect)\nexport const COLLECT_PATHS_REGEX = /^\\/(\\w+\\/)?collect/;\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { BackgroundSyncPlugin } from 'workbox-background-sync/BackgroundSyncPlugin.js';\nimport { cacheNames } from 'workbox-core/_private/cacheNames.js';\nimport { getFriendlyURL } from 'workbox-core/_private/getFriendlyURL.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { Route } from 'workbox-routing/Route.js';\nimport { Router } from 'workbox-routing/Router.js';\nimport { NetworkFirst } from 'workbox-strategies/NetworkFirst.js';\nimport { NetworkOnly } from 'workbox-strategies/NetworkOnly.js';\nimport { QUEUE_NAME, MAX_RETENTION_TIME, GOOGLE_ANALYTICS_HOST, GTM_HOST, ANALYTICS_JS_PATH, GTAG_JS_PATH, GTM_JS_PATH, COLLECT_PATHS_REGEX, } from './utils/constants.js';\nimport './_version.js';\n/**\n * Creates the requestWillDequeue callback to be used with the background\n * sync plugin. The callback takes the failed request and adds the\n * `qt` param based on the current time, as well as applies any other\n * user-defined hit modifications.\n *\n * @param {Object} config See {@link module:workbox-google-analytics.initialize}.\n * @return {Function} The requestWillDequeue callback function.\n *\n * @private\n */\nconst createOnSyncCallback = (config) => {\n return async ({ queue }) => {\n let entry;\n while (entry = await queue.shiftRequest()) {\n const { request, timestamp } = entry;\n const url = new URL(request.url);\n try {\n // Measurement protocol requests can set their payload parameters in\n // either the URL query string (for GET requests) or the POST body.\n const params = request.method === 'POST' ?\n new URLSearchParams(await request.clone().text()) :\n url.searchParams;\n // Calculate the qt param, accounting for the fact that an existing\n // qt param may be present and should be updated rather than replaced.\n const originalHitTime = timestamp - (Number(params.get('qt')) || 0);\n const queueTime = Date.now() - originalHitTime;\n // Set the qt param prior to applying hitFilter or parameterOverrides.\n params.set('qt', String(queueTime));\n // Apply `parameterOverrides`, if set.\n if (config.parameterOverrides) {\n for (const param of Object.keys(config.parameterOverrides)) {\n const value = config.parameterOverrides[param];\n params.set(param, value);\n }\n }\n // Apply `hitFilter`, if set.\n if (typeof config.hitFilter === 'function') {\n config.hitFilter.call(null, params);\n }\n // Retry the fetch. Ignore URL search params from the URL as they're\n // now in the post body.\n await fetch(new Request(url.origin + url.pathname, {\n body: params.toString(),\n method: 'POST',\n mode: 'cors',\n credentials: 'omit',\n headers: { 'Content-Type': 'text/plain' },\n }));\n if (process.env.NODE_ENV !== 'production') {\n logger.log(`Request for '${getFriendlyURL(url.href)}'` +\n `has been replayed`);\n }\n }\n catch (err) {\n await queue.unshiftRequest(entry);\n if (process.env.NODE_ENV !== 'production') {\n Line truncated
File diff suppressed because it is too large. Load diff
@@ -0,0 +1 @@
{"version":3,"file":"workbox-precaching.dev.js","sources":["../_version.js","../utils/precachePlugins.js","../addPlugins.js","../utils/createCacheKey.js","../utils/printCleanupDetails.js","../utils/printInstallDetails.js","../PrecacheController.js","../utils/getOrCreatePrecacheController.js","../utils/removeIgnoredSearchParams.js","../utils/generateURLVariations.js","../utils/getCacheKeyForURL.js","../utils/addFetchListener.js","../addRoute.js","../utils/deleteOutdatedCaches.js","../cleanupOutdatedCaches.js","../createHandler.js","../createHandlerBoundToURL.js","../getCacheKeyForURL.js","../matchPrecache.js","../precache.js","../precacheAndRoute.js"],"sourcesContent":["\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:precaching:5.1.4'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2019 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\nconst plugins = [];\nexport const precachePlugins = {\n /*\n * @return {Array}\n * @private\n */\n get() {\n return plugins;\n },\n /*\n * @param {Array} newPlugins\n * @private\n */\n add(newPlugins) {\n plugins.push(...newPlugins);\n },\n};\n","/*\n Copyright 2019 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { precachePlugins } from './utils/precachePlugins.js';\nimport './_version.js';\n/**\n * Adds plugins to precaching.\n *\n * @param {Array<Object>} newPlugins\n *\n * @memberof module:workbox-precaching\n */\nfunction addPlugins(newPlugins) {\n precachePlugins.add(newPlugins);\n}\nexport { addPlugins };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport '../_version.js';\n// Name of the search parameter used to store revision info.\nconst REVISION_SEARCH_PARAM = '__WB_REVISION__';\n/**\n * Converts a manifest entry into a versioned URL suitable for precaching.\n *\n * @param {Object|string} entry\n * @return {string} A URL with versioning info.\n *\n * @private\n * @memberof module:workbox-precaching\n */\nexport function createCacheKey(entry) {\n if (!entry) {\n throw new WorkboxError('add-to-cache-list-unexpected-type', { entry });\n }\n // If a precache manifest entry is a string, it's assumed to be a versioned\n // URL, like '/app.abcd1234.js'. Return as-is.\n if (typeof entry === 'string') {\n const urlObject = new URL(entry, location.href);\n return {\n cacheKey: urlObject.href,\n url: urlObject.href,\n };\n }\n const { revision, url } = entry;\n if (!url) {\n throw new WorkboxError('add-to-cache-list-unexpected-type', { entry });\n }\n // If there's just a URL and no revision, then it's also assumed to be a\n // versioned URL.\n if (!revision) {\n const urlObject = new URL(url, location.href);\n return {\n cacheKey: urlObject.href,\n url: urlObject.href,\n };\n }\n // Otherwise, construct a properly versioned URL using the custom Workbox\n // search parameter along with the revision info.\n const cacheKeyURL = new URL(url, location.href);\n const originalURL = new URL(url, location.href);\n cacheKeyURL.searchParams.set(REVISION_SEARCH_PARAM, revision);\n return {\n cacheKey: cacheKeyURL.href,\n url: originalURL.href,\n };\n}\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { logger } from 'workbox-core/_private/logger.js';\nimport '../_version.js';\n/**\n * @param {string} groupTitle\n * @param {Array<string>} deletedURLs\n *\n * @private\n */\nconst logGroup = (groupTitle, deletedURLs) => {\n logger.groupCollapsed(groupTitle);\n for (const url of deletedURLs) {\n logger.log(url);\n }\n logger.groupEnd();\n};\n/**\n * @param {Array<string>} deletedURLs\n *\n * @private\n * @memberof module:workbox-precaching\n */\nexport function printCleanupDetails(deletedURLs) {\n const deletionCount = deletedURLs.length;\n if (deletionCount > 0) {\n logger.groupCollapsed(`During precaching cleanup, ` +\n `${deletionCount} cached ` +\n `request${deletionCount === 1 ? ' was' : 's were'} deleted.`);\n logGroup('Deleted Cache Requests', deletedURLs);\n logger.groupEnd();\n }\n}\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensouLine truncated
@@ -0,0 +1,2 @@
this.workbox=this.workbox||{},this.workbox.precaching=function(t,e,n,i,s,c){"use strict";try{self["workbox:precaching:5.1.4"]&&_()}catch(t){}const r=[],o={get:()=>r,add(t){r.push(...t)}};function a(t){if(!t)throw new s.WorkboxError("add-to-cache-list-unexpected-type",{entry:t});if("string"==typeof t){const e=new URL(t,location.href);return{cacheKey:e.href,url:e.href}}const{revision:e,url:n}=t;if(!n)throw new s.WorkboxError("add-to-cache-list-unexpected-type",{entry:t});if(!e){const t=new URL(n,location.href);return{cacheKey:t.href,url:t.href}}const i=new URL(n,location.href),c=new URL(n,location.href);return i.searchParams.set("__WB_REVISION__",e),{cacheKey:i.href,url:c.href}}class h{constructor(t){this.t=e.cacheNames.getPrecacheName(t),this.i=new Map,this.s=new Map,this.o=new Map}addToCacheList(t){const e=[];for(const n of t){"string"==typeof n?e.push(n):n&&void 0===n.revision&&e.push(n.url);const{cacheKey:t,url:i}=a(n),c="string"!=typeof n&&n.revision?"reload":"default";if(this.i.has(i)&&this.i.get(i)!==t)throw new s.WorkboxError("add-to-cache-list-conflicting-entries",{firstEntry:this.i.get(i),secondEntry:t});if("string"!=typeof n&&n.integrity){if(this.o.has(t)&&this.o.get(t)!==n.integrity)throw new s.WorkboxError("add-to-cache-list-conflicting-integrities",{url:i});this.o.set(t,n.integrity)}if(this.i.set(i,t),this.s.set(i,c),e.length>0){const t=`Workbox is precaching URLs without revision info: ${e.join(", ")}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(t)}}}async install({event:t,plugins:e}={}){const n=[],i=[],s=await self.caches.open(this.t),c=await s.keys(),r=new Set(c.map(t=>t.url));for(const[t,e]of this.i)r.has(e)?i.push(t):n.push({cacheKey:e,url:t});const o=n.map(({cacheKey:n,url:i})=>{const s=this.o.get(n),c=this.s.get(i);return this.h({cacheKey:n,cacheMode:c,event:t,integrity:s,plugins:e,url:i})});await Promise.all(o);return{updatedURLs:n.map(t=>t.url),notUpdatedURLs:i}}async activate(){const t=await self.caches.open(this.t),e=await t.keys(),n=new Set(this.i.values()),i=[];for(const s of e)n.has(s.url)||(await t.delete(s),i.push(s.url));return{deletedURLs:i}}async h({cacheKey:t,url:e,cacheMode:r,event:o,plugins:a,integrity:h}){const u=new Request(e,{integrity:h,cache:r,credentials:"same-origin"});let l,f=await i.fetchWrapper.fetch({event:o,plugins:a,request:u});for(const t of a||[])"cacheWillUpdate"in t&&(l=t);if(!(l?await l.cacheWillUpdate({event:o,request:u,response:f}):f.status<400))throw new s.WorkboxError("bad-precaching-response",{url:e,status:f.status});f.redirected&&(f=await c.copyResponse(f)),await n.cacheWrapper.put({event:o,plugins:a,response:f,request:t===e?u:new Request(t),cacheName:this.t,matchOptions:{ignoreSearch:!0}})}getURLsToCacheKeys(){return this.i}getCachedURLs(){return[...this.i.keys()]}getCacheKeyForURL(t){const e=new URL(t,location.href);return this.i.get(e.href)}async matchPrecache(t){const e=t instanceof Request?t.url:t,n=this.getCacheKeyForURL(e);if(n){return(await self.caches.open(this.t)).match(n)}}createHandler(t=!0){return async({request:e})=>{try{const t=await this.matchPrecache(e);if(t)return t;throw new s.WorkboxError("missing-precache-entry",{cacheName:this.t,url:e instanceof Request?e.url:e})}catch(n){if(t)return fetch(e);throw n}}}createHandlerBoundToURL(t,e=!0){if(!this.getCacheKeyForURL(t))throw new s.WorkboxError("non-precached-url",{url:t});const n=this.createHandler(e),i=new Request(t);return()=>n({request:i})}}let u;const l=()=>(u||(u=new h),u);const f=(t,e)=>{const n=l().getURLsToCacheKeys();for(const i of function*(t,{ignoreURLParametersMatching:e,directoryIndex:n,cleanURLs:i,urlManipulation:s}={}){const c=new URL(t,location.href);c.hash="",yield c.href;const r=function(t,e=[]){for(const n of[...t.searchParams.keys()])e.some(t=>t.test(n))&&t.searchParams.delete(n);return t}(c,e);if(yield r.href,n&&r.pathname.endsWith("/")){const t=new URL(r.href);t.pathname+=n,yield t.href}if(i){const t=new URL(r.href);t.pathname+=".html",yield t.href}if(s){const t=s({url:c});for(const e of t)yield e.href}}(t,e)){const t=n.get(i);if(t)return t}};let w=!1;function d(t){w||((({ignoreURLParametersMatching:t=[/^utm_/],directoryIndex:n="index.html",cleanURLs:i=!0,urlManipulation:s}={})=>{const c=e.cacheNames.getPrecacheName();self.addEventListener("fetch",e=>{const r=f(e.request.url,{cleanURLs:i,directoryIndex:n,ignoreURLParametersMatching:t,urlManipulation:s});if(!r)return;let o=self.caches.open(c).then(t=>t.match(r)).then(t=>t||fetch(r));e.respondWith(o)})})(t),w=!0)}const y=t=>{const e=l(),n=o.get();t.waitUntil(e.install({event:t,plugins:n}).catch(t=>{throw t}))},p=t=>{const e=l();t.waitUntil(e.activate())};function g(t){l().addToCacheList(t),t.length>0&&(self.addEventListener("install",y),self.addEventListener("activate",p))}return t.PrecacheController=h,t.addPlugins=function(t){o.add(t)},t.addRoute=d,t.cleanupOutdatedCaches=function(){self.addEventListener("activate",t=>{const n=e.cacheNames.getPrecacheName();t.waitUntil((asynLine truncated
//# sourceMappingURL=workbox-precaching.prod.js.map
@@ -0,0 +1 @@
{"version":3,"file":"workbox-precaching.prod.js","sources":["../_version.js","../utils/precachePlugins.js","../utils/createCacheKey.js","../PrecacheController.js","../utils/getOrCreatePrecacheController.js","../utils/getCacheKeyForURL.js","../utils/generateURLVariations.js","../utils/removeIgnoredSearchParams.js","../addRoute.js","../utils/addFetchListener.js","../precache.js","../addPlugins.js","../cleanupOutdatedCaches.js","../utils/deleteOutdatedCaches.js","../createHandler.js","../createHandlerBoundToURL.js","../getCacheKeyForURL.js","../matchPrecache.js","../precacheAndRoute.js"],"sourcesContent":["\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:precaching:5.1.4'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2019 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\nconst plugins = [];\nexport const precachePlugins = {\n /*\n * @return {Array}\n * @private\n */\n get() {\n return plugins;\n },\n /*\n * @param {Array} newPlugins\n * @private\n */\n add(newPlugins) {\n plugins.push(...newPlugins);\n },\n};\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport '../_version.js';\n// Name of the search parameter used to store revision info.\nconst REVISION_SEARCH_PARAM = '__WB_REVISION__';\n/**\n * Converts a manifest entry into a versioned URL suitable for precaching.\n *\n * @param {Object|string} entry\n * @return {string} A URL with versioning info.\n *\n * @private\n * @memberof module:workbox-precaching\n */\nexport function createCacheKey(entry) {\n if (!entry) {\n throw new WorkboxError('add-to-cache-list-unexpected-type', { entry });\n }\n // If a precache manifest entry is a string, it's assumed to be a versioned\n // URL, like '/app.abcd1234.js'. Return as-is.\n if (typeof entry === 'string') {\n const urlObject = new URL(entry, location.href);\n return {\n cacheKey: urlObject.href,\n url: urlObject.href,\n };\n }\n const { revision, url } = entry;\n if (!url) {\n throw new WorkboxError('add-to-cache-list-unexpected-type', { entry });\n }\n // If there's just a URL and no revision, then it's also assumed to be a\n // versioned URL.\n if (!revision) {\n const urlObject = new URL(url, location.href);\n return {\n cacheKey: urlObject.href,\n url: urlObject.href,\n };\n }\n // Otherwise, construct a properly versioned URL using the custom Workbox\n // search parameter along with the revision info.\n const cacheKeyURL = new URL(url, location.href);\n const originalURL = new URL(url, location.href);\n cacheKeyURL.searchParams.set(REVISION_SEARCH_PARAM, revision);\n return {\n cacheKey: cacheKeyURL.href,\n url: originalURL.href,\n };\n}\n","/*\n Copyright 2019 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { cacheNames } from 'workbox-core/_private/cacheNames.js';\nimport { cacheWrapper } from 'workbox-core/_private/cacheWrapper.js';\nimport { fetchWrapper } from 'workbox-core/_private/fetchWrapper.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport { copyResponse } from 'workbox-core/copyResponse.js';\nimport { createCacheKey } from './utils/createCacheKey.js';\nimport { printCleanupDetails } from './utils/printCleanupDetails.js';\nimport { printInstallDetails } from './utils/printInstallDetails.js';\nimport './_version.js';\n/**\n * Performs efficient precaching of assets.\n *\n * @memberof module:workbox-precaching\n */\nclass PrecacheController {\n /**\n * Create a new PrecacheController.\n *\n * @param {string} [cacheName] An optional name for the cache, to override\n * the default precache name.\n */\n constructor(cacheName) {\n this._cacheName = cacheNames.getPrecacheName(cacheName);\n this._urlsToCacheKeys = new Map();\n this._urlsToCacheModes = new Map();\n this._cacheKeysToIntegrities = new Map();\n }\n /**\n * This method will add items to the precache list, removing duplicates\n * and ensuring the information is valid.\n *\n * @param {\n * Array<module:workbox-precaching.PrecacheController.PrecacheEntry|string>\n * } entries Array of entries to precache.\n */\n addToCacheList(entries) {\n if (process.env.NODE_ENV !== 'production') {\n Line truncated
@@ -0,0 +1,262 @@
this.workbox = this.workbox || {};
this.workbox.rangeRequests = (function (exports, WorkboxError_js, assert_js, logger_js) {
'use strict';
try {
self['workbox:range-requests:5.1.4'] && _();
} catch (e) {}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* @param {Blob} blob A source blob.
* @param {number} [start] The offset to use as the start of the
* slice.
* @param {number} [end] The offset to use as the end of the slice.
* @return {Object} An object with `start` and `end` properties, reflecting
* the effective boundaries to use given the size of the blob.
*
* @private
*/
function calculateEffectiveBoundaries(blob, start, end) {
{
assert_js.assert.isInstance(blob, Blob, {
moduleName: 'workbox-range-requests',
funcName: 'calculateEffectiveBoundaries',
paramName: 'blob'
});
}
const blobSize = blob.size;
if (end && end > blobSize || start && start < 0) {
throw new WorkboxError_js.WorkboxError('range-not-satisfiable', {
size: blobSize,
end,
start
});
}
let effectiveStart;
let effectiveEnd;
if (start !== undefined && end !== undefined) {
effectiveStart = start; // Range values are inclusive, so add 1 to the value.
effectiveEnd = end + 1;
} else if (start !== undefined && end === undefined) {
effectiveStart = start;
effectiveEnd = blobSize;
} else if (end !== undefined && start === undefined) {
effectiveStart = blobSize - end;
effectiveEnd = blobSize;
}
return {
start: effectiveStart,
end: effectiveEnd
};
}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* @param {string} rangeHeader A Range: header value.
* @return {Object} An object with `start` and `end` properties, reflecting
* the parsed value of the Range: header. If either the `start` or `end` are
* omitted, then `null` will be returned.
*
* @private
*/
function parseRangeHeader(rangeHeader) {
{
assert_js.assert.isType(rangeHeader, 'string', {
moduleName: 'workbox-range-requests',
funcName: 'parseRangeHeader',
paramName: 'rangeHeader'
});
}
const normalizedRangeHeader = rangeHeader.trim().toLowerCase();
if (!normalizedRangeHeader.startsWith('bytes=')) {
throw new WorkboxError_js.WorkboxError('unit-must-be-bytes', {
normalizedRangeHeader
});
} // Specifying multiple ranges separate by commas is valid syntax, but this
// library only attempts to handle a single, contiguous sequence of bytes.
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range#Syntax
if (normalizedRangeHeader.includes(',')) {
throw new WorkboxError_js.WorkboxError('single-range-only', {
normalizedRangeHeader
});
}
const rangeParts = /(\d*)-(\d*)/.exec(normalizedRangeHeader); // We need either at least one of the start or end values.
if (!rangeParts || !(rangeParts[1] || rangeParts[2])) {
throw new WorkboxError_js.WorkboxError('invalid-range-values', {
normalizedRangeHeader
});
}
return {
start: rangeParts[1] === '' ? undefined : Number(rangeParts[1]),
end: rangeParts[2] === '' ? undefined : Number(rangeParts[2])
};
}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* Given a `Request` and `Response` objects as input, this will return a
* promise for a new `Response`.
*
* If the original `Response` already contains partial content (i.e. it has
* a status of 206), then this assumes it already fulfills the `Range:`
* requirements, and will return it as-is.
*
* @param {Request} request A request, which should contain a Range:
* header.
* @param {Response} originalResponse A response.
* @return {Promise<Response>} Either a `206 Partial Content` response, with
* the response body set to the slice of content specified by the request's
* `Range:` header, or a `416 Range Not Satisfiable` response if the
* conditions of the `Range:` header can't be met.
*
* @memberof module:workbox-range-requests
*/
async function createPartialResponse(request, originalResponse) {
try {
if ("dev" !== 'production') {
assert_js.assert.isInstance(request, Request, {
moduleName: 'workbox-range-requests',
funcName: 'createPartialResponse',
paramName: 'request'
});
assert_js.assert.isInstance(originalResponse, Response, {
moduleName: 'workbox-range-requests',
funcName: 'createPartialResponse',
paramName: 'originalResponse'
});
}
if (originalResponse.status === 206) {
// If we already have a 206, then just pass it through as-is;
// see https://github.com/GoogleChrome/workbox/issues/1720
return originalResponse;
}
const rangeHeader = request.headers.get('range');
if (!rangeHeader) {
throw new WorkboxError_js.WorkboxError('no-range-header');
}
const boundaries = parseRangeHeader(rangeHeader);
const originalBlob = await originalResponse.blob();
const effectiveBoundaries = calculateEffectiveBoundaries(originalBlob, boundaries.start, boundaries.end);
const slicedBlob = originalBlob.slice(effectiveBoundaries.start, effectiveBoundaries.end);
const slicedBlobSize = slicedBlob.size;
const slicedResponse = new Response(slicedBlob, {
// Status code 206 is for a Partial Content response.
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/206
status: 206,
statusText: 'Partial Content',
headers: originalResponse.headers
});
slicedResponse.headers.set('Content-Length', String(slicedBlobSize));
slicedResponse.headers.set('Content-Range', `bytes ${effectiveBoundaries.start}-${effectiveBoundaries.end - 1}/` + originalBlob.size);
return slicedResponse;
} catch (error) {
{
logger_js.logger.warn(`Unable to construct a partial response; returning a ` + `416 Range Not Satisfiable response instead.`);
logger_js.logger.groupCollapsed(`View details here.`);
logger_js.logger.log(error);
logger_js.logger.log(request);
logger_js.logger.log(originalResponse);
logger_js.logger.groupEnd();
}
return new Response('', {
status: 416,
statusText: 'Range Not Satisfiable'
});
}
}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* The range request plugin makes it easy for a request with a 'Range' header to
* be fulfilled by a cached response.
*
* It does this by intercepting the `cachedResponseWillBeUsed` plugin callback
* and returning the appropriate subset of the cached response body.
*
* @memberof module:workbox-range-requests
*/
class RangeRequestsPlugin {
constructor() {
/**
* @param {Object} options
* @param {Request} options.request The original request, which may or may not
* contain a Range: header.
* @param {Response} options.cachedResponse The complete cached response.
* @return {Promise<Response>} If request contains a 'Range' header, then a
* new response with status 206 whose body is a subset of `cachedResponse` is
* returned. Otherwise, `cachedResponse` is returned as-is.
*
* @private
*/
this.cachedResponseWillBeUsed = async ({
request,
cachedResponse
}) => {
// Only return a sliced response if there's something valid in the cache,
// and there's a Range: header in the request.
if (cachedResponse && request.headers.has('range')) {
return await createPartialResponse(request, cachedResponse);
} // If there was no Range: header, or if cachedResponse wasn't valid, just
// pass it through as-is.
return cachedResponse;
};
}
}
exports.RangeRequestsPlugin = RangeRequestsPlugin;
exports.createPartialResponse = createPartialResponse;
return exports;
}({}, workbox.core._private, workbox.core._private, workbox.core._private));
//# sourceMappingURL=workbox-range-requests.dev.js.map
@@ -0,0 +1 @@
{"version":3,"file":"workbox-range-requests.dev.js","sources":["../_version.js","../utils/calculateEffectiveBoundaries.js","../utils/parseRangeHeader.js","../createPartialResponse.js","../RangeRequestsPlugin.js"],"sourcesContent":["\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:range-requests:5.1.4'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport { assert } from 'workbox-core/_private/assert.js';\nimport '../_version.js';\n/**\n * @param {Blob} blob A source blob.\n * @param {number} [start] The offset to use as the start of the\n * slice.\n * @param {number} [end] The offset to use as the end of the slice.\n * @return {Object} An object with `start` and `end` properties, reflecting\n * the effective boundaries to use given the size of the blob.\n *\n * @private\n */\nfunction calculateEffectiveBoundaries(blob, start, end) {\n if (process.env.NODE_ENV !== 'production') {\n assert.isInstance(blob, Blob, {\n moduleName: 'workbox-range-requests',\n funcName: 'calculateEffectiveBoundaries',\n paramName: 'blob',\n });\n }\n const blobSize = blob.size;\n if ((end && end > blobSize) || (start && start < 0)) {\n throw new WorkboxError('range-not-satisfiable', {\n size: blobSize,\n end,\n start,\n });\n }\n let effectiveStart;\n let effectiveEnd;\n if (start !== undefined && end !== undefined) {\n effectiveStart = start;\n // Range values are inclusive, so add 1 to the value.\n effectiveEnd = end + 1;\n }\n else if (start !== undefined && end === undefined) {\n effectiveStart = start;\n effectiveEnd = blobSize;\n }\n else if (end !== undefined && start === undefined) {\n effectiveStart = blobSize - end;\n effectiveEnd = blobSize;\n }\n return {\n start: effectiveStart,\n end: effectiveEnd,\n };\n}\nexport { calculateEffectiveBoundaries };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport { assert } from 'workbox-core/_private/assert.js';\nimport '../_version.js';\n/**\n * @param {string} rangeHeader A Range: header value.\n * @return {Object} An object with `start` and `end` properties, reflecting\n * the parsed value of the Range: header. If either the `start` or `end` are\n * omitted, then `null` will be returned.\n *\n * @private\n */\nfunction parseRangeHeader(rangeHeader) {\n if (process.env.NODE_ENV !== 'production') {\n assert.isType(rangeHeader, 'string', {\n moduleName: 'workbox-range-requests',\n funcName: 'parseRangeHeader',\n paramName: 'rangeHeader',\n });\n }\n const normalizedRangeHeader = rangeHeader.trim().toLowerCase();\n if (!normalizedRangeHeader.startsWith('bytes=')) {\n throw new WorkboxError('unit-must-be-bytes', { normalizedRangeHeader });\n }\n // Specifying multiple ranges separate by commas is valid syntax, but this\n // library only attempts to handle a single, contiguous sequence of bytes.\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range#Syntax\n if (normalizedRangeHeader.includes(',')) {\n throw new WorkboxError('single-range-only', { normalizedRangeHeader });\n }\n const rangeParts = /(\\d*)-(\\d*)/.exec(normalizedRangeHeader);\n // We need either at least one of the start or end values.\n if (!rangeParts || !(rangeParts[1] || rangeParts[2])) {\n throw new WorkboxError('invalid-range-values', { normalizedRangeHeader });\n }\n return {\n start: rangeParts[1] === '' ? undefined : Number(rangeParts[1]),\n end: rangeParts[2] === '' ? undefined : Number(rangeParts[2]),\n };\n}\nexport { parseRangeHeader };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { calculateEffectiveBoundaries } from './utils/calculateEffectiveBoundaries.js';\nimport { parseRangeHeader } from './utils/parseRangeHeader.js';\nimport './_version.js';\n/**\n * Given a `Request` and `Response` objects as input, this will return a\n * promise for a new `Response`.\n *\n * If the original `Response` already contains partial content (i.e. it has\n * a status of 206), then Line truncated
@@ -0,0 +1,2 @@
this.workbox=this.workbox||{},this.workbox.rangeRequests=function(t,e,n){"use strict";try{self["workbox:range-requests:5.1.4"]&&_()}catch(t){}async function r(t,n){try{if(206===n.status)return n;const r=t.headers.get("range");if(!r)throw new e.WorkboxError("no-range-header");const s=function(t){const n=t.trim().toLowerCase();if(!n.startsWith("bytes="))throw new e.WorkboxError("unit-must-be-bytes",{normalizedRangeHeader:n});if(n.includes(","))throw new e.WorkboxError("single-range-only",{normalizedRangeHeader:n});const r=/(\d*)-(\d*)/.exec(n);if(!r||!r[1]&&!r[2])throw new e.WorkboxError("invalid-range-values",{normalizedRangeHeader:n});return{start:""===r[1]?void 0:Number(r[1]),end:""===r[2]?void 0:Number(r[2])}}(r),a=await n.blob(),o=function(t,n,r){const s=t.size;if(r&&r>s||n&&n<0)throw new e.WorkboxError("range-not-satisfiable",{size:s,end:r,start:n});let a,o;return void 0!==n&&void 0!==r?(a=n,o=r+1):void 0!==n&&void 0===r?(a=n,o=s):void 0!==r&&void 0===n&&(a=s-r,o=s),{start:a,end:o}}(a,s.start,s.end),i=a.slice(o.start,o.end),d=i.size,u=new Response(i,{status:206,statusText:"Partial Content",headers:n.headers});return u.headers.set("Content-Length",String(d)),u.headers.set("Content-Range",`bytes ${o.start}-${o.end-1}/`+a.size),u}catch(t){return new Response("",{status:416,statusText:"Range Not Satisfiable"})}}return t.RangeRequestsPlugin=class{constructor(){this.cachedResponseWillBeUsed=async({request:t,cachedResponse:e})=>e&&t.headers.has("range")?await r(t,e):e}},t.createPartialResponse=r,t}({},workbox.core._private,workbox.core._private);
//# sourceMappingURL=workbox-range-requests.prod.js.map
@@ -0,0 +1 @@
{"version":3,"file":"workbox-range-requests.prod.js","sources":["../_version.js","../createPartialResponse.js","../utils/parseRangeHeader.js","../utils/calculateEffectiveBoundaries.js","../RangeRequestsPlugin.js"],"sourcesContent":["\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:range-requests:5.1.4'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { calculateEffectiveBoundaries } from './utils/calculateEffectiveBoundaries.js';\nimport { parseRangeHeader } from './utils/parseRangeHeader.js';\nimport './_version.js';\n/**\n * Given a `Request` and `Response` objects as input, this will return a\n * promise for a new `Response`.\n *\n * If the original `Response` already contains partial content (i.e. it has\n * a status of 206), then this assumes it already fulfills the `Range:`\n * requirements, and will return it as-is.\n *\n * @param {Request} request A request, which should contain a Range:\n * header.\n * @param {Response} originalResponse A response.\n * @return {Promise<Response>} Either a `206 Partial Content` response, with\n * the response body set to the slice of content specified by the request's\n * `Range:` header, or a `416 Range Not Satisfiable` response if the\n * conditions of the `Range:` header can't be met.\n *\n * @memberof module:workbox-range-requests\n */\nasync function createPartialResponse(request, originalResponse) {\n try {\n if (process.env.NODE_ENV !== 'production') {\n assert.isInstance(request, Request, {\n moduleName: 'workbox-range-requests',\n funcName: 'createPartialResponse',\n paramName: 'request',\n });\n assert.isInstance(originalResponse, Response, {\n moduleName: 'workbox-range-requests',\n funcName: 'createPartialResponse',\n paramName: 'originalResponse',\n });\n }\n if (originalResponse.status === 206) {\n // If we already have a 206, then just pass it through as-is;\n // see https://github.com/GoogleChrome/workbox/issues/1720\n return originalResponse;\n }\n const rangeHeader = request.headers.get('range');\n if (!rangeHeader) {\n throw new WorkboxError('no-range-header');\n }\n const boundaries = parseRangeHeader(rangeHeader);\n const originalBlob = await originalResponse.blob();\n const effectiveBoundaries = calculateEffectiveBoundaries(originalBlob, boundaries.start, boundaries.end);\n const slicedBlob = originalBlob.slice(effectiveBoundaries.start, effectiveBoundaries.end);\n const slicedBlobSize = slicedBlob.size;\n const slicedResponse = new Response(slicedBlob, {\n // Status code 206 is for a Partial Content response.\n // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/206\n status: 206,\n statusText: 'Partial Content',\n headers: originalResponse.headers,\n });\n slicedResponse.headers.set('Content-Length', String(slicedBlobSize));\n slicedResponse.headers.set('Content-Range', `bytes ${effectiveBoundaries.start}-${effectiveBoundaries.end - 1}/` +\n originalBlob.size);\n return slicedResponse;\n }\n catch (error) {\n if (process.env.NODE_ENV !== 'production') {\n logger.warn(`Unable to construct a partial response; returning a ` +\n `416 Range Not Satisfiable response instead.`);\n logger.groupCollapsed(`View details here.`);\n logger.log(error);\n logger.log(request);\n logger.log(originalResponse);\n logger.groupEnd();\n }\n return new Response('', {\n status: 416,\n statusText: 'Range Not Satisfiable',\n });\n }\n}\nexport { createPartialResponse };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport { assert } from 'workbox-core/_private/assert.js';\nimport '../_version.js';\n/**\n * @param {string} rangeHeader A Range: header value.\n * @return {Object} An object with `start` and `end` properties, reflecting\n * the parsed value of the Range: header. If either the `start` or `end` are\n * omitted, then `null` will be returned.\n *\n * @private\n */\nfunction parseRangeHeader(rangeHeader) {\n if (process.env.NODE_ENV !== 'production') {\n assertLine truncated
@@ -0,0 +1,923 @@
this.workbox = this.workbox || {};
this.workbox.routing = (function (exports, assert_js, logger_js, WorkboxError_js, getFriendlyURL_js) {
'use strict';
try {
self['workbox:routing:5.1.4'] && _();
} catch (e) {}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* The default HTTP method, 'GET', used when there's no specific method
* configured for a route.
*
* @type {string}
*
* @private
*/
const defaultMethod = 'GET';
/**
* The list of valid HTTP methods associated with requests that could be routed.
*
* @type {Array<string>}
*
* @private
*/
const validMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT'];
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* @param {function()|Object} handler Either a function, or an object with a
* 'handle' method.
* @return {Object} An object with a handle method.
*
* @private
*/
const normalizeHandler = handler => {
if (handler && typeof handler === 'object') {
{
assert_js.assert.hasMethod(handler, 'handle', {
moduleName: 'workbox-routing',
className: 'Route',
funcName: 'constructor',
paramName: 'handler'
});
}
return handler;
} else {
{
assert_js.assert.isType(handler, 'function', {
moduleName: 'workbox-routing',
className: 'Route',
funcName: 'constructor',
paramName: 'handler'
});
}
return {
handle: handler
};
}
};
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* A `Route` consists of a pair of callback functions, "match" and "handler".
* The "match" callback determine if a route should be used to "handle" a
* request by returning a non-falsy value if it can. The "handler" callback
* is called when there is a match and should return a Promise that resolves
* to a `Response`.
*
* @memberof module:workbox-routing
*/
class Route {
/**
* Constructor for Route class.
*
* @param {module:workbox-routing~matchCallback} match
* A callback function that determines whether the route matches a given
* `fetch` event by returning a non-falsy value.
* @param {module:workbox-routing~handlerCallback} handler A callback
* function that returns a Promise resolving to a Response.
* @param {string} [method='GET'] The HTTP method to match the Route
* against.
*/
constructor(match, handler, method = defaultMethod) {
{
assert_js.assert.isType(match, 'function', {
moduleName: 'workbox-routing',
className: 'Route',
funcName: 'constructor',
paramName: 'match'
});
if (method) {
assert_js.assert.isOneOf(method, validMethods, {
paramName: 'method'
});
}
} // These values are referenced directly by Router so cannot be
// altered by minificaton.
this.handler = normalizeHandler(handler);
this.match = match;
this.method = method;
}
}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* NavigationRoute makes it easy to create a
* [Route]{@link module:workbox-routing.Route} that matches for browser
* [navigation requests]{@link https://developers.google.com/web/fundamentals/primers/service-workers/high-performance-loading#first_what_are_navigation_requests}.
*
* It will only match incoming Requests whose
* [`mode`]{@link https://fetch.spec.whatwg.org/#concept-request-mode}
* is set to `navigate`.
*
* You can optionally only apply this route to a subset of navigation requests
* by using one or both of the `denylist` and `allowlist` parameters.
*
* @memberof module:workbox-routing
* @extends module:workbox-routing.Route
*/
class NavigationRoute extends Route {
/**
* If both `denylist` and `allowlist` are provided, the `denylist` will
* take precedence and the request will not match this route.
*
* The regular expressions in `allowlist` and `denylist`
* are matched against the concatenated
* [`pathname`]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLHyperlinkElementUtils/pathname}
* and [`search`]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLHyperlinkElementUtils/search}
* portions of the requested URL.
*
* @param {module:workbox-routing~handlerCallback} handler A callback
* function that returns a Promise resulting in a Response.
* @param {Object} options
* @param {Array<RegExp>} [options.denylist] If any of these patterns match,
* the route will not handle the request (even if a allowlist RegExp matches).
* @param {Array<RegExp>} [options.allowlist=[/./]] If any of these patterns
* match the URL's pathname and search parameter, the route will handle the
* request (assuming the denylist doesn't match).
*/
constructor(handler, {
allowlist = [/./],
denylist = []
} = {}) {
{
assert_js.assert.isArrayOfClass(allowlist, RegExp, {
moduleName: 'workbox-routing',
className: 'NavigationRoute',
funcName: 'constructor',
paramName: 'options.allowlist'
});
assert_js.assert.isArrayOfClass(denylist, RegExp, {
moduleName: 'workbox-routing',
className: 'NavigationRoute',
funcName: 'constructor',
paramName: 'options.denylist'
});
}
super(options => this._match(options), handler);
this._allowlist = allowlist;
this._denylist = denylist;
}
/**
* Routes match handler.
*
* @param {Object} options
* @param {URL} options.url
* @param {Request} options.request
* @return {boolean}
*
* @private
*/
_match({
url,
request
}) {
if (request && request.mode !== 'navigate') {
return false;
}
const pathnameAndSearch = url.pathname + url.search;
for (const regExp of this._denylist) {
if (regExp.test(pathnameAndSearch)) {
{
logger_js.logger.log(`The navigation route ${pathnameAndSearch} is not ` + `being used, since the URL matches this denylist pattern: ` + `${regExp}`);
}
return false;
}
}
if (this._allowlist.some(regExp => regExp.test(pathnameAndSearch))) {
{
logger_js.logger.debug(`The navigation route ${pathnameAndSearch} ` + `is being used.`);
}
return true;
}
{
logger_js.logger.log(`The navigation route ${pathnameAndSearch} is not ` + `being used, since the URL being navigated to doesn't ` + `match the allowlist.`);
}
return false;
}
}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* RegExpRoute makes it easy to create a regular expression based
* [Route]{@link module:workbox-routing.Route}.
*
* For same-origin requests the RegExp only needs to match part of the URL. For
* requests against third-party servers, you must define a RegExp that matches
* the start of the URL.
*
* [See the module docs for info.]{@link https://developers.google.com/web/tools/workbox/modules/workbox-routing}
*
* @memberof module:workbox-routing
* @extends module:workbox-routing.Route
*/
class RegExpRoute extends Route {
/**
* If the regular expression contains
* [capture groups]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#grouping-back-references},
* the captured values will be passed to the
* [handler's]{@link module:workbox-routing~handlerCallback} `params`
* argument.
*
* @param {RegExp} regExp The regular expression to match against URLs.
* @param {module:workbox-routing~handlerCallback} handler A callback
* function that returns a Promise resulting in a Response.
* @param {string} [method='GET'] The HTTP method to match the Route
* against.
*/
constructor(regExp, handler, method) {
{
assert_js.assert.isInstance(regExp, RegExp, {
moduleName: 'workbox-routing',
className: 'RegExpRoute',
funcName: 'constructor',
paramName: 'pattern'
});
}
const match = ({
url
}) => {
const result = regExp.exec(url.href); // Return immediately if there's no match.
if (!result) {
return;
} // Require that the match start at the first character in the URL string
// if it's a cross-origin request.
// See https://github.com/GoogleChrome/workbox/issues/281 for the context
// behind this behavior.
if (url.origin !== location.origin && result.index !== 0) {
{
logger_js.logger.debug(`The regular expression '${regExp}' only partially matched ` + `against the cross-origin URL '${url}'. RegExpRoute's will only ` + `handle cross-origin requests if they match the entire URL.`);
}
return;
} // If the route matches, but there aren't any capture groups defined, then
// this will return [], which is truthy and therefore sufficient to
// indicate a match.
// If there are capture groups, then it will return their values.
return result.slice(1);
};
super(match, handler, method);
}
}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* The Router can be used to process a FetchEvent through one or more
* [Routes]{@link module:workbox-routing.Route} responding with a Request if
* a matching route exists.
*
* If no route matches a given a request, the Router will use a "default"
* handler if one is defined.
*
* Should the matching Route throw an error, the Router will use a "catch"
* handler if one is defined to gracefully deal with issues and respond with a
* Request.
*
* If a request matches multiple routes, the **earliest** registered route will
* be used to respond to the request.
*
* @memberof module:workbox-routing
*/
class Router {
/**
* Initializes a new Router.
*/
constructor() {
this._routes = new Map();
}
/**
* @return {Map<string, Array<module:workbox-routing.Route>>} routes A `Map` of HTTP
* method name ('GET', etc.) to an array of all the corresponding `Route`
* instances that are registered.
*/
get routes() {
return this._routes;
}
/**
* Adds a fetch event listener to respond to events when a route matches
* the event's request.
*/
addFetchListener() {
// See https://github.com/Microsoft/TypeScript/issues/28357#issuecomment-436484705
self.addEventListener('fetch', event => {
const {
request
} = event;
const responsePromise = this.handleRequest({
request,
event
});
if (responsePromise) {
event.respondWith(responsePromise);
}
});
}
/**
* Adds a message event listener for URLs to cache from the window.
* This is useful to cache resources loaded on the page prior to when the
* service worker started controlling it.
*
* The format of the message data sent from the window should be as follows.
* Where the `urlsToCache` array may consist of URL strings or an array of
* URL string + `requestInit` object (the same as you'd pass to `fetch()`).
*
* ```
* {
* type: 'CACHE_URLS',
* payload: {
* urlsToCache: [
* './script1.js',
* './script2.js',
* ['./script3.js', {mode: 'no-cors'}],
* ],
* },
* }
* ```
*/
addCacheListener() {
// See https://github.com/Microsoft/TypeScript/issues/28357#issuecomment-436484705
self.addEventListener('message', event => {
if (event.data && event.data.type === 'CACHE_URLS') {
const {
payload
} = event.data;
{
logger_js.logger.debug(`Caching URLs from the window`, payload.urlsToCache);
}
const requestPromises = Promise.all(payload.urlsToCache.map(entry => {
if (typeof entry === 'string') {
entry = [entry];
}
const request = new Request(...entry);
return this.handleRequest({
request
}); // TODO(philipwalton): TypeScript errors without this typecast for
// some reason (probably a bug). The real type here should work but
// doesn't: `Array<Promise<Response> | undefined>`.
})); // TypeScript
event.waitUntil(requestPromises); // If a MessageChannel was used, reply to the message on success.
if (event.ports && event.ports[0]) {
requestPromises.then(() => event.ports[0].postMessage(true));
}
}
});
}
/**
* Apply the routing rules to a FetchEvent object to get a Response from an
* appropriate Route's handler.
*
* @param {Object} options
* @param {Request} options.request The request to handle (this is usually
* from a fetch event, but it does not have to be).
* @param {FetchEvent} [options.event] The event that triggered the request,
* if applicable.
* @return {Promise<Response>|undefined} A promise is returned if a
* registered route can handle the request. If there is no matching
* route and there's no `defaultHandler`, `undefined` is returned.
*/
handleRequest({
request,
event
}) {
{
assert_js.assert.isInstance(request, Request, {
moduleName: 'workbox-routing',
className: 'Router',
funcName: 'handleRequest',
paramName: 'options.request'
});
}
const url = new URL(request.url, location.href);
if (!url.protocol.startsWith('http')) {
{
logger_js.logger.debug(`Workbox Router only supports URLs that start with 'http'.`);
}
return;
}
const {
params,
route
} = this.findMatchingRoute({
url,
request,
event
});
let handler = route && route.handler;
const debugMessages = [];
{
if (handler) {
debugMessages.push([`Found a route to handle this request:`, route]);
if (params) {
debugMessages.push([`Passing the following params to the route's handler:`, params]);
}
}
} // If we don't have a handler because there was no matching route, then
// fall back to defaultHandler if that's defined.
if (!handler && this._defaultHandler) {
{
debugMessages.push(`Failed to find a matching route. Falling ` + `back to the default handler.`);
}
handler = this._defaultHandler;
}
if (!handler) {
{
// No handler so Workbox will do nothing. If logs is set of debug
// i.e. verbose, we should print out this information.
logger_js.logger.debug(`No route found for: ${getFriendlyURL_js.getFriendlyURL(url)}`);
}
return;
}
{
// We have a handler, meaning Workbox is going to handle the route.
// print the routing details to the console.
logger_js.logger.groupCollapsed(`Router is responding to: ${getFriendlyURL_js.getFriendlyURL(url)}`);
debugMessages.forEach(msg => {
if (Array.isArray(msg)) {
logger_js.logger.log(...msg);
} else {
logger_js.logger.log(msg);
}
});
logger_js.logger.groupEnd();
} // Wrap in try and catch in case the handle method throws a synchronous
// error. It should still callback to the catch handler.
let responsePromise;
try {
responsePromise = handler.handle({
url,
request,
event,
params
});
} catch (err) {
responsePromise = Promise.reject(err);
}
if (responsePromise instanceof Promise && this._catchHandler) {
responsePromise = responsePromise.catch(err => {
{
// Still include URL here as it will be async from the console group
// and may not make sense without the URL
logger_js.logger.groupCollapsed(`Error thrown when responding to: ` + ` ${getFriendlyURL_js.getFriendlyURL(url)}. Falling back to Catch Handler.`);
logger_js.logger.error(`Error thrown by:`, route);
logger_js.logger.error(err);
logger_js.logger.groupEnd();
}
return this._catchHandler.handle({
url,
request,
event
});
});
}
return responsePromise;
}
/**
* Checks a request and URL (and optionally an event) against the list of
* registered routes, and if there's a match, returns the corresponding
* route along with any params generated by the match.
*
* @param {Object} options
* @param {URL} options.url
* @param {Request} options.request The request to match.
* @param {Event} [options.event] The corresponding event (unless N/A).
* @return {Object} An object with `route` and `params` properties.
* They are populated if a matching route was found or `undefined`
* otherwise.
*/
findMatchingRoute({
url,
request,
event
}) {
{
assert_js.assert.isInstance(url, URL, {
moduleName: 'workbox-routing',
className: 'Router',
funcName: 'findMatchingRoute',
paramName: 'options.url'
});
assert_js.assert.isInstance(request, Request, {
moduleName: 'workbox-routing',
className: 'Router',
funcName: 'findMatchingRoute',
paramName: 'options.request'
});
}
const routes = this._routes.get(request.method) || [];
for (const route of routes) {
let params;
const matchResult = route.match({
url,
request,
event
});
if (matchResult) {
// See https://github.com/GoogleChrome/workbox/issues/2079
params = matchResult;
if (Array.isArray(matchResult) && matchResult.length === 0) {
// Instead of passing an empty array in as params, use undefined.
params = undefined;
} else if (matchResult.constructor === Object && Object.keys(matchResult).length === 0) {
// Instead of passing an empty object in as params, use undefined.
params = undefined;
} else if (typeof matchResult === 'boolean') {
// For the boolean value true (rather than just something truth-y),
// don't set params.
// See https://github.com/GoogleChrome/workbox/pull/2134#issuecomment-513924353
params = undefined;
} // Return early if have a match.
return {
route,
params
};
}
} // If no match was found above, return and empty object.
return {};
}
/**
* Define a default `handler` that's called when no routes explicitly
* match the incoming request.
*
* Without a default handler, unmatched requests will go against the
* network as if there were no service worker present.
*
* @param {module:workbox-routing~handlerCallback} handler A callback
* function that returns a Promise resulting in a Response.
*/
setDefaultHandler(handler) {
this._defaultHandler = normalizeHandler(handler);
}
/**
* If a Route throws an error while handling a request, this `handler`
* will be called and given a chance to provide a response.
*
* @param {module:workbox-routing~handlerCallback} handler A callback
* function that returns a Promise resulting in a Response.
*/
setCatchHandler(handler) {
this._catchHandler = normalizeHandler(handler);
}
/**
* Registers a route with the router.
*
* @param {module:workbox-routing.Route} route The route to register.
*/
registerRoute(route) {
{
assert_js.assert.isType(route, 'object', {
moduleName: 'workbox-routing',
className: 'Router',
funcName: 'registerRoute',
paramName: 'route'
});
assert_js.assert.hasMethod(route, 'match', {
moduleName: 'workbox-routing',
className: 'Router',
funcName: 'registerRoute',
paramName: 'route'
});
assert_js.assert.isType(route.handler, 'object', {
moduleName: 'workbox-routing',
className: 'Router',
funcName: 'registerRoute',
paramName: 'route'
});
assert_js.assert.hasMethod(route.handler, 'handle', {
moduleName: 'workbox-routing',
className: 'Router',
funcName: 'registerRoute',
paramName: 'route.handler'
});
assert_js.assert.isType(route.method, 'string', {
moduleName: 'workbox-routing',
className: 'Router',
funcName: 'registerRoute',
paramName: 'route.method'
});
}
if (!this._routes.has(route.method)) {
this._routes.set(route.method, []);
} // Give precedence to all of the earlier routes by adding this additional
// route to the end of the array.
this._routes.get(route.method).push(route);
}
/**
* Unregisters a route with the router.
*
* @param {module:workbox-routing.Route} route The route to unregister.
*/
unregisterRoute(route) {
if (!this._routes.has(route.method)) {
throw new WorkboxError_js.WorkboxError('unregister-route-but-not-found-with-method', {
method: route.method
});
}
const routeIndex = this._routes.get(route.method).indexOf(route);
if (routeIndex > -1) {
this._routes.get(route.method).splice(routeIndex, 1);
} else {
throw new WorkboxError_js.WorkboxError('unregister-route-route-not-registered');
}
}
}
/*
Copyright 2019 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
let defaultRouter;
/**
* Creates a new, singleton Router instance if one does not exist. If one
* does already exist, that instance is returned.
*
* @private
* @return {Router}
*/
const getOrCreateDefaultRouter = () => {
if (!defaultRouter) {
defaultRouter = new Router(); // The helpers that use the default Router assume these listeners exist.
defaultRouter.addFetchListener();
defaultRouter.addCacheListener();
}
return defaultRouter;
};
/*
Copyright 2019 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* Easily register a RegExp, string, or function with a caching
* strategy to a singleton Router instance.
*
* This method will generate a Route for you if needed and
* call [registerRoute()]{@link module:workbox-routing.Router#registerRoute}.
*
* @param {RegExp|string|module:workbox-routing.Route~matchCallback|module:workbox-routing.Route} capture
* If the capture param is a `Route`, all other arguments will be ignored.
* @param {module:workbox-routing~handlerCallback} [handler] A callback
* function that returns a Promise resulting in a Response. This parameter
* is required if `capture` is not a `Route` object.
* @param {string} [method='GET'] The HTTP method to match the Route
* against.
* @return {module:workbox-routing.Route} The generated `Route`(Useful for
* unregistering).
*
* @memberof module:workbox-routing
*/
function registerRoute(capture, handler, method) {
let route;
if (typeof capture === 'string') {
const captureUrl = new URL(capture, location.href);
{
if (!(capture.startsWith('/') || capture.startsWith('http'))) {
throw new WorkboxError_js.WorkboxError('invalid-string', {
moduleName: 'workbox-routing',
funcName: 'registerRoute',
paramName: 'capture'
});
} // We want to check if Express-style wildcards are in the pathname only.
// TODO: Remove this log message in v4.
const valueToCheck = capture.startsWith('http') ? captureUrl.pathname : capture; // See https://github.com/pillarjs/path-to-regexp#parameters
const wildcards = '[*:?+]';
if (new RegExp(`${wildcards}`).exec(valueToCheck)) {
logger_js.logger.debug(`The '$capture' parameter contains an Express-style wildcard ` + `character (${wildcards}). Strings are now always interpreted as ` + `exact matches; use a RegExp for partial or wildcard matches.`);
}
}
const matchCallback = ({
url
}) => {
{
if (url.pathname === captureUrl.pathname && url.origin !== captureUrl.origin) {
logger_js.logger.debug(`${capture} only partially matches the cross-origin URL ` + `${url}. This route will only handle cross-origin requests ` + `if they match the entire URL.`);
}
}
return url.href === captureUrl.href;
}; // If `capture` is a string then `handler` and `method` must be present.
route = new Route(matchCallback, handler, method);
} else if (capture instanceof RegExp) {
// If `capture` is a `RegExp` then `handler` and `method` must be present.
route = new RegExpRoute(capture, handler, method);
} else if (typeof capture === 'function') {
// If `capture` is a function then `handler` and `method` must be present.
route = new Route(capture, handler, method);
} else if (capture instanceof Route) {
route = capture;
} else {
throw new WorkboxError_js.WorkboxError('unsupported-route-type', {
moduleName: 'workbox-routing',
funcName: 'registerRoute',
paramName: 'capture'
});
}
const defaultRouter = getOrCreateDefaultRouter();
defaultRouter.registerRoute(route);
return route;
}
/*
Copyright 2019 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* If a Route throws an error while handling a request, this `handler`
* will be called and given a chance to provide a response.
*
* @param {module:workbox-routing~handlerCallback} handler A callback
* function that returns a Promise resulting in a Response.
*
* @memberof module:workbox-routing
*/
function setCatchHandler(handler) {
const defaultRouter = getOrCreateDefaultRouter();
defaultRouter.setCatchHandler(handler);
}
/*
Copyright 2019 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* Define a default `handler` that's called when no routes explicitly
* match the incoming request.
*
* Without a default handler, unmatched requests will go against the
* network as if there were no service worker present.
*
* @param {module:workbox-routing~handlerCallback} handler A callback
* function that returns a Promise resulting in a Response.
*
* @memberof module:workbox-routing
*/
function setDefaultHandler(handler) {
const defaultRouter = getOrCreateDefaultRouter();
defaultRouter.setDefaultHandler(handler);
}
exports.NavigationRoute = NavigationRoute;
exports.RegExpRoute = RegExpRoute;
exports.Route = Route;
exports.Router = Router;
exports.registerRoute = registerRoute;
exports.setCatchHandler = setCatchHandler;
exports.setDefaultHandler = setDefaultHandler;
return exports;
}({}, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private));
//# sourceMappingURL=workbox-routing.dev.js.map
@@ -0,0 +1 @@
{"version":3,"file":"workbox-routing.dev.js","sources":["../_version.js","../utils/constants.js","../utils/normalizeHandler.js","../Route.js","../NavigationRoute.js","../RegExpRoute.js","../Router.js","../utils/getOrCreateDefaultRouter.js","../registerRoute.js","../setCatchHandler.js","../setDefaultHandler.js"],"sourcesContent":["\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:routing:5.1.4'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\n/**\n * The default HTTP method, 'GET', used when there's no specific method\n * configured for a route.\n *\n * @type {string}\n *\n * @private\n */\nexport const defaultMethod = 'GET';\n/**\n * The list of valid HTTP methods associated with requests that could be routed.\n *\n * @type {Array<string>}\n *\n * @private\n */\nexport const validMethods = [\n 'DELETE',\n 'GET',\n 'HEAD',\n 'PATCH',\n 'POST',\n 'PUT',\n];\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport '../_version.js';\n/**\n * @param {function()|Object} handler Either a function, or an object with a\n * 'handle' method.\n * @return {Object} An object with a handle method.\n *\n * @private\n */\nexport const normalizeHandler = (handler) => {\n if (handler && typeof handler === 'object') {\n if (process.env.NODE_ENV !== 'production') {\n assert.hasMethod(handler, 'handle', {\n moduleName: 'workbox-routing',\n className: 'Route',\n funcName: 'constructor',\n paramName: 'handler',\n });\n }\n return handler;\n }\n else {\n if (process.env.NODE_ENV !== 'production') {\n assert.isType(handler, 'function', {\n moduleName: 'workbox-routing',\n className: 'Route',\n funcName: 'constructor',\n paramName: 'handler',\n });\n }\n return { handle: handler };\n }\n};\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { defaultMethod, validMethods } from './utils/constants.js';\nimport { normalizeHandler } from './utils/normalizeHandler.js';\nimport './_version.js';\n/**\n * A `Route` consists of a pair of callback functions, \"match\" and \"handler\".\n * The \"match\" callback determine if a route should be used to \"handle\" a\n * request by returning a non-falsy value if it can. The \"handler\" callback\n * is called when there is a match and should return a Promise that resolves\n * to a `Response`.\n *\n * @memberof module:workbox-routing\n */\nclass Route {\n /**\n * Constructor for Route class.\n *\n * @param {module:workbox-routing~matchCallback} match\n * A callback function that determines whether the route matches a given\n * `fetch` event by returning a non-falsy value.\n * @param {module:workbox-routing~handlerCallback} handler A callback\n * function that returns a Promise resolving to a Response.\n * @param {string} [method='GET'] The HTTP method to match the Route\n * against.\n */\n constructor(match, handler, method = defaultMethod) {\n if (process.env.NODE_ENV !== 'production') {\n assert.isType(match, 'function', {\n moduleName: 'workbox-routing',\n className: 'Route',\n funcName: 'constructor',\n paramName: 'match',\n });\n if (method) {\n assert.isOneOf(method, validMethods, { paramName: 'method' });\n }\n }\n // These values are referenced directly by Router so cannot be\n // altered by minificaton.\n this.handler = normalizeHandler(handler);\n this.match = match;\n this.method = method;\n }\n}\nexport { Route };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { Route } from './Route.js';\nimport './_version.js';\n/**\n * NavigationRoute makes it easy to create a\n * [Route]{@link module:workbox-routing.Route} that matches for browser\n * [navigation requests]{@link https://developers.google.com/web/fundamentals/primers/service-workers/high-performance-loading#first_what_are_nLine truncated
@@ -0,0 +1,2 @@
this.workbox=this.workbox||{},this.workbox.routing=function(t,e){"use strict";try{self["workbox:routing:5.1.4"]&&_()}catch(t){}const s=t=>t&&"object"==typeof t?t:{handle:t};class r{constructor(t,e,r="GET"){this.handler=s(e),this.match=t,this.method=r}}class n extends r{constructor(t,e,s){super(({url:e})=>{const s=t.exec(e.href);if(s&&(e.origin===location.origin||0===s.index))return s.slice(1)},e,s)}}class o{constructor(){this.t=new Map}get routes(){return this.t}addFetchListener(){self.addEventListener("fetch",t=>{const{request:e}=t,s=this.handleRequest({request:e,event:t});s&&t.respondWith(s)})}addCacheListener(){self.addEventListener("message",t=>{if(t.data&&"CACHE_URLS"===t.data.type){const{payload:e}=t.data,s=Promise.all(e.urlsToCache.map(t=>{"string"==typeof t&&(t=[t]);const e=new Request(...t);return this.handleRequest({request:e})}));t.waitUntil(s),t.ports&&t.ports[0]&&s.then(()=>t.ports[0].postMessage(!0))}})}handleRequest({request:t,event:e}){const s=new URL(t.url,location.href);if(!s.protocol.startsWith("http"))return;const{params:r,route:n}=this.findMatchingRoute({url:s,request:t,event:e});let o,i=n&&n.handler;if(!i&&this.s&&(i=this.s),i){try{o=i.handle({url:s,request:t,event:e,params:r})}catch(t){o=Promise.reject(t)}return o instanceof Promise&&this.o&&(o=o.catch(r=>this.o.handle({url:s,request:t,event:e}))),o}}findMatchingRoute({url:t,request:e,event:s}){const r=this.t.get(e.method)||[];for(const n of r){let r;const o=n.match({url:t,request:e,event:s});if(o)return r=o,(Array.isArray(o)&&0===o.length||o.constructor===Object&&0===Object.keys(o).length||"boolean"==typeof o)&&(r=void 0),{route:n,params:r}}return{}}setDefaultHandler(t){this.s=s(t)}setCatchHandler(t){this.o=s(t)}registerRoute(t){this.t.has(t.method)||this.t.set(t.method,[]),this.t.get(t.method).push(t)}unregisterRoute(t){if(!this.t.has(t.method))throw new e.WorkboxError("unregister-route-but-not-found-with-method",{method:t.method});const s=this.t.get(t.method).indexOf(t);if(!(s>-1))throw new e.WorkboxError("unregister-route-route-not-registered");this.t.get(t.method).splice(s,1)}}let i;const u=()=>(i||(i=new o,i.addFetchListener(),i.addCacheListener()),i);return t.NavigationRoute=class extends r{constructor(t,{allowlist:e=[/./],denylist:s=[]}={}){super(t=>this.i(t),t),this.u=e,this.h=s}i({url:t,request:e}){if(e&&"navigate"!==e.mode)return!1;const s=t.pathname+t.search;for(const t of this.h)if(t.test(s))return!1;return!!this.u.some(t=>t.test(s))}},t.RegExpRoute=n,t.Route=r,t.Router=o,t.registerRoute=function(t,s,o){let i;if("string"==typeof t){const e=new URL(t,location.href);i=new r(({url:t})=>t.href===e.href,s,o)}else if(t instanceof RegExp)i=new n(t,s,o);else if("function"==typeof t)i=new r(t,s,o);else{if(!(t instanceof r))throw new e.WorkboxError("unsupported-route-type",{moduleName:"workbox-routing",funcName:"registerRoute",paramName:"capture"});i=t}return u().registerRoute(i),i},t.setCatchHandler=function(t){u().setCatchHandler(t)},t.setDefaultHandler=function(t){u().setDefaultHandler(t)},t}({},workbox.core._private);
//# sourceMappingURL=workbox-routing.prod.js.map
@@ -0,0 +1 @@
{"version":3,"file":"workbox-routing.prod.js","sources":["../_version.js","../utils/constants.js","../utils/normalizeHandler.js","../Route.js","../RegExpRoute.js","../Router.js","../utils/getOrCreateDefaultRouter.js","../NavigationRoute.js","../registerRoute.js","../setCatchHandler.js","../setDefaultHandler.js"],"sourcesContent":["\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:routing:5.1.4'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\n/**\n * The default HTTP method, 'GET', used when there's no specific method\n * configured for a route.\n *\n * @type {string}\n *\n * @private\n */\nexport const defaultMethod = 'GET';\n/**\n * The list of valid HTTP methods associated with requests that could be routed.\n *\n * @type {Array<string>}\n *\n * @private\n */\nexport const validMethods = [\n 'DELETE',\n 'GET',\n 'HEAD',\n 'PATCH',\n 'POST',\n 'PUT',\n];\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport '../_version.js';\n/**\n * @param {function()|Object} handler Either a function, or an object with a\n * 'handle' method.\n * @return {Object} An object with a handle method.\n *\n * @private\n */\nexport const normalizeHandler = (handler) => {\n if (handler && typeof handler === 'object') {\n if (process.env.NODE_ENV !== 'production') {\n assert.hasMethod(handler, 'handle', {\n moduleName: 'workbox-routing',\n className: 'Route',\n funcName: 'constructor',\n paramName: 'handler',\n });\n }\n return handler;\n }\n else {\n if (process.env.NODE_ENV !== 'production') {\n assert.isType(handler, 'function', {\n moduleName: 'workbox-routing',\n className: 'Route',\n funcName: 'constructor',\n paramName: 'handler',\n });\n }\n return { handle: handler };\n }\n};\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { defaultMethod, validMethods } from './utils/constants.js';\nimport { normalizeHandler } from './utils/normalizeHandler.js';\nimport './_version.js';\n/**\n * A `Route` consists of a pair of callback functions, \"match\" and \"handler\".\n * The \"match\" callback determine if a route should be used to \"handle\" a\n * request by returning a non-falsy value if it can. The \"handler\" callback\n * is called when there is a match and should return a Promise that resolves\n * to a `Response`.\n *\n * @memberof module:workbox-routing\n */\nclass Route {\n /**\n * Constructor for Route class.\n *\n * @param {module:workbox-routing~matchCallback} match\n * A callback function that determines whether the route matches a given\n * `fetch` event by returning a non-falsy value.\n * @param {module:workbox-routing~handlerCallback} handler A callback\n * function that returns a Promise resolving to a Response.\n * @param {string} [method='GET'] The HTTP method to match the Route\n * against.\n */\n constructor(match, handler, method = defaultMethod) {\n if (process.env.NODE_ENV !== 'production') {\n assert.isType(match, 'function', {\n moduleName: 'workbox-routing',\n className: 'Route',\n funcName: 'constructor',\n paramName: 'match',\n });\n if (method) {\n assert.isOneOf(method, validMethods, { paramName: 'method' });\n }\n }\n // These values are referenced directly by Router so cannot be\n // altered by minificaton.\n this.handler = normalizeHandler(handler);\n this.match = match;\n this.method = method;\n }\n}\nexport { Route };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { Route } from './Route.js';\nimport './_version.js';\n/**\n * RegExpRoute makes it easy to create a regular expression based\n * [Route]{@link module:workbox-routing.Route}.\n *\n * For same-origin requests the RegExp only needs to match part of the URL. For\n * requests against third-party servers, you must define a RLine truncated
@@ -0,0 +1,923 @@
this.workbox = this.workbox || {};
this.workbox.strategies = (function (exports, assert_js, cacheNames_js, cacheWrapper_js, fetchWrapper_js, getFriendlyURL_js, logger_js, WorkboxError_js) {
'use strict';
try {
self['workbox:strategies:5.1.4'] && _();
} catch (e) {}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
const messages = {
strategyStart: (strategyName, request) => `Using ${strategyName} to respond to '${getFriendlyURL_js.getFriendlyURL(request.url)}'`,
printFinalResponse: response => {
if (response) {
logger_js.logger.groupCollapsed(`View the final response here.`);
logger_js.logger.log(response || '[No response returned]');
logger_js.logger.groupEnd();
}
}
};
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* An implementation of a [cache-first]{@link https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#cache-falling-back-to-network}
* request strategy.
*
* A cache first strategy is useful for assets that have been revisioned,
* such as URLs like `/styles/example.a8f5f1.css`, since they
* can be cached for long periods of time.
*
* If the network request fails, and there is no cache match, this will throw
* a `WorkboxError` exception.
*
* @memberof module:workbox-strategies
*/
class CacheFirst {
/**
* @param {Object} options
* @param {string} options.cacheName Cache name to store and retrieve
* requests. Defaults to cache names provided by
* [workbox-core]{@link module:workbox-core.cacheNames}.
* @param {Array<Object>} options.plugins [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}
* to use in conjunction with this caching strategy.
* @param {Object} options.fetchOptions Values passed along to the
* [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)
* of all fetch() requests made by this strategy.
* @param {Object} options.matchOptions [`CacheQueryOptions`](https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions)
*/
constructor(options = {}) {
this._cacheName = cacheNames_js.cacheNames.getRuntimeName(options.cacheName);
this._plugins = options.plugins || [];
this._fetchOptions = options.fetchOptions;
this._matchOptions = options.matchOptions;
}
/**
* This method will perform a request strategy and follows an API that
* will work with the
* [Workbox Router]{@link module:workbox-routing.Router}.
*
* @param {Object} options
* @param {Request|string} options.request A request to run this strategy for.
* @param {Event} [options.event] The event that triggered the request.
* @return {Promise<Response>}
*/
async handle({
event,
request
}) {
const logs = [];
if (typeof request === 'string') {
request = new Request(request);
}
{
assert_js.assert.isInstance(request, Request, {
moduleName: 'workbox-strategies',
className: 'CacheFirst',
funcName: 'makeRequest',
paramName: 'request'
});
}
let response = await cacheWrapper_js.cacheWrapper.match({
cacheName: this._cacheName,
request,
event,
matchOptions: this._matchOptions,
plugins: this._plugins
});
let error;
if (!response) {
{
logs.push(`No response found in the '${this._cacheName}' cache. ` + `Will respond with a network request.`);
}
try {
response = await this._getFromNetwork(request, event);
} catch (err) {
error = err;
}
{
if (response) {
logs.push(`Got response from network.`);
} else {
logs.push(`Unable to get a response from the network.`);
}
}
} else {
{
logs.push(`Found a cached response in the '${this._cacheName}' cache.`);
}
}
{
logger_js.logger.groupCollapsed(messages.strategyStart('CacheFirst', request));
for (const log of logs) {
logger_js.logger.log(log);
}
messages.printFinalResponse(response);
logger_js.logger.groupEnd();
}
if (!response) {
throw new WorkboxError_js.WorkboxError('no-response', {
url: request.url,
error
});
}
return response;
}
/**
* Handles the network and cache part of CacheFirst.
*
* @param {Request} request
* @param {Event} [event]
* @return {Promise<Response>}
*
* @private
*/
async _getFromNetwork(request, event) {
const response = await fetchWrapper_js.fetchWrapper.fetch({
request,
event,
fetchOptions: this._fetchOptions,
plugins: this._plugins
}); // Keep the service worker while we put the request to the cache
const responseClone = response.clone();
const cachePutPromise = cacheWrapper_js.cacheWrapper.put({
cacheName: this._cacheName,
request,
response: responseClone,
event,
plugins: this._plugins
});
if (event) {
try {
event.waitUntil(cachePutPromise);
} catch (error) {
{
logger_js.logger.warn(`Unable to ensure service worker stays alive when ` + `updating cache for '${getFriendlyURL_js.getFriendlyURL(request.url)}'.`);
}
}
}
return response;
}
}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* An implementation of a
* [cache-only]{@link https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#cache-only}
* request strategy.
*
* This class is useful if you want to take advantage of any
* [Workbox plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}.
*
* If there is no cache match, this will throw a `WorkboxError` exception.
*
* @memberof module:workbox-strategies
*/
class CacheOnly {
/**
* @param {Object} options
* @param {string} options.cacheName Cache name to store and retrieve
* requests. Defaults to cache names provided by
* [workbox-core]{@link module:workbox-core.cacheNames}.
* @param {Array<Object>} options.plugins [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}
* to use in conjunction with this caching strategy.
* @param {Object} options.matchOptions [`CacheQueryOptions`](https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions)
*/
constructor(options = {}) {
this._cacheName = cacheNames_js.cacheNames.getRuntimeName(options.cacheName);
this._plugins = options.plugins || [];
this._matchOptions = options.matchOptions;
}
/**
* This method will perform a request strategy and follows an API that
* will work with the
* [Workbox Router]{@link module:workbox-routing.Router}.
*
* @param {Object} options
* @param {Request|string} options.request A request to run this strategy for.
* @param {Event} [options.event] The event that triggered the request.
* @return {Promise<Response>}
*/
async handle({
event,
request
}) {
if (typeof request === 'string') {
request = new Request(request);
}
{
assert_js.assert.isInstance(request, Request, {
moduleName: 'workbox-strategies',
className: 'CacheOnly',
funcName: 'makeRequest',
paramName: 'request'
});
}
const response = await cacheWrapper_js.cacheWrapper.match({
cacheName: this._cacheName,
request,
event,
matchOptions: this._matchOptions,
plugins: this._plugins
});
{
logger_js.logger.groupCollapsed(messages.strategyStart('CacheOnly', request));
if (response) {
logger_js.logger.log(`Found a cached response in the '${this._cacheName}'` + ` cache.`);
messages.printFinalResponse(response);
} else {
logger_js.logger.log(`No response found in the '${this._cacheName}' cache.`);
}
logger_js.logger.groupEnd();
}
if (!response) {
throw new WorkboxError_js.WorkboxError('no-response', {
url: request.url
});
}
return response;
}
}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
const cacheOkAndOpaquePlugin = {
/**
* Returns a valid response (to allow caching) if the status is 200 (OK) or
* 0 (opaque).
*
* @param {Object} options
* @param {Response} options.response
* @return {Response|null}
*
* @private
*/
cacheWillUpdate: async ({
response
}) => {
if (response.status === 200 || response.status === 0) {
return response;
}
return null;
}
};
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* An implementation of a
* [network first]{@link https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#network-falling-back-to-cache}
* request strategy.
*
* By default, this strategy will cache responses with a 200 status code as
* well as [opaque responses]{@link https://developers.google.com/web/tools/workbox/guides/handle-third-party-requests}.
* Opaque responses are are cross-origin requests where the response doesn't
* support [CORS]{@link https://enable-cors.org/}.
*
* If the network request fails, and there is no cache match, this will throw
* a `WorkboxError` exception.
*
* @memberof module:workbox-strategies
*/
class NetworkFirst {
/**
* @param {Object} options
* @param {string} options.cacheName Cache name to store and retrieve
* requests. Defaults to cache names provided by
* [workbox-core]{@link module:workbox-core.cacheNames}.
* @param {Array<Object>} options.plugins [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}
* to use in conjunction with this caching strategy.
* @param {Object} options.fetchOptions Values passed along to the
* [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)
* of all fetch() requests made by this strategy.
* @param {Object} options.matchOptions [`CacheQueryOptions`](https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions)
* @param {number} options.networkTimeoutSeconds If set, any network requests
* that fail to respond within the timeout will fallback to the cache.
*
* This option can be used to combat
* "[lie-fi]{@link https://developers.google.com/web/fundamentals/performance/poor-connectivity/#lie-fi}"
* scenarios.
*/
constructor(options = {}) {
this._cacheName = cacheNames_js.cacheNames.getRuntimeName(options.cacheName);
if (options.plugins) {
const isUsingCacheWillUpdate = options.plugins.some(plugin => !!plugin.cacheWillUpdate);
this._plugins = isUsingCacheWillUpdate ? options.plugins : [cacheOkAndOpaquePlugin, ...options.plugins];
} else {
// No plugins passed in, use the default plugin.
this._plugins = [cacheOkAndOpaquePlugin];
}
this._networkTimeoutSeconds = options.networkTimeoutSeconds || 0;
{
if (this._networkTimeoutSeconds) {
assert_js.assert.isType(this._networkTimeoutSeconds, 'number', {
moduleName: 'workbox-strategies',
className: 'NetworkFirst',
funcName: 'constructor',
paramName: 'networkTimeoutSeconds'
});
}
}
this._fetchOptions = options.fetchOptions;
this._matchOptions = options.matchOptions;
}
/**
* This method will perform a request strategy and follows an API that
* will work with the
* [Workbox Router]{@link module:workbox-routing.Router}.
*
* @param {Object} options
* @param {Request|string} options.request A request to run this strategy for.
* @param {Event} [options.event] The event that triggered the request.
* @return {Promise<Response>}
*/
async handle({
event,
request
}) {
const logs = [];
if (typeof request === 'string') {
request = new Request(request);
}
{
assert_js.assert.isInstance(request, Request, {
moduleName: 'workbox-strategies',
className: 'NetworkFirst',
funcName: 'handle',
paramName: 'makeRequest'
});
}
const promises = [];
let timeoutId;
if (this._networkTimeoutSeconds) {
const {
id,
promise
} = this._getTimeoutPromise({
request,
event,
logs
});
timeoutId = id;
promises.push(promise);
}
const networkPromise = this._getNetworkPromise({
timeoutId,
request,
event,
logs
});
promises.push(networkPromise); // Promise.race() will resolve as soon as the first promise resolves.
let response = await Promise.race(promises); // If Promise.race() resolved with null, it might be due to a network
// timeout + a cache miss. If that were to happen, we'd rather wait until
// the networkPromise resolves instead of returning null.
// Note that it's fine to await an already-resolved promise, so we don't
// have to check to see if it's still "in flight".
if (!response) {
response = await networkPromise;
}
{
logger_js.logger.groupCollapsed(messages.strategyStart('NetworkFirst', request));
for (const log of logs) {
logger_js.logger.log(log);
}
messages.printFinalResponse(response);
logger_js.logger.groupEnd();
}
if (!response) {
throw new WorkboxError_js.WorkboxError('no-response', {
url: request.url
});
}
return response;
}
/**
* @param {Object} options
* @param {Request} options.request
* @param {Array} options.logs A reference to the logs array
* @param {Event} [options.event]
* @return {Promise<Response>}
*
* @private
*/
_getTimeoutPromise({
request,
logs,
event
}) {
let timeoutId;
const timeoutPromise = new Promise(resolve => {
const onNetworkTimeout = async () => {
{
logs.push(`Timing out the network response at ` + `${this._networkTimeoutSeconds} seconds.`);
}
resolve(await this._respondFromCache({
request,
event
}));
};
timeoutId = setTimeout(onNetworkTimeout, this._networkTimeoutSeconds * 1000);
});
return {
promise: timeoutPromise,
id: timeoutId
};
}
/**
* @param {Object} options
* @param {number|undefined} options.timeoutId
* @param {Request} options.request
* @param {Array} options.logs A reference to the logs Array.
* @param {Event} [options.event]
* @return {Promise<Response>}
*
* @private
*/
async _getNetworkPromise({
timeoutId,
request,
logs,
event
}) {
let error;
let response;
try {
response = await fetchWrapper_js.fetchWrapper.fetch({
request,
event,
fetchOptions: this._fetchOptions,
plugins: this._plugins
});
} catch (err) {
error = err;
}
if (timeoutId) {
clearTimeout(timeoutId);
}
{
if (response) {
logs.push(`Got response from network.`);
} else {
logs.push(`Unable to get a response from the network. Will respond ` + `with a cached response.`);
}
}
if (error || !response) {
response = await this._respondFromCache({
request,
event
});
{
if (response) {
logs.push(`Found a cached response in the '${this._cacheName}'` + ` cache.`);
} else {
logs.push(`No response found in the '${this._cacheName}' cache.`);
}
}
} else {
// Keep the service worker alive while we put the request in the cache
const responseClone = response.clone();
const cachePut = cacheWrapper_js.cacheWrapper.put({
cacheName: this._cacheName,
request,
response: responseClone,
event,
plugins: this._plugins
});
if (event) {
try {
// The event has been responded to so we can keep the SW alive to
// respond to the request
event.waitUntil(cachePut);
} catch (err) {
{
logger_js.logger.warn(`Unable to ensure service worker stays alive when ` + `updating cache for '${getFriendlyURL_js.getFriendlyURL(request.url)}'.`);
}
}
}
}
return response;
}
/**
* Used if the network timeouts or fails to make the request.
*
* @param {Object} options
* @param {Request} request The request to match in the cache
* @param {Event} [options.event]
* @return {Promise<Object>}
*
* @private
*/
_respondFromCache({
event,
request
}) {
return cacheWrapper_js.cacheWrapper.match({
cacheName: this._cacheName,
request,
event,
matchOptions: this._matchOptions,
plugins: this._plugins
});
}
}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* An implementation of a
* [network-only]{@link https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#network-only}
* request strategy.
*
* This class is useful if you want to take advantage of any
* [Workbox plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}.
*
* If the network request fails, this will throw a `WorkboxError` exception.
*
* @memberof module:workbox-strategies
*/
class NetworkOnly {
/**
* @param {Object} options
* @param {string} options.cacheName Cache name to store and retrieve
* requests. Defaults to cache names provided by
* [workbox-core]{@link module:workbox-core.cacheNames}.
* @param {Array<Object>} options.plugins [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}
* to use in conjunction with this caching strategy.
* @param {Object} options.fetchOptions Values passed along to the
* [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)
* of all fetch() requests made by this strategy.
*/
constructor(options = {}) {
this._plugins = options.plugins || [];
this._fetchOptions = options.fetchOptions;
}
/**
* This method will perform a request strategy and follows an API that
* will work with the
* [Workbox Router]{@link module:workbox-routing.Router}.
*
* @param {Object} options
* @param {Request|string} options.request The request to run this strategy for.
* @param {Event} [options.event] The event that triggered the request.
* @return {Promise<Response>}
*/
async handle({
event,
request
}) {
if (typeof request === 'string') {
request = new Request(request);
}
{
assert_js.assert.isInstance(request, Request, {
moduleName: 'workbox-strategies',
className: 'NetworkOnly',
funcName: 'handle',
paramName: 'request'
});
}
let error;
let response;
try {
response = await fetchWrapper_js.fetchWrapper.fetch({
request,
event,
fetchOptions: this._fetchOptions,
plugins: this._plugins
});
} catch (err) {
error = err;
}
{
logger_js.logger.groupCollapsed(messages.strategyStart('NetworkOnly', request));
if (response) {
logger_js.logger.log(`Got response from network.`);
} else {
logger_js.logger.log(`Unable to get a response from the network.`);
}
messages.printFinalResponse(response);
logger_js.logger.groupEnd();
}
if (!response) {
throw new WorkboxError_js.WorkboxError('no-response', {
url: request.url,
error
});
}
return response;
}
}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* An implementation of a
* [stale-while-revalidate]{@link https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#stale-while-revalidate}
* request strategy.
*
* Resources are requested from both the cache and the network in parallel.
* The strategy will respond with the cached version if available, otherwise
* wait for the network response. The cache is updated with the network response
* with each successful request.
*
* By default, this strategy will cache responses with a 200 status code as
* well as [opaque responses]{@link https://developers.google.com/web/tools/workbox/guides/handle-third-party-requests}.
* Opaque responses are cross-origin requests where the response doesn't
* support [CORS]{@link https://enable-cors.org/}.
*
* If the network request fails, and there is no cache match, this will throw
* a `WorkboxError` exception.
*
* @memberof module:workbox-strategies
*/
class StaleWhileRevalidate {
/**
* @param {Object} options
* @param {string} options.cacheName Cache name to store and retrieve
* requests. Defaults to cache names provided by
* [workbox-core]{@link module:workbox-core.cacheNames}.
* @param {Array<Object>} options.plugins [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}
* to use in conjunction with this caching strategy.
* @param {Object} options.fetchOptions Values passed along to the
* [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)
* of all fetch() requests made by this strategy.
* @param {Object} options.matchOptions [`CacheQueryOptions`](https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions)
*/
constructor(options = {}) {
this._cacheName = cacheNames_js.cacheNames.getRuntimeName(options.cacheName);
this._plugins = options.plugins || [];
if (options.plugins) {
const isUsingCacheWillUpdate = options.plugins.some(plugin => !!plugin.cacheWillUpdate);
this._plugins = isUsingCacheWillUpdate ? options.plugins : [cacheOkAndOpaquePlugin, ...options.plugins];
} else {
// No plugins passed in, use the default plugin.
this._plugins = [cacheOkAndOpaquePlugin];
}
this._fetchOptions = options.fetchOptions;
this._matchOptions = options.matchOptions;
}
/**
* This method will perform a request strategy and follows an API that
* will work with the
* [Workbox Router]{@link module:workbox-routing.Router}.
*
* @param {Object} options
* @param {Request|string} options.request A request to run this strategy for.
* @param {Event} [options.event] The event that triggered the request.
* @return {Promise<Response>}
*/
async handle({
event,
request
}) {
const logs = [];
if (typeof request === 'string') {
request = new Request(request);
}
{
assert_js.assert.isInstance(request, Request, {
moduleName: 'workbox-strategies',
className: 'StaleWhileRevalidate',
funcName: 'handle',
paramName: 'request'
});
}
const fetchAndCachePromise = this._getFromNetwork({
request,
event
});
let response = await cacheWrapper_js.cacheWrapper.match({
cacheName: this._cacheName,
request,
event,
matchOptions: this._matchOptions,
plugins: this._plugins
});
let error;
if (response) {
{
logs.push(`Found a cached response in the '${this._cacheName}'` + ` cache. Will update with the network response in the background.`);
}
if (event) {
try {
event.waitUntil(fetchAndCachePromise);
} catch (error) {
{
logger_js.logger.warn(`Unable to ensure service worker stays alive when ` + `updating cache for '${getFriendlyURL_js.getFriendlyURL(request.url)}'.`);
}
}
}
} else {
{
logs.push(`No response found in the '${this._cacheName}' cache. ` + `Will wait for the network response.`);
}
try {
response = await fetchAndCachePromise;
} catch (err) {
error = err;
}
}
{
logger_js.logger.groupCollapsed(messages.strategyStart('StaleWhileRevalidate', request));
for (const log of logs) {
logger_js.logger.log(log);
}
messages.printFinalResponse(response);
logger_js.logger.groupEnd();
}
if (!response) {
throw new WorkboxError_js.WorkboxError('no-response', {
url: request.url,
error
});
}
return response;
}
/**
* @param {Object} options
* @param {Request} options.request
* @param {Event} [options.event]
* @return {Promise<Response>}
*
* @private
*/
async _getFromNetwork({
request,
event
}) {
const response = await fetchWrapper_js.fetchWrapper.fetch({
request,
event,
fetchOptions: this._fetchOptions,
plugins: this._plugins
});
const cachePutPromise = cacheWrapper_js.cacheWrapper.put({
cacheName: this._cacheName,
request,
response: response.clone(),
event,
plugins: this._plugins
});
if (event) {
try {
event.waitUntil(cachePutPromise);
} catch (error) {
{
logger_js.logger.warn(`Unable to ensure service worker stays alive when ` + `updating cache for '${getFriendlyURL_js.getFriendlyURL(request.url)}'.`);
}
}
}
return response;
}
}
exports.CacheFirst = CacheFirst;
exports.CacheOnly = CacheOnly;
exports.NetworkFirst = NetworkFirst;
exports.NetworkOnly = NetworkOnly;
exports.StaleWhileRevalidate = StaleWhileRevalidate;
return exports;
}({}, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private));
//# sourceMappingURL=workbox-strategies.dev.js.map
@@ -0,0 +1 @@
{"version":3,"file":"workbox-strategies.dev.js","sources":["../_version.js","../utils/messages.js","../CacheFirst.js","../CacheOnly.js","../plugins/cacheOkAndOpaquePlugin.js","../NetworkFirst.js","../NetworkOnly.js","../StaleWhileRevalidate.js"],"sourcesContent":["\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:strategies:5.1.4'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { getFriendlyURL } from 'workbox-core/_private/getFriendlyURL.js';\nimport '../_version.js';\nexport const messages = {\n strategyStart: (strategyName, request) => `Using ${strategyName} to respond to '${getFriendlyURL(request.url)}'`,\n printFinalResponse: (response) => {\n if (response) {\n logger.groupCollapsed(`View the final response here.`);\n logger.log(response || '[No response returned]');\n logger.groupEnd();\n }\n },\n};\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { cacheNames } from 'workbox-core/_private/cacheNames.js';\nimport { cacheWrapper } from 'workbox-core/_private/cacheWrapper.js';\nimport { fetchWrapper } from 'workbox-core/_private/fetchWrapper.js';\nimport { getFriendlyURL } from 'workbox-core/_private/getFriendlyURL.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport { messages } from './utils/messages.js';\nimport './_version.js';\n/**\n * An implementation of a [cache-first]{@link https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#cache-falling-back-to-network}\n * request strategy.\n *\n * A cache first strategy is useful for assets that have been revisioned,\n * such as URLs like `/styles/example.a8f5f1.css`, since they\n * can be cached for long periods of time.\n *\n * If the network request fails, and there is no cache match, this will throw\n * a `WorkboxError` exception.\n *\n * @memberof module:workbox-strategies\n */\nclass CacheFirst {\n /**\n * @param {Object} options\n * @param {string} options.cacheName Cache name to store and retrieve\n * requests. Defaults to cache names provided by\n * [workbox-core]{@link module:workbox-core.cacheNames}.\n * @param {Array<Object>} options.plugins [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}\n * to use in conjunction with this caching strategy.\n * @param {Object} options.fetchOptions Values passed along to the\n * [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)\n * of all fetch() requests made by this strategy.\n * @param {Object} options.matchOptions [`CacheQueryOptions`](https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions)\n */\n constructor(options = {}) {\n this._cacheName = cacheNames.getRuntimeName(options.cacheName);\n this._plugins = options.plugins || [];\n this._fetchOptions = options.fetchOptions;\n this._matchOptions = options.matchOptions;\n }\n /**\n * This method will perform a request strategy and follows an API that\n * will work with the\n * [Workbox Router]{@link module:workbox-routing.Router}.\n *\n * @param {Object} options\n * @param {Request|string} options.request A request to run this strategy for.\n * @param {Event} [options.event] The event that triggered the request.\n * @return {Promise<Response>}\n */\n async handle({ event, request }) {\n const logs = [];\n if (typeof request === 'string') {\n request = new Request(request);\n }\n if (process.env.NODE_ENV !== 'production') {\n assert.isInstance(request, Request, {\n moduleName: 'workbox-strategies',\n className: 'CacheFirst',\n funcName: 'makeRequest',\n paramName: 'request',\n });\n }\n let response = await cacheWrapper.match({\n cacheName: this._cacheName,\n request,\n event,\n matchOptions: this._matchOptions,\n plugins: this._plugins,\n });\n let error;\n if (!response) {\n if (process.env.NODE_ENV !== 'production') {\n logs.push(`No response found in the '${this._cacheName}' cache. ` +\n `Will respond with a network request.`);\n }\n try {\n response = await this._getFromNetwork(request, event);\n }\n catch (Line truncated
@@ -0,0 +1,2 @@
this.workbox=this.workbox||{},this.workbox.strategies=function(t,e,s,i,n){"use strict";try{self["workbox:strategies:5.1.4"]&&_()}catch(t){}const r={cacheWillUpdate:async({response:t})=>200===t.status||0===t.status?t:null};return t.CacheFirst=class{constructor(t={}){this.t=e.cacheNames.getRuntimeName(t.cacheName),this.s=t.plugins||[],this.i=t.fetchOptions,this.h=t.matchOptions}async handle({event:t,request:e}){"string"==typeof e&&(e=new Request(e));let i,r=await s.cacheWrapper.match({cacheName:this.t,request:e,event:t,matchOptions:this.h,plugins:this.s});if(!r)try{r=await this.o(e,t)}catch(t){i=t}if(!r)throw new n.WorkboxError("no-response",{url:e.url,error:i});return r}async o(t,e){const n=await i.fetchWrapper.fetch({request:t,event:e,fetchOptions:this.i,plugins:this.s}),r=n.clone(),h=s.cacheWrapper.put({cacheName:this.t,request:t,response:r,event:e,plugins:this.s});if(e)try{e.waitUntil(h)}catch(t){}return n}},t.CacheOnly=class{constructor(t={}){this.t=e.cacheNames.getRuntimeName(t.cacheName),this.s=t.plugins||[],this.h=t.matchOptions}async handle({event:t,request:e}){"string"==typeof e&&(e=new Request(e));const i=await s.cacheWrapper.match({cacheName:this.t,request:e,event:t,matchOptions:this.h,plugins:this.s});if(!i)throw new n.WorkboxError("no-response",{url:e.url});return i}},t.NetworkFirst=class{constructor(t={}){if(this.t=e.cacheNames.getRuntimeName(t.cacheName),t.plugins){const e=t.plugins.some(t=>!!t.cacheWillUpdate);this.s=e?t.plugins:[r,...t.plugins]}else this.s=[r];this.u=t.networkTimeoutSeconds||0,this.i=t.fetchOptions,this.h=t.matchOptions}async handle({event:t,request:e}){const s=[];"string"==typeof e&&(e=new Request(e));const i=[];let r;if(this.u){const{id:n,promise:h}=this.l({request:e,event:t,logs:s});r=n,i.push(h)}const h=this.p({timeoutId:r,request:e,event:t,logs:s});i.push(h);let o=await Promise.race(i);if(o||(o=await h),!o)throw new n.WorkboxError("no-response",{url:e.url});return o}l({request:t,logs:e,event:s}){let i;return{promise:new Promise(e=>{i=setTimeout(async()=>{e(await this.q({request:t,event:s}))},1e3*this.u)}),id:i}}async p({timeoutId:t,request:e,logs:n,event:r}){let h,o;try{o=await i.fetchWrapper.fetch({request:e,event:r,fetchOptions:this.i,plugins:this.s})}catch(t){h=t}if(t&&clearTimeout(t),h||!o)o=await this.q({request:e,event:r});else{const t=o.clone(),i=s.cacheWrapper.put({cacheName:this.t,request:e,response:t,event:r,plugins:this.s});if(r)try{r.waitUntil(i)}catch(t){}}return o}q({event:t,request:e}){return s.cacheWrapper.match({cacheName:this.t,request:e,event:t,matchOptions:this.h,plugins:this.s})}},t.NetworkOnly=class{constructor(t={}){this.s=t.plugins||[],this.i=t.fetchOptions}async handle({event:t,request:e}){let s,r;"string"==typeof e&&(e=new Request(e));try{r=await i.fetchWrapper.fetch({request:e,event:t,fetchOptions:this.i,plugins:this.s})}catch(t){s=t}if(!r)throw new n.WorkboxError("no-response",{url:e.url,error:s});return r}},t.StaleWhileRevalidate=class{constructor(t={}){if(this.t=e.cacheNames.getRuntimeName(t.cacheName),this.s=t.plugins||[],t.plugins){const e=t.plugins.some(t=>!!t.cacheWillUpdate);this.s=e?t.plugins:[r,...t.plugins]}else this.s=[r];this.i=t.fetchOptions,this.h=t.matchOptions}async handle({event:t,request:e}){"string"==typeof e&&(e=new Request(e));const i=this.o({request:e,event:t});let r,h=await s.cacheWrapper.match({cacheName:this.t,request:e,event:t,matchOptions:this.h,plugins:this.s});if(h){if(t)try{t.waitUntil(i)}catch(r){}}else try{h=await i}catch(t){r=t}if(!h)throw new n.WorkboxError("no-response",{url:e.url,error:r});return h}async o({request:t,event:e}){const n=await i.fetchWrapper.fetch({request:t,event:e,fetchOptions:this.i,plugins:this.s}),r=s.cacheWrapper.put({cacheName:this.t,request:t,response:n.clone(),event:e,plugins:this.s});if(e)try{e.waitUntil(r)}catch(t){}return n}},t}({},workbox.core._private,workbox.core._private,workbox.core._private,workbox.core._private);
//# sourceMappingURL=workbox-strategies.prod.js.map
@@ -0,0 +1 @@
{"version":3,"file":"workbox-strategies.prod.js","sources":["../_version.js","../plugins/cacheOkAndOpaquePlugin.js","../CacheFirst.js","../CacheOnly.js","../NetworkFirst.js","../NetworkOnly.js","../StaleWhileRevalidate.js"],"sourcesContent":["\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:strategies:5.1.4'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\nexport const cacheOkAndOpaquePlugin = {\n /**\n * Returns a valid response (to allow caching) if the status is 200 (OK) or\n * 0 (opaque).\n *\n * @param {Object} options\n * @param {Response} options.response\n * @return {Response|null}\n *\n * @private\n */\n cacheWillUpdate: async ({ response }) => {\n if (response.status === 200 || response.status === 0) {\n return response;\n }\n return null;\n },\n};\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { cacheNames } from 'workbox-core/_private/cacheNames.js';\nimport { cacheWrapper } from 'workbox-core/_private/cacheWrapper.js';\nimport { fetchWrapper } from 'workbox-core/_private/fetchWrapper.js';\nimport { getFriendlyURL } from 'workbox-core/_private/getFriendlyURL.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport { messages } from './utils/messages.js';\nimport './_version.js';\n/**\n * An implementation of a [cache-first]{@link https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#cache-falling-back-to-network}\n * request strategy.\n *\n * A cache first strategy is useful for assets that have been revisioned,\n * such as URLs like `/styles/example.a8f5f1.css`, since they\n * can be cached for long periods of time.\n *\n * If the network request fails, and there is no cache match, this will throw\n * a `WorkboxError` exception.\n *\n * @memberof module:workbox-strategies\n */\nclass CacheFirst {\n /**\n * @param {Object} options\n * @param {string} options.cacheName Cache name to store and retrieve\n * requests. Defaults to cache names provided by\n * [workbox-core]{@link module:workbox-core.cacheNames}.\n * @param {Array<Object>} options.plugins [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}\n * to use in conjunction with this caching strategy.\n * @param {Object} options.fetchOptions Values passed along to the\n * [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)\n * of all fetch() requests made by this strategy.\n * @param {Object} options.matchOptions [`CacheQueryOptions`](https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions)\n */\n constructor(options = {}) {\n this._cacheName = cacheNames.getRuntimeName(options.cacheName);\n this._plugins = options.plugins || [];\n this._fetchOptions = options.fetchOptions;\n this._matchOptions = options.matchOptions;\n }\n /**\n * This method will perform a request strategy and follows an API that\n * will work with the\n * [Workbox Router]{@link module:workbox-routing.Router}.\n *\n * @param {Object} options\n * @param {Request|string} options.request A request to run this strategy for.\n * @param {Event} [options.event] The event that triggered the request.\n * @return {Promise<Response>}\n */\n async handle({ event, request }) {\n const logs = [];\n if (typeof request === 'string') {\n request = new Request(request);\n }\n if (process.env.NODE_ENV !== 'production') {\n assert.isInstance(request, Request, {\n moduleName: 'workbox-strategies',\n className: 'CacheFirst',\n funcName: 'makeRequest',\n paramName: 'request',\n });\n }\n let response = await cacheWrapper.match({\n cacheName: this._cacheName,\n request,\n event,\n matchOptions: this._matchOptions,\n plugins: this._plugins,\n });\n let error;\n if (!response) {\n if (process.env.NODE_ENV !== 'production') {\n logs.push(`No response found in the '${this._cacheName}' cache. ` +\n `Will respond with a network request.`);\n }\n try {\n response = await this._getFromNetwork(request, event);\n }\n catch (err) {\n error = err;\n }\n ifLine truncated
@@ -0,0 +1,318 @@
this.workbox = this.workbox || {};
this.workbox.streams = (function (exports, logger_js, assert_js, Deferred_js, canConstructReadableStream_js) {
'use strict';
try {
self['workbox:streams:5.1.4'] && _();
} catch (e) {}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* Takes either a Response, a ReadableStream, or a
* [BodyInit](https://fetch.spec.whatwg.org/#bodyinit) and returns the
* ReadableStreamReader object associated with it.
*
* @param {module:workbox-streams.StreamSource} source
* @return {ReadableStreamReader}
* @private
*/
function _getReaderFromSource(source) {
if (source instanceof Response) {
return source.body.getReader();
}
if (source instanceof ReadableStream) {
return source.getReader();
}
return new Response(source).body.getReader();
}
/**
* Takes multiple source Promises, each of which could resolve to a Response, a
* ReadableStream, or a [BodyInit](https://fetch.spec.whatwg.org/#bodyinit).
*
* Returns an object exposing a ReadableStream with each individual stream's
* data returned in sequence, along with a Promise which signals when the
* stream is finished (useful for passing to a FetchEvent's waitUntil()).
*
* @param {Array<Promise<module:workbox-streams.StreamSource>>} sourcePromises
* @return {Object<{done: Promise, stream: ReadableStream}>}
*
* @memberof module:workbox-streams
*/
function concatenate(sourcePromises) {
{
assert_js.assert.isArray(sourcePromises, {
moduleName: 'workbox-streams',
funcName: 'concatenate',
paramName: 'sourcePromises'
});
}
const readerPromises = sourcePromises.map(sourcePromise => {
return Promise.resolve(sourcePromise).then(source => {
return _getReaderFromSource(source);
});
});
const streamDeferred = new Deferred_js.Deferred();
let i = 0;
const logMessages = [];
const stream = new ReadableStream({
pull(controller) {
return readerPromises[i].then(reader => reader.read()).then(result => {
if (result.done) {
{
logMessages.push(['Reached the end of source:', sourcePromises[i]]);
}
i++;
if (i >= readerPromises.length) {
// Log all the messages in the group at once in a single group.
{
logger_js.logger.groupCollapsed(`Concatenating ${readerPromises.length} sources.`);
for (const message of logMessages) {
if (Array.isArray(message)) {
logger_js.logger.log(...message);
} else {
logger_js.logger.log(message);
}
}
logger_js.logger.log('Finished reading all sources.');
logger_js.logger.groupEnd();
}
controller.close();
streamDeferred.resolve();
return;
} // The `pull` method is defined because we're inside it.
return this.pull(controller);
} else {
controller.enqueue(result.value);
}
}).catch(error => {
{
logger_js.logger.error('An error occurred:', error);
}
streamDeferred.reject(error);
throw error;
});
},
cancel() {
{
logger_js.logger.warn('The ReadableStream was cancelled.');
}
streamDeferred.resolve();
}
});
return {
done: streamDeferred.promise,
stream
};
}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* This is a utility method that determines whether the current browser supports
* the features required to create streamed responses. Currently, it checks if
* [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/ReadableStream)
* is available.
*
* @private
* @param {HeadersInit} [headersInit] If there's no `Content-Type` specified,
* `'text/html'` will be used by default.
* @return {boolean} `true`, if the current browser meets the requirements for
* streaming responses, and `false` otherwise.
*
* @memberof module:workbox-streams
*/
function createHeaders(headersInit = {}) {
// See https://github.com/GoogleChrome/workbox/issues/1461
const headers = new Headers(headersInit);
if (!headers.has('content-type')) {
headers.set('content-type', 'text/html');
}
return headers;
}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* Takes multiple source Promises, each of which could resolve to a Response, a
* ReadableStream, or a [BodyInit](https://fetch.spec.whatwg.org/#bodyinit),
* along with a
* [HeadersInit](https://fetch.spec.whatwg.org/#typedefdef-headersinit).
*
* Returns an object exposing a Response whose body consists of each individual
* stream's data returned in sequence, along with a Promise which signals when
* the stream is finished (useful for passing to a FetchEvent's waitUntil()).
*
* @param {Array<Promise<module:workbox-streams.StreamSource>>} sourcePromises
* @param {HeadersInit} [headersInit] If there's no `Content-Type` specified,
* `'text/html'` will be used by default.
* @return {Object<{done: Promise, response: Response}>}
*
* @memberof module:workbox-streams
*/
function concatenateToResponse(sourcePromises, headersInit) {
const {
done,
stream
} = concatenate(sourcePromises);
const headers = createHeaders(headersInit);
const response = new Response(stream, {
headers
});
return {
done,
response
};
}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* This is a utility method that determines whether the current browser supports
* the features required to create streamed responses. Currently, it checks if
* [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/ReadableStream)
* can be created.
*
* @return {boolean} `true`, if the current browser meets the requirements for
* streaming responses, and `false` otherwise.
*
* @memberof module:workbox-streams
*/
function isSupported() {
return canConstructReadableStream_js.canConstructReadableStream();
}
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/**
* A shortcut to create a strategy that could be dropped-in to Workbox's router.
*
* On browsers that do not support constructing new `ReadableStream`s, this
* strategy will automatically wait for all the `sourceFunctions` to complete,
* and create a final response that concatenates their values together.
*
* @param {Array<function({event, request, url, params})>} sourceFunctions
* An array of functions similar to {@link module:workbox-routing~handlerCallback}
* but that instead return a {@link module:workbox-streams.StreamSource} (or a
* Promise which resolves to one).
* @param {HeadersInit} [headersInit] If there's no `Content-Type` specified,
* `'text/html'` will be used by default.
* @return {module:workbox-routing~handlerCallback}
* @memberof module:workbox-streams
*/
function strategy(sourceFunctions, headersInit) {
return async ({
event,
request,
url,
params
}) => {
const sourcePromises = sourceFunctions.map(fn => {
// Ensure the return value of the function is always a promise.
return Promise.resolve(fn({
event,
request,
url,
params
}));
});
if (isSupported()) {
const {
done,
response
} = concatenateToResponse(sourcePromises, headersInit);
if (event) {
event.waitUntil(done);
}
return response;
}
{
logger_js.logger.log(`The current browser doesn't support creating response ` + `streams. Falling back to non-streaming response instead.`);
} // Fallback to waiting for everything to finish, and concatenating the
// responses.
const blobPartsPromises = sourcePromises.map(async sourcePromise => {
const source = await sourcePromise;
if (source instanceof Response) {
return source.blob();
} else {
// Technically, a `StreamSource` object can include any valid
// `BodyInit` type, including `FormData` and `URLSearchParams`, which
// cannot be passed to the Blob constructor directly, so we have to
// convert them to actual Blobs first.
return new Response(source).blob();
}
});
const blobParts = await Promise.all(blobPartsPromises);
const headers = createHeaders(headersInit); // Constructing a new Response from a Blob source is well-supported.
// So is constructing a new Blob from multiple source Blobs or strings.
return new Response(new Blob(blobParts), {
headers
});
};
}
exports.concatenate = concatenate;
exports.concatenateToResponse = concatenateToResponse;
exports.isSupported = isSupported;
exports.strategy = strategy;
return exports;
}({}, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private));
//# sourceMappingURL=workbox-streams.dev.js.map
@@ -0,0 +1 @@
{"version":3,"file":"workbox-streams.dev.js","sources":["../_version.js","../concatenate.js","../utils/createHeaders.js","../concatenateToResponse.js","../isSupported.js","../strategy.js"],"sourcesContent":["\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:streams:5.1.4'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { Deferred } from 'workbox-core/_private/Deferred.js';\nimport './_version.js';\n/**\n * Takes either a Response, a ReadableStream, or a\n * [BodyInit](https://fetch.spec.whatwg.org/#bodyinit) and returns the\n * ReadableStreamReader object associated with it.\n *\n * @param {module:workbox-streams.StreamSource} source\n * @return {ReadableStreamReader}\n * @private\n */\nfunction _getReaderFromSource(source) {\n if (source instanceof Response) {\n return source.body.getReader();\n }\n if (source instanceof ReadableStream) {\n return source.getReader();\n }\n return new Response(source).body.getReader();\n}\n/**\n * Takes multiple source Promises, each of which could resolve to a Response, a\n * ReadableStream, or a [BodyInit](https://fetch.spec.whatwg.org/#bodyinit).\n *\n * Returns an object exposing a ReadableStream with each individual stream's\n * data returned in sequence, along with a Promise which signals when the\n * stream is finished (useful for passing to a FetchEvent's waitUntil()).\n *\n * @param {Array<Promise<module:workbox-streams.StreamSource>>} sourcePromises\n * @return {Object<{done: Promise, stream: ReadableStream}>}\n *\n * @memberof module:workbox-streams\n */\nfunction concatenate(sourcePromises) {\n if (process.env.NODE_ENV !== 'production') {\n assert.isArray(sourcePromises, {\n moduleName: 'workbox-streams',\n funcName: 'concatenate',\n paramName: 'sourcePromises',\n });\n }\n const readerPromises = sourcePromises.map((sourcePromise) => {\n return Promise.resolve(sourcePromise).then((source) => {\n return _getReaderFromSource(source);\n });\n });\n const streamDeferred = new Deferred();\n let i = 0;\n const logMessages = [];\n const stream = new ReadableStream({\n pull(controller) {\n return readerPromises[i]\n .then((reader) => reader.read())\n .then((result) => {\n if (result.done) {\n if (process.env.NODE_ENV !== 'production') {\n logMessages.push(['Reached the end of source:',\n sourcePromises[i]]);\n }\n i++;\n if (i >= readerPromises.length) {\n // Log all the messages in the group at once in a single group.\n if (process.env.NODE_ENV !== 'production') {\n logger.groupCollapsed(`Concatenating ${readerPromises.length} sources.`);\n for (const message of logMessages) {\n if (Array.isArray(message)) {\n logger.log(...message);\n }\n else {\n logger.log(message);\n }\n }\n logger.log('Finished reading all sources.');\n logger.groupEnd();\n }\n controller.close();\n streamDeferred.resolve();\n return;\n }\n // The `pull` method is defined because we're inside it.\n return this.pull(controller);\n }\n else {\n controller.enqueue(result.value);\n }\n }).catch((error) => {\n if (process.env.NODE_ENV !== 'production') {\n logger.error('An error occurred:', error);\n }\n streamDeferred.reject(error);\n throw error;\n });\n },\n cancel() {\n if (process.env.NODE_ENV !== 'production') {\n logger.warn('The ReadableStream was cancelled.');\n }\n streamDeferred.resolve();\n },\n });\n return { done: streamDeferred.promise, stream };\n}\nexport { concatenate };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\n/**\n * This is a utility method that deterLine truncated
@@ -0,0 +1,2 @@
this.workbox=this.workbox||{},this.workbox.streams=function(e,n,t){"use strict";try{self["workbox:streams:5.1.4"]&&_()}catch(e){}function s(e){const t=e.map(e=>Promise.resolve(e).then(e=>function(e){return e instanceof Response?e.body.getReader():e instanceof ReadableStream?e.getReader():new Response(e).body.getReader()}(e))),s=new n.Deferred;let r=0;const o=new ReadableStream({pull(e){return t[r].then(e=>e.read()).then(n=>{if(n.done)return r++,r>=t.length?(e.close(),void s.resolve()):this.pull(e);e.enqueue(n.value)}).catch(e=>{throw s.reject(e),e})},cancel(){s.resolve()}});return{done:s.promise,stream:o}}function r(e={}){const n=new Headers(e);return n.has("content-type")||n.set("content-type","text/html"),n}function o(e,n){const{done:t,stream:o}=s(e),c=r(n);return{done:t,response:new Response(o,{headers:c})}}function c(){return t.canConstructReadableStream()}return e.concatenate=s,e.concatenateToResponse=o,e.isSupported=c,e.strategy=function(e,n){return async({event:t,request:s,url:a,params:u})=>{const i=e.map(e=>Promise.resolve(e({event:t,request:s,url:a,params:u})));if(c()){const{done:e,response:s}=o(i,n);return t&&t.waitUntil(e),s}const f=i.map(async e=>{const n=await e;return n instanceof Response?n.blob():new Response(n).blob()}),p=await Promise.all(f),w=r(n);return new Response(new Blob(p),{headers:w})}},e}({},workbox.core._private,workbox.core._private);
//# sourceMappingURL=workbox-streams.prod.js.map
@@ -0,0 +1 @@
{"version":3,"file":"workbox-streams.prod.js","sources":["../_version.js","../concatenate.js","../utils/createHeaders.js","../concatenateToResponse.js","../isSupported.js","../strategy.js"],"sourcesContent":["\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:streams:5.1.4'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { Deferred } from 'workbox-core/_private/Deferred.js';\nimport './_version.js';\n/**\n * Takes either a Response, a ReadableStream, or a\n * [BodyInit](https://fetch.spec.whatwg.org/#bodyinit) and returns the\n * ReadableStreamReader object associated with it.\n *\n * @param {module:workbox-streams.StreamSource} source\n * @return {ReadableStreamReader}\n * @private\n */\nfunction _getReaderFromSource(source) {\n if (source instanceof Response) {\n return source.body.getReader();\n }\n if (source instanceof ReadableStream) {\n return source.getReader();\n }\n return new Response(source).body.getReader();\n}\n/**\n * Takes multiple source Promises, each of which could resolve to a Response, a\n * ReadableStream, or a [BodyInit](https://fetch.spec.whatwg.org/#bodyinit).\n *\n * Returns an object exposing a ReadableStream with each individual stream's\n * data returned in sequence, along with a Promise which signals when the\n * stream is finished (useful for passing to a FetchEvent's waitUntil()).\n *\n * @param {Array<Promise<module:workbox-streams.StreamSource>>} sourcePromises\n * @return {Object<{done: Promise, stream: ReadableStream}>}\n *\n * @memberof module:workbox-streams\n */\nfunction concatenate(sourcePromises) {\n if (process.env.NODE_ENV !== 'production') {\n assert.isArray(sourcePromises, {\n moduleName: 'workbox-streams',\n funcName: 'concatenate',\n paramName: 'sourcePromises',\n });\n }\n const readerPromises = sourcePromises.map((sourcePromise) => {\n return Promise.resolve(sourcePromise).then((source) => {\n return _getReaderFromSource(source);\n });\n });\n const streamDeferred = new Deferred();\n let i = 0;\n const logMessages = [];\n const stream = new ReadableStream({\n pull(controller) {\n return readerPromises[i]\n .then((reader) => reader.read())\n .then((result) => {\n if (result.done) {\n if (process.env.NODE_ENV !== 'production') {\n logMessages.push(['Reached the end of source:',\n sourcePromises[i]]);\n }\n i++;\n if (i >= readerPromises.length) {\n // Log all the messages in the group at once in a single group.\n if (process.env.NODE_ENV !== 'production') {\n logger.groupCollapsed(`Concatenating ${readerPromises.length} sources.`);\n for (const message of logMessages) {\n if (Array.isArray(message)) {\n logger.log(...message);\n }\n else {\n logger.log(message);\n }\n }\n logger.log('Finished reading all sources.');\n logger.groupEnd();\n }\n controller.close();\n streamDeferred.resolve();\n return;\n }\n // The `pull` method is defined because we're inside it.\n return this.pull(controller);\n }\n else {\n controller.enqueue(result.value);\n }\n }).catch((error) => {\n if (process.env.NODE_ENV !== 'production') {\n logger.error('An error occurred:', error);\n }\n streamDeferred.reject(error);\n throw error;\n });\n },\n cancel() {\n if (process.env.NODE_ENV !== 'production') {\n logger.warn('The ReadableStream was cancelled.');\n }\n streamDeferred.resolve();\n },\n });\n return { done: streamDeferred.promise, stream };\n}\nexport { concatenate };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\n/**\n * This is a utility method that deteLine truncated
@@ -0,0 +1,2 @@
!function(){"use strict";try{self["workbox:sw:5.1.4"]&&_()}catch(t){}const t={backgroundSync:"background-sync",broadcastUpdate:"broadcast-update",cacheableResponse:"cacheable-response",core:"core",expiration:"expiration",googleAnalytics:"offline-ga",navigationPreload:"navigation-preload",precaching:"precaching",rangeRequests:"range-requests",routing:"routing",strategies:"strategies",streams:"streams"};self.workbox=new class{constructor(){return this.v={},this.t={debug:"localhost"===self.location.hostname,modulePathPrefix:null,modulePathCb:null},this.s=this.t.debug?"dev":"prod",this.o=!1,new Proxy(this,{get(e,s){if(e[s])return e[s];const o=t[s];return o&&e.loadModule("workbox-"+o),e[s]}})}setConfig(t={}){if(this.o)throw new Error("Config must be set before accessing workbox.* modules");Object.assign(this.t,t),this.s=this.t.debug?"dev":"prod"}loadModule(t){const e=this.i(t);try{importScripts(e),this.o=!0}catch(s){throw console.error(`Unable to import module '${t}' from '${e}'.`),s}}i(t){if(this.t.modulePathCb)return this.t.modulePathCb(t,this.t.debug);let e=["https://storage.googleapis.com/workbox-cdn/releases/5.1.4"];const s=`${t}.${this.s}.js`,o=this.t.modulePathPrefix;return o&&(e=o.split("/"),""===e[e.length-1]&&e.splice(e.length-1,1)),e.push(s),e.join("/")}}}();
//# sourceMappingURL=workbox-sw.js.map
@@ -0,0 +1 @@
{"version":3,"file":"workbox-sw.js","sources":["../_version.mjs","../controllers/WorkboxSW.mjs","../index.mjs"],"sourcesContent":["try{self['workbox:sw:5.1.4']&&_()}catch(e){}// eslint-disable-line","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\n\nimport '../_version.mjs';\n\nconst CDN_PATH = `WORKBOX_CDN_ROOT_URL`;\n\nconst MODULE_KEY_TO_NAME_MAPPING = {\n /**\n * @name backgroundSync\n * @memberof workbox\n * @see module:workbox-background-sync\n */\n backgroundSync: 'background-sync',\n /**\n * @name broadcastUpdate\n * @memberof workbox\n * @see module:workbox-broadcast-update\n */\n broadcastUpdate: 'broadcast-update',\n /**\n * @name cacheableResponse\n * @memberof workbox\n * @see module:workbox-cacheable-response\n */\n cacheableResponse: 'cacheable-response',\n /**\n * @name core\n * @memberof workbox\n * @see module:workbox-core\n */\n core: 'core',\n /**\n * @name expiration\n * @memberof workbox\n * @see module:workbox-expiration\n */\n expiration: 'expiration',\n /**\n * @name googleAnalytics\n * @memberof workbox\n * @see module:workbox-google-analytics\n */\n googleAnalytics: 'offline-ga',\n /**\n * @name navigationPreload\n * @memberof workbox\n * @see module:workbox-navigation-preload\n */\n navigationPreload: 'navigation-preload',\n /**\n * @name precaching\n * @memberof workbox\n * @see module:workbox-precaching\n */\n precaching: 'precaching',\n /**\n * @name rangeRequests\n * @memberof workbox\n * @see module:workbox-range-requests\n */\n rangeRequests: 'range-requests',\n /**\n * @name routing\n * @memberof workbox\n * @see module:workbox-routing\n */\n routing: 'routing',\n /**\n * @name strategies\n * @memberof workbox\n * @see module:workbox-strategies\n */\n strategies: 'strategies',\n /**\n * @name streams\n * @memberof workbox\n * @see module:workbox-streams\n */\n streams: 'streams',\n};\n\n/**\n * This class can be used to make it easy to use the various parts of\n * Workbox.\n *\n * @private\n */\nexport class WorkboxSW {\n /**\n * Creates a proxy that automatically loads workbox namespaces on demand.\n *\n * @private\n */\n constructor() {\n this.v = {};\n this._options = {\n debug: self.location.hostname === 'localhost',\n modulePathPrefix: null,\n modulePathCb: null,\n };\n\n this._env = this._options.debug ? 'dev' : 'prod';\n this._modulesLoaded = false;\n\n return new Proxy(this, {\n get(target, key) {\n if (target[key]) {\n return target[key];\n }\n\n const moduleName = MODULE_KEY_TO_NAME_MAPPING[key];\n if (moduleName) {\n target.loadModule(`workbox-${moduleName}`);\n }\n\n return target[key];\n },\n });\n }\n\n /**\n * Updates the configuration options. You can specify whether to treat as a\n * debug build and whether to use a CDN or a specific path when importing\n * other workbox-modules\n *\n * @param {Object} [options]\n * @param {boolean} [options.debug] If true, `dev` builds are using, otherwise\n * `prod` builds are used. By default, `prod` is used unless on localhost.\n * @param {Function} [options.modulePathPrefix] To avoid using the CDN with\n * `workbox-sw` set the path prefix of where modules should be loaded from.\n * For example `modulePathPrefix: '/third_party/workbox/v3.0.0/'`.\n * @param {workbox~ModulePathCallback} [options.modulePathCb] If defined,\n * this callback will be responsible for determining the path of each\n * workbox module.\n *\n * @alias workbox.setConfig\n */\n setConfig(options = {}) {\n if (!this._modulesLoaded) {\n Object.assign(this._options, options);\n this._env = this._options.debug ? 'dev' : 'prod';\n } else {\n throw new Error('Config must be set before accessing workbox.* modules');\n }\n }\n\n /**\n * Load a Workbox module by passing in the appropriate module name.\n *\n * This is not generally needed unless you know there are modules that are\n * dynamically used and you want to safe guard use of the module while the\n * user may be offline.\n *\n * @param {string} moduleName\n *\n * @alias workbox.loadModule\n */\n loadModule(moduleName) {\n const modulePath = this._getImportPath(moduleName);\n try {\n importScripts(modulePath);\n this._modulesLoaded = true;\n } catch (err) {\n // TODO Add context of this error if using the CDN vs the local file.\n\n // We can't rely on workbox-core being loaded so using console\n // eslint-disable-next-line\n console.error(\n `Unable to import module '${moduleName}' from '${modulePath}'.`);\n throw err;\n }\n }\n\n /**\n * This method will get the path / CDN URL to be used foLine truncated
Loaded 100 of 120 files, more files were not shown because too many files have changed in this diff. Show more