Merge pull request #5403 from tsiegleauq/blacklist-browsers
Catch unsupported browsers
This commit is contained in:
commit
f590994875
@ -63,6 +63,7 @@
|
|||||||
"moment": "^2.24.0",
|
"moment": "^2.24.0",
|
||||||
"ng2-charts": "^2.3.0",
|
"ng2-charts": "^2.3.0",
|
||||||
"ng2-pdf-viewer": "^6.1.2",
|
"ng2-pdf-viewer": "^6.1.2",
|
||||||
|
"ngx-device-detector": "^1.4.4",
|
||||||
"ngx-file-drop": "^8.0.8",
|
"ngx-file-drop": "^8.0.8",
|
||||||
"ngx-mat-select-search": "^2.1.2",
|
"ngx-mat-select-search": "^2.1.2",
|
||||||
"ngx-material-timepicker": "^5.5.1",
|
"ngx-material-timepicker": "^5.5.1",
|
||||||
|
@ -7,6 +7,7 @@ import { LoginPrivacyPolicyComponent } from './site/login/components/login-priva
|
|||||||
import { LoginWrapperComponent } from './site/login/components/login-wrapper/login-wrapper.component';
|
import { LoginWrapperComponent } from './site/login/components/login-wrapper/login-wrapper.component';
|
||||||
import { ResetPasswordConfirmComponent } from './site/login/components/reset-password-confirm/reset-password-confirm.component';
|
import { ResetPasswordConfirmComponent } from './site/login/components/reset-password-confirm/reset-password-confirm.component';
|
||||||
import { ResetPasswordComponent } from './site/login/components/reset-password/reset-password.component';
|
import { ResetPasswordComponent } from './site/login/components/reset-password/reset-password.component';
|
||||||
|
import { UnsupportedBrowserComponent } from './site/login/components/unsupported-browser/unsupported-browser.component';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Global app routing
|
* Global app routing
|
||||||
@ -20,7 +21,8 @@ const routes: Routes = [
|
|||||||
{ path: 'reset-password', component: ResetPasswordComponent },
|
{ path: 'reset-password', component: ResetPasswordComponent },
|
||||||
{ path: 'reset-password-confirm', component: ResetPasswordConfirmComponent },
|
{ path: 'reset-password-confirm', component: ResetPasswordConfirmComponent },
|
||||||
{ path: 'legalnotice', component: LoginLegalNoticeComponent },
|
{ path: 'legalnotice', component: LoginLegalNoticeComponent },
|
||||||
{ path: 'privacypolicy', component: LoginPrivacyPolicyComponent }
|
{ path: 'privacypolicy', component: LoginPrivacyPolicyComponent },
|
||||||
|
{ path: 'unsupported-browser', component: UnsupportedBrowserComponent }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -6,6 +6,7 @@ import { ActivatedRoute, Router } from '@angular/router';
|
|||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
|
import { filter } from 'rxjs/operators';
|
||||||
|
|
||||||
import { AuthService } from 'app/core/core-services/auth.service';
|
import { AuthService } from 'app/core/core-services/auth.service';
|
||||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||||
@ -14,6 +15,7 @@ import { OverlayService } from 'app/core/ui-services/overlay.service';
|
|||||||
import { UserAuthType } from 'app/shared/models/users/user';
|
import { UserAuthType } from 'app/shared/models/users/user';
|
||||||
import { ParentErrorStateMatcher } from 'app/shared/parent-error-state-matcher';
|
import { ParentErrorStateMatcher } from 'app/shared/parent-error-state-matcher';
|
||||||
import { BaseViewComponent } from 'app/site/base/base-view';
|
import { BaseViewComponent } from 'app/site/base/base-view';
|
||||||
|
import { BrowserSupportService } from '../../services/browser-support.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Login mask component.
|
* Login mask component.
|
||||||
@ -31,6 +33,8 @@ export class LoginMaskComponent extends BaseViewComponent implements OnInit, OnD
|
|||||||
*/
|
*/
|
||||||
public hide: boolean;
|
public hide: boolean;
|
||||||
|
|
||||||
|
private checkBrowser = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reference to the SnackBarEntry for the installation notice send by the server.
|
* Reference to the SnackBarEntry for the installation notice send by the server.
|
||||||
*/
|
*/
|
||||||
@ -82,7 +86,8 @@ export class LoginMaskComponent extends BaseViewComponent implements OnInit, OnD
|
|||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
private loginDataService: LoginDataService,
|
private loginDataService: LoginDataService,
|
||||||
private overlayService: OverlayService
|
private overlayService: OverlayService,
|
||||||
|
private browserSupport: BrowserSupportService
|
||||||
) {
|
) {
|
||||||
super(title, translate, matSnackBar);
|
super(title, translate, matSnackBar);
|
||||||
// Hide the spinner if the user is at `login-mask`
|
// Hide the spinner if the user is at `login-mask`
|
||||||
@ -113,6 +118,14 @@ export class LoginMaskComponent extends BaseViewComponent implements OnInit, OnD
|
|||||||
this.authService.redirectUser(user.id);
|
this.authService.redirectUser(user.id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.route.queryParams.pipe(filter(params => params.checkBrowser)).subscribe(params => {
|
||||||
|
this.checkBrowser = params.checkBrowser === 'true';
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.checkBrowser) {
|
||||||
|
this.checkDevice();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -122,6 +135,12 @@ export class LoginMaskComponent extends BaseViewComponent implements OnInit, OnD
|
|||||||
this.clearOperatorSubscription();
|
this.clearOperatorSubscription();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private checkDevice(): void {
|
||||||
|
if (!this.browserSupport.isBrowserSupported()) {
|
||||||
|
this.router.navigate(['./unsupported-browser'], { relativeTo: this.route });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears the subscription to the operator.
|
* Clears the subscription to the operator.
|
||||||
*/
|
*/
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
<!-- The actual form -->
|
<!-- The actual form -->
|
||||||
<header>
|
<header>
|
||||||
<mat-toolbar class="login-logo-bar" color="primary">
|
<mat-toolbar class="login-logo-bar" color="primary">
|
||||||
<a routerLink="/login"><img src="assets/img/openslides-logo-dark.svg" alt="OpenSlides-logo" /></a>
|
<a routerLink="/login" [queryParams]="{ checkBrowser: false }"
|
||||||
|
><img src="assets/img/openslides-logo-dark.svg" alt="OpenSlides-logo"
|
||||||
|
/></a>
|
||||||
</mat-toolbar>
|
</mat-toolbar>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
<main>
|
||||||
|
<h1 class="center spacer-top-20" *ngIf="!supported">
|
||||||
|
{{ 'Your browser is not supported by OpenSlides' | translate }}
|
||||||
|
</h1>
|
||||||
|
<h1 class="center spacer-top-20" *ngIf="supported">
|
||||||
|
{{ 'Congratuations! Your browser is supported by OpenSlides' | translate }}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<mat-card class="os-card">
|
||||||
|
<div>
|
||||||
|
<span>
|
||||||
|
{{ 'Your browser' | translate }}: {{ currentBrowser }} ({{ 'version' | translate }}:
|
||||||
|
{{ browserVersion }})
|
||||||
|
</span>
|
||||||
|
<br />
|
||||||
|
<span *ngIf="!!supportedVersion">{{ 'Minimal required version for your browser' | translate }}: {{ supportedVersion }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="spacer-top-20" *ngIf="!supported">
|
||||||
|
<span>{{
|
||||||
|
'Please update your browser or contact your system administration to have your browser updated for you.'
|
||||||
|
| translate
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="spacer-top-20">
|
||||||
|
<span>
|
||||||
|
{{ 'OpenSlides recommends:' | translate }}
|
||||||
|
<ul>
|
||||||
|
<li *ngFor="let browser of recommendedBrowsers">
|
||||||
|
<a [href]="browser.url">{{ browser.name }}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</mat-card>
|
||||||
|
</main>
|
@ -0,0 +1,27 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { E2EImportsModule } from 'e2e-imports.module';
|
||||||
|
|
||||||
|
import { UnsupportedBrowserComponent } from './unsupported-browser.component';
|
||||||
|
|
||||||
|
describe('UnsupportedBrowserComponent', () => {
|
||||||
|
let component: UnsupportedBrowserComponent;
|
||||||
|
let fixture: ComponentFixture<UnsupportedBrowserComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [E2EImportsModule],
|
||||||
|
declarations: [UnsupportedBrowserComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(UnsupportedBrowserComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,28 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
import { BrowserRecommendation, BrowserSupportService } from '../../services/browser-support.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'os-unsupported-browser',
|
||||||
|
templateUrl: './unsupported-browser.component.html'
|
||||||
|
})
|
||||||
|
export class UnsupportedBrowserComponent implements OnInit {
|
||||||
|
public supported: boolean;
|
||||||
|
public currentBrowser: string;
|
||||||
|
public browserVersion: string;
|
||||||
|
public supportedVersion: number;
|
||||||
|
|
||||||
|
public get recommendedBrowsers(): BrowserRecommendation[] {
|
||||||
|
return this.browserSupprt.recommendedBrowsers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public constructor(private browserSupprt: BrowserSupportService) {}
|
||||||
|
|
||||||
|
public ngOnInit(): void {
|
||||||
|
const deviceInfo = this.browserSupprt.getDeviceInfo();
|
||||||
|
this.supported = this.browserSupprt.isBrowserSupported();
|
||||||
|
this.currentBrowser = deviceInfo.browser;
|
||||||
|
this.browserVersion = deviceInfo.browser_version;
|
||||||
|
this.supportedVersion = this.browserSupprt.getSupportedVersion(deviceInfo);
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,8 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
|
import { DeviceDetectorModule } from 'ngx-device-detector';
|
||||||
|
|
||||||
import { LoginLegalNoticeComponent } from './components/login-legal-notice/login-legal-notice.component';
|
import { LoginLegalNoticeComponent } from './components/login-legal-notice/login-legal-notice.component';
|
||||||
import { LoginMaskComponent } from './components/login-mask/login-mask.component';
|
import { LoginMaskComponent } from './components/login-mask/login-mask.component';
|
||||||
import { LoginPrivacyPolicyComponent } from './components/login-privacy-policy/login-privacy-policy.component';
|
import { LoginPrivacyPolicyComponent } from './components/login-privacy-policy/login-privacy-policy.component';
|
||||||
@ -9,16 +11,18 @@ import { LoginWrapperComponent } from './components/login-wrapper/login-wrapper.
|
|||||||
import { ResetPasswordConfirmComponent } from './components/reset-password-confirm/reset-password-confirm.component';
|
import { ResetPasswordConfirmComponent } from './components/reset-password-confirm/reset-password-confirm.component';
|
||||||
import { ResetPasswordComponent } from './components/reset-password/reset-password.component';
|
import { ResetPasswordComponent } from './components/reset-password/reset-password.component';
|
||||||
import { SharedModule } from '../../shared/shared.module';
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
|
import { UnsupportedBrowserComponent } from './components/unsupported-browser/unsupported-browser.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [CommonModule, RouterModule, SharedModule],
|
imports: [CommonModule, RouterModule, SharedModule, DeviceDetectorModule],
|
||||||
declarations: [
|
declarations: [
|
||||||
LoginWrapperComponent,
|
LoginWrapperComponent,
|
||||||
ResetPasswordComponent,
|
ResetPasswordComponent,
|
||||||
ResetPasswordConfirmComponent,
|
ResetPasswordConfirmComponent,
|
||||||
LoginMaskComponent,
|
LoginMaskComponent,
|
||||||
LoginLegalNoticeComponent,
|
LoginLegalNoticeComponent,
|
||||||
LoginPrivacyPolicyComponent
|
LoginPrivacyPolicyComponent,
|
||||||
|
UnsupportedBrowserComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class LoginModule {}
|
export class LoginModule {}
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { BrowserSupportService } from './browser-support.service';
|
||||||
|
|
||||||
|
describe('BrowserSupportService', () => {
|
||||||
|
let service: BrowserSupportService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(BrowserSupportService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,63 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
import { DeviceDetectorService, DeviceInfo } from 'ngx-device-detector';
|
||||||
|
|
||||||
|
const SmallestSupportedBrowserVersion = {
|
||||||
|
Chrome: 81,
|
||||||
|
Safari: 13,
|
||||||
|
Firefox: 68,
|
||||||
|
Opera: 66,
|
||||||
|
'MS-Edge': 81,
|
||||||
|
'MS-Edge-Chromium': 81
|
||||||
|
};
|
||||||
|
|
||||||
|
const BrowserBlacklist = ['IE'];
|
||||||
|
|
||||||
|
export interface BrowserRecommendation {
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class BrowserSupportService {
|
||||||
|
public readonly recommendedBrowsers: BrowserRecommendation[] = [
|
||||||
|
{
|
||||||
|
name: 'Google Chrome',
|
||||||
|
url: 'https://www.google.com/chrome/'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Mozilla Firefox',
|
||||||
|
url: 'https://www.mozilla.org/firefox/'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
public constructor(private deviceService: DeviceDetectorService) {}
|
||||||
|
|
||||||
|
public getDeviceInfo(): DeviceInfo {
|
||||||
|
return this.deviceService.getDeviceInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSupportedVersion(info: DeviceInfo): number {
|
||||||
|
return SmallestSupportedBrowserVersion[info.browser];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect the browser version and forward to an error page if the browser was too old
|
||||||
|
*/
|
||||||
|
public isBrowserSupported(): boolean {
|
||||||
|
const deviceInfo = this.deviceService.getDeviceInfo();
|
||||||
|
const browser = deviceInfo.browser;
|
||||||
|
const version = parseInt(deviceInfo.browser_version, 10);
|
||||||
|
|
||||||
|
if (BrowserBlacklist.includes(browser)) {
|
||||||
|
return false;
|
||||||
|
} else if (!SmallestSupportedBrowserVersion[browser]) {
|
||||||
|
// if we don't know the browser, let's assume they are working
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return version >= this.getSupportedVersion(deviceInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user