From 579f970323ea0bbc0d26f17cb9454c142f47bf6a Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Wed, 4 Apr 2018 08:22:55 -0400 Subject: [PATCH] move various angular bits into shared jslib --- package-lock.json | 146 +++++++++++++++++- package.json | 32 ++-- .../directives/api-action.directive.ts | 32 ++++ src/angular/directives/autofocus.directive.ts | 24 +++ .../directives/blur-click.directive.ts | 17 ++ .../directives/fallback-src.directive.ts | 20 +++ .../directives/stop-click.directive.ts | 13 ++ src/angular/directives/stop-prop.directive.ts | 13 ++ src/angular/pipes/i18n.pipe.ts | 17 ++ src/angular/services/auth-guard.service.ts | 31 ++++ src/angular/services/validation.service.ts | 37 +++++ 11 files changed, 366 insertions(+), 16 deletions(-) create mode 100644 src/angular/directives/api-action.directive.ts create mode 100644 src/angular/directives/autofocus.directive.ts create mode 100644 src/angular/directives/blur-click.directive.ts create mode 100644 src/angular/directives/fallback-src.directive.ts create mode 100644 src/angular/directives/stop-click.directive.ts create mode 100644 src/angular/directives/stop-prop.directive.ts create mode 100644 src/angular/pipes/i18n.pipe.ts create mode 100644 src/angular/services/auth-guard.service.ts create mode 100644 src/angular/services/validation.service.ts diff --git a/package-lock.json b/package-lock.json index c7cf6b454d..4a8f70fe13 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,9 +1,99 @@ { "name": "@bitwarden/jslib", - "version": "0.0.20", + "version": "0.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { + "@angular/animations": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-5.2.0.tgz", + "integrity": "sha512-JLR42YHiJppO4ruAkFxgbzghUDtHkXHkKPM8udd2qyt16T7e1OX7EEOrrmldUu59CC56tZnJ/32p4SrYmxyBSA==", + "dev": true, + "requires": { + "tslib": "1.9.0" + } + }, + "@angular/common": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-5.2.0.tgz", + "integrity": "sha512-yMFn2isC7/XOs56/2Kzzbb1AASHiwipAPOVFtKe7TdZQClO8fJXwCnk326rzr615+CG0eSBNQWeiFGyWN2riBA==", + "dev": true, + "requires": { + "tslib": "1.9.0" + } + }, + "@angular/compiler": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-5.2.0.tgz", + "integrity": "sha512-RfYa4ESgjGX0T0ob/Xz00IF7nd2xZkoyRy6oKgL82q42uzB3xZUDMrFNgeGxAUs3H22IkL46/5SSPOMOTMZ0NA==", + "dev": true, + "requires": { + "tslib": "1.9.0" + } + }, + "@angular/core": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-5.2.0.tgz", + "integrity": "sha512-s2ne45DguNUubhC1YgybGECC4Tyx3G4EZCntUiRMDWWkmKXSK+6dgHMesyDo8R5Oat8VfN4Anf8l3JHS1He8kg==", + "dev": true, + "requires": { + "tslib": "1.9.0" + } + }, + "@angular/forms": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-5.2.0.tgz", + "integrity": "sha512-g1/SF9lY0ZwzJ0w4NXbFsTGGEuUdgtaZny8DmkaqtmA7idby3FW398X0tv25KQfVYKtL+p9Jp1Y8EI0CvrIsvw==", + "dev": true, + "requires": { + "tslib": "1.9.0" + } + }, + "@angular/http": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@angular/http/-/http-5.2.0.tgz", + "integrity": "sha512-V5Cl24dP3rCXTTQvDc0TIKoWqBRAa0DWAQbtr7iuDAt5a1vPGdKz5K1sEiiV6ziwX6gzjiwHjUvL+B+WbIUrQA==", + "dev": true, + "requires": { + "tslib": "1.9.0" + } + }, + "@angular/platform-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-5.2.0.tgz", + "integrity": "sha512-c6cR15MfopPwGZ097HdRuAi9+R9BhA3bRRFpP2HmrSSB/BW4ZNovUYwB2QUMSYbd9s0lYTtnavqGm6DKcyF2QA==", + "dev": true, + "requires": { + "tslib": "1.9.0" + } + }, + "@angular/platform-browser-dynamic": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-5.2.0.tgz", + "integrity": "sha512-xG1eNoi8sm4Jcly2y98r5mqYVe3XV8sUJCtOhvGBYtvt4dKEQ5tOns6fWQ0nUbl6Vv3Y0xgGUS1JCtfut3DuaQ==", + "dev": true, + "requires": { + "tslib": "1.9.0" + } + }, + "@angular/router": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-5.2.0.tgz", + "integrity": "sha512-VXDXtp2A1GQEUEhXg0ZzqHdTUERLgDSo3/Mmpzt+dgLMKlXDSCykcm4gINwE5VQLGD1zQvDFCCRv3seGRNfrqA==", + "dev": true, + "requires": { + "tslib": "1.9.0" + } + }, + "@angular/upgrade": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@angular/upgrade/-/upgrade-5.2.0.tgz", + "integrity": "sha512-ezWfhBCiP7RX+59scxfYfjDMRw+qq0BVbm/EfOXdYFU0NHWo7lXJ3v+cUi18G+5GVjzwRiJDIKWhw1QEyq2nug==", + "dev": true, + "requires": { + "tslib": "1.9.0" + } + }, "@types/fs-extra": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-4.0.0.tgz", @@ -34,7 +124,8 @@ "@types/lunr": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@types/lunr/-/lunr-2.1.5.tgz", - "integrity": "sha512-esk3CG25hRtHsVHm+LOjiSFYdw8be3uIY653WUwR43Bro914HSimPgPpqgajkhTJ0awK3RQfaIxP7zvbtCpcyg==" + "integrity": "sha512-esk3CG25hRtHsVHm+LOjiSFYdw8be3uIY653WUwR43Bro914HSimPgPpqgajkhTJ0awK3RQfaIxP7zvbtCpcyg==", + "dev": true }, "@types/marked": { "version": "0.3.0", @@ -57,7 +148,8 @@ "@types/node-forge": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-0.7.1.tgz", - "integrity": "sha1-XoS4q/QthOxenQg+jHVzsxVcYzQ=" + "integrity": "sha1-XoS4q/QthOxenQg+jHVzsxVcYzQ=", + "dev": true }, "@types/shelljs": { "version": "0.7.0", @@ -71,7 +163,8 @@ "@types/webcrypto": { "version": "0.0.28", "resolved": "https://registry.npmjs.org/@types/webcrypto/-/webcrypto-0.0.28.tgz", - "integrity": "sha1-sGFgOQzQAPsgH2LrAqw0Hr9+YP4=" + "integrity": "sha1-sGFgOQzQAPsgH2LrAqw0Hr9+YP4=", + "dev": true }, "align-text": { "version": "0.1.4", @@ -90,6 +183,12 @@ "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", "dev": true }, + "angular2-toaster": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/angular2-toaster/-/angular2-toaster-4.0.2.tgz", + "integrity": "sha512-/ndYYbV/7WZx6ujm6avFUqfb+FKbrx7oT+3mYj8i0o9N26Ug+BseFjy6oRnlVVedl39yRP6hhea81QgKmoYbbQ==", + "dev": true + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -163,6 +262,12 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "core-js": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz", + "integrity": "sha1-TekR5mew6ukSTjQlS1OupvxhjT4=", + "dev": true + }, "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", @@ -305,7 +410,8 @@ "lunr": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.1.6.tgz", - "integrity": "sha512-ydJpB8CX8cZ/VE+KMaYaFcZ6+o2LruM6NG76VXdflYTgluvVemz1lW4anE+pyBbLvxJHZdvD1Jy/fOqdzAEJog==" + "integrity": "sha512-ydJpB8CX8cZ/VE+KMaYaFcZ6+o2LruM6NG76VXdflYTgluvVemz1lW4anE+pyBbLvxJHZdvD1Jy/fOqdzAEJog==", + "dev": true }, "marked": { "version": "0.3.9", @@ -330,7 +436,8 @@ "node-forge": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.1.tgz", - "integrity": "sha1-naYR6giYL0uUIGs760zJZl8gwwA=" + "integrity": "sha1-naYR6giYL0uUIGs760zJZl8gwwA=", + "dev": true }, "once": { "version": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -408,6 +515,15 @@ "glob": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz" } }, + "rxjs": { + "version": "5.5.6", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.6.tgz", + "integrity": "sha512-v4Q5HDC0FHAQ7zcBX7T2IL6O5ltl1a2GX4ENjPXg6SjDY69Cmx9v4113C99a4wGF16ClPv5Z8mghuYorVkg/kg==", + "dev": true, + "requires": { + "symbol-observable": "1.0.1" + } + }, "shelljs": { "version": "0.7.8", "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", @@ -434,6 +550,18 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, + "symbol-observable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", + "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=", + "dev": true + }, + "tslib": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.0.tgz", + "integrity": "sha512-f/qGG2tUkrISBlQZEjEqoZ3B2+npJjIf04H1wuAv9iA8i04Icp+61KRXxFdha22670NJopsZCIjhC3SnjPRKrQ==", + "dev": true + }, "tslint": { "version": "5.9.1", "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.9.1.tgz", @@ -839,6 +967,12 @@ "decamelize": "1.2.0", "window-size": "0.1.0" } + }, + "zone.js": { + "version": "0.8.19", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.8.19.tgz", + "integrity": "sha512-l9rofaOs6a4y1W8zt4pDmnCUCnYG377dG+5SZlXNWrTWYUuXFqcJZiOarhYiRVR0NI9sH/8ooPJiz4uprB/Mkg==", + "dev": true } } } diff --git a/package.json b/package.json index 935e5f8359..8a4632c076 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/jslib", - "version": "0.0.20", + "version": "0.0.0", "description": "Common code used across Bitwarden JavaScript projects.", "keywords": [ "bitwarden" @@ -27,16 +27,28 @@ "pub": "npm run build && npm publish --access public" }, "devDependencies": { - "rimraf": "^2.6.2", - "tslint": "^5.8.0", - "typedoc": "^0.9.0", - "typescript": "^2.7.1" - }, - "dependencies": { - "lunr": "2.1.6", - "node-forge": "0.7.1", + "@angular/animations": "5.2.0", + "@angular/common": "5.2.0", + "@angular/compiler": "5.2.0", + "@angular/core": "5.2.0", + "@angular/forms": "5.2.0", + "@angular/http": "5.2.0", + "@angular/platform-browser": "5.2.0", + "@angular/platform-browser-dynamic": "5.2.0", + "@angular/router": "5.2.0", + "@angular/upgrade": "5.2.0", "@types/lunr": "2.1.5", "@types/node-forge": "0.7.1", - "@types/webcrypto": "0.0.28" + "@types/webcrypto": "0.0.28", + "angular2-toaster": "4.0.2", + "core-js": "2.4.1", + "lunr": "2.1.6", + "node-forge": "0.7.1", + "rimraf": "^2.6.2", + "rxjs": "5.5.6", + "tslint": "^5.8.0", + "typedoc": "^0.9.0", + "typescript": "^2.7.1", + "zone.js": "0.8.19" } } diff --git a/src/angular/directives/api-action.directive.ts b/src/angular/directives/api-action.directive.ts new file mode 100644 index 0000000000..a04eb47947 --- /dev/null +++ b/src/angular/directives/api-action.directive.ts @@ -0,0 +1,32 @@ +import { + Directive, + ElementRef, + Input, + OnChanges, +} from '@angular/core'; + +import { ValidationService } from '../services/validation.service'; + +@Directive({ + selector: '[appApiAction]', +}) +export class ApiActionDirective implements OnChanges { + @Input() appApiAction: Promise; + + constructor(private el: ElementRef, private validationService: ValidationService) { } + + ngOnChanges(changes: any) { + if (this.appApiAction == null || this.appApiAction.then == null) { + return; + } + + this.el.nativeElement.loading = true; + + this.appApiAction.then((response: any) => { + this.el.nativeElement.loading = false; + }, (e: any) => { + this.el.nativeElement.loading = false; + this.validationService.showError(e); + }); + } +} diff --git a/src/angular/directives/autofocus.directive.ts b/src/angular/directives/autofocus.directive.ts new file mode 100644 index 0000000000..dd708e5ae4 --- /dev/null +++ b/src/angular/directives/autofocus.directive.ts @@ -0,0 +1,24 @@ +import { + Directive, + ElementRef, + Input, +} from '@angular/core'; + +@Directive({ + selector: '[appAutofocus]', +}) +export class AutofocusDirective { + @Input() set appAutofocus(condition: boolean | string) { + this.autofocus = condition === '' || condition === true; + } + + private autofocus: boolean; + + constructor(private el: ElementRef) { } + + ngOnInit() { + if (this.autofocus) { + this.el.nativeElement.focus(); + } + } +} diff --git a/src/angular/directives/blur-click.directive.ts b/src/angular/directives/blur-click.directive.ts new file mode 100644 index 0000000000..48555bb9ae --- /dev/null +++ b/src/angular/directives/blur-click.directive.ts @@ -0,0 +1,17 @@ +import { + Directive, + ElementRef, + HostListener, +} from '@angular/core'; + +@Directive({ + selector: '[appBlurClick]', +}) +export class BlurClickDirective { + constructor(private el: ElementRef) { + } + + @HostListener('click') onClick() { + this.el.nativeElement.blur(); + } +} diff --git a/src/angular/directives/fallback-src.directive.ts b/src/angular/directives/fallback-src.directive.ts new file mode 100644 index 0000000000..3e5e338144 --- /dev/null +++ b/src/angular/directives/fallback-src.directive.ts @@ -0,0 +1,20 @@ +import { + Directive, + ElementRef, + HostListener, + Input, +} from '@angular/core'; + +@Directive({ + selector: '[appFallbackSrc]', +}) +export class FallbackSrcDirective { + @Input('appFallbackSrc') appFallbackSrc: string; + + constructor(private el: ElementRef) { + } + + @HostListener('error') onError() { + this.el.nativeElement.src = this.appFallbackSrc; + } +} diff --git a/src/angular/directives/stop-click.directive.ts b/src/angular/directives/stop-click.directive.ts new file mode 100644 index 0000000000..0529556bc9 --- /dev/null +++ b/src/angular/directives/stop-click.directive.ts @@ -0,0 +1,13 @@ +import { + Directive, + HostListener, +} from '@angular/core'; + +@Directive({ + selector: '[appStopClick]', +}) +export class StopClickDirective { + @HostListener('click', ['$event']) onClick($event: MouseEvent) { + $event.preventDefault(); + } +} diff --git a/src/angular/directives/stop-prop.directive.ts b/src/angular/directives/stop-prop.directive.ts new file mode 100644 index 0000000000..b241f628cc --- /dev/null +++ b/src/angular/directives/stop-prop.directive.ts @@ -0,0 +1,13 @@ +import { + Directive, + HostListener, +} from '@angular/core'; + +@Directive({ + selector: '[appStopProp]', +}) +export class StopPropDirective { + @HostListener('click', ['$event']) onClick($event: MouseEvent) { + $event.stopPropagation(); + } +} diff --git a/src/angular/pipes/i18n.pipe.ts b/src/angular/pipes/i18n.pipe.ts new file mode 100644 index 0000000000..23115d1dbd --- /dev/null +++ b/src/angular/pipes/i18n.pipe.ts @@ -0,0 +1,17 @@ +import { + Pipe, + PipeTransform, +} from '@angular/core'; + +import { I18nService } from '../../abstractions/i18n.service'; + +@Pipe({ + name: 'i18n', +}) +export class I18nPipe implements PipeTransform { + constructor(private i18nService: I18nService) { } + + transform(id: string, p1?: string, p2?: string, p3?: string): string { + return this.i18nService.t(id, p1, p2, p3); + } +} diff --git a/src/angular/services/auth-guard.service.ts b/src/angular/services/auth-guard.service.ts new file mode 100644 index 0000000000..c388e2094c --- /dev/null +++ b/src/angular/services/auth-guard.service.ts @@ -0,0 +1,31 @@ +import { Injectable } from '@angular/core'; +import { + CanActivate, + Router, +} from '@angular/router'; + +import { CryptoService } from '../../abstractions/crypto.service'; +import { MessagingService } from '../../abstractions/messaging.service'; +import { UserService } from '../../abstractions/user.service'; + +@Injectable() +export class AuthGuardService implements CanActivate { + constructor(private cryptoService: CryptoService, private userService: UserService, private router: Router, + private messagingService: MessagingService) { } + + async canActivate() { + const isAuthed = await this.userService.isAuthenticated(); + if (!isAuthed) { + this.messagingService.send('logout'); + return false; + } + + const key = await this.cryptoService.getKey(); + if (key == null) { + this.router.navigate(['lock']); + return false; + } + + return true; + } +} diff --git a/src/angular/services/validation.service.ts b/src/angular/services/validation.service.ts new file mode 100644 index 0000000000..06c8bde835 --- /dev/null +++ b/src/angular/services/validation.service.ts @@ -0,0 +1,37 @@ +import { Injectable } from '@angular/core'; + +import { ToasterService } from 'angular2-toaster'; + +import { I18nService } from '../../abstractions/i18n.service'; + +@Injectable() +export class ValidationService { + constructor(private toasterService: ToasterService, private i18nService: I18nService) { } + + showError(data: any): string[] { + const defaultErrorMessage = this.i18nService.t('unexpectedError'); + const errors: string[] = []; + + if (data == null || typeof data !== 'object') { + errors.push(defaultErrorMessage); + } else if (data.validationErrors == null) { + errors.push(data.message ? data.message : defaultErrorMessage); + } else { + for (const key in data.validationErrors) { + if (!data.validationErrors.hasOwnProperty(key)) { + continue; + } + + data.validationErrors[key].forEach((item: string) => { + errors.push(item); + }); + } + } + + if (errors.length > 0) { + this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'), errors[0]); + } + + return errors; + } +}