@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/README.md CHANGED
@@ -1,191 +1,227 @@
1
1
  # Hawk Next.js
2
2
 
3
- Sends releases with source maps from Next.js builds to [Hawk](https://hawk.so), so errors from the browser show the original source code instead of minified chunks.
3
+ Error tracking for Next.js apps with [Hawk](https://hawk.so). Catches errors in the browser, on the server and in the Edge runtime, and sends releases with source maps, so errors from the browser show the original source code instead of minified chunks.
4
4
 
5
+ - 🐞 Errors from the browser, Node.js and Edge runtimes with one import
6
+ - 🧭 Errors of server components, route handlers, server actions and middleware with the route data
7
+ - 🧱 Ready error pages and error boundary for React
8
+ - 🙅 `redirect()`, `notFound()` and other Next.js control flow errors are skipped
9
+ - ⏱️ Events are delivered before serverless functions are frozen
5
10
  - 🌟 Client source maps sending
6
11
  - ⚡ Turbopack and webpack support
7
12
  - 🏷️ Automatic release id from CI, git or build time
8
13
  - 💬 Recent commits sending
9
14
  - 🙈 Source maps removal after sending, so the source code is not published
10
- - 🧩 One-line setup in `next.config`
11
15
  - 🛡️ Build never fails because of sending errors
12
16
 
13
- The package only sends releases, it doesn't catch errors. Use Hawk catchers for that, see [Error tracking](#error-tracking).
14
-
15
17
  ## Install
16
18
 
17
19
  ```shell
18
20
  yarn add @hawk.so/nextjs
19
21
  ```
20
22
 
21
- Install it as a regular dependency, not with `-D`: `next start` loads `next.config` too, and fails with `Cannot find module '@hawk.so/nextjs'` when production installs skip dev dependencies.
23
+ Install it as a regular dependency, not with `-D`: `next start` loads `next.config` too, and fails with `Cannot find module '@hawk.so/nextjs/config'` when production installs skip dev dependencies.
22
24
 
23
25
  ## Connect
24
26
 
25
- Wrap your Next.js config with `withHawkConfig`:
27
+ Put your project's Integration Token to the `NEXT_PUBLIC_HAWK_TOKEN` environment variable of the build, e.g. in `.env`:
28
+
29
+ ```shell
30
+ NEXT_PUBLIC_HAWK_TOKEN=eyJpbnRlZ3JhdGlvbklk...
31
+ ```
32
+
33
+ The plugin and the catchers read the same variable. The browser catcher has the token in the client code anyway, and Next.js inlines `NEXT_PUBLIC_` variables during the build, so the running app needs no Hawk variables.
34
+
35
+ Then add four files.
36
+
37
+ **1. `next.config.ts`** sends the release with source maps on `next build`:
26
38
 
27
39
  ```ts
28
- // next.config.ts
29
40
  import type { NextConfig } from 'next';
30
- import { withHawkConfig } from '@hawk.so/nextjs';
41
+ import { withHawkConfig } from '@hawk.so/nextjs/config';
31
42
 
32
43
  const nextConfig: NextConfig = {
33
44
  // your config
34
45
  };
35
46
 
36
- export default withHawkConfig(nextConfig, {
37
- token: process.env.HAWK_TOKEN, // Your project's Integration Token
38
- });
47
+ export default withHawkConfig(nextConfig);
39
48
  ```
40
49
 
41
- Config functions are supported as well: `withHawkConfig((phase, { defaultConfig }) => ({ ... }), options)`.
42
-
43
- On `next build` the plugin:
50
+ **2. `instrumentation-client.ts`** in the project root (or in `src`) catches errors in the browser. Next.js runs it before the app starts:
44
51
 
45
- 1. resolves the release id and exposes it to your code as `process.env.HAWK_RELEASE`
46
- 2. enables client source maps (`productionBrowserSourceMaps`) unless you set it yourself
47
- 3. after compilation sends recent commits and client source maps to Hawk
48
- 4. removes client source maps and `sourceMappingURL` comments from `.next/static`, so the source code is not published
52
+ ```ts
53
+ import * as Hawk from '@hawk.so/nextjs';
49
54
 
50
- `next dev` sends nothing.
55
+ Hawk.init();
51
56
 
52
- ### Plugin options
57
+ export const onRouterTransitionStart = Hawk.onRouterTransitionStart;
58
+ ```
53
59
 
54
- | name | type | required | description |
55
- | -- | -- | -- | -- |
56
- | `token` | string | optional | Your project's Integration Token. Default is the `HAWK_TOKEN` environment variable. Without a token the release is not sent |
57
- | `release` | string/number | optional | Unique identifier of the release. See [Release id](#release-id) |
58
- | `removeSourceMaps` | boolean | optional | Shows if the plugin should remove client source maps after sending. Default is `true` |
59
- | `commits` | boolean/object | optional | Sends recent commits with the release. Default is `true`. Pass `false` to disable or `{ repo, number }` to set the path to the git repository (default is the project directory) and the number of commits (default is `5`) |
60
+ **3. `instrumentation.ts`** catches errors on the server, in Node.js and Edge runtimes:
60
61
 
61
- ### Release id
62
+ ```ts
63
+ import * as Hawk from '@hawk.so/nextjs';
62
64
 
63
- Release id links error events to the source maps and commits sent from the build. The plugin picks it once per build, in this order:
65
+ export function register() {
66
+ Hawk.init();
67
+ }
64
68
 
65
- 1. `release` option
66
- 2. `HAWK_RELEASE` environment variable
67
- 3. commit SHA from the CI environment: GitHub Actions (`GITHUB_SHA`), GitLab CI (`CI_COMMIT_SHA`), Vercel (`VERCEL_GIT_COMMIT_SHA`), Netlify, Cloudflare Pages, CircleCI and [others](./src/release.ts)
68
- 4. current commit: `git rev-parse HEAD`
69
- 5. build start time, e.g. `2026-10-27T18:30:00.000Z`
69
+ export const onRequestError = Hawk.onRequestError;
70
+ ```
70
71
 
71
- Whatever is picked, the source maps and your code get the same value: the plugin inlines it into the build as `process.env.HAWK_RELEASE`. You don't need to set this variable when the app runs, just pass it to the catchers, see [Error tracking](#error-tracking).
72
+ **4. `app/global-error.tsx`** catches errors of the root layout and shows a minimal error page:
72
73
 
73
- Production builds often run without git and CI variables, for example in a Docker build or a separate Kubernetes pod. Then the release is the build time. Source maps still work, but commits are not sent and the release name tells nothing about the deployed version. To get meaningful releases, pass the id from your pipeline with the `HAWK_RELEASE` environment variable:
74
+ ```tsx
75
+ 'use client';
74
76
 
75
- ```dockerfile
76
- ARG HAWK_RELEASE
77
- RUN yarn build
77
+ export { HawkGlobalError as default } from '@hawk.so/nextjs';
78
78
  ```
79
79
 
80
- ```shell
81
- docker build --build-arg HAWK_RELEASE=$(git rev-parse HEAD) .
82
- ```
80
+ Next.js only logs errors shown by error pages, so send them from your `error.tsx` files with `useHawkError`:
83
81
 
84
- Or set the `release` option, for example to the version from `package.json`, which package managers expose while running `yarn build` or `npm run build`:
82
+ ```tsx
83
+ 'use client';
85
84
 
86
- ```ts
87
- export default withHawkConfig(nextConfig, {
88
- release: process.env.npm_package_version,
89
- });
90
- ```
85
+ import { useHawkError } from '@hawk.so/nextjs';
91
86
 
92
- A version is unique only if you bump it for every deploy, otherwise errors of different builds get into the same release.
87
+ export default function ErrorPage({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
88
+ useHawkError(error);
93
89
 
94
- ## Error tracking
90
+ return <button type="button" onClick={reset}>Try again</button>;
91
+ }
92
+ ```
95
93
 
96
- To catch errors, use the Hawk catchers with the release id from the plugin:
94
+ See the [example](./example) app with all of them.
97
95
 
98
- - [@hawk.so/browser](https://github.com/codex-team/hawk.javascript/tree/master/packages/browser) in the browser. Hawk applies the sent source maps to these errors.
99
- - [@hawk.so/nodejs](https://github.com/codex-team/hawk.nodejs) on the server. Hawk doesn't apply source maps to server errors, so their stack traces point to the compiled files in `.next/server`.
96
+ ## What is caught
100
97
 
101
- ```shell
102
- yarn add @hawk.so/browser @hawk.so/nodejs
103
- ```
98
+ | Error | Caught by |
99
+ | -- | -- |
100
+ | Browser: event handlers, effects, timers, unhandled rejections | `init()` in `instrumentation-client.ts` |
101
+ | Errors shown by `error.tsx` | `useHawkError(error)` |
102
+ | Errors of the root layout | `HawkGlobalError` in `global-error.tsx` |
103
+ | Errors of a component tree | `<HawkErrorBoundary>` |
104
+ | Server components, route handlers, server actions, middleware | `onRequestError` in `instrumentation.ts` |
105
+ | Uncaught exceptions and unhandled rejections of the Node.js server | `init()` in `instrumentation.ts` |
106
+ | Errors you catch yourself | `Hawk.send(error)` |
104
107
 
105
- Both catchers below take the Integration Token from `NEXT_PUBLIC_HAWK_TOKEN`. The browser catcher has the token in the client code anyway, and Next.js inlines `NEXT_PUBLIC_` variables during the build like `HAWK_RELEASE`, so the running app needs no Hawk variables.
108
+ Next.js uses errors for `redirect()`, `notFound()`, `forbidden()` and dynamic rendering bailouts. They are part of the normal flow and are not sent.
106
109
 
107
- ### Browser
110
+ Server errors reach the error pages in the browser with a `digest` and a generic message in production. They are already sent from the server, so `useHawkError` and the boundaries skip them. The server event has the same digest in `addons.nextjs.digest`, so you can find it by the digest shown on the page.
108
111
 
109
- Create the catcher in `instrumentation-client.ts` in the project root (or in `src`). Next.js runs this file before the app starts, so the catcher gets errors from event handlers, effects and unhandled promise rejections.
112
+ ## API
110
113
 
111
- ```ts
112
- // instrumentation-client.ts
113
- import HawkCatcher from '@hawk.so/browser';
114
+ All functions are imported from `@hawk.so/nextjs` and work in the browser, on the server and in the Edge runtime: Next.js picks the build for each runtime.
114
115
 
115
- const token = process.env.NEXT_PUBLIC_HAWK_TOKEN;
116
+ | name | description |
117
+ | -- | -- |
118
+ | `init(options?)` | Creates the catcher. Call it in `instrumentation-client.ts` and in `register()` of `instrumentation.ts`. See [options](#init-options) |
119
+ | `send(error, context?, user?)` | Sends an error or a message. `user` is used on the server only, in the browser use `setUser()` |
120
+ | `setUser(user)`, `clearUser()` | Sets the user sent with every event, in the browser only |
121
+ | `setContext(context)` | Sets data sent with every event. On the server it is shared by all requests of the process |
122
+ | `breadcrumbs.add(breadcrumb)`, `.get()`, `.clear()` | Actions that happened before the error, in the browser only. Clicks, requests and navigations are added automatically |
123
+ | `flush(timeout?)` | Waits for the events being sent, 2 seconds at most by default. Resolves with `false` on timeout |
124
+ | `onRequestError` | Export it from `instrumentation.ts` to send errors of server components, route handlers, server actions and middleware |
125
+ | `onRouterTransitionStart` | Export it from `instrumentation-client.ts` to add a breadcrumb for every navigation |
126
+ | `useHawkError(error)` | Sends the error passed to `error.tsx` |
127
+ | `HawkGlobalError` | Ready `global-error.tsx` |
128
+ | `HawkErrorBoundary` | Error boundary that sends errors of its children. Props: `fallback` (element or `({ error, reset }) => element`) and `onError(error, info)` |
116
129
 
117
- if (token) {
118
- new HawkCatcher({
119
- token,
120
- release: process.env.HAWK_RELEASE,
121
- });
122
- }
130
+ ```tsx
131
+ <HawkErrorBoundary fallback={({ reset }) => <button onClick={reset}>Reload the widget</button>}>
132
+ <Widget />
133
+ </HawkErrorBoundary>
123
134
  ```
124
135
 
125
- Errors caught by your error boundaries (`error.tsx` and a custom `global-error.tsx`) are only logged to the console by Next.js. Report them with `reportError`, so the catcher gets them like uncaught errors:
136
+ ### Init options
126
137
 
127
- ```tsx
128
- // app/error.tsx
129
- 'use client';
138
+ | name | type | description |
139
+ | -- | -- | -- |
140
+ | `token` | string | Integration Token. Default is the `NEXT_PUBLIC_HAWK_TOKEN` environment variable, on the server `HAWK_TOKEN` is checked first. Without a token nothing is sent |
141
+ | `release` | string | Release id. Default is `process.env.HAWK_RELEASE` set by the plugin, see [Release id](#release-id) |
142
+ | `context` | object | Data sent with every event |
143
+ | `beforeSend` | function | Filters or modifies events. Return the event to send it or `false` to drop it |
144
+ | `disableGlobalErrorsHandling` | boolean | Disables catching of uncaught errors and unhandled rejections |
145
+ | `collectorEndpoint` | string | Collector endpoint for self-hosted Hawk: WebSocket URL in the browser, HTTP URL on the server |
146
+ | `user` | object | Current user, in the browser only |
147
+ | `breadcrumbs` | false/object | Breadcrumbs options of [@hawk.so/browser](https://github.com/codex-team/hawk.javascript/tree/master/packages/browser), in the browser only |
148
+ | `consoleTracking` | boolean | Sends recent console output with the events, in the browser only. Default is `true` |
149
+ | `issues` | false/object | Web Vitals and long tasks detection of [@hawk.so/browser](https://github.com/codex-team/hawk.javascript/tree/master/packages/browser), in the browser only |
130
150
 
131
- import { useEffect } from 'react';
151
+ Pass different options to the browser and the server by calling `init()` with them in each file.
132
152
 
133
- export default function ErrorPage({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
134
- useEffect(() => {
135
- // Server errors have a digest and are sent by the Node.js catcher
136
- if (error.digest === undefined) {
137
- reportError(error);
138
- }
139
- }, [error]);
153
+ ### On the server
140
154
 
141
- return <button type="button" onClick={reset}>Try again</button>;
142
- }
143
- ```
155
+ - The server handles requests of many users at once, so there is no current user: `setUser()` is ignored. Pass the user with the event instead: `Hawk.send(error, { orderId }, { id: user.id })`.
156
+ - `onRequestError` waits for sending and passes it to `waitUntil` where the platform supports it, e.g. on Vercel. If you call `send()` right before a serverless function returns, wait for it with `await Hawk.flush()`.
157
+ - Events have Next.js data in `addons.nextjs`: `runtime`, `routerKind`, `routePath`, `routeType`, `method`, `path`, `digest` and the `host`, `referer` and `user-agent` headers. Cookies, authorization and IP addresses are not sent.
158
+ - The Edge runtime has no process events, so only errors passed to `onRequestError` and `send()` are sent. `next start` passes errors of Edge routes to `onRequestError` of the Node.js runtime.
159
+ - Hawk doesn't apply source maps to server errors yet, so their stack traces point to the compiled files in `.next/server`.
144
160
 
145
- ### Server
161
+ ## Plugin options
146
162
 
147
- Initialize the catcher in `instrumentation.ts` and send request errors from the `onRequestError` hook:
163
+ Pass them as the second argument of `withHawkConfig`:
148
164
 
149
165
  ```ts
150
- // instrumentation.ts
151
- import type { Instrumentation } from 'next';
166
+ export default withHawkConfig(nextConfig, {
167
+ commits: false,
168
+ });
169
+ ```
152
170
 
153
- export async function register() {
154
- const token = process.env.NEXT_PUBLIC_HAWK_TOKEN;
171
+ | name | type | required | description |
172
+ | -- | -- | -- | -- |
173
+ | `token` | string | optional | Your project's Integration Token. Default is the `HAWK_TOKEN` or `NEXT_PUBLIC_HAWK_TOKEN` environment variable. Without a token the release is not sent |
174
+ | `release` | string/number | optional | Unique identifier of the release. See [Release id](#release-id) |
175
+ | `removeSourceMaps` | boolean | optional | Shows if the plugin should remove client source maps after sending. Default is `true` |
176
+ | `commits` | boolean/object | optional | Sends recent commits with the release. Default is `true`. Pass `false` to disable or `{ repo, number }` to set the path to the git repository (default is the project directory) and the number of commits (default is `5`) |
155
177
 
156
- if (process.env.NEXT_RUNTIME === 'nodejs' && token) {
157
- const { default: HawkCatcher } = await import('@hawk.so/nodejs');
178
+ Config functions are supported as well: `withHawkConfig((phase, { defaultConfig }) => ({ ... }), options)`.
158
179
 
159
- HawkCatcher.init({
160
- token,
161
- release: process.env.HAWK_RELEASE,
162
- });
163
- }
164
- }
180
+ On `next build` the plugin:
181
+
182
+ 1. resolves the release id and exposes it to your code as `process.env.HAWK_RELEASE`
183
+ 2. enables client source maps (`productionBrowserSourceMaps`) unless you set it yourself
184
+ 3. after compilation sends recent commits and client source maps to Hawk
185
+ 4. removes client source maps and `sourceMappingURL` comments from `.next/static`, so the source code is not published
165
186
 
166
- export const onRequestError: Instrumentation.onRequestError = async (error, request, context) => {
167
- if (process.env.NEXT_RUNTIME === 'nodejs') {
168
- const { default: HawkCatcher } = await import('@hawk.so/nodejs');
187
+ `next dev` sends nothing.
169
188
 
170
- HawkCatcher.send(error as Error, {
171
- method: request.method,
172
- path: request.path,
173
- routePath: context.routePath,
174
- routeType: context.routeType,
175
- });
176
- }
177
- };
189
+ ### Release id
190
+
191
+ Release id links error events to the source maps and commits sent from the build. The plugin picks it once per build, in this order:
192
+
193
+ 1. `release` option
194
+ 2. `HAWK_RELEASE` environment variable
195
+ 3. commit SHA from the CI environment: GitHub Actions (`GITHUB_SHA`), GitLab CI (`CI_COMMIT_SHA`), Vercel (`VERCEL_GIT_COMMIT_SHA`), Netlify, Cloudflare Pages, CircleCI and [others](./src/config/release.ts)
196
+ 4. current commit: `git rev-parse HEAD`
197
+ 5. build start time, e.g. `2026-10-27T18:30:00.000Z`
198
+
199
+ Whatever is picked, the source maps and your code get the same value: the plugin inlines it into the build as `process.env.HAWK_RELEASE`, and `init()` takes it from there. You don't need to set this variable when the app runs.
200
+
201
+ Production builds often run without git and CI variables, for example in a Docker build or a separate Kubernetes pod. Then the release is the build time. Source maps still work, but commits are not sent and the release name tells nothing about the deployed version. To get meaningful releases, pass the id from your pipeline with the `HAWK_RELEASE` environment variable:
202
+
203
+ ```dockerfile
204
+ ARG HAWK_RELEASE
205
+ RUN yarn build
178
206
  ```
179
207
 
180
- - `register` runs once on server start. The catcher also handles `uncaughtException` and `unhandledRejection`.
181
- - Next.js handles errors of server components, route handlers and server actions itself, so they reach the catcher only through `onRequestError`.
182
- - `@hawk.so/nodejs` works only in the Node.js runtime. It is imported dynamically to keep it out of Edge bundles, so errors from the Edge runtime are not sent.
208
+ ```shell
209
+ docker build --build-arg HAWK_RELEASE=$(git rev-parse HEAD) .
210
+ ```
211
+
212
+ Or set the `release` option, for example to the version from `package.json`, which package managers expose while running `yarn build` or `npm run build`:
213
+
214
+ ```ts
215
+ export default withHawkConfig(nextConfig, {
216
+ release: process.env.npm_package_version,
217
+ });
218
+ ```
183
219
 
184
- See the [example](./example) app with both catchers.
220
+ A version is unique only if you bump it for every deploy, otherwise errors of different builds get into the same release.
185
221
 
186
222
  ### TypeScript
187
223
 
188
- `process.env.HAWK_RELEASE` is typed automatically when you import the plugin in `next.config.ts`. If your config is a JavaScript file, add the declaration to `tsconfig.json`:
224
+ `process.env.HAWK_RELEASE` is typed automatically when you import the package. If your config is a JavaScript file and you don't import the package in TypeScript files, add the declaration to `tsconfig.json`:
189
225
 
190
226
  ```json
191
227
  {
@@ -195,9 +231,19 @@ See the [example](./example) app with both catchers.
195
231
  }
196
232
  ```
197
233
 
234
+ ## Migration from Hawk catchers
235
+
236
+ If you connected `@hawk.so/browser` and `@hawk.so/nodejs` yourself:
237
+
238
+ 1. Import `withHawkConfig` from `@hawk.so/nextjs/config` instead of `@hawk.so/nextjs`.
239
+ 2. Replace the catchers in `instrumentation-client.ts` and `instrumentation.ts` with the files from [Connect](#connect).
240
+ 3. Replace `reportError(error)` in `error.tsx` with `useHawkError(error)`.
241
+ 4. Remove `@hawk.so/browser` and `@hawk.so/nodejs` from your dependencies, and `HAWK_TOKEN` if it has the same value as `NEXT_PUBLIC_HAWK_TOKEN`.
242
+
198
243
  ## Requirements
199
244
 
200
245
  - Next.js 15.4.1 or newer: the plugin uses the `compiler.runAfterProductionCompile` build hook
246
+ - React 18.2 or newer
201
247
  - Node.js 20.9 or newer
202
248
 
203
249
  ## Notes
@@ -213,7 +259,7 @@ See the [example](./example) app with both catchers.
213
259
  yarn # install dependencies
214
260
  yarn lint # ESLint
215
261
  yarn test # unit tests
216
- yarn e2e # build the example app and check what is sent to a mock collector
262
+ yarn e2e # build and start the example app, check the release and the events received by a mock collector
217
263
  ```
218
264
 
219
- `yarn e2e --debug-ids` checks indexed source maps produced with `turbopack.debugIds`, `yarn e2e --webpack` checks the webpack build.
265
+ `yarn e2e` opens the example in Chromium, install it once with `yarn playwright install chromium`. `yarn e2e --debug-ids` checks indexed source maps produced with `turbopack.debugIds`, `yarn e2e --webpack` checks the webpack build.
package/dist/client.js ADDED
@@ -0,0 +1,163 @@
1
+ // src/client/index.ts
2
+ import HawkCatcher from "@hawk.so/browser";
3
+ import { log } from "@hawk.so/core";
4
+
5
+ // src/common/carrier.ts
6
+ var CARRIER_KEY = /* @__PURE__ */ Symbol.for("hawk.nextjs");
7
+ function getCarrier() {
8
+ const store = globalThis;
9
+ store[CARRIER_KEY] ??= {};
10
+ return store[CARRIER_KEY];
11
+ }
12
+
13
+ // src/common/control-flow.ts
14
+ var CONTROL_FLOW_DIGEST_PREFIXES = [
15
+ "NEXT_REDIRECT",
16
+ "NEXT_HTTP_ERROR_FALLBACK;",
17
+ "NEXT_NOT_FOUND",
18
+ "BAILOUT_TO_CLIENT_SIDE_RENDERING",
19
+ "DYNAMIC_SERVER_USAGE",
20
+ "NEXT_PRERENDER_INTERRUPTED",
21
+ "HANGING_PROMISE_REJECTION"
22
+ ];
23
+ var STATIC_GEN_BAILOUT_CODE = "NEXT_STATIC_GEN_BAILOUT";
24
+ var REACT_POSTPONE_TYPE = /* @__PURE__ */ Symbol.for("react.postpone");
25
+ var MAX_CAUSE_DEPTH = 5;
26
+ function isControlFlowDigest(value) {
27
+ return typeof value === "string" && CONTROL_FLOW_DIGEST_PREFIXES.some((prefix) => value.startsWith(prefix));
28
+ }
29
+ function isNextControlFlowError(error) {
30
+ let current = error;
31
+ for (let depth = 0; depth < MAX_CAUSE_DEPTH; depth++) {
32
+ if (typeof current !== "object" || current === null) {
33
+ return false;
34
+ }
35
+ const value = current;
36
+ if (isControlFlowDigest(value.digest) || isControlFlowDigest(value.message) || value.code === STATIC_GEN_BAILOUT_CODE || value.$$typeof === REACT_POSTPONE_TYPE) {
37
+ return true;
38
+ }
39
+ current = value.cause;
40
+ }
41
+ return false;
42
+ }
43
+ function isNextControlFlowTitle(title) {
44
+ return isControlFlowDigest(title);
45
+ }
46
+
47
+ // src/common/error.ts
48
+ function isErrorLike(value) {
49
+ return typeof value === "object" && value !== null && typeof value.message === "string" && typeof value.stack === "string";
50
+ }
51
+ function normalizeError(value) {
52
+ if (value instanceof Error || typeof value === "string") {
53
+ return value;
54
+ }
55
+ if (isErrorLike(value)) {
56
+ const error = new Error(value.message);
57
+ error.name = typeof value.name === "string" ? value.name : error.name;
58
+ error.stack = value.stack;
59
+ return error;
60
+ }
61
+ try {
62
+ return JSON.stringify(value) ?? String(value);
63
+ } catch (_error) {
64
+ return String(value);
65
+ }
66
+ }
67
+
68
+ // src/client/index.ts
69
+ import { HawkErrorBoundary, HawkGlobalError, useHawkError } from "./react.js";
70
+ var isUserWarningShown = false;
71
+ function getCatcher() {
72
+ return getCarrier().catcher;
73
+ }
74
+ function captureWithAddons(catcher, error, addons) {
75
+ const integrations = {
76
+ nextjs: {
77
+ runtime: "browser",
78
+ ...addons
79
+ }
80
+ };
81
+ catcher.captureError(normalizeError(error), integrations);
82
+ }
83
+ function init(options = {}) {
84
+ const carrier = getCarrier();
85
+ if (carrier.catcher !== void 0) {
86
+ return;
87
+ }
88
+ const { beforeSend, ...settings } = options;
89
+ const token = options.token ?? process.env.NEXT_PUBLIC_HAWK_TOKEN;
90
+ if (token === void 0 || token === "") {
91
+ log("Integration Token is not provided, errors will not be sent. Set NEXT_PUBLIC_HAWK_TOKEN or pass `token` to init()", "warn");
92
+ return;
93
+ }
94
+ const filter = (event) => {
95
+ if (isNextControlFlowTitle(event.title)) {
96
+ return false;
97
+ }
98
+ return beforeSend === void 0 ? event : beforeSend(event);
99
+ };
100
+ const catcher = new HawkCatcher({
101
+ ...settings,
102
+ token,
103
+ release: options.release ?? process.env.HAWK_RELEASE,
104
+ beforeSend: filter
105
+ });
106
+ carrier.catcher = catcher;
107
+ carrier.capture = (error, addons) => {
108
+ captureWithAddons(catcher, error, addons);
109
+ };
110
+ }
111
+ function send(error, context, user) {
112
+ if (user !== void 0 && !isUserWarningShown) {
113
+ isUserWarningShown = true;
114
+ log("The user passed to send() is ignored in the browser, use setUser() instead", "warn");
115
+ }
116
+ if (isNextControlFlowError(error)) {
117
+ return;
118
+ }
119
+ getCatcher()?.send(normalizeError(error), context);
120
+ }
121
+ function setUser(user) {
122
+ getCatcher()?.setUser(user);
123
+ }
124
+ function clearUser() {
125
+ getCatcher()?.clearUser();
126
+ }
127
+ function setContext(context) {
128
+ getCatcher()?.setContext(context);
129
+ }
130
+ var breadcrumbs = {
131
+ add: (breadcrumb, hint) => getCatcher()?.breadcrumbs.add(breadcrumb, hint),
132
+ get: () => getCatcher()?.breadcrumbs.get() ?? [],
133
+ clear: () => getCatcher()?.breadcrumbs.clear()
134
+ };
135
+ function flush(_timeout) {
136
+ return Promise.resolve(true);
137
+ }
138
+ function onRequestError(_error, _request, _context) {
139
+ return Promise.resolve();
140
+ }
141
+ function onRouterTransitionStart(url, navigationType) {
142
+ getCatcher()?.breadcrumbs.add({
143
+ type: "navigation",
144
+ category: "next.router",
145
+ message: url,
146
+ level: "info",
147
+ data: { navigationType }
148
+ });
149
+ }
150
+ export {
151
+ HawkErrorBoundary,
152
+ HawkGlobalError,
153
+ breadcrumbs,
154
+ clearUser,
155
+ flush,
156
+ init,
157
+ onRequestError,
158
+ onRouterTransitionStart,
159
+ send,
160
+ setContext,
161
+ setUser,
162
+ useHawkError
163
+ };