ngx-oauth 7.0.1 → 8.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -195
- package/fesm2022/ngx-oauth-component.mjs +272 -0
- package/fesm2022/ngx-oauth-component.mjs.map +1 -0
- package/fesm2022/ngx-oauth.mjs +487 -669
- package/fesm2022/ngx-oauth.mjs.map +1 -1
- package/package.json +15 -9
- package/types/ngx-oauth-component.d.ts +51 -0
- package/types/ngx-oauth.d.ts +165 -0
- package/components/index.d.ts +0 -1
- package/components/o-auth-login.component.d.ts +0 -47
- package/config/index.d.ts +0 -17
- package/esm2022/components/index.mjs +0 -2
- package/esm2022/components/o-auth-login.component.mjs +0 -261
- package/esm2022/config/index.mjs +0 -32
- package/esm2022/index.mjs +0 -8
- package/esm2022/models/index.mjs +0 -16
- package/esm2022/ngx-oauth.mjs +0 -5
- package/esm2022/services/index.mjs +0 -5
- package/esm2022/services/o-auth-http-client.mjs +0 -15
- package/esm2022/services/o-auth-token.service.mjs +0 -80
- package/esm2022/services/o-auth.interceptor.mjs +0 -43
- package/esm2022/services/o-auth.service.mjs +0 -302
- package/index.d.ts +0 -4
- package/models/index.d.ts +0 -100
- package/services/index.d.ts +0 -4
- package/services/o-auth-http-client.d.ts +0 -6
- package/services/o-auth-token.service.d.ts +0 -21
- package/services/o-auth.interceptor.d.ts +0 -2
- package/services/o-auth.service.d.ts +0 -42
package/README.md
CHANGED
|
@@ -1,215 +1,49 @@
|
|
|
1
|
-
|
|
1
|
+
# ngx-oauth
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
> * **resource**
|
|
5
|
-
> * **implicit**
|
|
6
|
-
> * **authorization code**
|
|
7
|
-
> * **client credentials**
|
|
3
|
+
OAuth 2.1 library for Angular 21. Zoneless, signal-based.
|
|
8
4
|
|
|
9
|
-
|
|
5
|
+
## Projects
|
|
10
6
|
|
|
11
|
-
|
|
7
|
+
- `projects/ngx-oauth/` — the library
|
|
8
|
+
- `projects/ngx-oauth-sample/` — demo app
|
|
12
9
|
|
|
13
|
-
|
|
10
|
+
## Commands
|
|
14
11
|
|
|
15
|
-
|
|
12
|
+
| Command | Description |
|
|
13
|
+
|---|---|
|
|
14
|
+
| `npm run build:lib` | Build the library |
|
|
15
|
+
| `npm run build:app` | Build the demo app |
|
|
16
|
+
| `npm start` | Serve demo (`https://localhost:3000`, SSL enabled) |
|
|
17
|
+
| `npm test` | Run library tests (Vitest) |
|
|
18
|
+
| `npm run lint` | Lint both projects |
|
|
19
|
+
| `npm run format` | Format code |
|
|
16
20
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
```typescript
|
|
20
|
-
const oauthConfig = {
|
|
21
|
-
config: {
|
|
22
|
-
tokenPath: '/authorizationserver/oauth/token',
|
|
23
|
-
revokePath: '/authorizationserver/oauth/revoke', // optional
|
|
24
|
-
clientId: '<your_client_id>',
|
|
25
|
-
clientSecret: '<your_client_secret>'
|
|
26
|
-
},
|
|
27
|
-
storage: localStorage, // Optional, default value is localStorage
|
|
28
|
-
storageKey: 'token' // Optional, default value is 'token'
|
|
29
|
-
};
|
|
21
|
+
## Configure the demo
|
|
30
22
|
|
|
31
|
-
|
|
32
|
-
export const appConfig: ApplicationConfig = {
|
|
33
|
-
providers: [
|
|
34
|
-
provideRouter(routes),
|
|
35
|
-
provideClientHydration(),
|
|
36
|
-
provideHttpClient(
|
|
37
|
-
withFetch(),
|
|
38
|
-
withInterceptors([OAuthInterceptor])
|
|
39
|
-
),
|
|
40
|
-
provideOAuthConfig(keycloakOpenIDConfig),
|
|
41
|
-
]
|
|
42
|
-
};
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Example for **authorization code** flow with `OIDC` and `PKCE`
|
|
46
|
-
For public oauth clients `clientSecret` can be removed since is not used
|
|
23
|
+
Edit `projects/ngx-oauth-sample/src/app/app.config.ts`:
|
|
47
24
|
|
|
48
25
|
```typescript
|
|
49
26
|
const oauthConfig = {
|
|
50
27
|
config: {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
tokenPath: '/o/token/',
|
|
55
|
-
revokePath: '/o/revoke/',
|
|
56
|
-
scope: 'openid email profile',
|
|
57
|
-
pkce: true
|
|
58
|
-
},
|
|
59
|
-
}
|
|
60
|
-
```
|
|
28
|
+
// autodiscovery
|
|
29
|
+
issuerPath: 'https://your-idp.com/realms/realm',
|
|
30
|
+
clientId: 'your-client-id',
|
|
61
31
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
config: {
|
|
67
|
-
issuerPath: 'http://localhost:8080/realms/<some-realm>',
|
|
68
|
-
clientId: '<your_client_id>',
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
```
|
|
32
|
+
// or manual endpoints
|
|
33
|
+
// authorizePath: '/authorize',
|
|
34
|
+
// tokenPath: '/token',
|
|
35
|
+
// clientId: 'your-client-id',
|
|
72
36
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
```typescript
|
|
76
|
-
const azureOpenIDConfig = {
|
|
77
|
-
config: {
|
|
78
|
-
issuerPath: 'https://login.microsoftonline.com/common/v2.0', // for common make sure you app has "signInAudience": "AzureADandPersonalMicrosoftAccount",
|
|
79
|
-
clientId: '<your_client_id>',
|
|
80
|
-
scope: 'openid profile email offline_access',
|
|
81
|
-
pkce: true // manually, since is required, but code_challenge_methods_supported is not in openid configuration
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
***Google*** example
|
|
87
|
-
|
|
88
|
-
```typescript
|
|
89
|
-
const googleOpenIDConfig = {
|
|
90
|
-
type: OAuthType.AUTHORIZATION_CODE,
|
|
91
|
-
config: {
|
|
92
|
-
issuerPath: 'https://accounts.google.com',
|
|
93
|
-
clientId: '<your_client_id>',
|
|
94
|
-
clientSecret: '<your_client_secret>',
|
|
95
|
-
scope: 'openid profile email'
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
You can use the `oauth-login` component
|
|
101
|
-
|
|
102
|
-
```html
|
|
103
|
-
|
|
104
|
-
<div class="login-component">
|
|
105
|
-
<oauth-login></oauth-login>
|
|
106
|
-
</div>
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
or with params
|
|
110
|
-
|
|
111
|
-
```typescript
|
|
112
|
-
@Component({
|
|
113
|
-
selector: 'login-component',
|
|
114
|
-
template: `
|
|
115
|
-
<oauth-login [type]="type"
|
|
116
|
-
[i18n]="i18n"
|
|
117
|
-
[profileName$]="profileName$"
|
|
118
|
-
[useLogoutUrl]="useLogoutUrl"
|
|
119
|
-
[(state)]="state"></oauth-login>
|
|
120
|
-
`
|
|
121
|
-
})
|
|
122
|
-
export class LoginComponent {
|
|
123
|
-
i18n: OAuthLoginI18n = {
|
|
124
|
-
username: 'Username'
|
|
125
|
-
};
|
|
126
|
-
state = 'some_salt_hash_or_whatever';
|
|
127
|
-
// not only revoke tokens but also access the logout page if defined.
|
|
128
|
-
// logoutPath needs to be defined. logoutRedirectUri is optional. Current url will be used if undefined
|
|
129
|
-
useLogoutUrl = true;
|
|
130
|
-
|
|
131
|
-
constructor(private oauthService: OAuthService) {
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
get profileName$(): Observable<string> {
|
|
135
|
-
// ex: get profile name form oidc user_info endpoint or get it from some user service
|
|
136
|
-
return this.oauthService.userInfo$.pipe(
|
|
137
|
-
map(v => `${v.name} ${this.getPicture(v.picture)}`)
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// show profile picture if user info provides thumbnail url
|
|
142
|
-
getPicture(picture?: string) {
|
|
143
|
-
return picture && `<img class="rounded-circle img-thumbnail" src="${picture}">` || ''
|
|
37
|
+
scope: 'openid profile email',
|
|
38
|
+
pkce: true
|
|
144
39
|
}
|
|
145
40
|
}
|
|
146
41
|
```
|
|
147
42
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
or create your custom login template using OAuthService
|
|
151
|
-
|
|
152
|
-
```html
|
|
153
|
-
|
|
154
|
-
<div class="login-component">
|
|
155
|
-
<oauth-login>
|
|
156
|
-
<ng-template #login let-li="login" let-s="status" let-lo="logout">
|
|
157
|
-
<form (submit)="li({username: username, password: password})">
|
|
158
|
-
<ng-container *ngIf="s === OAuthStatus.AUTHORIZED; else loginTemplate">
|
|
159
|
-
<h2>profileName</h2>
|
|
160
|
-
<button (click)="lo()">Logout</button>
|
|
161
|
-
</ng-container>
|
|
162
|
-
<ng-template #loginTemplate>
|
|
163
|
-
<div class="card">
|
|
164
|
-
<div class="card-header text-center">
|
|
165
|
-
<h2 class="m-0 p-3">
|
|
166
|
-
<strong>Login</strong>
|
|
167
|
-
</h2>
|
|
168
|
-
</div>
|
|
169
|
-
<div class="card-body">
|
|
170
|
-
<div class="mb-3">
|
|
171
|
-
<input type="text" class="form-control" name="username" required [(ngModel)]="oauthService.username"
|
|
172
|
-
placeholder="username">
|
|
173
|
-
</div>
|
|
174
|
-
<div class="mb-3">
|
|
175
|
-
<input type="password" class="form-control" name="password" required [(ngModel)]="oauthService.password"
|
|
176
|
-
placeholder="password">
|
|
177
|
-
</div>
|
|
178
|
-
</div>
|
|
179
|
-
<div class="card-footer">
|
|
180
|
-
<div class="text-center">
|
|
181
|
-
<button type="submit" class="btn btn-primary">Submit</button>
|
|
182
|
-
</div>
|
|
183
|
-
</div>
|
|
184
|
-
</div>
|
|
185
|
-
</ng-template>
|
|
186
|
-
</form>
|
|
187
|
-
</ng-template>
|
|
188
|
-
</oauth-login>
|
|
189
|
-
</div>
|
|
190
|
-
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
and import OAuthService in your login component constructor
|
|
194
|
-
|
|
195
|
-
## Installing:
|
|
196
|
-
|
|
197
|
-
```
|
|
198
|
-
npm install ngx-oauth --save
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
## App Requirements
|
|
202
|
-
|
|
203
|
-
* none
|
|
204
|
-
|
|
205
|
-
## Running the demo
|
|
43
|
+
## Library Usage
|
|
206
44
|
|
|
207
|
-
|
|
208
|
-
* in app.component.ts add your **clientId, secret**, oauth server **token endpoint** and user **profile endpoint**
|
|
209
|
-
* npm i
|
|
210
|
-
* npm run build:lib
|
|
211
|
-
* npm start
|
|
45
|
+
See [ngx-oauth/README.md](projects/ngx-oauth/README.md) for the full library documentation.
|
|
212
46
|
|
|
213
|
-
|
|
47
|
+
## License
|
|
214
48
|
|
|
215
|
-
[MIT
|
|
49
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, PLATFORM_ID, input, viewChild, computed, signal, effect, ViewEncapsulation, Component } from '@angular/core';
|
|
3
|
+
import { isPlatformBrowser, CommonModule } from '@angular/common';
|
|
4
|
+
import * as i1 from '@angular/forms';
|
|
5
|
+
import { FormsModule } from '@angular/forms';
|
|
6
|
+
import * as i2 from '@angular/material/button';
|
|
7
|
+
import { MatButtonModule } from '@angular/material/button';
|
|
8
|
+
import * as i3 from '@angular/material/form-field';
|
|
9
|
+
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
10
|
+
import * as i4 from '@angular/material/icon';
|
|
11
|
+
import { MatIconModule } from '@angular/material/icon';
|
|
12
|
+
import * as i5 from '@angular/material/input';
|
|
13
|
+
import { MatInputModule } from '@angular/material/input';
|
|
14
|
+
import * as i6 from '@angular/material/list';
|
|
15
|
+
import { MatListModule } from '@angular/material/list';
|
|
16
|
+
import * as i7 from '@angular/material/menu';
|
|
17
|
+
import { MatMenuTrigger, MatMenuModule } from '@angular/material/menu';
|
|
18
|
+
import { OAUTH, OAUTH_USER, OAuthStatus, OAuthType } from 'ngx-oauth';
|
|
19
|
+
|
|
20
|
+
const defaultI18n = {
|
|
21
|
+
username: 'Username',
|
|
22
|
+
password: 'Password',
|
|
23
|
+
submit: 'Sign in',
|
|
24
|
+
logout: 'Sign out',
|
|
25
|
+
notAuthorized: 'Sign in',
|
|
26
|
+
authorized: 'Welcome',
|
|
27
|
+
denied: 'Access denied. Try again.',
|
|
28
|
+
dismiss: 'Dismiss',
|
|
29
|
+
showPassword: 'Show password',
|
|
30
|
+
hidePassword: 'Hide password'
|
|
31
|
+
};
|
|
32
|
+
class OAuthLoginComponent {
|
|
33
|
+
constructor() {
|
|
34
|
+
this.oauth = inject(OAUTH);
|
|
35
|
+
this.user = inject(OAUTH_USER);
|
|
36
|
+
this.isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
|
|
37
|
+
this.OAuthStatus = OAuthStatus;
|
|
38
|
+
this.config = input({}, ...(ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []));
|
|
39
|
+
this.i18n = input(defaultI18n, { ...(ngDevMode ? { debugName: "i18n" } : /* istanbul ignore next */ {}), transform: v => ({ ...defaultI18n, ...v }) });
|
|
40
|
+
this.trigger = viewChild(MatMenuTrigger, ...(ngDevMode ? [{ debugName: "trigger" }] : /* istanbul ignore next */ []));
|
|
41
|
+
this.status = this.oauth.status;
|
|
42
|
+
this.isAuthCode = computed(() => {
|
|
43
|
+
const { responseType } = this.config();
|
|
44
|
+
return responseType && responseType !== OAuthType.RESOURCE;
|
|
45
|
+
}, ...(ngDevMode ? [{ debugName: "isAuthCode" }] : /* istanbul ignore next */ []));
|
|
46
|
+
this.profile = computed(() => {
|
|
47
|
+
const info = this.user.value() ?? {};
|
|
48
|
+
const title = info.name || info.preferred_username || info.email || info.sub || '';
|
|
49
|
+
const subtitle = info.email ?? '';
|
|
50
|
+
const initials = `${info.given_name?.charAt(0) ?? ''}${info.family_name?.charAt(0) ?? ''}`.toUpperCase();
|
|
51
|
+
return { title, subtitle, picture: info.picture, initials };
|
|
52
|
+
}, ...(ngDevMode ? [{ debugName: "profile" }] : /* istanbul ignore next */ []));
|
|
53
|
+
this.visible = signal(false, ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
|
|
54
|
+
this.showError = signal(true, ...(ngDevMode ? [{ debugName: "showError" }] : /* istanbul ignore next */ []));
|
|
55
|
+
this.username = '';
|
|
56
|
+
this.password = '';
|
|
57
|
+
effect(() => {
|
|
58
|
+
if (this.status() === OAuthStatus.DENIED)
|
|
59
|
+
this.showError.set(true);
|
|
60
|
+
});
|
|
61
|
+
effect(() => {
|
|
62
|
+
const { username, password } = this.config();
|
|
63
|
+
if (username !== undefined)
|
|
64
|
+
this.username = username;
|
|
65
|
+
if (password !== undefined)
|
|
66
|
+
this.password = password;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
logout() {
|
|
70
|
+
this.trigger()?.closeMenu();
|
|
71
|
+
return this.oauth.logout(this.config().logoutRedirectUri, this.config().state);
|
|
72
|
+
}
|
|
73
|
+
dismissError() {
|
|
74
|
+
this.showError.set(false);
|
|
75
|
+
return this.oauth.logout();
|
|
76
|
+
}
|
|
77
|
+
login(parameters) {
|
|
78
|
+
this.trigger()?.closeMenu();
|
|
79
|
+
return this.oauth.login(parameters);
|
|
80
|
+
}
|
|
81
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: OAuthLoginComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
82
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.10", type: OAuthLoginComponent, isStandalone: true, selector: "oauth-login", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, i18n: { classPropertyName: "i18n", publicName: "i18n", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "trigger", first: true, predicate: MatMenuTrigger, descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
83
|
+
@if (isBrowser) {
|
|
84
|
+
@if (status(); as s) {
|
|
85
|
+
@if (s === OAuthStatus.NOT_AUTHORIZED && isAuthCode()) {
|
|
86
|
+
<button matIconButton type="button" [attr.aria-label]="i18n().notAuthorized" (click)="login(config())">
|
|
87
|
+
<mat-icon [fontSet]="'material-icons-outlined'">account_circle</mat-icon>
|
|
88
|
+
</button>
|
|
89
|
+
} @else {
|
|
90
|
+
<button
|
|
91
|
+
matIconButton
|
|
92
|
+
[matMenuTriggerFor]="menu"
|
|
93
|
+
[attr.aria-label]="s === OAuthStatus.AUTHORIZED ? i18n().authorized : i18n().notAuthorized">
|
|
94
|
+
<mat-icon [fontSet]="s === OAuthStatus.AUTHORIZED ? 'material-icons' : 'material-icons-outlined'">account_circle</mat-icon>
|
|
95
|
+
</button>
|
|
96
|
+
<mat-menu #menu="matMenu" xPosition="after">
|
|
97
|
+
<div tabindex="-1" (click)="$event.stopPropagation()" (keydown)="$event.stopPropagation()" class="oauth-login-content">
|
|
98
|
+
@if (s === OAuthStatus.AUTHORIZED) {
|
|
99
|
+
<mat-list class="p-0!">
|
|
100
|
+
<mat-list-item>
|
|
101
|
+
@if (profile().picture) {
|
|
102
|
+
<img matListItemAvatar [src]="profile().picture" alt="" />
|
|
103
|
+
} @else {
|
|
104
|
+
<div
|
|
105
|
+
matListItemAvatar
|
|
106
|
+
class="flex! items-center justify-center bg-blue-600 text-sm font-semibold uppercase text-white">
|
|
107
|
+
{{ profile().initials || '?' }}
|
|
108
|
+
</div>
|
|
109
|
+
}
|
|
110
|
+
<span matListItemTitle>{{ profile().title }}</span>
|
|
111
|
+
@if (profile().subtitle) {
|
|
112
|
+
<span matListItemLine>{{ profile().subtitle }}</span>
|
|
113
|
+
}
|
|
114
|
+
<div matListItemMeta>
|
|
115
|
+
<button mat-icon-button type="button" [attr.aria-label]="i18n().logout" (click)="logout()">
|
|
116
|
+
<mat-icon>close</mat-icon>
|
|
117
|
+
</button>
|
|
118
|
+
</div>
|
|
119
|
+
</mat-list-item>
|
|
120
|
+
</mat-list>
|
|
121
|
+
} @else if (s === OAuthStatus.DENIED && showError()) {
|
|
122
|
+
<mat-list class="p-0!">
|
|
123
|
+
<mat-list-item class="!bg-red-50 text-red-800">
|
|
124
|
+
<mat-icon matListItemIcon class="!text-red-600">error_outline</mat-icon>
|
|
125
|
+
<span matListItemLine class="flex-1 text-sm" [innerHTML]="i18n().denied"></span>
|
|
126
|
+
<button mat-icon-button matListItemMeta type="button" (click)="dismissError()" [attr.aria-label]="i18n().dismiss">
|
|
127
|
+
<mat-icon>close</mat-icon>
|
|
128
|
+
</button>
|
|
129
|
+
</mat-list-item>
|
|
130
|
+
</mat-list>
|
|
131
|
+
} @else {
|
|
132
|
+
<form
|
|
133
|
+
#form="ngForm"
|
|
134
|
+
(ngSubmit)="login({ username: username, password: password })"
|
|
135
|
+
autocomplete="on"
|
|
136
|
+
class="flex flex-col gap-3 m-3">
|
|
137
|
+
<mat-form-field subscriptSizing="dynamic" class="block w-full">
|
|
138
|
+
<mat-label>{{ i18n().username }}</mat-label>
|
|
139
|
+
<mat-icon matPrefix>alternate_email</mat-icon>
|
|
140
|
+
<input matInput name="username" autocomplete="username" required [(ngModel)]="username" />
|
|
141
|
+
</mat-form-field>
|
|
142
|
+
<mat-form-field subscriptSizing="dynamic" class="block w-full">
|
|
143
|
+
<mat-label>{{ i18n().password }}</mat-label>
|
|
144
|
+
<mat-icon matPrefix>password</mat-icon>
|
|
145
|
+
<input
|
|
146
|
+
matInput
|
|
147
|
+
name="password"
|
|
148
|
+
autocomplete="current-password"
|
|
149
|
+
required
|
|
150
|
+
[type]="visible() ? 'text' : 'password'"
|
|
151
|
+
[(ngModel)]="password" />
|
|
152
|
+
<button
|
|
153
|
+
mat-icon-button
|
|
154
|
+
matSuffix
|
|
155
|
+
type="button"
|
|
156
|
+
(click)="visible.set(!visible())"
|
|
157
|
+
[attr.aria-label]="visible() ? i18n().hidePassword : i18n().showPassword">
|
|
158
|
+
<mat-icon>{{ visible() ? 'visibility_off' : 'visibility' }}</mat-icon>
|
|
159
|
+
</button>
|
|
160
|
+
</mat-form-field>
|
|
161
|
+
<div class="flex justify-end pt-1">
|
|
162
|
+
<button mat-flat-button type="submit" [disabled]="form.invalid">{{ i18n().submit }}</button>
|
|
163
|
+
</div>
|
|
164
|
+
</form>
|
|
165
|
+
}
|
|
166
|
+
</div>
|
|
167
|
+
</mat-menu>
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
`, isInline: true, styles: [".mat-mdc-menu-panel:has(.oauth-login-content){max-width:none;min-width:360px}.oauth-login-content .mat-mdc-list-item .mdc-list-item__end{align-self:center!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatPrefix, selector: "[matPrefix], [matIconPrefix], [matTextPrefix]", inputs: ["matTextPrefix"] }, { kind: "directive", type: i3.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatListModule }, { kind: "component", type: i6.MatList, selector: "mat-list", exportAs: ["matList"] }, { kind: "component", type: i6.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "directive", type: i6.MatListItemAvatar, selector: "[matListItemAvatar]" }, { kind: "directive", type: i6.MatListItemIcon, selector: "[matListItemIcon]" }, { kind: "directive", type: i6.MatListItemLine, selector: "[matListItemLine]" }, { kind: "directive", type: i6.MatListItemTitle, selector: "[matListItemTitle]" }, { kind: "directive", type: i6.MatListItemMeta, selector: "[matListItemMeta]" }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i7.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "directive", type: i7.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }], encapsulation: i0.ViewEncapsulation.None }); }
|
|
172
|
+
}
|
|
173
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: OAuthLoginComponent, decorators: [{
|
|
174
|
+
type: Component,
|
|
175
|
+
args: [{ selector: 'oauth-login', standalone: true, imports: [CommonModule, FormsModule, MatButtonModule, MatFormFieldModule, MatIconModule, MatInputModule, MatListModule, MatMenuModule], template: `
|
|
176
|
+
@if (isBrowser) {
|
|
177
|
+
@if (status(); as s) {
|
|
178
|
+
@if (s === OAuthStatus.NOT_AUTHORIZED && isAuthCode()) {
|
|
179
|
+
<button matIconButton type="button" [attr.aria-label]="i18n().notAuthorized" (click)="login(config())">
|
|
180
|
+
<mat-icon [fontSet]="'material-icons-outlined'">account_circle</mat-icon>
|
|
181
|
+
</button>
|
|
182
|
+
} @else {
|
|
183
|
+
<button
|
|
184
|
+
matIconButton
|
|
185
|
+
[matMenuTriggerFor]="menu"
|
|
186
|
+
[attr.aria-label]="s === OAuthStatus.AUTHORIZED ? i18n().authorized : i18n().notAuthorized">
|
|
187
|
+
<mat-icon [fontSet]="s === OAuthStatus.AUTHORIZED ? 'material-icons' : 'material-icons-outlined'">account_circle</mat-icon>
|
|
188
|
+
</button>
|
|
189
|
+
<mat-menu #menu="matMenu" xPosition="after">
|
|
190
|
+
<div tabindex="-1" (click)="$event.stopPropagation()" (keydown)="$event.stopPropagation()" class="oauth-login-content">
|
|
191
|
+
@if (s === OAuthStatus.AUTHORIZED) {
|
|
192
|
+
<mat-list class="p-0!">
|
|
193
|
+
<mat-list-item>
|
|
194
|
+
@if (profile().picture) {
|
|
195
|
+
<img matListItemAvatar [src]="profile().picture" alt="" />
|
|
196
|
+
} @else {
|
|
197
|
+
<div
|
|
198
|
+
matListItemAvatar
|
|
199
|
+
class="flex! items-center justify-center bg-blue-600 text-sm font-semibold uppercase text-white">
|
|
200
|
+
{{ profile().initials || '?' }}
|
|
201
|
+
</div>
|
|
202
|
+
}
|
|
203
|
+
<span matListItemTitle>{{ profile().title }}</span>
|
|
204
|
+
@if (profile().subtitle) {
|
|
205
|
+
<span matListItemLine>{{ profile().subtitle }}</span>
|
|
206
|
+
}
|
|
207
|
+
<div matListItemMeta>
|
|
208
|
+
<button mat-icon-button type="button" [attr.aria-label]="i18n().logout" (click)="logout()">
|
|
209
|
+
<mat-icon>close</mat-icon>
|
|
210
|
+
</button>
|
|
211
|
+
</div>
|
|
212
|
+
</mat-list-item>
|
|
213
|
+
</mat-list>
|
|
214
|
+
} @else if (s === OAuthStatus.DENIED && showError()) {
|
|
215
|
+
<mat-list class="p-0!">
|
|
216
|
+
<mat-list-item class="!bg-red-50 text-red-800">
|
|
217
|
+
<mat-icon matListItemIcon class="!text-red-600">error_outline</mat-icon>
|
|
218
|
+
<span matListItemLine class="flex-1 text-sm" [innerHTML]="i18n().denied"></span>
|
|
219
|
+
<button mat-icon-button matListItemMeta type="button" (click)="dismissError()" [attr.aria-label]="i18n().dismiss">
|
|
220
|
+
<mat-icon>close</mat-icon>
|
|
221
|
+
</button>
|
|
222
|
+
</mat-list-item>
|
|
223
|
+
</mat-list>
|
|
224
|
+
} @else {
|
|
225
|
+
<form
|
|
226
|
+
#form="ngForm"
|
|
227
|
+
(ngSubmit)="login({ username: username, password: password })"
|
|
228
|
+
autocomplete="on"
|
|
229
|
+
class="flex flex-col gap-3 m-3">
|
|
230
|
+
<mat-form-field subscriptSizing="dynamic" class="block w-full">
|
|
231
|
+
<mat-label>{{ i18n().username }}</mat-label>
|
|
232
|
+
<mat-icon matPrefix>alternate_email</mat-icon>
|
|
233
|
+
<input matInput name="username" autocomplete="username" required [(ngModel)]="username" />
|
|
234
|
+
</mat-form-field>
|
|
235
|
+
<mat-form-field subscriptSizing="dynamic" class="block w-full">
|
|
236
|
+
<mat-label>{{ i18n().password }}</mat-label>
|
|
237
|
+
<mat-icon matPrefix>password</mat-icon>
|
|
238
|
+
<input
|
|
239
|
+
matInput
|
|
240
|
+
name="password"
|
|
241
|
+
autocomplete="current-password"
|
|
242
|
+
required
|
|
243
|
+
[type]="visible() ? 'text' : 'password'"
|
|
244
|
+
[(ngModel)]="password" />
|
|
245
|
+
<button
|
|
246
|
+
mat-icon-button
|
|
247
|
+
matSuffix
|
|
248
|
+
type="button"
|
|
249
|
+
(click)="visible.set(!visible())"
|
|
250
|
+
[attr.aria-label]="visible() ? i18n().hidePassword : i18n().showPassword">
|
|
251
|
+
<mat-icon>{{ visible() ? 'visibility_off' : 'visibility' }}</mat-icon>
|
|
252
|
+
</button>
|
|
253
|
+
</mat-form-field>
|
|
254
|
+
<div class="flex justify-end pt-1">
|
|
255
|
+
<button mat-flat-button type="submit" [disabled]="form.invalid">{{ i18n().submit }}</button>
|
|
256
|
+
</div>
|
|
257
|
+
</form>
|
|
258
|
+
}
|
|
259
|
+
</div>
|
|
260
|
+
</mat-menu>
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
`, encapsulation: ViewEncapsulation.None, styles: [".mat-mdc-menu-panel:has(.oauth-login-content){max-width:none;min-width:360px}.oauth-login-content .mat-mdc-list-item .mdc-list-item__end{align-self:center!important}\n"] }]
|
|
265
|
+
}], ctorParameters: () => [], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], i18n: [{ type: i0.Input, args: [{ isSignal: true, alias: "i18n", required: false }] }], trigger: [{ type: i0.ViewChild, args: [i0.forwardRef(() => MatMenuTrigger), { isSignal: true }] }] } });
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Generated bundle index. Do not edit.
|
|
269
|
+
*/
|
|
270
|
+
|
|
271
|
+
export { OAuthLoginComponent };
|
|
272
|
+
//# sourceMappingURL=ngx-oauth-component.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ngx-oauth-component.mjs","sources":["../../../projects/ngx-oauth/component/o-auth-login.component.ts","../../../projects/ngx-oauth/component/ngx-oauth-component.ts"],"sourcesContent":["import { Component, computed, effect, inject, input, PLATFORM_ID, signal, viewChild, ViewEncapsulation } from '@angular/core'\nimport { CommonModule, isPlatformBrowser } from '@angular/common'\nimport { FormsModule } from '@angular/forms'\nimport { MatButtonModule } from '@angular/material/button'\nimport { MatFormFieldModule } from '@angular/material/form-field'\nimport { MatIconModule } from '@angular/material/icon'\nimport { MatInputModule } from '@angular/material/input'\nimport { MatListModule } from '@angular/material/list'\nimport { MatMenuModule, MatMenuTrigger } from '@angular/material/menu'\nimport { AuthorizationCodeParameters, OAUTH, OAUTH_USER, OAuthParameters, OAuthStatus, OAuthType, ResourceOwnerParameters } from 'ngx-oauth'\n\nexport type OAuthLoginConfig = Partial<ResourceOwnerParameters & AuthorizationCodeParameters & { logoutRedirectUri: string }>\n\nexport type OAuthLoginI18n = {\n username?: string\n password?: string\n submit?: string\n logout?: string\n notAuthorized?: string\n authorized?: string\n denied?: string\n dismiss?: string\n showPassword?: string\n hidePassword?: string\n}\n\nconst defaultI18n: Required<OAuthLoginI18n> = {\n username: 'Username',\n password: 'Password',\n submit: 'Sign in',\n logout: 'Sign out',\n notAuthorized: 'Sign in',\n authorized: 'Welcome',\n denied: 'Access denied. Try again.',\n dismiss: 'Dismiss',\n showPassword: 'Show password',\n hidePassword: 'Hide password'\n}\n\n@Component({\n selector: 'oauth-login',\n standalone: true,\n imports: [CommonModule, FormsModule, MatButtonModule, MatFormFieldModule, MatIconModule, MatInputModule, MatListModule, MatMenuModule],\n template: `\n @if (isBrowser) {\n @if (status(); as s) {\n @if (s === OAuthStatus.NOT_AUTHORIZED && isAuthCode()) {\n <button matIconButton type=\"button\" [attr.aria-label]=\"i18n().notAuthorized\" (click)=\"login(config())\">\n <mat-icon [fontSet]=\"'material-icons-outlined'\">account_circle</mat-icon>\n </button>\n } @else {\n <button\n matIconButton\n [matMenuTriggerFor]=\"menu\"\n [attr.aria-label]=\"s === OAuthStatus.AUTHORIZED ? i18n().authorized : i18n().notAuthorized\">\n <mat-icon [fontSet]=\"s === OAuthStatus.AUTHORIZED ? 'material-icons' : 'material-icons-outlined'\">account_circle</mat-icon>\n </button>\n <mat-menu #menu=\"matMenu\" xPosition=\"after\">\n <div tabindex=\"-1\" (click)=\"$event.stopPropagation()\" (keydown)=\"$event.stopPropagation()\" class=\"oauth-login-content\">\n @if (s === OAuthStatus.AUTHORIZED) {\n <mat-list class=\"p-0!\">\n <mat-list-item>\n @if (profile().picture) {\n <img matListItemAvatar [src]=\"profile().picture\" alt=\"\" />\n } @else {\n <div\n matListItemAvatar\n class=\"flex! items-center justify-center bg-blue-600 text-sm font-semibold uppercase text-white\">\n {{ profile().initials || '?' }}\n </div>\n }\n <span matListItemTitle>{{ profile().title }}</span>\n @if (profile().subtitle) {\n <span matListItemLine>{{ profile().subtitle }}</span>\n }\n <div matListItemMeta>\n <button mat-icon-button type=\"button\" [attr.aria-label]=\"i18n().logout\" (click)=\"logout()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </mat-list-item>\n </mat-list>\n } @else if (s === OAuthStatus.DENIED && showError()) {\n <mat-list class=\"p-0!\">\n <mat-list-item class=\"!bg-red-50 text-red-800\">\n <mat-icon matListItemIcon class=\"!text-red-600\">error_outline</mat-icon>\n <span matListItemLine class=\"flex-1 text-sm\" [innerHTML]=\"i18n().denied\"></span>\n <button mat-icon-button matListItemMeta type=\"button\" (click)=\"dismissError()\" [attr.aria-label]=\"i18n().dismiss\">\n <mat-icon>close</mat-icon>\n </button>\n </mat-list-item>\n </mat-list>\n } @else {\n <form\n #form=\"ngForm\"\n (ngSubmit)=\"login({ username: username, password: password })\"\n autocomplete=\"on\"\n class=\"flex flex-col gap-3 m-3\">\n <mat-form-field subscriptSizing=\"dynamic\" class=\"block w-full\">\n <mat-label>{{ i18n().username }}</mat-label>\n <mat-icon matPrefix>alternate_email</mat-icon>\n <input matInput name=\"username\" autocomplete=\"username\" required [(ngModel)]=\"username\" />\n </mat-form-field>\n <mat-form-field subscriptSizing=\"dynamic\" class=\"block w-full\">\n <mat-label>{{ i18n().password }}</mat-label>\n <mat-icon matPrefix>password</mat-icon>\n <input\n matInput\n name=\"password\"\n autocomplete=\"current-password\"\n required\n [type]=\"visible() ? 'text' : 'password'\"\n [(ngModel)]=\"password\" />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n (click)=\"visible.set(!visible())\"\n [attr.aria-label]=\"visible() ? i18n().hidePassword : i18n().showPassword\">\n <mat-icon>{{ visible() ? 'visibility_off' : 'visibility' }}</mat-icon>\n </button>\n </mat-form-field>\n <div class=\"flex justify-end pt-1\">\n <button mat-flat-button type=\"submit\" [disabled]=\"form.invalid\">{{ i18n().submit }}</button>\n </div>\n </form>\n }\n </div>\n </mat-menu>\n }\n }\n }\n `,\n styles: `\n .mat-mdc-menu-panel:has(.oauth-login-content) {\n max-width: none;\n min-width: 360px;\n }\n .oauth-login-content .mat-mdc-list-item .mdc-list-item__end {\n align-self: center !important;\n }\n `,\n encapsulation: ViewEncapsulation.None\n})\nexport class OAuthLoginComponent {\n private oauth = inject(OAUTH)\n private user = inject(OAUTH_USER)\n protected readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID))\n readonly OAuthStatus = OAuthStatus\n readonly config = input<OAuthLoginConfig>({})\n readonly i18n = input<Required<OAuthLoginI18n>, OAuthLoginI18n | undefined>(defaultI18n, {\n transform: v => ({ ...defaultI18n, ...v })\n })\n readonly trigger = viewChild(MatMenuTrigger)\n protected readonly status = this.oauth.status\n protected readonly isAuthCode = computed(() => {\n const { responseType } = this.config()\n return responseType && responseType !== OAuthType.RESOURCE\n })\n protected readonly profile = computed(() => {\n const info = this.user.value() ?? {}\n const title = info.name || info.preferred_username || info.email || info.sub || ''\n const subtitle = info.email ?? ''\n const initials = `${info.given_name?.charAt(0) ?? ''}${info.family_name?.charAt(0) ?? ''}`.toUpperCase()\n return { title, subtitle, picture: info.picture, initials }\n })\n protected readonly visible = signal(false)\n protected readonly showError = signal(true)\n username = ''\n password = ''\n\n constructor() {\n effect(() => {\n if (this.status() === OAuthStatus.DENIED) this.showError.set(true)\n })\n effect(() => {\n const { username, password } = this.config()\n if (username !== undefined) this.username = username\n if (password !== undefined) this.password = password\n })\n }\n\n logout() {\n this.trigger()?.closeMenu()\n return this.oauth.logout(this.config().logoutRedirectUri, this.config().state)\n }\n\n dismissError() {\n this.showError.set(false)\n return this.oauth.logout()\n }\n\n login(parameters: OAuthLoginConfig) {\n this.trigger()?.closeMenu()\n return this.oauth.login(parameters as OAuthParameters)\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;AA0BA,MAAM,WAAW,GAA6B;AAC5C,IAAA,QAAQ,EAAE,UAAU;AACpB,IAAA,QAAQ,EAAE,UAAU;AACpB,IAAA,MAAM,EAAE,SAAS;AACjB,IAAA,MAAM,EAAE,UAAU;AAClB,IAAA,aAAa,EAAE,SAAS;AACxB,IAAA,UAAU,EAAE,SAAS;AACrB,IAAA,MAAM,EAAE,2BAA2B;AACnC,IAAA,OAAO,EAAE,SAAS;AAClB,IAAA,YAAY,EAAE,eAAe;AAC7B,IAAA,YAAY,EAAE;CACf;MA2GY,mBAAmB,CAAA;AA2B9B,IAAA,WAAA,GAAA;AA1BQ,QAAA,IAAA,CAAA,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;AACrB,QAAA,IAAA,CAAA,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC;QACd,IAAA,CAAA,SAAS,GAAG,iBAAiB,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QAC5D,IAAA,CAAA,WAAW,GAAG,WAAW;AACzB,QAAA,IAAA,CAAA,MAAM,GAAG,KAAK,CAAmB,EAAE,6EAAC;QACpC,IAAA,CAAA,IAAI,GAAG,KAAK,CAAuD,WAAW,4EACrF,SAAS,EAAE,CAAC,KAAK,EAAE,GAAG,WAAW,EAAE,GAAG,CAAC,EAAE,CAAC,EAAA,CAC1C;AACO,QAAA,IAAA,CAAA,OAAO,GAAG,SAAS,CAAC,cAAc,8EAAC;AACzB,QAAA,IAAA,CAAA,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM;AAC1B,QAAA,IAAA,CAAA,UAAU,GAAG,QAAQ,CAAC,MAAK;YAC5C,MAAM,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE;AACtC,YAAA,OAAO,YAAY,IAAI,YAAY,KAAK,SAAS,CAAC,QAAQ;AAC5D,QAAA,CAAC,iFAAC;AACiB,QAAA,IAAA,CAAA,OAAO,GAAG,QAAQ,CAAC,MAAK;YACzC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE;AACpC,YAAA,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,kBAAkB,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,GAAG,IAAI,EAAE;AAClF,YAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE;AACjC,YAAA,MAAM,QAAQ,GAAG,CAAA,EAAG,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA,EAAG,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA,CAAE,CAAC,WAAW,EAAE;AACxG,YAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE;AAC7D,QAAA,CAAC,8EAAC;AACiB,QAAA,IAAA,CAAA,OAAO,GAAG,MAAM,CAAC,KAAK,8EAAC;AACvB,QAAA,IAAA,CAAA,SAAS,GAAG,MAAM,CAAC,IAAI,gFAAC;QAC3C,IAAA,CAAA,QAAQ,GAAG,EAAE;QACb,IAAA,CAAA,QAAQ,GAAG,EAAE;QAGX,MAAM,CAAC,MAAK;AACV,YAAA,IAAI,IAAI,CAAC,MAAM,EAAE,KAAK,WAAW,CAAC,MAAM;AAAE,gBAAA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC;AACpE,QAAA,CAAC,CAAC;QACF,MAAM,CAAC,MAAK;YACV,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE;YAC5C,IAAI,QAAQ,KAAK,SAAS;AAAE,gBAAA,IAAI,CAAC,QAAQ,GAAG,QAAQ;YACpD,IAAI,QAAQ,KAAK,SAAS;AAAE,gBAAA,IAAI,CAAC,QAAQ,GAAG,QAAQ;AACtD,QAAA,CAAC,CAAC;IACJ;IAEA,MAAM,GAAA;AACJ,QAAA,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE;QAC3B,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,iBAAiB,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC;IAChF;IAEA,YAAY,GAAA;AACV,QAAA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC;AACzB,QAAA,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE;IAC5B;AAEA,IAAA,KAAK,CAAC,UAA4B,EAAA;AAChC,QAAA,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE;QAC3B,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,UAA6B,CAAC;IACxD;+GAnDW,mBAAmB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA,CAAA;mGAAnB,mBAAmB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,aAAA,EAAA,MAAA,EAAA,EAAA,MAAA,EAAA,EAAA,iBAAA,EAAA,QAAA,EAAA,UAAA,EAAA,QAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,IAAA,EAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,WAAA,EAAA,CAAA,EAAA,YAAA,EAAA,SAAA,EAAA,KAAA,EAAA,IAAA,EAAA,SAAA,EASD,cAAc,EAAA,WAAA,EAAA,IAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EA9GjC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyFT,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,yKAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EA1FS,YAAY,EAAA,EAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAE,WAAW,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,aAAA,EAAA,QAAA,EAAA,8CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,oBAAA,EAAA,QAAA,EAAA,8MAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,eAAA,EAAA,QAAA,EAAA,2CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,oBAAA,EAAA,QAAA,EAAA,sGAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,iBAAA,EAAA,QAAA,EAAA,wIAAA,EAAA,MAAA,EAAA,CAAA,UAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,OAAA,EAAA,QAAA,EAAA,qDAAA,EAAA,MAAA,EAAA,CAAA,MAAA,EAAA,UAAA,EAAA,SAAA,EAAA,gBAAA,CAAA,EAAA,OAAA,EAAA,CAAA,eAAA,CAAA,EAAA,QAAA,EAAA,CAAA,SAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,MAAA,EAAA,QAAA,EAAA,yEAAA,EAAA,MAAA,EAAA,CAAA,eAAA,CAAA,EAAA,OAAA,EAAA,CAAA,UAAA,CAAA,EAAA,QAAA,EAAA,CAAA,QAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAE,eAAe,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,SAAA,EAAA,QAAA,EAAA,iOAAA,EAAA,MAAA,EAAA,CAAA,WAAA,CAAA,EAAA,QAAA,EAAA,CAAA,WAAA,EAAA,WAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,aAAA,EAAA,QAAA,EAAA,sFAAA,EAAA,QAAA,EAAA,CAAA,WAAA,EAAA,WAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAE,kBAAkB,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,YAAA,EAAA,QAAA,EAAA,gBAAA,EAAA,MAAA,EAAA,CAAA,oBAAA,EAAA,OAAA,EAAA,YAAA,EAAA,YAAA,EAAA,iBAAA,EAAA,WAAA,CAAA,EAAA,QAAA,EAAA,CAAA,cAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,QAAA,EAAA,QAAA,EAAA,WAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,SAAA,EAAA,QAAA,EAAA,+CAAA,EAAA,MAAA,EAAA,CAAA,eAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,SAAA,EAAA,QAAA,EAAA,+CAAA,EAAA,MAAA,EAAA,CAAA,eAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAE,aAAa,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,OAAA,EAAA,QAAA,EAAA,UAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,QAAA,EAAA,SAAA,EAAA,SAAA,EAAA,UAAA,CAAA,EAAA,QAAA,EAAA,CAAA,SAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAE,cAAc,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,QAAA,EAAA,QAAA,EAAA,yHAAA,EAAA,MAAA,EAAA,CAAA,UAAA,EAAA,IAAA,EAAA,aAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,mBAAA,EAAA,kBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,qBAAA,CAAA,EAAA,QAAA,EAAA,CAAA,UAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAE,aAAa,6qBAAE,aAAa,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,OAAA,EAAA,QAAA,EAAA,UAAA,EAAA,MAAA,EAAA,CAAA,eAAA,EAAA,YAAA,EAAA,iBAAA,EAAA,kBAAA,EAAA,WAAA,EAAA,WAAA,EAAA,gBAAA,EAAA,aAAA,EAAA,OAAA,EAAA,WAAA,CAAA,EAAA,OAAA,EAAA,CAAA,QAAA,EAAA,OAAA,CAAA,EAAA,QAAA,EAAA,CAAA,SAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,cAAA,EAAA,QAAA,EAAA,6CAAA,EAAA,MAAA,EAAA,CAAA,sBAAA,EAAA,mBAAA,EAAA,oBAAA,EAAA,4BAAA,CAAA,EAAA,OAAA,EAAA,CAAA,YAAA,EAAA,YAAA,EAAA,YAAA,EAAA,aAAA,CAAA,EAAA,QAAA,EAAA,CAAA,gBAAA,CAAA,EAAA,CAAA,EAAA,aAAA,EAAA,EAAA,CAAA,iBAAA,CAAA,IAAA,EAAA,CAAA,CAAA;;4FAsG1H,mBAAmB,EAAA,UAAA,EAAA,CAAA;kBAzG/B,SAAS;+BACE,aAAa,EAAA,UAAA,EACX,IAAI,EAAA,OAAA,EACP,CAAC,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,kBAAkB,EAAE,aAAa,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,CAAC,EAAA,QAAA,EAC5H;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyFT,EAAA,aAAA,EAUc,iBAAiB,CAAC,IAAI,EAAA,MAAA,EAAA,CAAA,yKAAA,CAAA,EAAA;uSAWR,cAAc,CAAA,EAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,CAAA,EAAA,EAAA,CAAA;;ACzJ7C;;AAEG;;;;"}
|