mirror of
https://github.com/bitwarden/directory-connector
synced 2025-12-11 13:53:22 +00:00
wire up services
This commit is contained in:
2
jslib
2
jslib
Submodule jslib updated: 5d3b99ce6f...31bd1f9423
59
src/app/accounts/environment.component.html
Normal file
59
src/app/accounts/environment.component.html
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<div class="modal fade">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form class="modal-content" (ngSubmit)="submit()">
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="box">
|
||||||
|
<div class="box-header">
|
||||||
|
{{'selfHostedEnvironment' | i18n}}
|
||||||
|
</div>
|
||||||
|
<div class="box-content">
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="baseUrl">{{'baseUrl' | i18n}}</label>
|
||||||
|
<input id="baseUrl" type="text" name="BaseUrl" [(ngModel)]="baseUrl"
|
||||||
|
placeholder="ex. https://bitwarden.company.com">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="box-footer">
|
||||||
|
{{'selfHostedEnvironmentFooter' | i18n}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="box">
|
||||||
|
<div class="box-header">
|
||||||
|
<button type="button" (click)="toggleCustom()">
|
||||||
|
<i class="fa fa-plus-square-o" [hidden]="showCustom"></i>
|
||||||
|
<i class="fa fa-minus-square-o" [hidden]="!showCustom"></i>
|
||||||
|
{{'customEnvironment' | i18n}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="box-content" [hidden]="!showCustom">
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="webVaultUrl">{{'webVaultUrl' | i18n}}</label>
|
||||||
|
<input id="webVaultUrl" type="text" name="WebVaultUrl" [(ngModel)]="webVaultUrl">
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="apiUrl">{{'apiUrl' | i18n}}</label>
|
||||||
|
<input id="apiUrl" type="text" name="ApiUrl" [(ngModel)]="apiUrl">
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="identityUrl">{{'identityUrl' | i18n}}</label>
|
||||||
|
<input id="identityUrl" type="text" name="IdentityUrl" [(ngModel)]="identityUrl">
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="iconsUrl">{{'iconsUrl' | i18n}}</label>
|
||||||
|
<input id="iconsUrl" type="text" name="IconsUrl" [(ngModel)]="iconsUrl">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="box-footer" [hidden]="!showCustom">
|
||||||
|
{{'customEnvironmentFooter' | i18n}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button appBlurClick type="submit" class="primary" title="{{'save' | i18n}}">
|
||||||
|
<i class="fa fa-save fa-lg fa-fw"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
20
src/app/accounts/environment.component.ts
Normal file
20
src/app/accounts/environment.component.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
import { ToasterService } from 'angular2-toaster';
|
||||||
|
import { Angulartics2 } from 'angulartics2';
|
||||||
|
|
||||||
|
import { EnvironmentService } from 'jslib/abstractions/environment.service';
|
||||||
|
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||||
|
|
||||||
|
import { EnvironmentComponent as BaseEnvironmentComponent } from 'jslib/angular/components/environment.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-environment',
|
||||||
|
templateUrl: 'environment.component.html',
|
||||||
|
})
|
||||||
|
export class EnvironmentComponent extends BaseEnvironmentComponent {
|
||||||
|
constructor(analytics: Angulartics2, toasterService: ToasterService,
|
||||||
|
environmentService: EnvironmentService, i18nService: I18nService) {
|
||||||
|
super(analytics, toasterService, environmentService, i18nService);
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/app/accounts/login.component.html
Normal file
45
src/app/accounts/login.component.html
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<form id="login-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||||
|
<div class="content">
|
||||||
|
<img src="../../images/logo@2x.png" alt="bitwarden">
|
||||||
|
<p class="lead">{{'loginOrCreateNewAccount' | i18n}}</p>
|
||||||
|
<div class="box last">
|
||||||
|
<div class="box-content">
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="email">{{'emailAddress' | i18n}}</label>
|
||||||
|
<input id="email" type="text" name="Email" [(ngModel)]="email" required
|
||||||
|
[appAutofocus]="email === ''">
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row box-content-row-flex" appBoxRow>
|
||||||
|
<div class="row-main">
|
||||||
|
<label for="masterPassword">{{'masterPass' | i18n}}</label>
|
||||||
|
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" name="MasterPassword"
|
||||||
|
class="monospaced" [(ngModel)]="masterPassword" required [appAutofocus]="email !== ''">
|
||||||
|
</div>
|
||||||
|
<div class="action-buttons">
|
||||||
|
<a class="row-btn" href="#" appStopClick appBlurClick
|
||||||
|
title="{{'toggleVisibility' | i18n}}" (click)="togglePassword()">
|
||||||
|
<i class="fa fa-lg"
|
||||||
|
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="buttons">
|
||||||
|
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
|
||||||
|
<span [hidden]="form.loading"><i class="fa fa-sign-in"></i> {{'logIn' | i18n}}</span>
|
||||||
|
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading"></i>
|
||||||
|
</button>
|
||||||
|
<a routerLink="/register" class="btn block">
|
||||||
|
<i class="fa fa-pencil-square-o"></i> {{'createAccount' | i18n}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="sub-options">
|
||||||
|
<a routerLink="/hint">{{'getMasterPasswordHint' | i18n}}</a>
|
||||||
|
</div>
|
||||||
|
<a href="#" appStopClick (click)="settings()" class="settings-icon">
|
||||||
|
<i class="fa fa-cog fa-lg"></i><span> {{'settings' | i18n}}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<ng-template #environment></ng-template>
|
||||||
45
src/app/accounts/login.component.ts
Normal file
45
src/app/accounts/login.component.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import {
|
||||||
|
Component,
|
||||||
|
ComponentFactoryResolver,
|
||||||
|
ViewChild,
|
||||||
|
ViewContainerRef,
|
||||||
|
} from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
import { ToasterService } from 'angular2-toaster';
|
||||||
|
import { Angulartics2 } from 'angulartics2';
|
||||||
|
|
||||||
|
import { EnvironmentComponent } from './environment.component';
|
||||||
|
|
||||||
|
import { AuthService } from 'jslib/abstractions/auth.service';
|
||||||
|
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||||
|
import { SyncService } from 'jslib/abstractions/sync.service';
|
||||||
|
|
||||||
|
import { LoginComponent as BaseLoginComponent } from 'jslib/angular/components/login.component';
|
||||||
|
import { ModalComponent } from 'jslib/angular/components/modal.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-login',
|
||||||
|
templateUrl: 'login.component.html',
|
||||||
|
})
|
||||||
|
export class LoginComponent extends BaseLoginComponent {
|
||||||
|
@ViewChild('environment', { read: ViewContainerRef }) environmentModal: ViewContainerRef;
|
||||||
|
|
||||||
|
constructor(authService: AuthService, router: Router,
|
||||||
|
analytics: Angulartics2, toasterService: ToasterService,
|
||||||
|
i18nService: I18nService, syncService: SyncService,
|
||||||
|
private componentFactoryResolver: ComponentFactoryResolver) {
|
||||||
|
super(authService, router, analytics, toasterService, i18nService, syncService);
|
||||||
|
}
|
||||||
|
|
||||||
|
settings() {
|
||||||
|
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
|
||||||
|
const modal = this.environmentModal.createComponent(factory).instance;
|
||||||
|
const childComponent = modal.show<EnvironmentComponent>(EnvironmentComponent,
|
||||||
|
this.environmentModal);
|
||||||
|
|
||||||
|
childComponent.onSaved.subscribe(() => {
|
||||||
|
modal.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/app/accounts/two-factor-options.component.html
Normal file
28
src/app/accounts/two-factor-options.component.html
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<div class="modal fade">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="box">
|
||||||
|
<div class="box-header">
|
||||||
|
{{'twoStepOptions' | i18n}}
|
||||||
|
</div>
|
||||||
|
<div class="box-content">
|
||||||
|
<a href="#" appStopClick *ngFor="let p of providers" class="box-content-row"
|
||||||
|
(click)="choose(p)">
|
||||||
|
<img [src]="'images/two-factor/' + p.type + '.png'" alt="" class="img-right">
|
||||||
|
<span class="text">{{p.name}}</span>
|
||||||
|
<span class="detail">{{p.description}}</span>
|
||||||
|
</a>
|
||||||
|
<a href="#" appStopClick class="box-content-row" (click)="recover()">
|
||||||
|
<span class="text">{{'recoveryCodeTitle' | i18n}}</span>
|
||||||
|
<span class="detail">{{'recoveryCodeDesc' | i18n}}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
25
src/app/accounts/two-factor-options.component.ts
Normal file
25
src/app/accounts/two-factor-options.component.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
import { ToasterService } from 'angular2-toaster';
|
||||||
|
import { Angulartics2 } from 'angulartics2';
|
||||||
|
|
||||||
|
import { AuthService } from 'jslib/abstractions/auth.service';
|
||||||
|
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||||
|
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||||
|
|
||||||
|
import {
|
||||||
|
TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent,
|
||||||
|
} from 'jslib/angular/components/two-factor-options.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-two-factor-options',
|
||||||
|
templateUrl: 'two-factor-options.component.html',
|
||||||
|
})
|
||||||
|
export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent {
|
||||||
|
constructor(authService: AuthService, router: Router,
|
||||||
|
analytics: Angulartics2, toasterService: ToasterService,
|
||||||
|
i18nService: I18nService, platformUtilsService: PlatformUtilsService) {
|
||||||
|
super(authService, router, analytics, toasterService, i18nService, platformUtilsService, window);
|
||||||
|
}
|
||||||
|
}
|
||||||
75
src/app/accounts/two-factor.component.html
Normal file
75
src/app/accounts/two-factor.component.html
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
<form id="two-factor-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||||
|
<div class="content">
|
||||||
|
<h1>{{title}}</h1>
|
||||||
|
<p *ngIf="selectedProviderType === providerType.Authenticator">{{'enterVerificationCodeApp' | i18n}}</p>
|
||||||
|
<p *ngIf="selectedProviderType === providerType.Email">
|
||||||
|
{{'enterVerificationCodeEmail' | i18n : twoFactorEmail}}
|
||||||
|
</p>
|
||||||
|
<div class="box last"
|
||||||
|
*ngIf="selectedProviderType === providerType.Email || selectedProviderType === providerType.Authenticator">
|
||||||
|
<div class="box-content">
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="code">{{'verificationCode' | i18n}}</label>
|
||||||
|
<input id="code" type="text" name="Code" [(ngModel)]="token" required appAutofocus>
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||||
|
<label for="remember">{{'rememberMe' | i18n}}</label>
|
||||||
|
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ng-container *ngIf="selectedProviderType === providerType.Yubikey">
|
||||||
|
<p>{{'insertYubiKey' | i18n}}</p>
|
||||||
|
<img src="../../images/yubikey.jpg" alt="">
|
||||||
|
<div class="box last">
|
||||||
|
<div class="box-content">
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="code" class="sr-only">{{'verificationCode' | i18n}}</label>
|
||||||
|
<input id="code" type="password" name="Code" [(ngModel)]="token" required appAutofocus>
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||||
|
<label for="remember">{{'rememberMe' | i18n}}</label>
|
||||||
|
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="selectedProviderType === providerType.Duo ||
|
||||||
|
selectedProviderType === providerType.OrganizationDuo">
|
||||||
|
<div id="duo-frame"><iframe id="duo_iframe"></iframe></div>
|
||||||
|
<div class="box last">
|
||||||
|
<div class="box-content">
|
||||||
|
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||||
|
<label for="remember">{{'rememberMe' | i18n}}</label>
|
||||||
|
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
<div class="box last" *ngIf="selectedProviderType == null">
|
||||||
|
<div class="box-content">
|
||||||
|
<div class="box-content-row">
|
||||||
|
<p>{{'noTwoStepProviders' | i18n}}</p>
|
||||||
|
<p>{{'noTwoStepProviders2' | i18n}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="buttons">
|
||||||
|
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick
|
||||||
|
*ngIf="selectedProviderType != null && selectedProviderType !== providerType.Duo &&
|
||||||
|
selectedProviderType !== providerType.OrganizationDuo">
|
||||||
|
<span [hidden]="form.loading"><i class="fa fa-sign-in"></i> {{'continue' | i18n}}</span>
|
||||||
|
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading"></i>
|
||||||
|
</button>
|
||||||
|
<a routerLink="/login" class="btn block">{{'cancel' | i18n}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="sub-options">
|
||||||
|
<a href="#" appStopClick (click)="anotherMethod()">{{'useAnotherTwoStepMethod' | i18n}}</a>
|
||||||
|
<a href="#" appStopClick (click)="sendEmail(true)" [appApiAction]="emailPromise"
|
||||||
|
*ngIf="selectedProviderType === providerType.Email">
|
||||||
|
{{'sendVerificationCodeEmailAgain' | i18n}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<ng-template #twoFactorOptions></ng-template>
|
||||||
58
src/app/accounts/two-factor.component.ts
Normal file
58
src/app/accounts/two-factor.component.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import {
|
||||||
|
Component,
|
||||||
|
ComponentFactoryResolver,
|
||||||
|
ViewChild,
|
||||||
|
ViewContainerRef,
|
||||||
|
} from '@angular/core';
|
||||||
|
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
import { ToasterService } from 'angular2-toaster';
|
||||||
|
import { Angulartics2 } from 'angulartics2';
|
||||||
|
|
||||||
|
import { TwoFactorOptionsComponent } from './two-factor-options.component';
|
||||||
|
|
||||||
|
import { TwoFactorProviderType } from 'jslib/enums/twoFactorProviderType';
|
||||||
|
|
||||||
|
import { ApiService } from 'jslib/abstractions/api.service';
|
||||||
|
import { AuthService } from 'jslib/abstractions/auth.service';
|
||||||
|
import { EnvironmentService } from 'jslib/abstractions/environment.service';
|
||||||
|
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||||
|
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||||
|
import { SyncService } from 'jslib/abstractions/sync.service';
|
||||||
|
|
||||||
|
import { ModalComponent } from 'jslib/angular/components/modal.component';
|
||||||
|
import { TwoFactorComponent as BaseTwoFactorComponent } from 'jslib/angular/components/two-factor.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-two-factor',
|
||||||
|
templateUrl: 'two-factor.component.html',
|
||||||
|
})
|
||||||
|
export class TwoFactorComponent extends BaseTwoFactorComponent {
|
||||||
|
@ViewChild('twoFactorOptions', { read: ViewContainerRef }) twoFactorOptionsModal: ViewContainerRef;
|
||||||
|
|
||||||
|
constructor(authService: AuthService, router: Router,
|
||||||
|
analytics: Angulartics2, toasterService: ToasterService,
|
||||||
|
i18nService: I18nService, apiService: ApiService,
|
||||||
|
platformUtilsService: PlatformUtilsService, syncService: SyncService,
|
||||||
|
environmentService: EnvironmentService, private componentFactoryResolver: ComponentFactoryResolver) {
|
||||||
|
super(authService, router, analytics, toasterService, i18nService, apiService,
|
||||||
|
platformUtilsService, syncService, window, environmentService);
|
||||||
|
}
|
||||||
|
|
||||||
|
anotherMethod() {
|
||||||
|
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
|
||||||
|
const modal = this.twoFactorOptionsModal.createComponent(factory).instance;
|
||||||
|
const childComponent = modal.show<TwoFactorOptionsComponent>(TwoFactorOptionsComponent,
|
||||||
|
this.twoFactorOptionsModal);
|
||||||
|
|
||||||
|
childComponent.onProviderSelected.subscribe(async (provider: TwoFactorProviderType) => {
|
||||||
|
modal.close();
|
||||||
|
this.selectedProviderType = provider;
|
||||||
|
await this.init();
|
||||||
|
});
|
||||||
|
childComponent.onRecoverSelected.subscribe(() => {
|
||||||
|
modal.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/app/app-routing.module.ts
Normal file
31
src/app/app-routing.module.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import {
|
||||||
|
RouterModule,
|
||||||
|
Routes,
|
||||||
|
} from '@angular/router';
|
||||||
|
|
||||||
|
import { AuthGuardService } from 'jslib/angular/services/auth-guard.service';
|
||||||
|
|
||||||
|
import { LoginComponent } from './accounts/login.component';
|
||||||
|
import { TwoFactorComponent } from './accounts/two-factor.component';
|
||||||
|
import { DashboardComponent } from './dashboard/dashboard.component';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
|
||||||
|
{ path: 'login', component: LoginComponent },
|
||||||
|
{ path: '2fa', component: TwoFactorComponent },
|
||||||
|
{
|
||||||
|
path: 'dashboard',
|
||||||
|
component: DashboardComponent,
|
||||||
|
canActivate: [AuthGuardService],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forRoot(routes, {
|
||||||
|
useHash: true,
|
||||||
|
/*enableTracing: true,*/
|
||||||
|
})],
|
||||||
|
exports: [RouterModule],
|
||||||
|
})
|
||||||
|
export class AppRoutingModule { }
|
||||||
153
src/app/app.component.ts
Normal file
153
src/app/app.component.ts
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
import {
|
||||||
|
ToasterConfig,
|
||||||
|
ToasterContainerComponent,
|
||||||
|
} from 'angular2-toaster';
|
||||||
|
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
ComponentFactoryResolver,
|
||||||
|
NgZone,
|
||||||
|
OnDestroy,
|
||||||
|
OnInit,
|
||||||
|
Type,
|
||||||
|
ViewChild,
|
||||||
|
ViewContainerRef,
|
||||||
|
} from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
import { ToasterService } from 'angular2-toaster';
|
||||||
|
import { Angulartics2 } from 'angulartics2';
|
||||||
|
|
||||||
|
import { ModalComponent } from 'jslib/angular/components/modal.component';
|
||||||
|
|
||||||
|
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
|
||||||
|
|
||||||
|
import { AuthService } from 'jslib/abstractions/auth.service';
|
||||||
|
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
||||||
|
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||||
|
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||||
|
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||||
|
import { StorageService } from 'jslib/abstractions/storage.service';
|
||||||
|
import { TokenService } from 'jslib/abstractions/token.service';
|
||||||
|
import { UserService } from 'jslib/abstractions/user.service';
|
||||||
|
|
||||||
|
import { ConstantsService } from 'jslib/services/constants.service';
|
||||||
|
|
||||||
|
const BroadcasterSubscriptionId = 'AppComponent';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-root',
|
||||||
|
styles: [],
|
||||||
|
template: `
|
||||||
|
<toaster-container [toasterconfig]="toasterConfig"></toaster-container>
|
||||||
|
<ng-template #settings></ng-template>
|
||||||
|
<router-outlet></router-outlet>`,
|
||||||
|
})
|
||||||
|
export class AppComponent implements OnInit {
|
||||||
|
@ViewChild('settings', { read: ViewContainerRef }) settingsRef: ViewContainerRef;
|
||||||
|
|
||||||
|
toasterConfig: ToasterConfig = new ToasterConfig({
|
||||||
|
showCloseButton: true,
|
||||||
|
mouseoverTimerStop: true,
|
||||||
|
animation: 'flyRight',
|
||||||
|
limit: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
private lastActivity: number = null;
|
||||||
|
private modal: ModalComponent = null;
|
||||||
|
|
||||||
|
constructor(private angulartics2GoogleAnalytics: Angulartics2GoogleAnalytics,
|
||||||
|
private broadcasterService: BroadcasterService, private userService: UserService,
|
||||||
|
private tokenService: TokenService, private storageService: StorageService,
|
||||||
|
private authService: AuthService, private router: Router, private analytics: Angulartics2,
|
||||||
|
private toasterService: ToasterService, private i18nService: I18nService,
|
||||||
|
private platformUtilsService: PlatformUtilsService, private ngZone: NgZone,
|
||||||
|
private cryptoService: CryptoService, private componentFactoryResolver: ComponentFactoryResolver,
|
||||||
|
private messagingService: MessagingService) { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.ngZone.runOutsideAngular(() => {
|
||||||
|
setTimeout(async () => {
|
||||||
|
await this.updateAppMenu();
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
window.onmousemove = () => this.recordActivity();
|
||||||
|
window.onmousedown = () => this.recordActivity();
|
||||||
|
window.ontouchstart = () => this.recordActivity();
|
||||||
|
window.onclick = () => this.recordActivity();
|
||||||
|
window.onscroll = () => this.recordActivity();
|
||||||
|
window.onkeypress = () => this.recordActivity();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
|
||||||
|
this.ngZone.run(async () => {
|
||||||
|
switch (message.command) {
|
||||||
|
case 'loggedIn':
|
||||||
|
case 'unlocked':
|
||||||
|
case 'loggedOut':
|
||||||
|
this.updateAppMenu();
|
||||||
|
break;
|
||||||
|
case 'logout':
|
||||||
|
this.logOut(!!message.expired);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async updateAppMenu() {
|
||||||
|
this.messagingService.send('updateAppMenu', {
|
||||||
|
isAuthenticated: await this.userService.isAuthenticated(),
|
||||||
|
isLocked: (await this.cryptoService.getKey()) == null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async logOut(expired: boolean) {
|
||||||
|
const userId = await this.userService.getUserId();
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
this.tokenService.clearToken(),
|
||||||
|
this.cryptoService.clearKeys(),
|
||||||
|
this.userService.clear(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.authService.logOut(async () => {
|
||||||
|
this.analytics.eventTrack.next({ action: 'Logged Out' });
|
||||||
|
if (expired) {
|
||||||
|
this.toasterService.popAsync('warning', this.i18nService.t('loggedOut'),
|
||||||
|
this.i18nService.t('loginExpired'));
|
||||||
|
}
|
||||||
|
this.router.navigate(['login']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async recordActivity() {
|
||||||
|
const now = (new Date()).getTime();
|
||||||
|
if (this.lastActivity != null && now - this.lastActivity < 250) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastActivity = now;
|
||||||
|
this.storageService.save(ConstantsService.lastActiveKey, now);
|
||||||
|
}
|
||||||
|
|
||||||
|
private openModal<T>(type: Type<T>, ref: ViewContainerRef) {
|
||||||
|
if (this.modal != null) {
|
||||||
|
this.modal.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
|
||||||
|
this.modal = ref.createComponent(factory).instance;
|
||||||
|
const childComponent = this.modal.show<T>(type, ref);
|
||||||
|
|
||||||
|
this.modal.onClosed.subscribe(() => {
|
||||||
|
this.modal = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
79
src/app/app.module.ts
Normal file
79
src/app/app.module.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import 'core-js';
|
||||||
|
import 'zone.js/dist/zone';
|
||||||
|
|
||||||
|
import { ToasterModule } from 'angular2-toaster';
|
||||||
|
import { Angulartics2Module } from 'angulartics2';
|
||||||
|
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
|
||||||
|
|
||||||
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
|
import { ServicesModule } from './services.module';
|
||||||
|
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
|
|
||||||
|
import { IconComponent } from 'jslib/angular/components/icon.component';
|
||||||
|
import { ModalComponent } from 'jslib/angular/components/modal.component';
|
||||||
|
|
||||||
|
import { EnvironmentComponent } from './accounts/environment.component';
|
||||||
|
import { LoginComponent } from './accounts/login.component';
|
||||||
|
import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component';
|
||||||
|
import { TwoFactorComponent } from './accounts/two-factor.component';
|
||||||
|
import { DashboardComponent } from './dashboard/dashboard.component';
|
||||||
|
|
||||||
|
import { ApiActionDirective } from 'jslib/angular/directives/api-action.directive';
|
||||||
|
import { AutofocusDirective } from 'jslib/angular/directives/autofocus.directive';
|
||||||
|
import { BlurClickDirective } from 'jslib/angular/directives/blur-click.directive';
|
||||||
|
import { BoxRowDirective } from 'jslib/angular/directives/box-row.directive';
|
||||||
|
import { FallbackSrcDirective } from 'jslib/angular/directives/fallback-src.directive';
|
||||||
|
import { StopClickDirective } from 'jslib/angular/directives/stop-click.directive';
|
||||||
|
import { StopPropDirective } from 'jslib/angular/directives/stop-prop.directive';
|
||||||
|
|
||||||
|
import { I18nPipe } from 'jslib/angular/pipes/i18n.pipe';
|
||||||
|
import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
BrowserModule,
|
||||||
|
BrowserAnimationsModule,
|
||||||
|
FormsModule,
|
||||||
|
AppRoutingModule,
|
||||||
|
ServicesModule,
|
||||||
|
Angulartics2Module.forRoot([Angulartics2GoogleAnalytics], {
|
||||||
|
pageTracking: {
|
||||||
|
clearQueryParams: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
ToasterModule,
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
ApiActionDirective,
|
||||||
|
AppComponent,
|
||||||
|
AutofocusDirective,
|
||||||
|
BlurClickDirective,
|
||||||
|
BoxRowDirective,
|
||||||
|
DashboardComponent,
|
||||||
|
EnvironmentComponent,
|
||||||
|
FallbackSrcDirective,
|
||||||
|
I18nPipe,
|
||||||
|
IconComponent,
|
||||||
|
LoginComponent,
|
||||||
|
ModalComponent,
|
||||||
|
SearchCiphersPipe,
|
||||||
|
StopClickDirective,
|
||||||
|
StopPropDirective,
|
||||||
|
TwoFactorComponent,
|
||||||
|
TwoFactorOptionsComponent,
|
||||||
|
],
|
||||||
|
entryComponents: [
|
||||||
|
EnvironmentComponent,
|
||||||
|
ModalComponent,
|
||||||
|
TwoFactorOptionsComponent,
|
||||||
|
],
|
||||||
|
providers: [],
|
||||||
|
bootstrap: [AppComponent],
|
||||||
|
})
|
||||||
|
export class AppModule { }
|
||||||
1
src/app/dashboard/dashboard.component.html
Normal file
1
src/app/dashboard/dashboard.component.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
The dashboard!!
|
||||||
24
src/app/dashboard/dashboard.component.ts
Normal file
24
src/app/dashboard/dashboard.component.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import {
|
||||||
|
Component,
|
||||||
|
ComponentFactoryResolver,
|
||||||
|
ViewChild,
|
||||||
|
ViewContainerRef,
|
||||||
|
} from '@angular/core';
|
||||||
|
|
||||||
|
import { ToasterService } from 'angular2-toaster';
|
||||||
|
import { Angulartics2 } from 'angulartics2';
|
||||||
|
|
||||||
|
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||||
|
|
||||||
|
import { ModalComponent } from 'jslib/angular/components/modal.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-dashboard',
|
||||||
|
templateUrl: 'dashboard.component.html',
|
||||||
|
})
|
||||||
|
export class DashboardComponent {
|
||||||
|
@ViewChild('settings', { read: ViewContainerRef }) settingsModal: ViewContainerRef;
|
||||||
|
|
||||||
|
constructor(analytics: Angulartics2, toasterService: ToasterService,
|
||||||
|
i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver) {}
|
||||||
|
}
|
||||||
17
src/app/main.ts
Normal file
17
src/app/main.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { enableProdMode } from '@angular/core';
|
||||||
|
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||||
|
|
||||||
|
import { isDev } from 'jslib/electron/utils';
|
||||||
|
|
||||||
|
// tslint:disable-next-line
|
||||||
|
require('../scss/styles.scss');
|
||||||
|
// tslint:disable-next-line
|
||||||
|
require('../../jslib/src/misc/duo.js');
|
||||||
|
|
||||||
|
import { AppModule } from './app.module';
|
||||||
|
|
||||||
|
if (!isDev()) {
|
||||||
|
enableProdMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
platformBrowserDynamic().bootstrapModule(AppModule);
|
||||||
129
src/app/services.module.ts
Normal file
129
src/app/services.module.ts
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import {
|
||||||
|
APP_INITIALIZER,
|
||||||
|
NgModule,
|
||||||
|
} from '@angular/core';
|
||||||
|
|
||||||
|
import { ToasterModule } from 'angular2-toaster';
|
||||||
|
|
||||||
|
import { ElectronLogService } from 'jslib/electron/services/electronLog.service';
|
||||||
|
import { ElectronPlatformUtilsService } from 'jslib/electron/services/electronPlatformUtils.service';
|
||||||
|
import { ElectronRendererMessagingService } from 'jslib/electron/services/electronRendererMessaging.service';
|
||||||
|
import { ElectronRendererSecureStorageService } from 'jslib/electron/services/electronRendererSecureStorage.service';
|
||||||
|
import { ElectronStorageService } from 'jslib/electron/services/electronStorage.service';
|
||||||
|
import { isDev } from 'jslib/electron/utils';
|
||||||
|
|
||||||
|
import { I18nService } from '../services/i18n.service';
|
||||||
|
|
||||||
|
import { AuthGuardService } from 'jslib/angular/services/auth-guard.service';
|
||||||
|
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
|
||||||
|
import { ValidationService } from 'jslib/angular/services/validation.service';
|
||||||
|
|
||||||
|
import { Analytics } from 'jslib/misc/analytics';
|
||||||
|
|
||||||
|
import { ApiService } from 'jslib/services/api.service';
|
||||||
|
import { AppIdService } from 'jslib/services/appId.service';
|
||||||
|
import { AuthService } from 'jslib/services/auth.service';
|
||||||
|
import { ConstantsService } from 'jslib/services/constants.service';
|
||||||
|
import { ContainerService } from 'jslib/services/container.service';
|
||||||
|
import { CryptoService } from 'jslib/services/crypto.service';
|
||||||
|
import { EnvironmentService } from 'jslib/services/environment.service';
|
||||||
|
import { NodeCryptoFunctionService } from 'jslib/services/nodeCryptoFunction.service';
|
||||||
|
import { StateService } from 'jslib/services/state.service';
|
||||||
|
import { TokenService } from 'jslib/services/token.service';
|
||||||
|
import { UserService } from 'jslib/services/user.service';
|
||||||
|
|
||||||
|
import { ApiService as ApiServiceAbstraction } from 'jslib/abstractions/api.service';
|
||||||
|
import { AppIdService as AppIdServiceAbstraction } from 'jslib/abstractions/appId.service';
|
||||||
|
import { AuthService as AuthServiceAbstraction } from 'jslib/abstractions/auth.service';
|
||||||
|
import { CryptoService as CryptoServiceAbstraction } from 'jslib/abstractions/crypto.service';
|
||||||
|
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from 'jslib/abstractions/cryptoFunction.service';
|
||||||
|
import { EnvironmentService as EnvironmentServiceAbstraction } from 'jslib/abstractions/environment.service';
|
||||||
|
import { I18nService as I18nServiceAbstraction } from 'jslib/abstractions/i18n.service';
|
||||||
|
import { LogService as LogServiceAbstraction } from 'jslib/abstractions/log.service';
|
||||||
|
import { MessagingService as MessagingServiceAbstraction } from 'jslib/abstractions/messaging.service';
|
||||||
|
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from 'jslib/abstractions/platformUtils.service';
|
||||||
|
import { StateService as StateServiceAbstraction } from 'jslib/abstractions/state.service';
|
||||||
|
import { StorageService as StorageServiceAbstraction } from 'jslib/abstractions/storage.service';
|
||||||
|
import { TokenService as TokenServiceAbstraction } from 'jslib/abstractions/token.service';
|
||||||
|
import { UserService as UserServiceAbstraction } from 'jslib/abstractions/user.service';
|
||||||
|
|
||||||
|
const logService = new ElectronLogService();
|
||||||
|
const i18nService = new I18nService(window.navigator.language, './locales');
|
||||||
|
const stateService = new StateService();
|
||||||
|
const platformUtilsService = new ElectronPlatformUtilsService(i18nService, true);
|
||||||
|
const broadcasterService = new BroadcasterService();
|
||||||
|
const messagingService = new ElectronRendererMessagingService(broadcasterService);
|
||||||
|
const storageService: StorageServiceAbstraction = new ElectronStorageService();
|
||||||
|
const secureStorageService: StorageServiceAbstraction = new ElectronRendererSecureStorageService();
|
||||||
|
const cryptoFunctionService: CryptoFunctionServiceAbstraction = new NodeCryptoFunctionService();
|
||||||
|
const cryptoService = new CryptoService(storageService, secureStorageService, cryptoFunctionService);
|
||||||
|
const appIdService = new AppIdService(storageService);
|
||||||
|
const tokenService = new TokenService(storageService);
|
||||||
|
const apiService = new ApiService(tokenService, platformUtilsService,
|
||||||
|
(expired: boolean) => messagingService.send('logout', { expired: expired }));
|
||||||
|
const environmentService = new EnvironmentService(apiService, storageService);
|
||||||
|
const userService = new UserService(tokenService, storageService);
|
||||||
|
const containerService = new ContainerService(cryptoService, platformUtilsService);
|
||||||
|
const authService = new AuthService(cryptoService, apiService,
|
||||||
|
userService, tokenService, appIdService, i18nService, platformUtilsService, messagingService);
|
||||||
|
|
||||||
|
const analytics = new Analytics(window, () => isDev(), platformUtilsService, storageService, appIdService);
|
||||||
|
containerService.attachToWindow(window);
|
||||||
|
environmentService.setUrlsFromStorage().then(() => {
|
||||||
|
// Do nothing
|
||||||
|
});
|
||||||
|
|
||||||
|
export function initFactory(): Function {
|
||||||
|
return async () => {
|
||||||
|
await i18nService.init();
|
||||||
|
await authService.init();
|
||||||
|
const htmlEl = window.document.documentElement;
|
||||||
|
htmlEl.classList.add('os_' + platformUtilsService.getDeviceString());
|
||||||
|
htmlEl.classList.add('locale_' + i18nService.translationLocale);
|
||||||
|
|
||||||
|
let installAction = null;
|
||||||
|
const installedVersion = await storageService.get<string>(ConstantsService.installedVersionKey);
|
||||||
|
const currentVersion = platformUtilsService.getApplicationVersion();
|
||||||
|
if (installedVersion == null) {
|
||||||
|
installAction = 'install';
|
||||||
|
} else if (installedVersion !== currentVersion) {
|
||||||
|
installAction = 'update';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (installAction != null) {
|
||||||
|
await storageService.save(ConstantsService.installedVersionKey, currentVersion);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
ToasterModule,
|
||||||
|
],
|
||||||
|
declarations: [],
|
||||||
|
providers: [
|
||||||
|
ValidationService,
|
||||||
|
AuthGuardService,
|
||||||
|
{ provide: AuthServiceAbstraction, useValue: authService },
|
||||||
|
{ provide: EnvironmentServiceAbstraction, useValue: environmentService },
|
||||||
|
{ provide: TokenServiceAbstraction, useValue: tokenService },
|
||||||
|
{ provide: I18nServiceAbstraction, useValue: i18nService },
|
||||||
|
{ provide: CryptoServiceAbstraction, useValue: cryptoService },
|
||||||
|
{ provide: PlatformUtilsServiceAbstraction, useValue: platformUtilsService },
|
||||||
|
{ provide: ApiServiceAbstraction, useValue: apiService },
|
||||||
|
{ provide: UserServiceAbstraction, useValue: userService },
|
||||||
|
{ provide: MessagingServiceAbstraction, useValue: messagingService },
|
||||||
|
{ provide: BroadcasterService, useValue: broadcasterService },
|
||||||
|
{ provide: StorageServiceAbstraction, useValue: storageService },
|
||||||
|
{ provide: StateServiceAbstraction, useValue: stateService },
|
||||||
|
{ provide: LogServiceAbstraction, useValue: logService },
|
||||||
|
{
|
||||||
|
provide: APP_INITIALIZER,
|
||||||
|
useFactory: initFactory,
|
||||||
|
deps: [],
|
||||||
|
multi: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class ServicesModule {
|
||||||
|
}
|
||||||
BIN
src/images/icon.png
Normal file
BIN
src/images/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
6
src/images/loading.svg
Normal file
6
src/images/loading.svg
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 100% 100%">
|
||||||
|
<text fill="%23333333" x="50%" y="50%" font-family="\'Open Sans\', \'Helvetica Neue\', Helvetica, Arial, sans-serif"
|
||||||
|
font-size="18" text-anchor="middle">
|
||||||
|
Loading...
|
||||||
|
</text>
|
||||||
|
</svg>
|
||||||
BIN
src/images/logo.png
Normal file
BIN
src/images/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
BIN
src/images/logo@2x.png
Normal file
BIN
src/images/logo@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
BIN
src/images/logo@3x.png
Normal file
BIN
src/images/logo@3x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.2 KiB |
BIN
src/images/u2fkey.jpg
Normal file
BIN
src/images/u2fkey.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 174 KiB |
BIN
src/images/yubikey.jpg
Normal file
BIN
src/images/yubikey.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
5
src/locales/en/messages.json
Normal file
5
src/locales/en/messages.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"hello": {
|
||||||
|
"message": "hello"
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/services/i18n.service.ts
Normal file
15
src/services/i18n.service.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
import { I18nService as BaseI18nService } from 'jslib/services/i18n.service';
|
||||||
|
|
||||||
|
export class I18nService extends BaseI18nService {
|
||||||
|
constructor(systemLanguage: string, localesDirectory: string) {
|
||||||
|
super(systemLanguage, localesDirectory, (formattedLocale: string) => {
|
||||||
|
const filePath = path.join(__dirname, this.localesDirectory + '/' + formattedLocale + '/messages.json');
|
||||||
|
const localesJson = fs.readFileSync(filePath, 'utf8');
|
||||||
|
const locales = JSON.parse(localesJson.replace(/^\uFEFF/, '')); // strip the BOM
|
||||||
|
return Promise.resolve(locales);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules",
|
"node_modules",
|
||||||
"jslib/node_modules",
|
"jslib/node_modules",
|
||||||
|
"jslib/src/services/webCryptoFunction.service.ts",
|
||||||
"dist",
|
"dist",
|
||||||
"jslib/dist",
|
"jslib/dist",
|
||||||
"build",
|
"build",
|
||||||
|
|||||||
Reference in New Issue
Block a user