Skip to content

Commit 6fb51f6

Browse files
authored
feat: add general react support (#142)
* feat: add react support * feat: add nextjs support * feat: add example configuration for react cookie manager * feat: move nextjs outside of cookie-manager folder * fix: pnpm lock * feat: update react's reactme * fix: update imports, lint issues and improve readme * feat: add turbo config to prebuild package * chore: remove comments * fix: imports
1 parent e0d66cf commit 6fb51f6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+2305
-320
lines changed

.eslintrc.js

Lines changed: 0 additions & 6 deletions
This file was deleted.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { Service } from './types'
2+
import { GOOGLE_ANALYTICS_EXPIRATION_DAYS } from './constants'
3+
4+
export const loadGoogleAnalytics = (
5+
id: Service['id'],
6+
setServicesInitialized: (bool: boolean) => void
7+
): void => {
8+
function gtag(_key: string, _value: unknown, _config?: { cookie_expires: number }) {
9+
// eslint-disable-next-line prefer-rest-params
10+
window.dataLayer.push(arguments)
11+
}
12+
window.dataLayer = window.dataLayer || []
13+
14+
gtag('js', new Date())
15+
gtag('config', id, { cookie_expires: GOOGLE_ANALYTICS_EXPIRATION_DAYS * 24 * 60 * 60 })
16+
17+
const script = document.createElement('script')
18+
script.src = `https://www.googletagmanager.com/gtag/js?id=${id}`
19+
document.body.appendChild(script)
20+
setServicesInitialized(true)
21+
}
22+
23+
export const removeGoogleAnalytics = (id: Service['id']): void => {
24+
const scripts = Array.from(document.getElementsByTagName('script'))
25+
if (scripts && scripts.length) {
26+
scripts
27+
.find((script) => script?.src === `https://www.googletagmanager.com/gtag/js?id=${id}`)
28+
?.remove()
29+
scripts
30+
.find((script) => script?.src === 'https://www.google-analytics.com/analytics.js')
31+
?.remove()
32+
}
33+
}
34+
35+
export const updatePathGA = (id: Service['id'], path: string): void => {
36+
function gtag(_key: string, _value: unknown, _pagePathObject: { page_path: string }) {
37+
// eslint-disable-next-line prefer-rest-params
38+
window.dataLayer.push(arguments)
39+
}
40+
window.dataLayer = window.dataLayer || []
41+
gtag('config', id, {
42+
page_path: path
43+
})
44+
}

cookie-manager/core/enums.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
export enum SupportedService {
2+
GoogleAnalyticsUniversal = 'googleAnalyticsUniversal',
3+
GoogleAnalytics4 = 'googleAnalytics4',
4+
CustomCookie = 'customNecessaryCookies'
5+
}
6+
17
export enum CookieCategory {
28
Functionality = 'Functionality',
39
Statistics = 'Statistics',
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import type { Service, ServiceCookie } from './types'
2+
3+
export const INITIAL_SHOW_COOKIE_DISCLAIMER: boolean = false
4+
export const INITIAL_CONFIGURED_SERVICES: Service[] = []
5+
export const INITIAL_SERVICES_INITIALIZED: boolean = false
6+
export const INITIAL_NECESSARY_COOKIES: ServiceCookie[] = []
7+
export const INITIAL_ADDITIONAL_COOKIES: ServiceCookie[] = []

cookie-manager/core/services.ts

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import {
2+
GoogleOwnCookies,
3+
SKCM_GA_GOOGLE_ANALYTICS_4_COOKIE,
4+
SKCM_GA_GOOGLE_ANALYTICS_UNIVERSAL_COOKIE
5+
} from './cookieLib'
6+
import { CookieCategory, SupportedService } from './enums'
7+
import type { Service, ServiceCookie, SKCMConfiguration } from './types'
8+
import { deleteCookie, getCookie, setCookie } from './utils'
9+
import { loadGoogleAnalytics, removeGoogleAnalytics } from './clientSideOnly'
10+
import { COOKIE_EXPIRATION_DAYS } from './constants'
11+
12+
export function initializeServices(
13+
servicesInitialized: boolean,
14+
configuredServices: Service[],
15+
setServicesInitialized: (bool: boolean) => void
16+
): void {
17+
if (!servicesInitialized) {
18+
const googleAnalyticsUniversalConfig = configuredServices.find(
19+
({ type }) => type === SupportedService.GoogleAnalyticsUniversal
20+
)
21+
const googleAnalytics4Config = configuredServices.find(
22+
({ type }) => type === SupportedService.GoogleAnalytics4
23+
)
24+
if (googleAnalyticsUniversalConfig?.enabled) {
25+
loadGoogleAnalytics(googleAnalyticsUniversalConfig.id, setServicesInitialized)
26+
} else {
27+
if (googleAnalytics4Config?.enabled) {
28+
loadGoogleAnalytics(googleAnalytics4Config.id, setServicesInitialized)
29+
}
30+
}
31+
}
32+
}
33+
34+
interface InitConfiguredServicesArgs {
35+
services: SKCMConfiguration['services']
36+
onConfiguredServicesInitialized: (
37+
configuredServices: Service[],
38+
necessaryCookies: ServiceCookie[]
39+
) => void
40+
}
41+
export function initializeConfiguredServices({
42+
services: {
43+
googleAnalyticsUniversalId,
44+
googleAnalytics4Id,
45+
adCookiesEnabled,
46+
customNecessaryCookies
47+
} = {},
48+
onConfiguredServicesInitialized
49+
}: InitConfiguredServicesArgs): void {
50+
let _configuredServices: Service[] = []
51+
let _necessaryCookies: ServiceCookie[] = []
52+
if (googleAnalyticsUniversalId) {
53+
_configuredServices.push({
54+
type: SupportedService.GoogleAnalyticsUniversal,
55+
id: googleAnalyticsUniversalId,
56+
enabled: getCookie(SKCM_GA_GOOGLE_ANALYTICS_UNIVERSAL_COOKIE?.name) === 'true',
57+
cookies: GoogleOwnCookies.GoogleAnalyticsUniversal
58+
})
59+
_necessaryCookies.push(SKCM_GA_GOOGLE_ANALYTICS_UNIVERSAL_COOKIE)
60+
}
61+
if (googleAnalytics4Id) {
62+
_configuredServices.push({
63+
type: SupportedService.GoogleAnalytics4,
64+
id: googleAnalytics4Id,
65+
enabled: getCookie(SKCM_GA_GOOGLE_ANALYTICS_4_COOKIE?.name) === 'true',
66+
cookies: GoogleOwnCookies.GoogleAnalytics4
67+
})
68+
_necessaryCookies.push(SKCM_GA_GOOGLE_ANALYTICS_4_COOKIE)
69+
}
70+
if (customNecessaryCookies) {
71+
_necessaryCookies = [..._necessaryCookies, ...customNecessaryCookies]
72+
}
73+
if (!adCookiesEnabled) {
74+
const filteredCookies = _configuredServices.map((service) => ({
75+
...service,
76+
cookies: service?.cookies?.filter((cookie) => cookie?.category !== CookieCategory.Advertising)
77+
}))
78+
_configuredServices = filteredCookies
79+
}
80+
81+
onConfiguredServicesInitialized?.(_configuredServices, _necessaryCookies)
82+
}
83+
84+
export function stopCoreServices(
85+
configuredServices: Service[],
86+
removeAdditionalCookiesCb: () => void,
87+
setServicesInitialized: (bool: boolean) => void
88+
): void {
89+
const googleAnalyticsUniversalConfig = configuredServices?.find(
90+
({ type }) => type === SupportedService.GoogleAnalyticsUniversal
91+
)
92+
const googleAnalytics4Config = configuredServices?.find(
93+
({ type }) => type === SupportedService.GoogleAnalytics4
94+
)
95+
removeGoogleAnalytics(googleAnalytics4Config?.id)
96+
removeGoogleAnalytics(googleAnalyticsUniversalConfig?.id)
97+
removeAdditionalCookiesCb()
98+
99+
setServicesInitialized?.(false)
100+
}
101+
102+
export function setNecessaryCookies(
103+
value: 'true' | 'false',
104+
configuredServices: Service[],
105+
necessaryCookies: ServiceCookie[],
106+
setConfiguredServices: (services: Service[]) => void
107+
): void {
108+
const SKCM_NECESSARY_COOKIES: string[] = [
109+
SKCM_GA_GOOGLE_ANALYTICS_UNIVERSAL_COOKIE?.name,
110+
SKCM_GA_GOOGLE_ANALYTICS_4_COOKIE?.name
111+
]
112+
// set cookies
113+
const neededCookies =
114+
necessaryCookies?.filter(
115+
(cookie) => cookie?.showDisclaimerIfMissing && SKCM_NECESSARY_COOKIES.includes(cookie?.name)
116+
) ?? []
117+
for (let i = 0; i < neededCookies?.length; i++) {
118+
setCookie(neededCookies[i]?.name, value, COOKIE_EXPIRATION_DAYS)
119+
}
120+
// enable services
121+
const _configuredServices = configuredServices?.map((service) => ({
122+
...service,
123+
enabled: value === 'true'
124+
}))
125+
setConfiguredServices(_configuredServices)
126+
}
127+
128+
export function clearAdditionalCookies(necessaryCookies: ServiceCookie[]): void {
129+
const _necessaryCookies = necessaryCookies.map((cookie) => cookie.name)
130+
document.cookie
131+
?.split('; ')
132+
?.map((cookie) => cookie.split('=')[0])
133+
.forEach((cookie) => {
134+
if (!_necessaryCookies.includes(cookie)) {
135+
deleteCookie(cookie)
136+
}
137+
})
138+
}
139+
140+
// Check user has all needed necessary cookies already set
141+
export function checkAllRequiredCookies(necessaryCookies: ServiceCookie[]): boolean {
142+
const neededCookies = necessaryCookies?.filter((cookie) => cookie?.showDisclaimerIfMissing) ?? []
143+
for (let i = 0; i < neededCookies?.length; i++) {
144+
if (!getCookie(neededCookies[i].name)?.length) {
145+
return false
146+
}
147+
}
148+
return true
149+
}

cookie-manager/core/types.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
1-
import type { CookieCategory } from './enums'
2-
3-
export enum SupportedService {
4-
GoogleAnalyticsUniversal = 'googleAnalyticsUniversal',
5-
GoogleAnalytics4 = 'googleAnalytics4',
6-
CustomCookie = 'customNecessaryCookies'
7-
}
1+
import type { CookieCategory, SupportedService } from './enums'
82

93
export type Service = {
104
type: SupportedService

cookie-manager/core/utils.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
import type { Service, ServiceCookie, Theme } from './types'
12
import { DEFAULT_THEME_COLORS } from './constants'
2-
import type { Theme } from './types'
33

44
/*
55
* General utils for managing cookies in Typescript.
@@ -13,13 +13,14 @@ export function getCookie(name: string): string | undefined {
1313
return parts?.pop()?.split(';')?.shift() ?? undefined
1414
}
1515
}
16+
1617
export const setCookie = (name: string, val: string, expDays: number): void => {
1718
const date = new Date()
1819
const value = val
1920
date.setTime(date.getTime() + expDays * 24 * 60 * 60 * 1000)
2021
document.cookie = name + '=' + value + '; expires=' + date.toUTCString() + '; path=/'
2122
}
22-
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
23+
2324
export function deleteCookie(name: string) {
2425
const date = new Date()
2526
date.setTime(date.getTime() + -1 * 24 * 60 * 60 * 1000)
@@ -35,3 +36,12 @@ export const getInlineStyle = (theme: Theme = {}): string => {
3536
const mergedTheme = { ...DEFAULT_THEME_COLORS, ...theme }
3637
return formatStyles(mergedTheme)
3738
}
39+
40+
export function getAdditionalCookiesFromConfiguredServices(services: Service[]): ServiceCookie[] {
41+
return services.reduce((accumulator, service) => {
42+
const cookiesName = accumulator.map((cookie) => cookie.name)
43+
const serviceCookies =
44+
service?.cookies?.filter((cookie) => !cookiesName.includes(cookie.name)) ?? []
45+
return accumulator.concat(serviceCookies)
46+
}, [] as ServiceCookie[])
47+
}

cookie-manager/react/.eslintrc.cjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ module.exports = {
1212
}
1313
},
1414
parserOptions: {
15-
project: true
15+
project: true,
16+
ecmaVersion: 8
1617
}
1718
}

0 commit comments

Comments
 (0)