react-marketing-tools 1.0.0-alpha.2 → 1.0.0-alpha.3

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.
@@ -1,154 +1,258 @@
1
- //#region lib/destinations/gtm.ts
2
- var e = /^GTM-[A-Z0-9]+$/, t = () => window.dataLayer ??= [], n = (e) => Array.from(document.scripts).some((t) => t.src === e), r = ({ src: e, nonce: t }) => {
1
+ //#region lib/core/consent.ts
2
+ var e = [
3
+ "analytics",
4
+ "ads",
5
+ "adUserData",
6
+ "adPersonalization"
7
+ ], t = (e) => e === "granted" || e === "denied", n = ({ consent: e, gpc: t }) => {
8
+ let n = t ? "denied" : e;
9
+ return {
10
+ analytics: e,
11
+ ads: n,
12
+ adUserData: n,
13
+ adPersonalization: n
14
+ };
15
+ }, r = (e, t) => ({
16
+ analytics: t.analytics ?? e.analytics,
17
+ ads: t.ads ?? e.ads,
18
+ adUserData: t.adUserData ?? t.ads ?? e.adUserData,
19
+ adPersonalization: t.adPersonalization ?? t.ads ?? e.adPersonalization
20
+ }), i = (e) => ({
21
+ analytics_storage: e.analytics,
22
+ ad_storage: e.ads,
23
+ ad_user_data: e.adUserData,
24
+ ad_personalization: e.adPersonalization
25
+ }), a = (e) => Object.values(e).includes("denied"), o = () => typeof navigator < "u" && navigator.globalPrivacyControl === !0, s = () => window.dataLayer ??= [], c = () => Array.from(document.scripts).some((e) => URL.canParse(e.src) && new URL(e.src).pathname.endsWith("/gtag/js")), l = () => window.gtag ??= function() {
26
+ s().push(arguments);
27
+ }, u = ({ consent: e, waitForUpdate: t }) => {
28
+ l()("consent", "default", {
29
+ ...i(e),
30
+ ...a(e) ? { wait_for_update: t } : {}
31
+ });
32
+ }, d = (e) => {
33
+ l()("consent", "update", i(e));
34
+ }, f = (e, t) => {
35
+ let n = URL.canParse(e) ? new URL(e) : void 0;
36
+ if (n?.protocol !== "https:") throw Error(`[react-marketing-tools] ${t} must be an https:// URL (received ${JSON.stringify(e)}).`);
37
+ return n;
38
+ }, p = (e) => Array.from(document.scripts).some((t) => t.src === e), m = ({ src: e, nonce: t }) => {
3
39
  let n = document.createElement("script");
4
40
  n.async = !0, n.src = e, t && n.setAttribute("nonce", t), document.head.append(n);
5
- }, i = ({ containerId: i, loadScript: a = !0, nonce: o }) => {
6
- if (!e.test(i)) throw Error(`[react-marketing-tools] gtm.containerId must look like "GTM-XXXXXXX" (received ${JSON.stringify(i)}).`);
7
- let s = `https://www.googletagmanager.com/gtm.js?id=${i}`;
41
+ }, h = /^G-[A-Z0-9]+$/, g = ({ measurementId: e, pageViews: t = "auto", loadScript: n = !0, serverContainerUrl: r, waitForUpdate: i = 500, nonce: a }) => {
42
+ if (!h.test(e)) throw Error(`[react-marketing-tools] ga4.measurementId must look like "G-XXXXXXX" (received ${JSON.stringify(e)}).`);
43
+ r !== void 0 && f(r, "ga4.serverContainerUrl");
44
+ let o = `https://www.googletagmanager.com/gtag/js?id=${e}`, s = (e, t) => r ? {
45
+ ...e,
46
+ event_id: t
47
+ } : e;
48
+ return {
49
+ name: "ga4",
50
+ start({ consent: s }) {
51
+ u({
52
+ consent: s,
53
+ waitForUpdate: i
54
+ }), c() || (l()("js", /* @__PURE__ */ new Date()), n && m({
55
+ src: o,
56
+ nonce: a
57
+ })), l()("config", e, {
58
+ ...t === "manual" ? { send_page_view: !1 } : {},
59
+ ...r ? { server_container_url: r } : {}
60
+ });
61
+ },
62
+ track({ name: e, params: t, eventId: n }) {
63
+ l()("event", e, s(t, n));
64
+ },
65
+ consent(e) {
66
+ d(e);
67
+ },
68
+ page({ params: e, eventId: n }) {
69
+ t === "manual" && l()("event", "page_view", s(e, n));
70
+ },
71
+ identify({ userId: e }) {
72
+ l()("set", { user_id: e });
73
+ },
74
+ reset() {
75
+ l()("set", { user_id: null });
76
+ }
77
+ };
78
+ }, _ = /^GTM-[A-Z0-9]+$/, v = (e, t) => {
79
+ let n = f(t ?? "https://www.googletagmanager.com/gtm.js", "gtm.scriptUrl");
80
+ return n.searchParams.set("id", e), n.href;
81
+ }, y = ({ containerId: e, loadScript: t = !0, scriptUrl: n, waitForUpdate: r = 500, nonce: i }) => {
82
+ if (!_.test(e)) throw Error(`[react-marketing-tools] gtm.containerId must look like "GTM-XXXXXXX" (received ${JSON.stringify(e)}).`);
83
+ let a = v(e, n);
8
84
  return {
9
85
  name: "gtm",
10
- start() {
11
- let e = t();
12
- a && !n(s) && (e.push({
86
+ start({ consent: e }) {
87
+ u({
88
+ consent: e,
89
+ waitForUpdate: r
90
+ }), t && !p(a) && (s().push({
13
91
  "gtm.start": Date.now(),
14
92
  event: "gtm.js"
15
- }), r({
16
- src: s,
17
- nonce: o
93
+ }), m({
94
+ src: a,
95
+ nonce: i
18
96
  }));
19
97
  },
20
- track({ name: e, params: n, eventId: r }) {
21
- t().push({
22
- ...n,
98
+ track({ name: e, params: t, eventId: n }) {
99
+ s().push({
100
+ ...t,
23
101
  event: e,
24
- event_id: r
102
+ event_id: n
25
103
  });
26
104
  },
105
+ consent(e) {
106
+ d(e);
107
+ },
27
108
  identify({ userId: e }) {
28
- t().push({
109
+ s().push({
29
110
  event: "identify",
30
111
  user_id: e
31
112
  });
32
113
  },
33
114
  reset() {
34
- t().push({
115
+ s().push({
35
116
  event: "reset",
36
117
  user_id: void 0
37
118
  });
38
119
  }
39
120
  };
40
- }, a = class extends Error {
121
+ }, b = class extends Error {
41
122
  code;
42
123
  constructor(e, t, n) {
43
124
  super(`[react-marketing-tools] ${t}`, n), this.name = "AnalyticsError", this.code = e;
44
125
  }
45
- }, o = /^[A-Za-z][A-Za-z0-9_]{0,39}$/, s = [
126
+ }, x = /^[A-Za-z][A-Za-z0-9_]{0,39}$/, S = [
46
127
  "google_",
47
128
  "ga_",
48
129
  "firebase_"
49
- ], c = 25, l = 100, u = {
130
+ ], C = 25, w = 100, T = {
50
131
  page_location: 1e3,
51
132
  page_referrer: 420,
52
133
  page_title: 300
53
- }, d = [
134
+ }, E = [
54
135
  "email",
55
136
  "phone",
56
137
  "first_name",
57
138
  "last_name",
58
139
  "address",
59
140
  "password"
60
- ], f = String.raw`[\w.+-]+(?:@|%40)[\w-]+(?:\.[\w-]+)+`, p = "[redacted]", m = (e) => {
61
- if (!o.test(e)) return "must start with a letter, contain only letters, digits and underscores, and be at most 40 characters";
62
- let t = s.find((t) => e.toLowerCase().startsWith(t));
141
+ ], D = String.raw`[\w.+-]+(?:@|%40)[\w-]+(?:\.[\w-]+)+`, O = "[redacted]", k = (e) => {
142
+ if (!x.test(e)) return "must start with a letter, contain only letters, digits and underscores, and be at most 40 characters";
143
+ let t = S.find((t) => e.toLowerCase().startsWith(t));
63
144
  return t && `must not start with the reserved prefix "${t}"`;
64
- }, h = (e) => {
65
- let t = m(e);
145
+ }, A = (e) => {
146
+ let t = k(e);
66
147
  return t && `event name "${e}" ${t}`;
67
- }, g = (e) => {
148
+ }, j = (e) => {
68
149
  let t = Object.keys(e), n = t.flatMap((t) => {
69
- let n = m(t);
150
+ let n = k(t);
70
151
  if (n) return [`param "${t}" ${n}`];
71
- let r = e[t], i = u[t] ?? l;
152
+ let r = e[t], i = T[t] ?? w;
72
153
  return typeof r == "string" && r.length > i ? [`param "${t}" is longer than ${i} characters`] : [];
73
154
  });
74
- return t.length > c ? [`has ${t.length} params; the limit is ${c}`, ...n] : n;
75
- }, _ = (e) => new RegExp(f, "i").test(e), v = (e, t) => (typeof t == "string" || typeof t == "number") && d.some((t) => e.toLowerCase().includes(t)) ? p : typeof t == "string" ? t.replace(new RegExp(f, "gi"), p) : t, y = (e) => {
155
+ return t.length > C ? [`has ${t.length} params; the limit is ${C}`, ...n] : n;
156
+ }, M = (e) => new RegExp(D, "i").test(e), N = (e, t) => (typeof t == "string" || typeof t == "number") && E.some((t) => e.toLowerCase().includes(t)) ? O : typeof t == "string" ? t.replace(new RegExp(D, "gi"), O) : t, P = (e) => {
76
157
  let t = Object.entries(e).map(([e, t]) => [
77
158
  e,
78
159
  t,
79
- v(e, t)
160
+ N(e, t)
80
161
  ]);
81
162
  return {
82
163
  params: Object.fromEntries(t.map(([e, , t]) => [e, t])),
83
164
  redactedKeys: t.filter(([, e, t]) => t !== e).map(([e]) => e)
84
165
  };
85
- }, b = () => typeof window < "u", x = () => typeof crypto.randomUUID == "function" ? crypto.randomUUID() : "10000000-1000-4000-8000-100000000000".replace(/[018]/g, (e) => (Number(e) ^ crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(1))[0] & 15 >> Number(e) / 4).toString(16)), S = (e) => {
166
+ }, F = () => typeof window < "u", I = () => typeof crypto.randomUUID == "function" ? crypto.randomUUID() : "10000000-1000-4000-8000-100000000000".replace(/[018]/g, (e) => (Number(e) ^ crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(1))[0] & 15 >> Number(e) / 4).toString(16)), L = (e) => {
86
167
  if (e.consent !== "granted" && e.consent !== "denied") throw Error(`[react-marketing-tools] createAnalytics: "consent" must be 'granted' or 'denied' (received ${JSON.stringify(e.consent)}).`);
87
- }, C = (e) => {
88
- S(e);
89
- let { debug: t = !1, onError: n = console.error } = e, r = [...e.gtm ? [i({
90
- ...e.gtm,
91
- nonce: e.nonce
92
- })] : [], ...e.destinations ?? []], o = [], s = !1, c = (e) => {
93
- if (t) throw e;
94
- n(e);
95
- }, l = (e) => {
96
- for (let t of r) try {
168
+ }, R = (i) => {
169
+ L(i);
170
+ let { debug: a = !1, onError: s = console.error, respectGpc: c = !0 } = i, l = n({
171
+ consent: i.consent,
172
+ gpc: c && o()
173
+ }), u = l, d = [
174
+ ...i.gtm ? [y({
175
+ ...i.gtm,
176
+ nonce: i.nonce
177
+ })] : [],
178
+ ...i.ga4 ? [g({
179
+ ...i.ga4,
180
+ nonce: i.nonce
181
+ })] : [],
182
+ ...i.destinations ?? []
183
+ ], f = [], p = !1, m = (e) => {
184
+ if (a) throw e;
185
+ s(e);
186
+ }, h = (e) => {
187
+ for (let t of d) try {
97
188
  e(t);
98
189
  } catch (e) {
99
- n(new a("destination_failed", `destination "${t.name}" failed`, { cause: e }));
190
+ s(new b("destination_failed", `destination "${t.name}" failed`, { cause: e }));
100
191
  }
101
- }, u = (e) => {
102
- s ? l(e) : o.push(e);
103
- }, d = (e, t) => {
104
- let n = h(e);
192
+ }, _ = (e) => {
193
+ p ? h(e) : f.push(e);
194
+ }, v = (e, t) => {
195
+ let n = A(e);
105
196
  if (n) {
106
- c(new a("invalid_event", n));
197
+ m(new b("invalid_event", n));
107
198
  return;
108
199
  }
109
- for (let n of g(t)) c(new a("invalid_param", `event "${e}" ${n}`));
110
- let { params: r, redactedKeys: i } = y(t);
111
- return i.length > 0 && c(new a("pii_redacted", `event "${e}": personal data redacted from ${i.join(", ")}`)), {
200
+ for (let n of j(t)) m(new b("invalid_param", `event "${e}" ${n}`));
201
+ let { params: r, redactedKeys: i } = P(t);
202
+ return i.length > 0 && m(new b("pii_redacted", `event "${e}": personal data redacted from ${i.join(", ")}`)), {
112
203
  name: e,
113
204
  params: r,
114
- eventId: x(),
205
+ eventId: I(),
115
206
  timestamp: Date.now()
116
207
  };
117
208
  };
118
209
  return {
119
210
  start() {
120
- !s && b() && (s = !0, l((e) => e.start()), o.splice(0).forEach(l));
211
+ !p && F() && (p = !0, h((e) => e.start({ consent: l })), f.splice(0).forEach(h));
121
212
  },
122
213
  track(e, t = {}) {
123
- if (!b()) return;
124
- let n = d(e, t);
125
- n && u((e) => e.track(n));
214
+ if (!F()) return;
215
+ let n = v(e, t);
216
+ n && _((e) => e.track(n));
126
217
  },
127
218
  page(e = {}) {
128
- if (!b()) return;
129
- let t = d("page_view", {
219
+ if (!F()) return;
220
+ let t = v("page_view", {
130
221
  page_location: location.href,
131
222
  page_title: document.title,
132
223
  ...e
133
224
  });
134
- t && u((e) => e.page ? e.page(t) : e.track(t));
225
+ t && _((e) => e.page ? e.page(t) : e.track(t));
135
226
  },
136
227
  identify(e, t = {}) {
137
- if (b()) {
138
- if (!e || _(e)) {
139
- c(new a("invalid_user_id", "identify() needs a non-empty user id that is not personal data such as an email address"));
228
+ if (F()) {
229
+ if (!e || M(e)) {
230
+ m(new b("invalid_user_id", "identify() needs a non-empty user id that is not personal data such as an email address"));
140
231
  return;
141
232
  }
142
- u((n) => n.identify?.({
233
+ _((n) => n.identify?.({
143
234
  userId: e,
144
235
  traits: t
145
236
  }));
146
237
  }
147
238
  },
148
239
  reset() {
149
- b() && u((e) => e.reset?.());
240
+ F() && _((e) => e.reset?.());
241
+ },
242
+ consent: {
243
+ update(n) {
244
+ if (!F()) return;
245
+ let i = Object.entries(n).filter(([n, r]) => r !== void 0 && !(e.includes(n) && t(r)));
246
+ if (i.length > 0) {
247
+ m(new b("invalid_consent", `consent.update() ignored: ${i.map(([e, t]) => `${e}=${JSON.stringify(t)}`).join(", ")}. Use analytics, ads, adUserData or adPersonalization with 'granted' or 'denied'.`));
248
+ return;
249
+ }
250
+ let a = r(u, n);
251
+ u = a, _((e) => e.consent?.(a));
252
+ },
253
+ get: () => u
150
254
  }
151
255
  };
152
256
  };
153
257
  //#endregion
154
- export { a as n, C as t };
258
+ export { b as n, R as t };
@@ -0,0 +1,20 @@
1
+ import type { ConsentState, ConsentStatus, ConsentUpdate } from './types.js';
2
+ export declare const CONSENT_KEYS: readonly (keyof ConsentState)[];
3
+ export declare const isConsentStatus: (value: unknown) => value is ConsentStatus;
4
+ /** Every signal follows `consent`, except that Global Privacy Control (an ad opt-out) denies the advertising signals. */
5
+ export declare const initialConsentState: ({ consent, gpc, }: {
6
+ consent: ConsentStatus;
7
+ gpc: boolean;
8
+ }) => ConsentState;
9
+ /** `ads` also sets `adUserData` and `adPersonalization`, unless the update gives them explicitly (e.g. TCF-style CMPs). */
10
+ export declare const applyConsentUpdate: (state: ConsentState, update: ConsentUpdate) => ConsentState;
11
+ /** Google Consent Mode v2 signals. */
12
+ export declare const toGoogleConsent: (state: ConsentState) => {
13
+ analytics_storage: ConsentStatus;
14
+ ad_storage: ConsentStatus;
15
+ ad_user_data: ConsentStatus;
16
+ ad_personalization: ConsentStatus;
17
+ };
18
+ export declare const hasDeniedSignal: (state: ConsentState) => boolean;
19
+ /** Global Privacy Control: a browser-level "do not sell or share" signal. Absent in browsers that don't support it. */
20
+ export declare const readGlobalPrivacyControl: () => boolean;
@@ -1,4 +1,4 @@
1
- export type AnalyticsErrorCode = 'invalid_event' | 'invalid_param' | 'invalid_user_id' | 'pii_redacted' | 'destination_failed';
1
+ export type AnalyticsErrorCode = 'invalid_event' | 'invalid_param' | 'invalid_user_id' | 'invalid_consent' | 'pii_redacted' | 'destination_failed';
2
2
  /** Every problem the library reports to `onError`, or throws when `debug` is on. */
3
3
  export declare class AnalyticsError extends Error {
4
4
  readonly code: AnalyticsErrorCode;
@@ -1,5 +1,16 @@
1
1
  import type { AnalyticsError } from './errors.js';
2
2
  export type ConsentStatus = 'granted' | 'denied';
3
+ /**
4
+ * The visitor's consent, per purpose. `ads` covers advertising storage; `adUserData` (sending user data to ad platforms)
5
+ * and `adPersonalization` follow `ads` unless set explicitly.
6
+ */
7
+ export type ConsentState = {
8
+ analytics: ConsentStatus;
9
+ ads: ConsentStatus;
10
+ adUserData: ConsentStatus;
11
+ adPersonalization: ConsentStatus;
12
+ };
13
+ export type ConsentUpdate = Partial<ConsentState>;
3
14
  /** Event parameters, passed to every destination as-is. */
4
15
  export type EventParams = Record<string, unknown>;
5
16
  export type AnalyticsEvent = {
@@ -27,9 +38,13 @@ export type Identity = {
27
38
  /** Somewhere events are sent. Built-in destinations are configured by key; custom ones go in `destinations`. */
28
39
  export type Destination = {
29
40
  name: string;
30
- /** Called once, in the browser, by `analytics.start()`. */
31
- start(): void;
41
+ /** Called once, in the browser, by `analytics.start()`, with the consent state the page started with. */
42
+ start(context: {
43
+ consent: ConsentState;
44
+ }): void;
32
45
  track(event: AnalyticsEvent): void;
46
+ /** Receives every consent change, in order with events. */
47
+ consent?(state: ConsentState): void;
33
48
  /** Receives `page_view` events. Destinations without it get them through `track`. */
34
49
  page?(event: AnalyticsEvent): void;
35
50
  identify?(identity: Identity): void;
@@ -41,11 +56,36 @@ export type GtmConfig = {
41
56
  containerId: string;
42
57
  /** Set to `false` when the page already includes the GTM snippet. Defaults to `true`. */
43
58
  loadScript?: boolean;
59
+ /** Load `gtm.js` from this https URL instead of googletagmanager.com, e.g. your server-side GTM domain. */
60
+ scriptUrl?: string;
61
+ /** Milliseconds tags wait for a consent update when a Consent Mode signal starts denied. Defaults to 500. */
62
+ waitForUpdate?: number;
63
+ };
64
+ export type Ga4Config = {
65
+ /** Google Analytics 4 measurement ID, e.g. `G-XXXXXXX`. */
66
+ measurementId: string;
67
+ /**
68
+ * `'auto'` (default): GA4 records page views itself, including history changes when Enhanced Measurement is on.
69
+ * `'manual'`: only `analytics.page()` sends page views.
70
+ */
71
+ pageViews?: 'auto' | 'manual';
72
+ /** Set to `false` when the page already loads gtag.js. Defaults to `true`. */
73
+ loadScript?: boolean;
74
+ /** Your server-side GTM URL (https). Hits go there instead of Google, and events carry `event_id`. */
75
+ serverContainerUrl?: string;
76
+ /** Milliseconds tags wait for a consent update when a Consent Mode signal starts denied. Defaults to 500. */
77
+ waitForUpdate?: number;
44
78
  };
45
79
  export type AnalyticsConfig = {
46
- /** Initial consent state. Required, so every site makes an explicit choice. */
80
+ /** Initial consent for every purpose. Required, so every site makes an explicit choice. */
47
81
  consent: ConsentStatus;
82
+ /**
83
+ * Honour the browser's Global Privacy Control signal by starting the advertising signals denied. Defaults to `true`.
84
+ * An explicit `analytics.consent.update()` still wins.
85
+ */
86
+ respectGpc?: boolean;
48
87
  gtm?: GtmConfig;
88
+ ga4?: Ga4Config;
49
89
  /** Custom destinations, in addition to the built-in ones. */
50
90
  destinations?: Destination[];
51
91
  /** Content-Security-Policy nonce added to every script the library injects. */
@@ -66,4 +106,9 @@ export type Analytics = {
66
106
  identify(userId: string, traits?: IdentityTraits): void;
67
107
  /** Forgets the identified user, e.g. on logout. */
68
108
  reset(): void;
109
+ consent: {
110
+ /** Records the visitor's choice, e.g. from your consent banner. Omitted purposes keep their current state. */
111
+ update(update: ConsentUpdate): void;
112
+ get(): ConsentState;
113
+ };
69
114
  };
package/dist/core.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { createAnalytics } from './core/createAnalytics.js';
2
2
  export { AnalyticsError } from './core/errors.js';
3
3
  export type { AnalyticsErrorCode } from './core/errors.js';
4
- export type { Analytics, AnalyticsConfig, AnalyticsEvent, ConsentStatus, Destination, EventParams, GtmConfig, Identity, IdentityTraits, } from './core/types.js';
4
+ export type { Analytics, AnalyticsConfig, AnalyticsEvent, ConsentState, ConsentStatus, ConsentUpdate, Destination, EventParams, Ga4Config, GtmConfig, Identity, IdentityTraits, } from './core/types.js';
@@ -0,0 +1,6 @@
1
+ import type { Destination, Ga4Config } from '../core/types.js';
2
+ type Ga4DestinationOptions = Ga4Config & {
3
+ nonce?: string;
4
+ };
5
+ export declare const createGa4Destination: ({ measurementId, pageViews, loadScript, serverContainerUrl, waitForUpdate, nonce, }: Ga4DestinationOptions) => Destination;
6
+ export {};
@@ -0,0 +1,22 @@
1
+ import type { ConsentState } from '../core/types.js';
2
+ type Gtag = (...args: unknown[]) => void;
3
+ export declare const dataLayer: () => unknown[];
4
+ /**
5
+ * Whether the page already loads the gtag.js library (any stream, Google- or first-party-hosted). Checked by script rather
6
+ * than by `window.gtag`, because the Consent Mode default defines the gtag stub before any library loads.
7
+ */
8
+ export declare const isGtagLibraryOnPage: () => boolean;
9
+ /** The page's `gtag()`, defining Google's official command-queue stub when the page doesn't have one yet. */
10
+ export declare const gtag: () => Gtag;
11
+ /**
12
+ * Consent Mode v2 default. It must be queued before any config or container load. When a signal starts denied, tags
13
+ * wait `waitForUpdate` ms for the visitor's choice before firing.
14
+ */
15
+ export declare const setDefaultConsent: ({ consent, waitForUpdate, }: {
16
+ consent: ConsentState;
17
+ waitForUpdate: number;
18
+ }) => void;
19
+ export declare const updateConsent: (consent: ConsentState) => void;
20
+ /** Parses an option that must be an https:// URL, failing fast with the option's name. */
21
+ export declare const toHttpsUrl: (value: string, option: string) => URL;
22
+ export {};
@@ -2,5 +2,5 @@ import type { Destination, GtmConfig } from '../core/types.js';
2
2
  type GtmDestinationOptions = GtmConfig & {
3
3
  nonce?: string;
4
4
  };
5
- export declare const createGtmDestination: ({ containerId, loadScript, nonce, }: GtmDestinationOptions) => Destination;
5
+ export declare const createGtmDestination: ({ containerId, loadScript, scriptUrl, waitForUpdate, nonce, }: GtmDestinationOptions) => Destination;
6
6
  export {};
@@ -0,0 +1,6 @@
1
+ export declare const isScriptOnPage: (src: string) => boolean;
2
+ /** Appends an async vendor script, carrying the page's CSP nonce when there is one. */
3
+ export declare const injectScript: ({ src, nonce, }: {
4
+ src: string;
5
+ nonce?: string;
6
+ }) => void;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "react-marketing-tools",
3
3
  "type": "module",
4
- "version": "1.0.0-alpha.2",
4
+ "version": "1.0.0-alpha.3",
5
5
  "description": "React Marketing Tools are a set of tools to make it easier for you to implement analytics and track user journeys, interactions throughout your App. using dataLayer/Google Tag Manager, GA4 fetch directly or coming soon facebook pixel.",
6
6
  "license": "MIT",
7
7
  "author": "bronz3beard <exempli.gratia.webdesign@gmail.com> (https://www.heyrory.com/)",
@@ -63,21 +63,6 @@
63
63
  "test:watch": "vitest",
64
64
  "typecheck": "tsc --noEmit"
65
65
  },
66
- "browserslist": {
67
- "production": [
68
- ">0.2%",
69
- "not dead",
70
- "not op_mini all"
71
- ],
72
- "development": [
73
- "last 1 chrome version",
74
- "last 1 firefox version",
75
- "last 1 safari version"
76
- ]
77
- },
78
- "dependencies": {
79
- "device-detector-js": "^3.0.3"
80
- },
81
66
  "peerDependencies": {
82
67
  "react": ">=18.0.0"
83
68
  },
@@ -1,13 +0,0 @@
1
- import { TrackAnalyticsEventOptions } from '../types/index.js';
2
- /**
3
- *
4
- * @paramType {object} options
5
- * @property {object} data this is an object of any data you want to collect in analytics
6
- * @property {object} eventNameInfo
7
- * @property {string} analyticsType
8
- * @property {array | undefined} userDataKeysToHashArray default for this value is null, when used it should include an array of strings you wish to hash.
9
- * @property {boolean} dataLayerCheck
10
- * @property {object} consoleLogData (optional) is an object with the following attributes, "showGlobalVars", "showJourneyPropsPayload", "showUserProps" if any of the values are true you will be able to see the respective payload in your console.
11
- */
12
- declare const trackAnalyticsEvent: (options: TrackAnalyticsEventOptions) => Promise<void>;
13
- export { trackAnalyticsEvent };
@@ -1,3 +0,0 @@
1
- import { Ga4GoogleAnalyticsEventTracking } from '../types';
2
- declare const ga4GoogleAnalyticsEventTracking: (options: Ga4GoogleAnalyticsEventTracking) => Promise<void>;
3
- export default ga4GoogleAnalyticsEventTracking;
@@ -1,3 +0,0 @@
1
- import { HandleDataLayerPushOptions } from '../types';
2
- declare const handleDataLayerPush: (options: HandleDataLayerPushOptions) => Promise<void>;
3
- export default handleDataLayerPush;
@@ -1,41 +0,0 @@
1
- import DeviceDetector from 'device-detector-js';
2
- import { AllowedTypes, EventNameInfo } from '../types';
3
- /**
4
- *
5
- * @param {object} user the structure of this data is dictated by you, aka your API or DB.
6
- * @param {[string]} includeUserKeys is an array of strings that represent keys from your user data that you want to whitelist, the value for each key in the whitelist will be hashed.
7
- * @returns a new user object with all values hashed including only the whitelisted key value pairs
8
- */
9
- export declare const hashUserData: (user: Record<AllowedTypes, AllowedTypes>, includeUserKeys: Array<string>) => Promise<Record<AllowedTypes, AllowedTypes>>;
10
- /**
11
- *
12
- * @param {object} user the structure of this data is dictated by you, aka your API or DB.
13
- * @param {[string]} includeUserKeys is an array of strings that represent keys from your user data that you want to map over, if no keys are supplied the original passed in user data is returned.
14
- * @param {boolean} showMissingUserAttributesInConsole a boolean condition to show or hide "user" attributes that are not included in the "includeUserKeys" array, by console logging in dev tools.
15
- * @returns If no "includeUserKeys" are supplied the original ser object is returned, otherwise, a new user object with only the key value pairs that you included in your includeUserKeys array is returned.
16
- */
17
- export declare const buildNewUserData: (user: Record<AllowedTypes, AllowedTypes>, includeUserKeys: Array<string>, showMissingUserAttributesInConsole: boolean | undefined) => Record<string, string>;
18
- /**
19
- *
20
- * @paramType {Object} eventNameInfo
21
- * @property {analyticsPrefixActionList} actionPrefix either a built in value of JOURNEY | INTERACTION or a custom value added to the analyticsPrefixActionList object.
22
- * @property {string} description an OPTIONAL value passed in that is formatted like the following from 'Very descriptive description' to VERY_DESCRIPTIVE_DESCRIPTION.
23
- * @property {string} globalAppEvent the current event name for this track event selected from the analyticsGlobalEventActionList
24
- * @property {string} previousGlobalAppEvent an OPTIONAL value, the previous event name for this track event selected from the analyticsGlobalEventActionList.
25
- * @returns string eventName for this specific tracked event
26
- */
27
- export declare const buildAnalyticsEventName: (eventNameInfo: EventNameInfo) => string;
28
- /**
29
- *
30
- * @param {object} data
31
- * @param {string} globalAppEvent
32
- * @returns dataObject
33
- */
34
- export declare const buildEventDataObject: (data: Record<AllowedTypes, AllowedTypes>, globalAppEvent: string) => Promise<Record<AllowedTypes, AllowedTypes>>;
35
- /**
36
- *
37
- * @param {string} eventName
38
- * @returns a Boolean value, true if the event name is already in the dataLayer.
39
- */
40
- export declare const defaultDataLayerEventCheck: (eventName: string) => boolean;
41
- export declare const deviceDetectorInfo: (userAgent: string) => DeviceDetector.DeviceDetectorResult;
@@ -1,8 +0,0 @@
1
- import { BuildConfigOptions, Config, AnalyticsPlatform } from '../types/index.js';
2
- declare const analyticsPlatform: AnalyticsPlatform;
3
- declare const showMeBuildInAnalyticsPlatform: () => void;
4
- declare const showMeBuildInEventActionPrefixList: () => void;
5
- declare const showMeBuildInGlobalEventActionList: () => void;
6
- declare let config: Config;
7
- declare const buildConfig: (options: BuildConfigOptions) => void;
8
- export { config, analyticsPlatform, buildConfig, showMeBuildInAnalyticsPlatform, showMeBuildInGlobalEventActionList, showMeBuildInEventActionPrefixList, };
@@ -1,3 +0,0 @@
1
- import { IpInfo, ServerLocationData } from '../types';
2
- export declare const buildServerLocationData: (withServerLocationInfo: boolean | undefined, IP_INFO_TOKEN: string | undefined) => Promise<ServerLocationData | undefined>;
3
- export declare const getIpInfo: (IP_INFO_TOKEN: string) => Promise<IpInfo>;
@@ -1,130 +0,0 @@
1
- export interface AnalyticsEventActionPrefix extends Record<string, string> {
2
- }
3
- export interface AnalyticsGlobalEventAction extends Record<string, string> {
4
- }
5
- export type Event = {
6
- name: string;
7
- params: Record<AllowedTypes, AllowedTypes>;
8
- };
9
- export type GooglePayload = {
10
- non_personalized_ads: boolean;
11
- client_id: string;
12
- user_id: string;
13
- user_properties: {
14
- user_id: string;
15
- user_type: string;
16
- client_id: string;
17
- };
18
- events: Event[];
19
- };
20
- export type EventNameInfo = {
21
- eventName?: string;
22
- description?: string;
23
- actionPrefix: string;
24
- globalAppEvent: string;
25
- previousGlobalAppEvent?: string;
26
- };
27
- export type AllowedTypes = string | number;
28
- export type TrackAnalyticsEventOptions = {
29
- data: Record<AllowedTypes, AllowedTypes>;
30
- eventNameInfo: EventNameInfo;
31
- analyticsType: string;
32
- userDataKeysToHashArray?: Array<string> | null;
33
- dataLayerCheck?: boolean;
34
- consoleLogData?: object;
35
- };
36
- export type GlobalVars = {
37
- app: string;
38
- device_category?: string | undefined;
39
- device_os?: string | undefined;
40
- device_browser?: string | undefined;
41
- };
42
- export type DataLayer = {
43
- event: string;
44
- globalVars: GlobalVars;
45
- journeyProps: Record<any, any>;
46
- userProps: Record<any, any>;
47
- };
48
- export type ConsoleLogData = {
49
- showGlobalVars?: boolean;
50
- showJourneyPropsPayload?: boolean;
51
- showUserProps?: boolean;
52
- };
53
- export interface AnalyticsTrackerDataOptions {
54
- consoleLogData: ConsoleLogData | null;
55
- data: Record<any, any>;
56
- eventName: string;
57
- globalAppEvent: string;
58
- userDetails: Record<any, any>;
59
- }
60
- export interface HandleDataLayerPushOptions extends AnalyticsTrackerDataOptions {
61
- dataLayerCheck: boolean;
62
- }
63
- export type ServerLocationData = {
64
- SERVER_CITY: string;
65
- SERVER_COUNTRY: string;
66
- SERVER_HOSTNAME: string;
67
- SERVER_IP: string;
68
- SERVER_LOCATION: string;
69
- SERVER_POSTAL: string;
70
- SERVER_REGION: string;
71
- SERVER_TIMEZONE: string;
72
- };
73
- export type IpInfo = {
74
- city: string;
75
- country: string;
76
- SERVER_HOSTNAME: string;
77
- hostname: string;
78
- ip: string;
79
- loc: string;
80
- postal: string;
81
- region: string;
82
- timezone: string;
83
- };
84
- export type Ga4GoogleAnalyticsEventTracking = AnalyticsTrackerDataOptions;
85
- export type Platform = 'GOOGLE' | 'FACEBOOK' | 'DATALAYER_PUSH';
86
- export type AnalyticsPlatform = {
87
- [key in Platform]: Platform;
88
- };
89
- export type Tokens = {
90
- IP_INFO_TOKEN?: string;
91
- GA4_PUBLIC_API_SECRET?: string;
92
- GA4_PUBLIC_MEASUREMENT_ID?: string;
93
- };
94
- export type Config = {
95
- TOKENS?: Tokens;
96
- appName: string;
97
- appSessionCookieName: string;
98
- withDeviceInfo?: boolean;
99
- includeUserKeys: Array<string>;
100
- showMissingUserAttributesInConsole: boolean | undefined;
101
- withServerLocationInfo: boolean | undefined;
102
- eventActionPrefixList: AnalyticsEventActionPrefix;
103
- analyticsGlobalEventActionList: AnalyticsGlobalEventAction;
104
- };
105
- export type BuildConfigOptions = {
106
- appName: string;
107
- appSessionCookieName: string;
108
- eventActionPrefix?: AnalyticsEventActionPrefix;
109
- globalEventActionList?: AnalyticsGlobalEventAction;
110
- includeUserKeys?: Array<string> | undefined;
111
- showMissingUserAttributesInConsole?: boolean;
112
- showMeBuildInEventActionPrefixList?: boolean;
113
- TOKENS?: Tokens;
114
- withDeviceInfo?: boolean;
115
- withServerLocationInfo?: boolean;
116
- };
117
- export declare let config: Config;
118
- export type ProviderStateProps = {
119
- appName: string;
120
- appSessionCookieName: string;
121
- analyticsPlatform: AnalyticsPlatform;
122
- eventActionPrefixList: AnalyticsEventActionPrefix;
123
- analyticsGlobalEventActionList: AnalyticsGlobalEventAction;
124
- };
125
- export type ProviderApiProps = {
126
- trackAnalyticsEvent: (options: TrackAnalyticsEventOptions) => Promise<void>;
127
- showMeBuildInAnalyticsPlatform: () => void;
128
- showMeBuildInEventActionPrefixList: () => void;
129
- showMeBuildInGlobalEventActionList: () => void;
130
- };
@@ -1 +0,0 @@
1
- export declare const assertIsTrue: (condition: boolean, errorMessage?: string) => void;
@@ -1,5 +0,0 @@
1
- export declare const isClientSide: () => boolean;
2
- export declare const objectHasAttributes: (obj: object, key?: string) => boolean;
3
- export declare const replaceWhiteSpace: (stringValue: string, replaceValue: string) => string;
4
- export declare const checkAllArrayValuesReturnTrue: (valueArray: boolean[]) => boolean;
5
- export declare const hashString: (value: string) => Promise<string>;
@@ -1 +0,0 @@
1
- export declare const getCookieValueByName: (name: string) => string | undefined;