@sambath999/localize-token 12.2.2 → 12.2.3
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/angular.json +119 -0
- package/ng-package.json +19 -0
- package/package.json +49 -38
- package/release.ps1 +30 -0
- package/release.sh +26 -0
- package/src/app/app.component.html +11 -0
- package/src/app/app.component.scss +0 -0
- package/src/app/app.component.ts +26 -0
- package/src/app/app.module.ts +32 -0
- package/src/assets/.gitkeep +0 -0
- package/src/assets/images/logo-300px.png +0 -0
- package/src/index.html +103 -0
- package/src/localize-logindlg/localize-logindlg.component.ts +400 -0
- package/src/localize-logindlg/localize-logindlg.module.ts +25 -0
- package/src/localize-logindlg/localize-logindlg.service.ts +38 -0
- package/src/localize-logindlg/public-api.ts +3 -0
- package/src/localize-token/localize.api.service.ts +177 -0
- package/src/localize-token/localize.token.module.ts +13 -0
- package/src/localize-token/localize.token.service.ts +84 -0
- package/src/localize-token/localize.token.storage.ts +132 -0
- package/src/localize-token/localize.token.ts +86 -0
- package/{localize-token/public-api.d.ts → src/localize-token/public-api.ts} +1 -1
- package/src/main.ts +7 -0
- package/src/polyfills.ts +53 -0
- package/src/styles.css +0 -0
- package/src/types/index.ts +0 -0
- package/tsconfig.app.json +14 -0
- package/tsconfig.json +38 -0
- package/bundles/sambath999-localize-token.umd.js +0 -1149
- package/bundles/sambath999-localize-token.umd.js.map +0 -1
- package/esm2015/localize-logindlg/localize-logindlg.component.js +0 -391
- package/esm2015/localize-logindlg/localize-logindlg.module.js +0 -28
- package/esm2015/localize-logindlg/localize-logindlg.service.js +0 -37
- package/esm2015/localize-logindlg/public-api.js +0 -4
- package/esm2015/localize-token/localize.api.service.js +0 -159
- package/esm2015/localize-token/localize.token.js +0 -67
- package/esm2015/localize-token/localize.token.module.js +0 -14
- package/esm2015/localize-token/localize.token.service.js +0 -70
- package/esm2015/localize-token/localize.token.storage.js +0 -107
- package/esm2015/localize-token/public-api.js +0 -6
- package/esm2015/public-api.js +0 -3
- package/esm2015/sambath999-localize-token.js +0 -5
- package/fesm2015/sambath999-localize-token.js +0 -851
- package/fesm2015/sambath999-localize-token.js.map +0 -1
- package/localize-logindlg/localize-logindlg.component.d.ts +0 -33
- package/localize-logindlg/localize-logindlg.module.d.ts +0 -2
- package/localize-logindlg/localize-logindlg.service.d.ts +0 -16
- package/localize-logindlg/public-api.d.ts +0 -3
- package/localize-token/localize.api.service.d.ts +0 -69
- package/localize-token/localize.token.d.ts +0 -46
- package/localize-token/localize.token.module.d.ts +0 -2
- package/localize-token/localize.token.service.d.ts +0 -29
- package/localize-token/localize.token.storage.d.ts +0 -61
- package/sambath999-localize-token.d.ts +0 -4
- package/sambath999-localize-token.metadata.json +0 -1
- /package/{README.md → README.MD} +0 -0
- /package/{public-api.d.ts → src/public-api.ts} +0 -0
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
import { HttpClient } from "@angular/common/http";
|
|
2
|
+
import {
|
|
3
|
+
ChangeDetectorRef,
|
|
4
|
+
Component,
|
|
5
|
+
OnInit,
|
|
6
|
+
ViewEncapsulation,
|
|
7
|
+
} from "@angular/core";
|
|
8
|
+
import { MessageService } from "primeng/api";
|
|
9
|
+
import { DynamicDialogConfig, DynamicDialogRef } from "primeng/dynamicdialog";
|
|
10
|
+
import { JwtPayload, LocalizeTokenService } from "../localize-token/localize.token.service";
|
|
11
|
+
import { LocalizeToken } from "../localize-token/localize.token";
|
|
12
|
+
import { ILoginDialogConfig } from "./localize-logindlg.service";
|
|
13
|
+
|
|
14
|
+
@Component({
|
|
15
|
+
styles: [`
|
|
16
|
+
#login-dlg-wrap {
|
|
17
|
+
width: 100%;
|
|
18
|
+
max-width: 400px;
|
|
19
|
+
margin: 0 auto;
|
|
20
|
+
padding: 30px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.login-dlg-elm {
|
|
24
|
+
margin-top: 1rem;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
#login-dlg-header {
|
|
28
|
+
display: flex;
|
|
29
|
+
flex-direction: column;
|
|
30
|
+
align-items: center;
|
|
31
|
+
justify-content: center;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
#login-dlg-header h3 {
|
|
35
|
+
font-weight: bold;
|
|
36
|
+
font-size: 0.9rem;
|
|
37
|
+
color: orange;
|
|
38
|
+
text-align: center;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
#login-logo {
|
|
42
|
+
height: 40px;
|
|
43
|
+
width: 40px;
|
|
44
|
+
background: url("/assets/images/logo-300px.png") no-repeat;
|
|
45
|
+
background-size: contain;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
#login-dlg-content .p-inputgroup {
|
|
49
|
+
height: 45px;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
#login-dlg-content .p-inputgroup .p-inputgroup-addon {
|
|
53
|
+
height: 45px;
|
|
54
|
+
border-radius: 15px 0 0 15px;
|
|
55
|
+
width: 50px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
#login-dlg-content .p-inputgroup .p-inputgroup-addon * {
|
|
59
|
+
font-size: 1rem;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
#login-dlg-content .p-inputgroup input {
|
|
63
|
+
height: 45px;
|
|
64
|
+
border-radius: 0 15px 15px 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
#login-dlg-content button {
|
|
68
|
+
height: 45px;
|
|
69
|
+
border-radius: 15px;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/*check animation block*/
|
|
73
|
+
|
|
74
|
+
.check-animation-wrap {
|
|
75
|
+
top: 0;
|
|
76
|
+
left: 0;
|
|
77
|
+
position: absolute;
|
|
78
|
+
display: flex;
|
|
79
|
+
flex-direction: column;
|
|
80
|
+
align-items: center;
|
|
81
|
+
justify-content: center;
|
|
82
|
+
width: 100%;
|
|
83
|
+
height: calc(100% - 200px);
|
|
84
|
+
min-height: 400px;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.check-main-container {
|
|
88
|
+
width: 100%;
|
|
89
|
+
height: 100vh;
|
|
90
|
+
display: flex;
|
|
91
|
+
flex-flow: column;
|
|
92
|
+
justify-content: center;
|
|
93
|
+
align-items: center;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.check-container {
|
|
97
|
+
width: 6.25rem;
|
|
98
|
+
height: 7.5rem;
|
|
99
|
+
display: flex;
|
|
100
|
+
flex-flow: column;
|
|
101
|
+
align-items: center;
|
|
102
|
+
justify-content: space-between;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.check-container .check-background {
|
|
106
|
+
width: 100%;
|
|
107
|
+
height: calc(100% - 1.25rem);
|
|
108
|
+
background: linear-gradient(to bottom right, #5de593, #41d67c);
|
|
109
|
+
box-shadow: 0px 0px 0px 65px rgba(255, 255, 255, 0.25) inset, 0px 0px 0px 65px rgba(255, 255, 255, 0.25) inset;
|
|
110
|
+
transform: scale(0.84);
|
|
111
|
+
border-radius: 50%;
|
|
112
|
+
animation: animateContainer 0.75s ease-out forwards 0.75s;
|
|
113
|
+
display: flex;
|
|
114
|
+
align-items: center;
|
|
115
|
+
justify-content: center;
|
|
116
|
+
opacity: 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.check-container .check-background svg {
|
|
120
|
+
width: 65%;
|
|
121
|
+
transform: translateY(0.25rem);
|
|
122
|
+
stroke-dasharray: 80;
|
|
123
|
+
stroke-dashoffset: 80;
|
|
124
|
+
animation: animateCheck 0.35s forwards 1.25s ease-out;
|
|
125
|
+
min-width: auto !important;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.check-container .check-shadow {
|
|
129
|
+
bottom: calc(-15% - 5px);
|
|
130
|
+
left: 0;
|
|
131
|
+
border-radius: 50%;
|
|
132
|
+
background: radial-gradient(closest-side, rgba(73, 218, 131, 1), transparent);
|
|
133
|
+
animation: animateShadow 0.75s ease-out forwards 0.75s;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@keyframes animateContainer {
|
|
137
|
+
0% {
|
|
138
|
+
opacity: 0;
|
|
139
|
+
transform: scale(0);
|
|
140
|
+
box-shadow: 0px 0px 0px 65px rgba(255, 255, 255, 0.25) inset, 0px 0px 0px 65px rgba(255, 255, 255, 0.25) inset;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
25% {
|
|
144
|
+
opacity: 1;
|
|
145
|
+
transform: scale(0.9);
|
|
146
|
+
box-shadow: 0px 0px 0px 65px rgba(255, 255, 255, 0.25) inset, 0px 0px 0px 65px rgba(255, 255, 255, 0.25) inset;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
43.75% {
|
|
150
|
+
transform: scale(1.15);
|
|
151
|
+
box-shadow: 0px 0px 0px 43.334px rgba(255, 255, 255, 0.25) inset, 0px 0px 0px 65px rgba(255, 255, 255, 0.25) inset;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
62.5% {
|
|
155
|
+
transform: scale(1);
|
|
156
|
+
box-shadow: 0px 0px 0px 0px rgba(255, 255, 255, 0.25) inset, 0px 0px 0px 21.667px rgba(255, 255, 255, 0.25) inset;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
81.25% {
|
|
160
|
+
box-shadow: 0px 0px 0px 0px rgba(255, 255, 255, 0.25) inset, 0px 0px 0px 0px rgba(255, 255, 255, 0.25) inset;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
100% {
|
|
164
|
+
opacity: 1;
|
|
165
|
+
box-shadow: 0px 0px 0px 0px rgba(255, 255, 255, 0.25) inset, 0px 0px 0px 0px rgba(255, 255, 255, 0.25) inset;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
@keyframes animateCheck {
|
|
170
|
+
from {
|
|
171
|
+
stroke-dashoffset: 80;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
to {
|
|
175
|
+
stroke-dashoffset: 0;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
@keyframes animateShadow {
|
|
180
|
+
0% {
|
|
181
|
+
opacity: 0;
|
|
182
|
+
width: 100%;
|
|
183
|
+
height: 15%;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
25% {
|
|
187
|
+
opacity: 0.25;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
43.75% {
|
|
191
|
+
width: 40%;
|
|
192
|
+
height: 7%;
|
|
193
|
+
opacity: 0.35;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
100% {
|
|
197
|
+
width: 85%;
|
|
198
|
+
height: 15%;
|
|
199
|
+
opacity: 0.25;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
#login-dlg-wrap .loader-wrap {
|
|
203
|
+
display: flex;
|
|
204
|
+
justify-content: center;
|
|
205
|
+
align-items: center;
|
|
206
|
+
height:100%;
|
|
207
|
+
width:100%;
|
|
208
|
+
position: absolute;
|
|
209
|
+
top: 0;
|
|
210
|
+
left: 0;
|
|
211
|
+
z-index: 100;
|
|
212
|
+
background: #ffffff42;
|
|
213
|
+
backdrop-filter: blur(1px);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
#login-dlg-wrap .login-dlg-loader {
|
|
217
|
+
border: 15px solid #e7e7e7;
|
|
218
|
+
border-top: 15px solid #52dba1;
|
|
219
|
+
border-radius: 50%;
|
|
220
|
+
width: 100px;
|
|
221
|
+
height: 100px;
|
|
222
|
+
animation: spinloader 2s linear infinite;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
#login-dlg-wrap .loader-wrap::before {
|
|
226
|
+
content: "";
|
|
227
|
+
position: absolute;
|
|
228
|
+
width: 70px;
|
|
229
|
+
height: 70px;
|
|
230
|
+
transform: translate(-50%, -50%);
|
|
231
|
+
z-index: 1;
|
|
232
|
+
border: 15px solid #e7e7e700;
|
|
233
|
+
border-top: 15px solid #52dba1c9;
|
|
234
|
+
border-radius: 50%;
|
|
235
|
+
animation: spinloader .75s linear infinite;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
@keyframes spinloader {
|
|
239
|
+
0% { transform: rotate(0deg); }
|
|
240
|
+
100% { transform: rotate(360deg); }
|
|
241
|
+
}
|
|
242
|
+
`],
|
|
243
|
+
template: `<p-toast key="$login-dlg" position="top-center"></p-toast>
|
|
244
|
+
<div id="login-dlg-wrap">
|
|
245
|
+
<div id="login-dlg-header">
|
|
246
|
+
<div id="login-logo" class="p-mb-2"></div>
|
|
247
|
+
<h3 *ngIf="!success">Your session is expired! <br> Please login again to continue.</h3>
|
|
248
|
+
<h3 *ngIf="success" style="color:green !important;">You haved successfully logged in.</h3>
|
|
249
|
+
</div>
|
|
250
|
+
<div id="login-dlg-content">
|
|
251
|
+
<ng-container *ngIf="!success">
|
|
252
|
+
<div *ngIf="loading" class="loader-wrap">
|
|
253
|
+
<div class="login-dlg-loader"></div>
|
|
254
|
+
</div>
|
|
255
|
+
<div class="login-dlg-elm">
|
|
256
|
+
<div class="p-inputgroup">
|
|
257
|
+
<span class="p-inputgroup-addon">
|
|
258
|
+
<i class="material-icons-round">person</i>
|
|
259
|
+
</span>
|
|
260
|
+
<input disabled pInputText type="text" placeholder="Username" [value]="decodeToken?.email" />
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
|
|
264
|
+
<div class="login-dlg-elm">
|
|
265
|
+
<div class="p-inputgroup">
|
|
266
|
+
<span class="p-inputgroup-addon">
|
|
267
|
+
<i class="material-icons-round">lock</i>
|
|
268
|
+
</span>
|
|
269
|
+
<input [disabled]="loading" (keydown.enter)="clickLogin()" pInputText type="password" placeholder="Password" [(ngModel)]="password"
|
|
270
|
+
autofocus />
|
|
271
|
+
</div>
|
|
272
|
+
</div>
|
|
273
|
+
<div class="login-dlg-elm">
|
|
274
|
+
<button style="width: 100%;" pButton type="button" label="Login" (click)="clickLogin()"
|
|
275
|
+
[disabled]="!password || loading"></button>
|
|
276
|
+
</div>
|
|
277
|
+
|
|
278
|
+
<div class="login-dlg-elm" style="display:flex;align-items: center;user-select: none;">
|
|
279
|
+
<span>No, I want to login with another user.</span>
|
|
280
|
+
<button class="p-button-text" pButton type="button" label="Logout" (click)="clickLogout()"></button>
|
|
281
|
+
</div>
|
|
282
|
+
</ng-container>
|
|
283
|
+
|
|
284
|
+
<ng-container *ngIf="success">
|
|
285
|
+
<div style="margin-top:35px;"></div>
|
|
286
|
+
<div class="check-animation-wrap">
|
|
287
|
+
<div class="check-main-container">
|
|
288
|
+
<div class="check-container">
|
|
289
|
+
<div class="check-background">
|
|
290
|
+
<svg viewBox="0 0 65 51" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
291
|
+
<path d="M7 25L27.3077 44L58.5 7" stroke="white" stroke-width="13" stroke-linecap="round"
|
|
292
|
+
stroke-linejoin="round"></path>
|
|
293
|
+
</svg>
|
|
294
|
+
</div>
|
|
295
|
+
</div>
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
</ng-container>
|
|
299
|
+
</div>
|
|
300
|
+
</div>`,
|
|
301
|
+
selector: "app-localize-logindlg",
|
|
302
|
+
providers: [MessageService],
|
|
303
|
+
encapsulation: ViewEncapsulation.None,
|
|
304
|
+
})
|
|
305
|
+
export class LocalizeLogindlgComponent implements OnInit {
|
|
306
|
+
readonly messageKey = "$login-dlg";
|
|
307
|
+
password: any;
|
|
308
|
+
readonly decodeToken: JwtPayload | null;
|
|
309
|
+
loading = false;
|
|
310
|
+
success = false;
|
|
311
|
+
private get config() { return this.tokenConfig.getConfig }
|
|
312
|
+
private readonly loginConfig: ILoginDialogConfig
|
|
313
|
+
|
|
314
|
+
private logout?: () => any
|
|
315
|
+
private loginUrl?: string;
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
constructor(
|
|
319
|
+
private readonly messageService: MessageService,
|
|
320
|
+
private readonly cdt: ChangeDetectorRef,
|
|
321
|
+
private readonly dlgRef: DynamicDialogRef,
|
|
322
|
+
private readonly dlgConfig: DynamicDialogConfig,
|
|
323
|
+
private readonly tokenService: LocalizeTokenService,
|
|
324
|
+
private readonly httpClient: HttpClient,
|
|
325
|
+
private readonly tokenConfig: LocalizeToken,
|
|
326
|
+
) {
|
|
327
|
+
this.decodeToken = this.tokenService.decodeRefreshToken;
|
|
328
|
+
this.loginConfig = this.dlgConfig.data.loginConfig;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
ngOnInit(): void {
|
|
332
|
+
this.dlgConfig.closable = false;
|
|
333
|
+
this.logout = this.loginConfig.logoutFunc;
|
|
334
|
+
this.loginUrl = this.loginConfig.loginUrl
|
|
335
|
+
|
|
336
|
+
if (!this.decodeToken) {
|
|
337
|
+
this.showMessage("error", "Token is invalid");
|
|
338
|
+
setTimeout(() => this.logout?.(), 2000);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
ngAfterViewInit() {
|
|
343
|
+
this.cdt.detectChanges();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
async clickLogin() {
|
|
347
|
+
if (!this.isValidPassword) {
|
|
348
|
+
this.showMessage("error", "Password is required and must be at least 6 characters");
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
this.loading = true;
|
|
353
|
+
const loginRes = await this.login();
|
|
354
|
+
if (!loginRes?.status) {
|
|
355
|
+
this.showMessage("error", loginRes.message ?? "An error occurred");
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
this.tokenService.accessToken = loginRes.tokens.accessToken;
|
|
360
|
+
const cookieOptions = { expires: this.loginConfig.expire ?? 5 }
|
|
361
|
+
LocalizeToken.storage.set(this.config.refreshTokenName!, loginRes.tokens.refreshToken, cookieOptions);
|
|
362
|
+
this.success = true;
|
|
363
|
+
setTimeout(() => this.dlgRef.close(true), 2000);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
private async login() {
|
|
367
|
+
if (!this.loginUrl || !this.loginUrl.trim().length) {
|
|
368
|
+
this.showMessage("error", "Login url is required");
|
|
369
|
+
throw new Error("Login url is required");
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
try {
|
|
373
|
+
return await new Promise<any>((resolve, reject) =>
|
|
374
|
+
this.httpClient.post(this.loginUrl!, { password: this.password.trim() }, { headers: this.getHeaders() })
|
|
375
|
+
.subscribe({ next: resolve, error: reject }));
|
|
376
|
+
} catch (e: any) {
|
|
377
|
+
this.showMessage("error", e.message);
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
private getHeaders() {
|
|
383
|
+
return {
|
|
384
|
+
[LocalizeToken.httpHeaders.X_REFRESH_TOKEN]: LocalizeToken.storage.get(this.config.refreshTokenName!) ?? "",
|
|
385
|
+
[LocalizeToken.httpHeaders.X_TENANT]: LocalizeToken.storage.get(this.config.tenantTokenName!) ?? "",
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
get isValidPassword(): boolean {
|
|
390
|
+
this.loading = false;
|
|
391
|
+
return this.password && this.password.trim().length >= 6 && this.password.trim().length <= 16;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
clickLogout = () => this.logout?.();
|
|
395
|
+
|
|
396
|
+
private showMessage(severity: string, summary: string) {
|
|
397
|
+
this.messageService.add({ key: this.messageKey, severity, summary });
|
|
398
|
+
this.loading = false;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from "@angular/core";
|
|
2
|
+
import { CommonModule } from "@angular/common";
|
|
3
|
+
import { LocalizeLogindlgComponent } from "./localize-logindlg.component";
|
|
4
|
+
import { ToastModule } from "primeng/toast";
|
|
5
|
+
import { InputTextModule } from "primeng/inputtext";
|
|
6
|
+
import { ButtonModule } from "primeng/button";
|
|
7
|
+
import { BrowserModule } from '@angular/platform-browser';
|
|
8
|
+
import { FormsModule } from '@angular/forms';
|
|
9
|
+
import { LocalizeLogindlgService } from "./localize-logindlg.service";
|
|
10
|
+
|
|
11
|
+
@NgModule({
|
|
12
|
+
declarations: [LocalizeLogindlgComponent],
|
|
13
|
+
exports: [LocalizeLogindlgComponent],
|
|
14
|
+
imports: [
|
|
15
|
+
CommonModule,
|
|
16
|
+
ToastModule,
|
|
17
|
+
InputTextModule,
|
|
18
|
+
BrowserModule,
|
|
19
|
+
FormsModule,
|
|
20
|
+
ButtonModule,
|
|
21
|
+
],
|
|
22
|
+
providers: [LocalizeLogindlgService],
|
|
23
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
24
|
+
})
|
|
25
|
+
export class LocalizeLogindlgModule { }
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Injectable, Injector } from '@angular/core';
|
|
2
|
+
import { DialogService, DynamicDialogConfig } from 'primeng/dynamicdialog';
|
|
3
|
+
import { LocalizeLogindlgComponent } from './localize-logindlg.component';
|
|
4
|
+
export interface ILoginDialogConfig {
|
|
5
|
+
loginUrl?: string;
|
|
6
|
+
logoutUrl?: string;
|
|
7
|
+
logoutFunc?: () => void;
|
|
8
|
+
/**
|
|
9
|
+
* Cookie expiration date in days from now. If not provided the cookie is a session cookie
|
|
10
|
+
*/
|
|
11
|
+
expire?: number;
|
|
12
|
+
}
|
|
13
|
+
@Injectable({
|
|
14
|
+
providedIn: 'root'
|
|
15
|
+
})
|
|
16
|
+
export class LocalizeLogindlgService {
|
|
17
|
+
|
|
18
|
+
constructor(private readonly injector: Injector) { }
|
|
19
|
+
|
|
20
|
+
async openLoginDialog(loginConfig: ILoginDialogConfig, config?: DynamicDialogConfig) {
|
|
21
|
+
config ??= {
|
|
22
|
+
header: 'Login',
|
|
23
|
+
height: '650px',
|
|
24
|
+
style: { 'max-width': '400px', width: '100%', 'height': '650px' },
|
|
25
|
+
}
|
|
26
|
+
config.data = { ...config.data || {}, ...{ loginConfig } };
|
|
27
|
+
|
|
28
|
+
const dialogService = this.injector.get(DialogService);
|
|
29
|
+
const dialog = dialogService.open(LocalizeLogindlgComponent, config);
|
|
30
|
+
|
|
31
|
+
await new Promise<void>((resolve) =>
|
|
32
|
+
dialog.onClose.subscribe(res => {
|
|
33
|
+
if (res) {
|
|
34
|
+
resolve();
|
|
35
|
+
}
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { Injectable } from "@angular/core";
|
|
2
|
+
import { LocalizeTokenService } from "./localize.token.service";
|
|
3
|
+
import { HttpClient, HttpHeaders } from "@angular/common/http";
|
|
4
|
+
import { LocalizeToken, waitFor, waitUntil } from "./localize.token";
|
|
5
|
+
import { BehaviorSubject } from "rxjs";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Http method options
|
|
9
|
+
*/
|
|
10
|
+
export enum EMethod {
|
|
11
|
+
POST = 1,
|
|
12
|
+
GET = 2,
|
|
13
|
+
PUT = 3,
|
|
14
|
+
DELETE = 4,
|
|
15
|
+
PATCH = 5
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface ILocalizeApiConfigs {
|
|
19
|
+
onPrepareRequest?: () => any;
|
|
20
|
+
/**
|
|
21
|
+
* A function that is called when refresh token is missing then perform the auto logout.
|
|
22
|
+
* @returns - The base URL of the API.
|
|
23
|
+
*/
|
|
24
|
+
onAutoLogout?: () => any;
|
|
25
|
+
/**
|
|
26
|
+
* A function that is called when request revoking token unauthorized.
|
|
27
|
+
* @returns - The login dialog service.
|
|
28
|
+
*/
|
|
29
|
+
onRevokeUnauthorized?: () => any;
|
|
30
|
+
/**
|
|
31
|
+
* The time to wait for each request to complete in milliseconds.
|
|
32
|
+
* Be careful with this option, it may cause the application to hang if the request is not completed.
|
|
33
|
+
*/
|
|
34
|
+
waitEachRequest?: {
|
|
35
|
+
milliseconds: number
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@Injectable({
|
|
40
|
+
providedIn: 'root'
|
|
41
|
+
})
|
|
42
|
+
export class LocalizeApiService {
|
|
43
|
+
|
|
44
|
+
readonly isRequestingSubject = new BehaviorSubject<boolean>(false);
|
|
45
|
+
readonly isResolvedStartupSubject = new BehaviorSubject<boolean>(false);
|
|
46
|
+
get isRequesting() { return this.isRequestingSubject.value; }
|
|
47
|
+
get isResolvedStartup() { return this.isResolvedStartupSubject.value; }
|
|
48
|
+
|
|
49
|
+
private apiConfigs: ILocalizeApiConfigs = {};
|
|
50
|
+
private get config() { return this.tokenConfig.getConfig }
|
|
51
|
+
|
|
52
|
+
constructor(readonly httpClient: HttpClient,
|
|
53
|
+
private readonly tokenConfig: LocalizeToken,
|
|
54
|
+
private readonly localizeTokenService: LocalizeTokenService
|
|
55
|
+
) { }
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Initialize the API service.
|
|
59
|
+
* @param apiConfigs - The API configurations.
|
|
60
|
+
*/
|
|
61
|
+
init(apiConfigs: ILocalizeApiConfigs) {
|
|
62
|
+
this.apiConfigs = apiConfigs;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* A higher-order function that returns a curried function for making API requests.
|
|
68
|
+
*
|
|
69
|
+
* @param baseUrl - The base URL of the API.
|
|
70
|
+
* @returns A curried function that can be used to make API requests.
|
|
71
|
+
*/
|
|
72
|
+
func = (baseUrl: string) =>
|
|
73
|
+
(path: string, method: EMethod = EMethod.GET, value: any = null, isFormData: boolean = false, headers?: { [x: string]: string }) =>
|
|
74
|
+
this.base(baseUrl, path, method, value, isFormData, headers)
|
|
75
|
+
|
|
76
|
+
private async base(baseUrl: string,
|
|
77
|
+
path: string,
|
|
78
|
+
method: EMethod = EMethod.GET,
|
|
79
|
+
value: any = null,
|
|
80
|
+
isFormData: boolean = false,
|
|
81
|
+
headers?: { [x: string]: string }) {
|
|
82
|
+
|
|
83
|
+
await this.ifPromise(this.apiConfigs.onPrepareRequest);
|
|
84
|
+
|
|
85
|
+
const url = `${baseUrl.trim().replace(/\/?$/, '/')}${path.trim().replace(/^\//, '')}`;
|
|
86
|
+
const httpMethod = EMethod[method].toLowerCase();
|
|
87
|
+
const request = () => { return { body: value, headers: this.options(isFormData, headers) } };
|
|
88
|
+
|
|
89
|
+
// Wait for previous request to complete
|
|
90
|
+
await this.toWaitForPreviousRequest();
|
|
91
|
+
|
|
92
|
+
// Process request
|
|
93
|
+
try { return await this.processRequest(httpMethod, url, request()) }
|
|
94
|
+
// Handle unauthorized error if any
|
|
95
|
+
catch (error) {
|
|
96
|
+
if ((error as any).status !== 401) { throw error; }
|
|
97
|
+
return await this.onUnauthorizedError(httpMethod, url, request);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private async onUnauthorizedError(httpMethod: string, url: string, request: Function) {
|
|
102
|
+
await this.revokeToken();
|
|
103
|
+
if (!this.isResolvedStartup) {
|
|
104
|
+
return await this.processRequest(httpMethod, url, request());
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private async toWaitForPreviousRequest() {
|
|
109
|
+
if (this.localizeTokenService.isRevokingToken) {
|
|
110
|
+
await waitUntil(() => !this.localizeTokenService.isRevokingToken);
|
|
111
|
+
}
|
|
112
|
+
// to wait for each request in 50ms, even if the request is not completed
|
|
113
|
+
if (this.apiConfigs.waitEachRequest?.milliseconds) {
|
|
114
|
+
await waitFor(this.apiConfigs.waitEachRequest.milliseconds, this.isRequesting);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private async processRequest(method: string, url: string, options: any): Promise<any> {
|
|
119
|
+
this.isRequestingSubject.next(true);
|
|
120
|
+
const result = await new Promise((resolve, reject) =>
|
|
121
|
+
this.httpClient.request<any>(method, url, options).subscribe({ next: resolve, error: reject }))
|
|
122
|
+
this.isRequestingSubject.next(false);
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private async revokeToken(force: boolean = false): Promise<void> {
|
|
127
|
+
|
|
128
|
+
if (this.localizeTokenService.isRevokingToken && !force) {
|
|
129
|
+
await waitUntil(() => !this.localizeTokenService.isRevokingToken)
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
this.localizeTokenService.isRevokingToken = true;
|
|
135
|
+
const reqUrl = this.config.revokeTokenUrl;
|
|
136
|
+
const reqHeaders = this.options().append(LocalizeToken.httpHeaders.X_REFRESH_TOKEN, `${this.localizeTokenService.refreshToken}`);
|
|
137
|
+
const revokeToken: any = await new Promise((resolve, reject) =>
|
|
138
|
+
this.httpClient.get<any>(reqUrl!, { headers: reqHeaders }).subscribe({ next: resolve, error: reject }))
|
|
139
|
+
|
|
140
|
+
if (revokeToken?.status) {
|
|
141
|
+
this.localizeTokenService.accessToken = revokeToken.message;
|
|
142
|
+
} else if (!this.localizeTokenService.refreshToken) {
|
|
143
|
+
await this.ifPromise(this.apiConfigs.onAutoLogout)
|
|
144
|
+
} else {
|
|
145
|
+
if (this.apiConfigs.onRevokeUnauthorized) {
|
|
146
|
+
await this.ifPromise(this.apiConfigs.onRevokeUnauthorized);
|
|
147
|
+
await this.revokeToken(true);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
} finally {
|
|
151
|
+
this.localizeTokenService.isRevokingToken = false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** default http request options */
|
|
156
|
+
private options(isFormData: boolean = false, headers: { [key: string]: string } = {}): HttpHeaders {
|
|
157
|
+
const defaultHeaders = { [LocalizeToken.httpHeaders.AUTHORIZATION]: `Bearer ${this.localizeTokenService.accessToken}`, };
|
|
158
|
+
if (this.config.needTenant) {
|
|
159
|
+
defaultHeaders[LocalizeToken.httpHeaders.X_TENANT] = `${this.localizeTokenService.tenantToken()}`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!isFormData) {
|
|
163
|
+
defaultHeaders[LocalizeToken.httpHeaders.CONTENT_TYPE] = 'application/json';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const filteredHeaders = Object.keys(defaultHeaders).filter(key => !Object.keys(headers).includes(key))
|
|
167
|
+
.reduce((acc, key) => ({ ...acc, [key]: defaultHeaders[key] }), {})
|
|
168
|
+
const mergedHeaders = { ...filteredHeaders, ...headers };
|
|
169
|
+
return new HttpHeaders(mergedHeaders);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private async ifPromise(fn?: Function) {
|
|
173
|
+
if (!fn) return;
|
|
174
|
+
return fn instanceof Promise ? await fn() : fn();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
}//class
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { NgModule } from '@angular/core';
|
|
2
|
+
import { LocalizeTokenService } from './localize.token.service';
|
|
3
|
+
import { LocalizeApiService } from './localize.api.service';
|
|
4
|
+
import { LocalizeToken } from './localize.token';
|
|
5
|
+
|
|
6
|
+
@NgModule({
|
|
7
|
+
providers: [
|
|
8
|
+
LocalizeTokenService,
|
|
9
|
+
LocalizeApiService,
|
|
10
|
+
LocalizeToken
|
|
11
|
+
],
|
|
12
|
+
})
|
|
13
|
+
export class LocalizeTokenModule { }
|