@nextlytics/core 0.1.0-canary-1
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/anonymous-user.cjs +118 -0
- package/dist/anonymous-user.d.mts +22 -0
- package/dist/anonymous-user.d.ts +22 -0
- package/dist/anonymous-user.js +94 -0
- package/dist/backends/clickhouse.cjs +110 -0
- package/dist/backends/clickhouse.d.mts +58 -0
- package/dist/backends/clickhouse.d.ts +58 -0
- package/dist/backends/clickhouse.js +92 -0
- package/dist/backends/ga.cjs +207 -0
- package/dist/backends/ga.d.mts +21 -0
- package/dist/backends/ga.d.ts +21 -0
- package/dist/backends/ga.js +183 -0
- package/dist/backends/gtm.cjs +155 -0
- package/dist/backends/gtm.d.mts +11 -0
- package/dist/backends/gtm.d.ts +11 -0
- package/dist/backends/gtm.js +131 -0
- package/dist/backends/lib/db.cjs +150 -0
- package/dist/backends/lib/db.d.mts +121 -0
- package/dist/backends/lib/db.d.ts +121 -0
- package/dist/backends/lib/db.js +119 -0
- package/dist/backends/logging.cjs +45 -0
- package/dist/backends/logging.d.mts +7 -0
- package/dist/backends/logging.d.ts +7 -0
- package/dist/backends/logging.js +21 -0
- package/dist/backends/neon.cjs +84 -0
- package/dist/backends/neon.d.mts +11 -0
- package/dist/backends/neon.d.ts +11 -0
- package/dist/backends/neon.js +66 -0
- package/dist/backends/postgrest.cjs +98 -0
- package/dist/backends/postgrest.d.mts +46 -0
- package/dist/backends/postgrest.d.ts +46 -0
- package/dist/backends/postgrest.js +73 -0
- package/dist/backends/posthog.cjs +120 -0
- package/dist/backends/posthog.d.mts +13 -0
- package/dist/backends/posthog.d.ts +13 -0
- package/dist/backends/posthog.js +96 -0
- package/dist/backends/segment.cjs +112 -0
- package/dist/backends/segment.d.mts +43 -0
- package/dist/backends/segment.d.ts +43 -0
- package/dist/backends/segment.js +88 -0
- package/dist/client.cjs +171 -0
- package/dist/client.d.mts +29 -0
- package/dist/client.d.ts +29 -0
- package/dist/client.js +146 -0
- package/dist/config-helpers.cjs +71 -0
- package/dist/config-helpers.d.mts +16 -0
- package/dist/config-helpers.d.ts +16 -0
- package/dist/config-helpers.js +45 -0
- package/dist/handlers.cjs +123 -0
- package/dist/handlers.d.mts +9 -0
- package/dist/handlers.d.ts +9 -0
- package/dist/handlers.js +99 -0
- package/dist/headers.cjs +41 -0
- package/dist/headers.d.mts +3 -0
- package/dist/headers.d.ts +3 -0
- package/dist/headers.js +17 -0
- package/dist/index.cjs +41 -0
- package/dist/index.d.mts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +12 -0
- package/dist/middleware.cjs +204 -0
- package/dist/middleware.d.mts +10 -0
- package/dist/middleware.d.ts +10 -0
- package/dist/middleware.js +183 -0
- package/dist/pages-router.cjs +45 -0
- package/dist/pages-router.d.mts +45 -0
- package/dist/pages-router.d.ts +45 -0
- package/dist/pages-router.js +21 -0
- package/dist/plugins/vercel-geo.cjs +60 -0
- package/dist/plugins/vercel-geo.d.mts +25 -0
- package/dist/plugins/vercel-geo.d.ts +25 -0
- package/dist/plugins/vercel-geo.js +36 -0
- package/dist/server-component-context.cjs +95 -0
- package/dist/server-component-context.d.mts +30 -0
- package/dist/server-component-context.d.ts +30 -0
- package/dist/server-component-context.js +69 -0
- package/dist/server.cjs +236 -0
- package/dist/server.d.mts +13 -0
- package/dist/server.d.ts +13 -0
- package/dist/server.js +213 -0
- package/dist/template.cjs +108 -0
- package/dist/template.d.mts +27 -0
- package/dist/template.d.ts +27 -0
- package/dist/template.js +83 -0
- package/dist/types.cjs +16 -0
- package/dist/types.d.mts +216 -0
- package/dist/types.d.ts +216 -0
- package/dist/types.js +0 -0
- package/dist/uitils.cjs +94 -0
- package/dist/uitils.d.mts +22 -0
- package/dist/uitils.d.ts +22 -0
- package/dist/uitils.js +68 -0
- package/package.json +162 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Nextlytics, NextlyticsServer } from "./server";
|
|
2
|
+
import { NextlyticsClient, useNextlytics } from "./client";
|
|
3
|
+
import { getNextlyticsProps } from "./pages-router";
|
|
4
|
+
import { loggingBackend } from "./backends/logging";
|
|
5
|
+
export {
|
|
6
|
+
Nextlytics,
|
|
7
|
+
NextlyticsClient,
|
|
8
|
+
NextlyticsServer,
|
|
9
|
+
getNextlyticsProps,
|
|
10
|
+
loggingBackend,
|
|
11
|
+
useNextlytics
|
|
12
|
+
};
|
|
@@ -0,0 +1,204 @@
|
|
|
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 middleware_exports = {};
|
|
20
|
+
__export(middleware_exports, {
|
|
21
|
+
createNextlyticsMiddleware: () => createNextlyticsMiddleware
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(middleware_exports);
|
|
24
|
+
var import_server = require("next/server");
|
|
25
|
+
var import_server_component_context = require("./server-component-context");
|
|
26
|
+
var import_uitils = require("./uitils");
|
|
27
|
+
var import_anonymous_user = require("./anonymous-user");
|
|
28
|
+
function resolveBackends(config, ctx) {
|
|
29
|
+
const backends = config.backends || [];
|
|
30
|
+
return backends.map((backend) => typeof backend === "function" ? backend(ctx) : backend).filter((b) => b !== null);
|
|
31
|
+
}
|
|
32
|
+
function collectTemplates(backends) {
|
|
33
|
+
const templates = {};
|
|
34
|
+
for (const backend of backends) {
|
|
35
|
+
if (backend.getClientSideTemplates) {
|
|
36
|
+
Object.assign(templates, backend.getClientSideTemplates());
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return templates;
|
|
40
|
+
}
|
|
41
|
+
function createRequestContext(request) {
|
|
42
|
+
return {
|
|
43
|
+
headers: request.headers,
|
|
44
|
+
cookies: request.cookies
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function createNextlyticsMiddleware(config, dispatchEvent, updateEvent) {
|
|
48
|
+
const { eventEndpoint } = config;
|
|
49
|
+
return async (request) => {
|
|
50
|
+
const pathname = request.nextUrl.pathname;
|
|
51
|
+
const reqInfo = (0, import_uitils.getRequestInfo)(request);
|
|
52
|
+
if (pathname === eventEndpoint) {
|
|
53
|
+
if (request.method === "POST") {
|
|
54
|
+
return handleEventPost(request, config, dispatchEvent, updateEvent);
|
|
55
|
+
}
|
|
56
|
+
return Response.json({ error: "Method not allowed" }, { status: 405 });
|
|
57
|
+
}
|
|
58
|
+
if (reqInfo.isNextjsInternal || reqInfo.isPrefetch || reqInfo.isStaticFile) {
|
|
59
|
+
return import_server.NextResponse.next();
|
|
60
|
+
}
|
|
61
|
+
const pageRenderId = (0, import_uitils.generateId)();
|
|
62
|
+
const serverContext = (0, import_uitils.createServerContext)(request);
|
|
63
|
+
const response = import_server.NextResponse.next();
|
|
64
|
+
const ctx = createRequestContext(request);
|
|
65
|
+
const { anonId } = await (0, import_anonymous_user.resolveAnonymousUser)({ ctx, serverContext, config, response });
|
|
66
|
+
const backends = resolveBackends(config, ctx);
|
|
67
|
+
const templates = collectTemplates(backends);
|
|
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(
|
|
93
|
+
pageRenderId,
|
|
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);
|
|
105
|
+
}
|
|
106
|
+
(0, import_server_component_context.serializeServerComponentContext)(response, {
|
|
107
|
+
pageRenderId,
|
|
108
|
+
pathname: request.nextUrl.pathname,
|
|
109
|
+
search: request.nextUrl.search,
|
|
110
|
+
scripts,
|
|
111
|
+
templates
|
|
112
|
+
});
|
|
113
|
+
return response;
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function createPageViewEvent(pageRenderId, serverContext, isApiPath, userContext, anonymousUserId) {
|
|
117
|
+
const eventType = isApiPath ? "apiCall" : "pageView";
|
|
118
|
+
return {
|
|
119
|
+
collectedAt: serverContext.collectedAt.toISOString(),
|
|
120
|
+
eventId: pageRenderId,
|
|
121
|
+
type: eventType,
|
|
122
|
+
anonymousUserId,
|
|
123
|
+
serverContext,
|
|
124
|
+
userContext,
|
|
125
|
+
properties: {}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
async function getUserContext(config, ctx) {
|
|
129
|
+
if (!config.callbacks.getUser) return void 0;
|
|
130
|
+
try {
|
|
131
|
+
return await config.callbacks.getUser(ctx) || void 0;
|
|
132
|
+
} catch {
|
|
133
|
+
return void 0;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
async function handleEventPost(request, config, dispatchEvent, updateEvent) {
|
|
137
|
+
const pageRenderId = request.headers.get(import_server_component_context.headers.pageRenderId);
|
|
138
|
+
if (!pageRenderId) {
|
|
139
|
+
return Response.json({ error: "Missing page render ID" }, { status: 400 });
|
|
140
|
+
}
|
|
141
|
+
let body;
|
|
142
|
+
try {
|
|
143
|
+
body = await request.json();
|
|
144
|
+
} catch {
|
|
145
|
+
return Response.json({ error: "Invalid JSON" }, { status: 400 });
|
|
146
|
+
}
|
|
147
|
+
const { type, payload } = body;
|
|
148
|
+
const ctx = createRequestContext(request);
|
|
149
|
+
const serverContext = (0, import_uitils.createServerContext)(request);
|
|
150
|
+
const userContext = await getUserContext(config, ctx);
|
|
151
|
+
const { anonId: anonymousUserId } = await (0, import_anonymous_user.resolveAnonymousUser)({ ctx, serverContext, config });
|
|
152
|
+
if (type === "client-init") {
|
|
153
|
+
const clientContext = payload;
|
|
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
|
+
}
|
|
177
|
+
} else if (type === "client-event") {
|
|
178
|
+
const clientContext = payload.clientContext || void 0;
|
|
179
|
+
if (clientContext?.path) {
|
|
180
|
+
serverContext.path = clientContext.path;
|
|
181
|
+
}
|
|
182
|
+
const event = {
|
|
183
|
+
eventId: (0, import_uitils.generateId)(),
|
|
184
|
+
parentEventId: pageRenderId,
|
|
185
|
+
type: payload.name || type,
|
|
186
|
+
collectedAt: payload.collectedAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
187
|
+
anonymousUserId,
|
|
188
|
+
serverContext,
|
|
189
|
+
clientContext,
|
|
190
|
+
userContext,
|
|
191
|
+
properties: payload.props || {}
|
|
192
|
+
};
|
|
193
|
+
const { clientActions, completion } = dispatchEvent(event, ctx);
|
|
194
|
+
const actions = await clientActions;
|
|
195
|
+
(0, import_server.after)(() => completion);
|
|
196
|
+
const scripts = actions.items.filter((i) => i.type === "script-template");
|
|
197
|
+
return Response.json({ ok: true, scripts: scripts.length > 0 ? scripts : void 0 });
|
|
198
|
+
}
|
|
199
|
+
return Response.json({ ok: true });
|
|
200
|
+
}
|
|
201
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
202
|
+
0 && (module.exports = {
|
|
203
|
+
createNextlyticsMiddleware
|
|
204
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { NextMiddleware } from 'next/server';
|
|
2
|
+
import { NextlyticsEvent, RequestContext, DispatchResult } from './types.mjs';
|
|
3
|
+
import { NextlyticsConfigWithDefaults } from './config-helpers.mjs';
|
|
4
|
+
import 'next/dist/server/web/spec-extension/cookies';
|
|
5
|
+
|
|
6
|
+
type DispatchEvent = (event: NextlyticsEvent, ctx: RequestContext) => DispatchResult;
|
|
7
|
+
type UpdateEvent = (eventId: string, patch: Partial<NextlyticsEvent>, ctx: RequestContext) => Promise<void>;
|
|
8
|
+
declare function createNextlyticsMiddleware(config: NextlyticsConfigWithDefaults, dispatchEvent: DispatchEvent, updateEvent: UpdateEvent): NextMiddleware;
|
|
9
|
+
|
|
10
|
+
export { createNextlyticsMiddleware };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { NextMiddleware } 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';
|
|
5
|
+
|
|
6
|
+
type DispatchEvent = (event: NextlyticsEvent, ctx: RequestContext) => DispatchResult;
|
|
7
|
+
type UpdateEvent = (eventId: string, patch: Partial<NextlyticsEvent>, ctx: RequestContext) => Promise<void>;
|
|
8
|
+
declare function createNextlyticsMiddleware(config: NextlyticsConfigWithDefaults, dispatchEvent: DispatchEvent, updateEvent: UpdateEvent): NextMiddleware;
|
|
9
|
+
|
|
10
|
+
export { createNextlyticsMiddleware };
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { NextResponse, after } from "next/server";
|
|
2
|
+
import {
|
|
3
|
+
headers as analyticsHeaders,
|
|
4
|
+
serializeServerComponentContext
|
|
5
|
+
} from "./server-component-context";
|
|
6
|
+
import { generateId, getRequestInfo, createServerContext } from "./uitils";
|
|
7
|
+
import { resolveAnonymousUser } from "./anonymous-user";
|
|
8
|
+
function resolveBackends(config, ctx) {
|
|
9
|
+
const backends = config.backends || [];
|
|
10
|
+
return backends.map((backend) => typeof backend === "function" ? backend(ctx) : backend).filter((b) => b !== null);
|
|
11
|
+
}
|
|
12
|
+
function collectTemplates(backends) {
|
|
13
|
+
const templates = {};
|
|
14
|
+
for (const backend of backends) {
|
|
15
|
+
if (backend.getClientSideTemplates) {
|
|
16
|
+
Object.assign(templates, backend.getClientSideTemplates());
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return templates;
|
|
20
|
+
}
|
|
21
|
+
function createRequestContext(request) {
|
|
22
|
+
return {
|
|
23
|
+
headers: request.headers,
|
|
24
|
+
cookies: request.cookies
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function createNextlyticsMiddleware(config, dispatchEvent, updateEvent) {
|
|
28
|
+
const { eventEndpoint } = config;
|
|
29
|
+
return async (request) => {
|
|
30
|
+
const pathname = request.nextUrl.pathname;
|
|
31
|
+
const reqInfo = getRequestInfo(request);
|
|
32
|
+
if (pathname === eventEndpoint) {
|
|
33
|
+
if (request.method === "POST") {
|
|
34
|
+
return handleEventPost(request, config, dispatchEvent, updateEvent);
|
|
35
|
+
}
|
|
36
|
+
return Response.json({ error: "Method not allowed" }, { status: 405 });
|
|
37
|
+
}
|
|
38
|
+
if (reqInfo.isNextjsInternal || reqInfo.isPrefetch || reqInfo.isStaticFile) {
|
|
39
|
+
return NextResponse.next();
|
|
40
|
+
}
|
|
41
|
+
const pageRenderId = generateId();
|
|
42
|
+
const serverContext = createServerContext(request);
|
|
43
|
+
const response = NextResponse.next();
|
|
44
|
+
const ctx = createRequestContext(request);
|
|
45
|
+
const { anonId } = await resolveAnonymousUser({ ctx, serverContext, config, response });
|
|
46
|
+
const backends = resolveBackends(config, ctx);
|
|
47
|
+
const templates = collectTemplates(backends);
|
|
48
|
+
let scripts = [];
|
|
49
|
+
if (config.pageViewMode !== "client-init") {
|
|
50
|
+
if (config.excludePaths?.(pathname)) {
|
|
51
|
+
serializeServerComponentContext(response, {
|
|
52
|
+
pageRenderId,
|
|
53
|
+
pathname: request.nextUrl.pathname,
|
|
54
|
+
search: request.nextUrl.search,
|
|
55
|
+
scripts,
|
|
56
|
+
templates
|
|
57
|
+
});
|
|
58
|
+
return response;
|
|
59
|
+
}
|
|
60
|
+
const isApiPath = config.isApiPath(pathname);
|
|
61
|
+
if (isApiPath && config.excludeApiCalls) {
|
|
62
|
+
serializeServerComponentContext(response, {
|
|
63
|
+
pageRenderId,
|
|
64
|
+
pathname: request.nextUrl.pathname,
|
|
65
|
+
search: request.nextUrl.search,
|
|
66
|
+
scripts,
|
|
67
|
+
templates
|
|
68
|
+
});
|
|
69
|
+
return response;
|
|
70
|
+
}
|
|
71
|
+
const userContext = await getUserContext(config, ctx);
|
|
72
|
+
const pageViewEvent = createPageViewEvent(
|
|
73
|
+
pageRenderId,
|
|
74
|
+
serverContext,
|
|
75
|
+
isApiPath,
|
|
76
|
+
userContext,
|
|
77
|
+
anonId
|
|
78
|
+
);
|
|
79
|
+
const { clientActions, completion } = dispatchEvent(pageViewEvent, ctx);
|
|
80
|
+
const actions = await clientActions;
|
|
81
|
+
scripts = actions.items.filter(
|
|
82
|
+
(i) => i.type === "script-template"
|
|
83
|
+
);
|
|
84
|
+
after(() => completion);
|
|
85
|
+
}
|
|
86
|
+
serializeServerComponentContext(response, {
|
|
87
|
+
pageRenderId,
|
|
88
|
+
pathname: request.nextUrl.pathname,
|
|
89
|
+
search: request.nextUrl.search,
|
|
90
|
+
scripts,
|
|
91
|
+
templates
|
|
92
|
+
});
|
|
93
|
+
return response;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function createPageViewEvent(pageRenderId, serverContext, isApiPath, userContext, anonymousUserId) {
|
|
97
|
+
const eventType = isApiPath ? "apiCall" : "pageView";
|
|
98
|
+
return {
|
|
99
|
+
collectedAt: serverContext.collectedAt.toISOString(),
|
|
100
|
+
eventId: pageRenderId,
|
|
101
|
+
type: eventType,
|
|
102
|
+
anonymousUserId,
|
|
103
|
+
serverContext,
|
|
104
|
+
userContext,
|
|
105
|
+
properties: {}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
async function getUserContext(config, ctx) {
|
|
109
|
+
if (!config.callbacks.getUser) return void 0;
|
|
110
|
+
try {
|
|
111
|
+
return await config.callbacks.getUser(ctx) || void 0;
|
|
112
|
+
} catch {
|
|
113
|
+
return void 0;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async function handleEventPost(request, config, dispatchEvent, updateEvent) {
|
|
117
|
+
const pageRenderId = request.headers.get(analyticsHeaders.pageRenderId);
|
|
118
|
+
if (!pageRenderId) {
|
|
119
|
+
return Response.json({ error: "Missing page render ID" }, { status: 400 });
|
|
120
|
+
}
|
|
121
|
+
let body;
|
|
122
|
+
try {
|
|
123
|
+
body = await request.json();
|
|
124
|
+
} catch {
|
|
125
|
+
return Response.json({ error: "Invalid JSON" }, { status: 400 });
|
|
126
|
+
}
|
|
127
|
+
const { type, payload } = body;
|
|
128
|
+
const ctx = createRequestContext(request);
|
|
129
|
+
const serverContext = createServerContext(request);
|
|
130
|
+
const userContext = await getUserContext(config, ctx);
|
|
131
|
+
const { anonId: anonymousUserId } = await resolveAnonymousUser({ ctx, serverContext, config });
|
|
132
|
+
if (type === "client-init") {
|
|
133
|
+
const clientContext = payload;
|
|
134
|
+
if (clientContext?.path) {
|
|
135
|
+
serverContext.path = clientContext.path;
|
|
136
|
+
}
|
|
137
|
+
if (config.pageViewMode === "client-init") {
|
|
138
|
+
const event = {
|
|
139
|
+
eventId: pageRenderId,
|
|
140
|
+
type: "pageView",
|
|
141
|
+
collectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
142
|
+
anonymousUserId,
|
|
143
|
+
serverContext,
|
|
144
|
+
clientContext,
|
|
145
|
+
userContext,
|
|
146
|
+
properties: {}
|
|
147
|
+
};
|
|
148
|
+
const { clientActions, completion } = dispatchEvent(event, ctx);
|
|
149
|
+
const actions = await clientActions;
|
|
150
|
+
after(() => completion);
|
|
151
|
+
const scripts = actions.items.filter((i) => i.type === "script-template");
|
|
152
|
+
return Response.json({ ok: true, scripts: scripts.length > 0 ? scripts : void 0 });
|
|
153
|
+
} else {
|
|
154
|
+
after(() => updateEvent(pageRenderId, { clientContext, userContext, anonymousUserId }, ctx));
|
|
155
|
+
return Response.json({ ok: true });
|
|
156
|
+
}
|
|
157
|
+
} else if (type === "client-event") {
|
|
158
|
+
const clientContext = payload.clientContext || void 0;
|
|
159
|
+
if (clientContext?.path) {
|
|
160
|
+
serverContext.path = clientContext.path;
|
|
161
|
+
}
|
|
162
|
+
const event = {
|
|
163
|
+
eventId: generateId(),
|
|
164
|
+
parentEventId: pageRenderId,
|
|
165
|
+
type: payload.name || type,
|
|
166
|
+
collectedAt: payload.collectedAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
167
|
+
anonymousUserId,
|
|
168
|
+
serverContext,
|
|
169
|
+
clientContext,
|
|
170
|
+
userContext,
|
|
171
|
+
properties: payload.props || {}
|
|
172
|
+
};
|
|
173
|
+
const { clientActions, completion } = dispatchEvent(event, ctx);
|
|
174
|
+
const actions = await clientActions;
|
|
175
|
+
after(() => completion);
|
|
176
|
+
const scripts = actions.items.filter((i) => i.type === "script-template");
|
|
177
|
+
return Response.json({ ok: true, scripts: scripts.length > 0 ? scripts : void 0 });
|
|
178
|
+
}
|
|
179
|
+
return Response.json({ ok: true });
|
|
180
|
+
}
|
|
181
|
+
export {
|
|
182
|
+
createNextlyticsMiddleware
|
|
183
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
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 pages_router_exports = {};
|
|
20
|
+
__export(pages_router_exports, {
|
|
21
|
+
getNextlyticsProps: () => getNextlyticsProps
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(pages_router_exports);
|
|
24
|
+
var import_server_component_context = require("./server-component-context");
|
|
25
|
+
function getNextlyticsProps(ctx) {
|
|
26
|
+
const headersList = new Headers();
|
|
27
|
+
for (const [key, value] of Object.entries(ctx.req.headers)) {
|
|
28
|
+
if (value) {
|
|
29
|
+
headersList.set(key, Array.isArray(value) ? value[0] : value);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const context = (0, import_server_component_context.restoreServerComponentContext)(headersList);
|
|
33
|
+
if (!context) {
|
|
34
|
+
return { requestId: "" };
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
requestId: context.pageRenderId,
|
|
38
|
+
scripts: context.scripts,
|
|
39
|
+
templates: context.templates
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
43
|
+
0 && (module.exports = {
|
|
44
|
+
getNextlyticsProps
|
|
45
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { NextlyticsContext } from './client.mjs';
|
|
2
|
+
import 'react/jsx-runtime';
|
|
3
|
+
import 'react';
|
|
4
|
+
import './types.mjs';
|
|
5
|
+
import 'next/dist/server/web/spec-extension/cookies';
|
|
6
|
+
import 'next/server';
|
|
7
|
+
|
|
8
|
+
type ContextWithHeaders = {
|
|
9
|
+
req: {
|
|
10
|
+
headers: Record<string, string | string[] | undefined>;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Extract Nextlytics context from Pages Router context (getServerSideProps or getInitialProps).
|
|
15
|
+
* Use this in _app.tsx with getInitialProps to pass context to NextlyticsClient.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```tsx
|
|
19
|
+
* // pages/_app.tsx
|
|
20
|
+
* import type { AppContext, AppProps } from 'next/app'
|
|
21
|
+
* import { NextlyticsClient, getNextlyticsProps, type NextlyticsContext } from '@nextlytics/core'
|
|
22
|
+
*
|
|
23
|
+
* type MyAppProps = AppProps & { nextlyticsCtx: NextlyticsContext }
|
|
24
|
+
*
|
|
25
|
+
* function MyApp({ Component, pageProps, nextlyticsCtx }: MyAppProps) {
|
|
26
|
+
* return (
|
|
27
|
+
* <NextlyticsClient ctx={nextlyticsCtx}>
|
|
28
|
+
* <Component {...pageProps} />
|
|
29
|
+
* </NextlyticsClient>
|
|
30
|
+
* )
|
|
31
|
+
* }
|
|
32
|
+
*
|
|
33
|
+
* MyApp.getInitialProps = async (appContext: AppContext) => {
|
|
34
|
+
* return {
|
|
35
|
+
* pageProps: appContext.Component.getInitialProps
|
|
36
|
+
* ? await appContext.Component.getInitialProps(appContext.ctx)
|
|
37
|
+
* : {},
|
|
38
|
+
* nextlyticsCtx: getNextlyticsProps(appContext.ctx),
|
|
39
|
+
* }
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
declare function getNextlyticsProps(ctx: ContextWithHeaders): NextlyticsContext;
|
|
44
|
+
|
|
45
|
+
export { getNextlyticsProps };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { NextlyticsContext } from './client.js';
|
|
2
|
+
import 'react/jsx-runtime';
|
|
3
|
+
import 'react';
|
|
4
|
+
import './types.js';
|
|
5
|
+
import 'next/dist/server/web/spec-extension/cookies';
|
|
6
|
+
import 'next/server';
|
|
7
|
+
|
|
8
|
+
type ContextWithHeaders = {
|
|
9
|
+
req: {
|
|
10
|
+
headers: Record<string, string | string[] | undefined>;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Extract Nextlytics context from Pages Router context (getServerSideProps or getInitialProps).
|
|
15
|
+
* Use this in _app.tsx with getInitialProps to pass context to NextlyticsClient.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```tsx
|
|
19
|
+
* // pages/_app.tsx
|
|
20
|
+
* import type { AppContext, AppProps } from 'next/app'
|
|
21
|
+
* import { NextlyticsClient, getNextlyticsProps, type NextlyticsContext } from '@nextlytics/core'
|
|
22
|
+
*
|
|
23
|
+
* type MyAppProps = AppProps & { nextlyticsCtx: NextlyticsContext }
|
|
24
|
+
*
|
|
25
|
+
* function MyApp({ Component, pageProps, nextlyticsCtx }: MyAppProps) {
|
|
26
|
+
* return (
|
|
27
|
+
* <NextlyticsClient ctx={nextlyticsCtx}>
|
|
28
|
+
* <Component {...pageProps} />
|
|
29
|
+
* </NextlyticsClient>
|
|
30
|
+
* )
|
|
31
|
+
* }
|
|
32
|
+
*
|
|
33
|
+
* MyApp.getInitialProps = async (appContext: AppContext) => {
|
|
34
|
+
* return {
|
|
35
|
+
* pageProps: appContext.Component.getInitialProps
|
|
36
|
+
* ? await appContext.Component.getInitialProps(appContext.ctx)
|
|
37
|
+
* : {},
|
|
38
|
+
* nextlyticsCtx: getNextlyticsProps(appContext.ctx),
|
|
39
|
+
* }
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
declare function getNextlyticsProps(ctx: ContextWithHeaders): NextlyticsContext;
|
|
44
|
+
|
|
45
|
+
export { getNextlyticsProps };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { restoreServerComponentContext } from "./server-component-context";
|
|
2
|
+
function getNextlyticsProps(ctx) {
|
|
3
|
+
const headersList = new Headers();
|
|
4
|
+
for (const [key, value] of Object.entries(ctx.req.headers)) {
|
|
5
|
+
if (value) {
|
|
6
|
+
headersList.set(key, Array.isArray(value) ? value[0] : value);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
const context = restoreServerComponentContext(headersList);
|
|
10
|
+
if (!context) {
|
|
11
|
+
return { requestId: "" };
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
requestId: context.pageRenderId,
|
|
15
|
+
scripts: context.scripts,
|
|
16
|
+
templates: context.templates
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export {
|
|
20
|
+
getNextlyticsProps
|
|
21
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
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 vercel_geo_exports = {};
|
|
20
|
+
__export(vercel_geo_exports, {
|
|
21
|
+
vercelGeoPlugin: () => vercelGeoPlugin
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(vercel_geo_exports);
|
|
24
|
+
const VERCEL_GEO_HEADERS = {
|
|
25
|
+
"x-vercel-ip-country": "country",
|
|
26
|
+
"x-vercel-ip-country-region": "region",
|
|
27
|
+
"x-vercel-ip-city": "city",
|
|
28
|
+
"x-vercel-ip-latitude": "latitude",
|
|
29
|
+
"x-vercel-ip-longitude": "longitude",
|
|
30
|
+
"x-vercel-ip-timezone": "timezone"
|
|
31
|
+
};
|
|
32
|
+
function vercelGeoPlugin(options) {
|
|
33
|
+
const geoPropertyName = options?.geoPropertyName ?? "geo";
|
|
34
|
+
return {
|
|
35
|
+
async onDispatch(event) {
|
|
36
|
+
const headers = event.serverContext.requestHeaders;
|
|
37
|
+
const geo = {};
|
|
38
|
+
for (const [header, prop] of Object.entries(VERCEL_GEO_HEADERS)) {
|
|
39
|
+
const value = headers[header];
|
|
40
|
+
if (value) {
|
|
41
|
+
if (prop === "latitude" || prop === "longitude") {
|
|
42
|
+
const num = parseFloat(value);
|
|
43
|
+
if (!isNaN(num)) {
|
|
44
|
+
geo[prop] = num;
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
geo[prop] = value;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (Object.keys(geo).length > 0) {
|
|
52
|
+
event.properties[geoPropertyName] = geo;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
58
|
+
0 && (module.exports = {
|
|
59
|
+
vercelGeoPlugin
|
|
60
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { NextlyticsPlugin } from '../types.mjs';
|
|
2
|
+
import 'next/dist/server/web/spec-extension/cookies';
|
|
3
|
+
import 'next/server';
|
|
4
|
+
|
|
5
|
+
type VercelGeoPluginOptions = {
|
|
6
|
+
/** Property name to store geo data under. Default: "geo" */
|
|
7
|
+
geoPropertyName?: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Plugin that extracts Vercel geo headers and adds them to event properties.
|
|
11
|
+
* Only works when deployed to Vercel.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { vercelGeoPlugin } from "@nextlytics/core/plugins/vercel-geo";
|
|
16
|
+
*
|
|
17
|
+
* export const { middleware, handlers } = Nextlytics({
|
|
18
|
+
* plugins: [vercelGeoPlugin()],
|
|
19
|
+
* // ...
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
declare function vercelGeoPlugin(options?: VercelGeoPluginOptions): NextlyticsPlugin;
|
|
24
|
+
|
|
25
|
+
export { type VercelGeoPluginOptions, vercelGeoPlugin };
|