@autobusal/providers 1.44.5 → 1.45.0

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.
@@ -27,47 +27,84 @@ declare global {
27
27
 
28
28
  const CONTAINER_ID = 'gtm-container';
29
29
 
30
+ declare global {
31
+ interface Window {
32
+ gtag?: (...args: any[]) => void
33
+ }
34
+ }
35
+
36
+ const CONSENT_TYPES = ['ad_storage', 'ad_user_data', 'ad_personalization', 'analytics_storage'];
37
+
38
+ /**
39
+ * Google Consent Mode v2: the signal Tag Manager and GA4 read before any tag
40
+ * fires. Pushed onto the dataLayer through the gtag() shim, the way Google
41
+ * documents it, so it is in place before gtm.js is even requested.
42
+ */
43
+ const consentSignal = (command: 'default' | 'update', granted: boolean): void => {
44
+ window.dataLayer = window.dataLayer || [];
45
+
46
+ window.gtag = window.gtag || function () {
47
+ // eslint-disable-next-line prefer-rest-params
48
+ window.dataLayer!.push(arguments);
49
+ };
50
+
51
+ const state = Object.fromEntries(CONSENT_TYPES.map(type => [ type, granted ? 'granted' : 'denied' ]));
52
+
53
+ window.gtag('consent', command, command === 'default' ? { ...state, wait_for_update: 500 } : state);
54
+ };
55
+
56
+ /**
57
+ * Claude - 2026-09-25 (Ferjolt: consent, not a notice).
58
+ *
59
+ * NOTHING NON-ESSENTIAL LOADS UNTIL THE VISITOR ACCEPTS. Tag Manager (and
60
+ * GA4 and Microsoft Clarity, which it carries) used to load on every page
61
+ * while the cookie card said "by continuing you accept". Now the loader
62
+ * reads the recorded choice (@autobusal/common CookieNotification/consent):
63
+ * accepted loads the container as before; nothing recorded, or rejected,
64
+ * loads nothing and waits for the CONSENT_EVENT the card fires - so a
65
+ * visitor who accepts mid-visit gets the container then, together with
66
+ * every dataLayer event pushed in the meantime (they queue in the array).
67
+ *
68
+ * Consent Mode is signalled either way, so if the container is ever loaded
69
+ * with consent denied (a tag added later, say) Google's tags behave.
70
+ */
71
+ const CONSENT_COOKIE = 'OBTCookieConsent';
72
+ const CONSENT_EVENT = 'obt:cookie-consent';
73
+
74
+ const storedConsent = (): 'accepted' | 'rejected' | null => {
75
+ const match = document.cookie.match(new RegExp('(?:^|; )' + CONSENT_COOKIE + '=(accepted|rejected)'));
76
+
77
+ return match ? (match[1] as 'accepted' | 'rejected') : null;
78
+ };
79
+
80
+ // Setup renders more than once; the consent signal and the listener must
81
+ // exist exactly once per page, whatever the container element says
82
+ let armed = false;
83
+
30
84
  const analytics = (container: string | null | undefined): void => {
31
- if (!container) {
85
+ if (!container || armed) {
32
86
  return;
33
87
  }
34
88
 
35
- // Guard against a second insert. Two containers on one page double-count
36
- // every tag they fire, and Setup can re-run (a settings refetch, a
37
- // remount) without the page ever reloading.
38
89
  if (document.getElementById(CONTAINER_ID)) {
39
90
  return;
40
91
  }
41
92
 
93
+ armed = true;
94
+
42
95
  window.dataLayer = window.dataLayer || [];
43
- window.dataLayer.push({ 'gtm.start': Date.now(), event: 'gtm.js' });
44
-
45
- /*
46
- * LOADED IN THE FIRST QUIET MOMENT, not while the page races for the
47
- * network.
48
- *
49
- * Edited: Claude - Date: 2026-09-16
50
- *
51
- * Measured with Lighthouse against production on a throttled phone: the
52
- * container and the GA tag it pulls are 287 KB of script - more than this
53
- * app's own entry chunk - fetched in the same window as the bundle, the
54
- * translations and the images, on a connection modelled at 1.6 Mbps. The
55
- * page is no more usable for having them early, and every byte they take
56
- * is one the content is waiting for.
57
- *
58
- * The dataLayer is filled FIRST, above, and GTM reads whatever is already
59
- * in it when it boots - so a page view or a commerce event announced in
60
- * the meantime still arrives. That is the only thing that could have made
61
- * deferring this wrong.
62
- *
63
- * Safari has no requestIdleCallback, so setTimeout is the fallback rather
64
- * than a polyfill: "soon, after the load" is the whole requirement.
65
- */
96
+
97
+ const accepted = storedConsent() === 'accepted';
98
+
99
+ consentSignal('default', accepted);
100
+
66
101
  const load = (): void => {
67
102
  if (document.getElementById(CONTAINER_ID)) {
68
103
  return;
69
104
  }
70
105
 
106
+ window.dataLayer!.push({ 'gtm.start': Date.now(), event: 'gtm.js' });
107
+
71
108
  const script = document.createElement('script');
72
109
 
73
110
  script.id = CONTAINER_ID;
@@ -77,32 +114,6 @@ const analytics = (container: string | null | undefined): void => {
77
114
  document.head.appendChild(script);
78
115
  };
79
116
 
80
- /*
81
- * AFTER THE PAGE HAS LOADED, then in the first quiet moment.
82
- *
83
- * Edited: Claude - Date: 2026-09-16
84
- *
85
- * The idle wait above was not enough on its own, because "idle" arrives
86
- * early on a page that is still downloading: requestIdleCallback fires
87
- * whenever the main thread has a gap, and a phone waiting on the network
88
- * has plenty of gaps. Lighthouse on a throttled phone still found Tag
89
- * Manager and the Google Analytics tag it pulls in - 287 KB between them,
90
- * 131 KB of it never executed - arriving inside the page's own loading
91
- * window, competing for the same connection as the bundle and the images.
92
- *
93
- * Waiting for `load` first puts them after everything the page itself
94
- * asked for. The idle wait then keeps them off the main thread while the
95
- * page settles, with a ceiling so they are never deferred indefinitely.
96
- *
97
- * What this costs is measurement, not function, and it is worth naming: a
98
- * visitor who leaves within a couple of seconds of the page finishing
99
- * loading may not be counted. Everything else is kept. The dataLayer is
100
- * created and filled BEFORE any of this, so page views and commerce
101
- * events announced while the container is on its way are queued and
102
- * processed when it boots, not dropped.
103
- *
104
- * Safari has no requestIdleCallback, so a short timeout stands in for it.
105
- */
106
117
  const whenIdle = (): void => {
107
118
  if (typeof window.requestIdleCallback === 'function') {
108
119
  window.requestIdleCallback(load, { timeout: 3000 });
@@ -111,23 +122,30 @@ const analytics = (container: string | null | undefined): void => {
111
122
  }
112
123
  };
113
124
 
114
- if (document.readyState === 'complete') {
115
- whenIdle();
116
- } else {
117
- window.addEventListener('load', whenIdle, { once: true });
125
+ const start = (): void => {
126
+ if (document.readyState === 'complete') {
127
+ whenIdle();
128
+ } else {
129
+ window.addEventListener('load', whenIdle, { once: true });
130
+ }
131
+ };
132
+
133
+ if (accepted) {
134
+ start();
135
+
136
+ return;
118
137
  }
138
+
139
+ // no choice yet, or rejected: wait for the card
140
+ window.addEventListener(CONSENT_EVENT, (event: Event) => {
141
+ if ((event as CustomEvent<string>).detail === 'accepted') {
142
+ consentSignal('update', true);
143
+
144
+ start();
145
+ }
146
+ });
119
147
  };
120
148
 
121
- /**
122
- * Announce a page view.
123
- *
124
- * A single-page app never reloads, so GTM's own page-load trigger fires
125
- * exactly once per session - every client-side route change after that is
126
- * invisible unless it is announced. The container is expected to listen for
127
- * this `page_view` event and fire GA4 (or anything else) from it.
128
- *
129
- * Safe to call before GTM has finished loading: the push simply queues.
130
- */
131
149
  export const pageview = (path: string, title: string): void => {
132
150
  window.dataLayer = window.dataLayer || [];
133
151
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autobusal/providers",
3
- "version": "1.44.5",
3
+ "version": "1.45.0",
4
4
  "author": "Ferjolt Ozuni",
5
5
  "type": "module",
6
6
  "main": "index.ts",
package/types/agents.ts CHANGED
@@ -20,6 +20,9 @@ export interface AgentData {
20
20
  // Claude - 2026-08-22: the floor of the funds balance for website sales
21
21
  // (0 = prepaid, positive = postpaid down to -that-much)
22
22
  credit_limit?: number
23
+ // Claude - 2026-09-24: the agency has partner API access (the one gate
24
+ // Http\Middleware\ApiConsumer accepts); set by "Enable partner API"
25
+ is_api_consumer?: boolean
23
26
  mobile: string
24
27
  mobile2: string
25
28
  phone: string
package/types/users.ts CHANGED
@@ -60,6 +60,9 @@ export interface ComissionData {
60
60
 
61
61
  export interface ApiData {
62
62
  api: number
63
+ // Claude - 2026-09-24: whether a partner API key exists - the key itself is
64
+ // never sent (Agents\AdminController::get, ApiConsumers\AdminController::get)
65
+ has_token?: boolean
63
66
  bkt: number
64
67
  devpos: number
65
68
  stripe: number