@scaleway/use-analytics
Version:
A small hook to handle events analytics
157 lines (156 loc) • 4.95 kB
JavaScript
import { jsx } from "react/jsx-runtime";
import { parse, serialize } from "cookie";
import { useState, useMemo, useEffect, useCallback, createContext, useContext } from "react";
import { useDestinations } from "../analytics/useDestinations.js";
import { HASH_COOKIE, CATEGORIES, COOKIE_PREFIX, COOKIES_OPTIONS, CONSENT_ADVERTISING_MAX_AGE, CONSENT_MAX_AGE } from "../constants.js";
import { uniq } from "../helpers/array.js";
import { IS_CLIENT } from "../helpers/isClient.js";
import { stringToHash } from "../helpers/misc.js";
import { isCategoryKind } from "../types.js";
const CookieConsentContext = createContext(void 0);
const useCookieConsent = () => {
const context = useContext(CookieConsentContext);
if (context === void 0) {
throw new Error(
"useCookieConsent must be used within a CookieConsentProvider"
);
}
return context;
};
const CookieConsentProvider = ({
children,
isConsentRequired,
manualDestinations,
config,
cookiePrefix = COOKIE_PREFIX,
consentMaxAge = CONSENT_MAX_AGE,
consentAdvertisingMaxAge = CONSENT_ADVERTISING_MAX_AGE,
cookiesOptions = COOKIES_OPTIONS
}) => {
const [needConsent, setNeedsConsent] = useState(false);
const [cookies, setCookies] = useState(
IS_CLIENT ? parse(document.cookie) : {}
);
const {
destinations: analyticsDestinations,
isLoaded: isDestinationsLoaded
} = useDestinations(config);
const destinations = useMemo(
() => uniq([
...(analyticsDestinations ?? []).map(
(dest) => ({
category: dest.consents[0] ?? "essential",
displayName: dest.displayName,
name: dest.name
})
),
...manualDestinations ?? []
]),
[analyticsDestinations, manualDestinations]
);
const destinationsHash = useMemo(
() => stringToHash(
uniq(destinations.map(({ name }) => name)).sort().join(void 0)
),
[destinations]
);
useEffect(() => {
setNeedsConsent(
isConsentRequired && cookies[HASH_COOKIE] !== destinationsHash.toString() && analyticsDestinations !== void 0
);
}, [isConsentRequired, destinationsHash, analyticsDestinations, cookies]);
const cookieConsent = useMemo(
() => CATEGORIES.reduce(
(acc, category) => ({
...acc,
[category]: isConsentRequired || needConsent ? cookies[`${cookiePrefix}_${category}`] === "true" : true
}),
{}
),
[isConsentRequired, cookiePrefix, needConsent, cookies]
);
const saveConsent = useCallback(
(categoriesConsent) => {
for (const [consentName, consentValue] of Object.entries(
categoriesConsent
)) {
const consentCategoryName = isCategoryKind(consentName) ? consentName : "unknown";
const cookieName = `${cookiePrefix}_${consentCategoryName}`;
if (!consentValue) {
document.cookie = serialize(cookieName, "", {
...cookiesOptions,
expires: /* @__PURE__ */ new Date(0)
});
} else {
document.cookie = serialize(cookieName, consentValue.toString(), {
...cookiesOptions,
maxAge: consentCategoryName === "advertising" ? consentAdvertisingMaxAge : consentMaxAge
});
}
setCookies((prevCookies) => ({
...prevCookies,
[cookieName]: consentValue ? "true" : "false"
}));
}
document.cookie = serialize(HASH_COOKIE, destinationsHash.toString(), {
...cookiesOptions,
// Here we use the shortest max age to force to ask again for expired consent
maxAge: consentAdvertisingMaxAge
});
setCookies((prevCookies) => ({
...prevCookies,
[HASH_COOKIE]: destinationsHash.toString()
}));
setNeedsConsent(false);
},
[
destinationsHash,
consentAdvertisingMaxAge,
consentMaxAge,
cookiePrefix,
cookiesOptions
]
);
const allowedConsents = useMemo(
() => Object.keys(cookieConsent).filter(
// oxlint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
(key) => !cookieConsent[key]
),
[cookieConsent]
);
const deniedConsents = useMemo(
() => Object.keys(cookieConsent).filter(
// oxlint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
(key) => !allowedConsents.includes(key)
),
[cookieConsent, allowedConsents]
);
const value = useMemo(
() => ({
allowedConsents,
categories: CATEGORIES,
categoriesConsent: cookieConsent,
cookies,
deniedConsents,
destinations,
isDestinationsLoaded,
needConsent,
saveConsent
}),
[
destinations,
isDestinationsLoaded,
needConsent,
allowedConsents,
deniedConsents,
cookieConsent,
saveConsent,
cookies
]
);
return /* @__PURE__ */ jsx(CookieConsentContext.Provider, { value, children });
};
export {
CookieConsentProvider,
useCookieConsent
};