Merge pull request #4657 from tsiegleauq/service-worker-addons

Service Worker Updates
This commit is contained in:
Sean 2019-05-06 13:59:07 +02:00 committed by GitHub
commit e1b8e74e8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 162 additions and 40 deletions

View File

@ -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/**"
]
}
}
]
} }

View File

@ -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']);

View 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();
});
});

View 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, {});
}
}

View File

@ -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>

View File

@ -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();
}
} }

View File

@ -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):