tycho-components 0.36.9 → 0.36.11
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.
|
@@ -8,9 +8,10 @@ declare global {
|
|
|
8
8
|
* the vendor tag on mount from a Vite env var). Set `VITE_APP_CLARITY_ID`.
|
|
9
9
|
*
|
|
10
10
|
* On top of the plain snippet it stitches every session of the logged-in Tycho
|
|
11
|
-
* user under their stable `uid` (
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* user under their stable `uid` (from `CookieStorage` / `SecurityUtils.parseJwt`)
|
|
12
|
+
* via `clarity('identify', …)`, so cross-module full-page navigations count as
|
|
13
|
+
* ONE identified user instead of N anonymous ones. Re-runs identify when the JWT
|
|
14
|
+
* cookie changes (login/logout) or the tab becomes visible again. The friendly
|
|
15
|
+
* name is PII — drop the last `identify` argument for a stricter privacy posture.
|
|
15
16
|
*/
|
|
16
17
|
export default function AppClarity(): null;
|
|
@@ -1,48 +1,67 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
import CookieStorage, { JWT_CHANGED_EVENT } from '../../configs/CookieStorage';
|
|
3
|
+
import SecurityUtils from '../../functions/SecurityUtils';
|
|
4
|
+
const SCRIPT_ATTR = 'data-tycho-clarity';
|
|
2
5
|
/**
|
|
3
6
|
* Microsoft Clarity loader — mirrors {@link AppAnalytics} (null render, injects
|
|
4
7
|
* the vendor tag on mount from a Vite env var). Set `VITE_APP_CLARITY_ID`.
|
|
5
8
|
*
|
|
6
9
|
* On top of the plain snippet it stitches every session of the logged-in Tycho
|
|
7
|
-
* user under their stable `uid` (
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
10
|
+
* user under their stable `uid` (from `CookieStorage` / `SecurityUtils.parseJwt`)
|
|
11
|
+
* via `clarity('identify', …)`, so cross-module full-page navigations count as
|
|
12
|
+
* ONE identified user instead of N anonymous ones. Re-runs identify when the JWT
|
|
13
|
+
* cookie changes (login/logout) or the tab becomes visible again. The friendly
|
|
14
|
+
* name is PII — drop the last `identify` argument for a stricter privacy posture.
|
|
11
15
|
*/
|
|
12
16
|
export default function AppClarity() {
|
|
13
17
|
const CLARITY_ID = import.meta.env.VITE_APP_CLARITY_ID;
|
|
18
|
+
const lastUidRef = useRef(undefined);
|
|
14
19
|
useEffect(() => {
|
|
15
20
|
if (!CLARITY_ID) {
|
|
16
21
|
console.warn('Microsoft Clarity project ID not set.');
|
|
17
22
|
return;
|
|
18
23
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
`t=
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
try {
|
|
29
|
-
const match = document.cookie.match(/(?:^|;\s*)jwt_token_tycho=([^;]+)/);
|
|
30
|
-
if (match) {
|
|
31
|
-
let b64 = match[1].split('.')[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
32
|
-
b64 += '==='.slice((b64.length + 3) % 4);
|
|
33
|
-
const payload = JSON.parse(decodeURIComponent(atob(b64)
|
|
34
|
-
.split('')
|
|
35
|
-
.map((ch) => '%' + ('00' + ch.charCodeAt(0).toString(16)).slice(-2))
|
|
36
|
-
.join('')));
|
|
37
|
-
if (payload?.uid) {
|
|
38
|
-
window.clarity?.('identify', payload.uid, undefined, undefined, payload.name || undefined);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
24
|
+
if (!document.querySelector(`script[${SCRIPT_ATTR}]`)) {
|
|
25
|
+
const loader = document.createElement('script');
|
|
26
|
+
loader.setAttribute(SCRIPT_ATTR, CLARITY_ID);
|
|
27
|
+
loader.innerHTML =
|
|
28
|
+
`(function(c,l,a,r,i,t,y){c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};` +
|
|
29
|
+
`t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;` +
|
|
30
|
+
`y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y)})` +
|
|
31
|
+
`(window,document,"clarity","script","${CLARITY_ID}");`;
|
|
32
|
+
document.head.appendChild(loader);
|
|
41
33
|
window.clarity?.('consent');
|
|
42
34
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
35
|
+
const identifyUser = () => {
|
|
36
|
+
try {
|
|
37
|
+
const token = CookieStorage.getJwtToken();
|
|
38
|
+
if (!token) {
|
|
39
|
+
lastUidRef.current = undefined;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const payload = SecurityUtils.parseJwt(token);
|
|
43
|
+
if (!payload?.uid || payload.uid === lastUidRef.current)
|
|
44
|
+
return;
|
|
45
|
+
window.clarity?.('identify', payload.uid, undefined, undefined, payload.name || undefined);
|
|
46
|
+
lastUidRef.current = payload.uid;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
/* not logged in / unparseable — track anonymously */
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const onVisible = () => {
|
|
53
|
+
if (document.visibilityState === 'visible')
|
|
54
|
+
identifyUser();
|
|
55
|
+
};
|
|
56
|
+
identifyUser();
|
|
57
|
+
window.addEventListener(JWT_CHANGED_EVENT, identifyUser);
|
|
58
|
+
window.addEventListener('focus', identifyUser);
|
|
59
|
+
document.addEventListener('visibilitychange', onVisible);
|
|
60
|
+
return () => {
|
|
61
|
+
window.removeEventListener(JWT_CHANGED_EVENT, identifyUser);
|
|
62
|
+
window.removeEventListener('focus', identifyUser);
|
|
63
|
+
document.removeEventListener('visibilitychange', onVisible);
|
|
64
|
+
};
|
|
46
65
|
}, [CLARITY_ID]);
|
|
47
66
|
return null;
|
|
48
67
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** Dispatched on `window` whenever the Tycho JWT cookie is set or cleared. */
|
|
2
|
+
export declare const JWT_CHANGED_EVENT = "tycho:jwt-changed";
|
|
1
3
|
declare function getRedirectUri(): string | undefined;
|
|
2
4
|
declare function setRedirectUri(uri: string): void;
|
|
3
5
|
declare function removeRedirectUri(): void;
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import Cookies from 'js-cookie';
|
|
2
2
|
const REDIRECT_URI = 'redirect_uri_tycho';
|
|
3
3
|
const JWT_TOKEN = 'jwt_token_tycho';
|
|
4
|
+
/** Dispatched on `window` whenever the Tycho JWT cookie is set or cleared. */
|
|
5
|
+
export const JWT_CHANGED_EVENT = 'tycho:jwt-changed';
|
|
4
6
|
const expireDays = 7;
|
|
7
|
+
const notifyJwtChanged = () => {
|
|
8
|
+
window.dispatchEvent(new Event(JWT_CHANGED_EVENT));
|
|
9
|
+
};
|
|
5
10
|
const set = (key, value) => {
|
|
6
11
|
Cookies.set(key, value, { expires: expireDays });
|
|
7
12
|
};
|
|
@@ -14,13 +19,16 @@ const remove = (key) => {
|
|
|
14
19
|
};
|
|
15
20
|
const setJwtToken = (jwtToken) => {
|
|
16
21
|
Cookies.set(JWT_TOKEN, jwtToken, { expires: expireDays });
|
|
22
|
+
notifyJwtChanged();
|
|
17
23
|
};
|
|
18
24
|
const getJwtToken = () => {
|
|
19
25
|
const cookie = Cookies.get(JWT_TOKEN);
|
|
20
26
|
return cookie === 'undefined' ? '' : cookie;
|
|
21
27
|
};
|
|
22
28
|
const removeJwtToken = () => {
|
|
23
|
-
|
|
29
|
+
const removed = Cookies.remove(JWT_TOKEN);
|
|
30
|
+
notifyJwtChanged();
|
|
31
|
+
return removed;
|
|
24
32
|
};
|
|
25
33
|
function getRedirectUri() {
|
|
26
34
|
return Cookies.get(REDIRECT_URI);
|
|
@@ -11,7 +11,8 @@ const hasAccess = (uid, roles, mode) => {
|
|
|
11
11
|
};
|
|
12
12
|
const parseJwt = (token) => {
|
|
13
13
|
const base64Url = token.split('.')[1];
|
|
14
|
-
|
|
14
|
+
let base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
|
|
15
|
+
base64 += '==='.slice((base64.length + 3) % 4);
|
|
15
16
|
const payload = decodeURIComponent(window
|
|
16
17
|
.atob(base64)
|
|
17
18
|
.split('')
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tycho-components",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.36.
|
|
4
|
+
"version": "0.36.11",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"exports": {
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"react-i18next": "^13.0.2",
|
|
83
83
|
"react-router-dom": "^6.14.2",
|
|
84
84
|
"react-toastify": "^9.1.3",
|
|
85
|
-
"tycho-storybook": "0.10.
|
|
85
|
+
"tycho-storybook": "0.10.15",
|
|
86
86
|
"wavesurfer-react": "^2.2.2",
|
|
87
87
|
"wavesurfer.js": "^6.6.3"
|
|
88
88
|
},
|
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
"react-toastify": "^9.1.3",
|
|
112
112
|
"sass-embedded": "^1.97.2",
|
|
113
113
|
"storybook": "^10.1.11",
|
|
114
|
-
"tycho-storybook": "^0.10.
|
|
114
|
+
"tycho-storybook": "^0.10.15",
|
|
115
115
|
"typescript": "^5.7.3",
|
|
116
116
|
"vite": "^7.0.0",
|
|
117
117
|
"vitest": "^3.2.6",
|