@armco/analytics 0.2.12 → 0.3.1
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/core/analytics.d.ts +1 -0
- package/core/analytics.js +45 -2
- package/core/types.d.ts +5 -1
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/package.json +1 -1
- package/plugins/auto-track/click.js +5 -1
- package/plugins/auto-track/page.js +1 -0
- package/plugins/enrichment/user.js +8 -1
- package/utils/config-loader.d.ts +4 -1
- package/utils/config-loader.js +109 -0
- package/utils/logging.js +1 -1
package/core/analytics.d.ts
CHANGED
package/core/analytics.js
CHANGED
|
@@ -9,6 +9,7 @@ import { UserPlugin } from "../plugins/enrichment/user";
|
|
|
9
9
|
import { validateConfig, sanitizeEventData } from "../utils/validation";
|
|
10
10
|
import { createLogger } from "../utils/logging";
|
|
11
11
|
import { getEnvironmentType, isDoNotTrackEnabled, getTimestamp, deepMerge, } from "../utils/helpers";
|
|
12
|
+
import { resolveEndpoint, detectEnvironment } from "../utils/config-loader";
|
|
12
13
|
export class AnalyticsBuilder {
|
|
13
14
|
constructor() {
|
|
14
15
|
this.config = {};
|
|
@@ -122,11 +123,19 @@ export class Analytics {
|
|
|
122
123
|
if (this.initialized) {
|
|
123
124
|
throw new InitializationError("Analytics already initialized");
|
|
124
125
|
}
|
|
126
|
+
this.logger.info("Loading Configuration");
|
|
127
|
+
this.logger.info("Check if Analytics enabled");
|
|
125
128
|
if (this.config.respectDoNotTrack && isDoNotTrackEnabled()) {
|
|
126
129
|
this.logger.warn("Do Not Track is enabled, analytics will be disabled");
|
|
127
130
|
this.enabled = false;
|
|
128
131
|
return;
|
|
129
132
|
}
|
|
133
|
+
this.logger.info("Configuration loaded");
|
|
134
|
+
const currentEnv = this.config.environment || detectEnvironment();
|
|
135
|
+
this.logger.info(`Identified environment: ${currentEnv}`);
|
|
136
|
+
if (currentEnv !== "development" && currentEnv !== "local") {
|
|
137
|
+
this.logger.info("Identified non-dev environment, analytics auto load wouldn't be attempted.");
|
|
138
|
+
}
|
|
130
139
|
const context = {
|
|
131
140
|
config: this.config,
|
|
132
141
|
storage: this.storage,
|
|
@@ -135,6 +144,10 @@ export class Analytics {
|
|
|
135
144
|
getSessionId: this.getSessionId.bind(this),
|
|
136
145
|
getUserId: this.getUserId.bind(this),
|
|
137
146
|
};
|
|
147
|
+
if (this.config.showConsentPopup) {
|
|
148
|
+
this.logger.info("Display Tracker Popup");
|
|
149
|
+
}
|
|
150
|
+
this.logger.info("Hook Event Trackers");
|
|
138
151
|
for (const plugin of this.plugins) {
|
|
139
152
|
try {
|
|
140
153
|
this.logger.debug(`Initializing plugin: ${plugin.name}`);
|
|
@@ -145,10 +158,15 @@ export class Analytics {
|
|
|
145
158
|
}
|
|
146
159
|
}
|
|
147
160
|
if (this.config.submissionStrategy === "DEFER") {
|
|
161
|
+
this.logger.info('Hook Handlers to flush events (use when submissionStrategy is configured as "DEFER"');
|
|
148
162
|
this.flushInterval = setInterval(() => {
|
|
149
163
|
this.flush();
|
|
150
164
|
}, this.config.flushInterval);
|
|
151
165
|
}
|
|
166
|
+
if (this.config.enableLocation) {
|
|
167
|
+
this.logger.info("Find User Location Details");
|
|
168
|
+
}
|
|
169
|
+
this.logger.info("Initiate Session and Anonymous User ID");
|
|
152
170
|
if (getEnvironmentType() === "browser") {
|
|
153
171
|
window.addEventListener("beforeunload", () => {
|
|
154
172
|
this.handleBeforeUnload();
|
|
@@ -285,10 +303,35 @@ export class Analytics {
|
|
|
285
303
|
}
|
|
286
304
|
}
|
|
287
305
|
getEndpoint() {
|
|
288
|
-
if (this.config.
|
|
306
|
+
if (this.config.resolvedEndpoint) {
|
|
307
|
+
return this.config.resolvedEndpoint;
|
|
308
|
+
}
|
|
309
|
+
if (typeof this.config.endpoint === "string") {
|
|
289
310
|
return this.config.endpoint;
|
|
290
311
|
}
|
|
291
|
-
|
|
312
|
+
if (this.config.endpoint && typeof this.config.endpoint === "object") {
|
|
313
|
+
const endpoints = this.config.endpoint;
|
|
314
|
+
const currentEnv = this.config.environment || detectEnvironment();
|
|
315
|
+
if (endpoints[currentEnv]) {
|
|
316
|
+
return endpoints[currentEnv];
|
|
317
|
+
}
|
|
318
|
+
const fallbackOrder = ["development", "local", "staging", "production"];
|
|
319
|
+
for (const fallbackEnv of fallbackOrder) {
|
|
320
|
+
if (endpoints[fallbackEnv]) {
|
|
321
|
+
return endpoints[fallbackEnv];
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (this.config.apiKey) {
|
|
326
|
+
return "https://telemetry.armco.dev/events/add";
|
|
327
|
+
}
|
|
328
|
+
return "http://localhost:5001/events/add";
|
|
329
|
+
}
|
|
330
|
+
async resolveEndpointAsync() {
|
|
331
|
+
this.logger.info("Resolving endpoint with health check...");
|
|
332
|
+
const resolved = await resolveEndpoint(this.config);
|
|
333
|
+
this.config.resolvedEndpoint = resolved;
|
|
334
|
+
this.logger.info(`Resolved endpoint: ${resolved}`);
|
|
292
335
|
}
|
|
293
336
|
destroy() {
|
|
294
337
|
this.flush();
|
package/core/types.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export type Environment = "browser" | "node" | "unknown";
|
|
2
|
+
export type EnvironmentName = "development" | "staging" | "production" | "test" | "local" | string;
|
|
3
|
+
export type EndpointConfig = Record<EnvironmentName, string>;
|
|
2
4
|
export type SubmissionStrategy = "ONEVENT" | "DEFER";
|
|
3
5
|
export type LogLevel = "debug" | "info" | "warn" | "error" | "none";
|
|
4
6
|
export interface EventData {
|
|
@@ -54,8 +56,10 @@ export interface LocationData {
|
|
|
54
56
|
}
|
|
55
57
|
export interface AnalyticsConfig {
|
|
56
58
|
apiKey?: string;
|
|
57
|
-
endpoint?: string;
|
|
59
|
+
endpoint?: string | EndpointConfig;
|
|
60
|
+
resolvedEndpoint?: string;
|
|
58
61
|
updateEndpoint?: string;
|
|
62
|
+
environment?: EnvironmentName;
|
|
59
63
|
hostProjectName?: string;
|
|
60
64
|
trackEvents?: string[];
|
|
61
65
|
submissionStrategy?: SubmissionStrategy;
|
package/index.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export type { HTTPRequestEvent, HTTPRequestMetadata } from "./plugins/node/http-
|
|
|
18
18
|
export { validateConfig, validateUser, validatePageView, validateClickEvent, validateFormEvent, validateErrorEvent, sanitizeEventData, } from "./utils/validation";
|
|
19
19
|
export { Logger, getLogger, createLogger } from "./utils/logging";
|
|
20
20
|
export { generateId, getEnvironmentType, getEnvironment, isDoNotTrackEnabled, isBrowser, areCookiesAvailable, isLocalStorageAvailable, debounce, throttle, deepClone, deepMerge, } from "./utils/helpers";
|
|
21
|
-
export { loadConfigFromFile, loadConfig } from "./utils/config-loader";
|
|
21
|
+
export { loadConfigFromFile, loadConfig, detectEnvironment, checkEndpointHealth, resolveEndpoint } from "./utils/config-loader";
|
|
22
22
|
import { AnalyticsBuilder as Builder } from "./core/analytics";
|
|
23
23
|
export declare function createAnalytics(): Builder;
|
|
24
24
|
export default Builder;
|
package/index.js
CHANGED
|
@@ -16,7 +16,7 @@ export { HTTPRequestTrackingPlugin } from "./plugins/node/http-request-tracking"
|
|
|
16
16
|
export { validateConfig, validateUser, validatePageView, validateClickEvent, validateFormEvent, validateErrorEvent, sanitizeEventData, } from "./utils/validation";
|
|
17
17
|
export { Logger, getLogger, createLogger } from "./utils/logging";
|
|
18
18
|
export { generateId, getEnvironmentType, getEnvironment, isDoNotTrackEnabled, isBrowser, areCookiesAvailable, isLocalStorageAvailable, debounce, throttle, deepClone, deepMerge, } from "./utils/helpers";
|
|
19
|
-
export { loadConfigFromFile, loadConfig } from "./utils/config-loader";
|
|
19
|
+
export { loadConfigFromFile, loadConfig, detectEnvironment, checkEndpointHealth, resolveEndpoint } from "./utils/config-loader";
|
|
20
20
|
import { AnalyticsBuilder as Builder } from "./core/analytics";
|
|
21
21
|
export function createAnalytics() {
|
|
22
22
|
return new Builder();
|
package/package.json
CHANGED
|
@@ -22,8 +22,12 @@ export class ClickTrackingPlugin {
|
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
24
|
this.context = context;
|
|
25
|
+
this.logger.info("Attaching Click handlers");
|
|
25
26
|
this.attachHandlers();
|
|
26
|
-
|
|
27
|
+
const trackableElements = document.querySelectorAll(TRACKED_ELEMENTS.join(", "));
|
|
28
|
+
this.logger.info(`Found ${trackableElements.length} items that can be clicked!`);
|
|
29
|
+
this.logger.info("Dynamically added elements will be added to this list.");
|
|
30
|
+
this.logger.info("Click handlers Attached");
|
|
27
31
|
}
|
|
28
32
|
attachHandlers() {
|
|
29
33
|
this.boundHandler = this.handleClick.bind(this);
|
|
@@ -17,6 +17,10 @@ export class UserPlugin {
|
|
|
17
17
|
if (!this.user) {
|
|
18
18
|
this.generateAnonymousId();
|
|
19
19
|
}
|
|
20
|
+
const userId = this.getUserId();
|
|
21
|
+
if (userId) {
|
|
22
|
+
this.logger.info(`Tracking User as ${userId}`);
|
|
23
|
+
}
|
|
20
24
|
}
|
|
21
25
|
processEvent(event) {
|
|
22
26
|
if (this.user) {
|
|
@@ -120,7 +124,10 @@ export class UserPlugin {
|
|
|
120
124
|
if (config.apiKey) {
|
|
121
125
|
updateEndpoint = "https://telemetry.armco.dev/events/tag";
|
|
122
126
|
}
|
|
123
|
-
else if (config.
|
|
127
|
+
else if (config.resolvedEndpoint) {
|
|
128
|
+
updateEndpoint = config.resolvedEndpoint.replace("/add", "/tag");
|
|
129
|
+
}
|
|
130
|
+
else if (typeof config.endpoint === "string") {
|
|
124
131
|
updateEndpoint = config.endpoint.replace("/add", "/tag");
|
|
125
132
|
}
|
|
126
133
|
else {
|
package/utils/config-loader.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
-
import type { AnalyticsConfig } from "../core/types";
|
|
1
|
+
import type { AnalyticsConfig, EnvironmentName } from "../core/types";
|
|
2
2
|
export declare function loadConfigFromFile(): Promise<Partial<AnalyticsConfig> | null>;
|
|
3
3
|
export declare function loadConfig(overrides?: Partial<AnalyticsConfig>): Promise<Partial<AnalyticsConfig>>;
|
|
4
|
+
export declare function detectEnvironment(): EnvironmentName;
|
|
5
|
+
export declare function checkEndpointHealth(url: string): Promise<boolean>;
|
|
6
|
+
export declare function resolveEndpoint(config: Partial<AnalyticsConfig>): Promise<string>;
|
package/utils/config-loader.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getEnvironmentType } from "./helpers";
|
|
2
2
|
import { getLogger } from "./logging";
|
|
3
3
|
const CONFIG_FILE_NAME = "analyticsrc";
|
|
4
|
+
const DEFAULT_FALLBACK_ENDPOINT = "http://localhost:5001/events/add";
|
|
4
5
|
const logger = getLogger();
|
|
5
6
|
export async function loadConfigFromFile() {
|
|
6
7
|
const envType = getEnvironmentType();
|
|
@@ -57,3 +58,111 @@ export async function loadConfig(overrides) {
|
|
|
57
58
|
...overrides,
|
|
58
59
|
};
|
|
59
60
|
}
|
|
61
|
+
export function detectEnvironment() {
|
|
62
|
+
const envType = getEnvironmentType();
|
|
63
|
+
if (envType === "node") {
|
|
64
|
+
const nodeEnv = process.env.NODE_ENV?.toLowerCase();
|
|
65
|
+
if (nodeEnv) {
|
|
66
|
+
return nodeEnv;
|
|
67
|
+
}
|
|
68
|
+
return "development";
|
|
69
|
+
}
|
|
70
|
+
if (envType === "browser") {
|
|
71
|
+
const hostname = window.location.hostname;
|
|
72
|
+
if (hostname === "localhost" || hostname === "127.0.0.1") {
|
|
73
|
+
return "development";
|
|
74
|
+
}
|
|
75
|
+
if (hostname.includes("staging") || hostname.includes("stage")) {
|
|
76
|
+
return "staging";
|
|
77
|
+
}
|
|
78
|
+
if (hostname.includes("test") || hostname.includes("qa")) {
|
|
79
|
+
return "test";
|
|
80
|
+
}
|
|
81
|
+
return "production";
|
|
82
|
+
}
|
|
83
|
+
return "development";
|
|
84
|
+
}
|
|
85
|
+
export async function checkEndpointHealth(url) {
|
|
86
|
+
try {
|
|
87
|
+
const envType = getEnvironmentType();
|
|
88
|
+
if (envType === "node") {
|
|
89
|
+
const controller = new AbortController();
|
|
90
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
91
|
+
try {
|
|
92
|
+
const response = await fetch(url.replace("/events/add", "/health"), {
|
|
93
|
+
method: "GET",
|
|
94
|
+
signal: controller.signal,
|
|
95
|
+
});
|
|
96
|
+
clearTimeout(timeout);
|
|
97
|
+
return response.ok || response.status === 404;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
clearTimeout(timeout);
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else if (envType === "browser") {
|
|
105
|
+
const controller = new AbortController();
|
|
106
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
107
|
+
try {
|
|
108
|
+
const response = await fetch(url.replace("/events/add", "/health"), {
|
|
109
|
+
method: "GET",
|
|
110
|
+
mode: "no-cors",
|
|
111
|
+
signal: controller.signal,
|
|
112
|
+
});
|
|
113
|
+
clearTimeout(timeout);
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
clearTimeout(timeout);
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
export async function resolveEndpoint(config) {
|
|
128
|
+
const currentEnv = config.environment || detectEnvironment();
|
|
129
|
+
logger.info(`Identified environment: ${currentEnv}`);
|
|
130
|
+
if (typeof config.endpoint === "string") {
|
|
131
|
+
logger.debug(`Using single endpoint: ${config.endpoint}`);
|
|
132
|
+
return config.endpoint;
|
|
133
|
+
}
|
|
134
|
+
if (config.endpoint && typeof config.endpoint === "object") {
|
|
135
|
+
const endpoints = config.endpoint;
|
|
136
|
+
const envEndpoint = endpoints[currentEnv];
|
|
137
|
+
if (envEndpoint) {
|
|
138
|
+
logger.info(`Found endpoint for environment '${currentEnv}': ${envEndpoint}`);
|
|
139
|
+
const isHealthy = await checkEndpointHealth(envEndpoint);
|
|
140
|
+
if (isHealthy) {
|
|
141
|
+
logger.info(`Endpoint ${envEndpoint} is reachable`);
|
|
142
|
+
return envEndpoint;
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
logger.warn(`Endpoint ${envEndpoint} is not reachable, falling back to ${DEFAULT_FALLBACK_ENDPOINT}`);
|
|
146
|
+
return DEFAULT_FALLBACK_ENDPOINT;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
const fallbackOrder = ["development", "local", "staging", "production"];
|
|
151
|
+
for (const fallbackEnv of fallbackOrder) {
|
|
152
|
+
if (endpoints[fallbackEnv]) {
|
|
153
|
+
logger.warn(`No endpoint configured for '${currentEnv}', using '${fallbackEnv}' endpoint: ${endpoints[fallbackEnv]}`);
|
|
154
|
+
return endpoints[fallbackEnv];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
logger.warn(`No endpoint found in configuration, using fallback: ${DEFAULT_FALLBACK_ENDPOINT}`);
|
|
158
|
+
return DEFAULT_FALLBACK_ENDPOINT;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (config.apiKey) {
|
|
162
|
+
const defaultEndpoint = "https://telemetry.armco.dev/events/add";
|
|
163
|
+
logger.info(`Using default Armco endpoint: ${defaultEndpoint}`);
|
|
164
|
+
return defaultEndpoint;
|
|
165
|
+
}
|
|
166
|
+
logger.warn(`No endpoint configured, using fallback: ${DEFAULT_FALLBACK_ENDPOINT}`);
|
|
167
|
+
return DEFAULT_FALLBACK_ENDPOINT;
|
|
168
|
+
}
|
package/utils/logging.js
CHANGED