@nextlytics/core 0.2.2-canary.68 → 0.2.2
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/backends/segment.js +17 -49
- package/dist/client.js +0 -5
- package/dist/config-helpers.d.ts +2 -2
- package/dist/config-helpers.js +17 -2
- package/dist/handlers.d.ts +4 -7
- package/dist/handlers.js +89 -6
- package/dist/index.d.ts +1 -1
- package/dist/middleware.d.ts +2 -2
- package/dist/middleware.js +67 -94
- package/dist/server.js +17 -24
- package/dist/types.d.ts +8 -26
- package/package.json +1 -1
package/dist/backends/segment.js
CHANGED
|
@@ -37,25 +37,6 @@ function segmentBackend(config) {
|
|
|
37
37
|
throw new Error(`Segment error ${res.status}: ${text}`);
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
-
function buildUrl(event) {
|
|
41
|
-
if (event.clientContext?.url) return event.clientContext.url;
|
|
42
|
-
const { host: host2, path, search } = event.serverContext;
|
|
43
|
-
const protocol = host2.includes("localhost") || host2.match(/^[\d.:]+$/) ? "http" : "https";
|
|
44
|
-
const searchStr = Object.entries(search).flatMap(([k, vals]) => vals.map((v) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)).join("&");
|
|
45
|
-
return `${protocol}://${host2}${path}${searchStr ? `?${searchStr}` : ""}`;
|
|
46
|
-
}
|
|
47
|
-
function getSearchString(event) {
|
|
48
|
-
if (event.clientContext?.search) return event.clientContext.search;
|
|
49
|
-
return Object.entries(event.serverContext.search).flatMap(([k, vals]) => vals.map((v) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)).join("&");
|
|
50
|
-
}
|
|
51
|
-
function getReferringDomain(referer) {
|
|
52
|
-
if (!referer) return void 0;
|
|
53
|
-
try {
|
|
54
|
-
return new URL(referer).hostname;
|
|
55
|
-
} catch {
|
|
56
|
-
return void 0;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
40
|
function buildContext(event) {
|
|
60
41
|
const ctx = {
|
|
61
42
|
ip: event.serverContext.ip
|
|
@@ -63,45 +44,32 @@ function segmentBackend(config) {
|
|
|
63
44
|
if (event.userContext?.traits) {
|
|
64
45
|
ctx.traits = event.userContext.traits;
|
|
65
46
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
title: cc?.title,
|
|
75
|
-
url: buildUrl(event)
|
|
76
|
-
};
|
|
77
|
-
if (cc) {
|
|
78
|
-
ctx.userAgent = cc.userAgent;
|
|
79
|
-
ctx.locale = cc.locale;
|
|
80
|
-
if (cc.screen) {
|
|
47
|
+
if (event.clientContext) {
|
|
48
|
+
ctx.userAgent = event.clientContext.userAgent;
|
|
49
|
+
ctx.locale = event.clientContext.locale;
|
|
50
|
+
ctx.page = {
|
|
51
|
+
path: event.clientContext.path,
|
|
52
|
+
referrer: event.clientContext.referer
|
|
53
|
+
};
|
|
54
|
+
if (event.clientContext.screen) {
|
|
81
55
|
ctx.screen = {
|
|
82
|
-
width:
|
|
83
|
-
height:
|
|
84
|
-
innerWidth:
|
|
85
|
-
innerHeight:
|
|
86
|
-
density:
|
|
56
|
+
width: event.clientContext.screen.width,
|
|
57
|
+
height: event.clientContext.screen.height,
|
|
58
|
+
innerWidth: event.clientContext.screen.innerWidth,
|
|
59
|
+
innerHeight: event.clientContext.screen.innerHeight,
|
|
60
|
+
density: event.clientContext.screen.density
|
|
87
61
|
};
|
|
88
62
|
}
|
|
89
63
|
}
|
|
90
64
|
return ctx;
|
|
91
65
|
}
|
|
92
66
|
function buildProperties(event) {
|
|
93
|
-
const cc = event.clientContext;
|
|
94
|
-
const sc = event.serverContext;
|
|
95
67
|
return {
|
|
96
68
|
parentEventId: event.parentEventId,
|
|
97
|
-
path:
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
title: cc?.title,
|
|
102
|
-
referrer: cc?.referer,
|
|
103
|
-
width: cc?.screen?.innerWidth,
|
|
104
|
-
height: cc?.screen?.innerHeight,
|
|
69
|
+
path: event.serverContext.path,
|
|
70
|
+
host: event.serverContext.host,
|
|
71
|
+
method: event.serverContext.method,
|
|
72
|
+
search: event.serverContext.search,
|
|
105
73
|
...event.properties
|
|
106
74
|
};
|
|
107
75
|
}
|
package/dist/client.js
CHANGED
|
@@ -39,11 +39,6 @@ function createClientContext() {
|
|
|
39
39
|
collectedAt: /* @__PURE__ */ new Date(),
|
|
40
40
|
referer: isBrowser ? document.referrer || void 0 : void 0,
|
|
41
41
|
path: isBrowser ? window.location.pathname : void 0,
|
|
42
|
-
url: isBrowser ? window.location.href : void 0,
|
|
43
|
-
host: isBrowser ? window.location.host : void 0,
|
|
44
|
-
search: isBrowser ? window.location.search : void 0,
|
|
45
|
-
hash: isBrowser ? window.location.hash : void 0,
|
|
46
|
-
title: isBrowser ? document.title : void 0,
|
|
47
42
|
screen: {
|
|
48
43
|
width: isBrowser ? window.screen.width : void 0,
|
|
49
44
|
height: isBrowser ? window.screen.height : void 0,
|
package/dist/config-helpers.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { NextlyticsConfig } from './types.js';
|
|
|
2
2
|
import 'next/dist/server/web/spec-extension/cookies';
|
|
3
3
|
import 'next/server';
|
|
4
4
|
|
|
5
|
-
type NextlyticsConfigWithDefaults = Required<Pick<NextlyticsConfig, "excludeApiCalls" | "eventEndpoint" | "isApiPath" | "backends">> & NextlyticsConfig & {
|
|
5
|
+
type NextlyticsConfigWithDefaults = Required<Pick<NextlyticsConfig, "pageViewMode" | "excludeApiCalls" | "eventEndpoint" | "isApiPath" | "backends">> & NextlyticsConfig & {
|
|
6
6
|
anonymousUsers: Required<NonNullable<NextlyticsConfig["anonymousUsers"]>>;
|
|
7
7
|
};
|
|
8
8
|
declare function withDefaults(config: NextlyticsConfig): NextlyticsConfigWithDefaults;
|
|
@@ -10,7 +10,7 @@ interface ConfigValidationResult {
|
|
|
10
10
|
valid: boolean;
|
|
11
11
|
warnings: string[];
|
|
12
12
|
}
|
|
13
|
-
declare function validateConfig(
|
|
13
|
+
declare function validateConfig(config: NextlyticsConfig): ConfigValidationResult;
|
|
14
14
|
declare function logConfigWarnings(result: ConfigValidationResult): void;
|
|
15
15
|
|
|
16
16
|
export { type ConfigValidationResult, type NextlyticsConfigWithDefaults, logConfigWarnings, validateConfig, withDefaults };
|
package/dist/config-helpers.js
CHANGED
|
@@ -26,6 +26,7 @@ module.exports = __toCommonJS(config_helpers_exports);
|
|
|
26
26
|
function withDefaults(config) {
|
|
27
27
|
return {
|
|
28
28
|
...config,
|
|
29
|
+
pageViewMode: config.pageViewMode ?? "server",
|
|
29
30
|
excludeApiCalls: config.excludeApiCalls ?? false,
|
|
30
31
|
eventEndpoint: config.eventEndpoint ?? "/api/event",
|
|
31
32
|
isApiPath: config.isApiPath ?? (() => false),
|
|
@@ -40,8 +41,22 @@ function withDefaults(config) {
|
|
|
40
41
|
}
|
|
41
42
|
};
|
|
42
43
|
}
|
|
43
|
-
function validateConfig(
|
|
44
|
-
|
|
44
|
+
function validateConfig(config) {
|
|
45
|
+
const warnings = [];
|
|
46
|
+
if (config.pageViewMode === "client-init" && config.backends?.length) {
|
|
47
|
+
const staticBackends = config.backends.filter((b) => typeof b !== "function");
|
|
48
|
+
const backendsWithoutUpdates = staticBackends.filter((b) => !b.supportsUpdates);
|
|
49
|
+
if (backendsWithoutUpdates.length > 0) {
|
|
50
|
+
const backendNames = backendsWithoutUpdates.map((b) => `"${b.name}"`).join(", ");
|
|
51
|
+
warnings.push(
|
|
52
|
+
`[Nextlytics] pageViewMode="client-init" requires backends that support updates. These don't: ${backendNames}`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
valid: warnings.length === 0,
|
|
58
|
+
warnings
|
|
59
|
+
};
|
|
45
60
|
}
|
|
46
61
|
function logConfigWarnings(result) {
|
|
47
62
|
for (const warning of result.warnings) {
|
package/dist/handlers.d.ts
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { NextRequest } from 'next/server';
|
|
2
|
+
import { NextlyticsEvent, RequestContext, DispatchResult } from './types.js';
|
|
3
|
+
import { NextlyticsConfigWithDefaults } from './config-helpers.js';
|
|
4
|
+
import 'next/dist/server/web/spec-extension/cookies';
|
|
2
5
|
|
|
3
6
|
type AppRouteHandlers = Record<"GET" | "POST", (req: NextRequest) => Promise<Response>>;
|
|
4
|
-
|
|
5
|
-
* Route handlers for /api/event (deprecated - middleware handles this now)
|
|
6
|
-
*
|
|
7
|
-
* Kept for backward compatibility. If you have mounted these handlers at /api/event,
|
|
8
|
-
* the middleware will intercept the request first, so these won't be called.
|
|
9
|
-
*/
|
|
10
|
-
declare function createHandlers(): AppRouteHandlers;
|
|
7
|
+
declare function createHandlers(config: NextlyticsConfigWithDefaults, dispatchEvent: (event: NextlyticsEvent, ctx: RequestContext) => DispatchResult, updateEvent: (eventId: string, patch: Partial<NextlyticsEvent>, ctx: RequestContext) => Promise<void>): AppRouteHandlers;
|
|
11
8
|
|
|
12
9
|
export { createHandlers };
|
package/dist/handlers.js
CHANGED
|
@@ -21,16 +21,99 @@ __export(handlers_exports, {
|
|
|
21
21
|
createHandlers: () => createHandlers
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(handlers_exports);
|
|
24
|
-
|
|
24
|
+
var import_server_component_context = require("./server-component-context");
|
|
25
|
+
var import_uitils = require("./uitils");
|
|
26
|
+
var import_anonymous_user = require("./anonymous-user");
|
|
27
|
+
function createRequestContext(request) {
|
|
28
|
+
return {
|
|
29
|
+
headers: request.headers,
|
|
30
|
+
cookies: request.cookies
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
async function getUserContext(config, ctx) {
|
|
34
|
+
if (!config.callbacks.getUser) return void 0;
|
|
35
|
+
return await config.callbacks.getUser(ctx) || void 0;
|
|
36
|
+
}
|
|
37
|
+
function createHandlers(config, dispatchEvent, updateEvent) {
|
|
25
38
|
return {
|
|
26
39
|
GET: async () => {
|
|
27
40
|
return Response.json({ status: "ok" });
|
|
28
41
|
},
|
|
29
|
-
POST: async () => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
{ status:
|
|
33
|
-
|
|
42
|
+
POST: async (req) => {
|
|
43
|
+
const pageRenderId = req.headers.get(import_server_component_context.headers.pageRenderId);
|
|
44
|
+
if (!pageRenderId) {
|
|
45
|
+
return Response.json({ error: "Missing page render ID" }, { status: 400 });
|
|
46
|
+
}
|
|
47
|
+
let body;
|
|
48
|
+
try {
|
|
49
|
+
body = await req.json();
|
|
50
|
+
} catch {
|
|
51
|
+
return Response.json({ error: "Invalid JSON" }, { status: 400 });
|
|
52
|
+
}
|
|
53
|
+
const { type, payload } = body;
|
|
54
|
+
const ctx = createRequestContext(req);
|
|
55
|
+
if (type === "client-init") {
|
|
56
|
+
const clientContext = payload;
|
|
57
|
+
const serverContext = (0, import_uitils.createServerContext)(req);
|
|
58
|
+
if (clientContext?.path) {
|
|
59
|
+
serverContext.path = clientContext.path;
|
|
60
|
+
}
|
|
61
|
+
const userContext = await getUserContext(config, ctx);
|
|
62
|
+
const { anonId: anonymousUserId } = await (0, import_anonymous_user.resolveAnonymousUser)({
|
|
63
|
+
ctx,
|
|
64
|
+
serverContext,
|
|
65
|
+
config
|
|
66
|
+
});
|
|
67
|
+
if (config.pageViewMode === "client-init") {
|
|
68
|
+
const event = {
|
|
69
|
+
eventId: pageRenderId,
|
|
70
|
+
type: "pageView",
|
|
71
|
+
collectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
72
|
+
anonymousUserId,
|
|
73
|
+
serverContext,
|
|
74
|
+
clientContext,
|
|
75
|
+
userContext,
|
|
76
|
+
properties: {}
|
|
77
|
+
};
|
|
78
|
+
const { clientActions, completion } = dispatchEvent(event, ctx);
|
|
79
|
+
const actions = await clientActions;
|
|
80
|
+
completion.catch((err) => console.warn("[Nextlytics] Dispatch completion error:", err));
|
|
81
|
+
const scripts = actions.items.filter((i) => i.type === "script-template");
|
|
82
|
+
return Response.json({ ok: true, scripts: scripts.length > 0 ? scripts : void 0 });
|
|
83
|
+
} else {
|
|
84
|
+
await updateEvent(pageRenderId, { clientContext, userContext, anonymousUserId }, ctx);
|
|
85
|
+
return Response.json({ ok: true });
|
|
86
|
+
}
|
|
87
|
+
} else if (type === "client-event") {
|
|
88
|
+
const clientContext = payload.clientContext || void 0;
|
|
89
|
+
const serverContext = (0, import_uitils.createServerContext)(req);
|
|
90
|
+
if (clientContext?.path) {
|
|
91
|
+
serverContext.path = clientContext.path;
|
|
92
|
+
}
|
|
93
|
+
const userContext = await getUserContext(config, ctx);
|
|
94
|
+
const { anonId: anonymousUserId } = await (0, import_anonymous_user.resolveAnonymousUser)({
|
|
95
|
+
ctx,
|
|
96
|
+
serverContext,
|
|
97
|
+
config
|
|
98
|
+
});
|
|
99
|
+
const event = {
|
|
100
|
+
eventId: (0, import_uitils.generateId)(),
|
|
101
|
+
parentEventId: pageRenderId,
|
|
102
|
+
type: payload.name || type,
|
|
103
|
+
collectedAt: payload.collectedAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
104
|
+
anonymousUserId,
|
|
105
|
+
serverContext,
|
|
106
|
+
clientContext,
|
|
107
|
+
userContext,
|
|
108
|
+
properties: payload.props || {}
|
|
109
|
+
};
|
|
110
|
+
const { clientActions, completion } = dispatchEvent(event, ctx);
|
|
111
|
+
const actions = await clientActions;
|
|
112
|
+
completion.catch((err) => console.warn("[Nextlytics] Dispatch completion error:", err));
|
|
113
|
+
const scripts = actions.items.filter((i) => i.type === "script-template");
|
|
114
|
+
return Response.json({ ok: true, scripts: scripts.length > 0 ? scripts : void 0 });
|
|
115
|
+
}
|
|
116
|
+
return Response.json({ ok: true });
|
|
34
117
|
}
|
|
35
118
|
};
|
|
36
119
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { Nextlytics, NextlyticsServer } from './server.js';
|
|
|
2
2
|
export { NextlyticsClient, NextlyticsContext, useNextlytics } from './client.js';
|
|
3
3
|
export { getNextlyticsProps } from './pages-router.js';
|
|
4
4
|
export { loggingBackend } from './backends/logging.js';
|
|
5
|
-
export { AnonymousUserResult,
|
|
5
|
+
export { AnonymousUserResult, ClientContext, NextlyticsBackend, NextlyticsBackendFactory, NextlyticsConfig, NextlyticsEvent, NextlyticsPlugin, NextlyticsPluginFactory, NextlyticsResult, NextlyticsServerSide, RequestContext, ServerEventContext, UserContext } from './types.js';
|
|
6
6
|
import 'react/jsx-runtime';
|
|
7
7
|
import 'react';
|
|
8
8
|
import 'next/dist/server/web/spec-extension/cookies';
|
package/dist/middleware.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { NextMiddleware } from 'next/server';
|
|
2
|
-
import { NextlyticsEvent, RequestContext,
|
|
2
|
+
import { NextlyticsEvent, RequestContext, DispatchResult } from './types.js';
|
|
3
3
|
import { NextlyticsConfigWithDefaults } from './config-helpers.js';
|
|
4
4
|
import 'next/dist/server/web/spec-extension/cookies';
|
|
5
5
|
|
|
6
|
-
type DispatchEvent = (event: NextlyticsEvent, ctx: RequestContext
|
|
6
|
+
type DispatchEvent = (event: NextlyticsEvent, ctx: RequestContext) => DispatchResult;
|
|
7
7
|
type UpdateEvent = (eventId: string, patch: Partial<NextlyticsEvent>, ctx: RequestContext) => Promise<void>;
|
|
8
8
|
declare function createNextlyticsMiddleware(config: NextlyticsConfigWithDefaults, dispatchEvent: DispatchEvent, updateEvent: UpdateEvent): NextMiddleware;
|
|
9
9
|
|
package/dist/middleware.js
CHANGED
|
@@ -25,23 +25,13 @@ var import_server = require("next/server");
|
|
|
25
25
|
var import_server_component_context = require("./server-component-context");
|
|
26
26
|
var import_uitils = require("./uitils");
|
|
27
27
|
var import_anonymous_user = require("./anonymous-user");
|
|
28
|
-
function isBackendWithConfig(entry) {
|
|
29
|
-
return typeof entry === "object" && entry !== null && "backend" in entry;
|
|
30
|
-
}
|
|
31
28
|
function resolveBackends(config, ctx) {
|
|
32
|
-
const
|
|
33
|
-
return
|
|
34
|
-
if (isBackendWithConfig(entry)) {
|
|
35
|
-
const backend2 = typeof entry.backend === "function" ? entry.backend(ctx) : entry.backend;
|
|
36
|
-
return backend2 ? { backend: backend2, ingestPolicy: entry.ingestPolicy ?? "immediate" } : null;
|
|
37
|
-
}
|
|
38
|
-
const backend = typeof entry === "function" ? entry(ctx) : entry;
|
|
39
|
-
return backend ? { backend, ingestPolicy: "immediate" } : null;
|
|
40
|
-
}).filter((b) => b !== null);
|
|
29
|
+
const backends = config.backends || [];
|
|
30
|
+
return backends.map((backend) => typeof backend === "function" ? backend(ctx) : backend).filter((b) => b !== null);
|
|
41
31
|
}
|
|
42
32
|
function collectTemplates(backends) {
|
|
43
33
|
const templates = {};
|
|
44
|
-
for (const
|
|
34
|
+
for (const backend of backends) {
|
|
45
35
|
if (backend.getClientSideTemplates) {
|
|
46
36
|
Object.assign(templates, backend.getClientSideTemplates());
|
|
47
37
|
}
|
|
@@ -75,41 +65,44 @@ function createNextlyticsMiddleware(config, dispatchEvent, updateEvent) {
|
|
|
75
65
|
const { anonId } = await (0, import_anonymous_user.resolveAnonymousUser)({ ctx, serverContext, config, response });
|
|
76
66
|
const backends = resolveBackends(config, ctx);
|
|
77
67
|
const templates = collectTemplates(backends);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
68
|
+
let scripts = [];
|
|
69
|
+
if (config.pageViewMode !== "client-init") {
|
|
70
|
+
if (config.excludePaths?.(pathname)) {
|
|
71
|
+
(0, import_server_component_context.serializeServerComponentContext)(response, {
|
|
72
|
+
pageRenderId,
|
|
73
|
+
pathname: request.nextUrl.pathname,
|
|
74
|
+
search: request.nextUrl.search,
|
|
75
|
+
scripts,
|
|
76
|
+
templates
|
|
77
|
+
});
|
|
78
|
+
return response;
|
|
79
|
+
}
|
|
80
|
+
const isApiPath = config.isApiPath(pathname);
|
|
81
|
+
if (isApiPath && config.excludeApiCalls) {
|
|
82
|
+
(0, import_server_component_context.serializeServerComponentContext)(response, {
|
|
83
|
+
pageRenderId,
|
|
84
|
+
pathname: request.nextUrl.pathname,
|
|
85
|
+
search: request.nextUrl.search,
|
|
86
|
+
scripts,
|
|
87
|
+
templates
|
|
88
|
+
});
|
|
89
|
+
return response;
|
|
90
|
+
}
|
|
91
|
+
const userContext = await getUserContext(config, ctx);
|
|
92
|
+
const pageViewEvent = createPageViewEvent(
|
|
91
93
|
pageRenderId,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
94
|
+
serverContext,
|
|
95
|
+
isApiPath,
|
|
96
|
+
userContext,
|
|
97
|
+
anonId
|
|
98
|
+
);
|
|
99
|
+
const { clientActions, completion } = dispatchEvent(pageViewEvent, ctx);
|
|
100
|
+
const actions = await clientActions;
|
|
101
|
+
scripts = actions.items.filter(
|
|
102
|
+
(i) => i.type === "script-template"
|
|
103
|
+
);
|
|
104
|
+
(0, import_server.after)(() => completion);
|
|
98
105
|
}
|
|
99
|
-
const userContext = await getUserContext(config, ctx);
|
|
100
|
-
const pageViewEvent = createPageViewEvent(
|
|
101
|
-
pageRenderId,
|
|
102
|
-
serverContext,
|
|
103
|
-
isApiPath,
|
|
104
|
-
userContext,
|
|
105
|
-
anonId
|
|
106
|
-
);
|
|
107
|
-
const { clientActions, completion } = dispatchEvent(pageViewEvent, ctx, "immediate");
|
|
108
|
-
const actions = await clientActions;
|
|
109
|
-
const scripts = actions.items.filter(
|
|
110
|
-
(i) => i.type === "script-template"
|
|
111
|
-
);
|
|
112
|
-
(0, import_server.after)(() => completion);
|
|
113
106
|
(0, import_server_component_context.serializeServerComponentContext)(response, {
|
|
114
107
|
pageRenderId,
|
|
115
108
|
pathname: request.nextUrl.pathname,
|
|
@@ -140,25 +133,6 @@ async function getUserContext(config, ctx) {
|
|
|
140
133
|
return void 0;
|
|
141
134
|
}
|
|
142
135
|
}
|
|
143
|
-
function reconstructServerContext(apiCallContext, clientInit) {
|
|
144
|
-
const searchParams = {};
|
|
145
|
-
if (clientInit.search) {
|
|
146
|
-
const params = new URLSearchParams(clientInit.search);
|
|
147
|
-
params.forEach((value, key) => {
|
|
148
|
-
if (!searchParams[key]) searchParams[key] = [];
|
|
149
|
-
searchParams[key].push(value);
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
return {
|
|
153
|
-
...apiCallContext,
|
|
154
|
-
// Override with client-provided values
|
|
155
|
-
host: clientInit.host || apiCallContext.host,
|
|
156
|
-
path: clientInit.path || apiCallContext.path,
|
|
157
|
-
search: Object.keys(searchParams).length > 0 ? searchParams : apiCallContext.search,
|
|
158
|
-
method: "GET"
|
|
159
|
-
// Page loads are always GET
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
136
|
async function handleEventPost(request, config, dispatchEvent, updateEvent) {
|
|
163
137
|
const pageRenderId = request.headers.get(import_server_component_context.headers.pageRenderId);
|
|
164
138
|
if (!pageRenderId) {
|
|
@@ -172,40 +146,39 @@ async function handleEventPost(request, config, dispatchEvent, updateEvent) {
|
|
|
172
146
|
}
|
|
173
147
|
const { type, payload } = body;
|
|
174
148
|
const ctx = createRequestContext(request);
|
|
175
|
-
const
|
|
149
|
+
const serverContext = (0, import_uitils.createServerContext)(request);
|
|
176
150
|
const userContext = await getUserContext(config, ctx);
|
|
151
|
+
const { anonId: anonymousUserId } = await (0, import_anonymous_user.resolveAnonymousUser)({ ctx, serverContext, config });
|
|
177
152
|
if (type === "client-init") {
|
|
178
153
|
const clientContext = payload;
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
154
|
+
if (clientContext?.path) {
|
|
155
|
+
serverContext.path = clientContext.path;
|
|
156
|
+
}
|
|
157
|
+
if (config.pageViewMode === "client-init") {
|
|
158
|
+
const event = {
|
|
159
|
+
eventId: pageRenderId,
|
|
160
|
+
type: "pageView",
|
|
161
|
+
collectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
162
|
+
anonymousUserId,
|
|
163
|
+
serverContext,
|
|
164
|
+
clientContext,
|
|
165
|
+
userContext,
|
|
166
|
+
properties: {}
|
|
167
|
+
};
|
|
168
|
+
const { clientActions, completion } = dispatchEvent(event, ctx);
|
|
169
|
+
const actions = await clientActions;
|
|
170
|
+
(0, import_server.after)(() => completion);
|
|
171
|
+
const scripts = actions.items.filter((i) => i.type === "script-template");
|
|
172
|
+
return Response.json({ ok: true, scripts: scripts.length > 0 ? scripts : void 0 });
|
|
173
|
+
} else {
|
|
174
|
+
(0, import_server.after)(() => updateEvent(pageRenderId, { clientContext, userContext, anonymousUserId }, ctx));
|
|
175
|
+
return Response.json({ ok: true });
|
|
176
|
+
}
|
|
201
177
|
} else if (type === "client-event") {
|
|
202
178
|
const clientContext = payload.clientContext || void 0;
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
serverContext,
|
|
207
|
-
config
|
|
208
|
-
});
|
|
179
|
+
if (clientContext?.path) {
|
|
180
|
+
serverContext.path = clientContext.path;
|
|
181
|
+
}
|
|
209
182
|
const event = {
|
|
210
183
|
eventId: (0, import_uitils.generateId)(),
|
|
211
184
|
parentEventId: pageRenderId,
|
package/dist/server.js
CHANGED
|
@@ -33,19 +33,14 @@ var import_handlers = require("./handlers");
|
|
|
33
33
|
var import_config_helpers = require("./config-helpers");
|
|
34
34
|
var import_middleware = require("./middleware");
|
|
35
35
|
var import_uitils = require("./uitils");
|
|
36
|
-
function
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return entries.map((entry) => {
|
|
42
|
-
if (isBackendWithConfig(entry)) {
|
|
43
|
-
const backend2 = typeof entry.backend === "function" ? entry.backend(ctx) : entry.backend;
|
|
44
|
-
return backend2 ? { backend: backend2, ingestPolicy: entry.ingestPolicy ?? "immediate" } : null;
|
|
36
|
+
function resolveBackends(config, ctx) {
|
|
37
|
+
const backends = config.backends || [];
|
|
38
|
+
return backends.map((backend) => {
|
|
39
|
+
if (typeof backend === "function") {
|
|
40
|
+
return backend(ctx);
|
|
45
41
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}).filter((b) => b !== null).filter((b) => !policyFilter || b.ingestPolicy === policyFilter);
|
|
42
|
+
return backend;
|
|
43
|
+
}).filter((b) => b !== null);
|
|
49
44
|
}
|
|
50
45
|
function resolvePlugins(config, ctx) {
|
|
51
46
|
const plugins = config.plugins || [];
|
|
@@ -90,9 +85,9 @@ function Nextlytics(userConfig) {
|
|
|
90
85
|
const config = (0, import_config_helpers.withDefaults)(userConfig);
|
|
91
86
|
const validationResult = (0, import_config_helpers.validateConfig)(config);
|
|
92
87
|
(0, import_config_helpers.logConfigWarnings)(validationResult);
|
|
93
|
-
const dispatchEventInternal = (event, ctx
|
|
88
|
+
const dispatchEventInternal = (event, ctx) => {
|
|
94
89
|
const plugins = resolvePlugins(config, ctx);
|
|
95
|
-
const
|
|
90
|
+
const backends = resolveBackends(config, ctx);
|
|
96
91
|
const pluginsDone = (async () => {
|
|
97
92
|
for (const plugin of plugins) {
|
|
98
93
|
try {
|
|
@@ -103,7 +98,7 @@ function Nextlytics(userConfig) {
|
|
|
103
98
|
}
|
|
104
99
|
})();
|
|
105
100
|
const backendResults = pluginsDone.then(() => {
|
|
106
|
-
return
|
|
101
|
+
return backends.map((backend) => {
|
|
107
102
|
const start = Date.now();
|
|
108
103
|
const promise = backend.onEvent(event).then((result) => ({ ok: true, ms: Date.now() - start, result })).catch((err) => {
|
|
109
104
|
console.error(`[Nextlytics] Backend "${backend.name}" failed on onEvent:`, err);
|
|
@@ -121,7 +116,7 @@ function Nextlytics(userConfig) {
|
|
|
121
116
|
const completion = backendResults.then(async (results) => {
|
|
122
117
|
const settled = await Promise.all(results.map((r) => r.promise));
|
|
123
118
|
if (config.debug) {
|
|
124
|
-
const nameWidth = Math.max(...results.map((r) => r.backend.name.length)
|
|
119
|
+
const nameWidth = Math.max(...results.map((r) => r.backend.name.length));
|
|
125
120
|
console.log(
|
|
126
121
|
`[Nextlytics] dispatchEvent ${event.type} ${event.eventId} (${results.length} backends)`
|
|
127
122
|
);
|
|
@@ -136,11 +131,9 @@ function Nextlytics(userConfig) {
|
|
|
136
131
|
return { clientActions, completion };
|
|
137
132
|
};
|
|
138
133
|
const updateEventInternal = async (eventId, patch, ctx) => {
|
|
139
|
-
const
|
|
140
|
-
({ backend }) => backend.supportsUpdates
|
|
141
|
-
);
|
|
134
|
+
const backends = resolveBackends(config, ctx).filter((backend) => backend.supportsUpdates);
|
|
142
135
|
const results = await Promise.all(
|
|
143
|
-
|
|
136
|
+
backends.map(async (backend) => {
|
|
144
137
|
const start = Date.now();
|
|
145
138
|
try {
|
|
146
139
|
await backend.updateEvent(eventId, patch);
|
|
@@ -152,9 +145,9 @@ function Nextlytics(userConfig) {
|
|
|
152
145
|
}
|
|
153
146
|
})
|
|
154
147
|
);
|
|
155
|
-
if (config.debug &&
|
|
156
|
-
const nameWidth = Math.max(...
|
|
157
|
-
console.log(`[Nextlytics] updateEvent ${eventId} (${
|
|
148
|
+
if (config.debug && backends.length > 0) {
|
|
149
|
+
const nameWidth = Math.max(...backends.map((b) => b.name.length));
|
|
150
|
+
console.log(`[Nextlytics] updateEvent ${eventId} (${backends.length} backends)`);
|
|
158
151
|
results.forEach((r) => {
|
|
159
152
|
const status = r.ok ? "ok" : "fail";
|
|
160
153
|
console.log(` ${r.backend.name.padEnd(nameWidth)} ${status.padEnd(4)} ${r.ms}ms`);
|
|
@@ -170,7 +163,7 @@ function Nextlytics(userConfig) {
|
|
|
170
163
|
return updateEventInternal(eventId, patch, ctx);
|
|
171
164
|
};
|
|
172
165
|
const middleware = (0, import_middleware.createNextlyticsMiddleware)(config, dispatchEventInternal, updateEventInternal);
|
|
173
|
-
const handlers = (0, import_handlers.createHandlers)();
|
|
166
|
+
const handlers = (0, import_handlers.createHandlers)(config, dispatchEventInternal, updateEventInternal);
|
|
174
167
|
const analytics = async () => {
|
|
175
168
|
const headersList = await (0, import_headers.headers)();
|
|
176
169
|
const cookieStore = await (0, import_headers.cookies)();
|
package/dist/types.d.ts
CHANGED
|
@@ -28,16 +28,6 @@ interface ClientContext {
|
|
|
28
28
|
referer?: string;
|
|
29
29
|
/** window.location.pathname - may differ from server path in SPAs */
|
|
30
30
|
path?: string;
|
|
31
|
-
/** window.location.href */
|
|
32
|
-
url?: string;
|
|
33
|
-
/** window.location.host */
|
|
34
|
-
host?: string;
|
|
35
|
-
/** window.location.search (as string, e.g. "?foo=bar") */
|
|
36
|
-
search?: string;
|
|
37
|
-
/** window.location.hash */
|
|
38
|
-
hash?: string;
|
|
39
|
-
/** document.title */
|
|
40
|
-
title?: string;
|
|
41
31
|
/** Screen and viewport dimensions */
|
|
42
32
|
screen: {
|
|
43
33
|
/** screen.width */
|
|
@@ -109,20 +99,6 @@ type NextlyticsPlugin = {
|
|
|
109
99
|
};
|
|
110
100
|
/** Factory to create plugin per-request (for request-scoped plugins) */
|
|
111
101
|
type NextlyticsPluginFactory = (ctx: RequestContext) => NextlyticsPlugin;
|
|
112
|
-
/** When to ingest events for a backend */
|
|
113
|
-
type IngestPolicy =
|
|
114
|
-
/** Dispatch immediately in middleware (default) - faster but no client context */
|
|
115
|
-
"immediate"
|
|
116
|
-
/** Dispatch when client-init is received - has full client context (title, screen, etc) */
|
|
117
|
-
| "on-client-event";
|
|
118
|
-
/** Backend with configuration options */
|
|
119
|
-
type BackendWithConfig = {
|
|
120
|
-
backend: NextlyticsBackend | NextlyticsBackendFactory;
|
|
121
|
-
/** When to send events. Default: "immediate" */
|
|
122
|
-
ingestPolicy?: IngestPolicy;
|
|
123
|
-
};
|
|
124
|
-
/** Backend config entry - either a backend directly or with config */
|
|
125
|
-
type BackendConfigEntry = NextlyticsBackend | NextlyticsBackendFactory | BackendWithConfig;
|
|
126
102
|
type NextlyticsConfig = {
|
|
127
103
|
/** Enable debug logging (shows backend stats for each event) */
|
|
128
104
|
debug?: boolean;
|
|
@@ -138,6 +114,12 @@ type NextlyticsConfig = {
|
|
|
138
114
|
/** Cookie max age in seconds (default: 2 years) */
|
|
139
115
|
cookieMaxAge?: number;
|
|
140
116
|
};
|
|
117
|
+
/**
|
|
118
|
+
* When to record pageView:
|
|
119
|
+
* - "server": in middleware (default, more reliable)
|
|
120
|
+
* - "client-init": when JS loads (has client context) - NOT SUPPORTED CURRENTLU
|
|
121
|
+
*/
|
|
122
|
+
pageViewMode?: "server" | "client-init";
|
|
141
123
|
/** Skip tracking for API routes */
|
|
142
124
|
excludeApiCalls?: boolean;
|
|
143
125
|
/** Skip tracking for specific paths */
|
|
@@ -156,7 +138,7 @@ type NextlyticsConfig = {
|
|
|
156
138
|
}) => Promise<AnonymousUserResult>;
|
|
157
139
|
};
|
|
158
140
|
/** Analytics backends to send events to */
|
|
159
|
-
backends?:
|
|
141
|
+
backends?: (NextlyticsBackend | NextlyticsBackendFactory)[];
|
|
160
142
|
plugins?: (NextlyticsPlugin | NextlyticsPluginFactory)[];
|
|
161
143
|
};
|
|
162
144
|
type ClientAction = {
|
|
@@ -231,4 +213,4 @@ type NextlyticsResult = {
|
|
|
231
213
|
updateEvent: (eventId: string, patch: Partial<NextlyticsEvent>) => Promise<void>;
|
|
232
214
|
};
|
|
233
215
|
|
|
234
|
-
export type { AnonymousUserResult,
|
|
216
|
+
export type { AnonymousUserResult, ClientAction, ClientActionItem, ClientContext, DispatchResult, JavascriptTemplate, NextlyticsBackend, NextlyticsBackendFactory, NextlyticsConfig, NextlyticsEvent, NextlyticsPlugin, NextlyticsPluginFactory, NextlyticsResult, NextlyticsServerSide, RequestContext, ScriptElement, ServerEventContext, TemplatizedScriptInsertion, UserContext };
|