blob: af8950b1bcf19dba3c033d3db3c8dac0e36e802f [file] [log] [blame]
// 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.
/// Generates the flutter.js file.
///
/// flutter.js should be completely static, so **do not use any parameter or
/// environment variable to generate this file**.
String generateFlutterJsFile() {
return '''
// 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 {
// 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.
_scriptLoaded = null;
// Resolver for the pending promise returned by loadEntrypoint.
_didCreateEngineInitializerResolve = null;
/**
* 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.
* Needs to be weirdly bound like it is, so "this" is preserved across
* the JS <-> Flutter jumps.
* @param {*} engineInitializer
*/
didCreateEngineInitializer = (function(engineInitializer) {
if (typeof this._didCreateEngineInitializerResolve != "function") {
console.warn("Do not call didCreateEngineInitializer by hand. Start with loadEntrypoint instead.");
}
this._didCreateEngineInitializerResolve(engineInitializer);
// Remove this method after it's done, so Flutter Web can hot restart.
delete this.didCreateEngineInitializer;
}).bind(this);
_loadEntrypoint(entrypointUrl) {
if (!this._scriptLoaded) {
this._scriptLoaded = new Promise((resolve, reject) => {
let scriptTag = document.createElement("script");
scriptTag.src = entrypointUrl;
scriptTag.type = "application/javascript";
this._didCreateEngineInitializerResolve = resolve; // Cache the resolve, so it can be called from Flutter.
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. Falling back to plain <script> tag.");
} 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). Falling back to plain <script> tag.", 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);
}
});
// Timeout race promise
let timeout;
if (timeoutMillis > 0) {
timeout = new Promise((resolve, _) => {
setTimeout(() => {
if (!this._scriptLoaded) {
console.warn("Failed to load app from service worker. Falling back to plain <script> tag.");
resolve(this._loadEntrypoint(entrypointUrl));
}
}, timeoutMillis);
});
}
return Promise.race([loader, timeout]);
}
}
_flutter.loader = new FlutterLoader();
}());
''';
}