PWA service workers (#1319)
* PWA services worker - [x] Register service worker of PWA to install WorkAdventure application on desktop and mobile - [x] Create webpage specifique for PWA - [ ] Add register service to save and redirect on a card - [ ] Add possibilities to install PWA for one World (with register token if existing) * Finish PWA strategy to load last map visited * Fix feedback @Kharhamel * Fix feedback @Kharhamel
This commit is contained in:
parent
4b4356e7ff
commit
2a1af2a131
1
front/dist/index.tmpl.html
vendored
1
front/dist/index.tmpl.html
vendored
@ -34,6 +34,7 @@
|
|||||||
<title>WorkAdventure</title>
|
<title>WorkAdventure</title>
|
||||||
</head>
|
</head>
|
||||||
<body id="body" style="margin: 0; background-color: #000">
|
<body id="body" style="margin: 0; background-color: #000">
|
||||||
|
|
||||||
<div class="main-container" id="main-container">
|
<div class="main-container" id="main-container">
|
||||||
<!-- Create the editor container -->
|
<!-- Create the editor container -->
|
||||||
<div id="game" class="game">
|
<div id="game" class="game">
|
||||||
|
62
front/dist/resources/service-worker.html
vendored
Normal file
62
front/dist/resources/service-worker.html
vendored
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport"
|
||||||
|
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
|
||||||
|
<!-- TRACK CODE -->
|
||||||
|
<!-- END TRACK CODE -->
|
||||||
|
|
||||||
|
<link rel="apple-touch-icon" sizes="57x57" href="/static/images/favicons/apple-icon-57x57.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="60x60" href="/static/images/favicons/apple-icon-60x60.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="72x72" href="/static/images/favicons/apple-icon-72x72.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="76x76" href="/static/images/favicons/apple-icon-76x76.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="114x114" href="/static/images/favicons/apple-icon-114x114.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="120x120" href="/static/images/favicons/apple-icon-120x120.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="144x144" href="/static/images/favicons/apple-icon-144x144.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="152x152" href="/static/images/favicons/apple-icon-152x152.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/static/images/favicons/apple-icon-180x180.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="192x192" href="/static/images/favicons/android-icon-192x192.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/static/images/favicons/favicon-32x32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="96x96" href="/static/images/favicons/favicon-96x96.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="/static/images/favicons/favicon-16x16.png">
|
||||||
|
<meta name="msapplication-TileColor" content="#000000">
|
||||||
|
<meta name="msapplication-TileImage" content="/static/images/favicons/ms-icon-144x144.png">
|
||||||
|
<meta name="theme-color" content="#000000">
|
||||||
|
|
||||||
|
<title>WorkAdventure PWA</title>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body{
|
||||||
|
font-family: Whitney, Lato, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||||
|
}
|
||||||
|
body img{
|
||||||
|
position: absolute;
|
||||||
|
top: calc( 50% - 25px);
|
||||||
|
height: 59px;
|
||||||
|
width: 307px;
|
||||||
|
left: calc( 50% - 150px);
|
||||||
|
}
|
||||||
|
body p{
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
top: calc( 50% + 50px);
|
||||||
|
left: calc( 50% - 150px);
|
||||||
|
height: 59px;
|
||||||
|
width: 307px;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<img src="/static/images/logo.png" alt="WorkAdventure logo"/>
|
||||||
|
<p>Charging your workspace ...</p>
|
||||||
|
<script>
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location = localStorage.getItem('lastRoomUrl');
|
||||||
|
}, 4000);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
12
front/dist/resources/service-worker.js
vendored
12
front/dist/resources/service-worker.js
vendored
@ -48,6 +48,14 @@ self.addEventListener('fetch', function(event) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
self.addEventListener('activate', function(event) {
|
self.addEventListener('wait', function(event) {
|
||||||
//TODO activate service worker
|
//TODO wait
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('update', function(event) {
|
||||||
|
//TODO update
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('beforeinstallprompt', (e) => {
|
||||||
|
//TODO change prompt
|
||||||
});
|
});
|
@ -128,11 +128,12 @@
|
|||||||
"type": "image\/png"
|
"type": "image\/png"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"start_url": "/",
|
"start_url": "/resources/service-worker.html",
|
||||||
"background_color": "#000000",
|
"background_color": "#000000",
|
||||||
"display_override": ["window-control-overlay", "minimal-ui"],
|
"display_override": ["window-control-overlay", "minimal-ui"],
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"scope": "/",
|
"orientation": "portrait-primary",
|
||||||
|
"scope": "/resources/",
|
||||||
"lang": "en",
|
"lang": "en",
|
||||||
"theme_color": "#000000",
|
"theme_color": "#000000",
|
||||||
"shortcuts": [
|
"shortcuts": [
|
||||||
|
@ -6,6 +6,7 @@ import { GameConnexionTypes, urlManager } from "../Url/UrlManager";
|
|||||||
import { localUserStore } from "./LocalUserStore";
|
import { localUserStore } from "./LocalUserStore";
|
||||||
import { CharacterTexture, LocalUser } from "./LocalUser";
|
import { CharacterTexture, LocalUser } from "./LocalUser";
|
||||||
import { Room } from "./Room";
|
import { Room } from "./Room";
|
||||||
|
import { _ServiceWorker } from "../Network/ServiceWorker";
|
||||||
|
|
||||||
class ConnectionManager {
|
class ConnectionManager {
|
||||||
private localUser!: LocalUser;
|
private localUser!: LocalUser;
|
||||||
@ -14,6 +15,8 @@ class ConnectionManager {
|
|||||||
private reconnectingTimeout: NodeJS.Timeout | null = null;
|
private reconnectingTimeout: NodeJS.Timeout | null = null;
|
||||||
private _unloading: boolean = false;
|
private _unloading: boolean = false;
|
||||||
|
|
||||||
|
private serviceWorker?: _ServiceWorker;
|
||||||
|
|
||||||
get unloading() {
|
get unloading() {
|
||||||
return this._unloading;
|
return this._unloading;
|
||||||
}
|
}
|
||||||
@ -30,6 +33,8 @@ class ConnectionManager {
|
|||||||
public async initGameConnexion(): Promise<Room> {
|
public async initGameConnexion(): Promise<Room> {
|
||||||
const connexionType = urlManager.getGameConnexionType();
|
const connexionType = urlManager.getGameConnexionType();
|
||||||
this.connexionType = connexionType;
|
this.connexionType = connexionType;
|
||||||
|
|
||||||
|
let room: Room | null = null;
|
||||||
if (connexionType === GameConnexionTypes.register) {
|
if (connexionType === GameConnexionTypes.register) {
|
||||||
const organizationMemberToken = urlManager.getOrganizationToken();
|
const organizationMemberToken = urlManager.getOrganizationToken();
|
||||||
const data = await Axios.post(`${PUSHER_URL}/register`, { organizationMemberToken }).then(
|
const data = await Axios.post(`${PUSHER_URL}/register`, { organizationMemberToken }).then(
|
||||||
@ -40,7 +45,7 @@ class ConnectionManager {
|
|||||||
|
|
||||||
const roomUrl = data.roomUrl;
|
const roomUrl = data.roomUrl;
|
||||||
|
|
||||||
const room = await Room.createRoom(
|
room = await Room.createRoom(
|
||||||
new URL(
|
new URL(
|
||||||
window.location.protocol +
|
window.location.protocol +
|
||||||
"//" +
|
"//" +
|
||||||
@ -51,7 +56,6 @@ class ConnectionManager {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
urlManager.pushRoomIdToUrl(room);
|
urlManager.pushRoomIdToUrl(room);
|
||||||
return Promise.resolve(room);
|
|
||||||
} else if (
|
} else if (
|
||||||
connexionType === GameConnexionTypes.organization ||
|
connexionType === GameConnexionTypes.organization ||
|
||||||
connexionType === GameConnexionTypes.anonymous ||
|
connexionType === GameConnexionTypes.anonymous ||
|
||||||
@ -90,7 +94,7 @@ class ConnectionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//get detail map for anonymous login and set texture in local storage
|
//get detail map for anonymous login and set texture in local storage
|
||||||
const room = await Room.createRoom(new URL(roomPath));
|
room = await Room.createRoom(new URL(roomPath));
|
||||||
if (room.textures != undefined && room.textures.length > 0) {
|
if (room.textures != undefined && room.textures.length > 0) {
|
||||||
//check if texture was changed
|
//check if texture was changed
|
||||||
if (localUser.textures.length === 0) {
|
if (localUser.textures.length === 0) {
|
||||||
@ -107,10 +111,13 @@ class ConnectionManager {
|
|||||||
this.localUser = localUser;
|
this.localUser = localUser;
|
||||||
localUserStore.saveUser(localUser);
|
localUserStore.saveUser(localUser);
|
||||||
}
|
}
|
||||||
return Promise.resolve(room);
|
}
|
||||||
|
if (room == undefined) {
|
||||||
|
return Promise.reject(new Error("Invalid URL"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject(new Error("Invalid URL"));
|
this.serviceWorker = new _ServiceWorker();
|
||||||
|
return Promise.resolve(room);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async verifyToken(token: string): Promise<void> {
|
private async verifyToken(token: string): Promise<void> {
|
||||||
@ -148,6 +155,7 @@ class ConnectionManager {
|
|||||||
viewport,
|
viewport,
|
||||||
companion
|
companion
|
||||||
);
|
);
|
||||||
|
|
||||||
connection.onConnectError((error: object) => {
|
connection.onConnectError((error: object) => {
|
||||||
console.log("An error occurred while connecting to socket server. Retrying");
|
console.log("An error occurred while connecting to socket server. Retrying");
|
||||||
reject(error);
|
reject(error);
|
||||||
@ -166,6 +174,9 @@ class ConnectionManager {
|
|||||||
});
|
});
|
||||||
|
|
||||||
connection.onConnect((connect: OnConnectInterface) => {
|
connection.onConnect((connect: OnConnectInterface) => {
|
||||||
|
//save last room url connected
|
||||||
|
localUserStore.setLastRoomUrl(roomUrl);
|
||||||
|
|
||||||
resolve(connect);
|
resolve(connect);
|
||||||
});
|
});
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
|
@ -1,60 +1,61 @@
|
|||||||
import {areCharacterLayersValid, isUserNameValid, LocalUser} from "./LocalUser";
|
import { areCharacterLayersValid, isUserNameValid, LocalUser } from "./LocalUser";
|
||||||
|
|
||||||
const playerNameKey = 'playerName';
|
const playerNameKey = "playerName";
|
||||||
const selectedPlayerKey = 'selectedPlayer';
|
const selectedPlayerKey = "selectedPlayer";
|
||||||
const customCursorPositionKey = 'customCursorPosition';
|
const customCursorPositionKey = "customCursorPosition";
|
||||||
const characterLayersKey = 'characterLayers';
|
const characterLayersKey = "characterLayers";
|
||||||
const companionKey = 'companion';
|
const companionKey = "companion";
|
||||||
const gameQualityKey = 'gameQuality';
|
const gameQualityKey = "gameQuality";
|
||||||
const videoQualityKey = 'videoQuality';
|
const videoQualityKey = "videoQuality";
|
||||||
const audioPlayerVolumeKey = 'audioVolume';
|
const audioPlayerVolumeKey = "audioVolume";
|
||||||
const audioPlayerMuteKey = 'audioMute';
|
const audioPlayerMuteKey = "audioMute";
|
||||||
const helpCameraSettingsShown = 'helpCameraSettingsShown';
|
const helpCameraSettingsShown = "helpCameraSettingsShown";
|
||||||
const fullscreenKey = 'fullscreen';
|
const fullscreenKey = "fullscreen";
|
||||||
|
const lastRoomUrl = "lastRoomUrl";
|
||||||
|
|
||||||
class LocalUserStore {
|
class LocalUserStore {
|
||||||
saveUser(localUser: LocalUser) {
|
saveUser(localUser: LocalUser) {
|
||||||
localStorage.setItem('localUser', JSON.stringify(localUser));
|
localStorage.setItem("localUser", JSON.stringify(localUser));
|
||||||
}
|
}
|
||||||
getLocalUser(): LocalUser|null {
|
getLocalUser(): LocalUser | null {
|
||||||
const data = localStorage.getItem('localUser');
|
const data = localStorage.getItem("localUser");
|
||||||
return data ? JSON.parse(data) : null;
|
return data ? JSON.parse(data) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
setName(name:string): void {
|
setName(name: string): void {
|
||||||
localStorage.setItem(playerNameKey, name);
|
localStorage.setItem(playerNameKey, name);
|
||||||
}
|
}
|
||||||
getName(): string|null {
|
getName(): string | null {
|
||||||
const value = localStorage.getItem(playerNameKey) || '';
|
const value = localStorage.getItem(playerNameKey) || "";
|
||||||
return isUserNameValid(value) ? value : null;
|
return isUserNameValid(value) ? value : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
setPlayerCharacterIndex(playerCharacterIndex: number): void {
|
setPlayerCharacterIndex(playerCharacterIndex: number): void {
|
||||||
localStorage.setItem(selectedPlayerKey, ''+playerCharacterIndex);
|
localStorage.setItem(selectedPlayerKey, "" + playerCharacterIndex);
|
||||||
}
|
}
|
||||||
getPlayerCharacterIndex(): number {
|
getPlayerCharacterIndex(): number {
|
||||||
return parseInt(localStorage.getItem(selectedPlayerKey) || '');
|
return parseInt(localStorage.getItem(selectedPlayerKey) || "");
|
||||||
}
|
}
|
||||||
|
|
||||||
setCustomCursorPosition(activeRow:number, selectedLayers: number[]): void {
|
setCustomCursorPosition(activeRow: number, selectedLayers: number[]): void {
|
||||||
localStorage.setItem(customCursorPositionKey, JSON.stringify({activeRow, selectedLayers}));
|
localStorage.setItem(customCursorPositionKey, JSON.stringify({ activeRow, selectedLayers }));
|
||||||
}
|
}
|
||||||
getCustomCursorPosition(): {activeRow:number, selectedLayers:number[]}|null {
|
getCustomCursorPosition(): { activeRow: number; selectedLayers: number[] } | null {
|
||||||
return JSON.parse(localStorage.getItem(customCursorPositionKey) || "null");
|
return JSON.parse(localStorage.getItem(customCursorPositionKey) || "null");
|
||||||
}
|
}
|
||||||
|
|
||||||
setCharacterLayers(layers: string[]): void {
|
setCharacterLayers(layers: string[]): void {
|
||||||
localStorage.setItem(characterLayersKey, JSON.stringify(layers));
|
localStorage.setItem(characterLayersKey, JSON.stringify(layers));
|
||||||
}
|
}
|
||||||
getCharacterLayers(): string[]|null {
|
getCharacterLayers(): string[] | null {
|
||||||
const value = JSON.parse(localStorage.getItem(characterLayersKey) || "null");
|
const value = JSON.parse(localStorage.getItem(characterLayersKey) || "null");
|
||||||
return areCharacterLayersValid(value) ? value : null;
|
return areCharacterLayersValid(value) ? value : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
setCompanion(companion: string|null): void {
|
setCompanion(companion: string | null): void {
|
||||||
return localStorage.setItem(companionKey, JSON.stringify(companion));
|
return localStorage.setItem(companionKey, JSON.stringify(companion));
|
||||||
}
|
}
|
||||||
getCompanion(): string|null {
|
getCompanion(): string | null {
|
||||||
const companion = JSON.parse(localStorage.getItem(companionKey) || "null");
|
const companion = JSON.parse(localStorage.getItem(companionKey) || "null");
|
||||||
|
|
||||||
if (typeof companion !== "string" || companion === "") {
|
if (typeof companion !== "string" || companion === "") {
|
||||||
@ -68,45 +69,52 @@ class LocalUserStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setGameQualityValue(value: number): void {
|
setGameQualityValue(value: number): void {
|
||||||
localStorage.setItem(gameQualityKey, '' + value);
|
localStorage.setItem(gameQualityKey, "" + value);
|
||||||
}
|
}
|
||||||
getGameQualityValue(): number {
|
getGameQualityValue(): number {
|
||||||
return parseInt(localStorage.getItem(gameQualityKey) || '60');
|
return parseInt(localStorage.getItem(gameQualityKey) || "60");
|
||||||
}
|
}
|
||||||
|
|
||||||
setVideoQualityValue(value: number): void {
|
setVideoQualityValue(value: number): void {
|
||||||
localStorage.setItem(videoQualityKey, '' + value);
|
localStorage.setItem(videoQualityKey, "" + value);
|
||||||
}
|
}
|
||||||
getVideoQualityValue(): number {
|
getVideoQualityValue(): number {
|
||||||
return parseInt(localStorage.getItem(videoQualityKey) || '20');
|
return parseInt(localStorage.getItem(videoQualityKey) || "20");
|
||||||
}
|
}
|
||||||
|
|
||||||
setAudioPlayerVolume(value: number): void {
|
setAudioPlayerVolume(value: number): void {
|
||||||
localStorage.setItem(audioPlayerVolumeKey, '' + value);
|
localStorage.setItem(audioPlayerVolumeKey, "" + value);
|
||||||
}
|
}
|
||||||
getAudioPlayerVolume(): number {
|
getAudioPlayerVolume(): number {
|
||||||
return parseFloat(localStorage.getItem(audioPlayerVolumeKey) || '1');
|
return parseFloat(localStorage.getItem(audioPlayerVolumeKey) || "1");
|
||||||
}
|
}
|
||||||
|
|
||||||
setAudioPlayerMuted(value: boolean): void {
|
setAudioPlayerMuted(value: boolean): void {
|
||||||
localStorage.setItem(audioPlayerMuteKey, value.toString());
|
localStorage.setItem(audioPlayerMuteKey, value.toString());
|
||||||
}
|
}
|
||||||
getAudioPlayerMuted(): boolean {
|
getAudioPlayerMuted(): boolean {
|
||||||
return localStorage.getItem(audioPlayerMuteKey) === 'true';
|
return localStorage.getItem(audioPlayerMuteKey) === "true";
|
||||||
}
|
}
|
||||||
|
|
||||||
setHelpCameraSettingsShown(): void {
|
setHelpCameraSettingsShown(): void {
|
||||||
localStorage.setItem(helpCameraSettingsShown, '1');
|
localStorage.setItem(helpCameraSettingsShown, "1");
|
||||||
}
|
}
|
||||||
getHelpCameraSettingsShown(): boolean {
|
getHelpCameraSettingsShown(): boolean {
|
||||||
return localStorage.getItem(helpCameraSettingsShown) === '1';
|
return localStorage.getItem(helpCameraSettingsShown) === "1";
|
||||||
}
|
}
|
||||||
|
|
||||||
setFullscreen(value: boolean): void {
|
setFullscreen(value: boolean): void {
|
||||||
localStorage.setItem(fullscreenKey, value.toString());
|
localStorage.setItem(fullscreenKey, value.toString());
|
||||||
}
|
}
|
||||||
getFullscreen(): boolean {
|
getFullscreen(): boolean {
|
||||||
return localStorage.getItem(fullscreenKey) === 'true';
|
return localStorage.getItem(fullscreenKey) === "true";
|
||||||
|
}
|
||||||
|
|
||||||
|
setLastRoomUrl(roomUrl: string): void {
|
||||||
|
localStorage.setItem(lastRoomUrl, roomUrl.toString());
|
||||||
|
}
|
||||||
|
getLastRoomUrl(): string {
|
||||||
|
return localStorage.getItem(lastRoomUrl) ?? "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
20
front/src/Network/ServiceWorker.ts
Normal file
20
front/src/Network/ServiceWorker.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
export class _ServiceWorker {
|
||||||
|
constructor() {
|
||||||
|
if ("serviceWorker" in navigator) {
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
window.addEventListener("load", () => {
|
||||||
|
navigator.serviceWorker
|
||||||
|
.register("/resources/service-worker.js")
|
||||||
|
.then((serviceWorker) => {
|
||||||
|
console.info("Service Worker registered: ", serviceWorker);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Error registering the Service Worker: ", error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -162,16 +162,3 @@ const app = new App({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export default app;
|
export default app;
|
||||||
|
|
||||||
if ("serviceWorker" in navigator) {
|
|
||||||
window.addEventListener("load", function () {
|
|
||||||
navigator.serviceWorker
|
|
||||||
.register("/resources/service-worker.js")
|
|
||||||
.then((serviceWorker) => {
|
|
||||||
console.log("Service Worker registered: ", serviceWorker);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error("Error registering the Service Worker: ", error);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user