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 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
+ ```
@@ -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 };
@@ -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 };