codevdesign 1.0.4 → 1.0.6

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,186 +1,186 @@
1
- import { useAppStore } from '@/store/appStore'
2
-
3
- class RafraichisseurToken {
4
- private intervalleEnSecondes = 15
5
- private secondesAvantExpirationTokenPourRafraichir = 20
6
- private skewSeconds = 5 // marge anti-derives d’horloge
7
- private popupAffiche = false
8
- private appStore: ReturnType<typeof useAppStore> | null = null
9
- private timerId: number | null = null
10
-
11
- // Lance une seule fois
12
- public async demarrer(): Promise<void> {
13
- this.appStore = useAppStore()
14
- await this.verifierToken()
15
- if (this.timerId != null) return
16
- this.timerId = window.setInterval(() => {
17
- this.verifierToken()
18
- }, this.intervalleEnSecondes * 1000)
19
- }
20
-
21
- // Permet d’arrêter le timer (ex: au logout / destroy)
22
- public arreter(): void {
23
- if (this.timerId != null) {
24
- clearInterval(this.timerId)
25
- this.timerId = null
26
- }
27
- }
28
-
29
- private async verifierToken(): Promise<void> {
30
- if (!this.appStore) this.appStore = useAppStore()
31
- const modele = this.appStore?.appModele
32
- if (this.popupAffiche || !modele) return
33
- const tokenEncode = this.lireCookie(modele.nomCookie)
34
- if (!tokenEncode) {
35
- await this.rafraichir()
36
- return
37
- }
38
-
39
- let token: any
40
- try {
41
- token = this.parseJwt(tokenEncode)
42
- } catch {
43
- await this.rafraichir()
44
- return
45
- }
46
-
47
- const exp = Number(token?.exp)
48
- if (!Number.isFinite(exp)) {
49
- // exp manquant/invalide → tente refresh
50
- await this.rafraichir()
51
- return
52
- }
53
-
54
- const now = Math.floor(Date.now() / 1000)
55
- const refreshAt = exp - this.secondesAvantExpirationTokenPourRafraichir - this.skewSeconds
56
- if (now >= refreshAt) {
57
- await this.rafraichir()
58
- }
59
- }
60
-
61
- private async rafraichir(): Promise<void> {
62
- if (!this.appStore) this.appStore = useAppStore()
63
- const modele = this.appStore?.appModele
64
- if (!modele) return
65
- const url = this.getRefreshUrl()
66
- const controller = new AbortController()
67
- const timeout = setTimeout(() => controller.abort(), 10_000)
68
-
69
- try {
70
- //Première tentative sans iframe, pour la majorité des cas.
71
- const resp = await fetch(url, {
72
- method: 'POST',
73
- credentials: 'include',
74
- headers: { Accept: 'application/json' },
75
- redirect: 'manual',
76
- signal: controller.signal,
77
- })
78
-
79
- // redirection (souvent => login) → traiter comme non auth
80
-
81
- if (resp.type === 'opaqueredirect' || resp.status === 302) {
82
- this.rafraichirParIframe()
83
- return
84
- }
85
-
86
- // OK ou No Content: le cookie devrait être réécrit
87
- if (resp.status === 200 || resp.status === 204) {
88
- const jeton = this.lireCookie(modele.nomCookie)
89
- if (!jeton) this.rafraichirParIframe()
90
- return
91
- }
92
-
93
- // non auth / expiré (401, 419) + IIS timeout (440)
94
- if (resp.status === 401 || resp.status === 419 || resp.status === 440) {
95
- this.rafraichirParIframe()
96
- return
97
- }
98
-
99
- console.warn('Rafraichisseur token: statut inattendu', resp.status)
100
- } catch (err: any) {
101
- if (err?.name === 'AbortError') console.warn('RafraichisseurToken timeout')
102
- else console.error('Erreur rafraichisseur de token', err)
103
- // on réessaiera au prochain tick
104
- } finally {
105
- clearTimeout(timeout)
106
- }
107
- }
108
-
109
- private rafraichirParIframe() {
110
- if (!this.appStore) this.appStore = useAppStore()
111
- // 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é
112
- // ajax vers le refresh
113
- let iframe = document.createElement('iframe')
114
- const url = this.getRefreshUrl()
115
- iframe.src = `${url}?urlRetour=${encodeURI(window.localStorage.href)}`
116
- iframe.id = 'idRafrToken'
117
- iframe.style.display = 'none'
118
- document.body.appendChild(iframe)
119
- iframe.onload = () => {
120
- const jetonCSQC = this.lireCookie(this.appStore!.appModele!.nomCookie)
121
- if (jetonCSQC == null || jetonCSQC === '') {
122
- this.estDeconnecteAzure()
123
- }
124
-
125
- iframe.remove()
126
- }
127
- }
128
-
129
- private estDeconnecteAzure(): void {
130
- //on envoie au portail, pas le choix
131
- if (!this.appStore) this.appStore = useAppStore()
132
-
133
- const retour = encodeURI(window.location.href)
134
- window.open(`${this.appStore.appModele!.urlPortailSeConnecter}?urlRetour=${retour}`, '_self')
135
- return
136
- }
137
-
138
- public deconnecterPortail(): void {
139
- if (!this.appStore) this.appStore = useAppStore()
140
- const modele = this.appStore?.appModele
141
- if (!modele) return
142
-
143
- window.location.replace(`${modele.urlPortail}deconnecte?urlRetour=${encodeURIComponent(window.location.href)}`)
144
- }
145
-
146
-
147
- private lireCookie(nom: string): string | null {
148
- if (!document.cookie) return null
149
- const cookies = document.cookie.split(';').map(c => c.trim())
150
- for (const cookie of cookies) {
151
- if (cookie.startsWith(`${nom}=`)) {
152
- try {
153
- return decodeURIComponent(cookie.substring(nom.length + 1))
154
- } catch {
155
- return cookie.substring(nom.length + 1)
156
- }
157
- }
158
- }
159
- return null
160
- }
161
-
162
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
163
- private parseJwt(token: string): any {
164
- const parts = token.split('.')
165
- const base64Url = parts[1]
166
- if (!base64Url) throw new Error('Invalid JWT format')
167
- const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
168
- const jsonPayload = decodeURIComponent(
169
- atob(base64)
170
- .split('')
171
- .map(c => `%${`00${c.charCodeAt(0).toString(16)}`.slice(-2)}`)
172
- .join(''),
173
- )
174
- return JSON.parse(jsonPayload)
175
- }
176
-
177
- // URL refresh selon env (dev = proxy Vite ; prod = portail)
178
- private getRefreshUrl(): string {
179
- if (import.meta.env.MODE === 'development') return '/portail-refresh'
180
- return this.appStore!.appModele!.urlPortailRafraichissement
181
- }
182
- }
183
-
184
- // Instance
185
- const rafraichisseurToken = new RafraichisseurToken()
186
- export default rafraichisseurToken
1
+ import { useAppStore } from '@/store/appStore'
2
+
3
+ class RafraichisseurToken {
4
+ private intervalleEnSecondes = 15
5
+ private secondesAvantExpirationTokenPourRafraichir = 20
6
+ private skewSeconds = 5 // marge anti-derives d’horloge
7
+ private popupAffiche = false
8
+ private appStore: ReturnType<typeof useAppStore> | null = null
9
+ private timerId: number | null = null
10
+
11
+ // Lance une seule fois
12
+ public async demarrer(): Promise<void> {
13
+ this.appStore = useAppStore()
14
+ await this.verifierToken()
15
+ if (this.timerId != null) return
16
+ this.timerId = window.setInterval(() => {
17
+ this.verifierToken()
18
+ }, this.intervalleEnSecondes * 1000)
19
+ }
20
+
21
+ // Permet d’arrêter le timer (ex: au logout / destroy)
22
+ public arreter(): void {
23
+ if (this.timerId != null) {
24
+ clearInterval(this.timerId)
25
+ this.timerId = null
26
+ }
27
+ }
28
+
29
+ private async verifierToken(): Promise<void> {
30
+ if (!this.appStore) this.appStore = useAppStore()
31
+ const modele = this.appStore?.appModele
32
+ if (this.popupAffiche || !modele) return
33
+ const tokenEncode = this.lireCookie(modele.nomCookie)
34
+ if (!tokenEncode) {
35
+ await this.rafraichir()
36
+ return
37
+ }
38
+
39
+ let token: any
40
+ try {
41
+ token = this.parseJwt(tokenEncode)
42
+ } catch {
43
+ await this.rafraichir()
44
+ return
45
+ }
46
+
47
+ const exp = Number(token?.exp)
48
+ if (!Number.isFinite(exp)) {
49
+ // exp manquant/invalide → tente refresh
50
+ await this.rafraichir()
51
+ return
52
+ }
53
+
54
+ const now = Math.floor(Date.now() / 1000)
55
+ const refreshAt = exp - this.secondesAvantExpirationTokenPourRafraichir - this.skewSeconds
56
+ if (now >= refreshAt) {
57
+ await this.rafraichir()
58
+ }
59
+ }
60
+
61
+ private async rafraichir(): Promise<void> {
62
+ if (!this.appStore) this.appStore = useAppStore()
63
+ const modele = this.appStore?.appModele
64
+ if (!modele) return
65
+ const url = this.getRefreshUrl()
66
+ const controller = new AbortController()
67
+ const timeout = setTimeout(() => controller.abort(), 10_000)
68
+
69
+ try {
70
+ //Première tentative sans iframe, pour la majorité des cas.
71
+ const resp = await fetch(url, {
72
+ method: 'POST',
73
+ credentials: 'include',
74
+ headers: { Accept: 'application/json' },
75
+ redirect: 'manual',
76
+ signal: controller.signal,
77
+ })
78
+
79
+ // redirection (souvent => login) → traiter comme non auth
80
+
81
+ if (resp.type === 'opaqueredirect' || resp.status === 302) {
82
+ this.rafraichirParIframe()
83
+ return
84
+ }
85
+
86
+ // OK ou No Content: le cookie devrait être réécrit
87
+ if (resp.status === 200 || resp.status === 204) {
88
+ const jeton = this.lireCookie(modele.nomCookie)
89
+ if (!jeton) this.rafraichirParIframe()
90
+ return
91
+ }
92
+
93
+ // non auth / expiré (401, 419) + IIS timeout (440)
94
+ if (resp.status === 401 || resp.status === 419 || resp.status === 440) {
95
+ this.rafraichirParIframe()
96
+ return
97
+ }
98
+
99
+ console.warn('Rafraichisseur token: statut inattendu', resp.status)
100
+ } catch (err: any) {
101
+ if (err?.name === 'AbortError') console.warn('RafraichisseurToken timeout')
102
+ else console.error('Erreur rafraichisseur de token', err)
103
+ // on réessaiera au prochain tick
104
+ } finally {
105
+ clearTimeout(timeout)
106
+ }
107
+ }
108
+
109
+ private rafraichirParIframe() {
110
+ if (!this.appStore) this.appStore = useAppStore()
111
+ // 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é
112
+ // ajax vers le refresh
113
+ let iframe = document.createElement('iframe')
114
+ const url = this.getRefreshUrl()
115
+ iframe.src = `${url}?urlRetour=${encodeURI(window.localStorage.href)}`
116
+ iframe.id = 'idRafrToken'
117
+ iframe.style.display = 'none'
118
+ document.body.appendChild(iframe)
119
+ iframe.onload = () => {
120
+ const jetonCSQC = this.lireCookie(this.appStore!.appModele!.nomCookie)
121
+ if (jetonCSQC == null || jetonCSQC === '') {
122
+ this.estDeconnecteAzure()
123
+ }
124
+
125
+ iframe.remove()
126
+ }
127
+ }
128
+
129
+ private estDeconnecteAzure(): void {
130
+ //on envoie au portail, pas le choix
131
+ if (!this.appStore) this.appStore = useAppStore()
132
+
133
+ const retour = encodeURI(window.location.href)
134
+ window.open(`${this.appStore.appModele!.urlPortailSeConnecter}?urlRetour=${retour}`, '_self')
135
+ return
136
+ }
137
+
138
+ public deconnecterPortail(): void {
139
+ if (!this.appStore) this.appStore = useAppStore()
140
+ const modele = this.appStore?.appModele
141
+ if (!modele) return
142
+
143
+ window.location.replace(`${modele.urlPortail}deconnecte?urlRetour=${encodeURIComponent(window.location.href)}`)
144
+ }
145
+
146
+
147
+ private lireCookie(nom: string): string | null {
148
+ if (!document.cookie) return null
149
+ const cookies = document.cookie.split(';').map(c => c.trim())
150
+ for (const cookie of cookies) {
151
+ if (cookie.startsWith(`${nom}=`)) {
152
+ try {
153
+ return decodeURIComponent(cookie.substring(nom.length + 1))
154
+ } catch {
155
+ return cookie.substring(nom.length + 1)
156
+ }
157
+ }
158
+ }
159
+ return null
160
+ }
161
+
162
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
163
+ private parseJwt(token: string): any {
164
+ const parts = token.split('.')
165
+ const base64Url = parts[1]
166
+ if (!base64Url) throw new Error('Invalid JWT format')
167
+ const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
168
+ const jsonPayload = decodeURIComponent(
169
+ atob(base64)
170
+ .split('')
171
+ .map(c => `%${`00${c.charCodeAt(0).toString(16)}`.slice(-2)}`)
172
+ .join(''),
173
+ )
174
+ return JSON.parse(jsonPayload)
175
+ }
176
+
177
+ // URL refresh selon env (dev = proxy Vite ; prod = portail)
178
+ private getRefreshUrl(): string {
179
+ if (import.meta.env.MODE === 'development') return '/portail-refresh'
180
+ return this.appStore!.appModele!.urlPortailRafraichissement
181
+ }
182
+ }
183
+
184
+ // Instance
185
+ const rafraichisseurToken = new RafraichisseurToken()
186
+ export default rafraichisseurToken
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codevdesign",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "Composants Vuetify 3 pour les projets Codev",
5
5
  "files": [
6
6
  "./**/*.vue",