@autobusal/providers 1.45.2 → 1.45.4

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.
@@ -132,20 +132,73 @@ const analytics = (container: string | null | undefined): void => {
132
132
 
133
133
  if (accepted) {
134
134
  start();
135
-
136
- return;
137
135
  }
138
136
 
139
- // no choice yet, or rejected: wait for the card
137
+ /*
138
+ * Claude - 2026-09-25 (audit): consent can be WITHDRAWN. The listener used
139
+ * to be registered only when nothing was accepted at load, so a visitor
140
+ * who had accepted and later rejected from the card kept being measured
141
+ * for the rest of the visit (and their _ga/_clck cookies stayed). Now it
142
+ * is always registered (still once per page - `armed` above), and pushes a
143
+ * Consent Mode update only when the state actually changes, so repeated
144
+ * events never duplicate the dataLayer entries.
145
+ */
146
+ let granted = accepted;
147
+
140
148
  window.addEventListener(CONSENT_EVENT, (event: Event) => {
141
- if ((event as CustomEvent<string>).detail === 'accepted') {
142
- consentSignal('update', true);
149
+ const choice = (event as CustomEvent<string>).detail;
150
+
151
+ if (choice === 'accepted') {
152
+ if (!granted) {
153
+ granted = true;
154
+
155
+ consentSignal('update', true);
156
+ }
143
157
 
144
158
  start();
159
+ } else if (choice === 'rejected' && granted) {
160
+ granted = false;
161
+
162
+ // a loaded container cannot be unloaded; denied Consent Mode makes
163
+ // Google's tags stop storing and sending identifiers from here on
164
+ consentSignal('update', false);
165
+
166
+ expireAnalyticsCookies();
145
167
  }
146
168
  });
147
169
  };
148
170
 
171
+ /**
172
+ * Claude - 2026-09-25 (audit): drop the GA4 and Clarity cookies on rejection.
173
+ * They are set on the current host or on its registrable parent (GA's
174
+ * cookie_domain 'auto' picks the highest one it can write), so both are
175
+ * expired, with and without a leading dot, which is what a browser needs to
176
+ * match the cookie that was written.
177
+ */
178
+ const ANALYTICS_COOKIE = /^(_ga|_ga_.+|_gid|_clck|_clsk)$/;
179
+
180
+ const expireAnalyticsCookies = (): void => {
181
+ const names = document.cookie
182
+ .split(';')
183
+ .map(pair => pair.split('=')[0].trim())
184
+ .filter(name => ANALYTICS_COOKIE.test(name));
185
+
186
+ if (names.length === 0) {
187
+ return;
188
+ }
189
+
190
+ const host = window.location.hostname;
191
+ const parts = host.split('.');
192
+ const parent = parts.length > 2 ? parts.slice(-2).join('.') : host;
193
+ const domains = Array.from(new Set([ '', host, `.${ host }`, parent, `.${ parent }` ]));
194
+
195
+ names.forEach(name => {
196
+ domains.forEach(domain => {
197
+ document.cookie = `${ name }=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/${ domain ? `; domain=${ domain }` : '' }`;
198
+ });
199
+ });
200
+ };
201
+
149
202
  export const pageview = (path: string, title: string): void => {
150
203
  window.dataLayer = window.dataLayer || [];
151
204
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autobusal/providers",
3
- "version": "1.45.2",
3
+ "version": "1.45.4",
4
4
  "author": "Ferjolt Ozuni",
5
5
  "type": "module",
6
6
  "main": "index.ts",
package/types/routes.ts CHANGED
@@ -34,6 +34,8 @@ export interface RouteData {
34
34
  language: string
35
35
  template: string
36
36
  searchable: number
37
+ // Claude - 2026-09-26: consent to publish in the GTFS feed (1 by default)
38
+ gtfs?: number | boolean
37
39
  information_en: string
38
40
  information_sq: string
39
41