123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362 |
- // 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.
- if (!_flutter) {
- var _flutter = {};
- }
- _flutter.loader = null;
- (function () {
- "use strict";
- /**
- * Wraps `promise` in a timeout of the given `duration` in ms.
- *
- * Resolves/rejects with whatever the original `promises` does, or rejects
- * if `promise` takes longer to complete than `duration`. In that case,
- * `debugName` is used to compose a legible error message.
- *
- * If `duration` is < 0, the original `promise` is returned unchanged.
- * @param {Promise} promise
- * @param {number} duration
- * @param {string} debugName
- * @returns {Promise} a wrapped promise.
- */
- async function timeout(promise, duration, debugName) {
- if (duration < 0) {
- return promise;
- }
- let timeoutId;
- const _clock = new Promise((_, reject) => {
- timeoutId = setTimeout(() => {
- reject(
- new Error(
- `${debugName} took more than ${duration}ms to resolve. Moving on.`,
- {
- cause: timeout,
- }
- )
- );
- }, duration);
- });
- return Promise.race([promise, _clock]).finally(() => {
- clearTimeout(timeoutId);
- });
- }
- /**
- * Handles the creation of a TrustedTypes `policy` that validates URLs based
- * on an (optional) incoming array of RegExes.
- */
- class FlutterTrustedTypesPolicy {
- /**
- * Constructs the policy.
- * @param {[RegExp]} validPatterns the patterns to test URLs
- * @param {String} policyName the policy name (optional)
- */
- constructor(validPatterns, policyName = "flutter-js") {
- const patterns = validPatterns || [
- /\.dart\.js$/,
- /^flutter_service_worker.js$/
- ];
- if (window.trustedTypes) {
- this.policy = trustedTypes.createPolicy(policyName, {
- createScriptURL: function(url) {
- const parsed = new URL(url, window.location);
- const file = parsed.pathname.split("/").pop();
- const matches = patterns.some((pattern) => pattern.test(file));
- if (matches) {
- return parsed.toString();
- }
- console.error(
- "URL rejected by TrustedTypes policy",
- policyName, ":", url, "(download prevented)");
- }
- });
- }
- }
- }
- /**
- * Handles loading/reloading Flutter's service worker, if configured.
- *
- * @see: https://developers.google.com/web/fundamentals/primers/service-workers
- */
- class FlutterServiceWorkerLoader {
- /**
- * Injects a TrustedTypesPolicy (or undefined if the feature is not supported).
- * @param {TrustedTypesPolicy | undefined} policy
- */
- setTrustedTypesPolicy(policy) {
- this._ttPolicy = policy;
- }
- /**
- * Returns a Promise that resolves when the latest Flutter service worker,
- * configured by `settings` has been loaded and activated.
- *
- * Otherwise, the promise is rejected with an error message.
- * @param {*} settings Service worker settings
- * @returns {Promise} that resolves when the latest serviceWorker is ready.
- */
- loadServiceWorker(settings) {
- if (!("serviceWorker" in navigator) || settings == null) {
- // In the future, settings = null -> uninstall service worker?
- return Promise.reject(
- new Error("Service worker not supported (or configured).")
- );
- }
- const {
- serviceWorkerVersion,
- serviceWorkerUrl = "flutter_service_worker.js?v=" +
- serviceWorkerVersion,
- timeoutMillis = 4000,
- } = settings;
- // Apply the TrustedTypes policy, if present.
- let url = serviceWorkerUrl;
- if (this._ttPolicy != null) {
- url = this._ttPolicy.createScriptURL(url);
- }
- const serviceWorkerActivation = navigator.serviceWorker
- .register(url)
- .then(this._getNewServiceWorker)
- .then(this._waitForServiceWorkerActivation);
- // Timeout race promise
- return timeout(
- serviceWorkerActivation,
- timeoutMillis,
- "prepareServiceWorker"
- );
- }
- /**
- * Returns the latest service worker for the given `serviceWorkerRegistrationPromise`.
- *
- * This might return the current service worker, if there's no new service worker
- * awaiting to be installed/updated.
- *
- * @param {Promise<ServiceWorkerRegistration>} serviceWorkerRegistrationPromise
- * @returns {Promise<ServiceWorker>}
- */
- async _getNewServiceWorker(serviceWorkerRegistrationPromise) {
- const reg = await serviceWorkerRegistrationPromise;
- 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.
- console.debug("Installing/Activating first service worker.");
- return reg.installing || reg.waiting;
- } else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
- // When the app updates the serviceWorkerVersion changes, so we
- // need to ask the service worker to update.
- return reg.update().then((newReg) => {
- console.debug("Updating service worker.");
- return newReg.installing || newReg.waiting || newReg.active;
- });
- } else {
- console.debug("Loading from existing service worker.");
- return reg.active;
- }
- }
- /**
- * Returns a Promise that resolves when the `latestServiceWorker` changes its
- * state to "activated".
- *
- * @param {Promise<ServiceWorker>} latestServiceWorkerPromise
- * @returns {Promise<void>}
- */
- async _waitForServiceWorkerActivation(latestServiceWorkerPromise) {
- const serviceWorker = await latestServiceWorkerPromise;
- if (!serviceWorker || serviceWorker.state == "activated") {
- if (!serviceWorker) {
- return Promise.reject(
- new Error("Cannot activate a null service worker!")
- );
- } else {
- console.debug("Service worker already active.");
- return Promise.resolve();
- }
- }
- return new Promise((resolve, _) => {
- serviceWorker.addEventListener("statechange", () => {
- if (serviceWorker.state == "activated") {
- console.debug("Activated new service worker.");
- resolve();
- }
- });
- });
- }
- }
- /**
- * Handles injecting the main Flutter web entrypoint (main.dart.js), and notifying
- * the user when Flutter is ready, through `didCreateEngineInitializer`.
- *
- * @see https://docs.flutter.dev/development/platform-integration/web/initialization
- */
- class FlutterEntrypointLoader {
- /**
- * Creates a FlutterEntrypointLoader.
- */
- constructor() {
- // Watchdog to prevent injecting the main entrypoint multiple times.
- this._scriptLoaded = false;
- }
- /**
- * Injects a TrustedTypesPolicy (or undefined if the feature is not supported).
- * @param {TrustedTypesPolicy | undefined} policy
- */
- setTrustedTypesPolicy(policy) {
- this._ttPolicy = policy;
- }
- /**
- * Loads flutter main entrypoint, specified by `entrypointUrl`, and calls a
- * user-specified `onEntrypointLoaded` callback with an EngineInitializer
- * object when it's done.
- *
- * @param {*} options
- * @returns {Promise | undefined} that will eventually resolve with an
- * EngineInitializer, or will be rejected with the error caused by the loader.
- * Returns undefined when an `onEntrypointLoaded` callback is supplied in `options`.
- */
- async loadEntrypoint(options) {
- const { entrypointUrl = "main.dart.js", onEntrypointLoaded } =
- options || {};
- return this._loadEntrypoint(entrypointUrl, onEntrypointLoaded);
- }
- /**
- * Resolves the promise created by loadEntrypoint, and calls the `onEntrypointLoaded`
- * function supplied by the user (if needed).
- *
- * Called by Flutter through `_flutter.loader.didCreateEngineInitializer` method,
- * which is bound to the correct instance of the FlutterEntrypointLoader by
- * the FlutterLoader object.
- *
- * @param {Function} engineInitializer @see https://github.com/flutter/engine/blob/main/lib/web_ui/lib/src/engine/js_interop/js_loader.dart#L42
- */
- didCreateEngineInitializer(engineInitializer) {
- if (typeof this._didCreateEngineInitializerResolve === "function") {
- this._didCreateEngineInitializerResolve(engineInitializer);
- // Remove the resolver after the first time, so Flutter Web can hot restart.
- this._didCreateEngineInitializerResolve = null;
- // Make the engine revert to "auto" initialization on hot restart.
- delete _flutter.loader.didCreateEngineInitializer;
- }
- if (typeof this._onEntrypointLoaded === "function") {
- this._onEntrypointLoaded(engineInitializer);
- }
- }
- /**
- * Injects a script tag into the DOM, and configures this loader to be able to
- * handle the "entrypoint loaded" notifications received from Flutter web.
- *
- * @param {string} entrypointUrl the URL of the script that will initialize
- * Flutter.
- * @param {Function} onEntrypointLoaded a callback that will be called when
- * Flutter web notifies this object that the entrypoint is
- * loaded.
- * @returns {Promise | undefined} a Promise that resolves when the entrypoint
- * is loaded, or undefined if `onEntrypointLoaded`
- * is a function.
- */
- _loadEntrypoint(entrypointUrl, onEntrypointLoaded) {
- const useCallback = typeof onEntrypointLoaded === "function";
- if (!this._scriptLoaded) {
- this._scriptLoaded = true;
- const scriptTag = this._createScriptTag(entrypointUrl);
- if (useCallback) {
- // Just inject the script tag, and return nothing; Flutter will call
- // `didCreateEngineInitializer` when it's done.
- console.debug("Injecting <script> tag. Using callback.");
- this._onEntrypointLoaded = onEntrypointLoaded;
- document.body.append(scriptTag);
- } else {
- // Inject the script tag and return a promise that will get resolved
- // with the EngineInitializer object from Flutter when it calls
- // `didCreateEngineInitializer` later.
- return new Promise((resolve, reject) => {
- console.debug(
- "Injecting <script> tag. Using Promises. Use the callback approach instead!"
- );
- this._didCreateEngineInitializerResolve = resolve;
- scriptTag.addEventListener("error", reject);
- document.body.append(scriptTag);
- });
- }
- }
- }
- /**
- * Creates a script tag for the given URL.
- * @param {string} url
- * @returns {HTMLScriptElement}
- */
- _createScriptTag(url) {
- const scriptTag = document.createElement("script");
- scriptTag.type = "application/javascript";
- // Apply TrustedTypes validation, if available.
- let trustedUrl = url;
- if (this._ttPolicy != null) {
- trustedUrl = this._ttPolicy.createScriptURL(url);
- }
- scriptTag.src = trustedUrl;
- return scriptTag;
- }
- }
- /**
- * The public interface of _flutter.loader. Exposes two methods:
- * * loadEntrypoint (which coordinates the default Flutter web loading procedure)
- * * didCreateEngineInitializer (which is called by Flutter to notify that its
- * Engine is ready to be initialized)
- */
- class FlutterLoader {
- /**
- * Initializes the Flutter web app.
- * @param {*} options
- * @returns {Promise?} a (Deprecated) Promise that will eventually resolve
- * with an EngineInitializer, or will be rejected with
- * any error caused by the loader. Or Null, if the user
- * supplies an `onEntrypointLoaded` Function as an option.
- */
- async loadEntrypoint(options) {
- const { serviceWorker, ...entrypoint } = options || {};
- // A Trusted Types policy that is going to be used by the loader.
- const flutterTT = new FlutterTrustedTypesPolicy();
- // The FlutterServiceWorkerLoader instance could be injected as a dependency
- // (and dynamically imported from a module if not present).
- const serviceWorkerLoader = new FlutterServiceWorkerLoader();
- serviceWorkerLoader.setTrustedTypesPolicy(flutterTT.policy);
- await serviceWorkerLoader.loadServiceWorker(serviceWorker).catch(e => {
- // Regardless of what happens with the injection of the SW, the show must go on
- console.warn("Exception while loading service worker:", e);
- });
- // The FlutterEntrypointLoader instance could be injected as a dependency
- // (and dynamically imported from a module if not present).
- const entrypointLoader = new FlutterEntrypointLoader();
- entrypointLoader.setTrustedTypesPolicy(flutterTT.policy);
- // Install the `didCreateEngineInitializer` listener where Flutter web expects it to be.
- this.didCreateEngineInitializer =
- entrypointLoader.didCreateEngineInitializer.bind(entrypointLoader);
- return entrypointLoader.loadEntrypoint(entrypoint);
- }
- }
- _flutter.loader = new FlutterLoader();
- })();
|