next-fetch-panel 0.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 +175 -0
- package/dist/index.d.mts +30 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +922 -0
- package/dist/index.mjs +922 -0
- package/dist/middleware.d.mts +35 -0
- package/dist/middleware.d.ts +35 -0
- package/dist/middleware.js +65 -0
- package/dist/middleware.mjs +38 -0
- package/dist/patch.d.mts +15 -0
- package/dist/patch.d.ts +15 -0
- package/dist/patch.js +244 -0
- package/dist/patch.mjs +215 -0
- package/dist/route.d.mts +23 -0
- package/dist/route.d.ts +23 -0
- package/dist/route.js +96 -0
- package/dist/route.mjs +69 -0
- package/package.json +72 -0
package/README.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# next-fetch-panel
|
|
2
|
+
|
|
3
|
+
A real-time DevTools panel that intercepts all server-side `fetch()` calls in a Next.js App Router application and streams them to a floating panel in the browser. Each browser session only sees its own requests, so it is safe to run in shared environments like staging.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
- Next.js 15 or 16 (App Router)
|
|
10
|
+
- React 19
|
|
11
|
+
- Node.js runtime (not Edge)
|
|
12
|
+
- Tailwind CSS v4
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install next-fetch-panel
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Wiring it up
|
|
25
|
+
|
|
26
|
+
There are four integration points. Each one has a **simple form** (one line, nothing else needed) and a **composable form** (for projects that already have logic in those files).
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
### 1. `instrumentation.ts` — fetch interception
|
|
31
|
+
|
|
32
|
+
**Simple** — nothing else in your `instrumentation.ts`:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
// instrumentation.ts
|
|
36
|
+
export { register } from "next-fetch-panel/patch";
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Composable** — you already have setup code:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
// instrumentation.ts
|
|
43
|
+
import { registerDevPanel } from "next-fetch-panel/patch";
|
|
44
|
+
import { registerOTel } from "@vercel/otel";
|
|
45
|
+
|
|
46
|
+
export async function register() {
|
|
47
|
+
registerOTel("my-app");
|
|
48
|
+
await registerDevPanel();
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`registerDevPanel` skips itself when `NEXT_RUNTIME !== "nodejs"` so it never runs in Edge contexts.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
### 2. `middleware.ts` — session isolation
|
|
57
|
+
|
|
58
|
+
**Simple** — no other middleware:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
// middleware.ts
|
|
62
|
+
export { middleware, config } from "next-fetch-panel/middleware";
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Composable** — you already have middleware:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
// middleware.ts
|
|
69
|
+
import { withDevPanel } from "next-fetch-panel/middleware";
|
|
70
|
+
|
|
71
|
+
export const { middleware, config } = withDevPanel(async (request) => {
|
|
72
|
+
if (!request.cookies.has("auth-token"))
|
|
73
|
+
return NextResponse.redirect(new URL("/login", request.url));
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`withDevPanel` always handles the `NextResponse.next()` call internally — you only need to return a response when you want to block or redirect.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
### 3. `app/api/dev-network/route.ts` — SSE stream
|
|
82
|
+
|
|
83
|
+
**Simple** — no access control needed:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
// app/api/dev-network/route.ts
|
|
87
|
+
export { dynamic, GET } from "next-fetch-panel/route";
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Composable** — restrict access (recommended for staging):
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
// app/api/dev-network/route.ts
|
|
94
|
+
import { createDevPanelRoute } from "next-fetch-panel/route";
|
|
95
|
+
|
|
96
|
+
export const dynamic = "force-dynamic";
|
|
97
|
+
export const GET = createDevPanelRoute({
|
|
98
|
+
guard: (request) => {
|
|
99
|
+
const token = request.headers.get("authorization");
|
|
100
|
+
if (!isValidAdminToken(token))
|
|
101
|
+
return new Response("Forbidden", { status: 403 });
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
### 4. `app/layout.tsx` — panel component
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
// app/layout.tsx
|
|
112
|
+
import { DevNetworkPanel } from "next-fetch-panel";
|
|
113
|
+
|
|
114
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
115
|
+
return (
|
|
116
|
+
<html>
|
|
117
|
+
<body>
|
|
118
|
+
{children}
|
|
119
|
+
<DevNetworkPanel />
|
|
120
|
+
</body>
|
|
121
|
+
</html>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Redacting secrets
|
|
129
|
+
|
|
130
|
+
Edit `redact.ts` configuration patterns. By default, the panel redacts:
|
|
131
|
+
|
|
132
|
+
- URL params: `api_key`, `apikey`, `secret`, `token`, `access_token`, `password`, `key`
|
|
133
|
+
- Headers: `authorization`, `x-api-key`, `x-secret`, `x-auth-token`
|
|
134
|
+
- JSON body keys: `password`, `secret`, `token`, `api_key`, `apiKey`, `accessToken`, `access_token`
|
|
135
|
+
|
|
136
|
+
The actual HTTP request is never modified — only the copy kept for display.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Using axios
|
|
141
|
+
|
|
142
|
+
Create your axios instance with `adapter: "fetch"` (requires axios ≥ 1.7):
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
import axios from "axios";
|
|
146
|
+
|
|
147
|
+
const http = axios.create({ adapter: "fetch" });
|
|
148
|
+
const { data } = await http.get("https://api.example.com/users");
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Security notes
|
|
154
|
+
|
|
155
|
+
| Concern | How it is handled |
|
|
156
|
+
|---|---|
|
|
157
|
+
| User A seeing User B's requests | Each browser session gets a unique `__dev_sid` cookie. The SSE stream filters strictly by session ID. |
|
|
158
|
+
| Panel accessible to anyone | Use the `guard` option on `createDevPanelRoute`. |
|
|
159
|
+
| Secrets leaking to the browser | Configured patterns are replaced with `[REDACTED]` before entries reach the store. |
|
|
160
|
+
| Running in production | Add a `NODE_ENV` gate in `registerDevPanel` or behind a feature flag if needed. |
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Public API
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
import {
|
|
168
|
+
DevNetworkPanel, // React component — place in your layout
|
|
169
|
+
withDevPanel, // Middleware composer
|
|
170
|
+
registerDevPanel, // Call inside your register() function
|
|
171
|
+
createDevPanelRoute, // Route handler factory with optional guard
|
|
172
|
+
} from "next-fetch-panel";
|
|
173
|
+
|
|
174
|
+
import type { LogEntry } from "next-fetch-panel";
|
|
175
|
+
```
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
|
|
3
|
+
declare function DevNetworkPanel(): react.JSX.Element;
|
|
4
|
+
|
|
5
|
+
type LogEntry = {
|
|
6
|
+
id: string;
|
|
7
|
+
url: string;
|
|
8
|
+
method: string;
|
|
9
|
+
status: number | null;
|
|
10
|
+
statusText: string | null;
|
|
11
|
+
duration: number;
|
|
12
|
+
ts: number;
|
|
13
|
+
error?: string;
|
|
14
|
+
requestHeaders: Record<string, string>;
|
|
15
|
+
responseHeaders: Record<string, string>;
|
|
16
|
+
requestBody: string | null;
|
|
17
|
+
responseBody: string | null;
|
|
18
|
+
sessionId: string | null;
|
|
19
|
+
};
|
|
20
|
+
type Subscriber = (entry: LogEntry) => void;
|
|
21
|
+
type DevLogStore = {
|
|
22
|
+
push(entry: LogEntry): void;
|
|
23
|
+
subscribe(fn: Subscriber): () => void;
|
|
24
|
+
getBuffer(): LogEntry[];
|
|
25
|
+
};
|
|
26
|
+
declare global {
|
|
27
|
+
var __devLogStore: DevLogStore | undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export { DevNetworkPanel, type LogEntry };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
|
|
3
|
+
declare function DevNetworkPanel(): react.JSX.Element;
|
|
4
|
+
|
|
5
|
+
type LogEntry = {
|
|
6
|
+
id: string;
|
|
7
|
+
url: string;
|
|
8
|
+
method: string;
|
|
9
|
+
status: number | null;
|
|
10
|
+
statusText: string | null;
|
|
11
|
+
duration: number;
|
|
12
|
+
ts: number;
|
|
13
|
+
error?: string;
|
|
14
|
+
requestHeaders: Record<string, string>;
|
|
15
|
+
responseHeaders: Record<string, string>;
|
|
16
|
+
requestBody: string | null;
|
|
17
|
+
responseBody: string | null;
|
|
18
|
+
sessionId: string | null;
|
|
19
|
+
};
|
|
20
|
+
type Subscriber = (entry: LogEntry) => void;
|
|
21
|
+
type DevLogStore = {
|
|
22
|
+
push(entry: LogEntry): void;
|
|
23
|
+
subscribe(fn: Subscriber): () => void;
|
|
24
|
+
getBuffer(): LogEntry[];
|
|
25
|
+
};
|
|
26
|
+
declare global {
|
|
27
|
+
var __devLogStore: DevLogStore | undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export { DevNetworkPanel, type LogEntry };
|