@analytix402/sdk 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/dist/index.mjs ADDED
@@ -0,0 +1,383 @@
1
+ // src/client.ts
2
+ var SDK_NAME = "@analytix402/sdk";
3
+ var SDK_VERSION = "0.1.0";
4
+ var DEFAULT_CONFIG = {
5
+ baseUrl: "https://api.analytix402.com",
6
+ debug: false,
7
+ batchSize: 100,
8
+ flushInterval: 5e3,
9
+ maxRetries: 3,
10
+ maxQueueSize: 1e3,
11
+ timeout: 1e4,
12
+ excludePaths: ["/health", "/healthz", "/ready", "/metrics", "/favicon.ico"]
13
+ };
14
+ function createClient(config) {
15
+ if (!config.apiKey) {
16
+ throw new Error("Analytix402: apiKey is required");
17
+ }
18
+ if (!config.apiKey.startsWith("ax_live_") && !config.apiKey.startsWith("ax_test_")) {
19
+ console.warn("Analytix402: API key should start with ax_live_ or ax_test_");
20
+ }
21
+ const cfg = {
22
+ ...DEFAULT_CONFIG,
23
+ ...config
24
+ };
25
+ const agentId = cfg.agentId;
26
+ const queue = [];
27
+ let flushTimer = null;
28
+ let isFlushing = false;
29
+ let isShutdown = false;
30
+ const log = (...args) => {
31
+ if (cfg.debug) {
32
+ console.log("[Analytix402]", ...args);
33
+ }
34
+ };
35
+ const warn = (...args) => {
36
+ console.warn("[Analytix402]", ...args);
37
+ };
38
+ function enqueue(event) {
39
+ if (isShutdown) {
40
+ warn("Client is shutdown, event dropped");
41
+ return;
42
+ }
43
+ if (queue.length >= cfg.maxQueueSize) {
44
+ warn(`Queue full (${cfg.maxQueueSize}), dropping oldest event`);
45
+ queue.shift();
46
+ }
47
+ queue.push({
48
+ event,
49
+ attempts: 0,
50
+ addedAt: Date.now()
51
+ });
52
+ log(`Event queued (${queue.length} in queue)`);
53
+ if (queue.length >= cfg.batchSize) {
54
+ log("Batch size reached, flushing");
55
+ flush();
56
+ } else if (!flushTimer) {
57
+ flushTimer = setTimeout(() => {
58
+ flushTimer = null;
59
+ flush();
60
+ }, cfg.flushInterval);
61
+ }
62
+ }
63
+ function track(event) {
64
+ if (agentId && !event.agentId) {
65
+ event.agentId = agentId;
66
+ }
67
+ enqueue(event);
68
+ }
69
+ function heartbeat(status = "healthy", metadata) {
70
+ if (!agentId) {
71
+ warn("heartbeat() requires agentId in config");
72
+ return;
73
+ }
74
+ const event = {
75
+ type: "heartbeat",
76
+ agentId,
77
+ status,
78
+ metadata,
79
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
80
+ };
81
+ enqueue(event);
82
+ }
83
+ function reportOutcome(taskId, success, options) {
84
+ const event = {
85
+ type: "task_outcome",
86
+ agentId,
87
+ taskId,
88
+ success,
89
+ durationMs: options?.durationMs,
90
+ cost: options?.cost,
91
+ metadata: options?.metadata,
92
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
93
+ };
94
+ enqueue(event);
95
+ }
96
+ function startTask(taskId) {
97
+ const id = taskId || `task_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
98
+ const startTime = Date.now();
99
+ return {
100
+ taskId: id,
101
+ end(success, metadata) {
102
+ const durationMs = Date.now() - startTime;
103
+ reportOutcome(id, success, { durationMs, metadata });
104
+ }
105
+ };
106
+ }
107
+ function trackLLM(usage) {
108
+ const event = {
109
+ type: "llm_usage",
110
+ agentId,
111
+ taskId: usage.taskId,
112
+ model: usage.model,
113
+ provider: usage.provider,
114
+ inputTokens: usage.inputTokens,
115
+ outputTokens: usage.outputTokens,
116
+ totalTokens: usage.inputTokens + usage.outputTokens,
117
+ costUsd: usage.costUsd,
118
+ durationMs: usage.durationMs,
119
+ metadata: usage.metadata,
120
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
121
+ };
122
+ enqueue(event);
123
+ }
124
+ async function sendBatch(events) {
125
+ if (events.length === 0) return true;
126
+ const payload = {
127
+ events,
128
+ sdk: {
129
+ name: SDK_NAME,
130
+ version: SDK_VERSION
131
+ },
132
+ sentAt: (/* @__PURE__ */ new Date()).toISOString()
133
+ };
134
+ try {
135
+ const controller = new AbortController();
136
+ const timeoutId = setTimeout(() => controller.abort(), cfg.timeout);
137
+ const response = await fetch(`${cfg.baseUrl}/api/ingest/batch`, {
138
+ method: "POST",
139
+ headers: {
140
+ "Content-Type": "application/json",
141
+ "X-API-Key": cfg.apiKey,
142
+ "User-Agent": `${SDK_NAME}/${SDK_VERSION}`
143
+ },
144
+ body: JSON.stringify(payload),
145
+ signal: controller.signal
146
+ });
147
+ clearTimeout(timeoutId);
148
+ if (!response.ok) {
149
+ const text = await response.text().catch(() => "Unknown error");
150
+ throw new Error(`HTTP ${response.status}: ${text}`);
151
+ }
152
+ log(`Sent ${events.length} events successfully`);
153
+ return true;
154
+ } catch (error) {
155
+ if (error instanceof Error && error.name === "AbortError") {
156
+ warn("Request timed out");
157
+ } else {
158
+ warn("Failed to send events:", error);
159
+ }
160
+ return false;
161
+ }
162
+ }
163
+ async function flush() {
164
+ if (isFlushing || queue.length === 0) {
165
+ return;
166
+ }
167
+ isFlushing = true;
168
+ if (flushTimer) {
169
+ clearTimeout(flushTimer);
170
+ flushTimer = null;
171
+ }
172
+ const batch = queue.splice(0, cfg.batchSize);
173
+ const events = batch.map((q) => q.event);
174
+ log(`Flushing ${events.length} events`);
175
+ const success = await sendBatch(events);
176
+ if (!success) {
177
+ const retriable = batch.filter((q) => q.attempts < cfg.maxRetries);
178
+ if (retriable.length > 0) {
179
+ log(`Re-queuing ${retriable.length} events for retry`);
180
+ for (const item of retriable.reverse()) {
181
+ item.attempts++;
182
+ queue.unshift(item);
183
+ }
184
+ const backoff = Math.min(1e3 * Math.pow(2, retriable[0].attempts), 3e4);
185
+ log(`Retry scheduled in ${backoff}ms`);
186
+ flushTimer = setTimeout(() => {
187
+ flushTimer = null;
188
+ flush();
189
+ }, backoff);
190
+ } else {
191
+ warn(`Dropped ${batch.length} events after ${cfg.maxRetries} retries`);
192
+ }
193
+ }
194
+ isFlushing = false;
195
+ if (queue.length > 0 && !flushTimer) {
196
+ flushTimer = setTimeout(() => {
197
+ flushTimer = null;
198
+ flush();
199
+ }, cfg.flushInterval);
200
+ }
201
+ }
202
+ async function shutdown() {
203
+ log("Shutting down...");
204
+ isShutdown = true;
205
+ if (flushTimer) {
206
+ clearTimeout(flushTimer);
207
+ flushTimer = null;
208
+ }
209
+ if (queue.length > 0) {
210
+ log(`Flushing ${queue.length} remaining events`);
211
+ isFlushing = false;
212
+ await flush();
213
+ }
214
+ log("Shutdown complete");
215
+ }
216
+ if (typeof process !== "undefined") {
217
+ const handleExit = () => {
218
+ shutdown().catch(console.error);
219
+ };
220
+ process.on("beforeExit", handleExit);
221
+ process.on("SIGINT", handleExit);
222
+ process.on("SIGTERM", handleExit);
223
+ }
224
+ return {
225
+ track,
226
+ flush,
227
+ shutdown,
228
+ heartbeat,
229
+ reportOutcome,
230
+ startTask,
231
+ trackLLM
232
+ };
233
+ }
234
+
235
+ // src/middleware.ts
236
+ var X402_HEADERS = {
237
+ // Payment receipt header (contains payment proof)
238
+ PAYMENT: "x-payment",
239
+ // Payment token (JWT with payment info)
240
+ PAYMENT_TOKEN: "x-payment-token",
241
+ // Payer wallet address
242
+ PAYER: "x-payer",
243
+ // Payment amount
244
+ AMOUNT: "x-payment-amount",
245
+ // Payment currency
246
+ CURRENCY: "x-payment-currency",
247
+ // Transaction hash
248
+ TX_HASH: "x-payment-tx",
249
+ // Facilitator that processed payment
250
+ FACILITATOR: "x-facilitator"
251
+ };
252
+ var DEFAULT_EXCLUDE_PATHS = [
253
+ "/health",
254
+ "/healthz",
255
+ "/ready",
256
+ "/readyz",
257
+ "/live",
258
+ "/livez",
259
+ "/metrics",
260
+ "/favicon.ico",
261
+ "/.well-known"
262
+ ];
263
+ function matchPath(path, pattern) {
264
+ if (pattern.includes("*")) {
265
+ const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
266
+ return regex.test(path);
267
+ }
268
+ return path === pattern || path.startsWith(pattern + "/");
269
+ }
270
+ function extractPaymentInfo(req) {
271
+ const paymentHeader = req.get(X402_HEADERS.PAYMENT) || req.get(X402_HEADERS.PAYMENT_TOKEN);
272
+ if (!paymentHeader) {
273
+ return void 0;
274
+ }
275
+ const wallet = req.get(X402_HEADERS.PAYER) || "";
276
+ const amount = req.get(X402_HEADERS.AMOUNT) || "0";
277
+ const currency = req.get(X402_HEADERS.CURRENCY) || "USDC";
278
+ const txHash = req.get(X402_HEADERS.TX_HASH);
279
+ const facilitator = req.get(X402_HEADERS.FACILITATOR);
280
+ return {
281
+ amount,
282
+ currency,
283
+ wallet,
284
+ status: "success",
285
+ // If we have payment header, assume success (verified by facilitator)
286
+ txHash: txHash || void 0,
287
+ facilitator: facilitator || void 0
288
+ };
289
+ }
290
+ function getHeader(req, name) {
291
+ const value = req.headers[name.toLowerCase()];
292
+ if (Array.isArray(value)) {
293
+ return value[0];
294
+ }
295
+ return value;
296
+ }
297
+ function analytix402(config) {
298
+ const client = createClient(config);
299
+ const excludePaths = [
300
+ ...DEFAULT_EXCLUDE_PATHS,
301
+ ...config.excludePaths || []
302
+ ];
303
+ const debug = config.debug || false;
304
+ const agentId = config.agentId;
305
+ const agentName = config.agentName;
306
+ const log = (...args) => {
307
+ if (debug) {
308
+ console.log("[Analytix402]", ...args);
309
+ }
310
+ };
311
+ return function middleware(req, res, next) {
312
+ const startTime = Date.now();
313
+ const path = req.path || req.url;
314
+ const isExcluded = excludePaths.some((pattern) => matchPath(path, pattern));
315
+ if (isExcluded) {
316
+ log(`Excluded path: ${path}`);
317
+ return next();
318
+ }
319
+ if (config.shouldTrack) {
320
+ const requestInfo = {
321
+ method: req.method,
322
+ path,
323
+ url: req.originalUrl || req.url,
324
+ headers: req.headers,
325
+ ip: req.ip
326
+ };
327
+ if (!config.shouldTrack(requestInfo)) {
328
+ log(`Skipped by shouldTrack: ${path}`);
329
+ return next();
330
+ }
331
+ }
332
+ res.on("finish", () => {
333
+ const responseTime = Date.now() - startTime;
334
+ const statusCode = res.statusCode;
335
+ const payment = extractPaymentInfo(req);
336
+ let endpoint = path;
337
+ if (config.getEndpointName) {
338
+ endpoint = config.getEndpointName({
339
+ method: req.method,
340
+ path,
341
+ url: req.originalUrl || req.url,
342
+ headers: req.headers,
343
+ ip: req.ip
344
+ });
345
+ }
346
+ const event = {
347
+ type: "request",
348
+ method: req.method,
349
+ path,
350
+ endpoint,
351
+ statusCode,
352
+ responseTimeMs: responseTime,
353
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
354
+ ip: req.ip,
355
+ userAgent: getHeader(req, "user-agent")
356
+ };
357
+ if (agentId) {
358
+ event.agentId = agentId;
359
+ }
360
+ const taskId = getHeader(req, "x-task-id");
361
+ if (taskId) {
362
+ event.taskId = taskId;
363
+ }
364
+ if (payment) {
365
+ event.payment = payment;
366
+ log(`Payment captured: ${payment.amount} ${payment.currency} from ${payment.wallet}`);
367
+ }
368
+ client.track(event);
369
+ log(`Tracked: ${req.method} ${path} ${statusCode} ${responseTime}ms`);
370
+ });
371
+ next();
372
+ };
373
+ }
374
+ var Analytix402 = analytix402;
375
+ function createAnalytix402Client(config) {
376
+ return createClient(config);
377
+ }
378
+ export {
379
+ Analytix402,
380
+ analytix402,
381
+ createAnalytix402Client,
382
+ createClient
383
+ };
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@analytix402/sdk",
3
+ "version": "0.1.0",
4
+ "description": "Monitor, secure, and optimize your x402 APIs. Real-time analytics, security scanning, and revenue tracking.",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "require": "./dist/index.js",
12
+ "import": "./dist/index.mjs"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup src/index.ts --format cjs,esm --dts",
21
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
22
+ "typecheck": "tsc --noEmit",
23
+ "clean": "rm -rf dist",
24
+ "prepublishOnly": "npm run build"
25
+ },
26
+ "keywords": [
27
+ "x402",
28
+ "analytics",
29
+ "monitoring",
30
+ "security",
31
+ "api",
32
+ "express",
33
+ "middleware",
34
+ "crypto",
35
+ "payments",
36
+ "web3",
37
+ "http-402"
38
+ ],
39
+ "author": "Analytix402",
40
+ "license": "MIT",
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "https://github.com/MoonyPunts/Analytix402"
44
+ },
45
+ "homepage": "https://analytix402.com",
46
+ "bugs": {
47
+ "url": "https://github.com/MoonyPunts/Analytix402/issues"
48
+ },
49
+ "devDependencies": {
50
+ "@types/node": "^20.10.0",
51
+ "tsup": "^8.0.1",
52
+ "typescript": "^5.3.0"
53
+ },
54
+ "peerDependencies": {
55
+ "express": "^4.18.0 || ^5.0.0"
56
+ },
57
+ "peerDependenciesMeta": {
58
+ "express": {
59
+ "optional": true
60
+ }
61
+ },
62
+ "engines": {
63
+ "node": ">=18.0.0"
64
+ }
65
+ }