@hawk.so/nextjs 1.0.0 → 1.1.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/index.d.ts CHANGED
@@ -1,75 +1,315 @@
1
1
  /// <reference path="../global.d.ts" />
2
- import { NextConfig } from 'next';
2
+ import { HawkInitialSettings } from '@hawk.so/browser';
3
+ import { CatcherMessagePayload, EventContext, AffectedUser } from '@hawk.so/types';
4
+ export { AffectedUser, EventContext } from '@hawk.so/types';
5
+ import { BreadcrumbsAPI } from '@hawk.so/core';
6
+ export { BreadcrumbsAPI } from '@hawk.so/core';
7
+ import { Component, ReactNode, ErrorInfo, ReactElement } from 'react';
3
8
 
4
9
  /**
5
- * Options of the commits collecting
10
+ * Event passed to `beforeSend`: from the browser or from the server
6
11
  */
7
- interface CommitsOptions {
12
+ type HawkEvent = CatcherMessagePayload<'errors/javascript'> | CatcherMessagePayload<'errors/nodejs'>;
13
+ /**
14
+ * Options of `init()`. The same options are accepted in the browser and on the server,
15
+ * options of the browser catcher are ignored on the server
16
+ */
17
+ interface HawkInitOptions extends Pick<HawkInitialSettings, 'user' | 'debug' | 'breadcrumbs' | 'consoleTracking' | 'issues' | 'reconnectionTimeout' | 'transport'> {
18
+ /**
19
+ * Hawk Integration Token. Default is the NEXT_PUBLIC_HAWK_TOKEN environment variable,
20
+ * on the server HAWK_TOKEN is checked first
21
+ */
22
+ token?: string;
23
+ /**
24
+ * Release id. Default is `process.env.HAWK_RELEASE` set by `withHawkConfig`
25
+ */
26
+ release?: string;
8
27
  /**
9
- * Path to the repository with `.git` directory. Default is the Next.js project directory
28
+ * Collector endpoint overwrite: WebSocket URL in the browser, HTTP URL on the server
10
29
  */
11
- repo?: string;
30
+ collectorEndpoint?: string;
12
31
  /**
13
- * How many recent commits to send. Default is 5
32
+ * Data sent with every event
14
33
  */
15
- number?: number;
34
+ context?: EventContext;
35
+ /**
36
+ * Filters or modifies events before sending. Return the event to send it or `false` to drop it
37
+ */
38
+ beforeSend?: (event: HawkEvent) => HawkEvent | false | void;
39
+ /**
40
+ * Disables catching of uncaught errors and unhandled rejections: `window` handlers in the browser,
41
+ * `process` handlers in Node.js
42
+ */
43
+ disableGlobalErrorsHandling?: boolean;
16
44
  }
17
45
 
18
46
  /**
19
- * Options of the Hawk Next.js integration
47
+ * Request passed by Next.js to `onRequestError`
20
48
  */
21
- interface HawkNextOptions {
49
+ interface RequestErrorRequest {
22
50
  /**
23
- * Hawk Integration Token. Default is the HAWK_TOKEN environment variable
51
+ * Request path with the query string
24
52
  */
25
- token?: string;
53
+ path: string;
26
54
  /**
27
- * Unique identifier of the release.
28
- * Default is HAWK_RELEASE environment variable, commit SHA from CI or git, or the build time
55
+ * HTTP method
29
56
  */
30
- release?: string | number;
57
+ method: string;
31
58
  /**
32
- * Whether to remove client source maps after sending. Default is true
59
+ * Request headers
33
60
  */
34
- removeSourceMaps?: boolean;
61
+ headers: Record<string, string | string[] | undefined>;
62
+ }
63
+ /**
64
+ * Context passed by Next.js to `onRequestError`. Fields differ between Next.js versions, so they are loose strings
65
+ */
66
+ interface RequestErrorContext {
35
67
  /**
36
- * Options for sending recent commits with the release, or false to skip. Enabled by default
68
+ * `App Router` or `Pages Router`
37
69
  */
38
- commits?: boolean | CommitsOptions;
70
+ routerKind: string;
39
71
  /**
40
- * Collector endpoint overwrite
72
+ * Route file path, e.g. `/blog/[slug]`
41
73
  */
42
- collectorEndpoint?: string;
74
+ routePath: string;
75
+ /**
76
+ * `render`, `route`, `action`, `middleware` or `proxy`
77
+ */
78
+ routeType: string;
79
+ /**
80
+ * Rendering phase of the App Router
81
+ */
82
+ renderSource?: string;
83
+ /**
84
+ * Revalidation reason: `on-demand` or `stale`
85
+ */
86
+ revalidateReason?: string;
87
+ /**
88
+ * Render type of the App Router in Next.js 15: `dynamic` or `dynamic-resume`
89
+ */
90
+ renderType?: string;
43
91
  }
44
92
  /**
45
- * Context passed by Next.js to the config function
93
+ * Next.js data sent with events in `addons.nextjs`
46
94
  */
47
- interface NextConfigContext {
95
+ interface NextjsAddons {
96
+ /**
97
+ * Where the error happened: `nodejs`, `edge` or `browser`
98
+ */
99
+ runtime?: string;
100
+ /**
101
+ * Error page that caught the error: `error`, `global-error` or `boundary`
102
+ */
103
+ boundary?: string;
48
104
  /**
49
- * Default Next.js config
105
+ * React component stack of the error caught by `HawkErrorBoundary`
50
106
  */
51
- defaultConfig: NextConfig;
107
+ componentStack?: string;
108
+ /**
109
+ * Digest of the server error. The same digest is shown on the error page in the browser
110
+ */
111
+ digest?: string;
112
+ /**
113
+ * `App Router` or `Pages Router`
114
+ */
115
+ routerKind?: string;
116
+ /**
117
+ * Route file path, e.g. `/blog/[slug]`
118
+ */
119
+ routePath?: string;
120
+ /**
121
+ * `render`, `route`, `action`, `middleware` or `proxy`
122
+ */
123
+ routeType?: string;
124
+ /**
125
+ * Rendering phase of the App Router
126
+ */
127
+ renderSource?: string;
128
+ /**
129
+ * Revalidation reason
130
+ */
131
+ revalidateReason?: string;
132
+ /**
133
+ * Render type of the App Router
134
+ */
135
+ renderType?: string;
136
+ /**
137
+ * HTTP method
138
+ */
139
+ method?: string;
140
+ /**
141
+ * Request path with the query string
142
+ */
143
+ path?: string;
144
+ /**
145
+ * Request headers that don't contain credentials
146
+ */
147
+ headers?: Record<string, string>;
52
148
  }
149
+
150
+ /**
151
+ * Sends the error or message to Hawk. Next.js errors of `redirect()` and `notFound()` are skipped
152
+ * @param error - error or message
153
+ * @param context - data sent with the event
154
+ * @param user - user affected by the error, used on the server only. In the browser use `setUser()`
155
+ */
156
+ declare function send(error: unknown, context?: EventContext, user?: AffectedUser): void;
157
+ /**
158
+ * Sets the user sent with every event in the browser.
159
+ * Ignored on the server: it handles requests of many users at once, so pass the user to `send()` there
160
+ * @param _user - current user
161
+ */
162
+ declare function setUser(_user: AffectedUser): void;
163
+ /**
164
+ * Removes the user set with `setUser()`. Does nothing on the server
165
+ */
166
+ declare function clearUser(): void;
167
+ /**
168
+ * Sets data sent with every event. On the server it is shared by all requests of the process
169
+ * @param context - data to send
170
+ */
171
+ declare function setContext(context: EventContext | undefined): void;
53
172
  /**
54
- * Next.js config exported as a function
173
+ * Breadcrumbs sent with the events: actions that happened before the error.
174
+ * Collected in the browser only: on the server requests of different users would be mixed
55
175
  */
56
- type NextConfigFunction = (phase: string, context: NextConfigContext) => NextConfig | Promise<NextConfig>;
176
+ declare const breadcrumbs: BreadcrumbsAPI;
57
177
  /**
58
- * Wraps Next.js config to send releases with source maps to Hawk.
59
- *
60
- * - exposes the release id to the app code as `process.env.HAWK_RELEASE`
61
- * - enables client source maps for production builds
62
- * - after `next build` sends client source maps and recent commits to Hawk and removes the maps
63
- * @param nextConfig - Next.js config object
64
- * @param options - Hawk plugin options
178
+ * Waits for the events in progress, e.g. before a serverless function is frozen.
179
+ * `onRequestError` already waits for its events. In the browser resolves at once
180
+ * @param timeout - max time to wait, in milliseconds
181
+ * @returns false when the timeout is reached or the catcher is not created
65
182
  */
66
- declare function withHawkConfig(nextConfig: NextConfig, options?: HawkNextOptions): NextConfig;
183
+ declare function flush(timeout?: number): Promise<boolean>;
184
+ /**
185
+ * Sends errors of server components, route handlers, server actions and middleware, on the server only.
186
+ * Export it from `instrumentation.ts`: `export const onRequestError = Hawk.onRequestError`
187
+ * @param error - caught error
188
+ * @param request - failed request
189
+ * @param context - route data
190
+ */
191
+ declare function onRequestError(error: unknown, request: Readonly<RequestErrorRequest>, context: Readonly<RequestErrorContext>): Promise<void>;
192
+ /**
193
+ * Adds a breadcrumb for every navigation of the App Router, in the browser only.
194
+ * Export it from `instrumentation-client.ts`: `export const onRouterTransitionStart = Hawk.onRouterTransitionStart`
195
+ * @param _url - target URL
196
+ * @param _navigationType - `push`, `replace` or `traverse`
197
+ */
198
+ declare function onRouterTransitionStart(_url: string, _navigationType: string): void;
199
+
200
+ /**
201
+ * Error passed by Next.js to the error pages
202
+ */
203
+ type ErrorWithDigest = Error & {
204
+ /**
205
+ * Digest of the server error. Server errors have a generic message in production
206
+ */
207
+ digest?: string;
208
+ };
209
+ /**
210
+ * Props passed by Next.js to `error.tsx` and `global-error.tsx`
211
+ */
212
+ interface HawkErrorPageProps {
213
+ /**
214
+ * Caught error
215
+ */
216
+ error: ErrorWithDigest;
217
+ /**
218
+ * Renders the segment again
219
+ */
220
+ reset?: () => void;
221
+ }
222
+ /**
223
+ * Props passed to the fallback of `HawkErrorBoundary`
224
+ */
225
+ interface HawkErrorBoundaryFallbackProps {
226
+ /**
227
+ * Caught error
228
+ */
229
+ error: unknown;
230
+ /**
231
+ * Renders the children again
232
+ */
233
+ reset: () => void;
234
+ }
235
+ /**
236
+ * Content, fallback and error callback of `HawkErrorBoundary`
237
+ */
238
+ interface HawkErrorBoundaryProps {
239
+ /**
240
+ * Content to protect
241
+ */
242
+ children?: ReactNode;
243
+ /**
244
+ * Shown instead of the children after an error
245
+ */
246
+ fallback?: ReactNode | ((props: HawkErrorBoundaryFallbackProps) => ReactNode);
247
+ /**
248
+ * Called after the error is sent
249
+ */
250
+ onError?: (error: unknown, info: ErrorInfo) => void;
251
+ }
252
+ /**
253
+ * Whether `HawkErrorBoundary` shows the fallback, and the error it caught
254
+ */
255
+ interface HawkErrorBoundaryState {
256
+ /**
257
+ * Whether an error is caught
258
+ */
259
+ hasError: boolean;
260
+ /**
261
+ * Caught error
262
+ */
263
+ error: unknown;
264
+ }
265
+ /**
266
+ * Sends the error shown by `error.tsx` to Hawk.
267
+ * Next.js only logs errors caught by error boundaries, so the catcher doesn't get them otherwise
268
+ * @param error - error passed to the error page
269
+ */
270
+ declare function useHawkError(error: unknown): void;
271
+ /**
272
+ * Ready `app/global-error.tsx`: sends the error and shows a minimal error page.
273
+ * Use it as `export { HawkGlobalError as default } from '@hawk.so/nextjs'`
274
+ * @param props - props passed by Next.js
275
+ */
276
+ declare function HawkGlobalError({ error, reset }: HawkErrorPageProps): ReactElement;
277
+ /**
278
+ * Error boundary that sends errors of its children to Hawk and shows the fallback.
279
+ * Errors Next.js uses for `redirect()` and `notFound()` are passed to the Next.js boundaries
280
+ */
281
+ declare class HawkErrorBoundary extends Component<HawkErrorBoundaryProps, HawkErrorBoundaryState> {
282
+ /**
283
+ * Initial state
284
+ */
285
+ state: HawkErrorBoundaryState;
286
+ /**
287
+ * Shows the fallback after an error
288
+ * @param error - caught error
289
+ */
290
+ static getDerivedStateFromError(error: unknown): HawkErrorBoundaryState;
291
+ /**
292
+ * Sends the caught error
293
+ * @param error - caught error
294
+ * @param info - component stack
295
+ */
296
+ componentDidCatch(error: unknown, info: ErrorInfo): void;
297
+ /**
298
+ * Renders the children again
299
+ */
300
+ reset: () => void;
301
+ /**
302
+ * Renders the children or the fallback
303
+ */
304
+ render(): ReactNode;
305
+ }
306
+
67
307
  /**
68
- * Wraps Next.js config function to send releases with source maps to Hawk
69
- * @param nextConfig - function returning Next.js config
70
- * @param options - Hawk plugin options
308
+ * Initializes Hawk. Call it in `instrumentation-client.ts` for the browser
309
+ * and in `register()` of `instrumentation.ts` for the server
310
+ * @param options - token, release and other options
71
311
  */
72
- declare function withHawkConfig(nextConfig: NextConfigFunction, options?: HawkNextOptions): NextConfigFunction;
312
+ declare function init(options?: HawkInitOptions): void;
73
313
 
74
- export { withHawkConfig };
75
- export type { CommitsOptions, HawkNextOptions, NextConfigFunction };
314
+ export { HawkErrorBoundary, HawkGlobalError, breadcrumbs, clearUser, flush, init, onRequestError, onRouterTransitionStart, send, setContext, setUser, useHawkError };
315
+ export type { HawkErrorBoundaryFallbackProps, HawkErrorBoundaryProps, HawkErrorPageProps, HawkEvent, HawkInitOptions, NextjsAddons, RequestErrorContext, RequestErrorRequest };
package/dist/react.cjs ADDED
@@ -0,0 +1,199 @@
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
+
21
+ // src/react/index.tsx
22
+ var react_exports = {};
23
+ __export(react_exports, {
24
+ HawkErrorBoundary: () => HawkErrorBoundary,
25
+ HawkGlobalError: () => HawkGlobalError,
26
+ useHawkError: () => useHawkError
27
+ });
28
+ module.exports = __toCommonJS(react_exports);
29
+ var import_react = require("react");
30
+
31
+ // src/common/carrier.ts
32
+ var CARRIER_KEY = /* @__PURE__ */ Symbol.for("hawk.nextjs");
33
+ function getCarrier() {
34
+ const store = globalThis;
35
+ store[CARRIER_KEY] ??= {};
36
+ return store[CARRIER_KEY];
37
+ }
38
+
39
+ // src/common/control-flow.ts
40
+ var CONTROL_FLOW_DIGEST_PREFIXES = [
41
+ "NEXT_REDIRECT",
42
+ "NEXT_HTTP_ERROR_FALLBACK;",
43
+ "NEXT_NOT_FOUND",
44
+ "BAILOUT_TO_CLIENT_SIDE_RENDERING",
45
+ "DYNAMIC_SERVER_USAGE",
46
+ "NEXT_PRERENDER_INTERRUPTED",
47
+ "HANGING_PROMISE_REJECTION"
48
+ ];
49
+ var STATIC_GEN_BAILOUT_CODE = "NEXT_STATIC_GEN_BAILOUT";
50
+ var REACT_POSTPONE_TYPE = /* @__PURE__ */ Symbol.for("react.postpone");
51
+ var MAX_CAUSE_DEPTH = 5;
52
+ function isControlFlowDigest(value) {
53
+ return typeof value === "string" && CONTROL_FLOW_DIGEST_PREFIXES.some((prefix) => value.startsWith(prefix));
54
+ }
55
+ function isNextControlFlowError(error) {
56
+ let current = error;
57
+ for (let depth = 0; depth < MAX_CAUSE_DEPTH; depth++) {
58
+ if (typeof current !== "object" || current === null) {
59
+ return false;
60
+ }
61
+ const value = current;
62
+ if (isControlFlowDigest(value.digest) || isControlFlowDigest(value.message) || value.code === STATIC_GEN_BAILOUT_CODE || value.$$typeof === REACT_POSTPONE_TYPE) {
63
+ return true;
64
+ }
65
+ current = value.cause;
66
+ }
67
+ return false;
68
+ }
69
+ function getDigest(error) {
70
+ if (typeof error === "object" && error !== null) {
71
+ const { digest } = error;
72
+ if (typeof digest === "string" && digest !== "") {
73
+ return digest;
74
+ }
75
+ }
76
+ return void 0;
77
+ }
78
+
79
+ // src/react/index.tsx
80
+ var import_jsx_runtime = require("react/jsx-runtime");
81
+ function captureBoundaryError(error, boundary, componentStack) {
82
+ if (isNextControlFlowError(error) || getDigest(error) !== void 0) {
83
+ return;
84
+ }
85
+ getCarrier().capture?.(error, {
86
+ boundary,
87
+ ...typeof componentStack === "string" ? { componentStack } : {}
88
+ });
89
+ }
90
+ function useHawkError(error) {
91
+ (0, import_react.useEffect)(() => {
92
+ captureBoundaryError(error, "error");
93
+ }, [error]);
94
+ }
95
+ var globalErrorStyles = {
96
+ container: {
97
+ fontFamily: "system-ui, sans-serif",
98
+ minHeight: "100vh",
99
+ display: "flex",
100
+ flexDirection: "column",
101
+ alignItems: "center",
102
+ justifyContent: "center",
103
+ gap: 16,
104
+ textAlign: "center"
105
+ },
106
+ title: {
107
+ fontSize: 14,
108
+ fontWeight: 400,
109
+ margin: 0
110
+ },
111
+ digest: {
112
+ fontSize: 12,
113
+ opacity: 0.6,
114
+ margin: 0
115
+ },
116
+ button: {
117
+ fontSize: 14,
118
+ padding: "6px 14px",
119
+ cursor: "pointer"
120
+ }
121
+ };
122
+ function HawkGlobalError({ error, reset }) {
123
+ (0, import_react.useEffect)(() => {
124
+ captureBoundaryError(error, "global-error");
125
+ }, [error]);
126
+ const side = error.digest === void 0 ? "client" : "server";
127
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("html", { lang: "en", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("body", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", { style: globalErrorStyles.container, children: [
128
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h2", { style: globalErrorStyles.title, children: [
129
+ "Application error: a ",
130
+ side,
131
+ "-side exception has occurred."
132
+ ] }),
133
+ error.digest !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { style: globalErrorStyles.digest, children: [
134
+ "Digest: ",
135
+ error.digest
136
+ ] }),
137
+ reset !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", style: globalErrorStyles.button, onClick: reset, children: "Try again" })
138
+ ] }) }) });
139
+ }
140
+ var HawkErrorBoundary = class extends import_react.Component {
141
+ /**
142
+ * Initial state
143
+ */
144
+ state = {
145
+ hasError: false,
146
+ error: null
147
+ };
148
+ /**
149
+ * Shows the fallback after an error
150
+ * @param error - caught error
151
+ */
152
+ static getDerivedStateFromError(error) {
153
+ return {
154
+ hasError: true,
155
+ error
156
+ };
157
+ }
158
+ /**
159
+ * Sends the caught error
160
+ * @param error - caught error
161
+ * @param info - component stack
162
+ */
163
+ componentDidCatch(error, info) {
164
+ if (isNextControlFlowError(error)) {
165
+ return;
166
+ }
167
+ captureBoundaryError(error, "boundary", info.componentStack);
168
+ this.props.onError?.(error, info);
169
+ }
170
+ /**
171
+ * Renders the children again
172
+ */
173
+ reset = () => {
174
+ this.setState({
175
+ hasError: false,
176
+ error: null
177
+ });
178
+ };
179
+ /**
180
+ * Renders the children or the fallback
181
+ */
182
+ render() {
183
+ const { hasError, error } = this.state;
184
+ if (!hasError) {
185
+ return this.props.children;
186
+ }
187
+ if (isNextControlFlowError(error)) {
188
+ throw error;
189
+ }
190
+ const { fallback } = this.props;
191
+ if (typeof fallback === "function") {
192
+ return fallback({
193
+ error,
194
+ reset: this.reset
195
+ });
196
+ }
197
+ return fallback ?? null;
198
+ }
199
+ };