forked from mystiq/hydrogen-web
move service worker code in bundle, and support closing sessions
This commit is contained in:
parent
788bce7904
commit
101c7015f2
4 changed files with 161 additions and 46 deletions
41
index.html
41
index.html
|
@ -29,46 +29,5 @@
|
|||
}
|
||||
});
|
||||
</script>
|
||||
<script id="service-worker" type="module">
|
||||
|
||||
function workerMessage(worker, type) {
|
||||
const body = {type, id: Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)};
|
||||
let resolve;
|
||||
const promise = new Promise(r => resolve = r);
|
||||
const onMessage = function(event) {
|
||||
if (event.data.replyTo === body.id) {
|
||||
navigator.serviceWorker.removeEventListener("message", onMessage);
|
||||
resolve(event.data.content);
|
||||
}
|
||||
}
|
||||
navigator.serviceWorker.addEventListener("message", onMessage);
|
||||
worker.postMessage(body);
|
||||
return promise;
|
||||
}
|
||||
|
||||
if('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('sw.js').then(function(registration) {
|
||||
console.log("Service Worker registered");
|
||||
async function tryActivateUpdate() {
|
||||
if (registration.waiting && registration.active) {
|
||||
const version = await workerMessage(registration.waiting, "version");
|
||||
if (confirm(`Version ${version.version} (${version.buildHash}) is ready to install. Apply now?`)) {
|
||||
registration.waiting.postMessage({type: "skipWaiting"}); // will trigger controllerchange event
|
||||
}
|
||||
}
|
||||
}
|
||||
tryActivateUpdate();
|
||||
registration.onupdatefound = function() {
|
||||
const newWorker = registration.installing;
|
||||
newWorker.onstatechange = function() {
|
||||
tryActivateUpdate();
|
||||
}
|
||||
};
|
||||
});
|
||||
navigator.serviceWorker.addEventListener("controllerchange", function() {
|
||||
document.location.reload();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -140,6 +140,7 @@ async function buildHtml(doc, version, globalHash, modernOnly, assets) {
|
|||
});
|
||||
const pathsJSON = JSON.stringify({
|
||||
worker: assets.has("worker.js") ? assets.resolve(`worker.js`) : null,
|
||||
serviceWorker: "sw.js",
|
||||
olm: {
|
||||
wasm: assets.resolve("olm.wasm"),
|
||||
legacyBundle: assets.resolve("olm_legacy.js"),
|
||||
|
@ -156,7 +157,6 @@ async function buildHtml(doc, version, globalHash, modernOnly, assets) {
|
|||
);
|
||||
}
|
||||
doc("script#main").replaceWith(mainScripts.join(""));
|
||||
doc("script#service-worker").attr("type", "text/javascript");
|
||||
|
||||
const versionScript = doc("script#version");
|
||||
versionScript.attr("type", "text/javascript");
|
||||
|
|
14
src/main.js
14
src/main.js
|
@ -25,6 +25,7 @@ import {RootViewModel} from "./domain/RootViewModel.js";
|
|||
import {createNavigation, createRouter} from "./domain/navigation/index.js";
|
||||
import {RootView} from "./ui/web/RootView.js";
|
||||
import {Clock} from "./ui/web/dom/Clock.js";
|
||||
import {ServiceWorkerHandler} from "./ui/web/dom/ServiceWorkerHandler.js";
|
||||
import {History} from "./ui/web/dom/History.js";
|
||||
import {OnlineStatus} from "./ui/web/dom/OnlineStatus.js";
|
||||
import {CryptoDriver} from "./ui/web/dom/CryptoDriver.js";
|
||||
|
@ -106,8 +107,14 @@ export async function main(container, paths, legacyExtras) {
|
|||
} else {
|
||||
request = xhrRequest;
|
||||
}
|
||||
const navigation = createNavigation();
|
||||
const sessionInfoStorage = new SessionInfoStorage("hydrogen_sessions_v1");
|
||||
const storageFactory = new StorageFactory();
|
||||
let serviceWorkerHandler;
|
||||
if (paths.serviceWorker && "serviceWorker" in navigator) {
|
||||
serviceWorkerHandler = new ServiceWorkerHandler({navigation});
|
||||
serviceWorkerHandler.registerAndStart(paths.serviceWorker);
|
||||
}
|
||||
const storageFactory = new StorageFactory(serviceWorkerHandler);
|
||||
|
||||
const olmPromise = loadOlm(paths.olm);
|
||||
// if wasm is not supported, we'll want
|
||||
|
@ -116,8 +123,6 @@ export async function main(container, paths, legacyExtras) {
|
|||
if (!window.WebAssembly) {
|
||||
workerPromise = loadOlmWorker(paths);
|
||||
}
|
||||
|
||||
const navigation = createNavigation();
|
||||
const urlRouter = createRouter({navigation, history: new History()});
|
||||
urlRouter.attach();
|
||||
|
||||
|
@ -139,7 +144,8 @@ export async function main(container, paths, legacyExtras) {
|
|||
storageFactory,
|
||||
clock,
|
||||
urlRouter,
|
||||
navigation
|
||||
navigation,
|
||||
updateService: serviceWorkerHandler
|
||||
});
|
||||
window.__brawlViewModel = vm;
|
||||
await vm.load();
|
||||
|
|
150
src/ui/web/dom/ServiceWorkerHandler.js
Normal file
150
src/ui/web/dom/ServiceWorkerHandler.js
Normal file
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
// 3 (imaginary) interfaces are implemented here:
|
||||
// - OfflineAvailability (done by registering the sw)
|
||||
// - UpdateService (see checkForUpdate method, and should also emit events rather than showing confirm dialog here)
|
||||
// - ConcurrentAccessBlocker (see preventConcurrentSessionAccess method)
|
||||
export class ServiceWorkerHandler {
|
||||
constructor({navigation}) {
|
||||
this._waitingForReply = new Map();
|
||||
this._messageIdCounter = 0;
|
||||
this._registration = null;
|
||||
this._navigation = navigation;
|
||||
this._registrationPromise = null;
|
||||
}
|
||||
|
||||
registerAndStart(path) {
|
||||
this._registrationPromise = (async () => {
|
||||
navigator.serviceWorker.addEventListener("message", this);
|
||||
navigator.serviceWorker.addEventListener("controllerchange", this);
|
||||
this._registration = await navigator.serviceWorker.register(path);
|
||||
this._registrationPromise = null;
|
||||
console.log("Service Worker registered");
|
||||
this._registration.addEventListener("updatefound", this);
|
||||
this._tryActivateUpdate();
|
||||
})();
|
||||
}
|
||||
|
||||
_onMessage(event) {
|
||||
const {data} = event;
|
||||
const replyTo = data.replyTo;
|
||||
if (replyTo) {
|
||||
const resolve = this._waitingForReply.get(replyTo);
|
||||
if (resolve) {
|
||||
this._waitingForReply.delete(replyTo);
|
||||
resolve(data.payload);
|
||||
}
|
||||
}
|
||||
if (data.type === "closeSession") {
|
||||
const {sessionId} = data.payload;
|
||||
this._closeSessionIfNeeded(sessionId).finally(() => {
|
||||
event.source.postMessage({replyTo: data.id});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_closeSessionIfNeeded(sessionId) {
|
||||
const currentSession = this._navigation.path.get("session");
|
||||
if (sessionId && currentSession?.value === sessionId) {
|
||||
return new Promise(resolve => {
|
||||
const unsubscribe = this._navigation.pathObservable.subscribe(path => {
|
||||
const session = path.get("session");
|
||||
if (!session || session.value !== sessionId) {
|
||||
unsubscribe();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
this._navigation.push("session");
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
async _tryActivateUpdate() {
|
||||
if (this._registration.waiting && this._registration.active) {
|
||||
this._registration.waiting.removeEventListener("statechange", this);
|
||||
const version = await this._sendAndWaitForReply("version", null, this._registration.waiting);
|
||||
if (confirm(`Version ${version.version} (${version.buildHash}) is ready to install. Apply now?`)) {
|
||||
this._registration.waiting.postMessage({type: "skipWaiting"}); // will trigger controllerchange event
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleEvent(event) {
|
||||
switch (event.type) {
|
||||
case "message":
|
||||
this._onMessage(event);
|
||||
break;
|
||||
case "updatefound":
|
||||
this._registration.installing.addEventListener("statechange", this);
|
||||
this._tryActivateUpdate();
|
||||
break;
|
||||
case "statechange":
|
||||
this._tryActivateUpdate();
|
||||
break;
|
||||
case "controllerchange":
|
||||
// active service worker changed,
|
||||
// refresh, so we can get all assets
|
||||
// (and not some if we would not refresh)
|
||||
// up to date from it
|
||||
document.location.reload();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async _send(type, payload, worker = undefined) {
|
||||
if (this._registrationPromise) {
|
||||
await this._registrationPromise;
|
||||
}
|
||||
if (!worker) {
|
||||
worker = this._registration.active;
|
||||
}
|
||||
worker.postMessage({type, payload});
|
||||
}
|
||||
|
||||
async _sendAndWaitForReply(type, payload, worker = undefined) {
|
||||
if (this._registrationPromise) {
|
||||
await this._registrationPromise;
|
||||
}
|
||||
if (!worker) {
|
||||
worker = this._registration.active;
|
||||
}
|
||||
this._messageIdCounter += 1;
|
||||
const id = this._messageIdCounter;
|
||||
const promise = new Promise(resolve => {
|
||||
this._waitingForReply.set(id, resolve);
|
||||
});
|
||||
worker.postMessage({type, id, payload});
|
||||
return await promise;
|
||||
}
|
||||
|
||||
async checkForUpdate() {
|
||||
if (this._registrationPromise) {
|
||||
await this._registrationPromise;
|
||||
}
|
||||
this._registration.update();
|
||||
}
|
||||
|
||||
async preventConcurrentSessionAccess(sessionId) {
|
||||
// don't block if we didn't manage to install service worker
|
||||
if (!this._registration) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return this._sendAndWaitForReply("closeSession", {sessionId});
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue