codevdesign 1.0.53 → 1.0.54
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/composants/csqcDate.vue +79 -79
- package/index.ts +2 -0
- package/outils/csqcRafraichisseurTokenParent.ts +132 -0
- package/package.json +1 -1
package/composants/csqcDate.vue
CHANGED
|
@@ -1,79 +1,79 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<v-date-input
|
|
3
|
-
v-model="dateInterne"
|
|
4
|
-
v-bind="$attrs"
|
|
5
|
-
input-format="yyyy-MM-dd"
|
|
6
|
-
variant="outlined"
|
|
7
|
-
prepend-icon=""
|
|
8
|
-
prepend-inner-icon="$calendar"
|
|
9
|
-
density="comfortable"
|
|
10
|
-
@update:model-value="choixUtilisateur"
|
|
11
|
-
/>
|
|
12
|
-
</template>
|
|
13
|
-
|
|
14
|
-
<script setup lang="ts">
|
|
15
|
-
import { computed } from 'vue'
|
|
16
|
-
import { VDateInput } from 'vuetify/labs/VDateInput'
|
|
17
|
-
|
|
18
|
-
type Model = string | Date | null
|
|
19
|
-
|
|
20
|
-
const props = defineProps<{ modelValue: Model }>()
|
|
21
|
-
|
|
22
|
-
const emit = defineEmits<{
|
|
23
|
-
(e: 'update:model-value', v: string | null): void
|
|
24
|
-
(e: 'change', v: string | null): void
|
|
25
|
-
}>()
|
|
26
|
-
|
|
27
|
-
// Normalisation, TOUJOURS "YYYY-MM-DD" ou null
|
|
28
|
-
const normalizeToYmd = (v: Date | string | null | undefined): string | null => {
|
|
29
|
-
if (v == null || v === '') return null
|
|
30
|
-
|
|
31
|
-
if (v instanceof Date) {
|
|
32
|
-
if (isNaN(v.getTime())) return null
|
|
33
|
-
const yyyy = v.getFullYear()
|
|
34
|
-
const mm = String(v.getMonth() + 1).padStart(2, '0')
|
|
35
|
-
const dd = String(v.getDate()).padStart(2, '0')
|
|
36
|
-
return `${yyyy}-${mm}-${dd}`
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const s = String(v).trim()
|
|
40
|
-
|
|
41
|
-
// si ça commence par YYYY-MM-DD, on prend les 10 premiers chars
|
|
42
|
-
const m = s.match(/^(\d{4}-\d{2}-\d{2})/)
|
|
43
|
-
if (m) return m[1]
|
|
44
|
-
|
|
45
|
-
// Sinon, on tente un fallback
|
|
46
|
-
const d = new Date(s)
|
|
47
|
-
if (isNaN(d.getTime())) return null
|
|
48
|
-
const yyyy = d.getFullYear()
|
|
49
|
-
const mm = String(d.getMonth() + 1).padStart(2, '0')
|
|
50
|
-
const dd = String(d.getDate()).padStart(2, '0')
|
|
51
|
-
return `${yyyy}-${mm}-${dd}`
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// UI: VDateInput stable avec Date|null
|
|
55
|
-
const dateInterne = computed<Date | null>({
|
|
56
|
-
get() {
|
|
57
|
-
const ymd = normalizeToYmd(props.modelValue as any)
|
|
58
|
-
if (!ymd) return null
|
|
59
|
-
|
|
60
|
-
// Construire une Date locale sans décalage timezone (évite UTC shift)
|
|
61
|
-
const [y, m, d] = ymd.split('-').map(Number)
|
|
62
|
-
return new Date(y, m - 1, d)
|
|
63
|
-
},
|
|
64
|
-
set() {
|
|
65
|
-
// rien: on émet seulement dans choixUtilisateur
|
|
66
|
-
},
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
const choixUtilisateur = (v: Date | string | null) => {
|
|
70
|
-
const next = normalizeToYmd(v)
|
|
71
|
-
const current = normalizeToYmd(props.modelValue as any)
|
|
72
|
-
|
|
73
|
-
// Bloque la 2e émission au blur (même si type diffère Date/string)
|
|
74
|
-
if (next === current) return
|
|
75
|
-
|
|
76
|
-
emit('update:model-value', next)
|
|
77
|
-
emit('change', next)
|
|
78
|
-
}
|
|
79
|
-
</script>
|
|
1
|
+
<template>
|
|
2
|
+
<v-date-input
|
|
3
|
+
v-model="dateInterne"
|
|
4
|
+
v-bind="$attrs"
|
|
5
|
+
input-format="yyyy-MM-dd"
|
|
6
|
+
variant="outlined"
|
|
7
|
+
prepend-icon=""
|
|
8
|
+
prepend-inner-icon="$calendar"
|
|
9
|
+
density="comfortable"
|
|
10
|
+
@update:model-value="choixUtilisateur"
|
|
11
|
+
/>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script setup lang="ts">
|
|
15
|
+
import { computed } from 'vue'
|
|
16
|
+
import { VDateInput } from 'vuetify/labs/VDateInput'
|
|
17
|
+
|
|
18
|
+
type Model = string | Date | null
|
|
19
|
+
|
|
20
|
+
const props = defineProps<{ modelValue: Model }>()
|
|
21
|
+
|
|
22
|
+
const emit = defineEmits<{
|
|
23
|
+
(e: 'update:model-value', v: string | null): void
|
|
24
|
+
(e: 'change', v: string | null): void
|
|
25
|
+
}>()
|
|
26
|
+
|
|
27
|
+
// Normalisation, TOUJOURS "YYYY-MM-DD" ou null
|
|
28
|
+
const normalizeToYmd = (v: Date | string | null | undefined): string | null => {
|
|
29
|
+
if (v == null || v === '') return null
|
|
30
|
+
|
|
31
|
+
if (v instanceof Date) {
|
|
32
|
+
if (isNaN(v.getTime())) return null
|
|
33
|
+
const yyyy = v.getFullYear()
|
|
34
|
+
const mm = String(v.getMonth() + 1).padStart(2, '0')
|
|
35
|
+
const dd = String(v.getDate()).padStart(2, '0')
|
|
36
|
+
return `${yyyy}-${mm}-${dd}`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const s = String(v).trim()
|
|
40
|
+
|
|
41
|
+
// si ça commence par YYYY-MM-DD, on prend les 10 premiers chars
|
|
42
|
+
const m = s.match(/^(\d{4}-\d{2}-\d{2})/)
|
|
43
|
+
if (m) return m[1]
|
|
44
|
+
|
|
45
|
+
// Sinon, on tente un fallback
|
|
46
|
+
const d = new Date(s)
|
|
47
|
+
if (isNaN(d.getTime())) return null
|
|
48
|
+
const yyyy = d.getFullYear()
|
|
49
|
+
const mm = String(d.getMonth() + 1).padStart(2, '0')
|
|
50
|
+
const dd = String(d.getDate()).padStart(2, '0')
|
|
51
|
+
return `${yyyy}-${mm}-${dd}`
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// UI: VDateInput stable avec Date|null
|
|
55
|
+
const dateInterne = computed<Date | null>({
|
|
56
|
+
get() {
|
|
57
|
+
const ymd = normalizeToYmd(props.modelValue as any)
|
|
58
|
+
if (!ymd) return null
|
|
59
|
+
|
|
60
|
+
// Construire une Date locale sans décalage timezone (évite UTC shift)
|
|
61
|
+
const [y, m, d] = ymd.split('-').map(Number)
|
|
62
|
+
return new Date(y, m - 1, d)
|
|
63
|
+
},
|
|
64
|
+
set() {
|
|
65
|
+
// rien: on émet seulement dans choixUtilisateur
|
|
66
|
+
},
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const choixUtilisateur = (v: Date | string | null) => {
|
|
70
|
+
const next = normalizeToYmd(v)
|
|
71
|
+
const current = normalizeToYmd(props.modelValue as any)
|
|
72
|
+
|
|
73
|
+
// Bloque la 2e émission au blur (même si type diffère Date/string)
|
|
74
|
+
if (next === current) return
|
|
75
|
+
|
|
76
|
+
emit('update:model-value', next)
|
|
77
|
+
emit('change', next)
|
|
78
|
+
}
|
|
79
|
+
</script>
|
package/index.ts
CHANGED
|
@@ -35,6 +35,7 @@ import response from './modeles/response'
|
|
|
35
35
|
|
|
36
36
|
// outils
|
|
37
37
|
import csqcRafraichisseurToken from './outils/rafraichisseurToken'
|
|
38
|
+
import csqcRafraichisseurTokenParent from './outils/csqcRafraichisseurTokenParent'
|
|
38
39
|
import csqcOutils from './outils/csqcOutils'
|
|
39
40
|
|
|
40
41
|
// i18n
|
|
@@ -68,6 +69,7 @@ export {
|
|
|
68
69
|
csqcImportCSV,
|
|
69
70
|
csqcRechercheUtilisateur,
|
|
70
71
|
csqcRafraichisseurToken,
|
|
72
|
+
csqcRafraichisseurTokenParent,
|
|
71
73
|
csqcOutils,
|
|
72
74
|
modeleSnackbar,
|
|
73
75
|
modeleDatatableColonne as colonne,
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import i18n from '@/plugins/i18n'
|
|
2
|
+
|
|
3
|
+
class RafraichisseurTokenParent {
|
|
4
|
+
private intervalleEnSecondes = 5
|
|
5
|
+
private secondesAvantExpirationTokenParentPourRafraichir = 10
|
|
6
|
+
private popupAffiche = false
|
|
7
|
+
private timerId: number | null = null
|
|
8
|
+
|
|
9
|
+
public async demarrer(nomCookie: string, urlPortailSeConnecterParent: string, urlPortailRafraichissementParent: string) {
|
|
10
|
+
await this.verifierJeton(nomCookie, urlPortailSeConnecterParent, urlPortailRafraichissementParent)
|
|
11
|
+
if (this.timerId != null) return
|
|
12
|
+
window.setInterval(() => {
|
|
13
|
+
this.verifierJeton(nomCookie, urlPortailSeConnecterParent, urlPortailRafraichissementParent)
|
|
14
|
+
}, this.intervalleEnSecondes * 1000)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Permet d’arrêter le timer (ex: au logout / destroy)
|
|
18
|
+
public arreter(): void {
|
|
19
|
+
if (this.timerId != null) {
|
|
20
|
+
clearInterval(this.timerId)
|
|
21
|
+
this.timerId = null
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private async verifierJeton(nomCookie: string, urlPortailSeConnecterParent: string, urlPortailRafraichissementParent: string) {
|
|
26
|
+
if (this.popupAffiche) return //Ne rien faire si le popup de déconnexion est déjà affiché
|
|
27
|
+
|
|
28
|
+
const tokenEncode = this.lireCookie(nomCookie)
|
|
29
|
+
|
|
30
|
+
if (tokenEncode == null || (tokenEncode != null && !this.estJetonValide(tokenEncode))) {
|
|
31
|
+
await this.rafraichir(nomCookie, urlPortailSeConnecterParent, urlPortailRafraichissementParent)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private estJetonValide(tokenEncode: string) {
|
|
36
|
+
if (tokenEncode === null || tokenEncode === '') {
|
|
37
|
+
return false
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const token = this.parseJwt(tokenEncode)
|
|
41
|
+
|
|
42
|
+
let tempActuel = new Date().getTime() / 1000 // Temps en secondes
|
|
43
|
+
|
|
44
|
+
tempActuel += this.secondesAvantExpirationTokenParentPourRafraichir
|
|
45
|
+
if (tempActuel > token.exp) {
|
|
46
|
+
return false
|
|
47
|
+
}
|
|
48
|
+
return true
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private async rafraichir(nomCookie: string, urlPortailSeConnecterParent: string, urlPortailRafraichissementParent: string) {
|
|
52
|
+
if (!nomCookie) return
|
|
53
|
+
|
|
54
|
+
const controller = new AbortController()
|
|
55
|
+
const timeout = setTimeout(() => controller.abort(), 10_000)
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
// Rafraichir par IFrame est le seul moyen qui fonctionne pour ne pas avoir un HTTP 405 ou
|
|
59
|
+
// un NS_BINDING_ABORTED dans le frontend en appelant le Rafraichir du portail parent
|
|
60
|
+
await this.rafraichirParIframe(nomCookie, urlPortailSeConnecterParent, urlPortailRafraichissementParent)
|
|
61
|
+
|
|
62
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
63
|
+
} catch (err: any) {
|
|
64
|
+
if (err?.name === 'AbortError') console.warn('RafraichisseurTokenParent timeout')
|
|
65
|
+
else console.error('Erreur rafraichisseur de token parent', err)
|
|
66
|
+
// on réessaiera au prochain tick
|
|
67
|
+
} finally {
|
|
68
|
+
clearTimeout(timeout)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private async rafraichirParIframe(nomCookie: string, urlPortailSeConnecterParent: string, urlPortailRafraichissementParent: string) {
|
|
73
|
+
// Pour éviter les cross référence, on créé un iframe qui appel portail parent et force la MAJ du jeton ou l'invalidation du jeton, si jamais l'utilisateur n'est plus connecté
|
|
74
|
+
// ajax vers le refresh
|
|
75
|
+
const iframe = document.createElement('iframe')
|
|
76
|
+
const url = urlPortailRafraichissementParent
|
|
77
|
+
iframe.src = `${url}`
|
|
78
|
+
iframe.id = 'idRafrToken'
|
|
79
|
+
iframe.style.display = 'none'
|
|
80
|
+
document.body.appendChild(iframe)
|
|
81
|
+
iframe.onload = () => {
|
|
82
|
+
const jetonCSQC = this.lireCookie(nomCookie)
|
|
83
|
+
if (jetonCSQC == null || jetonCSQC === '') {
|
|
84
|
+
this.estDeconnecte(urlPortailSeConnecterParent)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
iframe.remove()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Attendre 1500 ms
|
|
91
|
+
await new Promise(resolve => setTimeout(resolve, 1500))
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private estDeconnecte(urlPortailSeConnecterParent: string) {
|
|
95
|
+
this.popupAffiche = true
|
|
96
|
+
if (confirm('rafr' + i18n.global.t('csqcRafraichisseurTokenParent.estDeconnecte'))) {
|
|
97
|
+
window.open(`${urlPortailSeConnecterParent}?urlRetour=${encodeURI(window.localStorage.href)}`, '_self')
|
|
98
|
+
this.popupAffiche = false
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private lireCookie(nom: string) {
|
|
103
|
+
const nameEQ = `${nom}=`
|
|
104
|
+
const ca = document.cookie.split(';')
|
|
105
|
+
for (let i = 0; i < ca.length; i += 1) {
|
|
106
|
+
let c = ca[i]
|
|
107
|
+
while (c.charAt(0) === ' ') {
|
|
108
|
+
c = c.substring(1, c.length)
|
|
109
|
+
}
|
|
110
|
+
if (c.indexOf(nameEQ) === 0) {
|
|
111
|
+
return c.substring(nameEQ.length, c.length)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return null
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private parseJwt(token: string) {
|
|
118
|
+
const base64Url = token.split('.')[1]
|
|
119
|
+
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
|
|
120
|
+
const jsonPayload = decodeURIComponent(
|
|
121
|
+
atob(base64)
|
|
122
|
+
.split('')
|
|
123
|
+
.map(c => `%${`00${c.charCodeAt(0).toString(16)}`.slice(-2)}`)
|
|
124
|
+
.join(''),
|
|
125
|
+
)
|
|
126
|
+
return JSON.parse(jsonPayload)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Instance
|
|
131
|
+
const rafraichisseurTokenParent = new RafraichisseurTokenParent()
|
|
132
|
+
export default rafraichisseurTokenParent
|