@usageflow/express 0.1.2 → 0.1.4
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/plugin.d.ts +1 -0
- package/dist/plugin.js +46 -4
- package/dist/plugin.js.map +1 -1
- package/package.json +2 -2
- package/src/plugin.ts +242 -193
package/dist/plugin.d.ts
CHANGED
|
@@ -15,4 +15,5 @@ export declare class ExpressUsageFlowAPI extends UsageFlowAPI {
|
|
|
15
15
|
private collectRequestMetadata;
|
|
16
16
|
private executeRequestWithMetadata;
|
|
17
17
|
createMiddleware(routes: Route[], whitelistRoutes?: Route[]): (request: Request, response: Response, next: NextFunction) => Promise<void>;
|
|
18
|
+
protected guessLedgerId(request: Request): string;
|
|
18
19
|
}
|
package/dist/plugin.js
CHANGED
|
@@ -15,12 +15,12 @@ class ExpressUsageFlowAPI extends core_1.UsageFlowAPI {
|
|
|
15
15
|
method: request.method,
|
|
16
16
|
url: request.route?.path || request.path || request.url || "/",
|
|
17
17
|
rawUrl: request.originalUrl || "/",
|
|
18
|
-
clientIP:
|
|
18
|
+
clientIP: clientIP || "unknown",
|
|
19
19
|
userAgent: request.headers["user-agent"],
|
|
20
20
|
timestamp: new Date().toISOString(),
|
|
21
21
|
headers,
|
|
22
|
-
queryParams: request.query,
|
|
23
|
-
pathParams: request.params,
|
|
22
|
+
queryParams: Object.keys(request.query).length > 0 ? request.query : null,
|
|
23
|
+
pathParams: Object.keys(request.params).length > 0 ? request.params : null,
|
|
24
24
|
body: request.body,
|
|
25
25
|
};
|
|
26
26
|
return metadata;
|
|
@@ -129,6 +129,10 @@ class ExpressUsageFlowAPI extends core_1.UsageFlowAPI {
|
|
|
129
129
|
"x-usage-key": self.apiKey,
|
|
130
130
|
"Content-Type": "application/json",
|
|
131
131
|
};
|
|
132
|
+
if (request.usageflow?.startTime) {
|
|
133
|
+
metadata.requestDuration =
|
|
134
|
+
Date.now() - request.usageflow.startTime;
|
|
135
|
+
}
|
|
132
136
|
const payload = {
|
|
133
137
|
alias: `${request.method} ${request.route?.path || request.path || request.url || "/"}`,
|
|
134
138
|
amount: 1,
|
|
@@ -142,7 +146,7 @@ class ExpressUsageFlowAPI extends core_1.UsageFlowAPI {
|
|
|
142
146
|
}).catch((error) => {
|
|
143
147
|
console.error("Error finalizing request:", error);
|
|
144
148
|
});
|
|
145
|
-
// Handle the different
|
|
149
|
+
// Handle the different overloadsx
|
|
146
150
|
if (typeof chunk === "function") {
|
|
147
151
|
return originalEnd.call(this, undefined, "utf8", chunk);
|
|
148
152
|
}
|
|
@@ -163,6 +167,44 @@ class ExpressUsageFlowAPI extends core_1.UsageFlowAPI {
|
|
|
163
167
|
}
|
|
164
168
|
};
|
|
165
169
|
}
|
|
170
|
+
guessLedgerId(request) {
|
|
171
|
+
const method = request.method;
|
|
172
|
+
const url = request.route?.path || request.path;
|
|
173
|
+
const config = this.apiConfig;
|
|
174
|
+
if (!config?.identityFieldName || !config?.identityFieldLocation) {
|
|
175
|
+
return `${method} ${url}`;
|
|
176
|
+
}
|
|
177
|
+
const fieldName = config.identityFieldName;
|
|
178
|
+
const location = config.identityFieldLocation;
|
|
179
|
+
switch (location) {
|
|
180
|
+
case "path_params":
|
|
181
|
+
if (request.params?.[fieldName]) {
|
|
182
|
+
return `${method} ${url} ${request.params[fieldName]}`;
|
|
183
|
+
}
|
|
184
|
+
break;
|
|
185
|
+
case "query_params":
|
|
186
|
+
if (request.query?.[fieldName]) {
|
|
187
|
+
return `${method} ${url} ${request.query[fieldName]}`;
|
|
188
|
+
}
|
|
189
|
+
break;
|
|
190
|
+
case "body":
|
|
191
|
+
if (request.body?.[fieldName]) {
|
|
192
|
+
return `${method} ${url} ${request.body[fieldName]}`;
|
|
193
|
+
}
|
|
194
|
+
break;
|
|
195
|
+
case "bearer_token":
|
|
196
|
+
const authHeader = request.headers.authorization;
|
|
197
|
+
const token = this.extractBearerToken(authHeader);
|
|
198
|
+
if (token) {
|
|
199
|
+
const claims = this.decodeJwtUnverified(token);
|
|
200
|
+
if (claims?.[fieldName]) {
|
|
201
|
+
return `${method} ${url} ${claims[fieldName]}`;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
return `${method} ${url}`;
|
|
207
|
+
}
|
|
166
208
|
}
|
|
167
209
|
exports.ExpressUsageFlowAPI = ExpressUsageFlowAPI;
|
|
168
210
|
//# 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.4",
|
|
4
4
|
"description": "UsageFlow plugin for Express applications",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"prepare": "npm run build"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@usageflow/core": "^0.1.
|
|
13
|
+
"@usageflow/core": "^0.1.3",
|
|
14
14
|
"express": "^4.18.0"
|
|
15
15
|
},
|
|
16
16
|
"homepage": "https://usageflow.io",
|
package/src/plugin.ts
CHANGED
|
@@ -2,217 +2,266 @@ import { Request, Response, NextFunction } from "express";
|
|
|
2
2
|
import { UsageFlowAPI, Route, RequestMetadata } from "@usageflow/core";
|
|
3
3
|
|
|
4
4
|
declare global {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
namespace Express {
|
|
6
|
+
interface Request {
|
|
7
|
+
usageflow?: {
|
|
8
|
+
startTime: number;
|
|
9
|
+
eventId?: string;
|
|
10
|
+
metadata?: RequestMetadata;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
12
13
|
}
|
|
13
|
-
}
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export class ExpressUsageFlowAPI extends UsageFlowAPI {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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();
|
|
29
|
-
}
|
|
17
|
+
private async collectRequestMetadata(
|
|
18
|
+
request: Request,
|
|
19
|
+
): Promise<RequestMetadata> {
|
|
20
|
+
const headers = this.sanitizeHeaders(
|
|
21
|
+
request.headers as Record<string, string>,
|
|
22
|
+
);
|
|
30
23
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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");
|
|
55
|
-
}
|
|
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();
|
|
29
|
+
}
|
|
56
30
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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;
|
|
31
|
+
const metadata: RequestMetadata = {
|
|
32
|
+
method: request.method,
|
|
33
|
+
url: request.route?.path || request.path || request.url || "/",
|
|
34
|
+
rawUrl: request.originalUrl || "/",
|
|
35
|
+
clientIP: clientIP || "unknown",
|
|
36
|
+
userAgent: request.headers["user-agent"] as string,
|
|
37
|
+
timestamp: new Date().toISOString(),
|
|
38
|
+
headers,
|
|
39
|
+
queryParams: Object.keys(request.query).length > 0 ? request.query : null,
|
|
40
|
+
pathParams: Object.keys(request.params).length > 0 ? request.params : null,
|
|
41
|
+
body: request.body,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return metadata;
|
|
98
45
|
}
|
|
99
|
-
}
|
|
100
46
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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");
|
|
55
|
+
}
|
|
105
56
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
57
|
+
const headers = {
|
|
58
|
+
"x-usage-key": this.apiKey,
|
|
59
|
+
"Content-Type": "application/json",
|
|
60
|
+
};
|
|
109
61
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
62
|
+
const payload = {
|
|
63
|
+
alias: ledgerId,
|
|
64
|
+
amount: 1,
|
|
65
|
+
metadata,
|
|
66
|
+
};
|
|
113
67
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
+
);
|
|
117
77
|
|
|
118
|
-
|
|
119
|
-
return next();
|
|
120
|
-
}
|
|
78
|
+
const data = await response.json();
|
|
121
79
|
|
|
122
|
-
|
|
80
|
+
if (response.status === 400) {
|
|
81
|
+
throw new Error(data.message);
|
|
82
|
+
}
|
|
123
83
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
)
|
|
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);
|
|
131
123
|
|
|
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
124
|
try {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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: Record<string, any> =
|
|
145
|
+
request.usageflow?.metadata || {};
|
|
146
|
+
metadata.responseStatusCode = response.statusCode;
|
|
147
|
+
|
|
148
|
+
// Add response body to metadata if it exists
|
|
149
|
+
if (chunk) {
|
|
150
|
+
try {
|
|
151
|
+
if (typeof chunk === "string") {
|
|
152
|
+
// Try to parse as JSON if it's a string
|
|
153
|
+
try {
|
|
154
|
+
const parsed = JSON.parse(chunk);
|
|
155
|
+
(metadata as any).body = parsed;
|
|
156
|
+
} catch {
|
|
157
|
+
// If not valid JSON, use the string as is
|
|
158
|
+
(metadata as any).body = chunk;
|
|
159
|
+
}
|
|
160
|
+
} else if (Buffer.isBuffer(chunk)) {
|
|
161
|
+
const str = chunk.toString("utf8");
|
|
162
|
+
// Try to parse as JSON if it's a buffer
|
|
163
|
+
try {
|
|
164
|
+
const parsed = JSON.parse(str);
|
|
165
|
+
(metadata as any).body = parsed;
|
|
166
|
+
} catch {
|
|
167
|
+
// If not valid JSON, use the string as is
|
|
168
|
+
(metadata as any).body = str;
|
|
169
|
+
}
|
|
170
|
+
} else if (typeof chunk === "object") {
|
|
171
|
+
(metadata as any).body = chunk;
|
|
172
|
+
}
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.error("Error parsing response body:", error);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const headers = {
|
|
179
|
+
"x-usage-key": self.apiKey!,
|
|
180
|
+
"Content-Type": "application/json",
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
if (request.usageflow?.startTime) {
|
|
184
|
+
metadata.requestDuration =
|
|
185
|
+
Date.now() - request.usageflow.startTime;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const payload = {
|
|
189
|
+
alias: `${request.method} ${request.route?.path || request.path || request.url || "/"}`,
|
|
190
|
+
amount: 1,
|
|
191
|
+
allocationId: request.usageflow?.eventId,
|
|
192
|
+
metadata,
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
fetch(`${self.usageflowUrl}/api/v1/ledgers/measure/allocate/use`, {
|
|
196
|
+
method: "POST",
|
|
197
|
+
headers,
|
|
198
|
+
body: JSON.stringify(payload),
|
|
199
|
+
}).catch((error) => {
|
|
200
|
+
console.error("Error finalizing request:", error);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Handle the different overloadsx
|
|
204
|
+
if (typeof chunk === "function") {
|
|
205
|
+
return originalEnd.call(this, undefined, "utf8", chunk);
|
|
206
|
+
}
|
|
207
|
+
if (typeof encoding === "function") {
|
|
208
|
+
return originalEnd.call(this, chunk, "utf8", encoding);
|
|
209
|
+
}
|
|
210
|
+
return originalEnd.call(this, chunk, encoding || "utf8", cb);
|
|
211
|
+
} as typeof response.end;
|
|
212
|
+
|
|
213
|
+
next();
|
|
214
|
+
} catch (error: any) {
|
|
215
|
+
console.error("Error executing request with metadata:", error);
|
|
216
|
+
response.status(400).json({
|
|
217
|
+
message: error.message,
|
|
218
|
+
blocked: true,
|
|
219
|
+
});
|
|
220
|
+
return;
|
|
174
221
|
}
|
|
175
|
-
|
|
222
|
+
};
|
|
223
|
+
}
|
|
176
224
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
225
|
+
protected guessLedgerId(request: Request): string {
|
|
226
|
+
const method = request.method;
|
|
227
|
+
const url = request.route?.path || request.path;
|
|
228
|
+
const config = this.apiConfig
|
|
181
229
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
allocationId: request.usageflow?.eventId,
|
|
186
|
-
metadata,
|
|
187
|
-
};
|
|
230
|
+
if (!config?.identityFieldName || !config?.identityFieldLocation) {
|
|
231
|
+
return `${method} ${url}`;
|
|
232
|
+
}
|
|
188
233
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
234
|
+
const fieldName = config.identityFieldName;
|
|
235
|
+
const location = config.identityFieldLocation;
|
|
236
|
+
|
|
237
|
+
switch (location) {
|
|
238
|
+
case "path_params":
|
|
239
|
+
if (request.params?.[fieldName]) {
|
|
240
|
+
return `${method} ${url} ${request.params[fieldName]}`;
|
|
241
|
+
}
|
|
242
|
+
break;
|
|
243
|
+
case "query_params":
|
|
244
|
+
if (request.query?.[fieldName]) {
|
|
245
|
+
return `${method} ${url} ${request.query[fieldName]}`;
|
|
246
|
+
}
|
|
247
|
+
break;
|
|
248
|
+
case "body":
|
|
249
|
+
if (request.body?.[fieldName]) {
|
|
250
|
+
return `${method} ${url} ${request.body[fieldName]}`;
|
|
251
|
+
}
|
|
252
|
+
break;
|
|
253
|
+
case "bearer_token":
|
|
254
|
+
const authHeader = request.headers.authorization;
|
|
255
|
+
const token = this.extractBearerToken(authHeader);
|
|
256
|
+
if (token) {
|
|
257
|
+
const claims = this.decodeJwtUnverified(token);
|
|
258
|
+
if (claims?.[fieldName]) {
|
|
259
|
+
return `${method} ${url} ${claims[fieldName]}`;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return `${method} ${url}`;
|
|
266
|
+
}
|
|
218
267
|
}
|