@usageflow/express 0.1.0 → 0.1.2
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 +90 -0
- package/dist/plugin.d.ts +2 -2
- package/dist/plugin.js +41 -41
- package/dist/plugin.js.map +1 -1
- package/package.json +8 -2
- package/src/plugin.ts +204 -191
package/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# @usageflow/express
|
|
2
|
+
|
|
3
|
+
Express.js middleware for UsageFlow API tracking. Easily monitor and analyze your Express.js API usage.
|
|
4
|
+
|
|
5
|
+
⚠️ **Beta Version Notice**: This package is currently in beta. Early adopters may encounter issues. We appreciate your feedback and contributions!
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @usageflow/express
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
const express = require("express");
|
|
17
|
+
const { ExpressUsageFlowAPI } = require("@usageflow/express");
|
|
18
|
+
|
|
19
|
+
const app = express();
|
|
20
|
+
app.use(express.json());
|
|
21
|
+
|
|
22
|
+
// Initialize UsageFlow
|
|
23
|
+
const usageFlow = new ExpressUsageFlowAPI();
|
|
24
|
+
usageFlow.init("YOUR_API_KEY");
|
|
25
|
+
|
|
26
|
+
// Create middleware
|
|
27
|
+
const middleware = usageFlow.createMiddleware(
|
|
28
|
+
[
|
|
29
|
+
{ method: "*", url: "*" }, // Track all routes
|
|
30
|
+
],
|
|
31
|
+
[
|
|
32
|
+
{ method: "GET", url: "/api/health" }, // Whitelist health check
|
|
33
|
+
],
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
// Apply middleware
|
|
37
|
+
app.use(middleware);
|
|
38
|
+
|
|
39
|
+
// Your routes
|
|
40
|
+
app.get("/api/users", (req, res) => {
|
|
41
|
+
res.json({ users: ["John", "Jane"] });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
app.listen(3000, () => {
|
|
45
|
+
console.log("Server running on port 3000");
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## TypeScript Support
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import express from "express";
|
|
53
|
+
import { ExpressUsageFlowAPI } from "@usageflow/express";
|
|
54
|
+
|
|
55
|
+
const app = express();
|
|
56
|
+
app.use(express.json());
|
|
57
|
+
|
|
58
|
+
const usageFlow = new ExpressUsageFlowAPI();
|
|
59
|
+
usageFlow.init("YOUR_API_KEY");
|
|
60
|
+
|
|
61
|
+
// Rest of the code remains the same
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Configuration Options
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
interface Route {
|
|
68
|
+
method: string; // HTTP method or '*' for all methods
|
|
69
|
+
url: string; // URL pattern or '*' for all URLs
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
usageFlow.createMiddleware(
|
|
73
|
+
routes: Route[], // Routes to track
|
|
74
|
+
whitelistRoutes?: Route[] // Routes to exclude from tracking
|
|
75
|
+
);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Beta Status
|
|
79
|
+
|
|
80
|
+
This is a beta release meant for early adopters. You may encounter:
|
|
81
|
+
|
|
82
|
+
- API changes in future versions
|
|
83
|
+
- Incomplete features
|
|
84
|
+
- Potential bugs
|
|
85
|
+
|
|
86
|
+
We're actively working on improvements and appreciate your feedback!
|
|
87
|
+
|
|
88
|
+
## License
|
|
89
|
+
|
|
90
|
+
MIT
|
package/dist/plugin.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Request, Response, NextFunction } from
|
|
2
|
-
import { UsageFlowAPI, Route, RequestMetadata } from
|
|
1
|
+
import { Request, Response, NextFunction } from "express";
|
|
2
|
+
import { UsageFlowAPI, Route, RequestMetadata } from "@usageflow/core";
|
|
3
3
|
declare global {
|
|
4
4
|
namespace Express {
|
|
5
5
|
interface Request {
|
package/dist/plugin.js
CHANGED
|
@@ -7,42 +7,42 @@ class ExpressUsageFlowAPI extends core_1.UsageFlowAPI {
|
|
|
7
7
|
const headers = this.sanitizeHeaders(request.headers);
|
|
8
8
|
// Get client IP, handling forwarded headers
|
|
9
9
|
let clientIP = request.ip;
|
|
10
|
-
const forwardedFor = request.headers[
|
|
11
|
-
if (forwardedFor && typeof forwardedFor ===
|
|
12
|
-
clientIP = forwardedFor.split(
|
|
10
|
+
const forwardedFor = request.headers["x-forwarded-for"];
|
|
11
|
+
if (forwardedFor && typeof forwardedFor === "string") {
|
|
12
|
+
clientIP = forwardedFor.split(",")[0].trim();
|
|
13
13
|
}
|
|
14
14
|
const metadata = {
|
|
15
15
|
method: request.method,
|
|
16
|
-
url: request.route?.path || request.path || request.url ||
|
|
17
|
-
rawUrl: request.originalUrl ||
|
|
18
|
-
clientIP: request.ip ||
|
|
19
|
-
userAgent: request.headers[
|
|
16
|
+
url: request.route?.path || request.path || request.url || "/",
|
|
17
|
+
rawUrl: request.originalUrl || "/",
|
|
18
|
+
clientIP: request.ip || "unknown",
|
|
19
|
+
userAgent: request.headers["user-agent"],
|
|
20
20
|
timestamp: new Date().toISOString(),
|
|
21
21
|
headers,
|
|
22
22
|
queryParams: request.query,
|
|
23
23
|
pathParams: request.params,
|
|
24
|
-
body: request.body
|
|
24
|
+
body: request.body,
|
|
25
25
|
};
|
|
26
26
|
return metadata;
|
|
27
27
|
}
|
|
28
28
|
async executeRequestWithMetadata(ledgerId, metadata, request, response) {
|
|
29
29
|
if (!this.apiKey) {
|
|
30
|
-
throw new Error(
|
|
30
|
+
throw new Error("API key not initialized");
|
|
31
31
|
}
|
|
32
32
|
const headers = {
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
"x-usage-key": this.apiKey,
|
|
34
|
+
"Content-Type": "application/json",
|
|
35
35
|
};
|
|
36
36
|
const payload = {
|
|
37
37
|
alias: ledgerId,
|
|
38
38
|
amount: 1,
|
|
39
|
-
metadata
|
|
39
|
+
metadata,
|
|
40
40
|
};
|
|
41
41
|
try {
|
|
42
42
|
const response = await fetch(`${this.usageflowUrl}/api/v1/ledgers/measure/allocate`, {
|
|
43
|
-
method:
|
|
43
|
+
method: "POST",
|
|
44
44
|
headers,
|
|
45
|
-
body: JSON.stringify(payload)
|
|
45
|
+
body: JSON.stringify(payload),
|
|
46
46
|
});
|
|
47
47
|
const data = await response.json();
|
|
48
48
|
if (response.status === 400) {
|
|
@@ -53,12 +53,13 @@ class ExpressUsageFlowAPI extends core_1.UsageFlowAPI {
|
|
|
53
53
|
request.usageflow.metadata = metadata;
|
|
54
54
|
}
|
|
55
55
|
else {
|
|
56
|
-
throw new Error(data.message ||
|
|
56
|
+
throw new Error(data.message || "Unknown error occurred");
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
catch (error) {
|
|
60
|
-
if (error.message ==
|
|
61
|
-
|
|
60
|
+
if (error.message ==
|
|
61
|
+
"Failed to use resource after retries: Faile to preform operation") {
|
|
62
|
+
throw new Error("Failed to allocate resource");
|
|
62
63
|
}
|
|
63
64
|
throw error;
|
|
64
65
|
}
|
|
@@ -71,12 +72,12 @@ class ExpressUsageFlowAPI extends core_1.UsageFlowAPI {
|
|
|
71
72
|
const method = request.method;
|
|
72
73
|
const url = request.route?.path || request.path || request.url;
|
|
73
74
|
request.usageflow = {
|
|
74
|
-
startTime: Date.now()
|
|
75
|
+
startTime: Date.now(),
|
|
75
76
|
};
|
|
76
|
-
if (this.shouldSkipRoute(method, url ||
|
|
77
|
+
if (this.shouldSkipRoute(method, url || "", whitelistMap)) {
|
|
77
78
|
return next();
|
|
78
79
|
}
|
|
79
|
-
if (!this.shouldMonitorRoute(method, url ||
|
|
80
|
+
if (!this.shouldMonitorRoute(method, url || "", routesMap)) {
|
|
80
81
|
return next();
|
|
81
82
|
}
|
|
82
83
|
const metadata = await this.collectRequestMetadata(request);
|
|
@@ -86,14 +87,14 @@ class ExpressUsageFlowAPI extends core_1.UsageFlowAPI {
|
|
|
86
87
|
const originalEnd = response.end;
|
|
87
88
|
response.end = function (chunk, encoding, cb) {
|
|
88
89
|
if (!request.usageflow?.eventId) {
|
|
89
|
-
return originalEnd.call(this, chunk, encoding ||
|
|
90
|
+
return originalEnd.call(this, chunk, encoding || "utf8", cb);
|
|
90
91
|
}
|
|
91
92
|
const metadata = request.usageflow?.metadata || {};
|
|
92
93
|
metadata.responseStatusCode = response.statusCode;
|
|
93
94
|
// Add response body to metadata if it exists
|
|
94
95
|
if (chunk) {
|
|
95
96
|
try {
|
|
96
|
-
if (typeof chunk ===
|
|
97
|
+
if (typeof chunk === "string") {
|
|
97
98
|
// Try to parse as JSON if it's a string
|
|
98
99
|
try {
|
|
99
100
|
const parsed = JSON.parse(chunk);
|
|
@@ -105,7 +106,7 @@ class ExpressUsageFlowAPI extends core_1.UsageFlowAPI {
|
|
|
105
106
|
}
|
|
106
107
|
}
|
|
107
108
|
else if (Buffer.isBuffer(chunk)) {
|
|
108
|
-
const str = chunk.toString(
|
|
109
|
+
const str = chunk.toString("utf8");
|
|
109
110
|
// Try to parse as JSON if it's a buffer
|
|
110
111
|
try {
|
|
111
112
|
const parsed = JSON.parse(str);
|
|
@@ -116,47 +117,47 @@ class ExpressUsageFlowAPI extends core_1.UsageFlowAPI {
|
|
|
116
117
|
metadata.body = str;
|
|
117
118
|
}
|
|
118
119
|
}
|
|
119
|
-
else if (typeof chunk ===
|
|
120
|
+
else if (typeof chunk === "object") {
|
|
120
121
|
metadata.body = chunk;
|
|
121
122
|
}
|
|
122
123
|
}
|
|
123
124
|
catch (error) {
|
|
124
|
-
console.error(
|
|
125
|
+
console.error("Error parsing response body:", error);
|
|
125
126
|
}
|
|
126
127
|
}
|
|
127
128
|
const headers = {
|
|
128
|
-
|
|
129
|
-
|
|
129
|
+
"x-usage-key": self.apiKey,
|
|
130
|
+
"Content-Type": "application/json",
|
|
130
131
|
};
|
|
131
132
|
const payload = {
|
|
132
|
-
alias: `${request.method} ${request.route?.path || request.path || request.url ||
|
|
133
|
+
alias: `${request.method} ${request.route?.path || request.path || request.url || "/"}`,
|
|
133
134
|
amount: 1,
|
|
134
135
|
allocationId: request.usageflow?.eventId,
|
|
135
|
-
metadata
|
|
136
|
+
metadata,
|
|
136
137
|
};
|
|
137
138
|
fetch(`${self.usageflowUrl}/api/v1/ledgers/measure/allocate/use`, {
|
|
138
|
-
method:
|
|
139
|
+
method: "POST",
|
|
139
140
|
headers,
|
|
140
|
-
body: JSON.stringify(payload)
|
|
141
|
-
}).catch(error => {
|
|
142
|
-
console.error(
|
|
141
|
+
body: JSON.stringify(payload),
|
|
142
|
+
}).catch((error) => {
|
|
143
|
+
console.error("Error finalizing request:", error);
|
|
143
144
|
});
|
|
144
145
|
// Handle the different overloads
|
|
145
|
-
if (typeof chunk ===
|
|
146
|
-
return originalEnd.call(this, undefined,
|
|
146
|
+
if (typeof chunk === "function") {
|
|
147
|
+
return originalEnd.call(this, undefined, "utf8", chunk);
|
|
147
148
|
}
|
|
148
|
-
if (typeof encoding ===
|
|
149
|
-
return originalEnd.call(this, chunk,
|
|
149
|
+
if (typeof encoding === "function") {
|
|
150
|
+
return originalEnd.call(this, chunk, "utf8", encoding);
|
|
150
151
|
}
|
|
151
|
-
return originalEnd.call(this, chunk, encoding ||
|
|
152
|
+
return originalEnd.call(this, chunk, encoding || "utf8", cb);
|
|
152
153
|
};
|
|
153
154
|
next();
|
|
154
155
|
}
|
|
155
156
|
catch (error) {
|
|
156
|
-
console.error(
|
|
157
|
+
console.error("Error executing request with metadata:", error);
|
|
157
158
|
response.status(400).json({
|
|
158
159
|
message: error.message,
|
|
159
|
-
blocked: true
|
|
160
|
+
blocked: true,
|
|
160
161
|
});
|
|
161
162
|
return;
|
|
162
163
|
}
|
|
@@ -164,5 +165,4 @@ class ExpressUsageFlowAPI extends core_1.UsageFlowAPI {
|
|
|
164
165
|
}
|
|
165
166
|
}
|
|
166
167
|
exports.ExpressUsageFlowAPI = ExpressUsageFlowAPI;
|
|
167
|
-
;
|
|
168
168
|
//# sourceMappingURL=plugin.js.map
|
package/dist/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":";;;AACA,0CAAuE;AAcvE,MAAa,mBAAoB,SAAQ,mBAAY;
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usageflow/express",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "UsageFlow plugin for Express applications",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -10,9 +10,15 @@
|
|
|
10
10
|
"prepare": "npm run build"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@usageflow/core": "^0.1.
|
|
13
|
+
"@usageflow/core": "^0.1.2",
|
|
14
14
|
"express": "^4.18.0"
|
|
15
15
|
},
|
|
16
|
+
"homepage": "https://usageflow.io",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/usageflow/js-mongorepo.git",
|
|
20
|
+
"directory": "packages/express"
|
|
21
|
+
},
|
|
16
22
|
"devDependencies": {
|
|
17
23
|
"@types/express": "^4.17.0",
|
|
18
24
|
"@types/node": "^20.0.0",
|
package/src/plugin.ts
CHANGED
|
@@ -1,205 +1,218 @@
|
|
|
1
|
-
import { Request, Response, NextFunction } from
|
|
2
|
-
import { UsageFlowAPI, Route, RequestMetadata } from
|
|
1
|
+
import { Request, Response, NextFunction } from "express";
|
|
2
|
+
import { UsageFlowAPI, Route, RequestMetadata } from "@usageflow/core";
|
|
3
3
|
|
|
4
4
|
declare global {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
5
|
+
namespace Express {
|
|
6
|
+
interface Request {
|
|
7
|
+
usageflow?: {
|
|
8
|
+
startTime: number;
|
|
9
|
+
eventId?: string;
|
|
10
|
+
metadata?: RequestMetadata;
|
|
11
|
+
};
|
|
13
12
|
}
|
|
13
|
+
}
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export class ExpressUsageFlowAPI extends UsageFlowAPI {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
url: request.route?.path || request.path || request.url || '/',
|
|
30
|
-
rawUrl: request.originalUrl || '/',
|
|
31
|
-
clientIP: request.ip || 'unknown',
|
|
32
|
-
userAgent: request.headers['user-agent'] as string,
|
|
33
|
-
timestamp: new Date().toISOString(),
|
|
34
|
-
headers,
|
|
35
|
-
queryParams: request.query as Record<string, any>,
|
|
36
|
-
pathParams: request.params,
|
|
37
|
-
body: request.body
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
return metadata;
|
|
17
|
+
private async collectRequestMetadata(
|
|
18
|
+
request: Request,
|
|
19
|
+
): Promise<RequestMetadata> {
|
|
20
|
+
const headers = this.sanitizeHeaders(
|
|
21
|
+
request.headers as Record<string, string>,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
// Get client IP, handling forwarded headers
|
|
25
|
+
let clientIP = request.ip;
|
|
26
|
+
const forwardedFor = request.headers["x-forwarded-for"];
|
|
27
|
+
if (forwardedFor && typeof forwardedFor === "string") {
|
|
28
|
+
clientIP = forwardedFor.split(",")[0].trim();
|
|
41
29
|
}
|
|
42
30
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
headers,
|
|
68
|
-
body: JSON.stringify(payload)
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
const data = await response.json();
|
|
72
|
-
|
|
73
|
-
if (response.status === 400) {
|
|
74
|
-
throw new Error(data.message);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (response.ok) {
|
|
78
|
-
request.usageflow!.eventId = data.eventId;
|
|
79
|
-
request.usageflow!.metadata = metadata;
|
|
80
|
-
} else {
|
|
81
|
-
throw new Error(data.message || 'Unknown error occurred');
|
|
82
|
-
}
|
|
83
|
-
} catch (error: any) {
|
|
84
|
-
if (error.message == 'Failed to use resource after retries: Faile to preform operation') {
|
|
85
|
-
throw new Error('Failed to allocate resource');
|
|
86
|
-
}
|
|
87
|
-
throw error;
|
|
88
|
-
}
|
|
31
|
+
const metadata: RequestMetadata = {
|
|
32
|
+
method: request.method,
|
|
33
|
+
url: request.route?.path || request.path || request.url || "/",
|
|
34
|
+
rawUrl: request.originalUrl || "/",
|
|
35
|
+
clientIP: request.ip || "unknown",
|
|
36
|
+
userAgent: request.headers["user-agent"] as string,
|
|
37
|
+
timestamp: new Date().toISOString(),
|
|
38
|
+
headers,
|
|
39
|
+
queryParams: request.query as Record<string, any>,
|
|
40
|
+
pathParams: request.params,
|
|
41
|
+
body: request.body,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return metadata;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private async executeRequestWithMetadata(
|
|
48
|
+
ledgerId: string,
|
|
49
|
+
metadata: RequestMetadata,
|
|
50
|
+
request: Request,
|
|
51
|
+
response: Response,
|
|
52
|
+
): Promise<void> {
|
|
53
|
+
if (!this.apiKey) {
|
|
54
|
+
throw new Error("API key not initialized");
|
|
89
55
|
}
|
|
90
56
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
57
|
+
const headers = {
|
|
58
|
+
"x-usage-key": this.apiKey,
|
|
59
|
+
"Content-Type": "application/json",
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const payload = {
|
|
63
|
+
alias: ledgerId,
|
|
64
|
+
amount: 1,
|
|
65
|
+
metadata,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const response = await fetch(
|
|
70
|
+
`${this.usageflowUrl}/api/v1/ledgers/measure/allocate`,
|
|
71
|
+
{
|
|
72
|
+
method: "POST",
|
|
73
|
+
headers,
|
|
74
|
+
body: JSON.stringify(payload),
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const data = await response.json();
|
|
79
|
+
|
|
80
|
+
if (response.status === 400) {
|
|
81
|
+
throw new Error(data.message);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (response.ok) {
|
|
85
|
+
request.usageflow!.eventId = data.eventId;
|
|
86
|
+
request.usageflow!.metadata = metadata;
|
|
87
|
+
} else {
|
|
88
|
+
throw new Error(data.message || "Unknown error occurred");
|
|
89
|
+
}
|
|
90
|
+
} catch (error: any) {
|
|
91
|
+
if (
|
|
92
|
+
error.message ==
|
|
93
|
+
"Failed to use resource after retries: Faile to preform operation"
|
|
94
|
+
) {
|
|
95
|
+
throw new Error("Failed to allocate resource");
|
|
96
|
+
}
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public createMiddleware(routes: Route[], whitelistRoutes: Route[] = []) {
|
|
102
|
+
const routesMap = this.createRoutesMap(routes);
|
|
103
|
+
const whitelistMap = this.createRoutesMap(whitelistRoutes);
|
|
104
|
+
const self = this;
|
|
105
|
+
|
|
106
|
+
return async (request: Request, response: Response, next: NextFunction) => {
|
|
107
|
+
const method = request.method;
|
|
108
|
+
const url = request.route?.path || request.path || request.url;
|
|
109
|
+
|
|
110
|
+
request.usageflow = {
|
|
111
|
+
startTime: Date.now(),
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
if (this.shouldSkipRoute(method, url || "", whitelistMap)) {
|
|
115
|
+
return next();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!this.shouldMonitorRoute(method, url || "", routesMap)) {
|
|
119
|
+
return next();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const metadata = await this.collectRequestMetadata(request);
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
await this.executeRequestWithMetadata(
|
|
126
|
+
`${method} ${url}`,
|
|
127
|
+
metadata,
|
|
128
|
+
request,
|
|
129
|
+
response,
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
// Capture response data
|
|
133
|
+
const originalEnd = response.end;
|
|
134
|
+
response.end = function (
|
|
135
|
+
this: Response,
|
|
136
|
+
chunk?: any,
|
|
137
|
+
encoding?: BufferEncoding,
|
|
138
|
+
cb?: () => void,
|
|
139
|
+
) {
|
|
140
|
+
if (!request.usageflow?.eventId) {
|
|
141
|
+
return originalEnd.call(this, chunk, encoding || "utf8", cb);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const metadata = request.usageflow?.metadata || {};
|
|
145
|
+
(metadata as any).responseStatusCode = response.statusCode;
|
|
146
|
+
|
|
147
|
+
// Add response body to metadata if it exists
|
|
148
|
+
if (chunk) {
|
|
149
|
+
try {
|
|
150
|
+
if (typeof chunk === "string") {
|
|
151
|
+
// Try to parse as JSON if it's a string
|
|
152
|
+
try {
|
|
153
|
+
const parsed = JSON.parse(chunk);
|
|
154
|
+
(metadata as any).body = parsed;
|
|
155
|
+
} catch {
|
|
156
|
+
// If not valid JSON, use the string as is
|
|
157
|
+
(metadata as any).body = chunk;
|
|
158
|
+
}
|
|
159
|
+
} else if (Buffer.isBuffer(chunk)) {
|
|
160
|
+
const str = chunk.toString("utf8");
|
|
161
|
+
// Try to parse as JSON if it's a buffer
|
|
162
|
+
try {
|
|
163
|
+
const parsed = JSON.parse(str);
|
|
164
|
+
(metadata as any).body = parsed;
|
|
165
|
+
} catch {
|
|
166
|
+
// If not valid JSON, use the string as is
|
|
167
|
+
(metadata as any).body = str;
|
|
168
|
+
}
|
|
169
|
+
} else if (typeof chunk === "object") {
|
|
170
|
+
(metadata as any).body = chunk;
|
|
171
|
+
}
|
|
172
|
+
} catch (error) {
|
|
173
|
+
console.error("Error parsing response body:", error);
|
|
106
174
|
}
|
|
175
|
+
}
|
|
107
176
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
177
|
+
const headers = {
|
|
178
|
+
"x-usage-key": self.apiKey!,
|
|
179
|
+
"Content-Type": "application/json",
|
|
180
|
+
};
|
|
111
181
|
|
|
112
|
-
|
|
182
|
+
const payload = {
|
|
183
|
+
alias: `${request.method} ${request.route?.path || request.path || request.url || "/"}`,
|
|
184
|
+
amount: 1,
|
|
185
|
+
allocationId: request.usageflow?.eventId,
|
|
186
|
+
metadata,
|
|
187
|
+
};
|
|
113
188
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
} else if (Buffer.isBuffer(chunk)) {
|
|
145
|
-
const str = chunk.toString('utf8');
|
|
146
|
-
// Try to parse as JSON if it's a buffer
|
|
147
|
-
try {
|
|
148
|
-
const parsed = JSON.parse(str);
|
|
149
|
-
(metadata as any).body = parsed;
|
|
150
|
-
} catch {
|
|
151
|
-
// If not valid JSON, use the string as is
|
|
152
|
-
(metadata as any).body = str;
|
|
153
|
-
}
|
|
154
|
-
} else if (typeof chunk === 'object') {
|
|
155
|
-
(metadata as any).body = chunk;
|
|
156
|
-
}
|
|
157
|
-
} catch (error) {
|
|
158
|
-
console.error('Error parsing response body:', error);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const headers = {
|
|
165
|
-
'x-usage-key': self.apiKey!,
|
|
166
|
-
'Content-Type': 'application/json'
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
const payload = {
|
|
170
|
-
alias: `${request.method} ${request.route?.path || request.path || request.url || '/'}`,
|
|
171
|
-
amount: 1,
|
|
172
|
-
allocationId: request.usageflow?.eventId,
|
|
173
|
-
metadata
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
fetch(`${self.usageflowUrl}/api/v1/ledgers/measure/allocate/use`, {
|
|
177
|
-
method: 'POST',
|
|
178
|
-
headers,
|
|
179
|
-
body: JSON.stringify(payload)
|
|
180
|
-
}).catch(error => {
|
|
181
|
-
console.error('Error finalizing request:', error);
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
// Handle the different overloads
|
|
185
|
-
if (typeof chunk === 'function') {
|
|
186
|
-
return originalEnd.call(this, undefined, 'utf8', chunk);
|
|
187
|
-
}
|
|
188
|
-
if (typeof encoding === 'function') {
|
|
189
|
-
return originalEnd.call(this, chunk, 'utf8', encoding);
|
|
190
|
-
}
|
|
191
|
-
return originalEnd.call(this, chunk, encoding || 'utf8', cb);
|
|
192
|
-
} as typeof response.end;
|
|
193
|
-
|
|
194
|
-
next();
|
|
195
|
-
} catch (error: any) {
|
|
196
|
-
console.error('Error executing request with metadata:', error);
|
|
197
|
-
response.status(400).json({
|
|
198
|
-
message: error.message,
|
|
199
|
-
blocked: true
|
|
200
|
-
});
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
};
|
|
189
|
+
fetch(`${self.usageflowUrl}/api/v1/ledgers/measure/allocate/use`, {
|
|
190
|
+
method: "POST",
|
|
191
|
+
headers,
|
|
192
|
+
body: JSON.stringify(payload),
|
|
193
|
+
}).catch((error) => {
|
|
194
|
+
console.error("Error finalizing request:", error);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Handle the different overloads
|
|
198
|
+
if (typeof chunk === "function") {
|
|
199
|
+
return originalEnd.call(this, undefined, "utf8", chunk);
|
|
200
|
+
}
|
|
201
|
+
if (typeof encoding === "function") {
|
|
202
|
+
return originalEnd.call(this, chunk, "utf8", encoding);
|
|
203
|
+
}
|
|
204
|
+
return originalEnd.call(this, chunk, encoding || "utf8", cb);
|
|
205
|
+
} as typeof response.end;
|
|
206
|
+
|
|
207
|
+
next();
|
|
208
|
+
} catch (error: any) {
|
|
209
|
+
console.error("Error executing request with metadata:", error);
|
|
210
|
+
response.status(400).json({
|
|
211
|
+
message: error.message,
|
|
212
|
+
blocked: true,
|
|
213
|
+
});
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
}
|