@nextlytics/core 0.3.1 → 0.4.0-canary.101

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.
@@ -6,6 +6,7 @@ import 'next/dist/server/web/spec-extension/cookies';
6
6
  type DispatchEvent = (event: NextlyticsEvent, ctx: RequestContext, policyFilter?: PageViewDelivery | "client-actions") => DispatchResult;
7
7
  type UpdateEvent = (eventId: string, patch: Partial<NextlyticsEvent>, ctx: RequestContext) => Promise<void>;
8
8
  declare function getUserContext(config: NextlyticsConfigWithDefaults, ctx: RequestContext): Promise<UserContext | undefined>;
9
+ declare function getEventProps(config: NextlyticsConfigWithDefaults, ctx: RequestContext, userContext?: UserContext): Promise<Record<string, unknown> | undefined>;
9
10
  declare function handleEventPost(request: NextRequest, config: NextlyticsConfigWithDefaults, dispatchEvent: DispatchEvent, updateEvent: UpdateEvent): Promise<Response>;
10
11
 
11
- export { type DispatchEvent, type UpdateEvent, getUserContext, handleEventPost };
12
+ export { type DispatchEvent, type UpdateEvent, getEventProps, getUserContext, handleEventPost };
@@ -18,6 +18,7 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var api_handler_exports = {};
20
20
  __export(api_handler_exports, {
21
+ getEventProps: () => getEventProps,
21
22
  getUserContext: () => getUserContext,
22
23
  handleEventPost: () => handleEventPost
23
24
  });
@@ -29,7 +30,8 @@ var import_anonymous_user = require("./anonymous-user");
29
30
  function createRequestContext(request) {
30
31
  return {
31
32
  headers: request.headers,
32
- cookies: request.cookies
33
+ cookies: request.cookies,
34
+ path: request.nextUrl.pathname
33
35
  };
34
36
  }
35
37
  async function getUserContext(config, ctx) {
@@ -40,6 +42,14 @@ async function getUserContext(config, ctx) {
40
42
  return void 0;
41
43
  }
42
44
  }
45
+ async function getEventProps(config, ctx, userContext) {
46
+ if (!config.callbacks.getProps) return void 0;
47
+ try {
48
+ return await config.callbacks.getProps({ ...ctx, user: userContext }) || void 0;
49
+ } catch {
50
+ return void 0;
51
+ }
52
+ }
43
53
  function reconstructServerContext(apiCallContext, clientContext) {
44
54
  const searchParams = {};
45
55
  if (clientContext.search) {
@@ -78,6 +88,8 @@ async function handleClientInit(request, hctx) {
78
88
  serverContext,
79
89
  config
80
90
  });
91
+ const pageCtx = { ...ctx, path: serverContext.path };
92
+ const propsFromCallback = await getEventProps(config, pageCtx, userContext);
81
93
  const isSoftNavigation = hctx.isSoftNavigation;
82
94
  const eventId = isSoftNavigation ? (0, import_uitils.generateId)() : pageRenderId;
83
95
  const event = {
@@ -90,7 +102,7 @@ async function handleClientInit(request, hctx) {
90
102
  serverContext,
91
103
  clientContext,
92
104
  userContext,
93
- properties: {}
105
+ properties: { ...propsFromCallback }
94
106
  };
95
107
  if (isSoftNavigation) {
96
108
  const { clientActions, completion: completion2 } = dispatchEvent(event, ctx);
@@ -115,6 +127,8 @@ async function handleClientEvent(request, hctx) {
115
127
  serverContext,
116
128
  config
117
129
  });
130
+ const pageCtx = { ...ctx, path: serverContext.path };
131
+ const propsFromCallback = await getEventProps(config, pageCtx, userContext);
118
132
  const event = {
119
133
  origin: "client",
120
134
  eventId: (0, import_uitils.generateId)(),
@@ -125,7 +139,7 @@ async function handleClientEvent(request, hctx) {
125
139
  serverContext,
126
140
  clientContext,
127
141
  userContext,
128
- properties: props || {}
142
+ properties: { ...propsFromCallback, ...props }
129
143
  };
130
144
  const { clientActions, completion } = dispatchEvent(event, ctx);
131
145
  const actions = await clientActions;
@@ -177,6 +191,7 @@ async function handleEventPost(request, config, dispatchEvent, updateEvent) {
177
191
  }
178
192
  // Annotate the CommonJS export names for ESM import in node:
179
193
  0 && (module.exports = {
194
+ getEventProps,
180
195
  getUserContext,
181
196
  handleEventPost
182
197
  });
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export { Nextlytics } from './server.js';
2
2
  export { getNextlyticsProps } from './pages-router.js';
3
3
  export { NextlyticsClient, NextlyticsContext, useNextlytics } from './client.js';
4
4
  export { loggingBackend } from './backends/logging.js';
5
+ export { PathMatcherOptions, pathMatcher } from './path-matcher.js';
5
6
  export { AnonymousUserResult, BackendConfigEntry, BackendWithConfig, ClientAction, ClientContext, ClientRequest, JavascriptTemplate, NextlyticsBackend, NextlyticsBackendFactory, NextlyticsClientContext, NextlyticsConfig, NextlyticsEvent, NextlyticsPlugin, NextlyticsPluginFactory, NextlyticsResult, NextlyticsServerSide, PageViewDelivery, PagesRouterContext, RequestContext, ServerEventContext, UserContext } from './types.js';
6
7
  import 'react/jsx-runtime';
7
8
  import 'react';
package/dist/index.js CHANGED
@@ -22,6 +22,7 @@ __export(index_exports, {
22
22
  NextlyticsClient: () => import_client.NextlyticsClient,
23
23
  getNextlyticsProps: () => import_pages_router.getNextlyticsProps,
24
24
  loggingBackend: () => import_logging.loggingBackend,
25
+ pathMatcher: () => import_path_matcher.pathMatcher,
25
26
  useNextlytics: () => import_client.useNextlytics
26
27
  });
27
28
  module.exports = __toCommonJS(index_exports);
@@ -29,11 +30,13 @@ var import_server = require("./server");
29
30
  var import_pages_router = require("./pages-router");
30
31
  var import_client = require("./client");
31
32
  var import_logging = require("./backends/logging");
33
+ var import_path_matcher = require("./path-matcher");
32
34
  // Annotate the CommonJS export names for ESM import in node:
33
35
  0 && (module.exports = {
34
36
  Nextlytics,
35
37
  NextlyticsClient,
36
38
  getNextlyticsProps,
37
39
  loggingBackend,
40
+ pathMatcher,
38
41
  useNextlytics
39
42
  });
@@ -29,7 +29,8 @@ var import_api_handler = require("./api-handler");
29
29
  function createRequestContext(request) {
30
30
  return {
31
31
  headers: request.headers,
32
- cookies: request.cookies
32
+ cookies: request.cookies,
33
+ path: request.nextUrl.pathname
33
34
  };
34
35
  }
35
36
  function createNextlyticsMiddleware(config, dispatchEvent, updateEvent) {
@@ -71,10 +72,14 @@ function createNextlyticsMiddleware(config, dispatchEvent, updateEvent) {
71
72
  return Response.json({ error: "Method not allowed" }, { status: 405 });
72
73
  }
73
74
  if (reqInfo.isNextjsInternal || reqInfo.isPrefetch || reqInfo.isStaticFile) {
74
- return import_server.NextResponse.next();
75
+ const response2 = import_server.NextResponse.next();
76
+ response2.headers.set(import_server_component_context.headerNames.active, "1");
77
+ return response2;
75
78
  }
76
79
  if (!reqInfo.isPageNavigation && !config.isApiPath(pathname)) {
77
- return import_server.NextResponse.next();
80
+ const response2 = import_server.NextResponse.next();
81
+ response2.headers.set(import_server_component_context.headerNames.active, "1");
82
+ return response2;
78
83
  }
79
84
  const pageRenderId = (0, import_uitils.generateId)();
80
85
  const serverContext = (0, import_uitils.createServerContext)(request);
@@ -102,12 +107,14 @@ function createNextlyticsMiddleware(config, dispatchEvent, updateEvent) {
102
107
  return response;
103
108
  }
104
109
  const userContext = await (0, import_api_handler.getUserContext)(config, ctx);
110
+ const extraProps = await (0, import_api_handler.getEventProps)(config, ctx, userContext);
105
111
  const pageViewEvent = createPageViewEvent(
106
112
  pageRenderId,
107
113
  serverContext,
108
114
  isApiPath,
109
115
  userContext,
110
- anonId
116
+ anonId,
117
+ extraProps
111
118
  );
112
119
  const { clientActions, completion } = dispatchEvent(pageViewEvent, ctx, "on-request");
113
120
  const actions = await clientActions;
@@ -124,7 +131,7 @@ function createNextlyticsMiddleware(config, dispatchEvent, updateEvent) {
124
131
  return response;
125
132
  };
126
133
  }
127
- function createPageViewEvent(pageRenderId, serverContext, isApiPath, userContext, anonymousUserId) {
134
+ function createPageViewEvent(pageRenderId, serverContext, isApiPath, userContext, anonymousUserId, extraProps) {
128
135
  const eventType = isApiPath ? "apiCall" : "pageView";
129
136
  return {
130
137
  origin: "server",
@@ -134,7 +141,7 @@ function createPageViewEvent(pageRenderId, serverContext, isApiPath, userContext
134
141
  anonymousUserId,
135
142
  serverContext,
136
143
  userContext,
137
- properties: {}
144
+ properties: { ...extraProps }
138
145
  };
139
146
  }
140
147
  // Annotate the CommonJS export names for ESM import in node:
@@ -0,0 +1,20 @@
1
+ type PathMatcherOptions = {
2
+ /** Paths to exclude. Matches exact path or path prefix (with `/` boundary). */
3
+ not?: string | string[];
4
+ /** Allow partial matches — path can have fewer segments than pattern. */
5
+ prefix?: boolean;
6
+ };
7
+ /**
8
+ * Match a URL path against a Next.js-style `[param]` pattern.
9
+ *
10
+ * Returns extracted params on match, or `null` on mismatch.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * pathMatcher("/[workspace]/[project]", "/acme/myproject")
15
+ * // => { workspace: "acme", project: "myproject" }
16
+ * ```
17
+ */
18
+ declare function pathMatcher(pattern: string, path: string, opts?: PathMatcherOptions): Record<string, string> | null;
19
+
20
+ export { type PathMatcherOptions, pathMatcher };
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var path_matcher_exports = {};
20
+ __export(path_matcher_exports, {
21
+ pathMatcher: () => pathMatcher
22
+ });
23
+ module.exports = __toCommonJS(path_matcher_exports);
24
+ function pathMatcher(pattern, path, opts) {
25
+ if (opts?.not) {
26
+ const exclusions = Array.isArray(opts.not) ? opts.not : [opts.not];
27
+ for (const excl of exclusions) {
28
+ if (path === excl || path.startsWith(excl + "/")) {
29
+ return null;
30
+ }
31
+ }
32
+ }
33
+ const patternSegments = pattern.split("/").filter(Boolean);
34
+ const pathSegments = path.split("/").filter(Boolean);
35
+ if (opts?.prefix) {
36
+ if (pathSegments.length === 0) return null;
37
+ if (pathSegments.length > patternSegments.length) return null;
38
+ } else {
39
+ if (pathSegments.length !== patternSegments.length) return null;
40
+ }
41
+ const params = {};
42
+ const segmentsToMatch = Math.min(patternSegments.length, pathSegments.length);
43
+ for (let i = 0; i < segmentsToMatch; i++) {
44
+ const pat = patternSegments[i];
45
+ const seg = pathSegments[i];
46
+ const paramMatch = pat.match(/^\[(\w+)]$/);
47
+ if (paramMatch) {
48
+ params[paramMatch[1]] = decodeURIComponent(seg);
49
+ } else if (pat !== seg) {
50
+ return null;
51
+ }
52
+ }
53
+ return params;
54
+ }
55
+ // Annotate the CommonJS export names for ESM import in node:
56
+ 0 && (module.exports = {
57
+ pathMatcher
58
+ });
@@ -14,7 +14,7 @@ type VercelGeoPluginOptions = {
14
14
  * ```ts
15
15
  * import { vercelGeoPlugin } from "@nextlytics/core/plugins/vercel-geo";
16
16
  *
17
- * export const { middleware, handlers } = Nextlytics({
17
+ * export const { middleware } = Nextlytics({
18
18
  * plugins: [vercelGeoPlugin()],
19
19
  * // ...
20
20
  * });
@@ -7,6 +7,7 @@ declare const headerNames: {
7
7
  readonly search: "x-nl-search";
8
8
  readonly pageRenderId: "x-nl-page-render-id";
9
9
  readonly isSoftNavigation: "x-nl-is-soft-nav";
10
+ readonly active: "x-nl-active";
10
11
  readonly scripts: "x-nl-scripts";
11
12
  };
12
13
  declare const LAST_PAGE_RENDER_ID_COOKIE = "last-page-render-id";
@@ -30,6 +30,7 @@ const headerNames = {
30
30
  search: `${HEADER_PREFIX}search`,
31
31
  pageRenderId: `${HEADER_PREFIX}page-render-id`,
32
32
  isSoftNavigation: `${HEADER_PREFIX}is-soft-nav`,
33
+ active: `${HEADER_PREFIX}active`,
33
34
  scripts: `${HEADER_PREFIX}scripts`
34
35
  };
35
36
  const LAST_PAGE_RENDER_ID_COOKIE = "last-page-render-id";
package/dist/server.js CHANGED
@@ -31,6 +31,7 @@ var import_client = require("./client");
31
31
  var import_config_helpers = require("./config-helpers");
32
32
  var import_middleware = require("./middleware");
33
33
  var import_uitils = require("./uitils");
34
+ var import_api_handler = require("./api-handler");
34
35
  function isBackendWithConfig(entry) {
35
36
  return typeof entry === "object" && entry !== null && "backend" in entry;
36
37
  }
@@ -76,7 +77,8 @@ async function createRequestContext() {
76
77
  const [_cookies, _headers] = await Promise.all([(0, import_headers.cookies)(), (0, import_headers.headers)()]);
77
78
  return {
78
79
  cookies: _cookies,
79
- headers: _headers
80
+ headers: _headers,
81
+ path: _headers.get("x-nl-pathname") || ""
80
82
  };
81
83
  }
82
84
  function collectTemplates(config, ctx) {
@@ -177,7 +179,9 @@ function Nextlytics(userConfig) {
177
179
  const headersList = await (0, import_headers.headers)();
178
180
  const ctx = (0, import_server_component_context.restoreServerComponentContext)(headersList);
179
181
  if (!ctx) {
180
- console.warn("[Nextlytics] nextlyticsMiddleware should be added in order for Server to work");
182
+ if (!headersList.get(import_server_component_context.headerNames.active)) {
183
+ console.warn("[Nextlytics] nextlyticsMiddleware should be added in order for Server to work");
184
+ }
181
185
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children });
182
186
  }
183
187
  const requestCtx = await createRequestContext();
@@ -189,7 +193,11 @@ function Nextlytics(userConfig) {
189
193
  const cookieStore = await (0, import_headers.cookies)();
190
194
  const pageRenderId = headersList.get(import_server_component_context.headerNames.pageRenderId);
191
195
  const serverContext = createServerContextFromHeaders(headersList);
192
- const ctx = { headers: headersList, cookies: cookieStore };
196
+ const ctx = {
197
+ headers: headersList,
198
+ cookies: cookieStore,
199
+ path: headersList.get(import_server_component_context.headerNames.pathname) || ""
200
+ };
193
201
  const { anonId: anonymousUserId } = await (0, import_anonymous_user.resolveAnonymousUser)({ ctx, serverContext, config });
194
202
  let userContext;
195
203
  if (config.callbacks.getUser) {
@@ -198,6 +206,7 @@ function Nextlytics(userConfig) {
198
206
  } catch {
199
207
  }
200
208
  }
209
+ const propsFromCallback = await (0, import_api_handler.getEventProps)(config, ctx, userContext);
201
210
  return {
202
211
  sendEvent: async (eventName, opts) => {
203
212
  if (!pageRenderId) {
@@ -213,7 +222,7 @@ function Nextlytics(userConfig) {
213
222
  anonymousUserId,
214
223
  serverContext,
215
224
  userContext,
216
- properties: opts?.props || {}
225
+ properties: { ...propsFromCallback, ...opts?.props }
217
226
  };
218
227
  await dispatchEventInternal(event, ctx);
219
228
  return { ok: true };
package/dist/types.d.ts CHANGED
@@ -104,6 +104,7 @@ type AnonymousUserResult = {
104
104
  type RequestContext = {
105
105
  headers: Headers;
106
106
  cookies: Pick<RequestCookies, "get" | "getAll" | "has">;
107
+ path: string;
107
108
  };
108
109
  type NextlyticsPlugin = {
109
110
  /**
@@ -160,6 +161,14 @@ type NextlyticsConfig = {
160
161
  ctx: RequestContext;
161
162
  originalAnonymousUserId?: string;
162
163
  }) => Promise<AnonymousUserResult>;
164
+ /**
165
+ * Derive extra event properties from the request context.
166
+ * Called on every request; the returned object is merged into `event.properties`.
167
+ * User-provided properties (e.g. from `sendEvent` or client custom events) take priority.
168
+ */
169
+ getProps?: (ctx: RequestContext & {
170
+ user?: UserContext;
171
+ }) => Record<string, unknown> | undefined | Promise<Record<string, unknown> | undefined>;
163
172
  };
164
173
  /** Analytics backends to send events to */
165
174
  backends?: BackendConfigEntry[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextlytics/core",
3
- "version": "0.3.1",
3
+ "version": "0.4.0-canary.101",
4
4
  "description": "Analytics library for Next.js",
5
5
  "license": "MIT",
6
6
  "repository": {