@salesforcedevs/dx-components 1.37.2 → 1.39.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.
package/lwc.config.json CHANGED
@@ -72,6 +72,7 @@
72
72
  "dx/iconBadge",
73
73
  "dx/imageAndLabel",
74
74
  "dx/input",
75
+ "dx/instrumentation",
75
76
  "dx/interactiveImage",
76
77
  "dx/logo",
77
78
  "dx/mainContentHeader",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforcedevs/dx-components",
3
- "version": "1.37.2",
3
+ "version": "1.39.0",
4
4
  "description": "DX Lightning web components",
5
5
  "license": "MIT",
6
6
  "engines": {
@@ -44,5 +44,5 @@
44
44
  "luxon": "3.4.4",
45
45
  "msw": "^2.12.4"
46
46
  },
47
- "gitHead": "ca79eefb9b9ec80bb48c96a091fd0ce558bda710"
47
+ "gitHead": "055e2a716cf3e946add92e953e136835730bc5fc"
48
48
  }
@@ -0,0 +1 @@
1
+ <template></template>
@@ -0,0 +1,200 @@
1
+ import { LightningElement, api } from "lwc";
2
+ import {
3
+ TRACKING_EVENT_NAME as DX_TRACKING_EVENT_NAME,
4
+ LISTENER_QUEUE as DX_LISTENER_QUEUE,
5
+ LISTENER_INDICATOR as DX_LISTENER_INDICATOR,
6
+ track as dxTrack
7
+ } from "dxUtils/analytics";
8
+
9
+ // Architect ("architectssite_track") namespace. These mirror the constants used
10
+ // by <arch-instrumentation> in @salesforcedevs/arch-components. They are declared
11
+ // locally rather than imported because dx-components does not depend on
12
+ // arch-components.
13
+ const ARCH_TRACKING_EVENT_NAME = "architectssite_track";
14
+ const ARCH_LISTENER_QUEUE = "__TDS_INSTRUMENTATION_QUEUE__";
15
+ const ARCH_LISTENER_INDICATOR = "__TDS_INSTRUMENTATION_IS_INITIALIZED__";
16
+
17
+ // Arch-namespace equivalent of dxUtils/analytics `track`: either dispatch the
18
+ // architect tracking event (once the listener is mounted) or queue it. Used to
19
+ // replay events that were queued in __TDS_INSTRUMENTATION_QUEUE__ before this
20
+ // component mounted.
21
+ function archTrack(
22
+ element: EventTarget,
23
+ event: string,
24
+ payload: Record<string, unknown>
25
+ ): void {
26
+ const detail = { event, payload };
27
+ if ((window as any)[ARCH_LISTENER_INDICATOR] === undefined) {
28
+ (window as any)[ARCH_LISTENER_QUEUE] =
29
+ (window as any)[ARCH_LISTENER_QUEUE] || [];
30
+ (window as any)[ARCH_LISTENER_QUEUE].push(detail);
31
+ } else {
32
+ const e = new CustomEvent(ARCH_TRACKING_EVENT_NAME, {
33
+ bubbles: true,
34
+ composed: true,
35
+ detail
36
+ });
37
+ element.dispatchEvent(e);
38
+ }
39
+ }
40
+
41
+ interface NamespaceConfig {
42
+ eventName: string;
43
+ queue: string;
44
+ indicator: string;
45
+ replay: (
46
+ element: EventTarget,
47
+ event: string,
48
+ payload: Record<string, unknown>
49
+ ) => void;
50
+ }
51
+
52
+ // The two tracking-event namespaces dx-instrumentation can bridge. Keyed by the
53
+ // values accepted by the `namespaces` attribute.
54
+ const NAMESPACE_CONFIGS: Record<"developer" | "architect", NamespaceConfig> = {
55
+ developer: {
56
+ eventName: DX_TRACKING_EVENT_NAME,
57
+ queue: DX_LISTENER_QUEUE,
58
+ indicator: DX_LISTENER_INDICATOR,
59
+ replay: dxTrack
60
+ },
61
+ architect: {
62
+ eventName: ARCH_TRACKING_EVENT_NAME,
63
+ queue: ARCH_LISTENER_QUEUE,
64
+ indicator: ARCH_LISTENER_INDICATOR,
65
+ replay: archTrack
66
+ }
67
+ };
68
+
69
+ // Maps the `namespaces` attribute to the configs it activates.
70
+ function resolveNamespaceConfigs(namespaces: string): NamespaceConfig[] {
71
+ switch (namespaces) {
72
+ case "developer":
73
+ return [NAMESPACE_CONFIGS.developer];
74
+ case "architect":
75
+ return [NAMESPACE_CONFIGS.architect];
76
+ case "both":
77
+ default:
78
+ return [NAMESPACE_CONFIGS.developer, NAMESPACE_CONFIGS.architect];
79
+ }
80
+ }
81
+
82
+ const googleTagManager = {
83
+ track: function (e: Event) {
84
+ if ((window as any).dataLayer === undefined) {
85
+ (window as any).dataLayer = [];
86
+ }
87
+ const { event, payload } = (e as CustomEvent).detail;
88
+ (window as any).dataLayer.push({ ...payload, event });
89
+ }
90
+ };
91
+
92
+ interface TrackEventListener {
93
+ add: () => void;
94
+ remove: () => void;
95
+ }
96
+
97
+ /**
98
+ * Attaches a listener for each of the given tracking event names (e.g.
99
+ * `developerwebsite_track` and/or `architectssite_track`), forwarding every
100
+ * caught event to every tracker function. A single component can therefore
101
+ * bridge one or both event namespaces into the dataLayer.
102
+ */
103
+ export function instrumentationTrackEventListener(
104
+ trackerFns: { track: (e: Event) => void }[] = [],
105
+ eventNames: string[] = [DX_TRACKING_EVENT_NAME, ARCH_TRACKING_EVENT_NAME]
106
+ ): TrackEventListener {
107
+ if (trackerFns.length === 0) {
108
+ throw new Error("No tracker functions passed");
109
+ }
110
+
111
+ const handleTrackEvent = (e: Event) => {
112
+ trackerFns.forEach((tracker) => {
113
+ tracker.track(e);
114
+ });
115
+ };
116
+
117
+ const attachEventListener = () => {
118
+ eventNames.forEach((eventName) => {
119
+ document.addEventListener(eventName, handleTrackEvent);
120
+ });
121
+ };
122
+
123
+ const removeEventListener = () => {
124
+ eventNames.forEach((eventName) => {
125
+ document.removeEventListener(eventName, handleTrackEvent);
126
+ });
127
+ };
128
+
129
+ return {
130
+ add: attachEventListener,
131
+ remove: removeEventListener
132
+ };
133
+ }
134
+
135
+ /**
136
+ * Unified instrumentation bridge. Mount a single <dx-instrumentation> to forward
137
+ * developer-website and/or architect-website tracking events into the GTM
138
+ * dataLayer. Renders nothing.
139
+ *
140
+ * By default (`namespaces="both"`) it bridges both the developer
141
+ * (`developerwebsite_track`) and architect (`architectssite_track`) namespaces,
142
+ * replacing the need to mount <dw-instrumentation> and <arch-instrumentation>
143
+ * separately. Set `namespaces="developer"` (or `"architect"`) to bridge only one
144
+ * — e.g. on a site whose header analytics already reach the dataLayer through
145
+ * another path, so draining the architect queue here would double-count.
146
+ */
147
+ export default class Instrumentation extends LightningElement {
148
+ @api useGoogleTagManager = false;
149
+
150
+ // "both" (default) | "developer" | "architect"
151
+ @api namespaces = "both";
152
+
153
+ private tracker: TrackEventListener | null = null;
154
+
155
+ private setupTracker() {
156
+ const configs = resolveNamespaceConfigs(this.namespaces);
157
+ const trackerFunctions = this.useGoogleTagManager
158
+ ? [googleTagManager]
159
+ : [];
160
+ this.tracker = instrumentationTrackEventListener(
161
+ trackerFunctions,
162
+ configs.map((c) => c.eventName)
163
+ );
164
+ this.tracker.add();
165
+ // Mark each active namespace initialized so track() calls from that
166
+ // stack dispatch live events instead of queueing.
167
+ configs.forEach((c) => {
168
+ (window as any)[c.indicator] = true;
169
+ });
170
+ }
171
+
172
+ private dispatchQueuedEvents() {
173
+ resolveNamespaceConfigs(this.namespaces).forEach((config) => {
174
+ const queue = (window as any)[config.queue];
175
+ if (Array.isArray(queue)) {
176
+ while (queue.length > 0) {
177
+ const event = queue.pop() as {
178
+ event: string;
179
+ payload: Record<string, unknown>;
180
+ };
181
+ config.replay(this, event.event, event.payload);
182
+ }
183
+ }
184
+ });
185
+ }
186
+
187
+ renderedCallback() {
188
+ if (this.tracker === null) {
189
+ this.setupTracker();
190
+ this.dispatchQueuedEvents();
191
+ }
192
+ }
193
+
194
+ disconnectedCallback() {
195
+ if (this.tracker !== null) {
196
+ this.tracker.remove();
197
+ this.tracker = null;
198
+ }
199
+ }
200
+ }