Workbox.mjs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. /*
  2. Copyright 2019 Google LLC
  3. Use of this source code is governed by an MIT-style
  4. license that can be found in the LICENSE file or at
  5. https://opensource.org/licenses/MIT.
  6. */
  7. import {Deferred} from 'workbox-core/_private/Deferred.mjs';
  8. import {logger} from 'workbox-core/_private/logger.mjs';
  9. import {messageSW} from './messageSW.mjs';
  10. import {EventTargetShim} from './utils/EventTargetShim.mjs';
  11. import {urlsMatch} from './utils/urlsMatch.mjs';
  12. import {WorkboxEvent} from './utils/WorkboxEvent.mjs';
  13. import './_version.mjs';
  14. // The time a SW must be in the waiting phase before we can conclude
  15. // `skipWaiting()` wasn't called. This 200 amount wasn't scientifically
  16. // chosen, but it seems to avoid false positives in my testing.
  17. const WAITING_TIMEOUT_DURATION = 200;
  18. // The amount of time after a registration that we can reasonably conclude
  19. // that the registration didn't trigger an update.
  20. const REGISTRATION_TIMEOUT_DURATION = 60000;
  21. /**
  22. * A class to aid in handling service worker registration, updates, and
  23. * reacting to service worker lifecycle events.
  24. *
  25. * @fires [message]{@link module:workbox-window.Workbox#message}
  26. * @fires [installed]{@link module:workbox-window.Workbox#installed}
  27. * @fires [waiting]{@link module:workbox-window.Workbox#waiting}
  28. * @fires [controlling]{@link module:workbox-window.Workbox#controlling}
  29. * @fires [activated]{@link module:workbox-window.Workbox#activated}
  30. * @fires [redundant]{@link module:workbox-window.Workbox#redundant}
  31. * @fires [externalinstalled]{@link module:workbox-window.Workbox#externalinstalled}
  32. * @fires [externalwaiting]{@link module:workbox-window.Workbox#externalwaiting}
  33. * @fires [externalactivated]{@link module:workbox-window.Workbox#externalactivated}
  34. *
  35. * @memberof module:workbox-window
  36. */
  37. class Workbox extends EventTargetShim {
  38. /**
  39. * Creates a new Workbox instance with a script URL and service worker
  40. * options. The script URL and options are the same as those used when
  41. * calling `navigator.serviceWorker.register(scriptURL, options)`. See:
  42. * https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register
  43. *
  44. * @param {string} scriptURL The service worker script associated with this
  45. * instance.
  46. * @param {Object} [registerOptions] The service worker options associated
  47. * with this instance.
  48. */
  49. constructor(scriptURL, registerOptions = {}) {
  50. super();
  51. this._scriptURL = scriptURL;
  52. this._registerOptions = registerOptions;
  53. this._updateFoundCount = 0;
  54. // Deferreds we can resolve later.
  55. this._swDeferred = new Deferred();
  56. this._activeDeferred = new Deferred();
  57. this._controllingDeferred = new Deferred();
  58. // Bind event handler callbacks.
  59. this._onMessage = this._onMessage.bind(this);
  60. this._onStateChange = this._onStateChange.bind(this);
  61. this._onUpdateFound = this._onUpdateFound.bind(this);
  62. this._onControllerChange = this._onControllerChange.bind(this);
  63. }
  64. /**
  65. * Registers a service worker for this instances script URL and service
  66. * worker options. By default this method delays registration until after
  67. * the window has loaded.
  68. *
  69. * @param {Object} [options]
  70. * @param {Function} [options.immediate=false] Setting this to true will
  71. * register the service worker immediately, even if the window has
  72. * not loaded (not recommended).
  73. */
  74. async register({immediate = false} = {}) {
  75. if (process.env.NODE_ENV !== 'production') {
  76. if (this._registrationTime) {
  77. logger.error('Cannot re-register a Workbox instance after it has ' +
  78. 'been registered. Create a new instance instead.');
  79. return;
  80. }
  81. }
  82. if (!immediate && document.readyState !== 'complete') {
  83. await new Promise((res) => addEventListener('load', res));
  84. }
  85. // Set this flag to true if any service worker was controlling the page
  86. // at registration time.
  87. this._isUpdate = Boolean(navigator.serviceWorker.controller);
  88. // Before registering, attempt to determine if a SW is already controlling
  89. // the page, and if that SW script (and version, if specified) matches this
  90. // instance's script.
  91. this._compatibleControllingSW = this._getControllingSWIfCompatible();
  92. this._registration = await this._registerScript();
  93. // If we have a compatible controller, store the controller as the "own"
  94. // SW, resolve active/controlling deferreds and add necessary listeners.
  95. if (this._compatibleControllingSW) {
  96. this._sw = this._compatibleControllingSW;
  97. this._activeDeferred.resolve(this._compatibleControllingSW);
  98. this._controllingDeferred.resolve(this._compatibleControllingSW);
  99. this._reportWindowReady(this._compatibleControllingSW);
  100. this._compatibleControllingSW.addEventListener(
  101. 'statechange', this._onStateChange, {once: true});
  102. }
  103. // If there's a waiting service worker with a matching URL before the
  104. // `updatefound` event fires, it likely means that this site is open
  105. // in another tab, or the user refreshed the page (and thus the prevoius
  106. // page wasn't fully unloaded before this page started loading).
  107. // https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#waiting
  108. const waitingSW = this._registration.waiting;
  109. if (waitingSW && urlsMatch(waitingSW.scriptURL, this._scriptURL)) {
  110. // Store the waiting SW as the "own" Sw, even if it means overwriting
  111. // a compatible controller.
  112. this._sw = waitingSW;
  113. // Run this in the next microtask, so any code that adds an event
  114. // listener after awaiting `register()` will get this event.
  115. Promise.resolve().then(() => {
  116. this.dispatchEvent(new WorkboxEvent('waiting', {
  117. sw: waitingSW,
  118. wasWaitingBeforeRegister: true,
  119. }));
  120. if (process.env.NODE_ENV !== 'production') {
  121. logger.warn('A service worker was already waiting to activate ' +
  122. 'before this script was registered...');
  123. }
  124. });
  125. }
  126. // If an "own" SW is already set, resolve the deferred.
  127. if (this._sw) {
  128. this._swDeferred.resolve(this._sw);
  129. }
  130. if (process.env.NODE_ENV !== 'production') {
  131. logger.log('Successfully registered service worker.', this._scriptURL);
  132. if (navigator.serviceWorker.controller) {
  133. if (this._compatibleControllingSW) {
  134. logger.debug('A service worker with the same script URL ' +
  135. 'is already controlling this page.');
  136. } else {
  137. logger.debug('A service worker with a different script URL is ' +
  138. 'currently controlling the page. The browser is now fetching ' +
  139. 'the new script now...');
  140. }
  141. }
  142. const currentPageIsOutOfScope = () => {
  143. const scopeURL = new URL(
  144. this._registerOptions.scope || this._scriptURL, document.baseURI);
  145. const scopeURLBasePath = new URL('./', scopeURL.href).pathname;
  146. return !location.pathname.startsWith(scopeURLBasePath);
  147. };
  148. if (currentPageIsOutOfScope()) {
  149. logger.warn('The current page is not in scope for the registered ' +
  150. 'service worker. Was this a mistake?');
  151. }
  152. }
  153. this._registration.addEventListener('updatefound', this._onUpdateFound);
  154. navigator.serviceWorker.addEventListener(
  155. 'controllerchange', this._onControllerChange, {once: true});
  156. // Add message listeners.
  157. if ('BroadcastChannel' in self) {
  158. this._broadcastChannel = new BroadcastChannel('workbox');
  159. this._broadcastChannel.addEventListener('message', this._onMessage);
  160. }
  161. navigator.serviceWorker.addEventListener('message', this._onMessage);
  162. return this._registration;
  163. }
  164. /**
  165. * Resolves to the service worker registered by this instance as soon as it
  166. * is active. If a service worker was already controlling at registration
  167. * time then it will resolve to that if the script URLs (and optionally
  168. * script versions) match, otherwise it will wait until an update is found
  169. * and activates.
  170. *
  171. * @return {Promise<ServiceWorker>}
  172. */
  173. get active() {
  174. return this._activeDeferred.promise;
  175. }
  176. /**
  177. * Resolves to the service worker registered by this instance as soon as it
  178. * is controlling the page. If a service worker was already controlling at
  179. * registration time then it will resolve to that if the script URLs (and
  180. * optionally script versions) match, otherwise it will wait until an update
  181. * is found and starts controlling the page.
  182. * Note: the first time a service worker is installed it will active but
  183. * not start controlling the page unless `clients.claim()` is called in the
  184. * service worker.
  185. *
  186. * @return {Promise<ServiceWorker>}
  187. */
  188. get controlling() {
  189. return this._controllingDeferred.promise;
  190. }
  191. /**
  192. * Resolves with a reference to a service worker that matches the script URL
  193. * of this instance, as soon as it's available.
  194. *
  195. * If, at registration time, there's already an active or waiting service
  196. * worker with a matching script URL, it will be used (with the waiting
  197. * service worker taking precedence over the active service worker if both
  198. * match, since the waiting service worker would have been registered more
  199. * recently).
  200. * If there's no matching active or waiting service worker at registration
  201. * time then the promise will not resolve until an update is found and starts
  202. * installing, at which point the installing service worker is used.
  203. *
  204. * @return {Promise<ServiceWorker>}
  205. */
  206. async getSW() {
  207. // If `this._sw` is set, resolve with that as we want `getSW()` to
  208. // return the correct (new) service worker if an update is found.
  209. return this._sw || this._swDeferred.promise;
  210. }
  211. /**
  212. * Sends the passed data object to the service worker registered by this
  213. * instance (via [`getSW()`]{@link module:workbox-window.Workbox#getSW}) and resolves
  214. * with a response (if any).
  215. *
  216. * A response can be set in a message handler in the service worker by
  217. * calling `event.ports[0].postMessage(...)`, which will resolve the promise
  218. * returned by `messageSW()`. If no response is set, the promise will never
  219. * resolve.
  220. *
  221. * @param {Object} data An object to send to the service worker
  222. * @return {Promise<Object>}
  223. */
  224. async messageSW(data) {
  225. const sw = await this.getSW();
  226. return messageSW(sw, data);
  227. }
  228. /**
  229. * Checks for a service worker already controlling the page and returns
  230. * it if its script URL matchs.
  231. *
  232. * @private
  233. * @return {ServiceWorker|undefined}
  234. */
  235. _getControllingSWIfCompatible() {
  236. const controller = navigator.serviceWorker.controller;
  237. if (controller && urlsMatch(controller.scriptURL, this._scriptURL)) {
  238. return controller;
  239. }
  240. }
  241. /**
  242. * Registers a service worker for this instances script URL and register
  243. * options and tracks the time registration was complete.
  244. *
  245. * @private
  246. */
  247. async _registerScript() {
  248. try {
  249. const reg = await navigator.serviceWorker.register(
  250. this._scriptURL, this._registerOptions);
  251. // Keep track of when registration happened, so it can be used in the
  252. // `this._onUpdateFound` heuristic. Also use the presence of this
  253. // property as a way to see if `.register()` has been called.
  254. this._registrationTime = performance.now();
  255. return reg;
  256. } catch (error) {
  257. if (process.env.NODE_ENV !== 'production') {
  258. logger.error(error);
  259. }
  260. // Re-throw the error.
  261. throw error;
  262. }
  263. }
  264. /**
  265. * Sends a message to the passed service worker that the window is ready.
  266. *
  267. * @param {ServiceWorker} sw
  268. * @private
  269. */
  270. _reportWindowReady(sw) {
  271. messageSW(sw, {
  272. type: 'WINDOW_READY',
  273. meta: 'workbox-window',
  274. });
  275. }
  276. /**
  277. * @private
  278. */
  279. _onUpdateFound() {
  280. const installingSW = this._registration.installing;
  281. // If the script URL passed to `navigator.serviceWorker.register()` is
  282. // different from the current controlling SW's script URL, we know any
  283. // successful registration calls will trigger an `updatefound` event.
  284. // But if the registered script URL is the same as the current controlling
  285. // SW's script URL, we'll only get an `updatefound` event if the file
  286. // changed since it was last registered. This can be a problem if the user
  287. // opens up the same page in a different tab, and that page registers
  288. // a SW that triggers an update. It's a problem because this page has no
  289. // good way of knowing whether the `updatefound` event came from the SW
  290. // script it registered or from a registration attempt made by a newer
  291. // version of the page running in another tab.
  292. // To minimize the possibility of a false positive, we use the logic here:
  293. let updateLikelyTriggeredExternally =
  294. // Since we enforce only calling `register()` once, and since we don't
  295. // add the `updatefound` event listener until the `register()` call, if
  296. // `_updateFoundCount` is > 0 then it means this method has already
  297. // been called, thus this SW must be external
  298. this._updateFoundCount > 0 ||
  299. // If the script URL of the installing SW is different from this
  300. // instance's script URL, we know it's definitely not from our
  301. // registration.
  302. !urlsMatch(installingSW.scriptURL, this._scriptURL) ||
  303. // If all of the above are false, then we use a time-based heuristic:
  304. // Any `updatefound` event that occurs long after our registration is
  305. // assumed to be external.
  306. (performance.now() >
  307. this._registrationTime + REGISTRATION_TIMEOUT_DURATION) ?
  308. // If any of the above are not true, we assume the update was
  309. // triggered by this instance.
  310. true : false;
  311. if (updateLikelyTriggeredExternally) {
  312. this._externalSW = installingSW;
  313. this._registration.removeEventListener(
  314. 'updatefound', this._onUpdateFound);
  315. } else {
  316. // If the update was not triggered externally we know the installing
  317. // SW is the one we registered, so we set it.
  318. this._sw = installingSW;
  319. this._swDeferred.resolve(installingSW);
  320. // The `installing` state isn't something we have a dedicated
  321. // callback for, but we do log messages for it in development.
  322. if (process.env.NODE_ENV !== 'production') {
  323. if (navigator.serviceWorker.controller) {
  324. logger.log('Updated service worker found. Installing now...');
  325. } else {
  326. logger.log('Service worker is installing...');
  327. }
  328. }
  329. }
  330. // Increment the `updatefound` count, so future invocations of this
  331. // method can be sure they were triggered externally.
  332. ++this._updateFoundCount;
  333. // Add a `statechange` listener regardless of whether this update was
  334. // triggered externally, since we have callbacks for both.
  335. installingSW.addEventListener('statechange', this._onStateChange);
  336. }
  337. /**
  338. * @private
  339. * @param {Event} originalEvent
  340. */
  341. _onStateChange(originalEvent) {
  342. const sw = originalEvent.target;
  343. const {state} = sw;
  344. const isExternal = sw === this._externalSW;
  345. const eventPrefix = isExternal ? 'external' : '';
  346. const eventProps = {sw, originalEvent};
  347. if (!isExternal && this._isUpdate) {
  348. eventProps.isUpdate = true;
  349. }
  350. this.dispatchEvent(new WorkboxEvent(
  351. eventPrefix + state, eventProps));
  352. if (state === 'installed') {
  353. // This timeout is used to ignore cases where the service worker calls
  354. // `skipWaiting()` in the install event, thus moving it directly in the
  355. // activating state. (Since all service workers *must* go through the
  356. // waiting phase, the only way to detect `skipWaiting()` called in the
  357. // install event is to observe that the time spent in the waiting phase
  358. // is very short.)
  359. // NOTE: we don't need separate timeouts for the own and external SWs
  360. // since they can't go through these phases at the same time.
  361. this._waitingTimeout = setTimeout(() => {
  362. // Ensure the SW is still waiting (it may now be redundant).
  363. if (state === 'installed' && this._registration.waiting === sw) {
  364. this.dispatchEvent(new WorkboxEvent(
  365. eventPrefix + 'waiting', eventProps));
  366. if (process.env.NODE_ENV !== 'production') {
  367. if (isExternal) {
  368. logger.warn('An external service worker has installed but is ' +
  369. 'waiting for this client to close before activating...');
  370. } else {
  371. logger.warn('The service worker has installed but is waiting ' +
  372. 'for existing clients to close before activating...');
  373. }
  374. }
  375. }
  376. }, WAITING_TIMEOUT_DURATION);
  377. } else if (state === 'activating') {
  378. clearTimeout(this._waitingTimeout);
  379. if (!isExternal) {
  380. this._activeDeferred.resolve(sw);
  381. }
  382. }
  383. if (process.env.NODE_ENV !== 'production') {
  384. switch (state) {
  385. case 'installed':
  386. if (isExternal) {
  387. logger.warn('An external service worker has installed. ' +
  388. 'You may want to suggest users reload this page.');
  389. } else {
  390. logger.log('Registered service worker installed.');
  391. }
  392. break;
  393. case 'activated':
  394. if (isExternal) {
  395. logger.warn('An external service worker has activated.');
  396. } else {
  397. logger.log('Registered service worker activated.');
  398. if (sw !== navigator.serviceWorker.controller) {
  399. logger.warn('The registered service worker is active but ' +
  400. 'not yet controlling the page. Reload or run ' +
  401. '`clients.claim()` in the service worker.');
  402. }
  403. }
  404. break;
  405. case 'redundant':
  406. if (sw === this._compatibleControllingSW) {
  407. logger.log('Previously controlling service worker now redundant!');
  408. } else if (!isExternal) {
  409. logger.log('Registered service worker now redundant!');
  410. }
  411. break;
  412. }
  413. }
  414. }
  415. /**
  416. * @private
  417. * @param {Event} originalEvent
  418. */
  419. _onControllerChange(originalEvent) {
  420. const sw = this._sw;
  421. if (sw === navigator.serviceWorker.controller) {
  422. this.dispatchEvent(new WorkboxEvent('controlling', {sw, originalEvent}));
  423. if (process.env.NODE_ENV !== 'production') {
  424. logger.log('Registered service worker now controlling this page.');
  425. }
  426. this._controllingDeferred.resolve(sw);
  427. }
  428. }
  429. /**
  430. * @private
  431. * @param {Event} originalEvent
  432. */
  433. _onMessage(originalEvent) {
  434. const {data} = originalEvent;
  435. this.dispatchEvent(new WorkboxEvent('message', {data, originalEvent}));
  436. }
  437. }
  438. // The jsdoc comments below outline the events this instance may dispatch:
  439. // -----------------------------------------------------------------------
  440. /**
  441. * The `message` event is dispatched any time a `postMessage` (or a
  442. * `BroadcastChannel` message with the `workbox` channel name) is received.
  443. *
  444. * @event module:workbox-window.Workbox#message
  445. * @type {WorkboxEvent}
  446. * @property {*} data The `data` property from the original `message` event.
  447. * @property {Event} originalEvent The original [`message`]{@link https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent}
  448. * event.
  449. * @property {string} type `message`.
  450. * @property {Workbox} target The `Workbox` instance.
  451. */
  452. /**
  453. * The `installed` event is dispatched if the state of a
  454. * [`Workbox`]{@link module:workbox-window.Workbox} instance's
  455. * [registered service worker]{@link https://developers.google.com/web/tools/workbox/modules/workbox-precaching#def-registered-sw}
  456. * changes to `installed`.
  457. *
  458. * Then can happen either the very first time a service worker is installed,
  459. * or after an update to the current service worker is found. In the case
  460. * of an update being found, the event's `isUpdate` property will be `true`.
  461. *
  462. * @event module:workbox-window.Workbox#installed
  463. * @type {WorkboxEvent}
  464. * @property {ServiceWorker} sw The service worker instance.
  465. * @property {Event} originalEvent The original [`statechange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/onstatechange}
  466. * event.
  467. * @property {boolean|undefined} isUpdate True if a service worker was already
  468. * controlling when this `Workbox` instance called `register()`.
  469. * @property {string} type `installed`.
  470. * @property {Workbox} target The `Workbox` instance.
  471. */
  472. /**
  473. * The `waiting` event is dispatched if the state of a
  474. * [`Workbox`]{@link module:workbox-window.Workbox} instance's
  475. * [registered service worker]{@link https://developers.google.com/web/tools/workbox/modules/workbox-precaching#def-registered-sw}
  476. * changes to `installed` and then doesn't immediately change to `activating`.
  477. * It may also be dispatched if a service worker with the same
  478. * [`scriptURL`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/scriptURL}
  479. * was already waiting when the [`register()`]{@link module:workbox-window.Workbox#register}
  480. * method was called.
  481. *
  482. * @event module:workbox-window.Workbox#waiting
  483. * @type {WorkboxEvent}
  484. * @property {ServiceWorker} sw The service worker instance.
  485. * @property {Event} originalEvent The native `controllerchange` event
  486. * @property {boolean|undefined} isUpdate True if a service worker was already
  487. * controlling when this `Workbox` instance called `register()`.
  488. * @property {boolean|undefined} wasWaitingBeforeRegister True if a service worker with
  489. * a matching `scriptURL` was already waiting when this `Workbox`
  490. * instance called `register()`.
  491. * @property {string} type `waiting`.
  492. * @property {Workbox} target The `Workbox` instance.
  493. */
  494. /**
  495. * The `controlling` event is dispatched if a
  496. * [`controllerchange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/oncontrollerchange}
  497. * fires on the service worker [container]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer}
  498. * and the [`scriptURL`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/scriptURL}
  499. * of the new [controller]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/controller}
  500. * matches the `scriptURL` of the `Workbox` instance's
  501. * [registered service worker]{@link https://developers.google.com/web/tools/workbox/modules/workbox-precaching#def-registered-sw}.
  502. *
  503. * @event module:workbox-window.Workbox#controlling
  504. * @type {WorkboxEvent}
  505. * @property {ServiceWorker} sw The service worker instance.
  506. * @property {Event} originalEvent The original [`controllerchange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/oncontrollerchange}
  507. * event.
  508. * @property {boolean|undefined} isUpdate True if a service worker was already
  509. * controlling when this service worker was registered.
  510. * @property {string} type `controlling`.
  511. * @property {Workbox} target The `Workbox` instance.
  512. */
  513. /**
  514. * The `activated` event is dispatched if the state of a
  515. * [`Workbox`]{@link module:workbox-window.Workbox} instance's
  516. * [registered service worker]{@link https://developers.google.com/web/tools/workbox/modules/workbox-precaching#def-registered-sw}
  517. * changes to `activated`.
  518. *
  519. * @event module:workbox-window.Workbox#activated
  520. * @type {WorkboxEvent}
  521. * @property {ServiceWorker} sw The service worker instance.
  522. * @property {Event} originalEvent The original [`statechange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/onstatechange}
  523. * event.
  524. * @property {boolean|undefined} isUpdate True if a service worker was already
  525. * controlling when this `Workbox` instance called `register()`.
  526. * @property {string} type `activated`.
  527. * @property {Workbox} target The `Workbox` instance.
  528. */
  529. /**
  530. * The `redundant` event is dispatched if the state of a
  531. * [`Workbox`]{@link module:workbox-window.Workbox} instance's
  532. * [registered service worker]{@link https://developers.google.com/web/tools/workbox/modules/workbox-precaching#def-registered-sw}
  533. * changes to `redundant`.
  534. *
  535. * @event module:workbox-window.Workbox#redundant
  536. * @type {WorkboxEvent}
  537. * @property {ServiceWorker} sw The service worker instance.
  538. * @property {Event} originalEvent The original [`statechange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/onstatechange}
  539. * event.
  540. * @property {boolean|undefined} isUpdate True if a service worker was already
  541. * controlling when this `Workbox` instance called `register()`.
  542. * @property {string} type `redundant`.
  543. * @property {Workbox} target The `Workbox` instance.
  544. */
  545. /**
  546. * The `externalinstalled` event is dispatched if the state of an
  547. * [external service worker]{@link https://developers.google.com/web/tools/workbox/modules/workbox-precaching#def-external-sw}
  548. * changes to `installed`.
  549. *
  550. * @event module:workbox-window.Workbox#externalinstalled
  551. * @type {WorkboxEvent}
  552. * @property {ServiceWorker} sw The service worker instance.
  553. * @property {Event} originalEvent The original [`statechange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/onstatechange}
  554. * event.
  555. * @property {string} type `externalinstalled`.
  556. * @property {Workbox} target The `Workbox` instance.
  557. */
  558. /**
  559. * The `externalwaiting` event is dispatched if the state of an
  560. * [external service worker]{@link https://developers.google.com/web/tools/workbox/modules/workbox-precaching#def-external-sw}
  561. * changes to `waiting`.
  562. *
  563. * @event module:workbox-window.Workbox#externalwaiting
  564. * @type {WorkboxEvent}
  565. * @property {ServiceWorker} sw The service worker instance.
  566. * @property {Event|undefined} originalEvent The original [`statechange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/onstatechange}
  567. * event.
  568. * @property {string} type `externalwaiting`.
  569. * @property {Workbox} target The `Workbox` instance.
  570. */
  571. /**
  572. * The `externalactivated` event is dispatched if the state of an
  573. * [external service worker]{@link https://developers.google.com/web/tools/workbox/modules/workbox-precaching#def-external-sw}
  574. * changes to `activated`.
  575. *
  576. * @event module:workbox-window.Workbox#externalactivated
  577. * @type {WorkboxEvent}
  578. * @property {ServiceWorker} sw The service worker instance.
  579. * @property {Event} originalEvent The original [`statechange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/onstatechange}
  580. * event.
  581. * @property {string} type `externalactivated`.
  582. * @property {Workbox} target The `Workbox` instance.
  583. */
  584. export {Workbox};