All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gwt.material.design.client.pwa.serviceworker.ServiceWorkerManager Maven / Gradle / Ivy

There is a newer version: 2.8.3
Show newest version
/*
 * #%L
 * GwtMaterial
 * %%
 * Copyright (C) 2015 - 2017 GwtMaterialDesign
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
package gwt.material.design.client.pwa.serviceworker;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.user.client.Window;
import gwt.material.design.client.pwa.PwaManager;
import gwt.material.design.client.pwa.base.PwaFeature;
import gwt.material.design.client.pwa.serviceworker.constants.ServiceWorkerMessage;
import gwt.material.design.client.pwa.serviceworker.constants.State;
import gwt.material.design.client.js.Navigator;
import gwt.material.design.client.pwa.serviceworker.js.ServiceWorker;
import gwt.material.design.client.pwa.serviceworker.js.ServiceWorkerContainer;
import gwt.material.design.client.pwa.serviceworker.js.ServiceWorkerRegistration;
import gwt.material.design.client.pwa.serviceworker.network.NetworkStatusManager;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import java.util.stream.Collectors;

/**
 * An implementation of Service Worker to manage its lifecycle
 * For more references see Service Worker Lifecycle
 *
 * @author kevzlou7979
 */
public class ServiceWorkerManager implements ServiceWorkerLifecycle, PwaFeature {

    private static final Logger logger = Logger.getLogger(ServiceWorkerManager.class.getSimpleName());

    private PwaManager manager;
    private String resource;
    private ServiceWorkerRegistration registration;

    private DefaultServiceWorkerPlugin defaultPlugin = new DefaultServiceWorkerPlugin(this);
    private Map, ServiceWorkerPlugin> plugins = new LinkedHashMap();
    private NetworkStatusManager networkStatusManager = new NetworkStatusManager();

    protected ServiceWorkerManager() {}

    public ServiceWorkerManager(PwaManager manager, String resource) {
        this(resource);
        this.manager = manager;
    }

    public ServiceWorkerManager(String resource) {
        assert resource != null : "Cannot have a null pwa resource.";
        this.resource = resource;
    }

    @Override
    public void load() {
        setupRegistration();

        networkStatusManager.load();
        networkStatusManager.addNetworkStatusChangeEvent(event -> {
            if (event.isOnline()) {
                onOnline(new ServiceEvent());
            } else {
                onOffline(new ServiceEvent());
            }
        });
    }

    @Override
    public void unload() {
        if (registration != null) {
            registration.unregister();
        }

        networkStatusManager.unload();
    }

    @Override
    public void reload() {
        unload();
        load();
    }

    /**
     * Initial setup of the service worker registration.
     */
    protected void setupRegistration() {
        if (isServiceWorkerSupported()) {
            Navigator.serviceWorker.register(getResource()).then(object -> {
                logger.info("Service worker has been successfully registered");
                registration = (ServiceWorkerRegistration) object;

                onRegistered(new ServiceEvent(), registration);

                // Observe service worker lifecycle
                observeLifecycle(registration);

                // Setup Service Worker events events
                setupOnControllerChangeEvent();
                setupOnMessageEvent();
                setupOnErrorEvent();
                return null;
            }, error -> {
                logger.info("ServiceWorker registration failed: " + error);
                return null;
            });
        } else {
            logger.info("Service worker is not supported by this browser.");
        }
    }

    protected void setupOnErrorEvent() {
        getServiceWorkerContainer().onerror = (e) -> {
            onError(new ServiceEvent(), "");
            return true;
        };
    }

    /**
     * The oncontrollerchange property of the ServiceWorkerContainer interface is an event handler
     * fired whenever a controllerchange event occurs — when the document's associated
     * ServiceWorkerRegistration acquires a new ServiceWorkerRegistration.active worker.
     */
    protected void setupOnControllerChangeEvent() {
        Navigator.serviceWorker.oncontrollerchange = e -> {
            onControllerChange(new ServiceEvent());
            return true;
        };
    }

    /**
     * Will listen to any broadcast messages from the service worker
     */
    protected void setupOnMessageEvent() {
        Navigator.serviceWorker.onmessage = e -> {
            boolean failing = false;
            if (e.data != null && e.data instanceof String) {
                switch (e.data.toString()) {
                    case ServiceWorkerMessage.FAILING_SERVER:
                        failing = true;
                        break;
                }
            }

            if (failing) {
                onServerFailing(new ServiceEvent());
            } else {
                onMessageReceived(new ServiceEvent(), e.data);
            }

            return true;
        };
    }

    /**
     * Will listen and observe to service worker life cycle. This method also tracks if there are incoming service worker.
     * @see Service Worker Lifecycles
     * @param registration - The Service Worker Registration
     */
    protected void observeLifecycle(ServiceWorkerRegistration registration) {
        // Will listen to Service Worker lifecycle
        if (registration.installing != null) {
            registration.onupdatefound = event -> {
                onStateChange(registration.installing);
                return true;
            };
        }

        // Will check if there's a waiting service worker to be installed from the current registration
        // If any then fire {@link #onNewServiceWorkerFound}
        if (registration.waiting != null) {
            onNewServiceWorkerFound(new ServiceEvent(), registration.waiting);
        }
    }

    /**
     * Will listen to any Service worker's {@link State} changes.
     */
    protected void onStateChange(ServiceWorker serviceWorker) {
        serviceWorker.onstatechange = e -> {
            State state = State.fromStyleName(serviceWorker.state);
            switch (state) {
                case INSTALLING:
                    onInstalling(new ServiceEvent());
                    break;
                case INSTALLED:
                    onInstalled(new ServiceEvent());
                    break;
                case ACTIVATING:
                    onActivating(new ServiceEvent());
                    break;
                case ACTIVATED:
                    onActivated(new ServiceEvent());
                    break;
                case REDUNDANT:
                    onRedundant(new ServiceEvent());
                    break;
            }
            return true;
        };
    }

    /**
     * Will set the polling request interval in milliseconds for new Service Worker instance.
     *
     * @param interval Interval must be in milliseconds and must be at least 1000ms.
     */
    public void setPollingInterval(int interval) {
        if (interval >= 1000) {
            Scheduler.get().scheduleFixedDelay(() -> {
                if (registration != null) {
                    registration.update();
                }
                return true;
            }, interval);
        } else {
            logger.warning("Polling interval must be at least 1000ms to perform the request.");
        }
    }

    /**
     * Checks the server for an updated version of the service worker without consulting caches.
     */
    public void update() {
        registration.update();
    }

    /**
     * Get the Service Worker Registration
     */
    public ServiceWorkerRegistration getServiceWorkerRegistration() {
        return registration;
    }

    public ServiceWorkerContainer getServiceWorkerContainer() {
        return Navigator.serviceWorker;
    }

    /**
     * Get the active Service Worker from the registration stack.
     */
    public ServiceWorker getServiceWorker() {
        return registration.active;
    }

    /**
     * Forces the waiting service worker to become the active service worker.
     */
    public void skipWaiting(ServiceWorker serviceWorker) {
        serviceWorker.postMessage("skipWaiting");
        Window.Location.reload();
    }

    /**
     * Will post a message to service-worker.js file with predefined data message.
     * It requires a command and object pattern to be extracted on the service worker.
     */
    public void postMessage(ServiceWorker serviceWorker, Object message) {
        serviceWorker.postMessage(message);
    }

    public void postMessage(Object message) {
        getServiceWorker().postMessage(message);
    }

    /**
     * Will detect if your browser supports service worker.
     *
     * @see Browser Support
     */
    public boolean isServiceWorkerSupported() {
        return Navigator.serviceWorker != null;
    }

    /**
     * Will add a {@link ServiceWorkerPlugin} for customized implementation
     * i.e Firebase Push Notification plugins
     */
    public void addPlugin(ServiceWorkerPlugin plugin) {
        plugin.setServiceWorkerManager(this);
        plugins.put(plugin.getClass(), plugin);
    }

    public void removePlugin(ServiceWorkerPlugin plugin) {
        plugin.setServiceWorkerManager(null);
        plugins.remove(plugin.getClass());
    }

    /**
     * Will set if we want to use the default plugin or not
     */
    public void useDefaultPlugin(boolean use) {
        if (use) {
            if (defaultPlugin == null) {
                defaultPlugin = new DefaultServiceWorkerPlugin(this);
            }
        } else {
            defaultPlugin = null;
        }
    }

    public boolean isUsingDefaultPlugin() {
        return defaultPlugin != null;
    }

    public List getPlugins() {
        return plugins.values().stream().collect(Collectors.toList());
    }

    @SuppressWarnings("unchecked")
    public  T getPlugin(Class plugin) {
        return (T) plugins.get(plugin);
    }

    @Override
    public String getResource() {
        return resource;
    }

    @Override
    public PwaManager getManager() {
        return manager;
    }

    @Override
    public boolean onRegistered(ServiceEvent event, ServiceWorkerRegistration registration) {
        GWT.log("Service Worker is registered");

        for (ServiceWorkerPlugin plugin : plugins.values()) {
            if(plugin.onRegistered(event, registration) || event.isStopPropagation()) {
                break; // Stop propagation
            }
        }

        if (isUsingDefaultPlugin() && !event.isPreventDefault()) {
            defaultPlugin.onRegistered(event, registration);
        }
        return true;
    }

    @Override
    public boolean onInstalling(ServiceEvent event) {
        GWT.log("Service worker is installing");

        for (ServiceWorkerPlugin plugin : plugins.values()) {
            if(plugin.onInstalling(event) || event.isStopPropagation()) {
                break; // Stop propagation
            }
        }

        if (isUsingDefaultPlugin() && !event.isPreventDefault()) {
            defaultPlugin.onInstalling(event);
        }
        return false;
    }

    @Override
    public boolean onInstalled(ServiceEvent event) {
        GWT.log("Service worker is installed");

        for (ServiceWorkerPlugin plugin : plugins.values()) {
            if(plugin.onInstalled(event) || event.isStopPropagation()) {
                break; // Stop propagation
            }
        }

        if (isUsingDefaultPlugin() && !event.isPreventDefault()) {
            defaultPlugin.onInstalled(event);
        }
        return false;
    }

    @Override
    public boolean onActivating(ServiceEvent event) {
        GWT.log("Service worker is activating");

        for (ServiceWorkerPlugin plugin : plugins.values()) {
            if(plugin.onActivating(event) || event.isStopPropagation()) {
                break; // Stop propagation
            }
        }

        if (isUsingDefaultPlugin() && !event.isPreventDefault()) {
            defaultPlugin.onActivating(event);
        }
        return false;
    }

    @Override
    public boolean onActivated(ServiceEvent event) {
        GWT.log("Service worker is activated");

        for (ServiceWorkerPlugin plugin : plugins.values()) {
            if(plugin.onActivated(event) || event.isStopPropagation()) {
                break; // Stop propagation
            }
        }

        if (isUsingDefaultPlugin() && !event.isPreventDefault()) {
            defaultPlugin.onActivated(event);
        }
        return false;
    }

    @Override
    public boolean onRedundant(ServiceEvent event) {
        for (ServiceWorkerPlugin plugin : plugins.values()) {
            if(plugin.onRedundant(event) || event.isStopPropagation()) {
                break; // Stop propagation
            }
        }

        if (isUsingDefaultPlugin() && !event.isPreventDefault()) {
            defaultPlugin.onRedundant(event);
        }
        return false;
    }

    @Override
    public boolean onControllerChange(ServiceEvent event) {
        for (ServiceWorkerPlugin plugin : plugins.values()) {
            if(plugin.onControllerChange(event) || event.isStopPropagation()) {
                break; // Stop propagation
            }
        }

        if (isUsingDefaultPlugin() && !event.isPreventDefault()) {
            defaultPlugin.onControllerChange(event);
        }
        return false;
    }

    @Override
    public boolean onNewServiceWorkerFound(ServiceEvent event, ServiceWorker serviceWorker) {
        for (ServiceWorkerPlugin plugin : plugins.values()) {
            if(plugin.onNewServiceWorkerFound(event, serviceWorker) || event.isStopPropagation()) {
                break; // Stop propagation
            }
        }

        if (isUsingDefaultPlugin() && !event.isPreventDefault()) {
            defaultPlugin.onNewServiceWorkerFound(event, serviceWorker);
        }
        return false;
    }

    @Override
    public boolean onOnline(ServiceEvent event) {
        GWT.log("Network Status is now online");

        for (ServiceWorkerPlugin plugin : plugins.values()) {
            if(plugin.onOnline(event) || event.isStopPropagation()) {
                break; // Stop propagation
            }
        }

        if (isUsingDefaultPlugin() && !event.isPreventDefault()) {
            defaultPlugin.onOnline(event);
        }
        return false;
    }

    @Override
    public boolean onOffline(ServiceEvent event) {
        GWT.log("Network Status is now offline");

        for (ServiceWorkerPlugin plugin : plugins.values()) {
            if(plugin.onOffline(event) || event.isStopPropagation()) {
                break; // Stop propagation
            }
        }

        if (isUsingDefaultPlugin() && !event.isPreventDefault()) {
            defaultPlugin.onOffline(event);
        }
        return false;
    }

    @Override
    public boolean onServerFailing(ServiceEvent event) {
        GWT.log("Can't connect to the server at the moment.", new RuntimeException());

        for (ServiceWorkerPlugin plugin : plugins.values()) {
            if(plugin.onServerFailing(event) || event.isStopPropagation()) {
                break; // Stop propagation
            }
        }

        if (isUsingDefaultPlugin() && !event.isPreventDefault()) {
            defaultPlugin.onServerFailing(event);
        }
        return false;
    }

    @Override
    public boolean onMessageReceived(ServiceEvent event, Object data) {
        GWT.log("Message received: " + data);

        for (ServiceWorkerPlugin plugin : plugins.values()) {
            if(plugin.onMessageReceived(event, data) || event.isStopPropagation()) {
                break; // Stop propagation
            }
        }

        if (isUsingDefaultPlugin() && !event.isPreventDefault()) {
            defaultPlugin.onMessageReceived(event, data);
        }
        return false;
    }

    @Override
    public boolean onError(ServiceEvent event, String message) {
        for (ServiceWorkerPlugin plugin : plugins.values()) {
            if(plugin.onError(event, message) || event.isStopPropagation()) {
                break; // Stop propagation
            }
        }

        if (isUsingDefaultPlugin() && !event.isPreventDefault()) {
            defaultPlugin.onError(event, message);
        }
        return false;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy