From a4ff241574240be90789ed81bc1e088368a5a711 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Tue, 5 Jun 2018 15:02:53 -0400 Subject: [PATCH] setup services, accounts components --- jslib | 2 +- locales/en/messages.json | 5 + locales/es/messages.json | 5 + src/app/accounts/hint.component.html | 30 +++ src/app/accounts/hint.component.ts | 22 +++ src/app/accounts/login.component.html | 40 ++++ src/app/accounts/login.component.ts | 24 +++ src/app/accounts/register.component.html | 71 +++++++ src/app/accounts/register.component.ts | 25 +++ .../two-factor-options.component.html | 24 +++ .../accounts/two-factor-options.component.ts | 31 +++ src/app/accounts/two-factor.component.html | 108 +++++++++++ src/app/accounts/two-factor.component.ts | 50 +++++ src/app/app-routing.module.ts | 11 +- src/app/app.module.ts | 44 +++++ src/app/services.module.ts | 167 ++++++++++++++++ src/app/vault/vault.component.html | 2 +- src/locales/en/messages.json | 5 + src/locales/es/messages.json | 5 + src/services/i18n.service.ts | 16 ++ src/services/webMessaging.service.ts | 7 + src/services/webPlatformUtils.service.ts | 181 ++++++++++++++++++ src/services/webStorage.service.ts | 24 +++ tsconfig.json | 1 + webpack.config.js | 1 + 25 files changed, 898 insertions(+), 3 deletions(-) create mode 100644 locales/en/messages.json create mode 100644 locales/es/messages.json create mode 100644 src/app/accounts/hint.component.html create mode 100644 src/app/accounts/hint.component.ts create mode 100644 src/app/accounts/login.component.html create mode 100644 src/app/accounts/login.component.ts create mode 100644 src/app/accounts/register.component.html create mode 100644 src/app/accounts/register.component.ts create mode 100644 src/app/accounts/two-factor-options.component.html create mode 100644 src/app/accounts/two-factor-options.component.ts create mode 100644 src/app/accounts/two-factor.component.html create mode 100644 src/app/accounts/two-factor.component.ts create mode 100644 src/app/services.module.ts create mode 100644 src/locales/en/messages.json create mode 100644 src/locales/es/messages.json create mode 100644 src/services/i18n.service.ts create mode 100644 src/services/webMessaging.service.ts create mode 100644 src/services/webPlatformUtils.service.ts create mode 100644 src/services/webStorage.service.ts diff --git a/jslib b/jslib index 66b3dbae17..476d21e9f0 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit 66b3dbae177a795d6b5168f7cdbba2f98f309272 +Subproject commit 476d21e9f07f648784e92e6e7ec4ae37910e2449 diff --git a/locales/en/messages.json b/locales/en/messages.json new file mode 100644 index 0000000000..a02b3399a4 --- /dev/null +++ b/locales/en/messages.json @@ -0,0 +1,5 @@ +{ + "hello": { + "message": "hello world" + } +} diff --git a/locales/es/messages.json b/locales/es/messages.json new file mode 100644 index 0000000000..54364e7310 --- /dev/null +++ b/locales/es/messages.json @@ -0,0 +1,5 @@ +{ + "hello": { + "message": "hola mundo" + } +} diff --git a/src/app/accounts/hint.component.html b/src/app/accounts/hint.component.html new file mode 100644 index 0000000000..74cd8e246b --- /dev/null +++ b/src/app/accounts/hint.component.html @@ -0,0 +1,30 @@ +
+
+ +
+ {{'passwordHint' | i18n}} +
+
+ +
+
+ +
+
+
+ + +
+
+ +
+
+
diff --git a/src/app/accounts/hint.component.ts b/src/app/accounts/hint.component.ts new file mode 100644 index 0000000000..bb84ff02f9 --- /dev/null +++ b/src/app/accounts/hint.component.ts @@ -0,0 +1,22 @@ +import { Component } from '@angular/core'; +import { Router } from '@angular/router'; + +import { ToasterService } from 'angular2-toaster'; +import { Angulartics2 } from 'angulartics2'; + +import { ApiService } from 'jslib/abstractions/api.service'; +import { I18nService } from 'jslib/abstractions/i18n.service'; + +import { HintComponent as BaseHintComponent } from 'jslib/angular/components/hint.component'; + +@Component({ + selector: 'app-hint', + templateUrl: 'hint.component.html', +}) +export class HintComponent extends BaseHintComponent { + constructor(router: Router, analytics: Angulartics2, + toasterService: ToasterService, i18nService: I18nService, + apiService: ApiService) { + super(router, analytics, toasterService, i18nService, apiService); + } +} diff --git a/src/app/accounts/login.component.html b/src/app/accounts/login.component.html new file mode 100644 index 0000000000..2682c0946c --- /dev/null +++ b/src/app/accounts/login.component.html @@ -0,0 +1,40 @@ +
+
+

{{'loginOrCreateNewAccount' | i18n}}

+
+
+
+ + +
+
+
+ + +
+
+ + + +
+
+
+
+
+ + + {{'createAccount' | i18n}} + +
+ +
+
diff --git a/src/app/accounts/login.component.ts b/src/app/accounts/login.component.ts new file mode 100644 index 0000000000..3904d2bf9c --- /dev/null +++ b/src/app/accounts/login.component.ts @@ -0,0 +1,24 @@ +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 { SyncService } from 'jslib/abstractions/sync.service'; + +import { LoginComponent as BaseLoginComponent } from 'jslib/angular/components/login.component'; + +@Component({ + selector: 'app-login', + templateUrl: 'login.component.html', +}) +export class LoginComponent extends BaseLoginComponent { + constructor(authService: AuthService, router: Router, + analytics: Angulartics2, toasterService: ToasterService, + i18nService: I18nService, private syncService: SyncService) { + super(authService, router, analytics, toasterService, i18nService); + this.successRoute = '/vault'; + } +} diff --git a/src/app/accounts/register.component.html b/src/app/accounts/register.component.html new file mode 100644 index 0000000000..db6f2ecfa3 --- /dev/null +++ b/src/app/accounts/register.component.html @@ -0,0 +1,71 @@ +
+
+ +
+ {{'createAccount' | i18n}} +
+
+ +
+
+ +
+
+
+ + +
+
+
+ + +
+
+ + + +
+
+
+ +
+
+
+
+
+ + +
+
+ + + +
+
+
+ + +
+
+ +
+
+
diff --git a/src/app/accounts/register.component.ts b/src/app/accounts/register.component.ts new file mode 100644 index 0000000000..0867b5903c --- /dev/null +++ b/src/app/accounts/register.component.ts @@ -0,0 +1,25 @@ +import { Component } from '@angular/core'; +import { Router } from '@angular/router'; + +import { ToasterService } from 'angular2-toaster'; +import { Angulartics2 } from 'angulartics2'; + +import { ApiService } from 'jslib/abstractions/api.service'; +import { AuthService } from 'jslib/abstractions/auth.service'; +import { CryptoService } from 'jslib/abstractions/crypto.service'; +import { I18nService } from 'jslib/abstractions/i18n.service'; + +import { RegisterComponent as BaseRegisterComponent } from 'jslib/angular/components/register.component'; + +@Component({ + selector: 'app-register', + templateUrl: 'register.component.html', +}) +export class RegisterComponent extends BaseRegisterComponent { + constructor(authService: AuthService, router: Router, + analytics: Angulartics2, toasterService: ToasterService, + i18nService: I18nService, cryptoService: CryptoService, + apiService: ApiService) { + super(authService, router, analytics, toasterService, i18nService, cryptoService, apiService); + } +} diff --git a/src/app/accounts/two-factor-options.component.html b/src/app/accounts/two-factor-options.component.html new file mode 100644 index 0000000000..d2bbd3112a --- /dev/null +++ b/src/app/accounts/two-factor-options.component.html @@ -0,0 +1,24 @@ +
+ +
+ {{'twoStepOptions' | i18n}} +
+
+
+ + + diff --git a/src/app/accounts/two-factor-options.component.ts b/src/app/accounts/two-factor-options.component.ts new file mode 100644 index 0000000000..779d4813c1 --- /dev/null +++ b/src/app/accounts/two-factor-options.component.ts @@ -0,0 +1,31 @@ +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); + } + + choose(p: any) { + super.choose(p); + this.authService.selectedTwoFactorProviderType = p.type; + this.router.navigate(['2fa']); + } +} diff --git a/src/app/accounts/two-factor.component.html b/src/app/accounts/two-factor.component.html new file mode 100644 index 0000000000..e4acd405cc --- /dev/null +++ b/src/app/accounts/two-factor.component.html @@ -0,0 +1,108 @@ +
+
+ +
+ {{title}} +
+
+ +
+
+ + +
+ + {{'enterVerificationCodeApp' | i18n}} + + + {{'enterVerificationCodeEmail' | i18n : twoFactorEmail}} + +
+
+
+
+ + +
+
+ + +
+
+
+
+ +
+

{{'insertYubiKey' | i18n}}

+ +
+
+
+
+ + +
+
+ + +
+
+
+
+ +
+ +
+

{{'insertU2f' | i18n}}

+ +
+
+
+
+
+ + +
+
+
+
+ +
+
{{'twoStepNewWindowMessage' | i18n}}
+
+
+
+ + +
+
+
+
+
+

{{'noTwoStepProviders' | i18n}}

+

{{'noTwoStepProviders2' | i18n}}

+
+ +
+
+ diff --git a/src/app/accounts/two-factor.component.ts b/src/app/accounts/two-factor.component.ts new file mode 100644 index 0000000000..3155e52d5c --- /dev/null +++ b/src/app/accounts/two-factor.component.ts @@ -0,0 +1,50 @@ +import { + ChangeDetectorRef, + Component, + NgZone, + OnDestroy, + OnInit, +} from '@angular/core'; + +import { Router } from '@angular/router'; + +import { ToasterService } from 'angular2-toaster'; +import { Angulartics2 } from 'angulartics2'; + +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 { BroadcasterService } from 'jslib/angular/services/broadcaster.service'; + +import { TwoFactorComponent as BaseTwoFactorComponent } from 'jslib/angular/components/two-factor.component'; + +const BroadcasterSubscriptionId = 'TwoFactorComponent'; + +@Component({ + selector: 'app-two-factor', + templateUrl: 'two-factor.component.html', +}) +export class TwoFactorComponent extends BaseTwoFactorComponent { + showNewWindowMessage = false; + + constructor(authService: AuthService, router: Router, + analytics: Angulartics2, toasterService: ToasterService, + i18nService: I18nService, apiService: ApiService, + platformUtilsService: PlatformUtilsService, syncService: SyncService, + environmentService: EnvironmentService, private ngZone: NgZone, + private broadcasterService: BroadcasterService, private changeDetectorRef: ChangeDetectorRef) { + super(authService, router, analytics, toasterService, i18nService, apiService, + platformUtilsService, window, environmentService); + this.successRoute = '/vault'; + } + + anotherMethod() { + this.router.navigate(['2fa-options']); + } +} diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 4a8999a0fd..db27217f96 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -4,10 +4,19 @@ import { Routes, } from '@angular/router'; +import { HintComponent } from './accounts/hint.component'; +import { LoginComponent } from './accounts/login.component'; +import { RegisterComponent } from './accounts/register.component'; +import { TwoFactorComponent } from './accounts/two-factor.component'; + import { VaultComponent } from './vault/vault.component'; const routes: Routes = [ - { path: '', redirectTo: '/vault', pathMatch: 'full' }, + { path: '', redirectTo: '/login', pathMatch: 'full' }, + { path: 'login', component: LoginComponent }, + { path: '2fa', component: TwoFactorComponent }, + { path: 'register', component: RegisterComponent }, + { path: 'hint', component: HintComponent }, { path: 'vault', component: VaultComponent, diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 2c88742b84..712261987b 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,6 +1,10 @@ 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 { NgModule } from '@angular/core'; @@ -8,19 +12,59 @@ import { FormsModule } from '@angular/forms'; import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { ServicesModule } from './services.module'; + import { AppComponent } from './app.component'; +import { HintComponent } from './accounts/hint.component'; +import { LoginComponent } from './accounts/login.component'; +import { RegisterComponent } from './accounts/register.component'; +import { TwoFactorComponent } from './accounts/two-factor.component'; + import { VaultComponent } from './vault/vault.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 { InputVerbatimDirective } from 'jslib/angular/directives/input-verbatim.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, + FallbackSrcDirective, + HintComponent, + I18nPipe, + InputVerbatimDirective, + LoginComponent, + RegisterComponent, + SearchCiphersPipe, + StopClickDirective, + StopPropDirective, + TwoFactorComponent, VaultComponent, ], entryComponents: [ diff --git a/src/app/services.module.ts b/src/app/services.module.ts new file mode 100644 index 0000000000..9944a6895c --- /dev/null +++ b/src/app/services.module.ts @@ -0,0 +1,167 @@ +import { + APP_INITIALIZER, + NgModule, +} from '@angular/core'; + +import { ToasterModule } from 'angular2-toaster'; + +import { I18nService } from '../services/i18n.service'; +import { WebMessagingService } from '../services/webMessaging.service'; +import { WebPlatformUtilsService } from '../services/webPlatformUtils.service'; +import { WebStorageService } from '../services/webStorage.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 { AuditService } from 'jslib/services/audit.service'; +import { AuthService } from 'jslib/services/auth.service'; +import { CipherService } from 'jslib/services/cipher.service'; +import { CollectionService } from 'jslib/services/collection.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 { ExportService } from 'jslib/services/export.service'; +import { FolderService } from 'jslib/services/folder.service'; +import { LockService } from 'jslib/services/lock.service'; +import { PasswordGenerationService } from 'jslib/services/passwordGeneration.service'; +import { SettingsService } from 'jslib/services/settings.service'; +import { StateService } from 'jslib/services/state.service'; +import { SyncService } from 'jslib/services/sync.service'; +import { TokenService } from 'jslib/services/token.service'; +import { TotpService } from 'jslib/services/totp.service'; +import { UserService } from 'jslib/services/user.service'; +import { WebCryptoFunctionService } from 'jslib/services/webCryptoFunction.service'; + +import { ApiService as ApiServiceAbstraction } from 'jslib/abstractions/api.service'; +import { AppIdService as AppIdServiceAbstraction } from 'jslib/abstractions/appId.service'; +import { AuditService as AuditServiceAbstraction } from 'jslib/abstractions/audit.service'; +import { AuthService as AuthServiceAbstraction } from 'jslib/abstractions/auth.service'; +import { CipherService as CipherServiceAbstraction } from 'jslib/abstractions/cipher.service'; +import { CollectionService as CollectionServiceAbstraction } from 'jslib/abstractions/collection.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 { ExportService as ExportServiceAbstraction } from 'jslib/abstractions/export.service'; +import { FolderService as FolderServiceAbstraction } from 'jslib/abstractions/folder.service'; +import { I18nService as I18nServiceAbstraction } from 'jslib/abstractions/i18n.service'; +import { LockService as LockServiceAbstraction } from 'jslib/abstractions/lock.service'; +import { LogService as LogServiceAbstraction } from 'jslib/abstractions/log.service'; +import { MessagingService as MessagingServiceAbstraction } from 'jslib/abstractions/messaging.service'; +import { + PasswordGenerationService as PasswordGenerationServiceAbstraction, +} from 'jslib/abstractions/passwordGeneration.service'; +import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from 'jslib/abstractions/platformUtils.service'; +import { SettingsService as SettingsServiceAbstraction } from 'jslib/abstractions/settings.service'; +import { StateService as StateServiceAbstraction } from 'jslib/abstractions/state.service'; +import { StorageService as StorageServiceAbstraction } from 'jslib/abstractions/storage.service'; +import { SyncService as SyncServiceAbstraction } from 'jslib/abstractions/sync.service'; +import { TokenService as TokenServiceAbstraction } from 'jslib/abstractions/token.service'; +import { TotpService as TotpServiceAbstraction } from 'jslib/abstractions/totp.service'; +import { UserService as UserServiceAbstraction } from 'jslib/abstractions/user.service'; + +const i18nService = new I18nService(window.navigator.language, 'locales'); +const stateService = new StateService(); +const broadcasterService = new BroadcasterService(); +const messagingService = new WebMessagingService(); +const platformUtilsService = new WebPlatformUtilsService(messagingService); +const storageService: StorageServiceAbstraction = new WebStorageService(); +const cryptoFunctionService: CryptoFunctionServiceAbstraction = new WebCryptoFunctionService(window, + platformUtilsService); +const cryptoService = new CryptoService(storageService, storageService, cryptoFunctionService); +const tokenService = new TokenService(storageService); +const appIdService = new AppIdService(storageService); +const apiService = new ApiService(tokenService, platformUtilsService, + async (expired: boolean) => messagingService.send('logout', { expired: expired })); +const environmentService = new EnvironmentService(apiService, storageService); +const userService = new UserService(tokenService, storageService); +const settingsService = new SettingsService(userService, storageService); +const cipherService = new CipherService(cryptoService, userService, settingsService, + apiService, storageService, i18nService, platformUtilsService); +const folderService = new FolderService(cryptoService, userService, + () => i18nService.t('noneFolder'), apiService, storageService, i18nService); +const collectionService = new CollectionService(cryptoService, userService, storageService, i18nService); +const lockService = new LockService(cipherService, folderService, collectionService, + cryptoService, platformUtilsService, storageService, messagingService, null); +const syncService = new SyncService(userService, apiService, settingsService, + folderService, cipherService, cryptoService, collectionService, storageService, messagingService, + async (expired: boolean) => messagingService.send('logout', { expired: expired })); +const passwordGenerationService = new PasswordGenerationService(cryptoService, storageService); +const totpService = new TotpService(storageService, cryptoFunctionService); +const containerService = new ContainerService(cryptoService, platformUtilsService); +const authService = new AuthService(cryptoService, apiService, + userService, tokenService, appIdService, i18nService, platformUtilsService, messagingService); +const exportService = new ExportService(folderService, cipherService); +const auditService = new AuditService(cryptoFunctionService); + +const analytics = new Analytics(window, () => platformUtilsService.isDev(), + platformUtilsService, storageService, appIdService); +containerService.attachToWindow(window); +environmentService.setUrlsFromStorage().then(() => { + return syncService.fullSync(true); +}); + +export function initFactory(): Function { + return async () => { + lockService.init(true); + const locale = await storageService.get(ConstantsService.localeKey); + await i18nService.init(locale); + await authService.init(); + const htmlEl = window.document.documentElement; + htmlEl.classList.add('os_' + platformUtilsService.getDeviceString()); + htmlEl.classList.add('locale_' + i18nService.translationLocale); + let theme = await storageService.get(ConstantsService.themeKey); + if (theme == null) { + theme = 'light'; + } + htmlEl.classList.add('theme_' + theme); + stateService.save(ConstantsService.disableFaviconKey, + await storageService.get(ConstantsService.disableFaviconKey)); + }; +} + +@NgModule({ + imports: [ + ToasterModule, + ], + declarations: [], + providers: [ + ValidationService, + AuthGuardService, + { provide: AuditServiceAbstraction, useValue: auditService }, + { provide: AuthServiceAbstraction, useValue: authService }, + { provide: CipherServiceAbstraction, useValue: cipherService }, + { provide: FolderServiceAbstraction, useValue: folderService }, + { provide: CollectionServiceAbstraction, useValue: collectionService }, + { provide: EnvironmentServiceAbstraction, useValue: environmentService }, + { provide: TotpServiceAbstraction, useValue: totpService }, + { provide: TokenServiceAbstraction, useValue: tokenService }, + { provide: I18nServiceAbstraction, useValue: i18nService }, + { provide: CryptoServiceAbstraction, useValue: cryptoService }, + { provide: PlatformUtilsServiceAbstraction, useValue: platformUtilsService }, + { provide: PasswordGenerationServiceAbstraction, useValue: passwordGenerationService }, + { provide: ApiServiceAbstraction, useValue: apiService }, + { provide: SyncServiceAbstraction, useValue: syncService }, + { provide: UserServiceAbstraction, useValue: userService }, + { provide: MessagingServiceAbstraction, useValue: messagingService }, + { provide: BroadcasterService, useValue: broadcasterService }, + { provide: SettingsServiceAbstraction, useValue: settingsService }, + { provide: LockServiceAbstraction, useValue: lockService }, + { provide: StorageServiceAbstraction, useValue: storageService }, + { provide: StateServiceAbstraction, useValue: stateService }, + { provide: ExportServiceAbstraction, useValue: exportService }, + { + provide: APP_INITIALIZER, + useFactory: initFactory, + deps: [], + multi: true, + }, + ], +}) +export class ServicesModule { +} diff --git a/src/app/vault/vault.component.html b/src/app/vault/vault.component.html index 12e73d8ccd..9999a5155f 100644 --- a/src/app/vault/vault.component.html +++ b/src/app/vault/vault.component.html @@ -1 +1 @@ -The vault!! +The vault!! {{ 'hello' | i18n }} diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json new file mode 100644 index 0000000000..a02b3399a4 --- /dev/null +++ b/src/locales/en/messages.json @@ -0,0 +1,5 @@ +{ + "hello": { + "message": "hello world" + } +} diff --git a/src/locales/es/messages.json b/src/locales/es/messages.json new file mode 100644 index 0000000000..54364e7310 --- /dev/null +++ b/src/locales/es/messages.json @@ -0,0 +1,5 @@ +{ + "hello": { + "message": "hola mundo" + } +} diff --git a/src/services/i18n.service.ts b/src/services/i18n.service.ts new file mode 100644 index 0000000000..b94183473d --- /dev/null +++ b/src/services/i18n.service.ts @@ -0,0 +1,16 @@ +import { I18nService as BaseI18nService } from 'jslib/services/i18n.service'; + +export class I18nService extends BaseI18nService { + constructor(systemLanguage: string, localesDirectory: string) { + super(systemLanguage, localesDirectory, async (formattedLocale: string) => { + const filePath = this.localesDirectory + '/' + formattedLocale + '/messages.json'; + const localesResult = await fetch(filePath); + const locales = await localesResult.json(); + return locales; + }); + + this.supportedTranslationLocales = [ + 'en', 'es', + ]; + } +} diff --git a/src/services/webMessaging.service.ts b/src/services/webMessaging.service.ts new file mode 100644 index 0000000000..07571ae288 --- /dev/null +++ b/src/services/webMessaging.service.ts @@ -0,0 +1,7 @@ +import { MessagingService } from 'jslib/abstractions/messaging.service'; + +export class WebMessagingService implements MessagingService { + send(subscriber: string, arg: any = {}) { + // + } +} diff --git a/src/services/webPlatformUtils.service.ts b/src/services/webPlatformUtils.service.ts new file mode 100644 index 0000000000..3908008693 --- /dev/null +++ b/src/services/webPlatformUtils.service.ts @@ -0,0 +1,181 @@ +import { DeviceType } from 'jslib/enums/deviceType'; + +import { MessagingService } from 'jslib/abstractions/messaging.service'; +import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; + +import { AnalyticsIds } from 'jslib/misc/analytics'; + +const DialogPromiseExpiration = 600000; // 10 minutes + +export class WebPlatformUtilsService implements PlatformUtilsService { + identityClientId: string = 'web'; + + private showDialogResolves = new Map void, date: Date }>(); + private deviceCache: DeviceType = null; + private analyticsIdCache: string = null; + + constructor(private messagingService: MessagingService) { } + + getDevice(): DeviceType { + if (this.deviceCache) { + return this.deviceCache; + } + + if (navigator.userAgent.indexOf(' Firefox/') !== -1 || navigator.userAgent.indexOf(' Gecko/') !== -1) { + this.deviceCache = DeviceType.Firefox; + } else if (navigator.userAgent.indexOf(' OPR/') >= 0) { + this.deviceCache = DeviceType.Opera; + } else if (navigator.userAgent.indexOf(' Edge/') !== -1) { + this.deviceCache = DeviceType.Edge; + } else if (navigator.userAgent.indexOf(' Vivaldi/') !== -1) { + this.deviceCache = DeviceType.Vivaldi; + } else if (navigator.userAgent.indexOf(' Safari/') !== -1 && navigator.userAgent.indexOf('Chrome') === -1) { + this.deviceCache = DeviceType.Safari; + } else if ((window as any).chrome && navigator.userAgent.indexOf(' Chrome/') !== -1) { + this.deviceCache = DeviceType.Chrome; + } + + return this.deviceCache; + } + + getDeviceString(): string { + return DeviceType[this.getDevice()].toLowerCase(); + } + + isFirefox(): boolean { + return this.getDevice() === DeviceType.Firefox; + } + + isChrome(): boolean { + return this.getDevice() === DeviceType.Chrome; + } + + isEdge(): boolean { + return this.getDevice() === DeviceType.Edge; + } + + isOpera(): boolean { + return this.getDevice() === DeviceType.Opera; + } + + isVivaldi(): boolean { + return this.getDevice() === DeviceType.Vivaldi; + } + + isSafari(): boolean { + return this.getDevice() === DeviceType.Safari; + } + + isMacAppStore(): boolean { + return false; + } + + analyticsId(): string { + if (this.analyticsIdCache) { + return this.analyticsIdCache; + } + + this.analyticsIdCache = (AnalyticsIds as any)[this.getDevice()]; + return this.analyticsIdCache; + } + + getDomain(uriString: string): string { + return uriString; + } + + isViewOpen(): boolean { + return false; + } + + launchUri(uri: string, options?: any): void { + // + } + + saveFile(win: Window, blobData: any, blobOptions: any, fileName: string): void { + // + } + + getApplicationVersion(): string { + return '1.2.3'; + } + + supportsU2f(win: Window): boolean { + if (win != null && (win as any).u2f != null) { + return true; + } + + return this.isChrome() || this.isOpera(); + } + + supportsDuo(): boolean { + return true; + } + + showToast(type: 'error' | 'success' | 'warning' | 'info', title: string, text: string, global?: any): void { + throw new Error('showToast not implemented'); + } + + showDialog(text: string, title?: string, confirmText?: string, cancelText?: string, type?: string) { + const dialogId = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); + this.messagingService.send('showDialog', { + text: text, + title: title, + confirmText: confirmText, + cancelText: cancelText, + type: type, + dialogId: dialogId, + }); + return new Promise((resolve) => { + this.showDialogResolves.set(dialogId, { resolve: resolve, date: new Date() }); + }); + } + + isDev(): boolean { + return process.env.ENV === 'development'; + } + + copyToClipboard(text: string, options?: any): void { + const doc = options ? options.doc : window.document; + if ((window as any).clipboardData && (window as any).clipboardData.setData) { + // IE specific code path to prevent textarea being shown while dialog is visible. + (window as any).clipboardData.setData('Text', text); + } else if (doc.queryCommandSupported && doc.queryCommandSupported('copy')) { + const textarea = doc.createElement('textarea'); + textarea.textContent = text; + // Prevent scrolling to bottom of page in MS Edge. + textarea.style.position = 'fixed'; + doc.body.appendChild(textarea); + textarea.select(); + + try { + // Security exception may be thrown by some browsers. + doc.execCommand('copy'); + } catch (e) { + // tslint:disable-next-line + console.warn('Copy to clipboard failed.', e); + } finally { + doc.body.removeChild(textarea); + } + } + } + + resolveDialogPromise(dialogId: number, confirmed: boolean) { + if (this.showDialogResolves.has(dialogId)) { + const resolveObj = this.showDialogResolves.get(dialogId); + resolveObj.resolve(confirmed); + this.showDialogResolves.delete(dialogId); + } + + // Clean up old promises + const deleteIds: number[] = []; + this.showDialogResolves.forEach((val, key) => { + const age = new Date().getTime() - val.date.getTime(); + if (age > DialogPromiseExpiration) { + deleteIds.push(key); + } + }); + deleteIds.forEach((id) => { + this.showDialogResolves.delete(id); + }); + } +} diff --git a/src/services/webStorage.service.ts b/src/services/webStorage.service.ts new file mode 100644 index 0000000000..239b080387 --- /dev/null +++ b/src/services/webStorage.service.ts @@ -0,0 +1,24 @@ +import { StorageService } from 'jslib/abstractions/storage.service'; + +export class WebStorageService implements StorageService { + private store: any = {}; + + get(key: string): Promise { + const val = this.store[key]; + if (val == null) { + return Promise.resolve(null); + } + return Promise.resolve(val as T); + } + + save(key: string, obj: any): Promise { + this.store[key] = obj; + return Promise.resolve(); + } + + remove(key: string): Promise { + delete this.store[key]; + return Promise.resolve(); + } + +} diff --git a/tsconfig.json b/tsconfig.json index f93ea545dd..b5ee7a3abf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -29,5 +29,6 @@ "jslib/src/angular/dummy.module.ts", "jslib/src/services/nodeApi.service.ts", "jslib/src/services/lowdbStorage.service.ts", + "jslib/src/misc/nodeUtils.ts", ] } diff --git a/webpack.config.js b/webpack.config.js index 265a0e89b2..d45e0f80ed 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -97,6 +97,7 @@ const plugins = [ { from: './src/browserconfig.xml' }, { from: './src/app-id.json' }, { from: './src/images', to: 'images' }, + { from: './src/locales', to: 'locales' }, ]), extractCss, new webpack.DefinePlugin({