@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.
- package/dist/api-handler.d.ts +2 -1
- package/dist/api-handler.js +18 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -0
- package/dist/middleware.js +13 -6
- package/dist/path-matcher.d.ts +20 -0
- package/dist/path-matcher.js +58 -0
- package/dist/plugins/vercel-geo.d.ts +1 -1
- package/dist/server-component-context.d.ts +1 -0
- package/dist/server-component-context.js +1 -0
- package/dist/server.js +13 -4
- package/dist/types.d.ts +9 -0
- package/package.json +1 -1
package/dist/api-handler.d.ts
CHANGED
|
@@ -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 };
|
package/dist/api-handler.js
CHANGED
|
@@ -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
|
});
|
package/dist/middleware.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 = {
|
|
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[];
|