From d007a521cc3a28ec23571b952bbf84a37187c3fc Mon Sep 17 00:00:00 2001 From: ManfredSteyer Date: Wed, 13 Sep 2017 23:44:03 +0200 Subject: [PATCH] fixed #103, #101, #99 --- angular-oauth2-oidc/src/base64-helper.ts | 5 +- angular-oauth2-oidc/src/oauth-service.ts | 62 +++++-- angular-oauth2-oidc/src/package.json | 2 +- sample - Kopie/.angular-cli.json | 62 +++++++ sample - Kopie/.editorconfig | 13 ++ sample - Kopie/.gitignore | 43 +++++ sample - Kopie/README.md | 28 ++++ sample - Kopie/e2e/app.e2e-spec.ts | 14 ++ sample - Kopie/e2e/app.po.ts | 11 ++ sample - Kopie/e2e/tsconfig.e2e.json | 14 ++ sample - Kopie/info.txt | 154 ++++++++++++++++++ sample - Kopie/karma.conf.js | 33 ++++ sample - Kopie/package.json | 51 ++++++ sample - Kopie/protractor.conf.js | 28 ++++ sample - Kopie/src/app/app.component.css | 0 sample - Kopie/src/app/app.component.html | 22 +++ sample - Kopie/src/app/app.component.spec.ts | 32 ++++ sample - Kopie/src/app/app.component.ts | 95 +++++++++++ sample - Kopie/src/app/app.module.ts | 46 ++++++ sample - Kopie/src/app/app.routes.ts | 40 +++++ sample - Kopie/src/app/app.tokens.ts | 3 + .../src/app/auth-password-flow.config.ts | 29 ++++ sample - Kopie/src/app/auth.config.ts | 26 +++ sample - Kopie/src/app/entities/flight.ts | 8 + .../alt-flight-card.component.html | 16 ++ .../alt-flight.card.component.ts | 17 ++ .../alt-flight-card/flight-list.ts | 28 ++++ .../flight-booking.component.html | 11 ++ .../flight-booking.component.ts | 9 + .../flight-booking/flight-booking.module.ts | 42 +++++ .../flight-booking/flight-booking.routes.ts | 33 ++++ .../flight-card/flight-card.component.html | 17 ++ .../flight-card/flight.card.component.ts | 17 ++ .../flight-edit/flight-edit.component.ts | 54 ++++++ .../flight-search-reactive.component.css | 13 ++ .../flight-search-reactive.component.html | 95 +++++++++++ .../flight-search-reactive.component.ts | 83 ++++++++++ .../flight-search/flight-search.component.css | 13 ++ .../flight-search.component.html | 124 ++++++++++++++ .../flight-search/flight-search.component.ts | 43 +++++ .../passenger-search.component.ts | 19 +++ .../flight-booking/services/flight.service.ts | 44 +++++ .../flight-history.component.ts | 16 ++ .../src/app/home/home.component.html | 51 ++++++ sample - Kopie/src/app/home/home.component.ts | 92 +++++++++++ .../password-flow-login.component.html | 53 ++++++ .../password-flow-login.component.ts | 78 +++++++++ .../src/app/shared/auth/auth.guard.ts | 11 ++ .../src/app/shared/date/date.component.ts | 50 ++++++ .../deactivation/LeaveComponentGuard.ts | 11 ++ .../src/app/shared/pipes/city.pipe.ts | 34 ++++ .../preload/custom-preloading.strategy.ts | 15 ++ .../src/app/shared/shared.module.ts | 46 ++++++ .../shared/validation/async-city.validator.ts | 33 ++++ .../app/shared/validation/city.validator.ts | 51 ++++++ .../shared/validation/roundtrip.validator.ts | 32 ++++ sample - Kopie/src/assets/.gitkeep | 0 .../src/environments/environment.prod.ts | 3 + .../src/environments/environment.ts | 8 + sample - Kopie/src/favicon.ico | Bin 0 -> 5430 bytes sample - Kopie/src/index.html | 53 ++++++ sample - Kopie/src/main.ts | 13 ++ sample - Kopie/src/polyfills.ts | 73 +++++++++ sample - Kopie/src/silent-refresh.html | 7 + sample - Kopie/src/styles.css | 1 + sample - Kopie/src/test.ts | 32 ++++ sample - Kopie/src/tsconfig.app.json | 14 ++ sample - Kopie/src/tsconfig.spec.json | 20 +++ sample - Kopie/src/typings.d.ts | 5 + sample - Kopie/tsconfig.json | 19 +++ sample - Kopie/tslint.json | 142 ++++++++++++++++ 71 files changed, 2442 insertions(+), 20 deletions(-) create mode 100644 sample - Kopie/.angular-cli.json create mode 100644 sample - Kopie/.editorconfig create mode 100644 sample - Kopie/.gitignore create mode 100644 sample - Kopie/README.md create mode 100644 sample - Kopie/e2e/app.e2e-spec.ts create mode 100644 sample - Kopie/e2e/app.po.ts create mode 100644 sample - Kopie/e2e/tsconfig.e2e.json create mode 100644 sample - Kopie/info.txt create mode 100644 sample - Kopie/karma.conf.js create mode 100644 sample - Kopie/package.json create mode 100644 sample - Kopie/protractor.conf.js create mode 100644 sample - Kopie/src/app/app.component.css create mode 100644 sample - Kopie/src/app/app.component.html create mode 100644 sample - Kopie/src/app/app.component.spec.ts create mode 100644 sample - Kopie/src/app/app.component.ts create mode 100644 sample - Kopie/src/app/app.module.ts create mode 100644 sample - Kopie/src/app/app.routes.ts create mode 100644 sample - Kopie/src/app/app.tokens.ts create mode 100644 sample - Kopie/src/app/auth-password-flow.config.ts create mode 100644 sample - Kopie/src/app/auth.config.ts create mode 100644 sample - Kopie/src/app/entities/flight.ts create mode 100644 sample - Kopie/src/app/flight-booking/alt-flight-card/alt-flight-card.component.html create mode 100644 sample - Kopie/src/app/flight-booking/alt-flight-card/alt-flight.card.component.ts create mode 100644 sample - Kopie/src/app/flight-booking/alt-flight-card/flight-list.ts create mode 100644 sample - Kopie/src/app/flight-booking/flight-booking.component.html create mode 100644 sample - Kopie/src/app/flight-booking/flight-booking.component.ts create mode 100644 sample - Kopie/src/app/flight-booking/flight-booking.module.ts create mode 100644 sample - Kopie/src/app/flight-booking/flight-booking.routes.ts create mode 100644 sample - Kopie/src/app/flight-booking/flight-card/flight-card.component.html create mode 100644 sample - Kopie/src/app/flight-booking/flight-card/flight.card.component.ts create mode 100644 sample - Kopie/src/app/flight-booking/flight-edit/flight-edit.component.ts create mode 100644 sample - Kopie/src/app/flight-booking/flight-search-reactive/flight-search-reactive.component.css create mode 100644 sample - Kopie/src/app/flight-booking/flight-search-reactive/flight-search-reactive.component.html create mode 100644 sample - Kopie/src/app/flight-booking/flight-search-reactive/flight-search-reactive.component.ts create mode 100644 sample - Kopie/src/app/flight-booking/flight-search/flight-search.component.css create mode 100644 sample - Kopie/src/app/flight-booking/flight-search/flight-search.component.html create mode 100644 sample - Kopie/src/app/flight-booking/flight-search/flight-search.component.ts create mode 100644 sample - Kopie/src/app/flight-booking/passenger-search/passenger-search.component.ts create mode 100644 sample - Kopie/src/app/flight-booking/services/flight.service.ts create mode 100644 sample - Kopie/src/app/flight-history/flight-history.component.ts create mode 100644 sample - Kopie/src/app/home/home.component.html create mode 100644 sample - Kopie/src/app/home/home.component.ts create mode 100644 sample - Kopie/src/app/password-flow-login/password-flow-login.component.html create mode 100644 sample - Kopie/src/app/password-flow-login/password-flow-login.component.ts create mode 100644 sample - Kopie/src/app/shared/auth/auth.guard.ts create mode 100644 sample - Kopie/src/app/shared/date/date.component.ts create mode 100644 sample - Kopie/src/app/shared/deactivation/LeaveComponentGuard.ts create mode 100644 sample - Kopie/src/app/shared/pipes/city.pipe.ts create mode 100644 sample - Kopie/src/app/shared/preload/custom-preloading.strategy.ts create mode 100644 sample - Kopie/src/app/shared/shared.module.ts create mode 100644 sample - Kopie/src/app/shared/validation/async-city.validator.ts create mode 100644 sample - Kopie/src/app/shared/validation/city.validator.ts create mode 100644 sample - Kopie/src/app/shared/validation/roundtrip.validator.ts create mode 100644 sample - Kopie/src/assets/.gitkeep create mode 100644 sample - Kopie/src/environments/environment.prod.ts create mode 100644 sample - Kopie/src/environments/environment.ts create mode 100644 sample - Kopie/src/favicon.ico create mode 100644 sample - Kopie/src/index.html create mode 100644 sample - Kopie/src/main.ts create mode 100644 sample - Kopie/src/polyfills.ts create mode 100644 sample - Kopie/src/silent-refresh.html create mode 100644 sample - Kopie/src/styles.css create mode 100644 sample - Kopie/src/test.ts create mode 100644 sample - Kopie/src/tsconfig.app.json create mode 100644 sample - Kopie/src/tsconfig.spec.json create mode 100644 sample - Kopie/src/typings.d.ts create mode 100644 sample - Kopie/tsconfig.json create mode 100644 sample - Kopie/tslint.json diff --git a/angular-oauth2-oidc/src/base64-helper.ts b/angular-oauth2-oidc/src/base64-helper.ts index d2a5b823..19c7029b 100644 --- a/angular-oauth2-oidc/src/base64-helper.ts +++ b/angular-oauth2-oidc/src/base64-helper.ts @@ -1,6 +1,9 @@ // see: https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_.22Unicode_Problem.22 export function b64DecodeUnicode(str) { - return decodeURIComponent(atob(str).split('').map(function(c) { + + let base64 = str.replace(/\-/g, '+').replace(/\_/g, '/'); + + return decodeURIComponent(atob(base64).split('').map(function(c) { return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); }).join('')); } diff --git a/angular-oauth2-oidc/src/oauth-service.ts b/angular-oauth2-oidc/src/oauth-service.ts index b3b41024..d4b5def7 100644 --- a/angular-oauth2-oidc/src/oauth-service.ts +++ b/angular-oauth2-oidc/src/oauth-service.ts @@ -92,11 +92,7 @@ export class OAuthService this.setupRefreshTimer(); - if (this.sessionChecksEnabled) { - this.restartSessionChecksIfStillLoggedIn(); - } - - this.restartRefreshTimerIfStillLoggedIn(); + } /** @@ -113,9 +109,14 @@ export class OAuthService if (this.sessionChecksEnabled) { this.setupSessionCheck(); } + + this.configChanged(); } - private restartSessionChecksIfStillLoggedIn(): void { + private configChanged(): void { + } + + public restartSessionChecksIfStillLoggedIn(): void { if (this.hasValidIdToken()) { this.initSessionCheck(); } @@ -145,8 +146,12 @@ export class OAuthService .events .filter(e => e.type === 'token_expires') .subscribe(e => { - this.silentRefresh(); + this.silentRefresh().catch(_ => { + this.debug('automatic silent refresh did not work'); + }) }); + + this.restartRefreshTimerIfStillLoggedIn(); } public loadDiscoveryDocumentAndTryLogin() { @@ -227,7 +232,8 @@ export class OAuthService private setupAccessTokenTimer(): void { let expiration = this.getAccessTokenExpiration(); - let timeout = this.calcTimeout(expiration); + let storedAt = this.getAccessTokenStoredAt(); + let timeout = this.calcTimeout(storedAt, expiration); this.accessTokenTimeoutSubscription = Observable @@ -239,7 +245,8 @@ export class OAuthService private setupIdTokenTimer(): void { let expiration = this.getIdTokenExpiration(); - let timeout = this.calcTimeout(expiration); + let storedAt = this.getIdTokenStoredAt(); + let timeout = this.calcTimeout(storedAt, expiration); this.idTokenTimeoutSubscription = Observable @@ -260,10 +267,8 @@ export class OAuthService } } - private calcTimeout(expiration: number): number { - let now = Date.now(); - let delta = (expiration - now) * this.timeoutFactor; - // let timeout = now + delta; + private calcTimeout(storedAt: number, expiration: number): number { + let delta = (expiration - storedAt) * this.timeoutFactor; return delta; } @@ -276,6 +281,7 @@ export class OAuthService */ public setStorage(storage: OAuthStorage): void { this._storage = storage; + this.configChanged(); } /** @@ -292,7 +298,11 @@ export class OAuthService return new Promise((resolve, reject) => { if (!fullUrl) { - fullUrl = this.issuer + '/.well-known/openid-configuration'; + fullUrl = this.issuer || ''; + if (!fullUrl.endsWith('/')) { + fullUrl += '/'; + } + fullUrl += '.well-known/openid-configuration'; } if (!this.validateUrlForHttps(fullUrl)) { @@ -321,6 +331,10 @@ export class OAuthService this.discoveryDocumentLoaded = true; this.discoveryDocumentLoadedSubject.next(doc); + if (this.sessionChecksEnabled) { + this.restartSessionChecksIfStillLoggedIn(); + } + this.loadJwks().then(jwks => { let result: object = { discoveryDocument: doc, @@ -965,7 +979,7 @@ export class OAuthService private storeAccessTokenResponse(accessToken: string, refreshToken: string, expiresIn: number): void { this._storage.setItem('access_token', accessToken); - + this._storage.setItem('access_token_stored_at', '' + Date.now()); if (expiresIn) { let expiresInMilliSeconds = expiresIn * 1000; let now = new Date(); @@ -1092,9 +1106,10 @@ export class OAuthService this._storage.setItem('id_token', idToken.idToken); this._storage.setItem('id_token_claims_obj', idToken.idTokenClaimsJson); this._storage.setItem('id_token_expires_at', '' + idToken.idTokenExpiresAt); + this._storage.setItem('id_token_stored_at', '' + Date.now()); } - protected storeSessionState(sessionState: string) { + protected storeSessionState(sessionState: string): void { this._storage.setItem('session_state', sessionState); } @@ -1273,6 +1288,15 @@ export class OAuthService return parseInt(this._storage.getItem('expires_at'), 10); } + + private getAccessTokenStoredAt(): number { + return parseInt(this._storage.getItem('access_token_stored_at'), 10); + } + + private getIdTokenStoredAt(): number { + return parseInt(this._storage.getItem('id_token_stored_at'), 10); + } + /** * Returns the expiration date of the id_token * as milliseconds since 1970. @@ -1340,7 +1364,9 @@ export class OAuthService this._storage.removeItem('expires_at'); this._storage.removeItem('id_token_claims_obj'); this._storage.removeItem('id_token_expires_at'); - + this._storage.removeItem('id_token_stored_at'); + this._storage.removeItem('access_token_stored_at'); + this.silentRefreshSubject = null; if (!this.logoutUrl) return; @@ -1350,7 +1376,7 @@ export class OAuthService let logoutUrl: string; if (!this.validateUrlForHttps(this.logoutUrl)) throw new Error('logoutUrl must use Http. Also check property requireHttps.'); - + // For backward compatibility if (this.logoutUrl.indexOf('{{') > -1) { logoutUrl = this.logoutUrl.replace(/\{\{id_token\}\}/, id_token); diff --git a/angular-oauth2-oidc/src/package.json b/angular-oauth2-oidc/src/package.json index 08e5a586..6aa4022c 100644 --- a/angular-oauth2-oidc/src/package.json +++ b/angular-oauth2-oidc/src/package.json @@ -1,6 +1,6 @@ { "name": "angular-oauth2-oidc", - "version": "2.1.1", + "version": "2.1.2", "repository": { "type": "git", "url": "https://github.com/manfredsteyer/angular-oauth2-oidc" diff --git a/sample - Kopie/.angular-cli.json b/sample - Kopie/.angular-cli.json new file mode 100644 index 00000000..cd7c68ac --- /dev/null +++ b/sample - Kopie/.angular-cli.json @@ -0,0 +1,62 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "project": { + "name": "sample2" + }, + "apps": [ + { + "root": "src", + "outDir": "dist", + "assets": [ + "assets", + "favicon.ico", + "silent-refresh.html" + ], + "index": "index.html", + "main": "main.ts", + "polyfills": "polyfills.ts", + "test": "test.ts", + "tsconfig": "tsconfig.app.json", + "testTsconfig": "tsconfig.spec.json", + "prefix": "app", + "styles": [ + "styles.css", + "../node_modules/bootstrap/dist/css/bootstrap.css" + ], + "scripts": [], + "environmentSource": "environments/environment.ts", + "environments": { + "dev": "environments/environment.ts", + "prod": "environments/environment.prod.ts" + } + } + ], + "e2e": { + "protractor": { + "config": "./protractor.conf.js" + } + }, + "lint": [ + { + "project": "src/tsconfig.app.json", + "exclude": "**/node_modules/**" + }, + { + "project": "src/tsconfig.spec.json", + "exclude": "**/node_modules/**" + }, + { + "project": "e2e/tsconfig.e2e.json", + "exclude": "**/node_modules/**" + } + ], + "test": { + "karma": { + "config": "./karma.conf.js" + } + }, + "defaults": { + "styleExt": "css", + "component": {} + } +} diff --git a/sample - Kopie/.editorconfig b/sample - Kopie/.editorconfig new file mode 100644 index 00000000..6e87a003 --- /dev/null +++ b/sample - Kopie/.editorconfig @@ -0,0 +1,13 @@ +# Editor configuration, see http://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/sample - Kopie/.gitignore b/sample - Kopie/.gitignore new file mode 100644 index 00000000..6b668143 --- /dev/null +++ b/sample - Kopie/.gitignore @@ -0,0 +1,43 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/dist +/tmp +/out-tsc + +# dependencies +/node_modules + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# misc +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +testem.log +/typings +yarn-error.log + +# e2e +/e2e/*.js +/e2e/*.map + +# System Files +.DS_Store +Thumbs.db diff --git a/sample - Kopie/README.md b/sample - Kopie/README.md new file mode 100644 index 00000000..b4082acf --- /dev/null +++ b/sample - Kopie/README.md @@ -0,0 +1,28 @@ +# Sample2 + +This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.3.1. + +## Development server + +Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. + +## Code scaffolding + +Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. + +## Build + +Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build. + +## Running unit tests + +Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). + +## Running end-to-end tests + +Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). +Before running the tests make sure you are serving the app via `ng serve`. + +## Further help + +To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). diff --git a/sample - Kopie/e2e/app.e2e-spec.ts b/sample - Kopie/e2e/app.e2e-spec.ts new file mode 100644 index 00000000..4ef49c9e --- /dev/null +++ b/sample - Kopie/e2e/app.e2e-spec.ts @@ -0,0 +1,14 @@ +import { AppPage } from './app.po'; + +describe('sample2 App', () => { + let page: AppPage; + + beforeEach(() => { + page = new AppPage(); + }); + + it('should display welcome message', () => { + page.navigateTo(); + expect(page.getParagraphText()).toEqual('Welcome to app!'); + }); +}); diff --git a/sample - Kopie/e2e/app.po.ts b/sample - Kopie/e2e/app.po.ts new file mode 100644 index 00000000..82ea75ba --- /dev/null +++ b/sample - Kopie/e2e/app.po.ts @@ -0,0 +1,11 @@ +import { browser, by, element } from 'protractor'; + +export class AppPage { + navigateTo() { + return browser.get('/'); + } + + getParagraphText() { + return element(by.css('app-root h1')).getText(); + } +} diff --git a/sample - Kopie/e2e/tsconfig.e2e.json b/sample - Kopie/e2e/tsconfig.e2e.json new file mode 100644 index 00000000..1d9e5edf --- /dev/null +++ b/sample - Kopie/e2e/tsconfig.e2e.json @@ -0,0 +1,14 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/e2e", + "baseUrl": "./", + "module": "commonjs", + "target": "es5", + "types": [ + "jasmine", + "jasminewd2", + "node" + ] + } +} diff --git a/sample - Kopie/info.txt b/sample - Kopie/info.txt new file mode 100644 index 00000000..7962b131 --- /dev/null +++ b/sample - Kopie/info.txt @@ -0,0 +1,154 @@ +sample2@0.0.0 C:\Users\Manfred\Documents\bücher\Angular 2\src\oauth-lib4\angular-oauth2-oidc\sample2 ++-- @angular/animations@4.3.5 +| `-- tslib@1.7.1 ++-- @angular/cli@1.3.1 +| +-- @angular-devkit/build-optimizer@0.0.13 +| +-- @ngtools/json-schema@1.1.0 +| +-- @ngtools/webpack@1.6.1 +| +-- autoprefixer@6.7.7 +| +-- chalk@2.1.0 +| +-- circular-dependency-plugin@3.0.0 +| +-- common-tags@1.4.0 +| +-- core-object@3.1.4 +| +-- css-loader@0.28.5 +| +-- cssnano@3.10.0 +| +-- denodeify@1.2.1 +| +-- diff@3.3.0 +| +-- ember-cli-normalize-entity-name@1.0.0 +| +-- ember-cli-string-utils@1.1.0 +| +-- exports-loader@0.6.4 +| +-- extract-text-webpack-plugin@3.0.0 +| +-- file-loader@0.10.1 +| +-- fs-extra@4.0.1 +| +-- get-caller-file@1.0.2 +| +-- glob@7.1.2 +| +-- heimdalljs@0.2.5 +| +-- heimdalljs-logger@0.1.9 +| +-- html-webpack-plugin@2.30.1 +| +-- inflection@1.12.0 +| +-- inquirer@3.2.2 +| +-- isbinaryfile@3.0.2 +| +-- istanbul-instrumenter-loader@2.0.0 +| +-- karma-source-map-support@1.2.0 +| +-- less@2.7.2 +| +-- less-loader@4.0.5 +| +-- license-webpack-plugin@0.5.1 +| +-- lodash@4.17.4 +| +-- memory-fs@0.4.1 +| +-- minimatch@3.0.4 +| +-- node-modules-path@1.0.1 +| +-- node-sass@4.5.3 +| +-- nopt@4.0.1 +| +-- opn@5.1.0 +| +-- portfinder@1.0.13 +| +-- postcss-loader@1.3.3 +| +-- postcss-url@5.1.2 +| +-- raw-loader@0.5.1 +| +-- resolve@1.4.0 +| +-- rsvp@3.6.2 +| +-- sass-loader@6.0.6 +| +-- script-loader@0.7.0 +| +-- semver@5.4.1 +| +-- silent-error@1.1.0 +| +-- source-map-loader@0.2.1 +| +-- source-map-support@0.4.16 +| +-- style-loader@0.13.2 +| +-- stylus@0.54.5 +| +-- stylus-loader@3.0.1 +| +-- temp@0.8.3 +| +-- url-loader@0.5.9 +| +-- walk-sync@0.3.2 +| +-- webpack@3.4.1 +| +-- webpack-dev-middleware@1.12.0 +| +-- webpack-dev-server@2.5.1 +| `-- webpack-merge@4.1.0 ++-- @angular/common@4.3.5 ++-- @angular/compiler@4.3.5 ++-- @angular/compiler-cli@4.3.5 +| +-- @angular/tsc-wrapped@4.3.5 +| +-- minimist@1.2.0 +| `-- reflect-metadata@0.1.10 ++-- @angular/core@4.3.5 ++-- @angular/forms@4.3.5 ++-- @angular/http@4.3.5 ++-- @angular/language-service@4.3.5 ++-- @angular/platform-browser@4.3.5 ++-- @angular/platform-browser-dynamic@4.3.5 ++-- @angular/router@4.3.5 ++-- @types/jasmine@2.5.53 ++-- @types/jasminewd2@2.0.2 ++-- @types/node@6.0.87 ++-- angular-oauth2-oidc@2.0.6 +| `-- jsrsasign@8.0.3 ++-- bootstrap@3.3.7 ++-- codelyzer@3.1.2 +| +-- app-root-path@2.0.1 +| +-- css-selector-tokenizer@0.7.0 +| +-- cssauron@1.4.0 +| +-- semver-dsl@1.0.1 +| +-- source-map@0.5.6 +| `-- sprintf-js@1.0.3 ++-- core-js@2.5.0 ++-- jasmine-core@2.6.4 ++-- jasmine-spec-reporter@4.1.1 +| `-- colors@1.1.2 ++-- karma@1.7.0 +| +-- bluebird@3.5.0 +| +-- body-parser@1.17.2 +| +-- chokidar@1.7.0 +| +-- combine-lists@1.0.1 +| +-- connect@3.6.3 +| +-- di@0.0.1 +| +-- dom-serialize@2.2.1 +| +-- expand-braces@0.1.2 +| +-- graceful-fs@4.1.11 +| +-- http-proxy@1.16.2 +| +-- lodash@3.10.1 +| +-- log4js@0.6.38 +| +-- mime@1.3.6 +| +-- optimist@0.6.1 +| +-- qjobs@1.1.5 +| +-- range-parser@1.2.0 +| +-- rimraf@2.6.1 +| +-- safe-buffer@5.1.1 +| +-- socket.io@1.7.3 +| +-- tmp@0.0.31 +| `-- useragent@2.2.1 ++-- karma-chrome-launcher@2.1.1 +| +-- fs-access@1.0.1 +| `-- which@1.3.0 ++-- karma-cli@1.0.1 ++-- karma-coverage-istanbul-reporter@1.3.0 +| `-- istanbul-api@1.1.11 ++-- karma-jasmine@1.1.0 ++-- karma-jasmine-html-reporter@0.2.2 ++-- protractor@5.1.2 +| +-- @types/q@0.0.32 +| +-- @types/selenium-webdriver@2.53.42 +| +-- blocking-proxy@0.0.5 +| +-- chalk@1.1.3 +| +-- jasmine@2.7.0 +| +-- jasminewd2@2.1.0 +| +-- optimist@0.6.1 +| +-- q@1.4.1 +| +-- saucelabs@1.3.0 +| +-- selenium-webdriver@3.0.1 +| +-- webdriver-js-extender@1.0.0 +| `-- webdriver-manager@12.0.6 ++-- rxjs@5.4.3 +| `-- symbol-observable@1.0.4 ++-- ts-node@3.2.2 +| +-- arrify@1.0.1 +| +-- make-error@1.3.0 +| +-- minimist@1.2.0 +| +-- mkdirp@0.5.1 +| +-- tsconfig@6.0.0 +| +-- v8flags@3.0.0 +| `-- yn@2.0.0 ++-- tslint@5.3.2 +| +-- babel-code-frame@6.26.0 +| +-- optimist@0.6.1 +| `-- tsutils@2.8.0 ++-- typescript@2.3.4 +`-- zone.js@0.8.16 + diff --git a/sample - Kopie/karma.conf.js b/sample - Kopie/karma.conf.js new file mode 100644 index 00000000..af139fad --- /dev/null +++ b/sample - Kopie/karma.conf.js @@ -0,0 +1,33 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/1.0/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular/cli'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage-istanbul-reporter'), + require('@angular/cli/plugins/karma') + ], + client:{ + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + coverageIstanbulReporter: { + reports: [ 'html', 'lcovonly' ], + fixWebpackSourcePaths: true + }, + angularCli: { + environment: 'dev' + }, + reporters: ['progress', 'kjhtml'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + singleRun: false + }); +}; diff --git a/sample - Kopie/package.json b/sample - Kopie/package.json new file mode 100644 index 00000000..d230d7ca --- /dev/null +++ b/sample - Kopie/package.json @@ -0,0 +1,51 @@ +{ + "name": "sample2", + "version": "0.0.0", + "license": "MIT", + "scripts": { + "ng": "ng", + "start": "ng serve --preserve-symlinks", + "build": "ng build", + "test": "ng test", + "lint": "ng lint", + "e2e": "ng e2e" + }, + "private": true, + "dependencies": { + "@angular/animations": "^4.2.4", + "@angular/common": "^4.2.4", + "@angular/compiler": "^4.2.4", + "@angular/core": "^4.2.4", + "@angular/forms": "^4.2.4", + "@angular/http": "^4.2.4", + "@angular/platform-browser": "^4.2.4", + "@angular/platform-browser-dynamic": "^4.2.4", + "@angular/router": "^4.2.4", + "angular-oauth2-oidc": "^2.1.1", + "bootstrap": "^3.3.7", + "core-js": "^2.4.1", + "rxjs": "^5.4.2", + "zone.js": "^0.8.14" + }, + "devDependencies": { + "@angular/cli": "1.3.1", + "@angular/compiler-cli": "^4.2.4", + "@angular/language-service": "^4.2.4", + "@types/jasmine": "~2.5.53", + "@types/jasminewd2": "~2.0.2", + "@types/node": "~6.0.60", + "codelyzer": "~3.1.1", + "jasmine-core": "~2.6.2", + "jasmine-spec-reporter": "~4.1.0", + "karma": "~1.7.0", + "karma-chrome-launcher": "~2.1.1", + "karma-cli": "~1.0.1", + "karma-coverage-istanbul-reporter": "^1.2.1", + "karma-jasmine": "~1.1.0", + "karma-jasmine-html-reporter": "^0.2.2", + "protractor": "~5.1.2", + "ts-node": "~3.2.0", + "tslint": "~5.3.2", + "typescript": "~2.3.3" + } +} diff --git a/sample - Kopie/protractor.conf.js b/sample - Kopie/protractor.conf.js new file mode 100644 index 00000000..7ee3b5ee --- /dev/null +++ b/sample - Kopie/protractor.conf.js @@ -0,0 +1,28 @@ +// Protractor configuration file, see link for more information +// https://github.com/angular/protractor/blob/master/lib/config.ts + +const { SpecReporter } = require('jasmine-spec-reporter'); + +exports.config = { + allScriptsTimeout: 11000, + specs: [ + './e2e/**/*.e2e-spec.ts' + ], + capabilities: { + 'browserName': 'chrome' + }, + directConnect: true, + baseUrl: 'http://localhost:4200/', + framework: 'jasmine', + jasmineNodeOpts: { + showColors: true, + defaultTimeoutInterval: 30000, + print: function() {} + }, + onPrepare() { + require('ts-node').register({ + project: 'e2e/tsconfig.e2e.json' + }); + jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); + } +}; diff --git a/sample - Kopie/src/app/app.component.css b/sample - Kopie/src/app/app.component.css new file mode 100644 index 00000000..e69de29b diff --git a/sample - Kopie/src/app/app.component.html b/sample - Kopie/src/app/app.component.html new file mode 100644 index 00000000..d2228e59 --- /dev/null +++ b/sample - Kopie/src/app/app.component.html @@ -0,0 +1,22 @@ + + + +
+ +
+ +
+ +
+ +
+ +
+ + \ No newline at end of file diff --git a/sample - Kopie/src/app/app.component.spec.ts b/sample - Kopie/src/app/app.component.spec.ts new file mode 100644 index 00000000..9510495a --- /dev/null +++ b/sample - Kopie/src/app/app.component.spec.ts @@ -0,0 +1,32 @@ +import { TestBed, async } from '@angular/core/testing'; + +import { AppComponent } from './app.component'; + +describe('AppComponent', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + AppComponent + ], + }).compileComponents(); + })); + + it('should create the app', async(() => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app).toBeTruthy(); + })); + + it(`should have as title 'app'`, async(() => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app.title).toEqual('app'); + })); + + it('should render title in a h1 tag', async(() => { + const fixture = TestBed.createComponent(AppComponent); + fixture.detectChanges(); + const compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!'); + })); +}); diff --git a/sample - Kopie/src/app/app.component.ts b/sample - Kopie/src/app/app.component.ts new file mode 100644 index 00000000..0913a5aa --- /dev/null +++ b/sample - Kopie/src/app/app.component.ts @@ -0,0 +1,95 @@ +import { authConfig } from './auth.config'; +import { FlightHistoryComponent } from './flight-history/flight-history.component'; +import { Component } from '@angular/core'; +import { OAuthService } from 'angular-oauth2-oidc'; +import { JwksValidationHandler } from 'angular-oauth2-oidc'; +import { Router } from "@angular/router"; + +@Component({ + selector: 'flight-app', + templateUrl: './app.component.html' +}) +export class AppComponent { + + constructor( + private router: Router, + private oauthService: OAuthService) { + + this.configureWithNewConfigApi(); + // this.configureAuth(); + // this.configurePasswordFlow(); + + } + + + // This api will come in the next version + private configureWithNewConfigApi() { + + this.oauthService.configure(authConfig); + this.oauthService.tokenValidationHandler = new JwksValidationHandler(); + this.oauthService.loadDiscoveryDocumentAndTryLogin(); + + // Optional + this.oauthService.setupAutomaticSilentRefresh(); + this.oauthService.events.subscribe(e => { + console.debug('oauth/oidc event', e); + }); + + this.oauthService.events.filter(e => e.type === 'session_terminated').subscribe(e => { + console.debug('Your session has been terminated!'); + }); + } + + private configureAuth() { + // URL of the SPA to redirect the user to after login + this.oauthService.redirectUri = window.location.origin + "/index.html"; + + // URL of the SPA to redirect the user after silent refresh + this.oauthService.silentRefreshRedirectUri = window.location.origin + "/silent-refresh.html"; + + // The SPA's id. The SPA is registerd with this id at the auth-server + this.oauthService.clientId = "spa-demo"; + + // set the scope for the permissions the client should request + // The first three are defined by OIDC. The 4th is a usecase-specific one + this.oauthService.scope = "openid profile email voucher"; + + // Url of the Identity Provider + this.oauthService.issuer = 'https://steyer-identity-server.azurewebsites.net/identity'; + + this.oauthService.tokenValidationHandler = new JwksValidationHandler(); + + this.oauthService.events.subscribe(e => { + console.debug('oauth/oidc event', e); + }); + + // Load Discovery Document and then try to login the user + this.oauthService.loadDiscoveryDocument().then((doc) => { + this.oauthService.tryLogin(); + }); + + this + .oauthService + .events + .filter(e => e.type == 'token_expires') + .subscribe(e => { + console.debug('received token_expires event', e); + this.oauthService.silentRefresh(); + }); + } + + private configurePasswordFlow() { + + // Set a dummy secret + // Please note that the auth-server used here demand the client to transmit a client secret, although + // the standard explicitly cites that the password flow can also be used without it. Using a client secret + // does not make sense for a SPA that runs in the browser. That's why the property is called dummyClientSecret + // Using such a dummy secreat is as safe as using no secret. + this.oauthService.dummyClientSecret = "geheim"; + + } + +} + + + diff --git a/sample - Kopie/src/app/app.module.ts b/sample - Kopie/src/app/app.module.ts new file mode 100644 index 00000000..14bb0c08 --- /dev/null +++ b/sample - Kopie/src/app/app.module.ts @@ -0,0 +1,46 @@ +import { authConfig } from './auth.config'; +import {NgModule} from '@angular/core'; +import {FormsModule, ReactiveFormsModule} from '@angular/forms'; +import {HttpModule} from '@angular/http'; +import {BrowserModule} from '@angular/platform-browser'; +import {AuthConfig, JwksValidationHandler, OAuthModule, ValidationHandler} from 'angular-oauth2-oidc'; + +import {AppComponent} from './app.component'; +import {AppRouterModule} from './app.routes'; +import {BASE_URL} from './app.tokens'; +import {FlightHistoryComponent} from './flight-history/flight-history.component'; +import {HomeComponent} from './home/home.component'; +import {PasswordFlowLoginComponent} from './password-flow-login/password-flow-login.component'; +import {SharedModule} from './shared/shared.module'; + +@NgModule({ + imports: [ + BrowserModule, + HttpModule, + FormsModule, + ReactiveFormsModule, + SharedModule.forRoot(), + AppRouterModule, + + OAuthModule.forRoot() + ], + declarations: [ + AppComponent, + HomeComponent, + FlightHistoryComponent +, + PasswordFlowLoginComponent +], + providers: [ + // {provide: AuthConfig, useValue: authConfig }, + // { provide: OAuthStorage, useClass: DemoStorage }, + // { provide: ValidationHandler, useClass: JwksValidationHandler }, + { provide: BASE_URL, useValue: "http://www.angular.at" } + ], + bootstrap: [ + AppComponent + + ] +}) +export class AppModule { +} diff --git a/sample - Kopie/src/app/app.routes.ts b/sample - Kopie/src/app/app.routes.ts new file mode 100644 index 00000000..32c7265d --- /dev/null +++ b/sample - Kopie/src/app/app.routes.ts @@ -0,0 +1,40 @@ +import { PasswordFlowLoginComponent } from './password-flow-login/password-flow-login.component'; +import {Routes, RouterModule, PreloadAllModules} from "@angular/router"; +import {HomeComponent} from "./home/home.component"; +import {FlightHistoryComponent} from "./flight-history/flight-history.component"; +import {CustomPreloadingStrategy} from "./shared/preload/custom-preloading.strategy"; + +let APP_ROUTES: Routes = [ + { + path: '', + redirectTo: 'home', + pathMatch: 'full' + }, + { + path: 'home', + component: HomeComponent + }, + { + path: 'password-flow-login', + component: PasswordFlowLoginComponent + }, + { + path: 'flight-booking', + loadChildren: './flight-booking/flight-booking.module#FlightBookingModule' + }, + { + path: 'history', + component: FlightHistoryComponent, + outlet: 'aux' + }, + { + path: '**', + redirectTo: 'home' + } +]; + +export let AppRouterModule = RouterModule.forRoot(APP_ROUTES, { + preloadingStrategy: CustomPreloadingStrategy, + // useHash: true, + // initialNavigation: false +} ); \ No newline at end of file diff --git a/sample - Kopie/src/app/app.tokens.ts b/sample - Kopie/src/app/app.tokens.ts new file mode 100644 index 00000000..c9579e06 --- /dev/null +++ b/sample - Kopie/src/app/app.tokens.ts @@ -0,0 +1,3 @@ +import {OpaqueToken} from "@angular/core"; + +export const BASE_URL = new OpaqueToken("BASE_URL"); diff --git a/sample - Kopie/src/app/auth-password-flow.config.ts b/sample - Kopie/src/app/auth-password-flow.config.ts new file mode 100644 index 00000000..c9104f87 --- /dev/null +++ b/sample - Kopie/src/app/auth-password-flow.config.ts @@ -0,0 +1,29 @@ +// This api will come in the next version + +import { AuthConfig } from 'angular-oauth2-oidc'; + +export const authPasswordFlowConfig: AuthConfig = { + + // Url of the Identity Provider + issuer: 'https://steyer-identity-server.azurewebsites.net/identity', + + // URL of the SPA to redirect the user to after login + redirectUri: window.location.origin + '/index.html', + + // URL of the SPA to redirect the user after silent refresh + silentRefreshRedirectUri: window.location.origin + '/silent-refresh.html', + + // The SPA's id. The SPA is registerd with this id at the auth-server + clientId: 'demo-resource-owner', + + dummyClientSecret: 'geheim', + + // set the scope for the permissions the client should request + // The first three are defined by OIDC. The 4th is a usecase-specific one + scope: 'openid profile email voucher', + + showDebugInformation: true, + + oidc: false + +} diff --git a/sample - Kopie/src/app/auth.config.ts b/sample - Kopie/src/app/auth.config.ts new file mode 100644 index 00000000..ed0b4223 --- /dev/null +++ b/sample - Kopie/src/app/auth.config.ts @@ -0,0 +1,26 @@ +// This api will come in the next version + +import { AuthConfig } from 'angular-oauth2-oidc'; + +export const authConfig: AuthConfig = { + + // Url of the Identity Provider + issuer: 'https://steyer-identity-server.azurewebsites.net/identity', + + // URL of the SPA to redirect the user to after login + redirectUri: window.location.origin + '/index.html', + + // URL of the SPA to redirect the user after silent refresh + silentRefreshRedirectUri: window.location.origin + '/silent-refresh.html', + + // The SPA's id. The SPA is registerd with this id at the auth-server + clientId: 'spa-demo', + + // set the scope for the permissions the client should request + // The first three are defined by OIDC. The 4th is a usecase-specific one + scope: 'openid profile email voucher', + + showDebugInformation: true, + + sessionChecksEnabled: true +} diff --git a/sample - Kopie/src/app/entities/flight.ts b/sample - Kopie/src/app/entities/flight.ts new file mode 100644 index 00000000..98a7adb0 --- /dev/null +++ b/sample - Kopie/src/app/entities/flight.ts @@ -0,0 +1,8 @@ + +export interface Flight { + id: number; // int + double + from: string; + to: string; + date: string; + // JSON: ISO-String 2016-12-24T17:00:00.000+01:00 +} diff --git a/sample - Kopie/src/app/flight-booking/alt-flight-card/alt-flight-card.component.html b/sample - Kopie/src/app/flight-booking/alt-flight-card/alt-flight-card.component.html new file mode 100644 index 00000000..2a3702c3 --- /dev/null +++ b/sample - Kopie/src/app/flight-booking/alt-flight-card/alt-flight-card.component.html @@ -0,0 +1,16 @@ +
+ +

{{item.from}} - {{item.to}}

+

Flugnr. #{{item.id}}

+

Datum: {{item.date | date:'dd.MM.yyyy HH:mm'}}

+ +

+ + + +

+
diff --git a/sample - Kopie/src/app/flight-booking/alt-flight-card/alt-flight.card.component.ts b/sample - Kopie/src/app/flight-booking/alt-flight-card/alt-flight.card.component.ts new file mode 100644 index 00000000..fc838a0e --- /dev/null +++ b/sample - Kopie/src/app/flight-booking/alt-flight-card/alt-flight.card.component.ts @@ -0,0 +1,17 @@ +import {Component, EventEmitter, Input, Output} from "@angular/core"; +import {Flight} from "../../entities/flight"; + +@Component({ + selector: 'alt-flight-card', + templateUrl: 'alt-flight-card.component.html' +}) +export class AltFlightCardComponent { + + @Input() item: Flight; + @Input() selected: boolean; + @Output() selectedChange = new EventEmitter(); + + select() { + this.selectedChange.emit(true); + } +} \ No newline at end of file diff --git a/sample - Kopie/src/app/flight-booking/alt-flight-card/flight-list.ts b/sample - Kopie/src/app/flight-booking/alt-flight-card/flight-list.ts new file mode 100644 index 00000000..e6506701 --- /dev/null +++ b/sample - Kopie/src/app/flight-booking/alt-flight-card/flight-list.ts @@ -0,0 +1,28 @@ + +import {Component, Input, Output, EventEmitter} from "@angular/core"; +import {Flight} from "../../entities/flight"; +@Component({ + selector: 'flight-list', + template: ` +
+
+ + +
+
+ ` +}) +export class FlightListComponent { + + @Input() flights: Flight[] = []; + @Input() selectedFlight: Flight; + @Output() selectedFlightChange = new EventEmitter(); + + change(f: Flight) { + this.selectedFlightChange.emit(f); + } + +} diff --git a/sample - Kopie/src/app/flight-booking/flight-booking.component.html b/sample - Kopie/src/app/flight-booking/flight-booking.component.html new file mode 100644 index 00000000..2d531679 --- /dev/null +++ b/sample - Kopie/src/app/flight-booking/flight-booking.component.html @@ -0,0 +1,11 @@ + + + +
+ +
\ No newline at end of file diff --git a/sample - Kopie/src/app/flight-booking/flight-booking.component.ts b/sample - Kopie/src/app/flight-booking/flight-booking.component.ts new file mode 100644 index 00000000..1b81e960 --- /dev/null +++ b/sample - Kopie/src/app/flight-booking/flight-booking.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; +import {FlightService} from "./services/flight.service"; + +@Component({ + selector: 'flight-booking', + templateUrl: './flight-booking.component.html' +}) +export class FlightBookingComponent { +} \ No newline at end of file diff --git a/sample - Kopie/src/app/flight-booking/flight-booking.module.ts b/sample - Kopie/src/app/flight-booking/flight-booking.module.ts new file mode 100644 index 00000000..52b25ee1 --- /dev/null +++ b/sample - Kopie/src/app/flight-booking/flight-booking.module.ts @@ -0,0 +1,42 @@ +import {NgModule} from "@angular/core"; +import {FlightSearchComponent} from "./flight-search/flight-search.component"; +import {FlightCardComponent} from "./flight-card/flight.card.component"; +import {AltFlightCardComponent} from "./alt-flight-card/alt-flight.card.component"; +import {FlightListComponent} from "./alt-flight-card/flight-list"; +import {PassengerSearchComponent} from "./passenger-search/passenger-search.component"; +import {FlightEditComponent} from "./flight-edit/flight-edit.component"; +import {FlightSearchReactiveComponent} from "./flight-search-reactive/flight-search-reactive.component"; +import {CommonModule} from "@angular/common"; +import {FormsModule, ReactiveFormsModule} from "@angular/forms"; +import {SharedModule} from "../shared/shared.module"; +import {FlightBookingRouterModule} from "./flight-booking.routes"; +import {FlightBookingComponent} from "./flight-booking.component"; +import {FlightService} from "./services/flight.service"; + +@NgModule({ + imports: [ + CommonModule, // ngFor + FormsModule, + ReactiveFormsModule, + SharedModule, + FlightBookingRouterModule + ], + declarations: [ + FlightSearchComponent, + FlightCardComponent, + AltFlightCardComponent, + FlightListComponent, + FlightSearchReactiveComponent, + PassengerSearchComponent, + FlightEditComponent, + FlightBookingComponent + ], + providers: [ + FlightService + ], + exports: [ + ] +}) +export class FlightBookingModule { + +} \ No newline at end of file diff --git a/sample - Kopie/src/app/flight-booking/flight-booking.routes.ts b/sample - Kopie/src/app/flight-booking/flight-booking.routes.ts new file mode 100644 index 00000000..c3802bd0 --- /dev/null +++ b/sample - Kopie/src/app/flight-booking/flight-booking.routes.ts @@ -0,0 +1,33 @@ +import {Routes, RouterModule} from "@angular/router"; +import {FlightSearchComponent} from "./flight-search/flight-search.component"; +import {PassengerSearchComponent} from "./passenger-search/passenger-search.component"; +import {FlightEditComponent} from "./flight-edit/flight-edit.component"; +import {FlightBookingComponent} from "./flight-booking.component"; +import {AuthGuard} from "../shared/auth/auth.guard"; +import {LeaveComponentGuard} from "../shared/deactivation/LeaveComponentGuard"; + +let FLIGHT_BOOKING_ROUTES: Routes = [ + { + path: '', + component: FlightBookingComponent, + canActivate: [AuthGuard], + children: [ + { + path: 'flight-search', + component: FlightSearchComponent + }, + { + path: 'passenger-search', + component: PassengerSearchComponent + }, + { + path: 'flight-edit/:id', + component: FlightEditComponent, + canDeactivate: [LeaveComponentGuard] + } + + ] + } +]; + +export let FlightBookingRouterModule = RouterModule.forChild(FLIGHT_BOOKING_ROUTES); \ No newline at end of file diff --git a/sample - Kopie/src/app/flight-booking/flight-card/flight-card.component.html b/sample - Kopie/src/app/flight-booking/flight-card/flight-card.component.html new file mode 100644 index 00000000..c372547c --- /dev/null +++ b/sample - Kopie/src/app/flight-booking/flight-card/flight-card.component.html @@ -0,0 +1,17 @@ +
+ +

{{item.from}} - {{item.to}}

+

Flugnr. #{{item.id}}

+

Datum: {{item.date | date:'dd.MM.yyyy HH:mm'}}

+ +

+ + + + +

+
diff --git a/sample - Kopie/src/app/flight-booking/flight-card/flight.card.component.ts b/sample - Kopie/src/app/flight-booking/flight-card/flight.card.component.ts new file mode 100644 index 00000000..6326e190 --- /dev/null +++ b/sample - Kopie/src/app/flight-booking/flight-card/flight.card.component.ts @@ -0,0 +1,17 @@ +import {Component, EventEmitter, Input, Output} from "@angular/core"; +import {Flight} from "../../entities/flight"; + +@Component({ + selector: 'flight-card', + templateUrl: './flight-card.component.html' +}) +export class FlightCardComponent { + + @Input() item: Flight; + @Input() selectedItem: Flight; + @Output() selectedItemChange = new EventEmitter(); + + select() { + this.selectedItemChange.emit(this.item); + } +} \ No newline at end of file diff --git a/sample - Kopie/src/app/flight-booking/flight-edit/flight-edit.component.ts b/sample - Kopie/src/app/flight-booking/flight-edit/flight-edit.component.ts new file mode 100644 index 00000000..aedbe55f --- /dev/null +++ b/sample - Kopie/src/app/flight-booking/flight-edit/flight-edit.component.ts @@ -0,0 +1,54 @@ +import { Component, OnInit } from '@angular/core'; +import {ActivatedRoute} from "@angular/router"; + +@Component({ + template: ` +

Flight Edit!

+

Hier könnte auch der Datensatz mit der Id {{id}} stehen!

+ +
+
+ Daten wurden nicht gespeichert! Trotzdem Maske verlassen? +
+
+ Ja + Nein +
+
+ + + + + ` +}) +export class FlightEditComponent implements OnInit { + + public id: string; + + constructor(private route: ActivatedRoute) { + + route.params.subscribe(p => { + this.id = p['id']; + }); + } + + ngOnInit() { } + + exitWarning = { + show: false, + resolve: null + } + + decide(decision: boolean) { + this.exitWarning.show = false; + this.exitWarning.resolve(decision); + } + + canDeactivate() { + this.exitWarning.show = true; + return new Promise((resolve) => { + this.exitWarning.resolve = resolve; + }); + } + +} \ No newline at end of file diff --git a/sample - Kopie/src/app/flight-booking/flight-search-reactive/flight-search-reactive.component.css b/sample - Kopie/src/app/flight-booking/flight-search-reactive/flight-search-reactive.component.css new file mode 100644 index 00000000..58633484 --- /dev/null +++ b/sample - Kopie/src/app/flight-booking/flight-search-reactive/flight-search-reactive.component.css @@ -0,0 +1,13 @@ + + +input.ng-valid { + border-left-style: solid; + border-left-color: forestgreen; + border-left-width: 5px; +} + +input.ng-invalid { + border-left-style: solid; + border-left-color: hotpink; + border-left-width: 5px; +} \ No newline at end of file diff --git a/sample - Kopie/src/app/flight-booking/flight-search-reactive/flight-search-reactive.component.html b/sample - Kopie/src/app/flight-booking/flight-search-reactive/flight-search-reactive.component.html new file mode 100644 index 00000000..b07bd6db --- /dev/null +++ b/sample - Kopie/src/app/flight-booking/flight-search-reactive/flight-search-reactive.component.html @@ -0,0 +1,95 @@ +

Flight Search (Reactive) !

+ + + +
+ +

Dynamisches Formular

+
+ + +
+ +

Statisches Formular

+ +
+ + + +
+ Validierungsfehler. Bitte prüfen Sie Ihre Eingaben. +
+ +
+ Diese Stadt wird nicht angefolgen +
+ +
+ Dieses Feld ist ein Pflichtfeld +
+ +
+ + +
+ + +
+
+ +
+ +
+ + + +
+
+ + + + + + + + +
+
+ + + +
+
Warenkorb
+----------------------
+{{selectedFlight | json}}
+
+
\ No newline at end of file diff --git a/sample - Kopie/src/app/flight-booking/flight-search-reactive/flight-search-reactive.component.ts b/sample - Kopie/src/app/flight-booking/flight-search-reactive/flight-search-reactive.component.ts new file mode 100644 index 00000000..97d554bc --- /dev/null +++ b/sample - Kopie/src/app/flight-booking/flight-search-reactive/flight-search-reactive.component.ts @@ -0,0 +1,83 @@ +import { Component } from '@angular/core'; +import {Flight} from "../../entities/flight"; +import {Http, URLSearchParams, Headers } from '@angular/http'; +import {FlightService} from "../services/flight.service"; +import {FormGroup, FormBuilder, Validators, AbstractControl} from "@angular/forms"; +import {CityValidatorDirective} from "../../shared/validation/city.validator"; + +@Component({ + selector: 'flight-search-reactive', + templateUrl: 'flight-search-reactive.component.html', + providers: [FlightService], + styleUrls: ['flight-search-reactive.component.css'] +}) +export class FlightSearchReactiveComponent { + + public flights: Array = []; + public selectedFlight: Flight; + + public filter: FormGroup; + + public formDesc = []; + + constructor( + private flightService: FlightService, + private fb: FormBuilder) { + + this.formDesc.push({ + label: 'Von', + name: 'from' + }); + + this.formDesc.push({ + label: 'Nach', + name: 'to' + }); + + this.filter= fb.group({ + 'from': [ + 'Graz', + [ + Validators.required, + Validators.minLength(3), + (c: AbstractControl): any => { + if (c.value != 'Graz' && c.value != 'Hamburg') { + return { + city: true + }; + } + return {} + } + ] + ], + 'to': ['Hamburg'] + }); + + this.filter.valueChanges.subscribe((e) => { + console.debug('formular geändert', e) + }); + + this.filter.controls['from'].valueChanges.subscribe((e) => { + console.debug('from geändert', e) + }); + + + } + + public select(f: Flight): void { + this.selectedFlight = f; + } + + public search(): void { + + var value = this.filter.value; + + this.flightService + .find(value.from, value.to); + + + + // .map(function(resp) { return resp.json() }) + + } +} \ No newline at end of file diff --git a/sample - Kopie/src/app/flight-booking/flight-search/flight-search.component.css b/sample - Kopie/src/app/flight-booking/flight-search/flight-search.component.css new file mode 100644 index 00000000..58633484 --- /dev/null +++ b/sample - Kopie/src/app/flight-booking/flight-search/flight-search.component.css @@ -0,0 +1,13 @@ + + +input.ng-valid { + border-left-style: solid; + border-left-color: forestgreen; + border-left-width: 5px; +} + +input.ng-invalid { + border-left-style: solid; + border-left-color: hotpink; + border-left-width: 5px; +} \ No newline at end of file diff --git a/sample - Kopie/src/app/flight-booking/flight-search/flight-search.component.html b/sample - Kopie/src/app/flight-booking/flight-search/flight-search.component.html new file mode 100644 index 00000000..d36dabdf --- /dev/null +++ b/sample - Kopie/src/app/flight-booking/flight-search/flight-search.component.html @@ -0,0 +1,124 @@ +

Flight Search!

+ + + +
+ +
+ Rund-Flüge sind nicht möglich. +
+ + +
+ + + +
+ Validierungsfehler. Bitte prüfen Sie Ihre Eingaben. +
+            {{ f.controls.from?.errors | json }}
+            
+
+ +
+ Async-City: Die Stadt wird gerade wegen eines Unwetters nicht angeflogen. +
+ + + +
+ + Validierung wird ausgeführt. Bitte etwas warten! + +
+ + +
+ Dieses Feld ist ein Pflichtfeld. +
+ +
+ Diese Stadt wird nicht angeflogen. +
+ +
+ Bitte erfassen Sie min. 3 Zeichen. +
+
+ Bitte nur Buchstaben erfassen. +
+ +
+ + +
+ + +
+
+ +
+ +
+ + + +
+
+ + + + + + +
+
+ + + +
+
Warenkorb
+----------------------
+{{selectedFlight | json}}
+
+
\ No newline at end of file diff --git a/sample - Kopie/src/app/flight-booking/flight-search/flight-search.component.ts b/sample - Kopie/src/app/flight-booking/flight-search/flight-search.component.ts new file mode 100644 index 00000000..77ffdcdb --- /dev/null +++ b/sample - Kopie/src/app/flight-booking/flight-search/flight-search.component.ts @@ -0,0 +1,43 @@ +import {Component} from '@angular/core'; +import {Headers, Http, URLSearchParams} from '@angular/http'; + +import {OAuthService} from 'angular-oauth2-oidc'; +import {Flight} from '../../entities/flight'; +import {FlightService} from '../services/flight.service'; + +@Component({ + selector: 'flight-search', + templateUrl: './flight-search.component.html', + styleUrls: ['./flight-search.component.css'] +}) +export class FlightSearchComponent { + + public from: string = "Graz"; + public to: string = ""; + public selectedFlight: Flight; + + constructor( + private flightService: FlightService, + private oauthService: OAuthService + ) { + console.debug('access-token', this.oauthService.getAccessToken()); + } + + // cmp.flights + public get flights() { + return this.flightService.flights; + } + + public select(f: Flight): void { + this.selectedFlight = f; + } + + public search(): void { + + this.flightService + .find(this.from, this.to); + + // .map(function(resp) { return resp.json() }) + + } +} \ No newline at end of file diff --git a/sample - Kopie/src/app/flight-booking/passenger-search/passenger-search.component.ts b/sample - Kopie/src/app/flight-booking/passenger-search/passenger-search.component.ts new file mode 100644 index 00000000..992ad7c8 --- /dev/null +++ b/sample - Kopie/src/app/flight-booking/passenger-search/passenger-search.component.ts @@ -0,0 +1,19 @@ +import { Component, OnInit } from '@angular/core'; +import { OAuthService } from "angular-oauth2-oidc"; + +@Component({ + template: ` +

PassengerSearch

+

Platzhalter-Seite. Hier könnte auch Ihre Werbung stehen ;-)

+

+ + ` +}) +export class PassengerSearchComponent implements OnInit { + constructor(private oauthService: OAuthService) { } + ngOnInit() { } + + refresh() { + this.oauthService.silentRefresh(); + } +} diff --git a/sample - Kopie/src/app/flight-booking/services/flight.service.ts b/sample - Kopie/src/app/flight-booking/services/flight.service.ts new file mode 100644 index 00000000..4cf0c8d0 --- /dev/null +++ b/sample - Kopie/src/app/flight-booking/services/flight.service.ts @@ -0,0 +1,44 @@ +import { Injectable, Inject} from '@angular/core'; +import {Http, Headers, URLSearchParams} from '@angular/http'; +import {BASE_URL} from "../../app.tokens"; +import {Observable} from "rxjs"; +import {Flight} from "../../entities/flight"; +import {OAuthService} from "angular-oauth2-oidc"; + +@Injectable() +export class FlightService { + + constructor( + private oauthService: OAuthService, + private http: Http, + @Inject(BASE_URL) private baseUrl: string + ) { + } + + public flights: Array = []; + + find(from: string, to: string): void { + let url = this.baseUrl + "/api/flight"; + let headers = new Headers(); + headers.set('Accept', 'application/json'); + headers.set('Authorization', 'Bearer ' + this.oauthService.getAccessToken()); + + let search = new URLSearchParams(); + search.set('from', from); + search.set('to', to); + + this + .http + .get(url, {headers, search}) + .map(resp => resp.json()) + .subscribe( + (flights) => { + this.flights = flights; + }, + (err) => { + console.warn('status', err.status); + } + ); + } + +} \ No newline at end of file diff --git a/sample - Kopie/src/app/flight-history/flight-history.component.ts b/sample - Kopie/src/app/flight-history/flight-history.component.ts new file mode 100644 index 00000000..1a5eef12 --- /dev/null +++ b/sample - Kopie/src/app/flight-history/flight-history.component.ts @@ -0,0 +1,16 @@ +import {Component} from "@angular/core"; + +@Component({ + template: ` +

Flight History

+
    +
  • Graz - Hamburg
  • +
  • Hamburg - Frankfurt
  • +
  • Frankfurt - Graz
  • +
+ ` +}) +export class FlightHistoryComponent { + +} + diff --git a/sample - Kopie/src/app/home/home.component.html b/sample - Kopie/src/app/home/home.component.html new file mode 100644 index 00000000..f0f82c08 --- /dev/null +++ b/sample - Kopie/src/app/home/home.component.html @@ -0,0 +1,51 @@ +Status: {{ givenName ? 'logged in' : 'logged out' }} +

Welcome!

+

Welcome, {{givenName}} {{familyName}}!

+ +
+
+

Login with Authorization Server

+
+ +
+ + + + +
+
+ +
+
+ Username/Password: max/geheim +
+
+ +
+
+

+ access_token_expiration: {{access_token_expiration}} +

+

+ id_token_expiration: {{id_token_expiration}} +

+
+
+ +
+
+

+ access_token: {{access_token}} +

+

+ id_token: {{id_token}} +

+
+ user profile: +
{{userProfile | json}}
+
+ +
+
+ + diff --git a/sample - Kopie/src/app/home/home.component.ts b/sample - Kopie/src/app/home/home.component.ts new file mode 100644 index 00000000..01b522b0 --- /dev/null +++ b/sample - Kopie/src/app/home/home.component.ts @@ -0,0 +1,92 @@ +import { authConfig } from '../auth.config'; +import { Component, OnInit } from '@angular/core'; +import {OAuthService} from "angular-oauth2-oidc"; + +@Component({ + templateUrl: './home.component.html' +}) +export class HomeComponent implements OnInit { + + loginFailed: boolean = false; + userProfile: object; + + constructor(private oauthService: OAuthService) { + // Tweak config for implicit flow. + // This is just needed b/c this demo uses both, + // implicit flow as well as password flow + this.oauthService.configure(authConfig) + } + + ngOnInit() { + } + + + login() { + this.oauthService.initImplicitFlow('http://www.myurl.com/x/y/z'); + } + + logout() { + this.oauthService.logOut(); + } + + loadUserProfile(): void { + this + .oauthService + .loadUserProfile() + .then(up => this.userProfile = up); + + } + + get givenName() { + var claims = this.oauthService.getIdentityClaims(); + if (!claims) return null; + return claims['given_name']; + } + + get familyName() { + var claims = this.oauthService.getIdentityClaims(); + if (!claims) return null; + return claims['family_name']; + } + + testSilentRefresh() { + /* + * Tweak config for implicit flow. + * This is needed b/c this sample uses both flows + */ + //this.oauthService.clientId = "spa-demo"; + this.oauthService.oidc = true; + + this + .oauthService + .silentRefresh() + .then(info => console.debug('refresh ok', info)) + .catch(err => console.error('refresh error', err)); + } + + set requestAccessToken(value: boolean) { + this.oauthService.requestAccessToken = value; + localStorage.setItem('requestAccessToken', '' + value); + } + + get requestAccessToken() { + return this.oauthService.requestAccessToken; + } + + get id_token() { + return this.oauthService.getIdToken(); + } + + get access_token() { + return this.oauthService.getAccessToken(); + } + + get id_token_expiration() { + return this.oauthService.getIdTokenExpiration(); + } + + get access_token_expiration() { + return this.oauthService.getAccessTokenExpiration(); + } + +} \ No newline at end of file diff --git a/sample - Kopie/src/app/password-flow-login/password-flow-login.component.html b/sample - Kopie/src/app/password-flow-login/password-flow-login.component.html new file mode 100644 index 00000000..a37e787c --- /dev/null +++ b/sample - Kopie/src/app/password-flow-login/password-flow-login.component.html @@ -0,0 +1,53 @@ +

Welcome!

+

Welcome, {{givenName}} {{familyName}}!

+ + +
+
+

Login with Username/Password

+ +

+ Login wasn't successfull. +

+ +
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
+ Username/Password: max/geheim +
+
+ +
+
+

+ access_token_expiration: {{access_token_expiration}} +

+
+
+ +
+
+

+ access_token: {{access_token}} +

+
+ user profile: +
{{userProfile | json}}
+
+ +
+
diff --git a/sample - Kopie/src/app/password-flow-login/password-flow-login.component.ts b/sample - Kopie/src/app/password-flow-login/password-flow-login.component.ts new file mode 100644 index 00000000..91e21b3f --- /dev/null +++ b/sample - Kopie/src/app/password-flow-login/password-flow-login.component.ts @@ -0,0 +1,78 @@ +import { authPasswordFlowConfig } from '../auth-password-flow.config'; +import { OAuthService } from 'angular-oauth2-oidc'; +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-password-flow-login', + templateUrl: './password-flow-login.component.html' +}) +export class PasswordFlowLoginComponent implements OnInit { + + userName: string; + password: string; + loginFailed: boolean = false; + userProfile: object; + + constructor(private oauthService: OAuthService) { + + // Tweak config for password flow + // This is just needed b/c this demo uses both, + // implicit flow as well as password flow + + this.oauthService.configure(authPasswordFlowConfig) + + } + + ngOnInit() { + } + + loadUserProfile(): void { + this + .oauthService + .loadUserProfile() + .then(up => this.userProfile = up); + + } + + get access_token() { + return this.oauthService.getAccessToken(); + } + + get access_token_expiration() { + return this.oauthService.getAccessTokenExpiration(); +} + + get givenName() { + var claims = this.oauthService.getIdentityClaims(); + if (!claims) return null; + return claims['given_name']; + } + + get familyName() { + var claims = this.oauthService.getIdentityClaims(); + if (!claims) return null; + return claims['family_name']; + } + + loginWithPassword() { + + this + .oauthService + .fetchTokenUsingPasswordFlowAndLoadUserProfile(this.userName, this.password) + .then(() => { + console.debug('successfully logged in'); + this.loginFailed = false; + }) + .catch((err) => { + console.error('error logging in', err); + this.loginFailed = true; + }); + } + + logout() { + this.oauthService.logOut(true); + } + + + +} diff --git a/sample - Kopie/src/app/shared/auth/auth.guard.ts b/sample - Kopie/src/app/shared/auth/auth.guard.ts new file mode 100644 index 00000000..dc76ba90 --- /dev/null +++ b/sample - Kopie/src/app/shared/auth/auth.guard.ts @@ -0,0 +1,11 @@ +import {Injectable} from "@angular/core"; +import {CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot} from "@angular/router"; + +@Injectable() +export class AuthGuard implements CanActivate { + + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { + return true; + } + +} \ No newline at end of file diff --git a/sample - Kopie/src/app/shared/date/date.component.ts b/sample - Kopie/src/app/shared/date/date.component.ts new file mode 100644 index 00000000..955439d8 --- /dev/null +++ b/sample - Kopie/src/app/shared/date/date.component.ts @@ -0,0 +1,50 @@ +import {Component, Input, OnInit, OnChanges} from '@angular/core'; + +@Component({ + selector: 'date-component', + template: ` +
+ {{day}}.{{month}}.{{year}} {{hour}}:{{minute}} +
+ ` + +}) +export class DateComponent implements OnInit, OnChanges { + + @Input() date: string; + + day; + month; + year; + hour; + minute; + + constructor() { + console.debug('ctrl') + } + + ngOnInit() { + + } + + ngOnChanges(change) { + + // if(change.date) { ... } + + console.debug('change', change); + + let date = new Date(this.date); + + + + this.day = date.getDate(); + this.month = date.getMonth() + 1; + this.year = date.getFullYear(); + + this.hour = date.getHours(); + this.minute = date.getMinutes(); + } + + + +} diff --git a/sample - Kopie/src/app/shared/deactivation/LeaveComponentGuard.ts b/sample - Kopie/src/app/shared/deactivation/LeaveComponentGuard.ts new file mode 100644 index 00000000..19b36582 --- /dev/null +++ b/sample - Kopie/src/app/shared/deactivation/LeaveComponentGuard.ts @@ -0,0 +1,11 @@ +import {CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot} from "@angular/router"; + +export class LeaveComponentGuard implements CanDeactivate { + + canDeactivate(component: any, route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { + + return component.canDeactivate(); + + } + +} \ No newline at end of file diff --git a/sample - Kopie/src/app/shared/pipes/city.pipe.ts b/sample - Kopie/src/app/shared/pipes/city.pipe.ts new file mode 100644 index 00000000..76d0ee78 --- /dev/null +++ b/sample - Kopie/src/app/shared/pipes/city.pipe.ts @@ -0,0 +1,34 @@ +import {Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'city', + pure: true +}) +export class CityPipe implements PipeTransform { + + transform(value: any, ...args: any[]): any { + + let fmt = args[0]; // short, long + let short, long; + + switch(value) { + case "Graz": + long = "Flughafen Graz Thalerhof"; + short = "GRZ"; + break; + case "Hamburg": + long = "Airport Hamburg Fuhlsbüttl Helmut Schmidt"; + short = "HAM"; + break; + default: + long = short = "ROM"; + } + + if (fmt == 'short') return short; + return long; + + } + + + +} \ No newline at end of file diff --git a/sample - Kopie/src/app/shared/preload/custom-preloading.strategy.ts b/sample - Kopie/src/app/shared/preload/custom-preloading.strategy.ts new file mode 100644 index 00000000..f0e7f83c --- /dev/null +++ b/sample - Kopie/src/app/shared/preload/custom-preloading.strategy.ts @@ -0,0 +1,15 @@ +import {PreloadingStrategy, Route} from "@angular/router"; +import {Observable} from 'rxjs'; + +export class CustomPreloadingStrategy implements PreloadingStrategy { + + preload(route: Route, fn: () => Observable): Observable { + //return Observable.of(true).delay(7000).flatMap(_ => fn()); + + if (true) { + return fn(); + } + + } + +} diff --git a/sample - Kopie/src/app/shared/shared.module.ts b/sample - Kopie/src/app/shared/shared.module.ts new file mode 100644 index 00000000..884f4880 --- /dev/null +++ b/sample - Kopie/src/app/shared/shared.module.ts @@ -0,0 +1,46 @@ +import {NgModule, ModuleWithProviders} from "@angular/core"; +import {FormsModule} from "@angular/forms"; +import {CommonModule} from "@angular/common"; +import {CityPipe} from "./pipes/city.pipe"; +import {CityValidatorDirective} from "./validation/city.validator"; +import {RoundTrip} from "./validation/roundtrip.validator"; +import {AsyncCityValidatorDirective} from "./validation/async-city.validator"; +import {DateComponent} from "./date/date.component"; +import {AuthGuard} from "./auth/auth.guard"; +import {LeaveComponentGuard} from "./deactivation/LeaveComponentGuard"; +import {CustomPreloadingStrategy} from "./preload/custom-preloading.strategy"; + +@NgModule({ + imports: [ + FormsModule, // [(ngModel)] + CommonModule // ngFor, ngIf, ngStyle, ngClass, date, json + ], + providers: [ + ], + declarations: [ + CityPipe, + CityValidatorDirective, + AsyncCityValidatorDirective, + RoundTrip, + DateComponent + ], + exports:[ + CityPipe, + CityValidatorDirective, + AsyncCityValidatorDirective, + RoundTrip, + DateComponent + ] +}) +export class SharedModule { + static forRoot(): ModuleWithProviders { + return { + providers: [ + AuthGuard, + LeaveComponentGuard, + CustomPreloadingStrategy + ], + ngModule: SharedModule + }; + } +} \ No newline at end of file diff --git a/sample - Kopie/src/app/shared/validation/async-city.validator.ts b/sample - Kopie/src/app/shared/validation/async-city.validator.ts new file mode 100644 index 00000000..b3c814d5 --- /dev/null +++ b/sample - Kopie/src/app/shared/validation/async-city.validator.ts @@ -0,0 +1,33 @@ + +import {Directive} from "@angular/core"; +import {NG_ASYNC_VALIDATORS, AbstractControl} from "@angular/forms"; + +@Directive({ + selector: 'input[async-city]', + providers: [ + { + provide: NG_ASYNC_VALIDATORS, + useExisting: AsyncCityValidatorDirective, + multi: true + }] +}) +export class AsyncCityValidatorDirective { + + validate(ctrl: AbstractControl): Promise { + + return new Promise((resolve: Function) => { + setTimeout(() => { + + if (ctrl.value == "Graz" || ctrl.value == "Hamburg") { + resolve({}); + return; + } + + resolve({ 'async-city': false}); + + }, 100); + }) + + } + +} \ No newline at end of file diff --git a/sample - Kopie/src/app/shared/validation/city.validator.ts b/sample - Kopie/src/app/shared/validation/city.validator.ts new file mode 100644 index 00000000..24ce4840 --- /dev/null +++ b/sample - Kopie/src/app/shared/validation/city.validator.ts @@ -0,0 +1,51 @@ + +import {Directive, Input, Attribute} from "@angular/core"; +import {NG_VALIDATORS, Validator, AbstractControl, FormGroup} from "@angular/forms"; + +@Directive({ + selector: 'input[city]', // + providers: [ + { + provide: NG_VALIDATORS, + useExisting: CityValidatorDirective, + multi: true + } + ] +}) +export class CityValidatorDirective implements Validator { + + // @Input() city: string; + + constructor(@Attribute('city') private city: string) { + } + + validate(c: AbstractControl): any { + + + let formGroup = c.root; + let otherValueCtrl = formGroup.controls['to']; + + if (!otherValueCtrl) return { }; + + let otherValue = otherValueCtrl.value; + + if(otherValue == c.value) { + return { + city: 'rundflug' + } + } + + if (!this.city) return { } + + let allowed = this.city.split(','); //['Graz', 'Hamburg', 'Wien', 'Frankfurt']; + + if (allowed.indexOf(c.value) == -1) { + return { + city: true + } + } + + return {}; + + } +} \ No newline at end of file diff --git a/sample - Kopie/src/app/shared/validation/roundtrip.validator.ts b/sample - Kopie/src/app/shared/validation/roundtrip.validator.ts new file mode 100644 index 00000000..f1a0103a --- /dev/null +++ b/sample - Kopie/src/app/shared/validation/roundtrip.validator.ts @@ -0,0 +1,32 @@ +import {Directive} from "@angular/core"; +import {FormGroup, Validator, AbstractControl, NG_VALIDATORS, FormGroupDirective} from "@angular/forms"; + +@Directive({ + selector: 'form[round-trip]', + providers: [{ provide: NG_VALIDATORS, useExisting: RoundTrip, multi: true }] +}) +export class RoundTrip implements Validator{ + + validate(control: AbstractControl): any { + + let formGroup = control; + let fromCtrl = formGroup.controls['from']; + let toCtrl = formGroup.controls['to']; + + if (!fromCtrl || !toCtrl) return { }; + + let from = fromCtrl.value; + let to = toCtrl.value; + + if (from == to) { + return { + 'round-trip': { + city: from + } + } + } + return { }; + + } + +} \ No newline at end of file diff --git a/sample - Kopie/src/assets/.gitkeep b/sample - Kopie/src/assets/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/sample - Kopie/src/environments/environment.prod.ts b/sample - Kopie/src/environments/environment.prod.ts new file mode 100644 index 00000000..3612073b --- /dev/null +++ b/sample - Kopie/src/environments/environment.prod.ts @@ -0,0 +1,3 @@ +export const environment = { + production: true +}; diff --git a/sample - Kopie/src/environments/environment.ts b/sample - Kopie/src/environments/environment.ts new file mode 100644 index 00000000..b7f639ae --- /dev/null +++ b/sample - Kopie/src/environments/environment.ts @@ -0,0 +1,8 @@ +// The file contents for the current environment will overwrite these during build. +// The build system defaults to the dev environment which uses `environment.ts`, but if you do +// `ng build --env=prod` then `environment.prod.ts` will be used instead. +// The list of which env maps to which file can be found in `.angular-cli.json`. + +export const environment = { + production: false +}; diff --git a/sample - Kopie/src/favicon.ico b/sample - Kopie/src/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..8081c7ceaf2be08bf59010158c586170d9d2d517 GIT binary patch literal 5430 zcmc(je{54#6vvCoAI3i*G5%$U7!sA3wtMZ$fH6V9C`=eXGJb@R1%(I_{vnZtpD{6n z5Pl{DmxzBDbrB>}`90e12m8T*36WoeDLA&SD_hw{H^wM!cl_RWcVA!I+x87ee975; z@4kD^=bYPn&pmG@(+JZ`rqQEKxW<}RzhW}I!|ulN=fmjVi@x{p$cC`)5$a!)X&U+blKNvN5tg=uLvuLnuqRM;Yc*swiexsoh#XPNu{9F#c`G zQLe{yWA(Y6(;>y|-efAy11k<09(@Oo1B2@0`PtZSkqK&${ zgEY}`W@t{%?9u5rF?}Y7OL{338l*JY#P!%MVQY@oqnItpZ}?s z!r?*kwuR{A@jg2Chlf0^{q*>8n5Ir~YWf*wmsh7B5&EpHfd5@xVaj&gqsdui^spyL zB|kUoblGoO7G(MuKTfa9?pGH0@QP^b#!lM1yHWLh*2iq#`C1TdrnO-d#?Oh@XV2HK zKA{`eo{--^K&MW66Lgsktfvn#cCAc*(}qsfhrvOjMGLE?`dHVipu1J3Kgr%g?cNa8 z)pkmC8DGH~fG+dlrp(5^-QBeEvkOvv#q7MBVLtm2oD^$lJZx--_=K&Ttd=-krx(Bb zcEoKJda@S!%%@`P-##$>*u%T*mh+QjV@)Qa=Mk1?#zLk+M4tIt%}wagT{5J%!tXAE;r{@=bb%nNVxvI+C+$t?!VJ@0d@HIyMJTI{vEw0Ul ze(ha!e&qANbTL1ZneNl45t=#Ot??C0MHjjgY8%*mGisN|S6%g3;Hlx#fMNcL<87MW zZ>6moo1YD?P!fJ#Jb(4)_cc50X5n0KoDYfdPoL^iV`k&o{LPyaoqMqk92wVM#_O0l z09$(A-D+gVIlq4TA&{1T@BsUH`Bm=r#l$Z51J-U&F32+hfUP-iLo=jg7Xmy+WLq6_tWv&`wDlz#`&)Jp~iQf zZP)tu>}pIIJKuw+$&t}GQuqMd%Z>0?t%&BM&Wo^4P^Y z)c6h^f2R>X8*}q|bblAF?@;%?2>$y+cMQbN{X$)^R>vtNq_5AB|0N5U*d^T?X9{xQnJYeU{ zoZL#obI;~Pp95f1`%X3D$Mh*4^?O?IT~7HqlWguezmg?Ybq|7>qQ(@pPHbE9V?f|( z+0xo!#m@Np9PljsyxBY-UA*{U*la#8Wz2sO|48_-5t8%_!n?S$zlGe+NA%?vmxjS- zHE5O3ZarU=X}$7>;Okp(UWXJxI%G_J-@IH;%5#Rt$(WUX?6*Ux!IRd$dLP6+SmPn= z8zjm4jGjN772R{FGkXwcNv8GBcZI#@Y2m{RNF_w8(Z%^A*!bS*!}s6sh*NnURytky humW;*g7R+&|Ledvc- + + + + Sample + + + + + + + + + + + + + diff --git a/sample - Kopie/src/main.ts b/sample - Kopie/src/main.ts new file mode 100644 index 00000000..a2d2fa81 --- /dev/null +++ b/sample - Kopie/src/main.ts @@ -0,0 +1,13 @@ +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { environment } from './environments/environment'; + +import 'rxjs/add/operator/map'; +import { AppModule } from "./app/app.module"; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/sample - Kopie/src/polyfills.ts b/sample - Kopie/src/polyfills.ts new file mode 100644 index 00000000..8f52dca7 --- /dev/null +++ b/sample - Kopie/src/polyfills.ts @@ -0,0 +1,73 @@ +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** IE9, IE10 and IE11 requires all of the following polyfills. **/ +import 'core-js/es6/symbol'; +import 'core-js/es6/object'; +import 'core-js/es6/function'; +import 'core-js/es6/parse-int'; +import 'core-js/es6/parse-float'; +import 'core-js/es6/number'; +import 'core-js/es6/math'; +import 'core-js/es6/string'; +import 'core-js/es6/date'; +import 'core-js/es6/array'; +import 'core-js/es6/regexp'; +import 'core-js/es6/map'; +import 'core-js/es6/weak-map'; +import 'core-js/es6/set'; + +/** IE10 and IE11 requires the following for NgClass support on SVG elements */ +// import 'classlist.js'; // Run `npm install --save classlist.js`. + +/** IE10 and IE11 requires the following to support `@angular/animation`. */ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + + +/** Evergreen browsers require these. **/ +import 'core-js/es6/reflect'; +import 'core-js/es7/reflect'; + + +/** ALL Firefox browsers require the following to support `@angular/animation`. **/ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + + + +/*************************************************************************************************** + * Zone JS is required by Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. + + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ + +/** + * Date, currency, decimal and percent pipes. + * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 + */ +// import 'intl'; // Run `npm install --save intl`. +/** + * Need to import at least one locale-data with intl. + */ +// import 'intl/locale-data/jsonp/en'; diff --git a/sample - Kopie/src/silent-refresh.html b/sample - Kopie/src/silent-refresh.html new file mode 100644 index 00000000..7e3423c4 --- /dev/null +++ b/sample - Kopie/src/silent-refresh.html @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/sample - Kopie/src/styles.css b/sample - Kopie/src/styles.css new file mode 100644 index 00000000..90d4ee00 --- /dev/null +++ b/sample - Kopie/src/styles.css @@ -0,0 +1 @@ +/* You can add global styles to this file, and also import other style files */ diff --git a/sample - Kopie/src/test.ts b/sample - Kopie/src/test.ts new file mode 100644 index 00000000..cd612eeb --- /dev/null +++ b/sample - Kopie/src/test.ts @@ -0,0 +1,32 @@ +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js/dist/long-stack-trace-zone'; +import 'zone.js/dist/proxy.js'; +import 'zone.js/dist/sync-test'; +import 'zone.js/dist/jasmine-patch'; +import 'zone.js/dist/async-test'; +import 'zone.js/dist/fake-async-test'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. +declare const __karma__: any; +declare const require: any; + +// Prevent Karma from running prematurely. +__karma__.loaded = function () {}; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); +// Finally, start Karma to run the tests. +__karma__.start(); diff --git a/sample - Kopie/src/tsconfig.app.json b/sample - Kopie/src/tsconfig.app.json new file mode 100644 index 00000000..b364c032 --- /dev/null +++ b/sample - Kopie/src/tsconfig.app.json @@ -0,0 +1,14 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/app", + "module": "es2015", + "baseUrl": "", + "types": [] + }, + + "exclude": [ + "test.ts", + "**/*.spec.ts" + ] +} diff --git a/sample - Kopie/src/tsconfig.spec.json b/sample - Kopie/src/tsconfig.spec.json new file mode 100644 index 00000000..510e3f1f --- /dev/null +++ b/sample - Kopie/src/tsconfig.spec.json @@ -0,0 +1,20 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/spec", + "module": "commonjs", + "target": "es5", + "baseUrl": "", + "types": [ + "jasmine", + "node" + ] + }, + "files": [ + "test.ts" + ], + "include": [ + "**/*.spec.ts", + "**/*.d.ts" + ] +} diff --git a/sample - Kopie/src/typings.d.ts b/sample - Kopie/src/typings.d.ts new file mode 100644 index 00000000..ef5c7bd6 --- /dev/null +++ b/sample - Kopie/src/typings.d.ts @@ -0,0 +1,5 @@ +/* SystemJS module definition */ +declare var module: NodeModule; +interface NodeModule { + id: string; +} diff --git a/sample - Kopie/tsconfig.json b/sample - Kopie/tsconfig.json new file mode 100644 index 00000000..a6c016bf --- /dev/null +++ b/sample - Kopie/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "outDir": "./dist/out-tsc", + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es5", + "typeRoots": [ + "node_modules/@types" + ], + "lib": [ + "es2017", + "dom" + ] + } +} diff --git a/sample - Kopie/tslint.json b/sample - Kopie/tslint.json new file mode 100644 index 00000000..0db5751c --- /dev/null +++ b/sample - Kopie/tslint.json @@ -0,0 +1,142 @@ +{ + "rulesDirectory": [ + "node_modules/codelyzer" + ], + "rules": { + "arrow-return-shorthand": true, + "callable-types": true, + "class-name": true, + "comment-format": [ + true, + "check-space" + ], + "curly": true, + "eofline": true, + "forin": true, + "import-blacklist": [ + true, + "rxjs" + ], + "import-spacing": true, + "indent": [ + true, + "spaces" + ], + "interface-over-type-literal": true, + "label-position": true, + "max-line-length": [ + true, + 140 + ], + "member-access": false, + "member-ordering": [ + true, + { + "order": [ + "static-field", + "instance-field", + "static-method", + "instance-method" + ] + } + ], + "no-arg": true, + "no-bitwise": true, + "no-console": [ + true, + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-construct": true, + "no-debugger": true, + "no-duplicate-super": true, + "no-empty": false, + "no-empty-interface": true, + "no-eval": true, + "no-inferrable-types": [ + true, + "ignore-params" + ], + "no-misused-new": true, + "no-non-null-assertion": true, + "no-shadowed-variable": true, + "no-string-literal": false, + "no-string-throw": true, + "no-switch-case-fall-through": true, + "no-trailing-whitespace": true, + "no-unnecessary-initializer": true, + "no-unused-expression": true, + "no-use-before-declare": true, + "no-var-keyword": true, + "object-literal-sort-keys": false, + "one-line": [ + true, + "check-open-brace", + "check-catch", + "check-else", + "check-whitespace" + ], + "prefer-const": true, + "quotemark": [ + true, + "single" + ], + "radix": true, + "semicolon": [ + true, + "always" + ], + "triple-equals": [ + true, + "allow-null-check" + ], + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "typeof-compare": true, + "unified-signatures": true, + "variable-name": false, + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ], + "directive-selector": [ + true, + "attribute", + "app", + "camelCase" + ], + "component-selector": [ + true, + "element", + "app", + "kebab-case" + ], + "use-input-property-decorator": true, + "use-output-property-decorator": true, + "use-host-property-decorator": true, + "no-input-rename": true, + "no-output-rename": true, + "use-life-cycle-interface": true, + "use-pipe-transform-interface": true, + "component-class-suffix": true, + "directive-class-suffix": true, + "no-access-missing-member": true, + "templates-use-public": true, + "invoke-injectable": true + } +}