@armco/analytics 0.2.11 → 0.2.12

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.
Files changed (44) hide show
  1. package/core/analytics.d.ts +48 -0
  2. package/core/analytics.js +311 -0
  3. package/core/errors.d.ts +26 -0
  4. package/core/errors.js +54 -0
  5. package/core/types.d.ts +133 -0
  6. package/core/types.js +1 -0
  7. package/global-modules.d.ts +14 -0
  8. package/index.d.ts +24 -0
  9. package/index.js +24 -0
  10. package/package.json +6 -36
  11. package/plugins/auto-track/click.d.ts +15 -0
  12. package/plugins/auto-track/click.js +95 -0
  13. package/plugins/auto-track/error.d.ts +15 -0
  14. package/plugins/auto-track/error.js +65 -0
  15. package/plugins/auto-track/form.d.ts +13 -0
  16. package/plugins/auto-track/form.js +54 -0
  17. package/plugins/auto-track/page.d.ts +14 -0
  18. package/plugins/auto-track/page.js +71 -0
  19. package/plugins/enrichment/session.d.ts +18 -0
  20. package/plugins/enrichment/session.js +81 -0
  21. package/plugins/enrichment/user.d.ts +20 -0
  22. package/plugins/enrichment/user.js +152 -0
  23. package/plugins/node/http-request-tracking.d.ts +54 -0
  24. package/plugins/node/http-request-tracking.js +158 -0
  25. package/storage/cookie-storage.d.ts +8 -0
  26. package/storage/cookie-storage.js +60 -0
  27. package/storage/hybrid-storage.d.ts +12 -0
  28. package/storage/hybrid-storage.js +108 -0
  29. package/storage/local-storage.d.ts +8 -0
  30. package/storage/local-storage.js +65 -0
  31. package/storage/memory-storage.d.ts +9 -0
  32. package/storage/memory-storage.js +22 -0
  33. package/transport/beacon-transport.d.ts +16 -0
  34. package/transport/beacon-transport.js +50 -0
  35. package/transport/fetch-transport.d.ts +20 -0
  36. package/transport/fetch-transport.js +112 -0
  37. package/utils/config-loader.d.ts +3 -0
  38. package/utils/config-loader.js +59 -0
  39. package/utils/helpers.d.ts +15 -0
  40. package/utils/helpers.js +148 -0
  41. package/utils/logging.d.ts +17 -0
  42. package/utils/logging.js +54 -0
  43. package/utils/validation.d.ts +9 -0
  44. package/utils/validation.js +146 -0
package/package.json CHANGED
@@ -1,25 +1,11 @@
1
1
  {
2
2
  "name": "@armco/analytics",
3
- "version": "0.2.11",
3
+ "version": "0.2.12",
4
4
  "description": "Universal Analytics Library for Browser and Node.js",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
7
  "type": "module",
8
- "scripts": {
9
- "build": "npx ts-node build.js",
10
- "lint": "npx eslint --ext .ts src/",
11
- "lint:tests": "npx eslint --ext .ts tests/",
12
- "start": "node ./dist --env=production",
13
- "dev": "nodemon",
14
- "test": "jest",
15
- "test:watch": "jest --watch",
16
- "test:coverage": "jest --coverage",
17
- "test:unit": "jest --testPathPattern=tests/unit",
18
- "test:integration": "jest --testPathPattern=tests/integration",
19
- "publish:local": "./publish-local.sh",
20
- "publish:sh": "./publish.sh",
21
- "publish:sh:minor": "./publish.sh minor"
22
- },
8
+ "scripts": {},
23
9
  "repository": {
24
10
  "type": "git",
25
11
  "url": "git+https://github.com/ReStruct-Corporate-Advantage/analytics.git"
@@ -37,27 +23,11 @@
37
23
  "url": "https://github.com/ReStruct-Corporate-Advantage/analytics/issues"
38
24
  },
39
25
  "homepage": "https://github.com/ReStruct-Corporate-Advantage/analytics#readme",
40
- "files": [
41
- "dist",
42
- "README.md",
43
- "LICENSE"
44
- ],
45
- "devDependencies": {
46
- "@jest/globals": "^29.7.0",
47
- "@types/fs-extra": "^11.0.1",
48
- "@types/jest": "^29.5.11",
49
- "@types/js-cookie": "^3.0.3",
50
- "@types/node": "^20.4.2",
51
- "@types/uuid": "^9.0.2",
52
- "fs-extra": "^11.1.1",
53
- "jest": "^29.7.0",
54
- "ts-jest": "^29.1.1",
55
- "typescript": "^5.1.6"
56
- },
26
+ "devDependencies": {},
57
27
  "dependencies": {
58
28
  "js-cookie": "^3.0.5",
59
29
  "jstz": "^2.1.1",
60
30
  "uuid": "^9.0.0",
61
31
  "zod": "^4.1.13"
62
32
  }
63
- }
33
+ }
@@ -0,0 +1,15 @@
1
+ import type { Plugin, PluginContext } from "../../core/types";
2
+ export declare class ClickTrackingPlugin implements Plugin {
3
+ name: string;
4
+ version: string;
5
+ private context?;
6
+ private logger;
7
+ private boundHandler?;
8
+ init(context: PluginContext): void;
9
+ private attachHandlers;
10
+ private handleClick;
11
+ private isTrackable;
12
+ private extractClickData;
13
+ private getElementPath;
14
+ destroy(): void;
15
+ }
@@ -0,0 +1,95 @@
1
+ import { isBrowser } from "../../utils/helpers";
2
+ import { getLogger } from "../../utils/logging";
3
+ const TRACKED_ELEMENTS = [
4
+ "a[href]",
5
+ "button",
6
+ "input[type='button']",
7
+ "input[type='submit']",
8
+ "input[type='reset']",
9
+ "[role='button']",
10
+ "[role='link']",
11
+ "[data-track='true']",
12
+ ];
13
+ export class ClickTrackingPlugin {
14
+ constructor() {
15
+ this.name = "ClickTrackingPlugin";
16
+ this.version = "1.0.0";
17
+ this.logger = getLogger();
18
+ }
19
+ init(context) {
20
+ if (!isBrowser()) {
21
+ this.logger.warn("Click tracking only available in browser");
22
+ return;
23
+ }
24
+ this.context = context;
25
+ this.attachHandlers();
26
+ this.logger.info("Click tracking initialized");
27
+ }
28
+ attachHandlers() {
29
+ this.boundHandler = this.handleClick.bind(this);
30
+ document.addEventListener("click", this.boundHandler, true);
31
+ }
32
+ handleClick(e) {
33
+ const element = e.target;
34
+ if (!this.isTrackable(element)) {
35
+ return;
36
+ }
37
+ const clickData = this.extractClickData(element);
38
+ if (this.context) {
39
+ this.context.track("CLICK", clickData);
40
+ }
41
+ }
42
+ isTrackable(element) {
43
+ return (element.matches(TRACKED_ELEMENTS.join(", ")) ||
44
+ element.onclick != null ||
45
+ window.getComputedStyle(element).cursor === "pointer");
46
+ }
47
+ extractClickData(element) {
48
+ const data = {
49
+ elementType: element.tagName.toLowerCase(),
50
+ elementId: element.id || undefined,
51
+ elementText: element.textContent?.trim() || undefined,
52
+ elementClasses: Array.from(element.classList),
53
+ elementPath: this.getElementPath(element),
54
+ };
55
+ if ("href" in element) {
56
+ data.href = element.href;
57
+ }
58
+ if ("value" in element && element.value) {
59
+ data.value = element.value;
60
+ }
61
+ const dataAttributes = { ...element.dataset };
62
+ if (Object.keys(dataAttributes).length > 0) {
63
+ data.dataAttributes = dataAttributes;
64
+ }
65
+ return data;
66
+ }
67
+ getElementPath(element) {
68
+ const path = [];
69
+ let current = element;
70
+ while (current && current !== document.body) {
71
+ let selector = current.tagName.toLowerCase();
72
+ if (current.id) {
73
+ selector += `#${current.id}`;
74
+ path.unshift(selector);
75
+ break;
76
+ }
77
+ else if (current.className) {
78
+ const classes = Array.from(current.classList)
79
+ .filter((c) => c.trim())
80
+ .join(".");
81
+ if (classes) {
82
+ selector += `.${classes}`;
83
+ }
84
+ }
85
+ path.unshift(selector);
86
+ current = current.parentElement;
87
+ }
88
+ return path.join(" > ");
89
+ }
90
+ destroy() {
91
+ if (this.boundHandler) {
92
+ document.removeEventListener("click", this.boundHandler, true);
93
+ }
94
+ }
95
+ }
@@ -0,0 +1,15 @@
1
+ import type { Plugin, PluginContext } from "../../core/types";
2
+ export declare class ErrorTrackingPlugin implements Plugin {
3
+ name: string;
4
+ version: string;
5
+ private context?;
6
+ private logger;
7
+ private boundErrorHandler?;
8
+ private boundRejectionHandler?;
9
+ init(context: PluginContext): void;
10
+ private attachHandlers;
11
+ private handleError;
12
+ private handleRejection;
13
+ trackError(error: Error | string): void;
14
+ destroy(): void;
15
+ }
@@ -0,0 +1,65 @@
1
+ import { isBrowser } from "../../utils/helpers";
2
+ import { getLogger } from "../../utils/logging";
3
+ export class ErrorTrackingPlugin {
4
+ constructor() {
5
+ this.name = "ErrorTrackingPlugin";
6
+ this.version = "1.0.0";
7
+ this.logger = getLogger();
8
+ }
9
+ init(context) {
10
+ if (!isBrowser()) {
11
+ this.logger.warn("Error tracking only available in browser");
12
+ return;
13
+ }
14
+ this.context = context;
15
+ this.attachHandlers();
16
+ this.logger.info("Error tracking initialized");
17
+ }
18
+ attachHandlers() {
19
+ this.boundErrorHandler = this.handleError.bind(this);
20
+ window.addEventListener("error", this.boundErrorHandler);
21
+ this.boundRejectionHandler = this.handleRejection.bind(this);
22
+ window.addEventListener("unhandledrejection", this.boundRejectionHandler);
23
+ }
24
+ handleError(e) {
25
+ const errorData = {
26
+ errorMessage: e.message,
27
+ errorStack: e.error?.stack,
28
+ errorType: e.error?.name || "Error",
29
+ filename: e.filename,
30
+ lineNumber: e.lineno,
31
+ columnNumber: e.colno,
32
+ };
33
+ if (this.context) {
34
+ this.context.track("ERROR", errorData);
35
+ }
36
+ }
37
+ handleRejection(e) {
38
+ const errorData = {
39
+ errorMessage: e.reason?.message || String(e.reason),
40
+ errorStack: e.reason?.stack,
41
+ errorType: "UnhandledPromiseRejection",
42
+ };
43
+ if (this.context) {
44
+ this.context.track("ERROR", errorData);
45
+ }
46
+ }
47
+ trackError(error) {
48
+ const errorData = {
49
+ errorMessage: typeof error === "string" ? error : error.message,
50
+ errorStack: typeof error === "string" ? undefined : error.stack,
51
+ errorType: typeof error === "string" ? "Error" : error.name,
52
+ };
53
+ if (this.context) {
54
+ this.context.track("ERROR", errorData);
55
+ }
56
+ }
57
+ destroy() {
58
+ if (this.boundErrorHandler) {
59
+ window.removeEventListener("error", this.boundErrorHandler);
60
+ }
61
+ if (this.boundRejectionHandler) {
62
+ window.removeEventListener("unhandledrejection", this.boundRejectionHandler);
63
+ }
64
+ }
65
+ }
@@ -0,0 +1,13 @@
1
+ import type { Plugin, PluginContext } from "../../core/types";
2
+ export declare class FormTrackingPlugin implements Plugin {
3
+ name: string;
4
+ version: string;
5
+ private context?;
6
+ private logger;
7
+ private boundHandler?;
8
+ init(context: PluginContext): void;
9
+ private attachHandlers;
10
+ private handleSubmit;
11
+ private extractFormData;
12
+ destroy(): void;
13
+ }
@@ -0,0 +1,54 @@
1
+ import { isBrowser } from "../../utils/helpers";
2
+ import { getLogger } from "../../utils/logging";
3
+ export class FormTrackingPlugin {
4
+ constructor() {
5
+ this.name = "FormTrackingPlugin";
6
+ this.version = "1.0.0";
7
+ this.logger = getLogger();
8
+ }
9
+ init(context) {
10
+ if (!isBrowser()) {
11
+ this.logger.warn("Form tracking only available in browser");
12
+ return;
13
+ }
14
+ this.context = context;
15
+ this.attachHandlers();
16
+ this.logger.info("Form tracking initialized");
17
+ }
18
+ attachHandlers() {
19
+ this.boundHandler = this.handleSubmit.bind(this);
20
+ document.addEventListener("submit", this.boundHandler, true);
21
+ }
22
+ handleSubmit(e) {
23
+ const form = e.target;
24
+ const formData = this.extractFormData(form);
25
+ if (this.context) {
26
+ this.context.track("FORM_SUBMIT", formData);
27
+ }
28
+ }
29
+ extractFormData(form) {
30
+ const data = {
31
+ formId: form.id || undefined,
32
+ formName: form.name || undefined,
33
+ formAction: form.action || undefined,
34
+ formMethod: form.method || undefined,
35
+ };
36
+ const fields = [];
37
+ const formElements = form.elements;
38
+ for (let i = 0; i < formElements.length; i++) {
39
+ const element = formElements[i];
40
+ if (element.name) {
41
+ fields.push(element.name);
42
+ }
43
+ }
44
+ if (fields.length > 0) {
45
+ data.fields = fields;
46
+ }
47
+ return data;
48
+ }
49
+ destroy() {
50
+ if (this.boundHandler) {
51
+ document.removeEventListener("submit", this.boundHandler, true);
52
+ }
53
+ }
54
+ }
@@ -0,0 +1,14 @@
1
+ import type { Plugin, PluginContext, PageViewEvent } from "../../core/types";
2
+ export declare class PageTrackingPlugin implements Plugin {
3
+ name: string;
4
+ version: string;
5
+ private context?;
6
+ private logger;
7
+ private lastUrl?;
8
+ init(context: PluginContext): void;
9
+ private attachHandlers;
10
+ private trackCurrentPage;
11
+ private getPageName;
12
+ trackPage(data: Partial<PageViewEvent>): void;
13
+ destroy(): void;
14
+ }
@@ -0,0 +1,71 @@
1
+ import { isBrowser } from "../../utils/helpers";
2
+ import { getLogger } from "../../utils/logging";
3
+ export class PageTrackingPlugin {
4
+ constructor() {
5
+ this.name = "PageTrackingPlugin";
6
+ this.version = "1.0.0";
7
+ this.logger = getLogger();
8
+ }
9
+ init(context) {
10
+ if (!isBrowser()) {
11
+ this.logger.warn("Page tracking only available in browser");
12
+ return;
13
+ }
14
+ this.context = context;
15
+ this.attachHandlers();
16
+ this.trackCurrentPage();
17
+ this.logger.info("Page tracking initialized");
18
+ }
19
+ attachHandlers() {
20
+ window.addEventListener("load", () => this.trackCurrentPage());
21
+ window.addEventListener("popstate", () => this.trackCurrentPage());
22
+ window.addEventListener("hashchange", () => this.trackCurrentPage());
23
+ }
24
+ trackCurrentPage() {
25
+ const url = window.location.href;
26
+ if (url === this.lastUrl) {
27
+ return;
28
+ }
29
+ this.lastUrl = url;
30
+ const pageData = {
31
+ pageName: this.getPageName(),
32
+ url: url,
33
+ referrer: document.referrer || undefined,
34
+ title: document.title || undefined,
35
+ };
36
+ if (this.context) {
37
+ this.context.track("PAGE_VIEW", pageData);
38
+ }
39
+ }
40
+ getPageName() {
41
+ if (document.title) {
42
+ return document.title;
43
+ }
44
+ const pathname = window.location.pathname;
45
+ if (pathname === "/" || pathname === "") {
46
+ return "Home";
47
+ }
48
+ return pathname
49
+ .split("/")
50
+ .filter((part) => part)
51
+ .map((part) => part
52
+ .split("-")
53
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
54
+ .join(" "))
55
+ .join(" - ");
56
+ }
57
+ trackPage(data) {
58
+ const pageData = {
59
+ pageName: data.pageName || this.getPageName(),
60
+ url: data.url || window.location.href,
61
+ referrer: data.referrer || document.referrer || undefined,
62
+ title: data.title || document.title || undefined,
63
+ };
64
+ if (this.context) {
65
+ this.context.track("PAGE_VIEW", pageData);
66
+ }
67
+ this.lastUrl = pageData.url;
68
+ }
69
+ destroy() {
70
+ }
71
+ }
@@ -0,0 +1,18 @@
1
+ import type { Plugin, PluginContext, TrackingEvent } from "../../core/types";
2
+ export declare class SessionPlugin implements Plugin {
3
+ name: string;
4
+ version: string;
5
+ private context?;
6
+ private sessionId;
7
+ private tabId;
8
+ private logger;
9
+ init(context: PluginContext): void;
10
+ processEvent(event: TrackingEvent): void;
11
+ private startSession;
12
+ private getTabId;
13
+ private storeSession;
14
+ private extendSession;
15
+ getSessionId(): string | null;
16
+ terminateSession(): void;
17
+ destroy(): void;
18
+ }
@@ -0,0 +1,81 @@
1
+ import { generateId } from "../../utils/helpers";
2
+ import { getLogger } from "../../utils/logging";
3
+ const SESSION_KEY = "ar_session_id";
4
+ const TAB_KEY = "ar_tab_id";
5
+ const SESSION_EXPIRATION_MINUTES = 30;
6
+ export class SessionPlugin {
7
+ constructor() {
8
+ this.name = "SessionPlugin";
9
+ this.version = "1.0.0";
10
+ this.sessionId = null;
11
+ this.tabId = null;
12
+ this.logger = getLogger();
13
+ }
14
+ init(context) {
15
+ this.context = context;
16
+ this.startSession();
17
+ }
18
+ processEvent(event) {
19
+ if (!this.sessionId) {
20
+ this.startSession();
21
+ }
22
+ event.sessionId = this.sessionId ?? undefined;
23
+ this.extendSession();
24
+ }
25
+ startSession() {
26
+ this.tabId = this.getTabId();
27
+ this.sessionId = generateId();
28
+ this.storeSession();
29
+ this.logger.debug("Session started:", this.sessionId);
30
+ }
31
+ getTabId() {
32
+ if (typeof sessionStorage === "undefined") {
33
+ return generateId();
34
+ }
35
+ let tabId = sessionStorage.getItem(TAB_KEY);
36
+ if (!tabId) {
37
+ tabId = `${generateId()}-${Date.now()}`;
38
+ sessionStorage.setItem(TAB_KEY, tabId);
39
+ }
40
+ return tabId;
41
+ }
42
+ storeSession() {
43
+ if (!this.context || !this.sessionId || !this.tabId) {
44
+ return;
45
+ }
46
+ const expirationDate = new Date();
47
+ expirationDate.setMinutes(expirationDate.getMinutes() + SESSION_EXPIRATION_MINUTES);
48
+ const cookieName = `${SESSION_KEY}_${this.tabId}`;
49
+ this.context.storage.setItem(cookieName, this.sessionId, {
50
+ expires: expirationDate,
51
+ secure: true,
52
+ sameSite: "lax",
53
+ });
54
+ }
55
+ extendSession() {
56
+ if (!this.sessionId) {
57
+ return;
58
+ }
59
+ this.storeSession();
60
+ }
61
+ getSessionId() {
62
+ if (!this.sessionId && this.context) {
63
+ const tabId = this.getTabId();
64
+ const cookieName = `${SESSION_KEY}_${tabId}`;
65
+ this.sessionId = this.context.storage.getItem(cookieName);
66
+ }
67
+ return this.sessionId;
68
+ }
69
+ terminateSession() {
70
+ if (!this.context || !this.tabId) {
71
+ return;
72
+ }
73
+ const cookieName = `${SESSION_KEY}_${this.tabId}`;
74
+ this.context.storage.removeItem(cookieName);
75
+ this.sessionId = null;
76
+ this.logger.debug("Session terminated");
77
+ }
78
+ destroy() {
79
+ this.terminateSession();
80
+ }
81
+ }
@@ -0,0 +1,20 @@
1
+ import type { Plugin, PluginContext, TrackingEvent, User } from "../../core/types";
2
+ export declare class UserPlugin implements Plugin {
3
+ name: string;
4
+ version: string;
5
+ private context?;
6
+ private user;
7
+ private anonymousId;
8
+ private logger;
9
+ init(context: PluginContext): void;
10
+ processEvent(event: TrackingEvent): void;
11
+ identify(user: User): Promise<void>;
12
+ private generateAnonymousId;
13
+ private loadUser;
14
+ private storeUser;
15
+ getUser(): User | null;
16
+ getUserId(): string | null;
17
+ logout(): void;
18
+ private updateAnonymousEvents;
19
+ destroy(): void;
20
+ }
@@ -0,0 +1,152 @@
1
+ import { generateId, getEnvironmentType } from "../../utils/helpers";
2
+ import { validateUser } from "../../utils/validation";
3
+ import { getLogger } from "../../utils/logging";
4
+ const USER_KEY = "ar_user";
5
+ const ANONYMOUS_ID_KEY = "ar_anonymous_id";
6
+ export class UserPlugin {
7
+ constructor() {
8
+ this.name = "UserPlugin";
9
+ this.version = "1.0.0";
10
+ this.user = null;
11
+ this.anonymousId = null;
12
+ this.logger = getLogger();
13
+ }
14
+ init(context) {
15
+ this.context = context;
16
+ this.loadUser();
17
+ if (!this.user) {
18
+ this.generateAnonymousId();
19
+ }
20
+ }
21
+ processEvent(event) {
22
+ if (this.user) {
23
+ event.userId = this.user.email;
24
+ event.data = {
25
+ ...event.data,
26
+ user: this.user,
27
+ };
28
+ }
29
+ else if (this.anonymousId) {
30
+ event.userId = this.anonymousId;
31
+ }
32
+ }
33
+ async identify(user) {
34
+ try {
35
+ const validatedUser = validateUser(user);
36
+ this.user = validatedUser;
37
+ this.storeUser(validatedUser);
38
+ if (this.context && this.anonymousId) {
39
+ const previousAnonymousId = this.anonymousId;
40
+ this.context.storage.removeItem(ANONYMOUS_ID_KEY);
41
+ this.context.track("IDENTIFY", {
42
+ anonymousId: previousAnonymousId,
43
+ email: validatedUser.email,
44
+ });
45
+ if (getEnvironmentType() === "browser") {
46
+ await this.updateAnonymousEvents(validatedUser.email, previousAnonymousId);
47
+ }
48
+ this.anonymousId = null;
49
+ }
50
+ this.logger.info("User identified:", validatedUser.email);
51
+ }
52
+ catch (error) {
53
+ this.logger.error("Failed to identify user:", error);
54
+ throw error;
55
+ }
56
+ }
57
+ generateAnonymousId() {
58
+ if (!this.context) {
59
+ return;
60
+ }
61
+ this.anonymousId = this.context.storage.getItem(ANONYMOUS_ID_KEY);
62
+ if (!this.anonymousId) {
63
+ this.anonymousId = generateId();
64
+ this.context.storage.setItem(ANONYMOUS_ID_KEY, this.anonymousId);
65
+ this.logger.debug("Anonymous ID generated:", this.anonymousId);
66
+ }
67
+ }
68
+ loadUser() {
69
+ if (!this.context) {
70
+ return;
71
+ }
72
+ const storedUser = this.context.storage.getItem(USER_KEY);
73
+ if (storedUser) {
74
+ try {
75
+ this.user = JSON.parse(storedUser);
76
+ this.logger.debug("User loaded from storage:", this.user?.email);
77
+ }
78
+ catch (error) {
79
+ this.logger.error("Failed to parse stored user:", error);
80
+ }
81
+ }
82
+ }
83
+ storeUser(user) {
84
+ if (!this.context) {
85
+ return;
86
+ }
87
+ try {
88
+ this.context.storage.setItem(USER_KEY, JSON.stringify(user));
89
+ }
90
+ catch (error) {
91
+ this.logger.error("Failed to store user:", error);
92
+ }
93
+ }
94
+ getUser() {
95
+ return this.user;
96
+ }
97
+ getUserId() {
98
+ return this.user?.email ?? this.anonymousId;
99
+ }
100
+ logout() {
101
+ if (!this.context) {
102
+ return;
103
+ }
104
+ this.context.storage.removeItem(USER_KEY);
105
+ this.user = null;
106
+ this.generateAnonymousId();
107
+ this.logger.info("User logged out");
108
+ }
109
+ async updateAnonymousEvents(email, anonymousId) {
110
+ if (!this.context) {
111
+ return;
112
+ }
113
+ const { config, transport } = this.context;
114
+ if (!transport.update) {
115
+ this.logger.debug("Transport does not support update, skipping event tagging");
116
+ return;
117
+ }
118
+ let updateEndpoint = config.updateEndpoint;
119
+ if (!updateEndpoint) {
120
+ if (config.apiKey) {
121
+ updateEndpoint = "https://telemetry.armco.dev/events/tag";
122
+ }
123
+ else if (config.endpoint) {
124
+ updateEndpoint = config.endpoint.replace("/add", "/tag");
125
+ }
126
+ else {
127
+ this.logger.warn("No update endpoint configured, skipping event tagging");
128
+ return;
129
+ }
130
+ }
131
+ try {
132
+ this.logger.info(`Updating anonymous events (${anonymousId}) with user identity (${email})`);
133
+ const response = await transport.update(updateEndpoint, {
134
+ email,
135
+ anonymousId,
136
+ });
137
+ if (response.success) {
138
+ this.logger.info(`Successfully tagged ${anonymousId} events with user ${email}`);
139
+ }
140
+ else {
141
+ this.logger.warn(`Failed to tag events: ${response.error || "Unknown error"}`);
142
+ }
143
+ }
144
+ catch (error) {
145
+ this.logger.error("Error updating anonymous events:", error);
146
+ }
147
+ }
148
+ destroy() {
149
+ this.user = null;
150
+ this.anonymousId = null;
151
+ }
152
+ }