164 lines
6.3 KiB
JavaScript
164 lines
6.3 KiB
JavaScript
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||
|
// Use of this source code is governed by a BSD-style license that can be
|
||
|
// found in the LICENSE file.
|
||
|
|
||
|
/**
|
||
|
* This script installs service_worker.js to provide PWA functionality to
|
||
|
* application. For more information, see:
|
||
|
* https://developers.google.com/web/fundamentals/primers/service-workers
|
||
|
*/
|
||
|
|
||
|
if (!_flutter) {
|
||
|
var _flutter = {};
|
||
|
}
|
||
|
_flutter.loader = null;
|
||
|
|
||
|
(function() {
|
||
|
"use strict";
|
||
|
class FlutterLoader {
|
||
|
/**
|
||
|
* Creates a FlutterLoader, and initializes its instance methods.
|
||
|
*/
|
||
|
constructor() {
|
||
|
// TODO: Move the below methods to "#private" once supported by all the browsers
|
||
|
// we support. In the meantime, we use the "revealing module" pattern.
|
||
|
|
||
|
// Watchdog to prevent injecting the main entrypoint multiple times.
|
||
|
this._scriptLoaded = null;
|
||
|
|
||
|
// Resolver for the pending promise returned by loadEntrypoint.
|
||
|
this._didCreateEngineInitializerResolve = null;
|
||
|
|
||
|
// Called by Flutter web.
|
||
|
// Bound to `this` now, so "this" is preserved across JS <-> Flutter jumps.
|
||
|
this.didCreateEngineInitializer = this._didCreateEngineInitializer.bind(this);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Initializes the main.dart.js with/without serviceWorker.
|
||
|
* @param {*} options
|
||
|
* @returns a Promise that will eventually resolve with an EngineInitializer,
|
||
|
* or will be rejected with the error caused by the loader.
|
||
|
*/
|
||
|
loadEntrypoint(options) {
|
||
|
const {
|
||
|
entrypointUrl = "main.dart.js",
|
||
|
serviceWorker,
|
||
|
} = (options || {});
|
||
|
return this._loadWithServiceWorker(entrypointUrl, serviceWorker);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Resolves the promise created by loadEntrypoint.
|
||
|
* Called by Flutter through the public `didCreateEngineInitializer` method,
|
||
|
* which is bound to the correct instance of the FlutterLoader on the page.
|
||
|
* @param {*} engineInitializer
|
||
|
*/
|
||
|
_didCreateEngineInitializer(engineInitializer) {
|
||
|
if (typeof this._didCreateEngineInitializerResolve != "function") {
|
||
|
console.warn("Do not call didCreateEngineInitializer by hand. Start with loadEntrypoint instead.");
|
||
|
}
|
||
|
this._didCreateEngineInitializerResolve(engineInitializer);
|
||
|
// Remove the public method after it's done, so Flutter Web can hot restart.
|
||
|
delete this.didCreateEngineInitializer;
|
||
|
}
|
||
|
|
||
|
_loadEntrypoint(entrypointUrl) {
|
||
|
if (!this._scriptLoaded) {
|
||
|
console.debug("Injecting <script> tag.");
|
||
|
this._scriptLoaded = new Promise((resolve, reject) => {
|
||
|
let scriptTag = document.createElement("script");
|
||
|
scriptTag.src = entrypointUrl;
|
||
|
scriptTag.type = "application/javascript";
|
||
|
// Cache the resolve, so it can be called from Flutter.
|
||
|
// Note: Flutter hot restart doesn't re-create this promise, so this
|
||
|
// can only be called once. Instead, we need to model this as a stream
|
||
|
// of `engineCreated` events coming from Flutter that are handled by JS.
|
||
|
this._didCreateEngineInitializerResolve = resolve;
|
||
|
scriptTag.addEventListener("error", reject);
|
||
|
document.body.append(scriptTag);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
return this._scriptLoaded;
|
||
|
}
|
||
|
|
||
|
_waitForServiceWorkerActivation(serviceWorker, entrypointUrl) {
|
||
|
if (!serviceWorker || serviceWorker.state == "activated") {
|
||
|
if (!serviceWorker) {
|
||
|
console.warn("Cannot activate a null service worker.");
|
||
|
} else {
|
||
|
console.debug("Service worker already active.");
|
||
|
}
|
||
|
return this._loadEntrypoint(entrypointUrl);
|
||
|
}
|
||
|
return new Promise((resolve, _) => {
|
||
|
serviceWorker.addEventListener("statechange", () => {
|
||
|
if (serviceWorker.state == "activated") {
|
||
|
console.debug("Installed new service worker.");
|
||
|
resolve(this._loadEntrypoint(entrypointUrl));
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
_loadWithServiceWorker(entrypointUrl, serviceWorkerOptions) {
|
||
|
if (!("serviceWorker" in navigator) || serviceWorkerOptions == null) {
|
||
|
console.warn("Service worker not supported (or configured).", serviceWorkerOptions);
|
||
|
return this._loadEntrypoint(entrypointUrl);
|
||
|
}
|
||
|
|
||
|
const {
|
||
|
serviceWorkerVersion,
|
||
|
timeoutMillis = 4000,
|
||
|
} = serviceWorkerOptions;
|
||
|
|
||
|
let serviceWorkerUrl = "flutter_service_worker.js?v=" + serviceWorkerVersion;
|
||
|
let loader = navigator.serviceWorker.register(serviceWorkerUrl)
|
||
|
.then((reg) => {
|
||
|
if (!reg.active && (reg.installing || reg.waiting)) {
|
||
|
// No active web worker and we have installed or are installing
|
||
|
// one for the first time. Simply wait for it to activate.
|
||
|
let sw = reg.installing || reg.waiting;
|
||
|
return this._waitForServiceWorkerActivation(sw, entrypointUrl);
|
||
|
} else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
|
||
|
// When the app updates the serviceWorkerVersion changes, so we
|
||
|
// need to ask the service worker to update.
|
||
|
console.debug("New service worker available.");
|
||
|
return reg.update().then((reg) => {
|
||
|
console.debug("Service worker updated.");
|
||
|
let sw = reg.installing || reg.waiting || reg.active;
|
||
|
return this._waitForServiceWorkerActivation(sw, entrypointUrl);
|
||
|
});
|
||
|
} else {
|
||
|
// Existing service worker is still good.
|
||
|
console.debug("Loading app from service worker.");
|
||
|
return this._loadEntrypoint(entrypointUrl);
|
||
|
}
|
||
|
})
|
||
|
.catch((error) => {
|
||
|
// Some exception happened while registering/activating the service worker.
|
||
|
console.warn("Failed to register or activate service worker:", error);
|
||
|
return this._loadEntrypoint(entrypointUrl);
|
||
|
});
|
||
|
|
||
|
// Timeout race promise
|
||
|
let timeout;
|
||
|
if (timeoutMillis > 0) {
|
||
|
timeout = new Promise((resolve, _) => {
|
||
|
setTimeout(() => {
|
||
|
if (!this._scriptLoaded) {
|
||
|
console.warn("Loading from service worker timed out after", timeoutMillis, "milliseconds.");
|
||
|
resolve(this._loadEntrypoint(entrypointUrl));
|
||
|
}
|
||
|
}, timeoutMillis);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
return Promise.race([loader, timeout]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_flutter.loader = new FlutterLoader();
|
||
|
}());
|