Service Worker Updates
Introdcues a new update service. Listens to service-worker updates and shows a snack-bar to inform about updates. Provides a function to manually check for updates. The service worker tries to be consistent in it's own version and updates in the background. Some manuall trigger will be required to update, which is either a reload or the execution of the provded check function with help from @FinnStutzenstein
This commit is contained in:
parent
d7c6583b7e
commit
e4d3e119d3
@ -1,26 +1,20 @@
|
|||||||
{
|
{
|
||||||
"index": "/index.html",
|
"index": "/index.html",
|
||||||
"assetGroups": [
|
"assetGroups": [
|
||||||
{
|
{
|
||||||
"name": "app",
|
"name": "app",
|
||||||
"installMode": "prefetch",
|
"installMode": "prefetch",
|
||||||
"resources": {
|
"resources": {
|
||||||
"files": [
|
"files": ["/favicon.png", "/index.html", "/*.css", "/*.js"]
|
||||||
"/favicon.png",
|
}
|
||||||
"/index.html",
|
},
|
||||||
"/*.css",
|
{
|
||||||
"/*.js"
|
"name": "assets",
|
||||||
]
|
"installMode": "lazy",
|
||||||
}
|
"updateMode": "prefetch",
|
||||||
}, {
|
"resources": {
|
||||||
"name": "assets",
|
"files": ["/assets/**"]
|
||||||
"installMode": "lazy",
|
}
|
||||||
"updateMode": "prefetch",
|
}
|
||||||
"resources": {
|
]
|
||||||
"files": [
|
|
||||||
"/assets/**"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import { OperatorService } from './core/core-services/operator.service';
|
|||||||
import { ServertimeService } from './core/core-services/servertime.service';
|
import { ServertimeService } from './core/core-services/servertime.service';
|
||||||
import { ThemeService } from './core/ui-services/theme.service';
|
import { ThemeService } from './core/ui-services/theme.service';
|
||||||
import { DataStoreUpgradeService } from './core/core-services/data-store-upgrade.service';
|
import { DataStoreUpgradeService } from './core/core-services/data-store-upgrade.service';
|
||||||
|
import { UpdateService } from './core/ui-services/update.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Angular's global App Component
|
* Angular's global App Component
|
||||||
@ -38,6 +39,8 @@ export class AppComponent {
|
|||||||
* @param countUsersService to call the constructor of the CountUserService
|
* @param countUsersService to call the constructor of the CountUserService
|
||||||
* @param configService to call the constructor of the ConfigService
|
* @param configService to call the constructor of the ConfigService
|
||||||
* @param loadFontService to call the constructor of the LoadFontService
|
* @param loadFontService to call the constructor of the LoadFontService
|
||||||
|
* @param dataStoreUpgradeService
|
||||||
|
* @param update Service Worker Updates
|
||||||
*/
|
*/
|
||||||
public constructor(
|
public constructor(
|
||||||
translate: TranslateService,
|
translate: TranslateService,
|
||||||
@ -50,7 +53,8 @@ export class AppComponent {
|
|||||||
countUsersService: CountUsersService, // Needed to register itself.
|
countUsersService: CountUsersService, // Needed to register itself.
|
||||||
configService: ConfigService,
|
configService: ConfigService,
|
||||||
loadFontService: LoadFontService,
|
loadFontService: LoadFontService,
|
||||||
dataStoreUpgradeService: DataStoreUpgradeService // to start it.
|
dataStoreUpgradeService: DataStoreUpgradeService, // to start it.
|
||||||
|
update: UpdateService
|
||||||
) {
|
) {
|
||||||
// manually add the supported languages
|
// manually add the supported languages
|
||||||
translate.addLangs(['en', 'de', 'cs']);
|
translate.addLangs(['en', 'de', 'cs']);
|
||||||
|
18
client/src/app/core/ui-services/update.service.spec.ts
Normal file
18
client/src/app/core/ui-services/update.service.spec.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { UpdateService } from './update.service';
|
||||||
|
import { E2EImportsModule } from 'e2e-imports.module';
|
||||||
|
|
||||||
|
describe('UpdateService', () => {
|
||||||
|
beforeEach(() =>
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [E2EImportsModule],
|
||||||
|
providers: [UpdateService]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
const service: UpdateService = TestBed.get(UpdateService);
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
61
client/src/app/core/ui-services/update.service.ts
Normal file
61
client/src/app/core/ui-services/update.service.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
import { SwUpdate } from '@angular/service-worker';
|
||||||
|
import { MatSnackBar } from '@angular/material';
|
||||||
|
import { NotifyService } from '../core-services/notify.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle Service Worker updates using the SwUpdate service form angular.
|
||||||
|
*/
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class UpdateService {
|
||||||
|
private static NOTIFY_NAME = 'swCheckForUpdate';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
* Listens to available updates
|
||||||
|
*
|
||||||
|
* @param swUpdate Service Worker update service
|
||||||
|
* @param matSnackBar Currently to show that an update is available
|
||||||
|
*/
|
||||||
|
public constructor(private swUpdate: SwUpdate, matSnackBar: MatSnackBar, private notify: NotifyService) {
|
||||||
|
swUpdate.available.subscribe(() => {
|
||||||
|
// TODO: Find a better solution OR make an update-bar like for history mode
|
||||||
|
const ref = matSnackBar.open('A new update is available!', 'Refresh', {
|
||||||
|
duration: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
// Enforces an update
|
||||||
|
ref.onAction().subscribe(() => {
|
||||||
|
this.swUpdate.activateUpdate().then(() => {
|
||||||
|
document.location.reload();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen on requests from other users to check for updates.
|
||||||
|
this.notify.getMessageObservable(UpdateService.NOTIFY_NAME).subscribe(() => {
|
||||||
|
this.checkForUpdate();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trigger that to manually check for updates
|
||||||
|
*/
|
||||||
|
public checkForUpdate(): void {
|
||||||
|
if (this.swUpdate.isEnabled) {
|
||||||
|
this.swUpdate.checkForUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits a message to all clients initiating to check for updates. This method
|
||||||
|
* can only be called by users with 'users.can_manage'. This will be checked by
|
||||||
|
* the server.
|
||||||
|
*/
|
||||||
|
public initiateUpdateCheckForAllClients(): void {
|
||||||
|
this.notify.sendToAllUsers(UpdateService.NOTIFY_NAME, {});
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
<os-head-bar [nav]=false [goBack]=true>
|
<os-head-bar [nav]="false" [goBack]="true">
|
||||||
<div class="title-slot">
|
<div class="title-slot">
|
||||||
<h2 translate>Legal notice</h2>
|
<h2 translate>Legal notice</h2>
|
||||||
</div>
|
</div>
|
||||||
@ -7,9 +7,29 @@
|
|||||||
<os-legal-notice-content></os-legal-notice-content>
|
<os-legal-notice-content></os-legal-notice-content>
|
||||||
|
|
||||||
<mat-card class="os-card">
|
<mat-card class="os-card">
|
||||||
<button type="button" mat-button (click)="resetCache()">
|
<div>
|
||||||
<span translate>Reset cache</span>
|
<button type="button" mat-button (click)="resetCache()">
|
||||||
</button>
|
<span translate>Reset cache</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button type="button" mat-button (click)="checkForUpdate()">
|
||||||
|
<span translate>Check for updates</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *osPerms="'users.can_manage'">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
mat-button
|
||||||
|
(click)="initiateUpdateCheckForAllClients()"
|
||||||
|
matTooltip="{{ 'This will send an update notification to all active clients' | translate }}"
|
||||||
|
>
|
||||||
|
<span translate>Initiate update check for all clients</span>
|
||||||
|
<mat-icon class="warning spacer-left-10">warning</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
|
|
||||||
<os-count-users></os-count-users>
|
<os-count-users></os-count-users>
|
||||||
|
@ -1,14 +1,23 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { OpenSlidesService } from 'app/core/core-services/openslides.service';
|
import { OpenSlidesService } from 'app/core/core-services/openslides.service';
|
||||||
|
import { UpdateService } from 'app/core/ui-services/update.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'os-legal-notice',
|
selector: 'os-legal-notice',
|
||||||
templateUrl: './legal-notice.component.html'
|
templateUrl: './legal-notice.component.html'
|
||||||
})
|
})
|
||||||
export class LegalNoticeComponent {
|
export class LegalNoticeComponent {
|
||||||
public constructor(private openSlidesService: OpenSlidesService) {}
|
public constructor(private openSlidesService: OpenSlidesService, private update: UpdateService) {}
|
||||||
|
|
||||||
public resetCache(): void {
|
public resetCache(): void {
|
||||||
this.openSlidesService.reset();
|
this.openSlidesService.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public checkForUpdate(): void {
|
||||||
|
this.update.checkForUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public initiateUpdateCheckForAllClients(): void {
|
||||||
|
this.update.initiateUpdateCheckForAllClients();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from typing import Any
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
from ..utils.auth import async_has_perm
|
||||||
from ..utils.constants import get_constants
|
from ..utils.constants import get_constants
|
||||||
from ..utils.projector import get_projector_data
|
from ..utils.projector import get_projector_data
|
||||||
from ..utils.websocket import (
|
from ..utils.websocket import (
|
||||||
@ -44,19 +45,34 @@ class NotifyWebsocketClientMessage(BaseWebsocketClientMessage):
|
|||||||
},
|
},
|
||||||
"required": ["name", "content"],
|
"required": ["name", "content"],
|
||||||
}
|
}
|
||||||
|
# Define a required permission for a notify message here. If the emitting user does not
|
||||||
|
# have this permission, he will get an error message in response.
|
||||||
|
notify_permissions: Dict[str, str] = {"swCheckForUpdate": "users.can_manage"}
|
||||||
|
|
||||||
async def receive_content(
|
async def receive_content(
|
||||||
self, consumer: "ProtocollAsyncJsonWebsocketConsumer", content: Any, id: str
|
self, consumer: "ProtocollAsyncJsonWebsocketConsumer", content: Any, id: str
|
||||||
) -> None:
|
) -> None:
|
||||||
await consumer.channel_layer.group_send(
|
# Check if the user is allowed to send this notify message
|
||||||
"site",
|
perm = self.notify_permissions.get(content["name"])
|
||||||
{
|
if perm is not None and not await async_has_perm(
|
||||||
"type": "send_notify",
|
consumer.scope["user"]["id"], perm
|
||||||
"incomming": content,
|
):
|
||||||
"senderChannelName": consumer.channel_name,
|
await consumer.send_json(
|
||||||
"senderUserId": consumer.scope["user"]["id"],
|
type="error",
|
||||||
},
|
content=f"You need '{perm}' to send this message.",
|
||||||
)
|
in_response=id,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Forward to all other active site consumers to handle the notify message.
|
||||||
|
await consumer.channel_layer.group_send(
|
||||||
|
"site",
|
||||||
|
{
|
||||||
|
"type": "send_notify",
|
||||||
|
"incomming": content,
|
||||||
|
"senderChannelName": consumer.channel_name,
|
||||||
|
"senderUserId": consumer.scope["user"]["id"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ConstantsWebsocketClientMessage(BaseWebsocketClientMessage):
|
class ConstantsWebsocketClientMessage(BaseWebsocketClientMessage):
|
||||||
|
Loading…
Reference in New Issue
Block a user