@nextlytics/core 0.3.0-canary.80 → 0.3.0

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/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { RequestCookies } from 'next/dist/server/web/spec-extension/cookies';
2
- import { NextMiddleware } from 'next/server';
2
+ import { NextRequest, NextMiddleware } from 'next/server';
3
3
 
4
4
  /** Server-side request context collected in middleware */
5
5
  interface ServerEventContext {
@@ -69,12 +69,6 @@ interface UserContext {
69
69
  }
70
70
  /** Analytics event sent to backends */
71
71
  interface NextlyticsEvent {
72
- /**
73
- * Where the event was triggered?
74
- * - server — on server, e.g in route or server side component
75
- * - client — on client
76
- */
77
- origin: "server" | "client";
78
72
  /** ISO timestamp when event was collected */
79
73
  collectedAt: string;
80
74
  /** Unique event ID */
@@ -115,17 +109,17 @@ type NextlyticsPlugin = {
115
109
  };
116
110
  /** Factory to create plugin per-request (for request-scoped plugins) */
117
111
  type NextlyticsPluginFactory = (ctx: RequestContext) => NextlyticsPlugin;
118
- /** When to deliver page view events for a backend */
119
- type PageViewDelivery =
120
- /** Dispatch on server request in middleware (default) - faster but no client context */
121
- "on-request"
122
- /** Dispatch on page load (client-side) - has full client context (title, screen, etc) */
123
- | "on-page-load";
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";
124
118
  /** Backend with configuration options */
125
119
  type BackendWithConfig = {
126
120
  backend: NextlyticsBackend | NextlyticsBackendFactory;
127
- /** When to send events. Default: "on-request" */
128
- pageViewDelivery?: PageViewDelivery;
121
+ /** When to send events. Default: "immediate" */
122
+ ingestPolicy?: IngestPolicy;
129
123
  };
130
124
  /** Backend config entry - either a backend directly or with config */
131
125
  type BackendConfigEntry = NextlyticsBackend | NextlyticsBackendFactory | BackendWithConfig;
@@ -187,16 +181,13 @@ type DispatchResult = {
187
181
  };
188
182
  type JavascriptTemplate = {
189
183
  items: ScriptElement[];
190
- /**
191
- * Optional dependency key template. When this value changes, the script re-injects.
192
- * If omitted, the template is treated as "once".
193
- */
194
- deps?: string | string[];
195
184
  };
196
185
  type ScriptElement = {
197
- async?: boolean;
198
- body?: string | string[];
186
+ async?: string;
187
+ body?: string;
199
188
  src?: string;
189
+ /** If true, skip insertion if script with same src already exists */
190
+ singleton?: boolean;
200
191
  };
201
192
  /** Backend that receives analytics events */
202
193
  type NextlyticsBackend = {
@@ -210,7 +201,7 @@ type NextlyticsBackend = {
210
201
  returnsClientActions?: boolean;
211
202
  getClientSideTemplates?: () => Record<string, JavascriptTemplate>;
212
203
  /** Handle new event */
213
- onEvent(event: NextlyticsEvent): Promise<ClientAction | void | undefined>;
204
+ onEvent(event: NextlyticsEvent): Promise<void | ClientAction>;
214
205
  /** Update existing event (e.g. add client context to server pageView) */
215
206
  updateEvent(eventId: string, patch: Partial<NextlyticsEvent>): Promise<void> | void;
216
207
  };
@@ -225,41 +216,11 @@ type NextlyticsServerSide = {
225
216
  ok: boolean;
226
217
  }>;
227
218
  };
228
- /** Context for Pages Router _app.tsx */
229
- type PagesRouterContext = {
230
- req: {
231
- headers: Record<string, string | string[] | undefined>;
232
- cookies?: Record<string, string>;
233
- };
234
- };
235
- /** Context passed to NextlyticsClient */
236
- type NextlyticsClientContext = {
237
- requestId: string;
238
- scripts?: TemplatizedScriptInsertion<unknown>[];
239
- templates?: Record<string, JavascriptTemplate>;
240
- };
241
- /** Client-to-server request types (discriminated union) */
242
- type ClientRequest = {
243
- type: "page-view";
244
- clientContext: ClientContext;
245
- /** If true, only update existing event (soft navigation - no dispatch, no scripts) */
246
- softNavigation?: boolean;
247
- } | {
248
- type: "custom-event";
249
- name: string;
250
- props?: Record<string, unknown>;
251
- collectedAt: string;
252
- clientContext: ClientContext;
253
- };
254
- /**
255
- * Result of any /api/event call
256
- */
257
- type ClientRequestResult = {
258
- ok: boolean;
259
- items?: ClientActionItem[];
260
- };
219
+ type AppRouteHandlers = Record<"GET" | "POST", (req: NextRequest) => Promise<Response>>;
261
220
  /** Return value from Nextlytics() */
262
221
  type NextlyticsResult = {
222
+ /** Route handlers for /api/event */
223
+ handlers: AppRouteHandlers;
263
224
  /** Get server-side analytics API */
264
225
  analytics: () => Promise<NextlyticsServerSide>;
265
226
  /** Middleware to intercept requests */
@@ -268,10 +229,6 @@ type NextlyticsResult = {
268
229
  dispatchEvent: (event: NextlyticsEvent) => Promise<DispatchResult>;
269
230
  /** Manually update existing event */
270
231
  updateEvent: (eventId: string, patch: Partial<NextlyticsEvent>) => Promise<void>;
271
- /** Server component that wraps your app to provide analytics context (App Router) */
272
- NextlyticsServer: (props: {
273
- children: React.ReactNode;
274
- }) => Promise<React.ReactElement>;
275
232
  };
276
233
 
277
- export type { AnonymousUserResult, BackendConfigEntry, BackendWithConfig, ClientAction, ClientActionItem, ClientContext, ClientRequest, ClientRequestResult, DispatchResult, JavascriptTemplate, NextlyticsBackend, NextlyticsBackendFactory, NextlyticsClientContext, NextlyticsConfig, NextlyticsEvent, NextlyticsPlugin, NextlyticsPluginFactory, NextlyticsResult, NextlyticsServerSide, PageViewDelivery, PagesRouterContext, RequestContext, ScriptElement, ServerEventContext, TemplatizedScriptInsertion, UserContext };
234
+ export type { AnonymousUserResult, BackendConfigEntry, BackendWithConfig, ClientAction, ClientActionItem, ClientContext, DispatchResult, IngestPolicy, JavascriptTemplate, NextlyticsBackend, NextlyticsBackendFactory, NextlyticsConfig, NextlyticsEvent, NextlyticsPlugin, NextlyticsPluginFactory, NextlyticsResult, NextlyticsServerSide, RequestContext, ScriptElement, ServerEventContext, TemplatizedScriptInsertion, UserContext };
package/dist/uitils.d.ts CHANGED
@@ -2,12 +2,6 @@ import { NextRequest } from 'next/server';
2
2
  import { ServerEventContext } from './types.js';
3
3
  import 'next/dist/server/web/spec-extension/cookies';
4
4
 
5
- /** Returns the full installed Next.js version string (e.g. "16.1.6"), or undefined. */
6
- declare function getNextVersion(): string | undefined;
7
- /** True if the installed Next.js major version is 15. */
8
- declare function isNext15(): boolean;
9
- /** True if the installed Next.js major version is 16. */
10
- declare function isNext16(): boolean;
11
5
  /** Generate a random base62 ID (16 chars = 62^16 ≈ 4.7 × 10^28 combinations) */
12
6
  declare function generateId(): string;
13
7
  type RequestInfo = {
@@ -25,4 +19,4 @@ type RequestInfo = {
25
19
  declare function getRequestInfo(request: NextRequest): RequestInfo;
26
20
  declare function createServerContext(request: NextRequest): ServerEventContext;
27
21
 
28
- export { type RequestInfo, createServerContext, generateId, getNextVersion, getRequestInfo, isNext15, isNext16 };
22
+ export { type RequestInfo, createServerContext, generateId, getRequestInfo };
package/dist/uitils.js CHANGED
@@ -20,41 +20,10 @@ var uitils_exports = {};
20
20
  __export(uitils_exports, {
21
21
  createServerContext: () => createServerContext,
22
22
  generateId: () => generateId,
23
- getNextVersion: () => getNextVersion,
24
- getRequestInfo: () => getRequestInfo,
25
- isNext15: () => isNext15,
26
- isNext16: () => isNext16
23
+ getRequestInfo: () => getRequestInfo
27
24
  });
28
25
  module.exports = __toCommonJS(uitils_exports);
29
26
  var import_headers = require("./headers");
30
- let _nextVersion;
31
- function detectNextVersion() {
32
- if (_nextVersion !== void 0) return _nextVersion;
33
- try {
34
- const pkg = require("next/package.json");
35
- _nextVersion = pkg.version;
36
- } catch {
37
- console.warn(
38
- "[Nextlytics] Could not read Next.js version from next/package.json.\nThis can happen if your bundler does not support JSON imports from\nnode_modules. Ensure `resolveJsonModule: true` is set in tsconfig\nand your bundler can resolve peer-dependency subpath imports.\nTurbopack and webpack (default Next.js bundlers) both support this."
39
- );
40
- _nextVersion = "";
41
- }
42
- return _nextVersion || void 0;
43
- }
44
- function parseMajor(version) {
45
- if (!version) return void 0;
46
- const major = parseInt(version.split(".")[0], 10);
47
- return Number.isFinite(major) ? major : void 0;
48
- }
49
- function getNextVersion() {
50
- return detectNextVersion();
51
- }
52
- function isNext15() {
53
- return parseMajor(detectNextVersion()) === 15;
54
- }
55
- function isNext16() {
56
- return parseMajor(detectNextVersion()) === 16;
57
- }
58
27
  const BASE62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
59
28
  function generateId() {
60
29
  const length = 16;
@@ -76,15 +45,15 @@ function getRequestInfo(request) {
76
45
  );
77
46
  const hasStandardPrefetchHeader = headers.get("next-router-prefetch") === "1" || headers.get("purpose") === "prefetch" || headers.get("sec-purpose") === "prefetch";
78
47
  const nextUrl = headers.get("next-url");
48
+ const isRscPrefetch = nextUrl !== null && nextUrl !== pathname;
49
+ const isPrefetch = hasStandardPrefetchHeader || isRscPrefetch;
79
50
  const isRsc = !!(nextUrl || headers.get("rsc"));
80
51
  const secFetchDest = headers.get("sec-fetch-dest");
81
52
  const secFetchMode = headers.get("sec-fetch-mode");
82
53
  const accept = headers.get("accept") || "";
83
54
  const isDocumentRequest = secFetchDest === "document" || secFetchMode === "navigate";
84
55
  const acceptsHtml = accept.includes("text/html");
85
- const isPageNavigation = isDocumentRequest || acceptsHtml;
86
- const isRscPrefetch = nextUrl !== null && nextUrl !== pathname;
87
- const isPrefetch = hasStandardPrefetchHeader || isRscPrefetch && !isPageNavigation;
56
+ const isPageNavigation = isRsc || isDocumentRequest || acceptsHtml;
88
57
  return {
89
58
  isPrefetch,
90
59
  isRsc,
@@ -121,8 +90,5 @@ function createServerContext(request) {
121
90
  0 && (module.exports = {
122
91
  createServerContext,
123
92
  generateId,
124
- getNextVersion,
125
- getRequestInfo,
126
- isNext15,
127
- isNext16
93
+ getRequestInfo
128
94
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextlytics/core",
3
- "version": "0.3.0-canary.80",
3
+ "version": "0.3.0",
4
4
  "description": "Analytics library for Next.js",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,11 +0,0 @@
1
- import { NextRequest } from 'next/server';
2
- import { NextlyticsEvent, RequestContext, PageViewDelivery, DispatchResult, UserContext } 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, policyFilter?: PageViewDelivery | "client-actions") => DispatchResult;
7
- type UpdateEvent = (eventId: string, patch: Partial<NextlyticsEvent>, ctx: RequestContext) => Promise<void>;
8
- declare function getUserContext(config: NextlyticsConfigWithDefaults, ctx: RequestContext): Promise<UserContext | undefined>;
9
- declare function handleEventPost(request: NextRequest, config: NextlyticsConfigWithDefaults, dispatchEvent: DispatchEvent, updateEvent: UpdateEvent): Promise<Response>;
10
-
11
- export { type DispatchEvent, type UpdateEvent, getUserContext, handleEventPost };
@@ -1,182 +0,0 @@
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 api_handler_exports = {};
20
- __export(api_handler_exports, {
21
- getUserContext: () => getUserContext,
22
- handleEventPost: () => handleEventPost
23
- });
24
- module.exports = __toCommonJS(api_handler_exports);
25
- var import_server = require("next/server");
26
- var import_server_component_context = require("./server-component-context");
27
- var import_uitils = require("./uitils");
28
- var import_anonymous_user = require("./anonymous-user");
29
- function createRequestContext(request) {
30
- return {
31
- headers: request.headers,
32
- cookies: request.cookies
33
- };
34
- }
35
- async function getUserContext(config, ctx) {
36
- if (!config.callbacks.getUser) return void 0;
37
- try {
38
- return await config.callbacks.getUser(ctx) || void 0;
39
- } catch {
40
- return void 0;
41
- }
42
- }
43
- function reconstructServerContext(apiCallContext, clientContext) {
44
- const searchParams = {};
45
- if (clientContext.search) {
46
- const params = new URLSearchParams(clientContext.search);
47
- params.forEach((value, key) => {
48
- if (!searchParams[key]) searchParams[key] = [];
49
- searchParams[key].push(value);
50
- });
51
- }
52
- return {
53
- ...apiCallContext,
54
- host: clientContext.host || apiCallContext.host,
55
- path: clientContext.path || apiCallContext.path,
56
- search: Object.keys(searchParams).length > 0 ? searchParams : apiCallContext.search,
57
- method: "GET"
58
- };
59
- }
60
- function filterScripts(actions) {
61
- const scripts = actions.items.filter((i) => i.type === "script-template");
62
- return scripts.length > 0 ? scripts : void 0;
63
- }
64
- async function handleClientInit(request, hctx) {
65
- const {
66
- pageRenderId,
67
- ctx,
68
- apiCallServerContext,
69
- userContext,
70
- config,
71
- dispatchEvent,
72
- updateEvent
73
- } = hctx;
74
- const { clientContext } = request;
75
- const serverContext = reconstructServerContext(apiCallServerContext, clientContext);
76
- const { anonId: anonymousUserId } = await (0, import_anonymous_user.resolveAnonymousUser)({
77
- ctx,
78
- serverContext,
79
- config
80
- });
81
- const isSoftNavigation = hctx.isSoftNavigation;
82
- const eventId = isSoftNavigation ? (0, import_uitils.generateId)() : pageRenderId;
83
- const event = {
84
- origin: "client",
85
- eventId,
86
- parentEventId: isSoftNavigation ? pageRenderId : void 0,
87
- type: "pageView",
88
- collectedAt: (/* @__PURE__ */ new Date()).toISOString(),
89
- anonymousUserId,
90
- serverContext,
91
- clientContext,
92
- userContext,
93
- properties: {}
94
- };
95
- if (isSoftNavigation) {
96
- const { clientActions, completion: completion2 } = dispatchEvent(event, ctx);
97
- const actions = await clientActions;
98
- (0, import_server.after)(() => completion2);
99
- return Response.json({
100
- ok: true,
101
- items: filterScripts(actions)
102
- });
103
- }
104
- const { completion } = dispatchEvent(event, ctx, "client-actions");
105
- (0, import_server.after)(() => completion);
106
- (0, import_server.after)(() => updateEvent(pageRenderId, { clientContext, userContext, anonymousUserId }, ctx));
107
- return Response.json({ ok: true });
108
- }
109
- async function handleClientEvent(request, hctx) {
110
- const { pageRenderId, ctx, apiCallServerContext, userContext, config, dispatchEvent } = hctx;
111
- const { clientContext, name, props, collectedAt } = request;
112
- const serverContext = clientContext ? reconstructServerContext(apiCallServerContext, clientContext) : apiCallServerContext;
113
- const { anonId: anonymousUserId } = await (0, import_anonymous_user.resolveAnonymousUser)({
114
- ctx,
115
- serverContext,
116
- config
117
- });
118
- const event = {
119
- origin: "client",
120
- eventId: (0, import_uitils.generateId)(),
121
- parentEventId: pageRenderId,
122
- type: name,
123
- collectedAt: collectedAt || (/* @__PURE__ */ new Date()).toISOString(),
124
- anonymousUserId,
125
- serverContext,
126
- clientContext,
127
- userContext,
128
- properties: props || {}
129
- };
130
- const { clientActions, completion } = dispatchEvent(event, ctx);
131
- const actions = await clientActions;
132
- (0, import_server.after)(() => completion);
133
- return Response.json({ ok: true, items: filterScripts(actions) });
134
- }
135
- async function handleEventPost(request, config, dispatchEvent, updateEvent) {
136
- const softNavHeader = request.headers.get(import_server_component_context.headerNames.isSoftNavigation);
137
- const isSoftNavigation = softNavHeader === "1";
138
- const pageRenderIdHeader = request.headers.get(import_server_component_context.headerNames.pageRenderId);
139
- if (!pageRenderIdHeader) {
140
- return Response.json({ error: "Missing page render ID" }, { status: 400 });
141
- }
142
- let body;
143
- try {
144
- body = await request.json();
145
- } catch {
146
- return Response.json({ error: "Invalid JSON" }, { status: 400 });
147
- }
148
- const ctx = createRequestContext(request);
149
- const apiCallServerContext = (0, import_uitils.createServerContext)(request);
150
- const userContext = await getUserContext(config, ctx);
151
- const cookiePageRenderId = request.cookies.get(import_server_component_context.LAST_PAGE_RENDER_ID_COOKIE)?.value;
152
- const pageRenderId = isSoftNavigation ? cookiePageRenderId ?? (0, import_uitils.generateId)() : pageRenderIdHeader;
153
- if (isSoftNavigation && !cookiePageRenderId && config.debug) {
154
- console.warn(
155
- "[Nextlytics] Missing last-page-render-id cookie on soft navigation; using a new id."
156
- );
157
- }
158
- const hctx = {
159
- pageRenderId,
160
- isSoftNavigation,
161
- ctx,
162
- apiCallServerContext,
163
- userContext,
164
- config,
165
- dispatchEvent,
166
- updateEvent
167
- };
168
- const bodyType = body.type;
169
- switch (bodyType) {
170
- case "page-view":
171
- return handleClientInit(body, hctx);
172
- case "custom-event":
173
- return handleClientEvent(body, hctx);
174
- default:
175
- return Response.json({ ok: false, error: `Unknown body type ${bodyType}` }, { status: 400 });
176
- }
177
- }
178
- // Annotate the CommonJS export names for ESM import in node:
179
- 0 && (module.exports = {
180
- getUserContext,
181
- handleEventPost
182
- });
@@ -1,35 +0,0 @@
1
- import * as react from 'react';
2
-
3
- declare const debug: (...args: unknown[]) => void;
4
- type NavigationEvent = {
5
- softNavigation: boolean;
6
- signal?: AbortSignal;
7
- };
8
- declare function usePathnameSafe(): string | null;
9
- /**
10
- * Hook that detects page navigations and calls the callback once per navigation.
11
- *
12
- * - App Router: initial load = hard nav, subsequent pathname changes = soft nav
13
- * - Pages Router: every navigation changes requestId = always hard nav
14
- *
15
- * Provides an AbortSignal for soft navigations to cancel in-flight requests.
16
- */
17
- declare function useNavigation(requestId: string, onNavigate: (event: NavigationEvent) => void): void;
18
- /** Props for the InjectScript utility component */
19
- type InjectScriptProps = {
20
- /** Inline script body (mutually exclusive with src) */
21
- body?: string;
22
- /** External script URL (mutually exclusive with body) */
23
- src?: string;
24
- /** Load external script async */
25
- async?: boolean;
26
- /** Dependencies that control re-injection. Empty/undefined = once, changes trigger re-injection */
27
- deps?: unknown[];
28
- };
29
- /**
30
- * Pure utility component that injects a script into the document head.
31
- * Agnostic to templates, combining logic - just handles the injection lifecycle.
32
- */
33
- declare const InjectScript: react.NamedExoticComponent<InjectScriptProps>;
34
-
35
- export { InjectScript, type InjectScriptProps, type NavigationEvent, debug, useNavigation, usePathnameSafe };
@@ -1,121 +0,0 @@
1
- "use strict";
2
- "use client";
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __export = (target, all) => {
8
- for (var name in all)
9
- __defProp(target, name, { get: all[name], enumerable: true });
10
- };
11
- var __copyProps = (to, from, except, desc) => {
12
- if (from && typeof from === "object" || typeof from === "function") {
13
- for (let key of __getOwnPropNames(from))
14
- if (!__hasOwnProp.call(to, key) && key !== except)
15
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
- }
17
- return to;
18
- };
19
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
- var client_utils_exports = {};
21
- __export(client_utils_exports, {
22
- InjectScript: () => InjectScript,
23
- debug: () => debug,
24
- useNavigation: () => useNavigation,
25
- usePathnameSafe: () => usePathnameSafe
26
- });
27
- module.exports = __toCommonJS(client_utils_exports);
28
- var import_react = require("react");
29
- const DEBUG_KEY = "nextlytics:debug";
30
- function isDebugEnabled() {
31
- if (typeof window === "undefined") return false;
32
- try {
33
- return localStorage.getItem(DEBUG_KEY) === "true";
34
- } catch {
35
- return false;
36
- }
37
- }
38
- const debug = (...args) => {
39
- if (!isDebugEnabled()) return;
40
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[1].slice(0, -1);
41
- console.log(`[${timestamp}] [Nextlytics]`, ...args);
42
- };
43
- let usePathnameImpl = null;
44
- try {
45
- usePathnameImpl = require("next/navigation").usePathname;
46
- } catch {
47
- }
48
- function usePathnameSafe() {
49
- if (!usePathnameImpl) return null;
50
- return usePathnameImpl();
51
- }
52
- function useNavigation(requestId, onNavigate) {
53
- const pathname = usePathnameSafe();
54
- const stateRef = (0, import_react.useRef)(null);
55
- const onNavigateRef = (0, import_react.useRef)(onNavigate);
56
- onNavigateRef.current = onNavigate;
57
- (0, import_react.useEffect)(() => {
58
- const prev = stateRef.current;
59
- const isInitial = prev === null;
60
- const requestIdChanged = !isInitial && prev.requestId !== requestId;
61
- const pathnameChanged = !isInitial && pathname !== null && prev.pathname !== pathname;
62
- stateRef.current = { requestId, pathname };
63
- if (!isInitial && !requestIdChanged && !pathnameChanged) {
64
- return;
65
- }
66
- const softNavigation = !isInitial && !requestIdChanged && pathnameChanged;
67
- debug("Navigation", {
68
- isInitial,
69
- softNavigation,
70
- requestId,
71
- pathname,
72
- requestIdChanged,
73
- pathnameChanged
74
- });
75
- if (softNavigation) {
76
- const controller = new AbortController();
77
- onNavigateRef.current({ softNavigation: true, signal: controller.signal });
78
- return () => {
79
- debug("Aborting previous soft navigation request");
80
- controller.abort();
81
- };
82
- } else {
83
- onNavigateRef.current({ softNavigation: false });
84
- }
85
- }, [requestId, pathname]);
86
- }
87
- function arraysEqual(a, b) {
88
- if (a === b) return true;
89
- if (!a || !b || a.length !== b.length) return false;
90
- return a.every((v, i) => v === b[i]);
91
- }
92
- const InjectScript = (0, import_react.memo)(
93
- function InjectScript2({ body, src, async: isAsync, deps = [] }) {
94
- const depsKey = deps.map(String).join("\0");
95
- (0, import_react.useEffect)(() => {
96
- const el = document.createElement("script");
97
- if (src) {
98
- el.src = src;
99
- if (isAsync) el.async = true;
100
- debug("Inject external", { src, deps });
101
- } else if (body) {
102
- el.textContent = body;
103
- debug("Inject inline", { body: body.slice(0, 100), deps });
104
- }
105
- document.head.appendChild(el);
106
- return () => {
107
- debug("Remove script", { deps });
108
- el.remove();
109
- };
110
- }, [depsKey]);
111
- return null;
112
- },
113
- (prev, next) => prev.body === next.body && prev.src === next.src && prev.async === next.async && arraysEqual(prev.deps, next.deps)
114
- );
115
- // Annotate the CommonJS export names for ESM import in node:
116
- 0 && (module.exports = {
117
- InjectScript,
118
- debug,
119
- useNavigation,
120
- usePathnameSafe
121
- });
@@ -1,6 +0,0 @@
1
- type JsonValue = string | number | boolean | null | JsonValue[] | {
2
- [key: string]: JsonValue;
3
- };
4
- declare function stableHash(value: JsonValue): string;
5
-
6
- export { type JsonValue, stableHash };
@@ -1,76 +0,0 @@
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 stable_hash_exports = {};
20
- __export(stable_hash_exports, {
21
- stableHash: () => stableHash
22
- });
23
- module.exports = __toCommonJS(stable_hash_exports);
24
- const FNV_OFFSET_BASIS = 2166136261;
25
- const FNV_PRIME = 16777619;
26
- function stableStringify(value, seen) {
27
- if (value === null) return "null";
28
- const valueType = typeof value;
29
- if (valueType === "string") return JSON.stringify(value);
30
- if (valueType === "number") return Number.isFinite(value) ? String(value) : "null";
31
- if (valueType === "boolean") return value ? "true" : "false";
32
- if (valueType === "bigint") {
33
- throw new TypeError("Do not know how to serialize a BigInt");
34
- }
35
- if (valueType === "undefined" || valueType === "function" || valueType === "symbol") {
36
- return void 0;
37
- }
38
- if (typeof value.toJSON === "function") {
39
- return stableStringify(value.toJSON(), seen);
40
- }
41
- if (Array.isArray(value)) {
42
- const items = value.map((item) => stableStringify(item, seen) ?? "null");
43
- return `[${items.join(",")}]`;
44
- }
45
- const obj = value;
46
- if (seen.has(obj)) {
47
- throw new TypeError("Converting circular structure to JSON");
48
- }
49
- seen.add(obj);
50
- const keys = Object.keys(obj).sort();
51
- const parts = [];
52
- for (const key of keys) {
53
- const next = stableStringify(obj[key], seen);
54
- if (next !== void 0) {
55
- parts.push(`${JSON.stringify(key)}:${next}`);
56
- }
57
- }
58
- seen.delete(obj);
59
- return `{${parts.join(",")}}`;
60
- }
61
- function fnv1aHash(input) {
62
- let hash = FNV_OFFSET_BASIS;
63
- for (let i = 0; i < input.length; i += 1) {
64
- hash ^= input.charCodeAt(i);
65
- hash = Math.imul(hash, FNV_PRIME) >>> 0;
66
- }
67
- return hash.toString(16).padStart(8, "0");
68
- }
69
- function stableHash(value) {
70
- const stable = stableStringify(value, /* @__PURE__ */ new WeakSet()) ?? "null";
71
- return fnv1aHash(stable);
72
- }
73
- // Annotate the CommonJS export names for ESM import in node:
74
- 0 && (module.exports = {
75
- stableHash
76
- });