codevdesign 1.0.67 → 1.0.69

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.
@@ -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 dhorloge
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
- await this.verifierJeton(nomTemoin, urlPortail)
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
- // Permet d’arrêter le timer (ex: au logout / destroy)
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.rafraichir(nomTemoin, urlPortail)
36
- return
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,9 @@ 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
+ if (urlPortail == null || urlPortail == '') return
92
+ this.popupAffiche = true // bloquer tout nouveau tick pendant le refresh
73
93
  const url = this.getRefreshUrl(urlPortail)
74
94
  const controller = new AbortController()
75
95
  const timeout = setTimeout(() => controller.abort(), 10_000)
@@ -84,51 +104,73 @@ class RafraichisseurToken {
84
104
  signal: controller.signal,
85
105
  })
86
106
 
87
- // redirection (souvent => login)traiter comme non auth
88
-
107
+ // redirection vers Azure ADsession expirée, l'iframe ne peut pas compléter le flow OIDC cross-site
108
+ //console.log('[RafraichisseurToken] fetch réponse type:', resp.type, 'status:', resp.status)
89
109
  if (resp.type === 'opaqueredirect' || resp.status === 302) {
90
- this.rafraichirParIframe(nomCookie, urlPortail)
110
+ this.estDeconnecteAzure(urlPortail)
91
111
  return
92
112
  }
93
113
 
94
114
  // OK ou No Content: le cookie devrait être réécrit
95
115
  if (resp.status === 200 || resp.status === 204) {
96
116
  const jeton = this.lireCookie(nomCookie)
97
- if (!jeton) this.rafraichirParIframe(nomCookie, urlPortail)
117
+ if (!jeton) {
118
+ // console.log('[RafraichisseurToken] 200 mais cookie absent → iframe')
119
+ this.rafraichirParIframe(nomCookie, urlPortail)
120
+ } else {
121
+ this.popupAffiche = false // succès, reprendre la surveillance
122
+ }
98
123
  return
99
124
  }
100
125
 
101
126
  // non auth / expiré (401, 419) + IIS timeout (440)
102
127
  if (resp.status === 401 || resp.status === 419 || resp.status === 440) {
128
+ // console.log('[RafraichisseurToken] statut auth → iframe')
103
129
  this.rafraichirParIframe(nomCookie, urlPortail)
104
130
  return
105
131
  }
106
132
 
107
133
  console.warn('Rafraichisseur token: statut inattendu', resp.status)
134
+ this.popupAffiche = false // statut inattendu, permettre un retry
108
135
  } catch (err: any) {
109
136
  if (err?.name === 'AbortError') console.warn('RafraichisseurToken timeout')
110
137
  else console.error('Erreur rafraichisseur de token', err)
111
- // on réessaiera au prochain tick
138
+ this.reloadSiErreurReseau()
112
139
  } finally {
113
140
  clearTimeout(timeout)
114
141
  }
115
142
  }
116
143
 
117
144
  private rafraichirParIframe(nomCookie: string, urlPortail: string): void {
118
- // Pour éviter les cross référence, on créé un iframe qui appel portail et force la MAJ du jeton ou l'invalidation du jeton, si jamais l'utilisateur n'est plus connecté
119
- // ajax vers le refresh
145
+ this.popupAffiche = true // bloquer le timer pendant que l'iframe tente l'auth
120
146
  let iframe = document.createElement('iframe')
121
147
  const url = this.getRefreshUrl(urlPortail)
122
148
  iframe.src = `${url}?urlRetour=${encodeURI(window.location.href)}`
123
149
  iframe.id = 'idRafrToken'
124
150
  iframe.style.display = 'none'
125
151
  document.body.appendChild(iframe)
152
+
153
+ const iframeTimeout = setTimeout(() => {
154
+ iframe.onload = null
155
+ iframe.src = 'about:blank'
156
+ iframe.remove()
157
+ this.popupAffiche = false
158
+ console.warn('[RafraichisseurToken] iframe timeout — popupAffiche réinitialisé')
159
+ }, 15_000)
160
+
126
161
  iframe.onload = () => {
127
- const jetonCSQC = this.lireCookie(nomCookie)
128
- if (jetonCSQC == null || jetonCSQC === '') {
162
+ clearTimeout(iframeTimeout)
163
+ iframe.onload = null // empêcher les re-entrées sur les redirects OIDC internes
164
+ const nomSecure = nomCookie.replace('_expiration', '')
165
+ const cookieAVerifier = nomSecure !== nomCookie ? nomSecure : nomCookie
166
+ const jeton = this.lireCookie(cookieAVerifier)
167
+ if (jeton == null || jeton === '') {
129
168
  this.estDeconnecteAzure(urlPortail)
169
+ } else {
170
+ this.popupAffiche = false // cookie recréé avec succès, reprendre la surveillance
130
171
  }
131
172
 
173
+ iframe.src = 'about:blank' // stopper les navigations internes
132
174
  iframe.remove()
133
175
  }
134
176
  }
@@ -176,6 +218,21 @@ class RafraichisseurToken {
176
218
  return JSON.parse(jsonPayload)
177
219
  }
178
220
 
221
+ private reloadSiErreurReseau(): void {
222
+ const CLE = 'rafraichisseur_dernierReload'
223
+ // console.log('[RafraichisseurToken] reload si erreur')
224
+ const maintenant = Date.now()
225
+ const dernierReload = Number(sessionStorage.getItem(CLE) ?? 0)
226
+ if (maintenant - dernierReload > 30_000) {
227
+ // console.log('[RafraichisseurToken] Écriture sessionStorage:', CLE, maintenant)
228
+ sessionStorage.setItem(CLE, String(maintenant))
229
+
230
+ globalThis.location.reload()
231
+ } else {
232
+ this.popupAffiche = false
233
+ }
234
+ }
235
+
179
236
  // URL refresh selon env (dev = proxy Vite ; prod = portail)
180
237
  private getRefreshUrl(urlPortail: string): string {
181
238
  if (import.meta.env.MODE === 'development') return '/portail-refresh'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codevdesign",
3
- "version": "1.0.67",
3
+ "version": "1.0.69",
4
4
  "description": "Composants Vuetify 3 pour les projets Codev",
5
5
  "files": [
6
6
  "./**/*.vue",