@usageflow/core 0.2.5 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +207 -26
- package/dist/base.d.ts +11 -6
- package/dist/base.js +150 -89
- package/dist/base.js.map +1 -1
- package/dist/types.d.ts +7 -1
- package/dist/types.js.map +1 -1
- package/jest.config.js +14 -0
- package/package.json +6 -7
- package/src/base.ts +216 -112
- package/src/types.ts +8 -1
- package/test/base.test.ts +290 -0
- package/test/socket.test.ts +72 -0
- package/test/src/base.d.ts +62 -0
- package/test/src/base.js +440 -0
- package/test/src/base.js.map +1 -0
- package/test/src/index.d.ts +3 -0
- package/test/src/index.js +20 -0
- package/test/src/index.js.map +1 -0
- package/test/src/socket.d.ts +26 -0
- package/test/src/socket.js +266 -0
- package/test/src/socket.js.map +1 -0
- package/test/src/types.d.ts +117 -0
- package/test/src/types.js +11 -0
- package/test/src/types.js.map +1 -0
- package/test/tsconfig.test.tsbuildinfo +1 -0
- package/test/types.test.ts +56 -0
- package/tsconfig.json +2 -1
- package/tsconfig.test.json +11 -0
- package/tsconfig.test.tsbuildinfo +1 -0
- package/tsconfig.tsbuildinfo +1 -1
package/src/base.ts
CHANGED
|
@@ -9,19 +9,31 @@ import {
|
|
|
9
9
|
RequestMetadata,
|
|
10
10
|
UsageFlowRequest,
|
|
11
11
|
UsageFlowAPIConfig,
|
|
12
|
+
BlockedEndpoint,
|
|
12
13
|
} from "./types";
|
|
13
14
|
import { UsageFlowSocketManger } from "./socket";
|
|
15
|
+
import { UsageFlowLogger, usLogger } from "@usageflow/logger";
|
|
16
|
+
import { randomUUID } from "crypto";
|
|
14
17
|
|
|
15
18
|
export abstract class UsageFlowAPI {
|
|
16
19
|
protected apiKey: string | null = null;
|
|
17
20
|
protected usageflowUrl: string = "https://api.usageflow.io";
|
|
18
|
-
protected webServer:
|
|
21
|
+
protected webServer: "express" | "fastify" | "nestjs" = "express";
|
|
19
22
|
protected apiConfigs: UsageFlowConfig[] = [];
|
|
23
|
+
protected blockedEndpoints: string[] = [];
|
|
20
24
|
private configUpdateInterval: NodeJS.Timeout | null = null;
|
|
21
|
-
socketManager: UsageFlowSocketManger | null = null;
|
|
25
|
+
private socketManager: UsageFlowSocketManger | null = null;
|
|
22
26
|
private applicationId: boolean = false;
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
private logger: UsageFlowLogger;
|
|
28
|
+
|
|
29
|
+
constructor(config: UsageFlowAPIConfig = { apiKey: "", poolSize: 5 }) {
|
|
30
|
+
// Initialize logger with service name
|
|
31
|
+
this.logger = usLogger({ service: 'USAGEFLOW_BASE', pretty: true, silent: process.env.UF_LOGS_ENABLED !== 'true' });
|
|
32
|
+
// Set default context for all logs
|
|
33
|
+
this.logger.setContext({
|
|
34
|
+
component: 'UsageFlowAPI',
|
|
35
|
+
webServer: this.webServer,
|
|
36
|
+
});
|
|
25
37
|
this.init(config.apiKey, this.usageflowUrl, config.poolSize);
|
|
26
38
|
}
|
|
27
39
|
|
|
@@ -41,18 +53,30 @@ export abstract class UsageFlowAPI {
|
|
|
41
53
|
// this.startConfigUpdater();
|
|
42
54
|
this.socketManager = new UsageFlowSocketManger(apiKey, poolSize);
|
|
43
55
|
// Connect the socket manager
|
|
44
|
-
this.socketManager
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
56
|
+
this.socketManager
|
|
57
|
+
.connect()
|
|
58
|
+
.catch((error) => {
|
|
59
|
+
this.logger.error(
|
|
60
|
+
"[UsageFlow] Failed to establish WebSocket connection:",
|
|
61
|
+
error,
|
|
62
|
+
);
|
|
63
|
+
})
|
|
64
|
+
.then(() => {
|
|
65
|
+
this.logger.info("WebSocket connection established");
|
|
66
|
+
if (this.socketManager?.isConnected()) {
|
|
67
|
+
this.startConfigUpdater();
|
|
68
|
+
} else {
|
|
69
|
+
this.logger.error("WebSocket connection failed");
|
|
70
|
+
}
|
|
71
|
+
});
|
|
50
72
|
|
|
51
73
|
return this;
|
|
52
74
|
}
|
|
53
75
|
|
|
54
|
-
public setWebServer(webServer:
|
|
76
|
+
public setWebServer(webServer: "express" | "fastify" | "nestjs"): void {
|
|
55
77
|
this.webServer = webServer;
|
|
78
|
+
// Update logger context with new web server
|
|
79
|
+
this.logger.setContext({ webServer });
|
|
56
80
|
}
|
|
57
81
|
|
|
58
82
|
public getApiKey(): string | null {
|
|
@@ -67,26 +91,27 @@ export abstract class UsageFlowAPI {
|
|
|
67
91
|
* Start background config update process
|
|
68
92
|
*/
|
|
69
93
|
private startConfigUpdater(): void {
|
|
94
|
+
this.logger.info("Starting background config update process");
|
|
70
95
|
if (this.configUpdateInterval) {
|
|
71
96
|
clearInterval(this.configUpdateInterval);
|
|
72
97
|
}
|
|
73
98
|
|
|
74
|
-
this.fetchApiPolicies().catch(
|
|
99
|
+
this.fetchApiPolicies().catch(this.logger.error);
|
|
75
100
|
this.configUpdateInterval = setInterval(async () => {
|
|
76
|
-
await
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
101
|
+
await Promise.all([
|
|
102
|
+
this.fetchApiPolicies(),
|
|
103
|
+
this.fetchBlockedEndpoints(),
|
|
104
|
+
]);
|
|
105
|
+
}, 10000);
|
|
80
106
|
}
|
|
81
107
|
|
|
82
108
|
public getRoutePattern(request: UsageFlowRequest): string {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return request?.routeOptions?.url || request.url || '';
|
|
109
|
+
if (this.webServer === "fastify") {
|
|
110
|
+
return request?.routeOptions?.url || request.url || "";
|
|
86
111
|
}
|
|
87
112
|
|
|
88
113
|
// For NestJS, prioritize route.path with baseUrl
|
|
89
|
-
if (this.webServer ===
|
|
114
|
+
if (this.webServer === "nestjs") {
|
|
90
115
|
// Method 1: Use request.route.path (available after route matching in NestJS)
|
|
91
116
|
if (request.route?.path) {
|
|
92
117
|
const baseUrl = request.baseUrl || "";
|
|
@@ -102,7 +127,9 @@ export abstract class UsageFlowAPI {
|
|
|
102
127
|
let path = request.path || request.url?.split("?")[0] || "/";
|
|
103
128
|
|
|
104
129
|
// Split path into segments and replace matching segments with param names
|
|
105
|
-
const pathSegments = path
|
|
130
|
+
const pathSegments = path
|
|
131
|
+
.split("/")
|
|
132
|
+
.filter((segment) => segment !== "");
|
|
106
133
|
const paramEntries = Object.entries(request.params);
|
|
107
134
|
|
|
108
135
|
// Replace segments that match param values with :paramName
|
|
@@ -121,21 +148,22 @@ export abstract class UsageFlowAPI {
|
|
|
121
148
|
}
|
|
122
149
|
}
|
|
123
150
|
|
|
124
|
-
const routePattern =
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (route.regexp) {
|
|
132
|
-
const patterned = route.regexp.test(request.url);
|
|
133
|
-
if (patterned) {
|
|
134
|
-
return patterned;
|
|
151
|
+
const routePattern =
|
|
152
|
+
request.route?.path ||
|
|
153
|
+
request.app._router.stack.find((route: any) => {
|
|
154
|
+
// a => a.path == request.url
|
|
155
|
+
if (!route.route) return false;
|
|
156
|
+
if (route.path) {
|
|
157
|
+
return route.path == request.url;
|
|
135
158
|
}
|
|
136
|
-
}
|
|
137
159
|
|
|
138
|
-
|
|
160
|
+
if (route.regexp) {
|
|
161
|
+
const patterned = route.regexp.test(request.url);
|
|
162
|
+
if (patterned) {
|
|
163
|
+
return patterned;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
})?.route?.path;
|
|
139
167
|
|
|
140
168
|
if (routePattern) {
|
|
141
169
|
return routePattern;
|
|
@@ -163,7 +191,9 @@ export abstract class UsageFlowAPI {
|
|
|
163
191
|
if (layer.route) {
|
|
164
192
|
const route = layer.route;
|
|
165
193
|
const routePath = route.path;
|
|
166
|
-
const routeMethods = Object.keys(route.methods).map(m =>
|
|
194
|
+
const routeMethods = Object.keys(route.methods).map((m) =>
|
|
195
|
+
m.toLowerCase(),
|
|
196
|
+
);
|
|
167
197
|
|
|
168
198
|
// Check if method matches and path pattern matches
|
|
169
199
|
if (routeMethods.includes(method) || routeMethods.includes("*")) {
|
|
@@ -173,7 +203,9 @@ export abstract class UsageFlowAPI {
|
|
|
173
203
|
if (request.params && Object.keys(request.params).length > 0) {
|
|
174
204
|
// Check if route path contains the param names
|
|
175
205
|
const paramNames = Object.keys(request.params);
|
|
176
|
-
const routeHasParams = paramNames.some(param =>
|
|
206
|
+
const routeHasParams = paramNames.some((param) =>
|
|
207
|
+
routePath.includes(`:${param}`),
|
|
208
|
+
);
|
|
177
209
|
if (routeHasParams) {
|
|
178
210
|
return pattern;
|
|
179
211
|
}
|
|
@@ -184,7 +216,11 @@ export abstract class UsageFlowAPI {
|
|
|
184
216
|
}
|
|
185
217
|
}
|
|
186
218
|
}
|
|
187
|
-
} else if (
|
|
219
|
+
} else if (
|
|
220
|
+
layer.name === "router" &&
|
|
221
|
+
layer.handle &&
|
|
222
|
+
layer.handle.stack
|
|
223
|
+
) {
|
|
188
224
|
// Handle router middleware (nested routers)
|
|
189
225
|
const mountPath = layer.regexp.source
|
|
190
226
|
.replace("\\/?", "")
|
|
@@ -198,19 +234,30 @@ export abstract class UsageFlowAPI {
|
|
|
198
234
|
if (subLayer.route) {
|
|
199
235
|
const route = subLayer.route;
|
|
200
236
|
const routePath = route.path;
|
|
201
|
-
const routeMethods = Object.keys(route.methods).map(m =>
|
|
202
|
-
|
|
203
|
-
|
|
237
|
+
const routeMethods = Object.keys(route.methods).map((m) =>
|
|
238
|
+
m.toLowerCase(),
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
if (
|
|
242
|
+
routeMethods.includes(method) ||
|
|
243
|
+
routeMethods.includes("*")
|
|
244
|
+
) {
|
|
204
245
|
const fullPath = mountPath + routePath;
|
|
205
246
|
// Check if this route matches by examining params
|
|
206
|
-
if (
|
|
247
|
+
if (
|
|
248
|
+
request.params &&
|
|
249
|
+
Object.keys(request.params).length > 0
|
|
250
|
+
) {
|
|
207
251
|
const paramNames = Object.keys(request.params);
|
|
208
|
-
const routeHasParams = paramNames.some(param =>
|
|
252
|
+
const routeHasParams = paramNames.some((param) =>
|
|
253
|
+
routePath.includes(`:${param}`),
|
|
254
|
+
);
|
|
209
255
|
if (routeHasParams) {
|
|
210
256
|
return fullPath;
|
|
211
257
|
}
|
|
212
258
|
} else if (!routePath.includes(":")) {
|
|
213
|
-
const currentPath =
|
|
259
|
+
const currentPath =
|
|
260
|
+
request.path || request.url?.split("?")[0] || "";
|
|
214
261
|
if (currentPath === fullPath || currentPath === routePath) {
|
|
215
262
|
return fullPath;
|
|
216
263
|
}
|
|
@@ -223,7 +270,10 @@ export abstract class UsageFlowAPI {
|
|
|
223
270
|
}
|
|
224
271
|
} catch (error) {
|
|
225
272
|
// Silently fail and try next method
|
|
226
|
-
console.debug(
|
|
273
|
+
console.debug(
|
|
274
|
+
"[UsageFlow] Could not extract route from router stack:",
|
|
275
|
+
error,
|
|
276
|
+
);
|
|
227
277
|
}
|
|
228
278
|
|
|
229
279
|
// Method 3: Reconstruct pattern from params and path
|
|
@@ -255,111 +305,160 @@ export abstract class UsageFlowAPI {
|
|
|
255
305
|
return baseUrl + path || "/";
|
|
256
306
|
}
|
|
257
307
|
|
|
258
|
-
public guessLedgerId(
|
|
308
|
+
public guessLedgerId(
|
|
309
|
+
request: UsageFlowRequest,
|
|
310
|
+
overrideUrl?: string,
|
|
311
|
+
): { ledgerId: string, hasLimit: boolean } {
|
|
259
312
|
const method = request.method;
|
|
260
313
|
const url = overrideUrl || this.getRoutePattern(request);
|
|
261
|
-
const configs = this.apiConfigs
|
|
314
|
+
const configs = this.apiConfigs;
|
|
262
315
|
|
|
263
316
|
if (!configs.length) {
|
|
264
|
-
return `${method} ${url}
|
|
317
|
+
return { ledgerId: `${method} ${url}`, hasLimit: false };
|
|
265
318
|
}
|
|
266
319
|
|
|
267
|
-
|
|
268
320
|
for (const config of configs) {
|
|
269
321
|
const fieldName = config.identityFieldName!;
|
|
270
322
|
const location = config.identityFieldLocation;
|
|
323
|
+
const hasLimit = config.hasRateLimit || false;
|
|
271
324
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
case "query_params":
|
|
279
|
-
if (request.query?.[fieldName]) {
|
|
280
|
-
return `${method} ${url} ${request.query[fieldName]}`;
|
|
281
|
-
}
|
|
282
|
-
break;
|
|
283
|
-
case "body":
|
|
284
|
-
if (request.body?.[fieldName]) {
|
|
285
|
-
return `${method} ${url} ${request.body[fieldName]}`;
|
|
286
|
-
}
|
|
287
|
-
break;
|
|
288
|
-
case "bearer_token":
|
|
289
|
-
const authHeader = this.getHeaderValue(request.headers, 'authorization');
|
|
290
|
-
const token = this.extractBearerToken(authHeader || undefined);
|
|
291
|
-
if (token) {
|
|
292
|
-
const claims = this.decodeJwtUnverified(token);
|
|
293
|
-
if (claims?.[fieldName]) {
|
|
294
|
-
return `${method} ${url} ${claims[fieldName]}`;
|
|
325
|
+
if (method === config.method && url === config.url) {
|
|
326
|
+
|
|
327
|
+
switch (location) {
|
|
328
|
+
case "path_params":
|
|
329
|
+
if (request.params?.[fieldName]) {
|
|
330
|
+
return { ledgerId: `${method} ${url} ${request.params[fieldName]}`, hasLimit };
|
|
295
331
|
}
|
|
296
|
-
|
|
297
|
-
|
|
332
|
+
break;
|
|
333
|
+
case "query_params":
|
|
334
|
+
if (request.query?.[fieldName]) {
|
|
335
|
+
return { ledgerId: `${method} ${url} ${request.query[fieldName]}`, hasLimit };
|
|
336
|
+
}
|
|
337
|
+
break;
|
|
338
|
+
case "body":
|
|
339
|
+
if (request.body?.[fieldName]) {
|
|
340
|
+
return { ledgerId: `${method} ${url} ${request.body[fieldName]}`, hasLimit };
|
|
341
|
+
}
|
|
342
|
+
break;
|
|
343
|
+
case "bearer_token":
|
|
344
|
+
const authHeader = this.getHeaderValue(
|
|
345
|
+
request.headers,
|
|
346
|
+
"authorization",
|
|
347
|
+
);
|
|
348
|
+
const token = this.extractBearerToken(authHeader || undefined);
|
|
349
|
+
if (token) {
|
|
350
|
+
const claims = this.decodeJwtUnverified(token);
|
|
351
|
+
if (claims?.[fieldName]) {
|
|
352
|
+
return { ledgerId: `${method} ${url} ${claims[fieldName]}`, hasLimit };
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
break;
|
|
298
356
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
357
|
+
case "headers": {
|
|
358
|
+
const headerValue = this.getHeaderValue(request.headers, fieldName);
|
|
359
|
+
if (headerValue) {
|
|
360
|
+
return { ledgerId: `${method} ${url} ${headerValue}`, hasLimit };
|
|
361
|
+
}
|
|
362
|
+
break;
|
|
303
363
|
}
|
|
304
|
-
break;
|
|
305
364
|
}
|
|
306
365
|
}
|
|
307
366
|
}
|
|
308
367
|
|
|
309
|
-
return `${method} ${url}
|
|
368
|
+
return { ledgerId: `${method} ${url}`, hasLimit: false };
|
|
310
369
|
}
|
|
311
370
|
|
|
312
371
|
private async fetchApiPolicies(): Promise<void> {
|
|
313
372
|
if (this.socketManager && this.socketManager.isConnected()) {
|
|
314
|
-
const response = await this.socketManager.sendAsync<{
|
|
373
|
+
const response = await this.socketManager.sendAsync<{
|
|
374
|
+
policies: UsageFlowConfig[];
|
|
375
|
+
total: number;
|
|
376
|
+
}>({
|
|
315
377
|
type: "get_application_policies",
|
|
316
378
|
payload: null,
|
|
317
379
|
});
|
|
318
|
-
if (response.type ===
|
|
380
|
+
if (response.type === "success") {
|
|
319
381
|
this.apiConfigs = response.payload?.policies || [];
|
|
320
382
|
}
|
|
321
383
|
}
|
|
322
384
|
}
|
|
323
385
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
): Promise<void> {
|
|
386
|
+
|
|
387
|
+
private async fetchBlockedEndpoints(): Promise<void> {
|
|
327
388
|
if (this.socketManager && this.socketManager.isConnected()) {
|
|
328
|
-
this.socketManager.sendAsync<
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
|
|
389
|
+
const response = await this.socketManager.sendAsync<{
|
|
390
|
+
endpoints: BlockedEndpoint[];
|
|
391
|
+
total: number;
|
|
392
|
+
}>({
|
|
393
|
+
type: "get_blocked_endpoints",
|
|
394
|
+
payload: null,
|
|
334
395
|
});
|
|
396
|
+
if (response.type === "success") {
|
|
397
|
+
response.payload?.endpoints || [];
|
|
398
|
+
this.blockedEndpoints = response.payload?.endpoints.map(({ method, url, identity }) => `${method} ${url} ${identity}`) || [];
|
|
399
|
+
}
|
|
335
400
|
}
|
|
336
401
|
}
|
|
337
402
|
|
|
403
|
+
async useAllocationRequest(payload: RequestForAllocation): Promise<void> {
|
|
404
|
+
if (this.socketManager && this.socketManager.isConnected()) {
|
|
405
|
+
this.socketManager
|
|
406
|
+
.sendAsync<any>({
|
|
407
|
+
type: "use_allocation",
|
|
408
|
+
payload,
|
|
409
|
+
})
|
|
410
|
+
.catch((error) => {
|
|
411
|
+
console.error(
|
|
412
|
+
"[UsageFlow] Error sending finalization via WebSocket:",
|
|
413
|
+
error,
|
|
414
|
+
);
|
|
415
|
+
throw error;
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
}
|
|
338
419
|
|
|
339
420
|
async allocationRequest(
|
|
340
421
|
request: UsageFlowRequest,
|
|
341
422
|
payload: RequestForAllocation,
|
|
342
423
|
metadata: RequestMetadata,
|
|
424
|
+
hasLimit: boolean,
|
|
343
425
|
): Promise<void> {
|
|
344
426
|
if (this.socketManager && this.socketManager.isConnected()) {
|
|
345
427
|
try {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
428
|
+
if (hasLimit) {
|
|
429
|
+
const allocationResponse = await this.socketManager.sendAsync<any>({
|
|
430
|
+
type: "request_for_allocation",
|
|
431
|
+
payload,
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
if (allocationResponse.type === "error") {
|
|
435
|
+
throw new Error(
|
|
436
|
+
allocationResponse.message || allocationResponse.error,
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
if (allocationResponse.type === "success") {
|
|
440
|
+
request.usageflow!.eventId = allocationResponse.payload.allocationId;
|
|
441
|
+
request.usageflow!.metadata = metadata;
|
|
442
|
+
return;
|
|
443
|
+
} else {
|
|
444
|
+
throw new Error(
|
|
445
|
+
allocationResponse.message || "Unknown error occurred",
|
|
446
|
+
);
|
|
447
|
+
}
|
|
358
448
|
} else {
|
|
359
|
-
|
|
449
|
+
payload.allocationId = randomUUID();
|
|
450
|
+
await this.socketManager.send({
|
|
451
|
+
type: "request_for_allocation",
|
|
452
|
+
payload,
|
|
453
|
+
});
|
|
454
|
+
request.usageflow!.eventId = payload.allocationId;
|
|
455
|
+
request.usageflow!.metadata = metadata;
|
|
360
456
|
}
|
|
361
457
|
} catch (error: any) {
|
|
362
|
-
console.error(
|
|
458
|
+
console.error(
|
|
459
|
+
"[UsageFlow] WebSocket allocation failed, falling back to HTTP:",
|
|
460
|
+
error,
|
|
461
|
+
);
|
|
363
462
|
throw error;
|
|
364
463
|
// Fall through to HTTP request
|
|
365
464
|
}
|
|
@@ -468,8 +567,8 @@ export abstract class UsageFlowAPI {
|
|
|
468
567
|
}
|
|
469
568
|
|
|
470
569
|
// Check if it's a bearer token
|
|
471
|
-
const parts = authHeader.split(
|
|
472
|
-
if (parts.length !== 2 || parts[0].toLowerCase() !==
|
|
570
|
+
const parts = authHeader.split(" ");
|
|
571
|
+
if (parts.length !== 2 || parts[0].toLowerCase() !== "bearer") {
|
|
473
572
|
return null;
|
|
474
573
|
}
|
|
475
574
|
|
|
@@ -479,14 +578,19 @@ export abstract class UsageFlowAPI {
|
|
|
479
578
|
/**
|
|
480
579
|
* Get header value from headers object (handles both Record and Headers types)
|
|
481
580
|
*/
|
|
482
|
-
private getHeaderValue(
|
|
581
|
+
private getHeaderValue(
|
|
582
|
+
headers: Record<string, string | string[] | undefined> | Headers,
|
|
583
|
+
key: string,
|
|
584
|
+
): string | null {
|
|
483
585
|
// Check if it's a Headers object (from Fetch API) by checking for the 'get' method
|
|
484
|
-
if (headers && typeof (headers as any).get ===
|
|
586
|
+
if (headers && typeof (headers as any).get === "function") {
|
|
485
587
|
return (headers as any).get(key) || null;
|
|
486
588
|
}
|
|
487
589
|
// Otherwise, treat it as a Record
|
|
488
|
-
const value = (headers as Record<string, string | string[] | undefined>)[
|
|
489
|
-
|
|
590
|
+
const value = (headers as Record<string, string | string[] | undefined>)[
|
|
591
|
+
key
|
|
592
|
+
];
|
|
593
|
+
if (typeof value === "string") {
|
|
490
594
|
return value;
|
|
491
595
|
}
|
|
492
596
|
if (Array.isArray(value) && value.length > 0) {
|
|
@@ -503,14 +607,14 @@ export abstract class UsageFlowAPI {
|
|
|
503
607
|
public decodeJwtUnverified(token: string): Record<string, any> | null {
|
|
504
608
|
try {
|
|
505
609
|
// Split the token into parts
|
|
506
|
-
const parts = token.split(
|
|
610
|
+
const parts = token.split(".");
|
|
507
611
|
if (parts.length !== 3) {
|
|
508
612
|
return null;
|
|
509
613
|
}
|
|
510
614
|
|
|
511
615
|
// Decode the payload (claims)
|
|
512
616
|
const payload = parts[1];
|
|
513
|
-
const decoded = Buffer.from(payload,
|
|
617
|
+
const decoded = Buffer.from(payload, "base64").toString("utf-8");
|
|
514
618
|
return JSON.parse(decoded);
|
|
515
619
|
} catch (error) {
|
|
516
620
|
return null;
|
package/src/types.ts
CHANGED
|
@@ -62,6 +62,13 @@ export interface UsageFlowConfig {
|
|
|
62
62
|
method: string;
|
|
63
63
|
identityFieldName?: string;
|
|
64
64
|
identityFieldLocation?: string;
|
|
65
|
+
hasRateLimit?: boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface BlockedEndpoint {
|
|
69
|
+
method: string;
|
|
70
|
+
url: string;
|
|
71
|
+
identity: string
|
|
65
72
|
}
|
|
66
73
|
|
|
67
74
|
export interface UsageFlowResponse {
|
|
@@ -90,7 +97,7 @@ export interface RequestForAllocation {
|
|
|
90
97
|
duration?: number;
|
|
91
98
|
}
|
|
92
99
|
|
|
93
|
-
export type MessageTypes = 'request_for_allocation' | 'use_allocation' | 'get_application_policies';
|
|
100
|
+
export type MessageTypes = 'request_for_allocation' | 'use_allocation' | 'get_application_policies' | 'get_blocked_endpoints';
|
|
94
101
|
|
|
95
102
|
export interface UsageFlowSocketMessage {
|
|
96
103
|
type: MessageTypes;
|