codevdesign 1.0.67 → 1.0.68
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/outils/rafraichisseurToken.ts +73 -17
- package/package.json +1 -1
|
@@ -1,22 +1,38 @@
|
|
|
1
1
|
class RafraichisseurToken {
|
|
2
2
|
private intervalleEnSecondes = 15
|
|
3
3
|
private secondesAvantExpirationTokenPourRafraichir = 20
|
|
4
|
-
private skewSeconds = 5 // marge anti-derives d
|
|
4
|
+
private skewSeconds = 5 // marge anti-derives d'horloge
|
|
5
5
|
private popupAffiche = false
|
|
6
6
|
private timerId: number | null = null
|
|
7
|
+
private nomTemoin = 'csqc_jeton_secure_expiration'
|
|
8
|
+
private urlPortail = ''
|
|
9
|
+
private refreshPromise: Promise<void> | null = null
|
|
7
10
|
|
|
8
11
|
// Lance une seule fois
|
|
9
12
|
public async demarrer(nomTemoin: string | null, urlPortail: string): Promise<void> {
|
|
10
13
|
urlPortail = urlPortail.replace(/\/+$/, '')
|
|
11
14
|
if (nomTemoin == null || nomTemoin === '') nomTemoin = 'csqc_jeton_secure_expiration'
|
|
12
|
-
|
|
15
|
+
this.nomTemoin = nomTemoin as string
|
|
16
|
+
this.urlPortail = urlPortail
|
|
17
|
+
await this.verifierJeton(nomTemoin as string, urlPortail)
|
|
13
18
|
if (this.timerId != null) return
|
|
14
19
|
this.timerId = window.setInterval(() => {
|
|
15
|
-
this.verifierJeton(nomTemoin, urlPortail)
|
|
20
|
+
this.verifierJeton(nomTemoin as string, urlPortail)
|
|
16
21
|
}, this.intervalleEnSecondes * 1000)
|
|
17
22
|
}
|
|
18
23
|
|
|
19
|
-
|
|
24
|
+
public attendreRefreshSiNecessaire(force = false): Promise<void> {
|
|
25
|
+
if (this.refreshPromise) return this.refreshPromise
|
|
26
|
+
if (force || !this.estJetonValide(this.nomTemoin)) {
|
|
27
|
+
this.refreshPromise = this.rafraichir(this.nomTemoin, this.urlPortail).finally(() => {
|
|
28
|
+
this.refreshPromise = null
|
|
29
|
+
})
|
|
30
|
+
return this.refreshPromise
|
|
31
|
+
}
|
|
32
|
+
return Promise.resolve()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Permet d'arrêter le timer (ex: au logout / destroy)
|
|
20
36
|
public arreter(): void {
|
|
21
37
|
if (this.timerId != null) {
|
|
22
38
|
clearInterval(this.timerId)
|
|
@@ -32,8 +48,11 @@ class RafraichisseurToken {
|
|
|
32
48
|
if (this.popupAffiche) return
|
|
33
49
|
|
|
34
50
|
if (!this.estJetonValide(nomTemoin)) {
|
|
35
|
-
this.
|
|
36
|
-
|
|
51
|
+
if (!this.refreshPromise) {
|
|
52
|
+
this.refreshPromise = this.rafraichir(nomTemoin, urlPortail).finally(() => {
|
|
53
|
+
this.refreshPromise = null
|
|
54
|
+
})
|
|
55
|
+
}
|
|
37
56
|
}
|
|
38
57
|
}
|
|
39
58
|
|
|
@@ -54,8 +73,6 @@ class RafraichisseurToken {
|
|
|
54
73
|
|
|
55
74
|
const exp = Number(token?.exp)
|
|
56
75
|
if (!Number.isFinite(exp)) {
|
|
57
|
-
// exp manquant/invalide → tente refresh
|
|
58
|
-
|
|
59
76
|
return false
|
|
60
77
|
}
|
|
61
78
|
|
|
@@ -70,6 +87,8 @@ class RafraichisseurToken {
|
|
|
70
87
|
|
|
71
88
|
private async rafraichir(nomCookie: string, urlPortail: string): Promise<void> {
|
|
72
89
|
if (!nomCookie) return
|
|
90
|
+
//console.log('[RafraichisseurToken] rafraichir() appelé')
|
|
91
|
+
this.popupAffiche = true // bloquer tout nouveau tick pendant le refresh
|
|
73
92
|
const url = this.getRefreshUrl(urlPortail)
|
|
74
93
|
const controller = new AbortController()
|
|
75
94
|
const timeout = setTimeout(() => controller.abort(), 10_000)
|
|
@@ -84,51 +103,73 @@ class RafraichisseurToken {
|
|
|
84
103
|
signal: controller.signal,
|
|
85
104
|
})
|
|
86
105
|
|
|
87
|
-
// redirection
|
|
88
|
-
|
|
106
|
+
// redirection vers Azure AD → session expirée, l'iframe ne peut pas compléter le flow OIDC cross-site
|
|
107
|
+
//console.log('[RafraichisseurToken] fetch réponse type:', resp.type, 'status:', resp.status)
|
|
89
108
|
if (resp.type === 'opaqueredirect' || resp.status === 302) {
|
|
90
|
-
this.
|
|
109
|
+
this.estDeconnecteAzure(urlPortail)
|
|
91
110
|
return
|
|
92
111
|
}
|
|
93
112
|
|
|
94
113
|
// OK ou No Content: le cookie devrait être réécrit
|
|
95
114
|
if (resp.status === 200 || resp.status === 204) {
|
|
96
115
|
const jeton = this.lireCookie(nomCookie)
|
|
97
|
-
if (!jeton)
|
|
116
|
+
if (!jeton) {
|
|
117
|
+
// console.log('[RafraichisseurToken] 200 mais cookie absent → iframe')
|
|
118
|
+
this.rafraichirParIframe(nomCookie, urlPortail)
|
|
119
|
+
} else {
|
|
120
|
+
this.popupAffiche = false // succès, reprendre la surveillance
|
|
121
|
+
}
|
|
98
122
|
return
|
|
99
123
|
}
|
|
100
124
|
|
|
101
125
|
// non auth / expiré (401, 419) + IIS timeout (440)
|
|
102
126
|
if (resp.status === 401 || resp.status === 419 || resp.status === 440) {
|
|
127
|
+
// console.log('[RafraichisseurToken] statut auth → iframe')
|
|
103
128
|
this.rafraichirParIframe(nomCookie, urlPortail)
|
|
104
129
|
return
|
|
105
130
|
}
|
|
106
131
|
|
|
107
132
|
console.warn('Rafraichisseur token: statut inattendu', resp.status)
|
|
133
|
+
this.popupAffiche = false // statut inattendu, permettre un retry
|
|
108
134
|
} catch (err: any) {
|
|
109
135
|
if (err?.name === 'AbortError') console.warn('RafraichisseurToken timeout')
|
|
110
136
|
else console.error('Erreur rafraichisseur de token', err)
|
|
111
|
-
|
|
137
|
+
this.reloadSiErreurReseau()
|
|
112
138
|
} finally {
|
|
113
139
|
clearTimeout(timeout)
|
|
114
140
|
}
|
|
115
141
|
}
|
|
116
142
|
|
|
117
143
|
private rafraichirParIframe(nomCookie: string, urlPortail: string): void {
|
|
118
|
-
|
|
119
|
-
// ajax vers le refresh
|
|
144
|
+
this.popupAffiche = true // bloquer le timer pendant que l'iframe tente l'auth
|
|
120
145
|
let iframe = document.createElement('iframe')
|
|
121
146
|
const url = this.getRefreshUrl(urlPortail)
|
|
122
147
|
iframe.src = `${url}?urlRetour=${encodeURI(window.location.href)}`
|
|
123
148
|
iframe.id = 'idRafrToken'
|
|
124
149
|
iframe.style.display = 'none'
|
|
125
150
|
document.body.appendChild(iframe)
|
|
151
|
+
|
|
152
|
+
const iframeTimeout = setTimeout(() => {
|
|
153
|
+
iframe.onload = null
|
|
154
|
+
iframe.src = 'about:blank'
|
|
155
|
+
iframe.remove()
|
|
156
|
+
this.popupAffiche = false
|
|
157
|
+
console.warn('[RafraichisseurToken] iframe timeout — popupAffiche réinitialisé')
|
|
158
|
+
}, 15_000)
|
|
159
|
+
|
|
126
160
|
iframe.onload = () => {
|
|
127
|
-
|
|
128
|
-
|
|
161
|
+
clearTimeout(iframeTimeout)
|
|
162
|
+
iframe.onload = null // empêcher les re-entrées sur les redirects OIDC internes
|
|
163
|
+
const nomSecure = nomCookie.replace('_expiration', '')
|
|
164
|
+
const cookieAVerifier = nomSecure !== nomCookie ? nomSecure : nomCookie
|
|
165
|
+
const jeton = this.lireCookie(cookieAVerifier)
|
|
166
|
+
if (jeton == null || jeton === '') {
|
|
129
167
|
this.estDeconnecteAzure(urlPortail)
|
|
168
|
+
} else {
|
|
169
|
+
this.popupAffiche = false // cookie recréé avec succès, reprendre la surveillance
|
|
130
170
|
}
|
|
131
171
|
|
|
172
|
+
iframe.src = 'about:blank' // stopper les navigations internes
|
|
132
173
|
iframe.remove()
|
|
133
174
|
}
|
|
134
175
|
}
|
|
@@ -176,6 +217,21 @@ class RafraichisseurToken {
|
|
|
176
217
|
return JSON.parse(jsonPayload)
|
|
177
218
|
}
|
|
178
219
|
|
|
220
|
+
private reloadSiErreurReseau(): void {
|
|
221
|
+
const CLE = 'rafraichisseur_dernierReload'
|
|
222
|
+
// console.log('[RafraichisseurToken] reload si erreur')
|
|
223
|
+
const maintenant = Date.now()
|
|
224
|
+
const dernierReload = Number(sessionStorage.getItem(CLE) ?? 0)
|
|
225
|
+
if (maintenant - dernierReload > 30_000) {
|
|
226
|
+
// console.log('[RafraichisseurToken] Écriture sessionStorage:', CLE, maintenant)
|
|
227
|
+
sessionStorage.setItem(CLE, String(maintenant))
|
|
228
|
+
|
|
229
|
+
globalThis.location.reload()
|
|
230
|
+
} else {
|
|
231
|
+
this.popupAffiche = false
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
179
235
|
// URL refresh selon env (dev = proxy Vite ; prod = portail)
|
|
180
236
|
private getRefreshUrl(urlPortail: string): string {
|
|
181
237
|
if (import.meta.env.MODE === 'development') return '/portail-refresh'
|