@phuetz/code-buddy 0.1.0 → 0.1.1
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/.codebuddy/skills/bundled/brave-search/SKILL.md +490 -0
- package/.codebuddy/skills/bundled/exa-search/SKILL.md +1122 -0
- package/.codebuddy/skills/bundled/perplexity/SKILL.md +748 -0
- package/.codebuddy/skills/bundled/playwright/SKILL.md +520 -0
- package/.codebuddy/skills/bundled/puppeteer/SKILL.md +708 -0
- package/.codebuddy/skills/bundled/web-fetch/SKILL.md +1003 -0
- package/README.md +56 -0
- package/dist/agent/agent-state.d.ts +3 -3
- package/dist/agent/agent-state.js +6 -6
- package/dist/agent/agent-state.js.map +1 -1
- package/dist/agent/base-agent.d.ts +4 -4
- package/dist/agent/base-agent.js +22 -9
- package/dist/agent/base-agent.js.map +1 -1
- package/dist/agent/cache-trace.d.ts +56 -0
- package/dist/agent/cache-trace.js +98 -0
- package/dist/agent/cache-trace.js.map +1 -0
- package/dist/agent/codebuddy-agent.js +3 -2
- package/dist/agent/codebuddy-agent.js.map +1 -1
- package/dist/agent/execution/agent-executor.d.ts +4 -4
- package/dist/agent/execution/agent-executor.js +41 -7
- package/dist/agent/execution/agent-executor.js.map +1 -1
- package/dist/agent/facades/agent-context-facade.js +1 -3
- package/dist/agent/facades/agent-context-facade.js.map +1 -1
- package/dist/agent/facades/message-history-manager.js +14 -12
- package/dist/agent/facades/message-history-manager.js.map +1 -1
- package/dist/agent/facades/session-facade.d.ts +3 -3
- package/dist/agent/facades/session-facade.js +6 -6
- package/dist/agent/facades/session-facade.js.map +1 -1
- package/dist/agent/history-repair.d.ts +37 -0
- package/dist/agent/history-repair.js +124 -0
- package/dist/agent/history-repair.js.map +1 -0
- package/dist/agent/specialized/archive-agent.d.ts +3 -0
- package/dist/agent/specialized/archive-agent.js +71 -31
- package/dist/agent/specialized/archive-agent.js.map +1 -1
- package/dist/agent/specialized/security-review/agent.js +19 -8
- package/dist/agent/specialized/security-review/agent.js.map +1 -1
- package/dist/agent/tool-executor.js +5 -0
- package/dist/agent/tool-executor.js.map +1 -1
- package/dist/agent/turn-diff-tracker.d.ts +79 -0
- package/dist/agent/turn-diff-tracker.js +195 -0
- package/dist/agent/turn-diff-tracker.js.map +1 -0
- package/dist/checkpoints/checkpoint-versioning.js +78 -20
- package/dist/checkpoints/checkpoint-versioning.js.map +1 -1
- package/dist/cli/config-loader.js +2 -4
- package/dist/cli/config-loader.js.map +1 -1
- package/dist/commands/handlers/fcs-handlers.js +1 -1
- package/dist/commands/handlers/fcs-handlers.js.map +1 -1
- package/dist/commands/handlers/memory-handlers.js +2 -1
- package/dist/commands/handlers/memory-handlers.js.map +1 -1
- package/dist/commands/handlers/worktree-handlers.js +11 -0
- package/dist/commands/handlers/worktree-handlers.js.map +1 -1
- package/dist/commands/mcp.d.ts +1 -0
- package/dist/commands/mcp.js +66 -7
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/pipeline.js +25 -13
- package/dist/commands/pipeline.js.map +1 -1
- package/dist/config/model-tools.d.ts +41 -0
- package/dist/config/model-tools.js +194 -0
- package/dist/config/model-tools.js.map +1 -0
- package/dist/context/context-manager-v2.d.ts +2 -1
- package/dist/context/context-manager-v2.js +34 -5
- package/dist/context/context-manager-v2.js.map +1 -1
- package/dist/daemon/daemon-manager.js +23 -19
- package/dist/daemon/daemon-manager.js.map +1 -1
- package/dist/database/database-manager.d.ts +4 -0
- package/dist/database/database-manager.js +16 -7
- package/dist/database/database-manager.js.map +1 -1
- package/dist/desktop-automation/nutjs-provider.js +89 -0
- package/dist/desktop-automation/nutjs-provider.js.map +1 -1
- package/dist/fcs/builtins.d.ts +2 -6
- package/dist/fcs/builtins.js +2 -568
- package/dist/fcs/builtins.js.map +1 -1
- package/dist/fcs/codebuddy-bindings.d.ts +3 -43
- package/dist/fcs/codebuddy-bindings.js +2 -606
- package/dist/fcs/codebuddy-bindings.js.map +1 -1
- package/dist/fcs/index.d.ts +2 -27
- package/dist/fcs/index.js +2 -53
- package/dist/fcs/index.js.map +1 -1
- package/dist/fcs/lexer.d.ts +2 -37
- package/dist/fcs/lexer.js +2 -459
- package/dist/fcs/lexer.js.map +1 -1
- package/dist/fcs/parser.d.ts +2 -68
- package/dist/fcs/parser.js +2 -893
- package/dist/fcs/parser.js.map +1 -1
- package/dist/fcs/runtime.d.ts +2 -59
- package/dist/fcs/runtime.js +2 -623
- package/dist/fcs/runtime.js.map +1 -1
- package/dist/fcs/script-registry.d.ts +3 -69
- package/dist/fcs/script-registry.js +2 -219
- package/dist/fcs/script-registry.js.map +1 -1
- package/dist/fcs/sync-bindings.d.ts +3 -101
- package/dist/fcs/sync-bindings.js +2 -410
- package/dist/fcs/sync-bindings.js.map +1 -1
- package/dist/fcs/types.d.ts +2 -285
- package/dist/fcs/types.js +2 -103
- package/dist/fcs/types.js.map +1 -1
- package/dist/hooks/use-input-handler.d.ts +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/input/voice-control.js +11 -5
- package/dist/input/voice-control.js.map +1 -1
- package/dist/integrations/json-rpc/server.js +5 -5
- package/dist/integrations/json-rpc/server.js.map +1 -1
- package/dist/integrations/mcp/mcp-server.js +1 -1
- package/dist/integrations/mcp/mcp-server.js.map +1 -1
- package/dist/mcp/client.js +2 -1
- package/dist/mcp/client.js.map +1 -1
- package/dist/mcp/config.js +89 -5
- package/dist/mcp/config.js.map +1 -1
- package/dist/mcp/mcp-client.js +65 -14
- package/dist/mcp/mcp-client.js.map +1 -1
- package/dist/mcp/transports.d.ts +0 -1
- package/dist/mcp/transports.js +1 -5
- package/dist/mcp/transports.js.map +1 -1
- package/dist/mcp/types.d.ts +2 -0
- package/dist/persistence/session-lock.d.ts +42 -0
- package/dist/persistence/session-lock.js +165 -0
- package/dist/persistence/session-lock.js.map +1 -0
- package/dist/persistence/session-store.d.ts +18 -3
- package/dist/persistence/session-store.js +90 -21
- package/dist/persistence/session-store.js.map +1 -1
- package/dist/plugins/isolated-plugin-runner.d.ts +6 -0
- package/dist/plugins/isolated-plugin-runner.js +19 -1
- package/dist/plugins/isolated-plugin-runner.js.map +1 -1
- package/dist/providers/local-llm-provider.js +21 -4
- package/dist/providers/local-llm-provider.js.map +1 -1
- package/dist/sandbox/docker-sandbox.js +7 -4
- package/dist/sandbox/docker-sandbox.js.map +1 -1
- package/dist/scripting/builtins.d.ts +8 -3
- package/dist/scripting/builtins.js +506 -355
- package/dist/scripting/builtins.js.map +1 -1
- package/dist/scripting/codebuddy-bindings.d.ts +47 -0
- package/dist/scripting/codebuddy-bindings.js +487 -0
- package/dist/scripting/codebuddy-bindings.js.map +1 -0
- package/dist/scripting/index.d.ts +33 -30
- package/dist/scripting/index.js +41 -36
- package/dist/scripting/index.js.map +1 -1
- package/dist/scripting/lexer.d.ts +31 -13
- package/dist/scripting/lexer.js +379 -292
- package/dist/scripting/lexer.js.map +1 -1
- package/dist/scripting/parser.d.ts +63 -44
- package/dist/scripting/parser.js +700 -473
- package/dist/scripting/parser.js.map +1 -1
- package/dist/scripting/runtime.d.ts +55 -24
- package/dist/scripting/runtime.js +600 -288
- package/dist/scripting/runtime.js.map +1 -1
- package/dist/scripting/script-registry.d.ts +54 -0
- package/dist/scripting/script-registry.js +202 -0
- package/dist/scripting/script-registry.js.map +1 -0
- package/dist/scripting/sync-bindings.d.ts +105 -0
- package/dist/scripting/sync-bindings.js +353 -0
- package/dist/scripting/sync-bindings.js.map +1 -0
- package/dist/scripting/types.d.ts +297 -199
- package/dist/scripting/types.js +86 -60
- package/dist/scripting/types.js.map +1 -1
- package/dist/search/usearch-index.js +42 -7
- package/dist/search/usearch-index.js.map +1 -1
- package/dist/security/bash-parser.d.ts +51 -0
- package/dist/security/bash-parser.js +327 -0
- package/dist/security/bash-parser.js.map +1 -0
- package/dist/security/skill-scanner.d.ts +36 -0
- package/dist/security/skill-scanner.js +149 -0
- package/dist/security/skill-scanner.js.map +1 -0
- package/dist/security/trust-folders.d.ts +1 -0
- package/dist/security/trust-folders.js +19 -1
- package/dist/security/trust-folders.js.map +1 -1
- package/dist/server/websocket/handler.js +15 -5
- package/dist/server/websocket/handler.js.map +1 -1
- package/dist/skills/eligibility.js +26 -4
- package/dist/skills/eligibility.js.map +1 -1
- package/dist/tasks/background-tasks.js +5 -1
- package/dist/tasks/background-tasks.js.map +1 -1
- package/dist/tools/apply-patch.d.ts +55 -0
- package/dist/tools/apply-patch.js +273 -0
- package/dist/tools/apply-patch.js.map +1 -0
- package/dist/tools/registry/bash-tools.js +6 -3
- package/dist/tools/registry/bash-tools.js.map +1 -1
- package/dist/tools/registry/misc-tools.js +1 -2
- package/dist/tools/registry/misc-tools.js.map +1 -1
- package/dist/tools/registry/search-tools.js +1 -1
- package/dist/tools/registry/search-tools.js.map +1 -1
- package/dist/tools/registry/text-editor-tools.js +1 -1
- package/dist/tools/registry/text-editor-tools.js.map +1 -1
- package/dist/tools/registry/todo-tools.js +37 -5
- package/dist/tools/registry/todo-tools.js.map +1 -1
- package/dist/tools/registry/tool-registry.js +5 -4
- package/dist/tools/registry/tool-registry.js.map +1 -1
- package/dist/tools/registry/web-tools.d.ts +1 -1
- package/dist/tools/registry/web-tools.js +28 -8
- package/dist/tools/registry/web-tools.js.map +1 -1
- package/dist/tools/text-editor.d.ts +1 -1
- package/dist/tools/text-editor.js +23 -5
- package/dist/tools/text-editor.js.map +1 -1
- package/dist/tools/web-search.d.ts +52 -37
- package/dist/tools/web-search.js +368 -163
- package/dist/tools/web-search.js.map +1 -1
- package/dist/ui/components/ChatInterface.d.ts +1 -1
- package/dist/utils/head-tail-truncation.d.ts +34 -0
- package/dist/utils/head-tail-truncation.js +98 -0
- package/dist/utils/head-tail-truncation.js.map +1 -0
- package/dist/utils/sanitize.d.ts +5 -0
- package/dist/utils/sanitize.js +19 -0
- package/dist/utils/sanitize.js.map +1 -1
- package/dist/utils/settings-manager.js +4 -4
- package/dist/utils/settings-manager.js.map +1 -1
- package/package.json +3 -1
|
@@ -0,0 +1,1003 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: web-fetch
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
description: HTTP requests, REST API testing, fetch API, axios, curl, webhook testing, and API integration
|
|
5
|
+
author: Code Buddy
|
|
6
|
+
tags: http, rest, api, fetch, axios, curl, webhooks, testing
|
|
7
|
+
env:
|
|
8
|
+
HTTP_PROXY: ""
|
|
9
|
+
HTTPS_PROXY: ""
|
|
10
|
+
NO_PROXY: ""
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Web Fetch & HTTP Client
|
|
14
|
+
|
|
15
|
+
Web Fetch provides powerful tools for making HTTP requests, testing REST APIs, consuming webhooks, and integrating with external services. Includes native fetch API, axios library, curl commands, and specialized testing utilities.
|
|
16
|
+
|
|
17
|
+
## Direct Control (CLI / API / Scripting)
|
|
18
|
+
|
|
19
|
+
### Native Fetch API (Node.js 18+)
|
|
20
|
+
|
|
21
|
+
```javascript
|
|
22
|
+
// Basic GET request
|
|
23
|
+
async function fetchData(url) {
|
|
24
|
+
try {
|
|
25
|
+
const response = await fetch(url);
|
|
26
|
+
|
|
27
|
+
if (!response.ok) {
|
|
28
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const data = await response.json();
|
|
32
|
+
return data;
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error('Fetch error:', error.message);
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// POST request with JSON body
|
|
40
|
+
async function createResource(url, data) {
|
|
41
|
+
const response = await fetch(url, {
|
|
42
|
+
method: 'POST',
|
|
43
|
+
headers: {
|
|
44
|
+
'Content-Type': 'application/json',
|
|
45
|
+
'Authorization': `Bearer ${process.env.API_TOKEN}`
|
|
46
|
+
},
|
|
47
|
+
body: JSON.stringify(data)
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (!response.ok) {
|
|
51
|
+
const error = await response.text();
|
|
52
|
+
throw new Error(`Failed to create resource: ${error}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return await response.json();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Multipart form data (file upload)
|
|
59
|
+
async function uploadFile(url, filePath) {
|
|
60
|
+
const fs = require('fs');
|
|
61
|
+
const FormData = require('form-data');
|
|
62
|
+
|
|
63
|
+
const form = new FormData();
|
|
64
|
+
form.append('file', fs.createReadStream(filePath));
|
|
65
|
+
form.append('metadata', JSON.stringify({ name: 'test.pdf' }));
|
|
66
|
+
|
|
67
|
+
const response = await fetch(url, {
|
|
68
|
+
method: 'POST',
|
|
69
|
+
headers: form.getHeaders(),
|
|
70
|
+
body: form
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return await response.json();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Request with timeout
|
|
77
|
+
async function fetchWithTimeout(url, timeout = 5000) {
|
|
78
|
+
const controller = new AbortController();
|
|
79
|
+
const id = setTimeout(() => controller.abort(), timeout);
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const response = await fetch(url, {
|
|
83
|
+
signal: controller.signal
|
|
84
|
+
});
|
|
85
|
+
clearTimeout(id);
|
|
86
|
+
return await response.json();
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if (error.name === 'AbortError') {
|
|
89
|
+
throw new Error('Request timeout');
|
|
90
|
+
}
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Streaming response
|
|
96
|
+
async function streamResponse(url) {
|
|
97
|
+
const response = await fetch(url);
|
|
98
|
+
const reader = response.body.getReader();
|
|
99
|
+
const decoder = new TextDecoder();
|
|
100
|
+
|
|
101
|
+
while (true) {
|
|
102
|
+
const { done, value } = await reader.read();
|
|
103
|
+
if (done) break;
|
|
104
|
+
|
|
105
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
106
|
+
process.stdout.write(chunk);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Retry logic with exponential backoff
|
|
111
|
+
async function fetchWithRetry(url, maxRetries = 3) {
|
|
112
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
113
|
+
try {
|
|
114
|
+
const response = await fetch(url);
|
|
115
|
+
if (response.ok) return await response.json();
|
|
116
|
+
|
|
117
|
+
// Retry on 5xx errors
|
|
118
|
+
if (response.status >= 500 && i < maxRetries - 1) {
|
|
119
|
+
const delay = Math.pow(2, i) * 1000;
|
|
120
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
125
|
+
} catch (error) {
|
|
126
|
+
if (i === maxRetries - 1) throw error;
|
|
127
|
+
await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Axios Library
|
|
134
|
+
|
|
135
|
+
```javascript
|
|
136
|
+
const axios = require('axios');
|
|
137
|
+
|
|
138
|
+
// Basic requests
|
|
139
|
+
async function axiosExamples() {
|
|
140
|
+
// GET request
|
|
141
|
+
const response = await axios.get('https://api.example.com/users');
|
|
142
|
+
console.log(response.data);
|
|
143
|
+
|
|
144
|
+
// POST with data
|
|
145
|
+
const created = await axios.post('https://api.example.com/users', {
|
|
146
|
+
name: 'John Doe',
|
|
147
|
+
email: 'john@example.com'
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// PUT update
|
|
151
|
+
await axios.put('https://api.example.com/users/123', {
|
|
152
|
+
name: 'Jane Doe'
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// DELETE
|
|
156
|
+
await axios.delete('https://api.example.com/users/123');
|
|
157
|
+
|
|
158
|
+
// PATCH partial update
|
|
159
|
+
await axios.patch('https://api.example.com/users/123', {
|
|
160
|
+
email: 'newemail@example.com'
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Configure axios instance
|
|
165
|
+
const api = axios.create({
|
|
166
|
+
baseURL: 'https://api.example.com',
|
|
167
|
+
timeout: 10000,
|
|
168
|
+
headers: {
|
|
169
|
+
'Content-Type': 'application/json',
|
|
170
|
+
'Authorization': `Bearer ${process.env.API_TOKEN}`
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Request interceptor (add auth token)
|
|
175
|
+
api.interceptors.request.use(
|
|
176
|
+
config => {
|
|
177
|
+
const token = getAccessToken();
|
|
178
|
+
if (token) {
|
|
179
|
+
config.headers.Authorization = `Bearer ${token}`;
|
|
180
|
+
}
|
|
181
|
+
return config;
|
|
182
|
+
},
|
|
183
|
+
error => Promise.reject(error)
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
// Response interceptor (refresh token on 401)
|
|
187
|
+
api.interceptors.response.use(
|
|
188
|
+
response => response,
|
|
189
|
+
async error => {
|
|
190
|
+
const originalRequest = error.config;
|
|
191
|
+
|
|
192
|
+
if (error.response?.status === 401 && !originalRequest._retry) {
|
|
193
|
+
originalRequest._retry = true;
|
|
194
|
+
const newToken = await refreshAccessToken();
|
|
195
|
+
originalRequest.headers.Authorization = `Bearer ${newToken}`;
|
|
196
|
+
return api(originalRequest);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return Promise.reject(error);
|
|
200
|
+
}
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
// File upload with progress
|
|
204
|
+
async function uploadWithProgress(url, filePath) {
|
|
205
|
+
const fs = require('fs');
|
|
206
|
+
const FormData = require('form-data');
|
|
207
|
+
|
|
208
|
+
const form = new FormData();
|
|
209
|
+
form.append('file', fs.createReadStream(filePath));
|
|
210
|
+
|
|
211
|
+
const response = await axios.post(url, form, {
|
|
212
|
+
headers: form.getHeaders(),
|
|
213
|
+
onUploadProgress: progressEvent => {
|
|
214
|
+
const percentCompleted = Math.round(
|
|
215
|
+
(progressEvent.loaded * 100) / progressEvent.total
|
|
216
|
+
);
|
|
217
|
+
console.log(`Upload progress: ${percentCompleted}%`);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
return response.data;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Concurrent requests
|
|
225
|
+
async function parallelRequests(urls) {
|
|
226
|
+
try {
|
|
227
|
+
const responses = await Promise.all(
|
|
228
|
+
urls.map(url => axios.get(url))
|
|
229
|
+
);
|
|
230
|
+
return responses.map(r => r.data);
|
|
231
|
+
} catch (error) {
|
|
232
|
+
console.error('One or more requests failed:', error.message);
|
|
233
|
+
throw error;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Rate-limited requests
|
|
238
|
+
class RateLimiter {
|
|
239
|
+
constructor(maxRequests, timeWindow) {
|
|
240
|
+
this.maxRequests = maxRequests;
|
|
241
|
+
this.timeWindow = timeWindow;
|
|
242
|
+
this.queue = [];
|
|
243
|
+
this.pending = 0;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async request(fn) {
|
|
247
|
+
while (this.pending >= this.maxRequests) {
|
|
248
|
+
await new Promise(resolve => this.queue.push(resolve));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
this.pending++;
|
|
252
|
+
setTimeout(() => {
|
|
253
|
+
this.pending--;
|
|
254
|
+
const resolve = this.queue.shift();
|
|
255
|
+
if (resolve) resolve();
|
|
256
|
+
}, this.timeWindow);
|
|
257
|
+
|
|
258
|
+
return await fn();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const limiter = new RateLimiter(5, 1000); // 5 requests per second
|
|
263
|
+
|
|
264
|
+
async function fetchRateLimited(url) {
|
|
265
|
+
return await limiter.request(() => axios.get(url));
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### cURL Commands
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
# Basic GET request
|
|
273
|
+
curl https://api.example.com/users
|
|
274
|
+
|
|
275
|
+
# GET with headers
|
|
276
|
+
curl -H "Authorization: Bearer TOKEN" \
|
|
277
|
+
-H "Content-Type: application/json" \
|
|
278
|
+
https://api.example.com/users
|
|
279
|
+
|
|
280
|
+
# POST with JSON data
|
|
281
|
+
curl -X POST https://api.example.com/users \
|
|
282
|
+
-H "Content-Type: application/json" \
|
|
283
|
+
-d '{"name":"John Doe","email":"john@example.com"}'
|
|
284
|
+
|
|
285
|
+
# POST from file
|
|
286
|
+
curl -X POST https://api.example.com/users \
|
|
287
|
+
-H "Content-Type: application/json" \
|
|
288
|
+
-d @data.json
|
|
289
|
+
|
|
290
|
+
# PUT request
|
|
291
|
+
curl -X PUT https://api.example.com/users/123 \
|
|
292
|
+
-H "Content-Type: application/json" \
|
|
293
|
+
-d '{"name":"Jane Doe"}'
|
|
294
|
+
|
|
295
|
+
# DELETE request
|
|
296
|
+
curl -X DELETE https://api.example.com/users/123 \
|
|
297
|
+
-H "Authorization: Bearer TOKEN"
|
|
298
|
+
|
|
299
|
+
# File upload
|
|
300
|
+
curl -X POST https://api.example.com/upload \
|
|
301
|
+
-F "file=@document.pdf" \
|
|
302
|
+
-F "metadata=@meta.json;type=application/json"
|
|
303
|
+
|
|
304
|
+
# Follow redirects
|
|
305
|
+
curl -L https://example.com/redirect
|
|
306
|
+
|
|
307
|
+
# Save response to file
|
|
308
|
+
curl -o output.json https://api.example.com/data
|
|
309
|
+
|
|
310
|
+
# Include response headers
|
|
311
|
+
curl -i https://api.example.com/users
|
|
312
|
+
|
|
313
|
+
# Verbose output (debugging)
|
|
314
|
+
curl -v https://api.example.com/users
|
|
315
|
+
|
|
316
|
+
# Send cookies
|
|
317
|
+
curl -b "session=abc123" https://api.example.com/profile
|
|
318
|
+
|
|
319
|
+
# Save cookies
|
|
320
|
+
curl -c cookies.txt https://api.example.com/login
|
|
321
|
+
|
|
322
|
+
# Use saved cookies
|
|
323
|
+
curl -b cookies.txt https://api.example.com/profile
|
|
324
|
+
|
|
325
|
+
# Basic authentication
|
|
326
|
+
curl -u username:password https://api.example.com/secure
|
|
327
|
+
|
|
328
|
+
# Custom request method
|
|
329
|
+
curl -X PATCH https://api.example.com/users/123 \
|
|
330
|
+
-d '{"email":"new@example.com"}'
|
|
331
|
+
|
|
332
|
+
# Timeout settings
|
|
333
|
+
curl --connect-timeout 5 --max-time 10 https://api.example.com
|
|
334
|
+
|
|
335
|
+
# Proxy
|
|
336
|
+
curl -x http://proxy:8080 https://api.example.com
|
|
337
|
+
|
|
338
|
+
# Ignore SSL certificate
|
|
339
|
+
curl -k https://self-signed.example.com
|
|
340
|
+
|
|
341
|
+
# Multiple requests
|
|
342
|
+
curl -Z https://api.example.com/endpoint1 https://api.example.com/endpoint2
|
|
343
|
+
|
|
344
|
+
# Rate limit (1 request per second)
|
|
345
|
+
for url in $(cat urls.txt); do
|
|
346
|
+
curl "$url"
|
|
347
|
+
sleep 1
|
|
348
|
+
done
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### REST API Testing
|
|
352
|
+
|
|
353
|
+
```javascript
|
|
354
|
+
const assert = require('assert');
|
|
355
|
+
|
|
356
|
+
class APITester {
|
|
357
|
+
constructor(baseURL) {
|
|
358
|
+
this.baseURL = baseURL;
|
|
359
|
+
this.results = [];
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
async test(name, testFn) {
|
|
363
|
+
try {
|
|
364
|
+
await testFn();
|
|
365
|
+
this.results.push({ name, status: 'PASS' });
|
|
366
|
+
console.log(`✓ ${name}`);
|
|
367
|
+
} catch (error) {
|
|
368
|
+
this.results.push({ name, status: 'FAIL', error: error.message });
|
|
369
|
+
console.error(`✗ ${name}: ${error.message}`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
async get(path, expectedStatus = 200) {
|
|
374
|
+
const response = await fetch(`${this.baseURL}${path}`);
|
|
375
|
+
assert.strictEqual(response.status, expectedStatus,
|
|
376
|
+
`Expected status ${expectedStatus}, got ${response.status}`);
|
|
377
|
+
return response;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async post(path, data, expectedStatus = 201) {
|
|
381
|
+
const response = await fetch(`${this.baseURL}${path}`, {
|
|
382
|
+
method: 'POST',
|
|
383
|
+
headers: { 'Content-Type': 'application/json' },
|
|
384
|
+
body: JSON.stringify(data)
|
|
385
|
+
});
|
|
386
|
+
assert.strictEqual(response.status, expectedStatus);
|
|
387
|
+
return response;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
assertSchema(data, schema) {
|
|
391
|
+
for (const [key, type] of Object.entries(schema)) {
|
|
392
|
+
assert(key in data, `Missing key: ${key}`);
|
|
393
|
+
assert.strictEqual(typeof data[key], type,
|
|
394
|
+
`Expected ${key} to be ${type}, got ${typeof data[key]}`);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
async report() {
|
|
399
|
+
const passed = this.results.filter(r => r.status === 'PASS').length;
|
|
400
|
+
const failed = this.results.filter(r => r.status === 'FAIL').length;
|
|
401
|
+
console.log(`\nResults: ${passed} passed, ${failed} failed`);
|
|
402
|
+
return { passed, failed, results: this.results };
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Usage
|
|
407
|
+
async function runTests() {
|
|
408
|
+
const tester = new APITester('https://api.example.com');
|
|
409
|
+
|
|
410
|
+
await tester.test('GET /users returns 200', async () => {
|
|
411
|
+
const response = await tester.get('/users');
|
|
412
|
+
const data = await response.json();
|
|
413
|
+
assert(Array.isArray(data), 'Response should be an array');
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
await tester.test('POST /users creates user', async () => {
|
|
417
|
+
const userData = { name: 'Test User', email: 'test@example.com' };
|
|
418
|
+
const response = await tester.post('/users', userData);
|
|
419
|
+
const created = await response.json();
|
|
420
|
+
tester.assertSchema(created, { id: 'number', name: 'string', email: 'string' });
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
await tester.test('GET /users/:id returns user', async () => {
|
|
424
|
+
const response = await tester.get('/users/1');
|
|
425
|
+
const user = await response.json();
|
|
426
|
+
assert(user.id === 1, 'User ID should match');
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
await tester.report();
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Webhook Testing
|
|
434
|
+
|
|
435
|
+
```javascript
|
|
436
|
+
const express = require('express');
|
|
437
|
+
const crypto = require('crypto');
|
|
438
|
+
|
|
439
|
+
// Webhook receiver server
|
|
440
|
+
function createWebhookServer(port = 3000) {
|
|
441
|
+
const app = express();
|
|
442
|
+
app.use(express.json());
|
|
443
|
+
|
|
444
|
+
const receivedWebhooks = [];
|
|
445
|
+
|
|
446
|
+
// Verify HMAC signature
|
|
447
|
+
function verifySignature(payload, signature, secret) {
|
|
448
|
+
const hmac = crypto.createHmac('sha256', secret);
|
|
449
|
+
const digest = hmac.update(payload).digest('hex');
|
|
450
|
+
return crypto.timingSafeEqual(
|
|
451
|
+
Buffer.from(signature),
|
|
452
|
+
Buffer.from(digest)
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
app.post('/webhook', (req, res) => {
|
|
457
|
+
const signature = req.headers['x-hub-signature-256'];
|
|
458
|
+
const payload = JSON.stringify(req.body);
|
|
459
|
+
|
|
460
|
+
// Verify signature if present
|
|
461
|
+
if (signature) {
|
|
462
|
+
const secret = process.env.WEBHOOK_SECRET;
|
|
463
|
+
if (!verifySignature(payload, signature.replace('sha256=', ''), secret)) {
|
|
464
|
+
return res.status(401).json({ error: 'Invalid signature' });
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Store webhook
|
|
469
|
+
receivedWebhooks.push({
|
|
470
|
+
timestamp: new Date(),
|
|
471
|
+
headers: req.headers,
|
|
472
|
+
body: req.body
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
console.log('Webhook received:', req.body);
|
|
476
|
+
res.status(200).json({ received: true });
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
app.get('/webhooks', (req, res) => {
|
|
480
|
+
res.json(receivedWebhooks);
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
return app.listen(port, () => {
|
|
484
|
+
console.log(`Webhook server listening on port ${port}`);
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Webhook sender/tester
|
|
489
|
+
async function sendWebhook(url, data, secret) {
|
|
490
|
+
const payload = JSON.stringify(data);
|
|
491
|
+
const hmac = crypto.createHmac('sha256', secret);
|
|
492
|
+
const signature = hmac.update(payload).digest('hex');
|
|
493
|
+
|
|
494
|
+
const response = await fetch(url, {
|
|
495
|
+
method: 'POST',
|
|
496
|
+
headers: {
|
|
497
|
+
'Content-Type': 'application/json',
|
|
498
|
+
'X-Hub-Signature-256': `sha256=${signature}`,
|
|
499
|
+
'X-Webhook-Event': data.event
|
|
500
|
+
},
|
|
501
|
+
body: payload
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
return {
|
|
505
|
+
status: response.status,
|
|
506
|
+
body: await response.json()
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Test webhook delivery
|
|
511
|
+
async function testWebhookDelivery(webhookUrl, testData) {
|
|
512
|
+
const results = [];
|
|
513
|
+
|
|
514
|
+
for (const test of testData) {
|
|
515
|
+
try {
|
|
516
|
+
const result = await sendWebhook(webhookUrl, test.payload, test.secret);
|
|
517
|
+
results.push({
|
|
518
|
+
test: test.name,
|
|
519
|
+
success: result.status === 200,
|
|
520
|
+
status: result.status,
|
|
521
|
+
response: result.body
|
|
522
|
+
});
|
|
523
|
+
} catch (error) {
|
|
524
|
+
results.push({
|
|
525
|
+
test: test.name,
|
|
526
|
+
success: false,
|
|
527
|
+
error: error.message
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return results;
|
|
533
|
+
}
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
### GraphQL Queries
|
|
537
|
+
|
|
538
|
+
```javascript
|
|
539
|
+
async function graphqlQuery(endpoint, query, variables = {}) {
|
|
540
|
+
const response = await fetch(endpoint, {
|
|
541
|
+
method: 'POST',
|
|
542
|
+
headers: {
|
|
543
|
+
'Content-Type': 'application/json',
|
|
544
|
+
'Authorization': `Bearer ${process.env.GRAPHQL_TOKEN}`
|
|
545
|
+
},
|
|
546
|
+
body: JSON.stringify({ query, variables })
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
const result = await response.json();
|
|
550
|
+
|
|
551
|
+
if (result.errors) {
|
|
552
|
+
throw new Error(`GraphQL errors: ${JSON.stringify(result.errors)}`);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
return result.data;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Example queries
|
|
559
|
+
const GET_USER = `
|
|
560
|
+
query GetUser($id: ID!) {
|
|
561
|
+
user(id: $id) {
|
|
562
|
+
id
|
|
563
|
+
name
|
|
564
|
+
email
|
|
565
|
+
posts {
|
|
566
|
+
id
|
|
567
|
+
title
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
`;
|
|
572
|
+
|
|
573
|
+
const CREATE_POST = `
|
|
574
|
+
mutation CreatePost($input: PostInput!) {
|
|
575
|
+
createPost(input: $input) {
|
|
576
|
+
id
|
|
577
|
+
title
|
|
578
|
+
content
|
|
579
|
+
author {
|
|
580
|
+
name
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
`;
|
|
585
|
+
|
|
586
|
+
// Usage
|
|
587
|
+
const user = await graphqlQuery('https://api.example.com/graphql', GET_USER, {
|
|
588
|
+
id: '123'
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
const post = await graphqlQuery('https://api.example.com/graphql', CREATE_POST, {
|
|
592
|
+
input: {
|
|
593
|
+
title: 'New Post',
|
|
594
|
+
content: 'Content here',
|
|
595
|
+
authorId: '123'
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
## MCP Server Integration
|
|
601
|
+
|
|
602
|
+
Add to `.codebuddy/mcp.json`:
|
|
603
|
+
|
|
604
|
+
```json
|
|
605
|
+
{
|
|
606
|
+
"mcpServers": {
|
|
607
|
+
"fetch": {
|
|
608
|
+
"command": "npx",
|
|
609
|
+
"args": [
|
|
610
|
+
"-y",
|
|
611
|
+
"@modelcontextprotocol/server-fetch"
|
|
612
|
+
]
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
Or with custom configuration:
|
|
619
|
+
|
|
620
|
+
```json
|
|
621
|
+
{
|
|
622
|
+
"mcpServers": {
|
|
623
|
+
"fetch": {
|
|
624
|
+
"command": "node",
|
|
625
|
+
"args": [
|
|
626
|
+
"/path/to/fetch-mcp-server/index.js"
|
|
627
|
+
],
|
|
628
|
+
"env": {
|
|
629
|
+
"HTTP_TIMEOUT": "30000",
|
|
630
|
+
"MAX_REDIRECTS": "5",
|
|
631
|
+
"USER_AGENT": "Code-Buddy-MCP/1.0"
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
### Available MCP Tools
|
|
639
|
+
|
|
640
|
+
**From @modelcontextprotocol/server-fetch:**
|
|
641
|
+
- `fetch` - Make HTTP request with full control
|
|
642
|
+
- `get` - Simple GET request
|
|
643
|
+
- `post` - POST request with JSON body
|
|
644
|
+
- `put` - PUT request for updates
|
|
645
|
+
- `delete` - DELETE request
|
|
646
|
+
- `head` - HEAD request (headers only)
|
|
647
|
+
- `options` - OPTIONS request (CORS preflight)
|
|
648
|
+
|
|
649
|
+
**Tool Parameters:**
|
|
650
|
+
```javascript
|
|
651
|
+
{
|
|
652
|
+
"url": "https://api.example.com/endpoint",
|
|
653
|
+
"method": "GET|POST|PUT|DELETE|PATCH",
|
|
654
|
+
"headers": {
|
|
655
|
+
"Authorization": "Bearer TOKEN",
|
|
656
|
+
"Content-Type": "application/json"
|
|
657
|
+
},
|
|
658
|
+
"body": "string or JSON",
|
|
659
|
+
"timeout": 30000,
|
|
660
|
+
"follow_redirects": true,
|
|
661
|
+
"max_redirects": 5
|
|
662
|
+
}
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
## Common Workflows
|
|
666
|
+
|
|
667
|
+
### 1. API Integration: Complete CRUD Operations
|
|
668
|
+
|
|
669
|
+
```javascript
|
|
670
|
+
// Step 1: Setup API client with authentication
|
|
671
|
+
const API_BASE = 'https://api.example.com';
|
|
672
|
+
const API_KEY = process.env.API_KEY;
|
|
673
|
+
|
|
674
|
+
const headers = {
|
|
675
|
+
'Content-Type': 'application/json',
|
|
676
|
+
'Authorization': `Bearer ${API_KEY}`
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
// Step 2: Create new resource
|
|
680
|
+
async function createUser(userData) {
|
|
681
|
+
const response = await fetch(`${API_BASE}/users`, {
|
|
682
|
+
method: 'POST',
|
|
683
|
+
headers,
|
|
684
|
+
body: JSON.stringify(userData)
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
if (!response.ok) {
|
|
688
|
+
throw new Error(`Create failed: ${response.statusText}`);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
return await response.json();
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// Step 3: Read and verify
|
|
695
|
+
async function getUser(userId) {
|
|
696
|
+
const response = await fetch(`${API_BASE}/users/${userId}`, { headers });
|
|
697
|
+
return await response.json();
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// Step 4: Update resource
|
|
701
|
+
async function updateUser(userId, updates) {
|
|
702
|
+
const response = await fetch(`${API_BASE}/users/${userId}`, {
|
|
703
|
+
method: 'PATCH',
|
|
704
|
+
headers,
|
|
705
|
+
body: JSON.stringify(updates)
|
|
706
|
+
});
|
|
707
|
+
return await response.json();
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// Step 5: Delete and verify
|
|
711
|
+
async function deleteUser(userId) {
|
|
712
|
+
const response = await fetch(`${API_BASE}/users/${userId}`, {
|
|
713
|
+
method: 'DELETE',
|
|
714
|
+
headers
|
|
715
|
+
});
|
|
716
|
+
return response.ok;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// Execute workflow
|
|
720
|
+
const user = await createUser({ name: 'John', email: 'john@example.com' });
|
|
721
|
+
const fetched = await getUser(user.id);
|
|
722
|
+
await updateUser(user.id, { email: 'newemail@example.com' });
|
|
723
|
+
await deleteUser(user.id);
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
### 2. REST API Testing Suite
|
|
727
|
+
|
|
728
|
+
```javascript
|
|
729
|
+
// Step 1: Define test suite
|
|
730
|
+
const tests = [
|
|
731
|
+
{
|
|
732
|
+
name: 'Authentication works',
|
|
733
|
+
run: async () => {
|
|
734
|
+
const response = await fetch('https://api.example.com/auth/login', {
|
|
735
|
+
method: 'POST',
|
|
736
|
+
headers: { 'Content-Type': 'application/json' },
|
|
737
|
+
body: JSON.stringify({ username: 'test', password: 'test123' })
|
|
738
|
+
});
|
|
739
|
+
assert.strictEqual(response.status, 200);
|
|
740
|
+
const data = await response.json();
|
|
741
|
+
assert(data.token, 'Token should be present');
|
|
742
|
+
return data.token;
|
|
743
|
+
}
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
name: 'Protected endpoint requires auth',
|
|
747
|
+
run: async () => {
|
|
748
|
+
const response = await fetch('https://api.example.com/protected');
|
|
749
|
+
assert.strictEqual(response.status, 401);
|
|
750
|
+
}
|
|
751
|
+
},
|
|
752
|
+
{
|
|
753
|
+
name: 'Pagination works correctly',
|
|
754
|
+
run: async () => {
|
|
755
|
+
const response = await fetch('https://api.example.com/users?page=1&limit=10');
|
|
756
|
+
const data = await response.json();
|
|
757
|
+
assert(data.items.length <= 10, 'Should respect limit');
|
|
758
|
+
assert(data.pagination, 'Should include pagination info');
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
];
|
|
762
|
+
|
|
763
|
+
// Step 2: Run all tests
|
|
764
|
+
let passed = 0;
|
|
765
|
+
let failed = 0;
|
|
766
|
+
const results = [];
|
|
767
|
+
|
|
768
|
+
for (const test of tests) {
|
|
769
|
+
try {
|
|
770
|
+
await test.run();
|
|
771
|
+
console.log(`✓ ${test.name}`);
|
|
772
|
+
passed++;
|
|
773
|
+
results.push({ name: test.name, status: 'PASS' });
|
|
774
|
+
} catch (error) {
|
|
775
|
+
console.error(`✗ ${test.name}: ${error.message}`);
|
|
776
|
+
failed++;
|
|
777
|
+
results.push({ name: test.name, status: 'FAIL', error: error.message });
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// Step 3: Generate test report
|
|
782
|
+
const report = {
|
|
783
|
+
timestamp: new Date(),
|
|
784
|
+
summary: { passed, failed, total: tests.length },
|
|
785
|
+
results
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
// Step 4: Save results
|
|
789
|
+
await fs.writeFile('test-results.json', JSON.stringify(report, null, 2));
|
|
790
|
+
|
|
791
|
+
// Step 5: Exit with appropriate code
|
|
792
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
### 3. Webhook Listener and Processor
|
|
796
|
+
|
|
797
|
+
```javascript
|
|
798
|
+
// Step 1: Create webhook server
|
|
799
|
+
const express = require('express');
|
|
800
|
+
const app = express();
|
|
801
|
+
app.use(express.json());
|
|
802
|
+
|
|
803
|
+
const webhookQueue = [];
|
|
804
|
+
|
|
805
|
+
// Step 2: Setup webhook endpoint with validation
|
|
806
|
+
app.post('/webhook', (req, res) => {
|
|
807
|
+
const signature = req.headers['x-webhook-signature'];
|
|
808
|
+
const secret = process.env.WEBHOOK_SECRET;
|
|
809
|
+
|
|
810
|
+
// Verify signature
|
|
811
|
+
const crypto = require('crypto');
|
|
812
|
+
const hmac = crypto.createHmac('sha256', secret);
|
|
813
|
+
const digest = hmac.update(JSON.stringify(req.body)).digest('hex');
|
|
814
|
+
|
|
815
|
+
if (signature !== digest) {
|
|
816
|
+
return res.status(401).json({ error: 'Invalid signature' });
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// Queue webhook for processing
|
|
820
|
+
webhookQueue.push({
|
|
821
|
+
id: crypto.randomUUID(),
|
|
822
|
+
timestamp: new Date(),
|
|
823
|
+
event: req.headers['x-webhook-event'],
|
|
824
|
+
data: req.body
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
res.status(200).json({ received: true });
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
// Step 3: Process webhooks asynchronously
|
|
831
|
+
async function processWebhooks() {
|
|
832
|
+
while (true) {
|
|
833
|
+
if (webhookQueue.length > 0) {
|
|
834
|
+
const webhook = webhookQueue.shift();
|
|
835
|
+
try {
|
|
836
|
+
await handleWebhook(webhook);
|
|
837
|
+
console.log(`Processed webhook ${webhook.id}`);
|
|
838
|
+
} catch (error) {
|
|
839
|
+
console.error(`Failed to process webhook ${webhook.id}:`, error);
|
|
840
|
+
// Retry logic here
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// Step 4: Handle different webhook types
|
|
848
|
+
async function handleWebhook(webhook) {
|
|
849
|
+
switch (webhook.event) {
|
|
850
|
+
case 'user.created':
|
|
851
|
+
await onUserCreated(webhook.data);
|
|
852
|
+
break;
|
|
853
|
+
case 'order.completed':
|
|
854
|
+
await onOrderCompleted(webhook.data);
|
|
855
|
+
break;
|
|
856
|
+
default:
|
|
857
|
+
console.log(`Unknown event: ${webhook.event}`);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// Step 5: Start server and processor
|
|
862
|
+
app.listen(3000, () => console.log('Webhook server running on port 3000'));
|
|
863
|
+
processWebhooks();
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
### 4. API Performance Monitoring
|
|
867
|
+
|
|
868
|
+
```javascript
|
|
869
|
+
// Step 1: Define monitoring targets
|
|
870
|
+
const endpoints = [
|
|
871
|
+
{ name: 'Homepage', url: 'https://api.example.com/', method: 'GET' },
|
|
872
|
+
{ name: 'Users API', url: 'https://api.example.com/users', method: 'GET' },
|
|
873
|
+
{ name: 'Search API', url: 'https://api.example.com/search?q=test', method: 'GET' }
|
|
874
|
+
];
|
|
875
|
+
|
|
876
|
+
// Step 2: Measure response times
|
|
877
|
+
async function measureEndpoint(endpoint) {
|
|
878
|
+
const startTime = Date.now();
|
|
879
|
+
try {
|
|
880
|
+
const response = await fetch(endpoint.url, {
|
|
881
|
+
method: endpoint.method,
|
|
882
|
+
signal: AbortSignal.timeout(10000)
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
const responseTime = Date.now() - startTime;
|
|
886
|
+
|
|
887
|
+
return {
|
|
888
|
+
name: endpoint.name,
|
|
889
|
+
status: response.status,
|
|
890
|
+
responseTime,
|
|
891
|
+
success: response.ok,
|
|
892
|
+
timestamp: new Date()
|
|
893
|
+
};
|
|
894
|
+
} catch (error) {
|
|
895
|
+
return {
|
|
896
|
+
name: endpoint.name,
|
|
897
|
+
error: error.message,
|
|
898
|
+
success: false,
|
|
899
|
+
timestamp: new Date()
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// Step 3: Run monitoring loop
|
|
905
|
+
const results = [];
|
|
906
|
+
for (const endpoint of endpoints) {
|
|
907
|
+
const result = await measureEndpoint(endpoint);
|
|
908
|
+
results.push(result);
|
|
909
|
+
console.log(`${result.name}: ${result.responseTime}ms (${result.status})`);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// Step 4: Calculate statistics
|
|
913
|
+
const stats = {
|
|
914
|
+
avgResponseTime: results.reduce((sum, r) => sum + (r.responseTime || 0), 0) / results.length,
|
|
915
|
+
successRate: (results.filter(r => r.success).length / results.length) * 100,
|
|
916
|
+
slowest: results.sort((a, b) => (b.responseTime || 0) - (a.responseTime || 0))[0]
|
|
917
|
+
};
|
|
918
|
+
|
|
919
|
+
// Step 5: Alert if thresholds exceeded
|
|
920
|
+
if (stats.avgResponseTime > 1000) {
|
|
921
|
+
console.warn(`⚠️ Average response time high: ${stats.avgResponseTime}ms`);
|
|
922
|
+
}
|
|
923
|
+
if (stats.successRate < 95) {
|
|
924
|
+
console.error(`❌ Success rate low: ${stats.successRate}%`);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
await fs.writeFile('monitoring-results.json', JSON.stringify({ stats, results }, null, 2));
|
|
928
|
+
```
|
|
929
|
+
|
|
930
|
+
### 5. Bulk Data Import via API
|
|
931
|
+
|
|
932
|
+
```javascript
|
|
933
|
+
// Step 1: Load data from CSV
|
|
934
|
+
const fs = require('fs').promises;
|
|
935
|
+
const csv = require('csv-parser');
|
|
936
|
+
const { createReadStream } = require('fs');
|
|
937
|
+
|
|
938
|
+
async function loadCSV(filePath) {
|
|
939
|
+
return new Promise((resolve, reject) => {
|
|
940
|
+
const rows = [];
|
|
941
|
+
createReadStream(filePath)
|
|
942
|
+
.pipe(csv())
|
|
943
|
+
.on('data', row => rows.push(row))
|
|
944
|
+
.on('end', () => resolve(rows))
|
|
945
|
+
.on('error', reject);
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// Step 2: Batch upload function
|
|
950
|
+
async function uploadBatch(records, batchSize = 100) {
|
|
951
|
+
const API_URL = 'https://api.example.com/users/bulk';
|
|
952
|
+
const results = { success: 0, failed: 0, errors: [] };
|
|
953
|
+
|
|
954
|
+
for (let i = 0; i < records.length; i += batchSize) {
|
|
955
|
+
const batch = records.slice(i, i + batchSize);
|
|
956
|
+
|
|
957
|
+
try {
|
|
958
|
+
const response = await fetch(API_URL, {
|
|
959
|
+
method: 'POST',
|
|
960
|
+
headers: {
|
|
961
|
+
'Content-Type': 'application/json',
|
|
962
|
+
'Authorization': `Bearer ${process.env.API_KEY}`
|
|
963
|
+
},
|
|
964
|
+
body: JSON.stringify({ records: batch })
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
if (response.ok) {
|
|
968
|
+
results.success += batch.length;
|
|
969
|
+
console.log(`Uploaded batch ${i / batchSize + 1}: ${batch.length} records`);
|
|
970
|
+
} else {
|
|
971
|
+
results.failed += batch.length;
|
|
972
|
+
const error = await response.text();
|
|
973
|
+
results.errors.push({ batch: i / batchSize + 1, error });
|
|
974
|
+
}
|
|
975
|
+
} catch (error) {
|
|
976
|
+
results.failed += batch.length;
|
|
977
|
+
results.errors.push({ batch: i / batchSize + 1, error: error.message });
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// Rate limiting: wait between batches
|
|
981
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
return results;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// Step 3: Load and validate data
|
|
988
|
+
const records = await loadCSV('users.csv');
|
|
989
|
+
console.log(`Loaded ${records.length} records`);
|
|
990
|
+
|
|
991
|
+
// Step 4: Upload in batches
|
|
992
|
+
const results = await uploadBatch(records, 100);
|
|
993
|
+
|
|
994
|
+
// Step 5: Report results
|
|
995
|
+
console.log(`\nUpload complete:`);
|
|
996
|
+
console.log(`Success: ${results.success}`);
|
|
997
|
+
console.log(`Failed: ${results.failed}`);
|
|
998
|
+
if (results.errors.length > 0) {
|
|
999
|
+
console.error('Errors:', results.errors);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
await fs.writeFile('import-results.json', JSON.stringify(results, null, 2));
|
|
1003
|
+
```
|