@nextlytics/core 0.3.0-canary.70 → 0.3.0-canary.80
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 +11 -0
- package/dist/api-handler.js +182 -0
- package/dist/backends/ga.d.ts +5 -0
- package/dist/backends/ga.js +93 -17
- package/dist/backends/gtm.js +25 -8
- package/dist/backends/logging.js +33 -0
- package/dist/backends/segment.js +1 -0
- package/dist/client-utils.d.ts +35 -0
- package/dist/client-utils.js +121 -0
- package/dist/client.d.ts +1 -4
- package/dist/client.js +141 -67
- package/dist/config-helpers.js +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -3
- package/dist/middleware.d.ts +2 -3
- package/dist/middleware.js +39 -127
- package/dist/pages-router.d.ts +6 -31
- package/dist/pages-router.js +1 -2
- package/dist/server-component-context.d.ts +10 -11
- package/dist/server-component-context.js +18 -28
- package/dist/server.d.ts +1 -6
- package/dist/server.js +40 -21
- package/dist/stable-hash.d.ts +6 -0
- package/dist/stable-hash.js +76 -0
- package/dist/types.d.ts +61 -18
- package/dist/uitils.d.ts +7 -1
- package/dist/uitils.js +39 -5
- package/package.json +1 -1
- package/dist/handlers.d.ts +0 -12
- package/dist/handlers.js +0 -40
package/dist/client.js
CHANGED
|
@@ -25,13 +25,15 @@ __export(client_exports, {
|
|
|
25
25
|
module.exports = __toCommonJS(client_exports);
|
|
26
26
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
27
27
|
var import_react = require("react");
|
|
28
|
+
var import_client_utils = require("./client-utils");
|
|
28
29
|
var import_server_component_context = require("./server-component-context");
|
|
29
30
|
var import_template = require("./template");
|
|
31
|
+
var import_stable_hash = require("./stable-hash");
|
|
30
32
|
const templateFunctions = {
|
|
31
33
|
q: (v) => JSON.stringify(v ?? null),
|
|
32
|
-
json: (v) => JSON.stringify(v ?? null)
|
|
34
|
+
json: (v) => JSON.stringify(v ?? null),
|
|
35
|
+
stableHash: (v) => (0, import_stable_hash.stableHash)(v)
|
|
33
36
|
};
|
|
34
|
-
const compiledCache = {};
|
|
35
37
|
const NextlyticsContext = (0, import_react.createContext)(null);
|
|
36
38
|
function createClientContext() {
|
|
37
39
|
const isBrowser = typeof window !== "undefined";
|
|
@@ -55,94 +57,165 @@ function createClientContext() {
|
|
|
55
57
|
locale: isBrowser ? navigator.language : void 0
|
|
56
58
|
};
|
|
57
59
|
}
|
|
58
|
-
function
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
compiledCache[cacheKey] = {
|
|
62
|
-
src: item.src ? (0, import_template.compile)(item.src) : void 0,
|
|
63
|
-
body: item.body ? (0, import_template.compile)(item.body) : void 0
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
return compiledCache[cacheKey];
|
|
67
|
-
}
|
|
68
|
-
function executeTemplatedScripts(scripts, templates) {
|
|
69
|
-
if (!scripts || typeof window === "undefined") return;
|
|
60
|
+
function deduplicateScripts(scripts, templates) {
|
|
61
|
+
const result = [];
|
|
62
|
+
const firstSeenByDeps = /* @__PURE__ */ new Set();
|
|
70
63
|
for (const script of scripts) {
|
|
71
|
-
if (script.type !== "script-template")
|
|
72
|
-
|
|
64
|
+
if (script.type !== "script-template") continue;
|
|
65
|
+
const template = templates[script.templateId];
|
|
66
|
+
if (!template) continue;
|
|
67
|
+
if (!template.deps) {
|
|
68
|
+
result.push(script);
|
|
73
69
|
continue;
|
|
74
70
|
}
|
|
71
|
+
const paramsRecord = script.params || {};
|
|
72
|
+
const deps = compileTemplateDeps(template, paramsRecord);
|
|
73
|
+
const depsKey = `${script.templateId}\0${deps.join("\0")}`;
|
|
74
|
+
if (firstSeenByDeps.has(depsKey)) continue;
|
|
75
|
+
firstSeenByDeps.add(depsKey);
|
|
76
|
+
result.push(script);
|
|
77
|
+
}
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
function compileScripts(scripts, templates) {
|
|
81
|
+
const result = [];
|
|
82
|
+
for (const [scriptIndex, script] of scripts.entries()) {
|
|
83
|
+
if (script.type !== "script-template") continue;
|
|
75
84
|
const template = templates[script.templateId];
|
|
76
85
|
if (!template) {
|
|
77
|
-
console.warn(`[Nextlytics]
|
|
86
|
+
console.warn(`[Nextlytics] Template "${script.templateId}" not found`);
|
|
78
87
|
continue;
|
|
79
88
|
}
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
if (item.
|
|
89
|
+
const paramsRecord = script.params || {};
|
|
90
|
+
const deps = compileTemplateDeps(template, paramsRecord);
|
|
91
|
+
let itemIndex = 0;
|
|
92
|
+
for (const item of template.items) {
|
|
93
|
+
const keyPrefix = `${script.templateId}:${scriptIndex}:${item.src ? "ext" : "body"}:${itemIndex}`;
|
|
94
|
+
if (item.src) {
|
|
95
|
+
const compiledSrc = (0, import_template.compile)(item.src);
|
|
96
|
+
const src = (0, import_template.apply)(compiledSrc, paramsRecord, templateFunctions);
|
|
97
|
+
result.push({
|
|
98
|
+
key: keyPrefix,
|
|
99
|
+
src,
|
|
100
|
+
async: item.async,
|
|
101
|
+
deps
|
|
102
|
+
});
|
|
103
|
+
itemIndex++;
|
|
86
104
|
continue;
|
|
87
105
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
if (compiled.body) {
|
|
93
|
-
el.textContent = (0, import_template.apply)(compiled.body, params, templateFunctions);
|
|
94
|
-
}
|
|
95
|
-
if (item.async) {
|
|
96
|
-
el.async = true;
|
|
106
|
+
if (!item.body) {
|
|
107
|
+
itemIndex++;
|
|
108
|
+
continue;
|
|
97
109
|
}
|
|
98
|
-
|
|
110
|
+
const bodyText = Array.isArray(item.body) ? item.body.join("\n") : item.body;
|
|
111
|
+
const compiled = (0, import_template.compile)(bodyText);
|
|
112
|
+
const body = (0, import_template.apply)(compiled, paramsRecord, templateFunctions);
|
|
113
|
+
result.push({ key: keyPrefix, body, deps });
|
|
114
|
+
itemIndex++;
|
|
99
115
|
}
|
|
100
116
|
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
function compileTemplateDeps(template, paramsRecord) {
|
|
120
|
+
if (!template.deps) return [];
|
|
121
|
+
const rawDeps = Array.isArray(template.deps) ? template.deps : [template.deps];
|
|
122
|
+
return rawDeps.map((dep) => (0, import_template.apply)((0, import_template.compile)(dep), paramsRecord, templateFunctions));
|
|
101
123
|
}
|
|
102
|
-
|
|
124
|
+
function NextlyticsScripts({
|
|
125
|
+
initialScripts
|
|
126
|
+
}) {
|
|
127
|
+
const context = (0, import_react.useContext)(NextlyticsContext);
|
|
128
|
+
if (!context) {
|
|
129
|
+
throw new Error("NextlyticsScripts should be called within NextlyticsContext");
|
|
130
|
+
}
|
|
131
|
+
const { scriptsRef, subscribersRef, templates } = context;
|
|
132
|
+
const [, forceUpdate] = (0, import_react.useReducer)((x) => x + 1, 0);
|
|
133
|
+
(0, import_react.useEffect)(() => {
|
|
134
|
+
subscribersRef.current.add(forceUpdate);
|
|
135
|
+
return () => {
|
|
136
|
+
subscribersRef.current.delete(forceUpdate);
|
|
137
|
+
};
|
|
138
|
+
}, [subscribersRef]);
|
|
139
|
+
const allScripts = [...initialScripts, ...scriptsRef.current];
|
|
140
|
+
const dedupedScripts = (0, import_react.useMemo)(
|
|
141
|
+
() => deduplicateScripts(allScripts, templates),
|
|
142
|
+
[allScripts, templates]
|
|
143
|
+
);
|
|
144
|
+
const compiled = (0, import_react.useMemo)(
|
|
145
|
+
() => compileScripts(dedupedScripts, templates),
|
|
146
|
+
[dedupedScripts, templates]
|
|
147
|
+
);
|
|
148
|
+
(0, import_client_utils.debug)("Rendering scripts", {
|
|
149
|
+
initialCount: initialScripts.length,
|
|
150
|
+
dynamicCount: scriptsRef.current.length,
|
|
151
|
+
totalCount: allScripts.length,
|
|
152
|
+
compiledCount: compiled.length
|
|
153
|
+
});
|
|
154
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: compiled.map(({ key, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_client_utils.InjectScript, { ...props }, key)) });
|
|
155
|
+
}
|
|
156
|
+
async function sendEventToServer(requestId, request, { signal, isSoftNavigation } = {}) {
|
|
103
157
|
try {
|
|
158
|
+
const headers = {
|
|
159
|
+
"Content-Type": "application/json",
|
|
160
|
+
[import_server_component_context.headerNames.pageRenderId]: requestId
|
|
161
|
+
};
|
|
162
|
+
if (isSoftNavigation) {
|
|
163
|
+
headers[import_server_component_context.headerNames.isSoftNavigation] = "1";
|
|
164
|
+
}
|
|
104
165
|
const response = await fetch("/api/event", {
|
|
105
166
|
method: "POST",
|
|
106
|
-
headers
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
},
|
|
110
|
-
body: JSON.stringify({ type, payload })
|
|
167
|
+
headers,
|
|
168
|
+
body: JSON.stringify(request),
|
|
169
|
+
signal
|
|
111
170
|
});
|
|
112
171
|
if (response.status === 404) {
|
|
113
172
|
console.error(
|
|
114
|
-
"[Nextlytics] In order for NextlyticsClient to work, you must
|
|
173
|
+
"[Nextlytics] In order for NextlyticsClient to work, you must install nextlytics middleware"
|
|
115
174
|
);
|
|
116
175
|
return { ok: false };
|
|
117
176
|
}
|
|
118
177
|
const data = await response.json().catch(() => ({ ok: response.ok }));
|
|
119
|
-
return { ok: data.ok ?? response.ok,
|
|
178
|
+
return { ok: data.ok ?? response.ok, items: data.items };
|
|
120
179
|
} catch (error) {
|
|
180
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
181
|
+
return { ok: false };
|
|
182
|
+
}
|
|
121
183
|
console.error("[Nextlytics] Failed to send event:", error);
|
|
122
184
|
return { ok: false };
|
|
123
185
|
}
|
|
124
186
|
}
|
|
125
|
-
const initializedRequestIds = /* @__PURE__ */ new Set();
|
|
126
187
|
function NextlyticsClient(props) {
|
|
127
|
-
const requestId =
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
(0, import_react.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
188
|
+
const { requestId, scripts: initialScripts = [], templates = {} } = props.ctx;
|
|
189
|
+
const scriptsRef = (0, import_react.useRef)([]);
|
|
190
|
+
const subscribersRef = (0, import_react.useRef)(/* @__PURE__ */ new Set());
|
|
191
|
+
const addScripts = (0, import_react.useCallback)((newScripts) => {
|
|
192
|
+
(0, import_client_utils.debug)("Adding scripts", {
|
|
193
|
+
newCount: newScripts.length,
|
|
194
|
+
templateIds: newScripts.map((s) => s.templateId)
|
|
195
|
+
});
|
|
196
|
+
scriptsRef.current = [...scriptsRef.current, ...newScripts];
|
|
197
|
+
subscribersRef.current.forEach((cb) => cb());
|
|
198
|
+
}, []);
|
|
199
|
+
const contextValue = (0, import_react.useMemo)(
|
|
200
|
+
() => ({ requestId, templates, addScripts, scriptsRef, subscribersRef }),
|
|
201
|
+
[requestId, templates, addScripts]
|
|
202
|
+
);
|
|
203
|
+
(0, import_client_utils.useNavigation)(requestId, ({ softNavigation, signal }) => {
|
|
204
|
+
(0, import_client_utils.debug)("Sending page-view", { requestId, softNavigation });
|
|
136
205
|
const clientContext = createClientContext();
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
206
|
+
sendEventToServer(
|
|
207
|
+
requestId,
|
|
208
|
+
{ type: "page-view", clientContext, softNavigation: softNavigation || void 0 },
|
|
209
|
+
{ signal, isSoftNavigation: softNavigation }
|
|
210
|
+
).then(({ items }) => {
|
|
211
|
+
(0, import_client_utils.debug)("page-view response", { scriptsCount: items?.length ?? 0 });
|
|
212
|
+
if (items?.length) addScripts(items);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(NextlyticsContext.Provider, { value: contextValue, children: [
|
|
216
|
+
props.children,
|
|
217
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(NextlyticsScripts, { initialScripts })
|
|
218
|
+
] });
|
|
146
219
|
}
|
|
147
220
|
function useNextlytics() {
|
|
148
221
|
const context = (0, import_react.useContext)(NextlyticsContext);
|
|
@@ -151,23 +224,24 @@ function useNextlytics() {
|
|
|
151
224
|
"[Nextlytics] useNextlytics() must be used within a component wrapped by <NextlyticsServer>. Add <NextlyticsServer> at the top of your layout.tsx file."
|
|
152
225
|
);
|
|
153
226
|
}
|
|
154
|
-
const { requestId,
|
|
155
|
-
const
|
|
227
|
+
const { requestId, addScripts } = context;
|
|
228
|
+
const sendEvent = (0, import_react.useCallback)(
|
|
156
229
|
async (eventName, opts) => {
|
|
157
|
-
const result = await
|
|
230
|
+
const result = await sendEventToServer(requestId, {
|
|
231
|
+
type: "custom-event",
|
|
158
232
|
name: eventName,
|
|
159
233
|
props: opts?.props,
|
|
160
234
|
collectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
161
235
|
clientContext: createClientContext()
|
|
162
236
|
});
|
|
163
|
-
if (result.
|
|
164
|
-
|
|
237
|
+
if (result.items && result.items.length > 0) {
|
|
238
|
+
addScripts(result.items);
|
|
165
239
|
}
|
|
166
240
|
return { ok: result.ok };
|
|
167
241
|
},
|
|
168
|
-
[requestId,
|
|
242
|
+
[requestId, addScripts]
|
|
169
243
|
);
|
|
170
|
-
return { sendEvent
|
|
244
|
+
return { sendEvent };
|
|
171
245
|
}
|
|
172
246
|
// Annotate the CommonJS export names for ESM import in node:
|
|
173
247
|
0 && (module.exports = {
|
package/dist/config-helpers.js
CHANGED
|
@@ -28,7 +28,7 @@ function withDefaults(config) {
|
|
|
28
28
|
...config,
|
|
29
29
|
excludeApiCalls: config.excludeApiCalls ?? false,
|
|
30
30
|
eventEndpoint: config.eventEndpoint ?? "/api/event",
|
|
31
|
-
isApiPath: config.isApiPath ?? (() =>
|
|
31
|
+
isApiPath: config.isApiPath ?? ((str) => str.startsWith("/api")),
|
|
32
32
|
backends: config.backends ?? [],
|
|
33
33
|
anonymousUsers: {
|
|
34
34
|
gdprMode: config.anonymousUsers?.gdprMode ?? true,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export { Nextlytics
|
|
2
|
-
export { NextlyticsClient, NextlyticsContext, useNextlytics } from './client.js';
|
|
1
|
+
export { Nextlytics } from './server.js';
|
|
3
2
|
export { getNextlyticsProps } from './pages-router.js';
|
|
3
|
+
export { NextlyticsClient, NextlyticsContext, useNextlytics } from './client.js';
|
|
4
4
|
export { loggingBackend } from './backends/logging.js';
|
|
5
|
-
export { AnonymousUserResult, BackendConfigEntry, BackendWithConfig, ClientContext,
|
|
5
|
+
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
6
|
import 'react/jsx-runtime';
|
|
7
7
|
import 'react';
|
|
8
8
|
import 'next/dist/server/web/spec-extension/cookies';
|
package/dist/index.js
CHANGED
|
@@ -20,21 +20,19 @@ var index_exports = {};
|
|
|
20
20
|
__export(index_exports, {
|
|
21
21
|
Nextlytics: () => import_server.Nextlytics,
|
|
22
22
|
NextlyticsClient: () => import_client.NextlyticsClient,
|
|
23
|
-
NextlyticsServer: () => import_server.NextlyticsServer,
|
|
24
23
|
getNextlyticsProps: () => import_pages_router.getNextlyticsProps,
|
|
25
24
|
loggingBackend: () => import_logging.loggingBackend,
|
|
26
25
|
useNextlytics: () => import_client.useNextlytics
|
|
27
26
|
});
|
|
28
27
|
module.exports = __toCommonJS(index_exports);
|
|
29
28
|
var import_server = require("./server");
|
|
30
|
-
var import_client = require("./client");
|
|
31
29
|
var import_pages_router = require("./pages-router");
|
|
30
|
+
var import_client = require("./client");
|
|
32
31
|
var import_logging = require("./backends/logging");
|
|
33
32
|
// Annotate the CommonJS export names for ESM import in node:
|
|
34
33
|
0 && (module.exports = {
|
|
35
34
|
Nextlytics,
|
|
36
35
|
NextlyticsClient,
|
|
37
|
-
NextlyticsServer,
|
|
38
36
|
getNextlyticsProps,
|
|
39
37
|
loggingBackend,
|
|
40
38
|
useNextlytics
|
package/dist/middleware.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { NextMiddleware } from 'next/server';
|
|
2
|
-
import { NextlyticsEvent, RequestContext, IngestPolicy, DispatchResult } from './types.js';
|
|
3
2
|
import { NextlyticsConfigWithDefaults } from './config-helpers.js';
|
|
3
|
+
import { DispatchEvent, UpdateEvent } from './api-handler.js';
|
|
4
|
+
import './types.js';
|
|
4
5
|
import 'next/dist/server/web/spec-extension/cookies';
|
|
5
6
|
|
|
6
|
-
type DispatchEvent = (event: NextlyticsEvent, ctx: RequestContext, policyFilter?: IngestPolicy) => DispatchResult;
|
|
7
|
-
type UpdateEvent = (eventId: string, patch: Partial<NextlyticsEvent>, ctx: RequestContext) => Promise<void>;
|
|
8
7
|
declare function createNextlyticsMiddleware(config: NextlyticsConfigWithDefaults, dispatchEvent: DispatchEvent, updateEvent: UpdateEvent): NextMiddleware;
|
|
9
8
|
|
|
10
9
|
export { createNextlyticsMiddleware };
|
package/dist/middleware.js
CHANGED
|
@@ -25,29 +25,7 @@ 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
|
-
|
|
29
|
-
return typeof entry === "object" && entry !== null && "backend" in entry;
|
|
30
|
-
}
|
|
31
|
-
function resolveBackends(config, ctx) {
|
|
32
|
-
const entries = config.backends || [];
|
|
33
|
-
return entries.map((entry) => {
|
|
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);
|
|
41
|
-
}
|
|
42
|
-
function collectTemplates(backends) {
|
|
43
|
-
const templates = {};
|
|
44
|
-
for (const { backend } of backends) {
|
|
45
|
-
if (backend.getClientSideTemplates) {
|
|
46
|
-
Object.assign(templates, backend.getClientSideTemplates());
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return templates;
|
|
50
|
-
}
|
|
28
|
+
var import_api_handler = require("./api-handler");
|
|
51
29
|
function createRequestContext(request) {
|
|
52
30
|
return {
|
|
53
31
|
headers: request.headers,
|
|
@@ -59,29 +37,57 @@ function createNextlyticsMiddleware(config, dispatchEvent, updateEvent) {
|
|
|
59
37
|
return async (request) => {
|
|
60
38
|
const pathname = request.nextUrl.pathname;
|
|
61
39
|
const reqInfo = (0, import_uitils.getRequestInfo)(request);
|
|
40
|
+
const middlewareDebug = config.debug || process.env.NEXTLYTICS_MIDDLEWARE_DEBUG === "true";
|
|
41
|
+
if (middlewareDebug) {
|
|
42
|
+
const headers = request.headers;
|
|
43
|
+
const debugHeaders = {};
|
|
44
|
+
headers.forEach((value, key) => {
|
|
45
|
+
debugHeaders[key] = value;
|
|
46
|
+
});
|
|
47
|
+
console.log("[Nextlytics][middleware]", {
|
|
48
|
+
url: request.url,
|
|
49
|
+
pathname,
|
|
50
|
+
search: request.nextUrl.search,
|
|
51
|
+
method: request.method,
|
|
52
|
+
nextVersion: (0, import_uitils.getNextVersion)(),
|
|
53
|
+
destination: request.destination,
|
|
54
|
+
referrer: request.referrer,
|
|
55
|
+
mode: request.mode,
|
|
56
|
+
cache: request.cache,
|
|
57
|
+
redirect: request.redirect,
|
|
58
|
+
integrity: request.integrity,
|
|
59
|
+
isPrefetch: reqInfo.isPrefetch,
|
|
60
|
+
isRsc: reqInfo.isRsc,
|
|
61
|
+
isPageNavigation: reqInfo.isPageNavigation,
|
|
62
|
+
isStaticFile: reqInfo.isStaticFile,
|
|
63
|
+
isNextjsInternal: reqInfo.isNextjsInternal,
|
|
64
|
+
headers: debugHeaders
|
|
65
|
+
});
|
|
66
|
+
}
|
|
62
67
|
if (pathname === eventEndpoint) {
|
|
63
68
|
if (request.method === "POST") {
|
|
64
|
-
return handleEventPost(request, config, dispatchEvent, updateEvent);
|
|
69
|
+
return (0, import_api_handler.handleEventPost)(request, config, dispatchEvent, updateEvent);
|
|
65
70
|
}
|
|
66
71
|
return Response.json({ error: "Method not allowed" }, { status: 405 });
|
|
67
72
|
}
|
|
68
73
|
if (reqInfo.isNextjsInternal || reqInfo.isPrefetch || reqInfo.isStaticFile) {
|
|
69
74
|
return import_server.NextResponse.next();
|
|
70
75
|
}
|
|
76
|
+
if (!reqInfo.isPageNavigation && !config.isApiPath(pathname)) {
|
|
77
|
+
return import_server.NextResponse.next();
|
|
78
|
+
}
|
|
71
79
|
const pageRenderId = (0, import_uitils.generateId)();
|
|
72
80
|
const serverContext = (0, import_uitils.createServerContext)(request);
|
|
73
81
|
const response = import_server.NextResponse.next();
|
|
74
82
|
const ctx = createRequestContext(request);
|
|
83
|
+
response.cookies.set(import_server_component_context.LAST_PAGE_RENDER_ID_COOKIE, pageRenderId, { path: "/" });
|
|
75
84
|
const { anonId } = await (0, import_anonymous_user.resolveAnonymousUser)({ ctx, serverContext, config, response });
|
|
76
|
-
const backends = resolveBackends(config, ctx);
|
|
77
|
-
const templates = collectTemplates(backends);
|
|
78
85
|
if (config.excludePaths?.(pathname)) {
|
|
79
86
|
(0, import_server_component_context.serializeServerComponentContext)(response, {
|
|
80
87
|
pageRenderId,
|
|
81
88
|
pathname: request.nextUrl.pathname,
|
|
82
89
|
search: request.nextUrl.search,
|
|
83
|
-
scripts: []
|
|
84
|
-
templates
|
|
90
|
+
scripts: []
|
|
85
91
|
});
|
|
86
92
|
return response;
|
|
87
93
|
}
|
|
@@ -91,12 +97,11 @@ function createNextlyticsMiddleware(config, dispatchEvent, updateEvent) {
|
|
|
91
97
|
pageRenderId,
|
|
92
98
|
pathname: request.nextUrl.pathname,
|
|
93
99
|
search: request.nextUrl.search,
|
|
94
|
-
scripts: []
|
|
95
|
-
templates
|
|
100
|
+
scripts: []
|
|
96
101
|
});
|
|
97
102
|
return response;
|
|
98
103
|
}
|
|
99
|
-
const userContext = await getUserContext(config, ctx);
|
|
104
|
+
const userContext = await (0, import_api_handler.getUserContext)(config, ctx);
|
|
100
105
|
const pageViewEvent = createPageViewEvent(
|
|
101
106
|
pageRenderId,
|
|
102
107
|
serverContext,
|
|
@@ -104,7 +109,7 @@ function createNextlyticsMiddleware(config, dispatchEvent, updateEvent) {
|
|
|
104
109
|
userContext,
|
|
105
110
|
anonId
|
|
106
111
|
);
|
|
107
|
-
const { clientActions, completion } = dispatchEvent(pageViewEvent, ctx, "
|
|
112
|
+
const { clientActions, completion } = dispatchEvent(pageViewEvent, ctx, "on-request");
|
|
108
113
|
const actions = await clientActions;
|
|
109
114
|
const scripts = actions.items.filter(
|
|
110
115
|
(i) => i.type === "script-template"
|
|
@@ -114,8 +119,7 @@ function createNextlyticsMiddleware(config, dispatchEvent, updateEvent) {
|
|
|
114
119
|
pageRenderId,
|
|
115
120
|
pathname: request.nextUrl.pathname,
|
|
116
121
|
search: request.nextUrl.search,
|
|
117
|
-
scripts
|
|
118
|
-
templates
|
|
122
|
+
scripts
|
|
119
123
|
});
|
|
120
124
|
return response;
|
|
121
125
|
};
|
|
@@ -123,6 +127,7 @@ function createNextlyticsMiddleware(config, dispatchEvent, updateEvent) {
|
|
|
123
127
|
function createPageViewEvent(pageRenderId, serverContext, isApiPath, userContext, anonymousUserId) {
|
|
124
128
|
const eventType = isApiPath ? "apiCall" : "pageView";
|
|
125
129
|
return {
|
|
130
|
+
origin: "server",
|
|
126
131
|
collectedAt: serverContext.collectedAt.toISOString(),
|
|
127
132
|
eventId: pageRenderId,
|
|
128
133
|
type: eventType,
|
|
@@ -132,99 +137,6 @@ function createPageViewEvent(pageRenderId, serverContext, isApiPath, userContext
|
|
|
132
137
|
properties: {}
|
|
133
138
|
};
|
|
134
139
|
}
|
|
135
|
-
async function getUserContext(config, ctx) {
|
|
136
|
-
if (!config.callbacks.getUser) return void 0;
|
|
137
|
-
try {
|
|
138
|
-
return await config.callbacks.getUser(ctx) || void 0;
|
|
139
|
-
} catch {
|
|
140
|
-
return void 0;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
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
|
-
async function handleEventPost(request, config, dispatchEvent, updateEvent) {
|
|
163
|
-
const pageRenderId = request.headers.get(import_server_component_context.headers.pageRenderId);
|
|
164
|
-
if (!pageRenderId) {
|
|
165
|
-
return Response.json({ error: "Missing page render ID" }, { status: 400 });
|
|
166
|
-
}
|
|
167
|
-
let body;
|
|
168
|
-
try {
|
|
169
|
-
body = await request.json();
|
|
170
|
-
} catch {
|
|
171
|
-
return Response.json({ error: "Invalid JSON" }, { status: 400 });
|
|
172
|
-
}
|
|
173
|
-
const { type, payload } = body;
|
|
174
|
-
const ctx = createRequestContext(request);
|
|
175
|
-
const apiCallServerContext = (0, import_uitils.createServerContext)(request);
|
|
176
|
-
const userContext = await getUserContext(config, ctx);
|
|
177
|
-
if (type === "client-init") {
|
|
178
|
-
const clientContext = payload;
|
|
179
|
-
const serverContext = reconstructServerContext(apiCallServerContext, clientContext);
|
|
180
|
-
const { anonId: anonymousUserId } = await (0, import_anonymous_user.resolveAnonymousUser)({
|
|
181
|
-
ctx,
|
|
182
|
-
serverContext,
|
|
183
|
-
config
|
|
184
|
-
});
|
|
185
|
-
const event = {
|
|
186
|
-
eventId: pageRenderId,
|
|
187
|
-
type: "pageView",
|
|
188
|
-
collectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
189
|
-
anonymousUserId,
|
|
190
|
-
serverContext,
|
|
191
|
-
clientContext,
|
|
192
|
-
userContext,
|
|
193
|
-
properties: {}
|
|
194
|
-
};
|
|
195
|
-
const { clientActions, completion } = dispatchEvent(event, ctx, "on-client-event");
|
|
196
|
-
const actions = await clientActions;
|
|
197
|
-
(0, import_server.after)(() => completion);
|
|
198
|
-
(0, import_server.after)(() => updateEvent(pageRenderId, { clientContext, userContext, anonymousUserId }, ctx));
|
|
199
|
-
const scripts = actions.items.filter((i) => i.type === "script-template");
|
|
200
|
-
return Response.json({ ok: true, scripts: scripts.length > 0 ? scripts : void 0 });
|
|
201
|
-
} else if (type === "client-event") {
|
|
202
|
-
const clientContext = payload.clientContext || void 0;
|
|
203
|
-
const serverContext = clientContext ? reconstructServerContext(apiCallServerContext, clientContext) : apiCallServerContext;
|
|
204
|
-
const { anonId: anonymousUserId } = await (0, import_anonymous_user.resolveAnonymousUser)({
|
|
205
|
-
ctx,
|
|
206
|
-
serverContext,
|
|
207
|
-
config
|
|
208
|
-
});
|
|
209
|
-
const event = {
|
|
210
|
-
eventId: (0, import_uitils.generateId)(),
|
|
211
|
-
parentEventId: pageRenderId,
|
|
212
|
-
type: payload.name || type,
|
|
213
|
-
collectedAt: payload.collectedAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
214
|
-
anonymousUserId,
|
|
215
|
-
serverContext,
|
|
216
|
-
clientContext,
|
|
217
|
-
userContext,
|
|
218
|
-
properties: payload.props || {}
|
|
219
|
-
};
|
|
220
|
-
const { clientActions, completion } = dispatchEvent(event, ctx);
|
|
221
|
-
const actions = await clientActions;
|
|
222
|
-
(0, import_server.after)(() => completion);
|
|
223
|
-
const scripts = actions.items.filter((i) => i.type === "script-template");
|
|
224
|
-
return Response.json({ ok: true, scripts: scripts.length > 0 ? scripts : void 0 });
|
|
225
|
-
}
|
|
226
|
-
return Response.json({ ok: true });
|
|
227
|
-
}
|
|
228
140
|
// Annotate the CommonJS export names for ESM import in node:
|
|
229
141
|
0 && (module.exports = {
|
|
230
142
|
createNextlyticsMiddleware
|
package/dist/pages-router.d.ts
CHANGED
|
@@ -5,41 +5,16 @@ import './types.js';
|
|
|
5
5
|
import 'next/dist/server/web/spec-extension/cookies';
|
|
6
6
|
import 'next/server';
|
|
7
7
|
|
|
8
|
-
type
|
|
8
|
+
type PagesRouterContext = {
|
|
9
9
|
req: {
|
|
10
10
|
headers: Record<string, string | string[] | undefined>;
|
|
11
|
+
cookies?: Record<string, string>;
|
|
11
12
|
};
|
|
12
13
|
};
|
|
13
14
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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
|
-
* ```
|
|
15
|
+
* Get Nextlytics props for Pages Router _app.tsx.
|
|
16
|
+
* Reads context from headers set by middleware.
|
|
42
17
|
*/
|
|
43
|
-
declare function getNextlyticsProps(ctx:
|
|
18
|
+
declare function getNextlyticsProps(ctx: PagesRouterContext): NextlyticsContext;
|
|
44
19
|
|
|
45
|
-
export { getNextlyticsProps };
|
|
20
|
+
export { type PagesRouterContext, getNextlyticsProps };
|
package/dist/pages-router.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server';
|
|
2
|
-
import { TemplatizedScriptInsertion
|
|
2
|
+
import { TemplatizedScriptInsertion } from './types.js';
|
|
3
3
|
import 'next/dist/server/web/spec-extension/cookies';
|
|
4
4
|
|
|
5
|
-
declare const
|
|
6
|
-
readonly pathname: "x-
|
|
7
|
-
readonly search: "x-
|
|
8
|
-
readonly pageRenderId: "x-
|
|
9
|
-
readonly
|
|
10
|
-
readonly
|
|
5
|
+
declare const headerNames: {
|
|
6
|
+
readonly pathname: "x-nl-pathname";
|
|
7
|
+
readonly search: "x-nl-search";
|
|
8
|
+
readonly pageRenderId: "x-nl-page-render-id";
|
|
9
|
+
readonly isSoftNavigation: "x-nl-is-soft-nav";
|
|
10
|
+
readonly scripts: "x-nl-scripts";
|
|
11
11
|
};
|
|
12
|
+
declare const LAST_PAGE_RENDER_ID_COOKIE = "last-page-render-id";
|
|
12
13
|
/** Context passed from middleware to server components via headers */
|
|
13
14
|
type ServerComponentContext = {
|
|
14
15
|
/** Unique page render ID (event ID) */
|
|
@@ -17,14 +18,12 @@ type ServerComponentContext = {
|
|
|
17
18
|
pathname: string;
|
|
18
19
|
/** Query string */
|
|
19
20
|
search: string;
|
|
20
|
-
/** Script actions to execute on client */
|
|
21
|
+
/** Script actions to execute on client (params only, templates come from config) */
|
|
21
22
|
scripts: TemplatizedScriptInsertion<unknown>[];
|
|
22
|
-
/** Template definitions for scripts */
|
|
23
|
-
templates: Record<string, JavascriptTemplate>;
|
|
24
23
|
};
|
|
25
24
|
/** Serialize context to response headers (called in middleware) */
|
|
26
25
|
declare function serializeServerComponentContext(response: NextResponse, ctx: ServerComponentContext): void;
|
|
27
26
|
/** Restore context from request headers (called in server components) */
|
|
28
27
|
declare function restoreServerComponentContext(headersList: Headers): ServerComponentContext | null;
|
|
29
28
|
|
|
30
|
-
export { type ServerComponentContext,
|
|
29
|
+
export { LAST_PAGE_RENDER_ID_COOKIE, type ServerComponentContext, headerNames, restoreServerComponentContext, serializeServerComponentContext };
|