/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* strict-local
* @format
*/
/* eslint-env worker, serviceworker */
"use strict";
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
Promise.resolve(value).then(_next, _throw);
}
}
function _asyncToGenerator(fn) {
return function() {
var self = this,
args = arguments;
return new Promise(function(resolve, reject) {
var gen = fn.apply(self, args);
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
}
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
}
_next(undefined);
});
};
}
const BundleNotFoundError = require("./BundleNotFoundError");
const WebSocketHMRClient = require("../WebSocketHMRClient");
const bundleToString = require("./bundleToString");
const patchBundle = require("./patchBundle");
const stringToBundle = require("./stringToBundle");
const _require = require("./bundleDB"),
openDB = _require.openDB,
getBundleMetadataFromDB = _require.getBundleMetadata,
setBundleMetadata = _require.setBundleMetadata,
removeBundleMetadata = _require.removeBundleMetadata;
const _require2 = require("./metadata"),
fetchBundleMetadata = _require2.fetchBundleMetadata;
const _require3 = require("./response"),
createResponse = _require3.createResponse,
getRevisionId = _require3.getRevisionId;
class UpdateError extends Error {
constructor(bundleUrl, originalError) {
super(
`Error retrieving an initial update for the bundle \`${bundleUrl}\`.`
);
this.stack = "Caused by: " + originalError.stack;
}
}
function defaultGetHmrServerUrl(bundleUrl, revisionId) {
const url = new URL(bundleUrl);
return `${url.protocol === "https:" ? "wss" : "ws"}://${
url.host
}/hot?revisionId=${revisionId}`;
}
function defaultOnUpdate(clientId, update) {
clients.get(clientId).then(client => {
if (client != null) {
client.postMessage({
type: "METRO_UPDATE",
update
});
}
});
}
function defaultOnUpdateStart(clientId) {
clients.get(clientId).then(client => {
if (client != null) {
client.postMessage({
type: "METRO_UPDATE_START"
});
}
});
}
function defaultOnUpdateError(clientId, error) {
clients.get(clientId).then(client => {
if (client != null) {
client.postMessage({
type: "METRO_UPDATE_ERROR",
error
});
}
});
}
const DEFAULT_DB_NAME = "__metroBundleDB";
const CACHE_VERSION = 1;
const DEFAULT_CACHE_NAME = "__metroBundleCacheV" + CACHE_VERSION;
function create() {
let _ref =
arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
_ref$getHmrServerUrl = _ref.getHmrServerUrl,
getHmrServerUrl =
_ref$getHmrServerUrl === void 0
? defaultGetHmrServerUrl
: _ref$getHmrServerUrl,
_ref$getBundleMetadat = _ref.getBundleMetadata,
getBundleMetadata =
_ref$getBundleMetadat === void 0
? fetchBundleMetadata
: _ref$getBundleMetadat,
_ref$onUpdateStart = _ref.onUpdateStart,
onUpdateStart =
_ref$onUpdateStart === void 0 ? defaultOnUpdateStart : _ref$onUpdateStart,
_ref$onUpdate = _ref.onUpdate,
onUpdate = _ref$onUpdate === void 0 ? defaultOnUpdate : _ref$onUpdate,
_ref$onUpdateError = _ref.onUpdateError,
onUpdateError =
_ref$onUpdateError === void 0 ? defaultOnUpdateError : _ref$onUpdateError,
_ref$bundleCacheName = _ref.bundleCacheName,
bundleCacheName =
_ref$bundleCacheName === void 0
? DEFAULT_CACHE_NAME
: _ref$bundleCacheName,
_ref$bundleDBName = _ref.bundleDBName,
bundleDBName =
_ref$bundleDBName === void 0 ? DEFAULT_DB_NAME : _ref$bundleDBName;
const cachePromise = caches.open(bundleCacheName);
const dbPromise = openDB(DEFAULT_DB_NAME);
const clients = new Map();
const setupUpdates =
/*#__PURE__*/
(function() {
var _ref2 = _asyncToGenerator(function*(
bundleKey,
clientId,
prevRevisionId,
prevBundleRes,
prevBundleMetadataPromise
) {
const cache = yield cachePromise;
const db = yield dbPromise;
let bundleRes = prevBundleRes;
let revisionId = prevRevisionId;
let bundlePromise = _asyncToGenerator(function*() {
const stringBundle = yield prevBundleRes.clone().text();
const prevBundleMetadata = yield prevBundleMetadataPromise;
return stringToBundle(stringBundle, prevBundleMetadata);
})();
let resolveBundleRes;
let rejectBundleRes;
const client = {
ids: new Set([clientId]),
bundleResPromise: new Promise((resolve, reject) => {
resolveBundleRes = resolve;
rejectBundleRes = reject;
})
};
clients.set(bundleKey, client);
let resolved = false;
const wsClient = new WebSocketHMRClient(
getHmrServerUrl(bundleKey, prevRevisionId)
);
wsClient.on("connection-error", error => {
rejectBundleRes(error);
});
wsClient.on("close", () => {
clients.delete(bundleKey);
});
wsClient.on("error", error => {
if (!resolved) {
rejectBundleRes(error);
return;
}
client.ids.forEach(clientId => onUpdateError(clientId, error));
});
wsClient.on("update-start", () => {
client.ids.forEach(clientId => onUpdateStart(clientId));
});
wsClient.on(
"update",
/*#__PURE__*/
(function() {
var _ref4 = _asyncToGenerator(function*(update) {
if (resolved) {
// Only notify clients for later updates.
client.ids.forEach(clientId => onUpdate(clientId, update));
}
let nextBundleRes;
if (revisionId === update.revisionId) {
nextBundleRes = bundleRes;
} else {
let bundle;
try {
bundle = yield bundlePromise;
} catch (error) {
// This error should only happen when either the initial bundle or the
// initial bundle metadata are invalid or cannot be retrieved.
rejectBundleRes(error);
return;
}
const nextBundle = patchBundle(bundle, {
added: update.added,
modified: update.modified,
deleted: update.deleted
});
bundlePromise = Promise.resolve(nextBundle);
const _bundleToString = bundleToString(nextBundle),
stringBundle = _bundleToString.code,
metadata = _bundleToString.metadata;
nextBundleRes = createResponse(
stringBundle,
update.revisionId,
new Headers({
// In development, we expect the bundle URL to be static. As such,
// the browser should always request the Service Worker for the
// latest version.
"Cache-Control": "no-cache"
})
);
cache.put(bundleKey, nextBundleRes.clone());
setBundleMetadata(db, update.revisionId, metadata);
removeBundleMetadata(db, revisionId);
revisionId = update.revisionId;
} // We need to clone the response before it can be consumed anywhere else.
bundleRes = nextBundleRes.clone();
if (!resolved) {
resolved = true;
resolveBundleRes(nextBundleRes);
} else {
client.bundleResPromise = Promise.resolve(nextBundleRes);
}
});
return function(_x6) {
return _ref4.apply(this, arguments);
};
})()
);
wsClient.enable();
return client;
});
return function setupUpdates(_x, _x2, _x3, _x4, _x5) {
return _ref2.apply(this, arguments);
};
})();
function getOrFetchBundleMetadata(_x7, _x8) {
return _getOrFetchBundleMetadata.apply(this, arguments);
}
function _getOrFetchBundleMetadata() {
_getOrFetchBundleMetadata = _asyncToGenerator(function*(
bundleKey,
revisionId
) {
const metadata = yield getBundleMetadataFromDB(
yield dbPromise,
revisionId
);
if (metadata != null) {
return metadata;
}
return yield getBundleMetadata(bundleKey, revisionId);
});
return _getOrFetchBundleMetadata.apply(this, arguments);
}
const getBundle =
/*#__PURE__*/
(function() {
var _ref5 = _asyncToGenerator(function*(bundleKey, clientId) {
let client = clients.get(bundleKey);
if (client != null) {
// There's already an update client running for this bundle URL.
client.ids.add(clientId);
} else {
const cache = yield cachePromise;
const prevBundleRes = yield cache.match(bundleKey);
if (prevBundleRes == null) {
throw new BundleNotFoundError(bundleKey);
}
const prevRevisionId = getRevisionId(prevBundleRes); // We could expect metadata to always be defined. However, the cache and the
// database can be cleared independently, which means that there is a
// possibility that the bundle cache was cleared and the database was not
// and vice versa.
const prevBundleMetadataPromise = getOrFetchBundleMetadata(
bundleKey,
prevRevisionId
);
client = yield setupUpdates(
bundleKey,
clientId,
prevRevisionId,
prevBundleRes,
prevBundleMetadataPromise
);
}
let bundleRes;
try {
// Whenever we consume a response, we need to clone it so that we can
// still use its body for the next request.
bundleRes = yield client.bundleResPromise;
client.bundleResPromise = Promise.resolve(bundleRes.clone());
} catch (error) {
throw new UpdateError(bundleKey, error);
}
return bundleRes;
});
return function getBundle(_x9, _x10) {
return _ref5.apply(this, arguments);
};
})();
const registerBundle =
/*#__PURE__*/
(function() {
var _ref6 = _asyncToGenerator(function*(bundleKey, bundleRes, clientId) {
const cache = yield cachePromise; // Since the user might not be aware of Response semantics, we should not
// consume the provided response's body, but instead make clones of it.
const initialRevisionId = getRevisionId(bundleRes);
const putPromise = cache.put(bundleKey, bundleRes.clone()); // See the comment regarding getOrFetchBundleMetadata in getBundle.
const metadataPromise = getOrFetchBundleMetadata(
bundleKey,
initialRevisionId
);
yield Promise.all([
putPromise,
_asyncToGenerator(function*() {
const metadata = yield metadataPromise;
yield setBundleMetadata(
yield dbPromise,
initialRevisionId,
metadata
);
})(),
setupUpdates(
bundleKey,
clientId,
initialRevisionId,
bundleRes.clone(),
metadataPromise
)
]);
});
return function registerBundle(_x11, _x12, _x13) {
return _ref6.apply(this, arguments);
};
})();
return {
getBundle,
registerBundle
};
}
module.exports = {
create,
BundleNotFoundError,
UpdateError,
CACHE_VERSION
};