1
- import { type ConsentData } from '@farfetch/blackout-analytics' ;
1
+ import { type ConsentData , utils } from '@farfetch/blackout-analytics' ;
2
+ import { GCM_SHARED_COOKIE_NAME , setCookie } from './cookieUtils.js' ;
2
3
import {
3
4
type GoogleConsentCategoryConfig ,
4
5
type GoogleConsentModeConfig ,
5
6
GoogleConsentType ,
6
7
} from './types.js' ;
7
- import { isEqual , omit } from 'lodash-es' ;
8
+ import { omit } from 'lodash-es' ;
8
9
9
10
/**
10
11
* GoogleConsentMode handles with Google Consent Mode v2.
11
12
*/
12
13
export class GoogleConsentMode {
13
14
private dataLayer ! : string ; // Stores different data layer names
14
- private config ?: GoogleConsentModeConfig ; // Stores default or customized consent category mappings
15
- private configExcludingModeRegionsAndWaitForUpdate ! : Record <
16
- string ,
17
- GoogleConsentCategoryConfig
18
- > ; // exclude not consent properties from config
19
- private lastConsent ?: Record <
20
- string ,
21
- Array < string > | string | number | undefined
22
- > ;
15
+ private configWithConsentOnly ! : Record < string , GoogleConsentCategoryConfig > ; // exclude not consent properties from config
16
+ private consentDataLayerCommands : Array <
17
+ [
18
+ 'consent' ,
19
+ 'default' | 'update' ,
20
+ Record < string , Array < string > | string | number | undefined > | undefined ,
21
+ ]
22
+ > = [ ] ;
23
+ private waitForUpdate ?: number ;
24
+ private regions ?: GoogleConsentModeConfig [ 'regions' ] ;
25
+ private hasConfig : boolean ;
23
26
24
27
constructor (
25
28
dataLayer : string ,
26
29
initConsent : ConsentData | null ,
27
30
config ?: GoogleConsentModeConfig ,
28
31
) {
29
32
this . dataLayer = dataLayer ;
30
- this . config = config ;
33
+
34
+ this . waitForUpdate = config ?. waitForUpdate ;
35
+ this . regions = config ?. regions ;
31
36
32
37
// select only the Google Consent Elements
33
- this . configExcludingModeRegionsAndWaitForUpdate = omit ( this . config || { } , [
38
+ this . configWithConsentOnly = omit ( config || { } , [
34
39
'waitForUpdate' ,
35
40
'regions' ,
36
41
'mode' ,
37
42
] ) ;
38
43
39
- this . loadDefaults ( initConsent ) ;
44
+ this . hasConfig = Object . keys ( this . configWithConsentOnly ) . length > 0 ;
45
+
46
+ this . initialize ( initConsent ) ;
40
47
}
48
+
41
49
/**
42
- * Write google consent default values to dataLayer.
43
- *
44
- * @param initConsent - The init consent data to be set.
50
+ * Tries to load shared consent from cookies if available
51
+ * and writes it to the dataLayer.
52
+ * This method is only supposed to be called if no google
53
+ * consent config was passed.
45
54
*/
46
- private loadDefaults ( initConsent : ConsentData | null ) {
47
- if ( this . config ) {
48
- const initialValue : Record < string , string | number > = { } ;
55
+ private loadSharedConsentFromCookies ( ) {
56
+ const consentModeCookieValue = utils . getCookie ( GCM_SHARED_COOKIE_NAME ) ;
49
57
50
- if ( this . config . waitForUpdate ) {
51
- initialValue [ 'wait_for_update' ] = this . config . waitForUpdate ;
58
+ if ( consentModeCookieValue ) {
59
+ try {
60
+ const values = JSON . parse ( consentModeCookieValue ) ;
61
+
62
+ if ( Array . isArray ( values ) ) {
63
+ values . forEach ( value => {
64
+ const [ consentCommand , command , consent ] = value ;
65
+
66
+ this . write ( consentCommand , command , consent ) ;
67
+ } ) ;
68
+ }
69
+ } catch {
70
+ // Do nothing...
52
71
}
72
+ }
73
+ }
53
74
54
- // Obtain default google consent registry
55
- const consentRegistry = Object . keys (
56
- this . configExcludingModeRegionsAndWaitForUpdate ,
57
- ) . reduce (
58
- ( result , consentKey ) => ( {
59
- ...result ,
60
- [ consentKey ] :
61
- this . configExcludingModeRegionsAndWaitForUpdate [ consentKey ]
62
- ?. default || GoogleConsentType . Denied ,
63
- } ) ,
64
- initialValue ,
65
- ) ;
75
+ /**
76
+ * Loads default values from the configuration and
77
+ * writes them in a cookie for sharing.
78
+ *
79
+ * @param initConsent - The consent data available, which can be null if the user has not yet given consent.
80
+ */
81
+ private loadDefaultsFromConfig ( initConsent : ConsentData | null ) {
82
+ const initialValue : Record < string , string | number > = { } ;
66
83
67
- // Write default consent to data layer
68
- this . write ( 'consent' , 'default' , consentRegistry ) ;
84
+ if ( this . waitForUpdate ) {
85
+ initialValue [ 'wait_for_update' ] = this . waitForUpdate ;
86
+ }
69
87
70
- // write regions to data layer if they exists
71
- this . config . regions ?. forEach ( region => {
88
+ // Obtain default google consent registry
89
+ const consentRegistry = Object . keys ( this . configWithConsentOnly ) . reduce (
90
+ ( result , consentKey ) => ( {
91
+ ...result ,
92
+ [ consentKey ] :
93
+ this . configWithConsentOnly [ consentKey ] ?. default ||
94
+ GoogleConsentType . Denied ,
95
+ } ) ,
96
+ initialValue ,
97
+ ) ;
98
+
99
+ // Write default consent to data layer
100
+ this . write ( 'consent' , 'default' , consentRegistry ) ;
101
+
102
+ // write regions to data layer if they exist
103
+ const regions = this . regions ;
104
+
105
+ if ( regions ) {
106
+ regions . forEach ( region => {
72
107
this . write ( 'consent' , 'default' , region ) ;
73
108
} ) ;
109
+ }
110
+
111
+ this . updateConsent ( initConsent ) ;
74
112
75
- this . updateConsent ( initConsent ) ;
113
+ this . saveConsent ( ) ;
114
+ }
115
+
116
+ /**
117
+ * Try to set consent types with dataLayer. If a valid
118
+ * config was passed, default values for the consent
119
+ * types are used. Else, try to load the commands
120
+ * set from the cookie if it is available.
121
+ *
122
+ * @param initConsent - The consent data available, which can be null if the user has not yet given consent.
123
+ */
124
+ private initialize ( initConsent : ConsentData | null ) {
125
+ if ( this . hasConfig ) {
126
+ this . loadDefaultsFromConfig ( initConsent ) ;
127
+ } else {
128
+ this . loadSharedConsentFromCookies ( ) ;
76
129
}
77
130
}
78
131
79
132
/**
80
- * Update consent.
133
+ * Writes consent updates to the dataLayer
134
+ * by applying the configuration (if any) to
135
+ * the passed consent data.
81
136
*
82
- * @param consentData - The consent data to be set .
137
+ * @param consentData - Consent data obtained from the user or null if not available .
83
138
*/
84
139
updateConsent ( consentData : ConsentData | null ) {
85
- if ( this . config ) {
86
- // Dealing with null or undefined consent values
87
- const safeConsent = consentData || { } ;
88
-
140
+ if ( this . hasConfig && consentData ) {
89
141
// Fill consent value into consent element, using analytics consent categories
90
- const consentRegistry = Object . keys (
91
- this . configExcludingModeRegionsAndWaitForUpdate ,
92
- ) . reduce ( ( result , consentKey ) => {
93
- let consentValue = GoogleConsentType . Denied ;
94
- const consent =
95
- this . configExcludingModeRegionsAndWaitForUpdate [ consentKey ] ;
96
-
97
- if ( consent ) {
98
- // has consent config key
99
-
100
- if ( consent . getConsentValue ) {
101
- // give priority to custom function
102
- consentValue = consent . getConsentValue ( safeConsent ) ;
103
- } else if (
104
- consent ?. categories !== undefined &&
105
- consent . categories . every ( consent => safeConsent [ consent ] )
106
- ) {
107
- // The second option to assign value is by categories list
108
- consentValue = GoogleConsentType . Granted ;
142
+ const consentRegistry = Object . keys ( this . configWithConsentOnly ) . reduce (
143
+ ( result , consentKey ) => {
144
+ let consentValue = GoogleConsentType . Denied ;
145
+ const consent = this . configWithConsentOnly [ consentKey ] ;
146
+
147
+ if ( consent ) {
148
+ // has consent config key
149
+ if ( consent . getConsentValue ) {
150
+ // give priority to custom function
151
+ consentValue = consent . getConsentValue ( consentData ) ;
152
+ } else if (
153
+ consent ?. categories !== undefined &&
154
+ consent . categories . every ( consent => consentData [ consent ] )
155
+ ) {
156
+ // The second option to assign value is by categories list
157
+ consentValue = GoogleConsentType . Granted ;
158
+ }
109
159
}
110
- }
111
160
112
- return {
113
- ...result ,
114
- [ consentKey ] : consentValue ,
115
- } ;
116
- } , { } ) ;
161
+ return {
162
+ ...result ,
163
+ [ consentKey ] : consentValue ,
164
+ } ;
165
+ } ,
166
+ { } ,
167
+ ) ;
117
168
118
169
// Write consent to data layer
119
170
this . write ( 'consent' , 'update' , consentRegistry ) ;
171
+
172
+ this . saveConsent ( ) ;
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Saves calculated google consent mode to a cookie
178
+ * for sharing consent between apps in same
179
+ * domain.
180
+ */
181
+ saveConsent ( ) {
182
+ if ( this . consentDataLayerCommands . length > 0 ) {
183
+ setCookie (
184
+ GCM_SHARED_COOKIE_NAME ,
185
+ JSON . stringify ( this . consentDataLayerCommands ) ,
186
+ ) ;
120
187
}
121
188
}
122
189
@@ -128,11 +195,8 @@ export class GoogleConsentMode {
128
195
* @param consentParams - The consent arguments.
129
196
*/
130
197
private write (
131
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
132
198
consentCommand : 'consent' ,
133
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
134
199
command : 'default' | 'update' ,
135
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
136
200
consentParams :
137
201
| Record < string , Array < string > | string | number | undefined >
138
202
| undefined ,
@@ -141,19 +205,19 @@ export class GoogleConsentMode {
141
205
// that was written to the datalayer, so the parameters added to the function signature are only to
142
206
// avoid mistakes when calling the function.
143
207
144
- if (
145
- this . config &&
146
- typeof window !== 'undefined' &&
147
- consentParams &&
148
- ! isEqual ( this . lastConsent , consentParams )
149
- ) {
208
+ if ( typeof window !== 'undefined' && consentParams ) {
150
209
// @ts -ignore
151
210
window [ this . dataLayer ] = window [ this . dataLayer ] || [ ] ;
152
211
153
212
// @ts -ignore
154
213
// eslint-disable-next-line prefer-rest-params
155
214
window [ this . dataLayer ] . push ( arguments ) ;
156
- this . lastConsent = consentParams ;
215
+
216
+ this . consentDataLayerCommands . push ( [
217
+ consentCommand ,
218
+ command ,
219
+ consentParams ,
220
+ ] ) ;
157
221
}
158
222
}
159
223
}
0 commit comments