@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.
@@ -44,5 +44,6 @@ export declare class Analytics implements IAnalytics {
44
44
  flush(): Promise<void>;
45
45
  private handleBeforeUnload;
46
46
  private getEndpoint;
47
+ resolveEndpointAsync(): Promise<void>;
47
48
  destroy(): void;
48
49
  }
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.endpoint) {
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
- return "https://telemetry.armco.dev/events/add";
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@armco/analytics",
3
- "version": "0.2.12",
3
+ "version": "0.3.1",
4
4
  "description": "Universal Analytics Library for Browser and Node.js",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -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
- this.logger.info("Click tracking initialized");
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);
@@ -13,6 +13,7 @@ export class PageTrackingPlugin {
13
13
  }
14
14
  this.context = context;
15
15
  this.attachHandlers();
16
+ this.logger.info("Logging page load");
16
17
  this.trackCurrentPage();
17
18
  this.logger.info("Page tracking initialized");
18
19
  }
@@ -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.endpoint) {
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 {
@@ -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>;
@@ -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
@@ -1,5 +1,5 @@
1
1
  export class Logger {
2
- constructor(level = "info", prefix = "[Analytics]") {
2
+ constructor(level = "info", prefix = "[ANALYTICS]") {
3
3
  this.level = level;
4
4
  this.prefix = prefix;
5
5
  }