codevdesign 1.0.53 → 1.0.55

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,6 +1,6 @@
1
1
  <template>
2
2
  <v-date-input
3
- v-model="dateInterne"
3
+ :model-value="dateInterne"
4
4
  v-bind="$attrs"
5
5
  input-format="yyyy-MM-dd"
6
6
  variant="outlined"
@@ -12,7 +12,7 @@
12
12
  </template>
13
13
 
14
14
  <script setup lang="ts">
15
- import { computed } from 'vue'
15
+ import { computed, ref } from 'vue'
16
16
  import { VDateInput } from 'vuetify/labs/VDateInput'
17
17
 
18
18
  type Model = string | Date | null
@@ -20,7 +20,7 @@
20
20
  const props = defineProps<{ modelValue: Model }>()
21
21
 
22
22
  const emit = defineEmits<{
23
- (e: 'update:model-value', v: string | null): void
23
+ (e: 'update:modelValue', v: string | null): void
24
24
  (e: 'change', v: string | null): void
25
25
  }>()
26
26
 
@@ -28,6 +28,7 @@
28
28
  const normalizeToYmd = (v: Date | string | null | undefined): string | null => {
29
29
  if (v == null || v === '') return null
30
30
 
31
+ // Date -> YYYY-MM-DD (local)
31
32
  if (v instanceof Date) {
32
33
  if (isNaN(v.getTime())) return null
33
34
  const yyyy = v.getFullYear()
@@ -36,44 +37,43 @@
36
37
  return `${yyyy}-${mm}-${dd}`
37
38
  }
38
39
 
40
+ // String -> accepte seulement YYYY-MM-DD
39
41
  const s = String(v).trim()
42
+ const m = s.match(/^(\d{4})-(\d{2})-(\d{2})$/)
43
+ if (!m) return null
40
44
 
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]
45
+ // Optionnel: validation simple (évite 2026-99-99)
46
+ const y = Number(m[1]),
47
+ mo = Number(m[2]),
48
+ d = Number(m[3])
49
+ if (mo < 1 || mo > 12 || d < 1 || d > 31) return null
44
50
 
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}`
51
+ return `${m[1]}-${m[2]}-${m[3]}`
52
52
  }
53
53
 
54
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
- },
55
+ const dateInterne = computed<Date | null>(() => {
56
+ const ymd = normalizeToYmd(props.modelValue as any)
57
+ if (!ymd) return null
58
+ const [y, m, d] = ymd.split('-').map(Number)
59
+ return new Date(y, m - 1, d) // local, stable
67
60
  })
68
61
 
62
+ const prevBeforeEmit = ref<string | null>(null)
63
+
69
64
  const choixUtilisateur = (v: Date | string | null) => {
70
65
  const next = normalizeToYmd(v)
71
66
  const current = normalizeToYmd(props.modelValue as any)
72
67
 
73
- // Bloque la 2e émission au blur (même si type diffère Date/string)
68
+ // rollback: on reçoit l'ancienne valeur (celle qu'on avait avant)
69
+ if (prevBeforeEmit.value && next === prevBeforeEmit.value && current !== prevBeforeEmit.value) {
70
+ return
71
+ }
72
+
74
73
  if (next === current) return
75
74
 
76
- emit('update:model-value', next)
75
+ prevBeforeEmit.value = current
76
+ emit('update:modelValue', next)
77
77
  emit('change', next)
78
78
  }
79
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codevdesign",
3
- "version": "1.0.53",
3
+ "version": "1.0.55",
4
4
  "description": "Composants Vuetify 3 pour les projets Codev",
5
5
  "files": [
6
6
  "./**/*.vue",