@neomanex/analytics-nuxt 1.0.2 → 1.0.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.
package/dist/module.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { defineNuxtModule, createResolver, addServerHandler, addPlugin, addImports, addTypeTemplate } from '@nuxt/kit';
1
+ import { defineNuxtModule, createResolver, addServerHandler, addServerPlugin, addPlugin, addImports, addTypeTemplate } from '@nuxt/kit';
2
2
  import { defu } from 'defu';
3
3
 
4
4
  const module = defineNuxtModule({
@@ -48,6 +48,7 @@ const module = defineNuxtModule({
48
48
  route: "/api/_analytics",
49
49
  handler: resolver.resolve("./runtime/server/api/_analytics.post")
50
50
  });
51
+ addServerPlugin(resolver.resolve("./runtime/server/plugins/correlation-meta"));
51
52
  addPlugin({
52
53
  src: resolver.resolve("./runtime/plugin.server"),
53
54
  mode: "server"
@@ -25,10 +25,19 @@ export class AnalyticsClient {
25
25
  event_type: eventType,
26
26
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
27
27
  source: this.config.source,
28
+ correlation_id: options?.correlation_id,
28
29
  metadata: metadata ?? void 0,
29
30
  account_id: options?.account_id,
30
- user_id: options?.user_id
31
+ user_id: options?.user_id,
32
+ client_ip: options?.client_ip,
33
+ user_agent: options?.user_agent,
34
+ referer: options?.referer,
35
+ accept_language: options?.accept_language
31
36
  };
37
+ if (event.metadata?.correlation_id) {
38
+ const { correlation_id: _, ...rest } = event.metadata;
39
+ event.metadata = Object.keys(rest).length > 0 ? rest : void 0;
40
+ }
32
41
  if (this.queue.length >= this.config.maxQueueSize) {
33
42
  this.handleQueueOverflow(event);
34
43
  return;
@@ -72,17 +81,28 @@ export class AnalyticsClient {
72
81
  get queueLength() {
73
82
  return this.queue.length;
74
83
  }
84
+ /**
85
+ * Get the configured batch endpoint path (for sendBeacon usage).
86
+ */
87
+ get batchEndpointUrl() {
88
+ return this.config.batchEndpoint;
89
+ }
75
90
  /**
76
91
  * Build an event payload for sendBeacon usage (browser page.leave).
77
92
  * Returns the JSON string to send via navigator.sendBeacon.
78
93
  */
79
- buildBeaconPayload(eventType, metadata) {
94
+ buildBeaconPayload(eventType, metadata, options) {
80
95
  const event = {
81
96
  event_type: eventType,
82
97
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
83
98
  source: this.config.source,
99
+ correlation_id: options?.correlation_id,
84
100
  metadata
85
101
  };
102
+ if (event.metadata?.correlation_id) {
103
+ const { correlation_id: _, ...rest } = event.metadata;
104
+ event.metadata = Object.keys(rest).length > 0 ? rest : void 0;
105
+ }
86
106
  return JSON.stringify({ events: [event] });
87
107
  }
88
108
  /**
@@ -114,7 +134,7 @@ export class AnalyticsClient {
114
134
  * Send a batch of events via ofetch.
115
135
  */
116
136
  async sendBatch(events) {
117
- await $fetch("/api/v1/events/batch", {
137
+ await $fetch(this.config.batchEndpoint, {
118
138
  baseURL: this.config.apiUrl,
119
139
  method: "POST",
120
140
  body: { events }
@@ -4,11 +4,13 @@ export const DEFAULT_MAX_QUEUE_SIZE = 1e3;
4
4
  export const DEFAULT_MAX_RETRIES = 3;
5
5
  export const DEFAULT_RETRY_DELAY = 1e3;
6
6
  export const DEFAULT_RETRY_BACKOFF_MULTIPLIER = 2;
7
+ export const DEFAULT_BATCH_ENDPOINT = "/api/v1/events/batch";
7
8
  export const OVERFLOW_WARNING_DEBOUNCE_MS = 6e4;
8
9
  export function resolveConfig(config) {
9
10
  return {
10
11
  apiUrl: config.apiUrl,
11
12
  source: config.source,
13
+ batchEndpoint: config.batchEndpoint ?? DEFAULT_BATCH_ENDPOINT,
12
14
  flushInterval: config.flushInterval ?? DEFAULT_FLUSH_INTERVAL,
13
15
  flushSize: config.flushSize ?? DEFAULT_FLUSH_SIZE,
14
16
  maxQueueSize: config.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE,
@@ -37,7 +37,8 @@ export default defineNuxtPlugin(() => {
37
37
  client = getInstance();
38
38
  } catch {
39
39
  client = init({
40
- apiUrl: "/api/_analytics",
40
+ apiUrl: "",
41
+ batchEndpoint: "/api/_analytics",
41
42
  source: publicConfig.source,
42
43
  flushInterval: 1e4,
43
44
  flushSize: 5
@@ -55,8 +56,9 @@ export default defineNuxtPlugin(() => {
55
56
  screen_height: window.screen.height,
56
57
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
57
58
  language: navigator.language,
58
- correlation_id: correlationId,
59
59
  session_id: sessionId
60
+ }, {
61
+ correlation_id: correlationId
60
62
  });
61
63
  isFirstPageView = false;
62
64
  }
@@ -71,8 +73,9 @@ export default defineNuxtPlugin(() => {
71
73
  query: to.query,
72
74
  title: typeof document !== "undefined" ? document.title : void 0,
73
75
  from_path: from.path,
74
- correlation_id: getCorrelationId(),
75
76
  session_id: sessionId
77
+ }, {
78
+ correlation_id: getCorrelationId()
76
79
  });
77
80
  });
78
81
  }
@@ -84,10 +87,11 @@ export default defineNuxtPlugin(() => {
84
87
  path: window.location.pathname,
85
88
  duration_ms: durationMs,
86
89
  scroll_depth_pct: getScrollDepthPct(),
87
- correlation_id: getCorrelationId(),
88
90
  session_id: sessionId
91
+ }, {
92
+ correlation_id: getCorrelationId()
89
93
  });
90
- navigator.sendBeacon("/api/_analytics", payload);
94
+ navigator.sendBeacon(client.batchEndpointUrl, payload);
91
95
  });
92
96
  }
93
97
  return {
@@ -33,19 +33,22 @@ export default defineEventHandler(async (event) => {
33
33
  const acceptLanguage = getRequestHeader(event, "accept-language");
34
34
  const origin = getRequestHeader(event, "origin");
35
35
  const correlationId = getRequestHeader(event, "x-correlation-id");
36
- const enrichedEvents = body.events.map((evt) => ({
37
- ...evt,
38
- source: evt.source || analyticsConfig.source,
39
- client_ip: clientIp,
40
- user_agent: userAgent,
41
- referer,
42
- accept_language: acceptLanguage,
43
- metadata: {
44
- ...evt.metadata,
45
- origin,
46
- correlation_id: correlationId ?? evt.metadata?.correlation_id
47
- }
48
- }));
36
+ const enrichedEvents = body.events.map((evt) => {
37
+ const { correlation_id: metadataCorrelationId, ...restMetadata } = evt.metadata ?? {};
38
+ return {
39
+ ...evt,
40
+ source: evt.source || analyticsConfig.source,
41
+ correlation_id: correlationId ?? evt.correlation_id ?? metadataCorrelationId,
42
+ client_ip: clientIp,
43
+ user_agent: userAgent,
44
+ referer,
45
+ accept_language: acceptLanguage,
46
+ metadata: {
47
+ ...restMetadata,
48
+ origin
49
+ }
50
+ };
51
+ });
49
52
  try {
50
53
  const response = await $fetch("/api/v1/events/batch", {
51
54
  baseURL: analyticsConfig.apiUrl,
@@ -1,5 +1,5 @@
1
1
  import { defineEventHandler, getRequestHeader, setResponseHeader, useRuntimeConfig, getRequestURL } from "#imports";
2
- import { getInstance } from "../../client.js";
2
+ import { getInstance, init } from "../../client.js";
3
3
  const EXCLUDED_PREFIXES = ["/api/", "/_nuxt/", "/__nuxt_error"];
4
4
  const EXCLUDED_EXACT = ["/favicon.ico", "/_health", "/healthz"];
5
5
  function extractClientIp(event) {
@@ -43,6 +43,7 @@ export default defineEventHandler((event) => {
43
43
  }
44
44
  const correlationId = generateCorrelationId();
45
45
  setResponseHeader(event, "X-Correlation-Id", correlationId);
46
+ event.context.correlationId = correlationId;
46
47
  const clientIp = extractClientIp(event);
47
48
  const userAgent = getRequestHeader(event, "user-agent");
48
49
  const referer = getRequestHeader(event, "referer");
@@ -51,16 +52,32 @@ export default defineEventHandler((event) => {
51
52
  url.searchParams.forEach((value, key) => {
52
53
  query[key] = value;
53
54
  });
55
+ let client;
56
+ try {
57
+ client = getInstance();
58
+ } catch {
59
+ try {
60
+ const analyticsApiConfig = config.analytics;
61
+ client = init({
62
+ apiUrl: analyticsApiConfig.apiUrl,
63
+ source: analyticsApiConfig.source,
64
+ flushInterval: 5e3,
65
+ flushSize: 20
66
+ });
67
+ } catch {
68
+ return;
69
+ }
70
+ }
54
71
  try {
55
- const client = getInstance();
56
72
  client.track("page.visit", {
57
73
  path,
58
- query: Object.keys(query).length > 0 ? query : void 0,
59
- correlation_id: correlationId,
74
+ query: Object.keys(query).length > 0 ? query : void 0
75
+ }, {
60
76
  client_ip: clientIp,
61
77
  user_agent: userAgent,
62
78
  referer,
63
- accept_language: acceptLanguage
79
+ accept_language: acceptLanguage,
80
+ correlation_id: correlationId
64
81
  });
65
82
  } catch {
66
83
  }
@@ -0,0 +1,9 @@
1
+ import { defineNitroPlugin } from "#imports";
2
+ export default defineNitroPlugin((nitroApp) => {
3
+ nitroApp.hooks.hook("render:html", (html, { event }) => {
4
+ const correlationId = event.context.correlationId;
5
+ if (correlationId) {
6
+ html.head.push(`<meta name="x-correlation-id" content="${correlationId}">`);
7
+ }
8
+ });
9
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neomanex/analytics-nuxt",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Nuxt module for analytics tracking with SSR middleware, browser plugin, and server proxy",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",