@usageflow/core 0.2.4 → 0.2.6
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 +9 -8
- package/dist/base.js +111 -49
- package/dist/base.js.map +1 -1
- package/dist/socket.js +2 -2
- package/dist/socket.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/jest.config.js +14 -0
- package/package.json +6 -7
- package/src/base.ts +177 -67
- package/src/socket.ts +2 -2
- package/src/types.ts +5 -0
- 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/README.md
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Core functionality for UsageFlow API tracking. This package provides the base implementation for tracking API usage across different Node.js frameworks.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@usageflow/core)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
7
|
|
|
7
8
|
## Installation
|
|
8
9
|
|
|
@@ -10,46 +11,226 @@ Core functionality for UsageFlow API tracking. This package provides the base im
|
|
|
10
11
|
npm install @usageflow/core
|
|
11
12
|
```
|
|
12
13
|
|
|
14
|
+
## Overview
|
|
15
|
+
|
|
16
|
+
`@usageflow/core` is the foundational package that provides core functionality for UsageFlow integrations. It includes:
|
|
17
|
+
|
|
18
|
+
- Base `UsageFlowAPI` class for framework-agnostic usage tracking
|
|
19
|
+
- WebSocket connection management with connection pooling
|
|
20
|
+
- Route pattern matching and ledger ID generation
|
|
21
|
+
- Request metadata collection and sanitization
|
|
22
|
+
- Configuration management and policy fetching
|
|
23
|
+
|
|
13
24
|
## Usage
|
|
14
25
|
|
|
15
|
-
This
|
|
26
|
+
This package is typically used indirectly through framework-specific implementations:
|
|
16
27
|
|
|
17
|
-
- [@usageflow/express](
|
|
18
|
-
- [@usageflow/fastify](
|
|
19
|
-
- [@usageflow/nestjs](
|
|
28
|
+
- [@usageflow/express](../express) for Express.js
|
|
29
|
+
- [@usageflow/fastify](../fastify) for Fastify
|
|
30
|
+
- [@usageflow/nestjs](../nestjs) for NestJS
|
|
31
|
+
|
|
32
|
+
If you need to extend UsageFlow for a different framework, you can extend the `UsageFlowAPI` class:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { UsageFlowAPI, UsageFlowAPIConfig } from '@usageflow/core';
|
|
20
36
|
|
|
21
|
-
|
|
37
|
+
class CustomFrameworkUsageFlowAPI extends UsageFlowAPI {
|
|
38
|
+
constructor(config: UsageFlowAPIConfig) {
|
|
39
|
+
super(config);
|
|
40
|
+
this.setWebServer('custom');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Implement framework-specific methods
|
|
44
|
+
}
|
|
45
|
+
```
|
|
22
46
|
|
|
23
47
|
## API Reference
|
|
24
48
|
|
|
49
|
+
### UsageFlowAPIConfig
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
interface UsageFlowAPIConfig {
|
|
53
|
+
apiKey: string; // Your UsageFlow API key
|
|
54
|
+
poolSize?: number; // Number of WebSocket connections (default: 5)
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### UsageFlowAPI
|
|
59
|
+
|
|
60
|
+
Base class for all UsageFlow implementations.
|
|
61
|
+
|
|
62
|
+
#### Constructor
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
constructor(config: UsageFlowAPIConfig)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
#### Methods
|
|
69
|
+
|
|
70
|
+
##### `setWebServer(webServer: 'express' | 'fastify' | 'nestjs'): void`
|
|
71
|
+
|
|
72
|
+
Sets the web server type for route pattern extraction.
|
|
73
|
+
|
|
74
|
+
##### `getApiKey(): string | null`
|
|
75
|
+
|
|
76
|
+
Returns the configured API key.
|
|
77
|
+
|
|
78
|
+
##### `getUsageflowUrl(): string`
|
|
79
|
+
|
|
80
|
+
Returns the UsageFlow API URL.
|
|
81
|
+
|
|
82
|
+
##### `getRoutePattern(request: UsageFlowRequest): string`
|
|
83
|
+
|
|
84
|
+
Extracts the route pattern from a request object. Supports Express, Fastify, and NestJS.
|
|
85
|
+
|
|
86
|
+
##### `guessLedgerId(request: UsageFlowRequest, overrideUrl?: string): string`
|
|
87
|
+
|
|
88
|
+
Generates a ledger ID for tracking based on the request and configured policies.
|
|
89
|
+
|
|
90
|
+
##### `createRoutesMap(routes: Route[]): RoutesMap`
|
|
91
|
+
|
|
92
|
+
Converts an array of routes into a map for efficient lookup.
|
|
93
|
+
|
|
94
|
+
##### `shouldSkipRoute(method: string, url: string, whitelistMap: RoutesMap): boolean`
|
|
95
|
+
|
|
96
|
+
Checks if a route should be skipped based on the whitelist.
|
|
97
|
+
|
|
98
|
+
##### `shouldMonitorRoute(method: string, url: string, routesMap: RoutesMap): boolean`
|
|
99
|
+
|
|
100
|
+
Checks if a route should be monitored based on the routes map.
|
|
101
|
+
|
|
102
|
+
##### `sanitizeHeaders(headers: Headers): Headers`
|
|
103
|
+
|
|
104
|
+
Sanitizes sensitive information from headers (authorization tokens, API keys, etc.).
|
|
105
|
+
|
|
106
|
+
##### `extractBearerToken(authHeader?: string): string | null`
|
|
107
|
+
|
|
108
|
+
Extracts the bearer token from an Authorization header.
|
|
109
|
+
|
|
110
|
+
##### `decodeJwtUnverified(token: string): Record<string, any> | null`
|
|
111
|
+
|
|
112
|
+
Decodes a JWT token without verifying the signature.
|
|
113
|
+
|
|
114
|
+
##### `allocationRequest(request: UsageFlowRequest, payload: RequestForAllocation, metadata: RequestMetadata): Promise<void>`
|
|
115
|
+
|
|
116
|
+
Sends an allocation request via WebSocket.
|
|
117
|
+
|
|
118
|
+
##### `useAllocationRequest(payload: RequestForAllocation): Promise<void>`
|
|
119
|
+
|
|
120
|
+
Finalizes an allocation request.
|
|
121
|
+
|
|
122
|
+
##### `destroy(): void`
|
|
123
|
+
|
|
124
|
+
Cleans up resources including intervals and WebSocket connections.
|
|
125
|
+
|
|
126
|
+
### UsageFlowSocketManager
|
|
127
|
+
|
|
128
|
+
Manages WebSocket connections with connection pooling.
|
|
129
|
+
|
|
130
|
+
#### Constructor
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
constructor(apiKey: string, poolSize?: number)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### Methods
|
|
137
|
+
|
|
138
|
+
##### `connect(): Promise<void>`
|
|
139
|
+
|
|
140
|
+
Establishes WebSocket connections in the pool.
|
|
141
|
+
|
|
142
|
+
##### `sendAsync<T>(payload: UsageFlowSocketMessage): Promise<UsageFlowSocketResponse<T>>`
|
|
143
|
+
|
|
144
|
+
Sends a message and waits for a response.
|
|
145
|
+
|
|
146
|
+
##### `send(payload: UsageFlowSocketMessage): void`
|
|
147
|
+
|
|
148
|
+
Sends a message without waiting for a response.
|
|
149
|
+
|
|
150
|
+
##### `isConnected(): boolean`
|
|
151
|
+
|
|
152
|
+
Checks if at least one WebSocket connection is active.
|
|
153
|
+
|
|
154
|
+
##### `close(): void`
|
|
155
|
+
|
|
156
|
+
Closes all WebSocket connections.
|
|
157
|
+
|
|
158
|
+
##### `destroy(): void`
|
|
159
|
+
|
|
160
|
+
Cleans up all resources.
|
|
161
|
+
|
|
162
|
+
## Types
|
|
163
|
+
|
|
164
|
+
### Route
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
interface Route {
|
|
168
|
+
method: string; // HTTP method or '*' for all methods
|
|
169
|
+
url: string; // URL pattern or '*' for all URLs
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### RequestMetadata
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
interface RequestMetadata {
|
|
177
|
+
method: string;
|
|
178
|
+
url: string;
|
|
179
|
+
rawUrl: string;
|
|
180
|
+
clientIP: string;
|
|
181
|
+
userAgent?: string;
|
|
182
|
+
timestamp: string;
|
|
183
|
+
headers: Record<string, string>;
|
|
184
|
+
queryParams: Record<string, any> | null;
|
|
185
|
+
pathParams: Record<string, any> | null;
|
|
186
|
+
body?: any;
|
|
187
|
+
requestDuration?: number;
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### UsageFlowConfig
|
|
192
|
+
|
|
25
193
|
```typescript
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
url: string,
|
|
37
|
-
whitelistMap: Map<string, Set<string>>,
|
|
38
|
-
): boolean;
|
|
39
|
-
sanitizeHeaders(headers: Record<string, string>): Record<string, string>;
|
|
194
|
+
interface UsageFlowConfig {
|
|
195
|
+
url: string;
|
|
196
|
+
method: string;
|
|
197
|
+
identityFieldName?: string;
|
|
198
|
+
identityFieldLocation?:
|
|
199
|
+
| 'path_params'
|
|
200
|
+
| 'query_params'
|
|
201
|
+
| 'body'
|
|
202
|
+
| 'bearer_token'
|
|
203
|
+
| 'headers';
|
|
40
204
|
}
|
|
41
205
|
```
|
|
42
206
|
|
|
43
|
-
##
|
|
207
|
+
## Requirements
|
|
44
208
|
|
|
45
|
-
|
|
209
|
+
- Node.js >= 18.0.0
|
|
210
|
+
- TypeScript >= 5.0.0 (for TypeScript projects)
|
|
46
211
|
|
|
47
|
-
|
|
48
|
-
- Incomplete features
|
|
49
|
-
- Potential bugs
|
|
212
|
+
## Dependencies
|
|
50
213
|
|
|
51
|
-
|
|
214
|
+
- `axios`: HTTP client for API requests
|
|
215
|
+
- `ws`: WebSocket client library
|
|
216
|
+
|
|
217
|
+
## Development
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
# Install dependencies
|
|
221
|
+
npm install
|
|
222
|
+
|
|
223
|
+
# Build the package
|
|
224
|
+
npm run build
|
|
225
|
+
|
|
226
|
+
# Run tests
|
|
227
|
+
npm test
|
|
228
|
+
```
|
|
52
229
|
|
|
53
230
|
## License
|
|
54
231
|
|
|
55
232
|
MIT
|
|
233
|
+
|
|
234
|
+
## Support
|
|
235
|
+
|
|
236
|
+
For issues, questions, or contributions, please visit our [GitHub repository](https://github.com/usageflow/js-mongorepo).
|
package/dist/base.d.ts
CHANGED
|
@@ -1,29 +1,30 @@
|
|
|
1
|
-
import { Route, UsageFlowConfig, RoutesMap, Headers, RequestForAllocation, RequestMetadata, UsageFlowRequest } from "./types";
|
|
2
|
-
import { UsageFlowSocketManger } from "./socket";
|
|
1
|
+
import { Route, UsageFlowConfig, RoutesMap, Headers, RequestForAllocation, RequestMetadata, UsageFlowRequest, UsageFlowAPIConfig } from "./types";
|
|
3
2
|
export declare abstract class UsageFlowAPI {
|
|
4
3
|
protected apiKey: string | null;
|
|
5
4
|
protected usageflowUrl: string;
|
|
6
|
-
protected webServer:
|
|
5
|
+
protected webServer: "express" | "fastify" | "nestjs";
|
|
7
6
|
protected apiConfigs: UsageFlowConfig[];
|
|
8
7
|
private configUpdateInterval;
|
|
9
|
-
socketManager
|
|
8
|
+
private socketManager;
|
|
10
9
|
private applicationId;
|
|
10
|
+
private logger;
|
|
11
|
+
constructor(config?: UsageFlowAPIConfig);
|
|
11
12
|
/**
|
|
12
13
|
* Initialize the UsageFlow API with credentials
|
|
13
14
|
* @param apiKey - Your UsageFlow API key
|
|
14
15
|
* @param usageflowUrl - The UsageFlow API URL (default: https://api.usageflow.io)
|
|
15
16
|
* @param poolSize - Number of WebSocket connections in the pool (default: 5)
|
|
16
17
|
*/
|
|
17
|
-
init
|
|
18
|
-
setWebServer(webServer:
|
|
18
|
+
private init;
|
|
19
|
+
setWebServer(webServer: "express" | "fastify" | "nestjs"): void;
|
|
19
20
|
getApiKey(): string | null;
|
|
20
21
|
getUsageflowUrl(): string;
|
|
21
22
|
/**
|
|
22
23
|
* Start background config update process
|
|
23
24
|
*/
|
|
24
25
|
private startConfigUpdater;
|
|
25
|
-
|
|
26
|
-
guessLedgerId(request: UsageFlowRequest): string;
|
|
26
|
+
getRoutePattern(request: UsageFlowRequest): string;
|
|
27
|
+
guessLedgerId(request: UsageFlowRequest, overrideUrl?: string): string;
|
|
27
28
|
private fetchApiPolicies;
|
|
28
29
|
useAllocationRequest(payload: RequestForAllocation): Promise<void>;
|
|
29
30
|
allocationRequest(request: UsageFlowRequest, payload: RequestForAllocation, metadata: RequestMetadata): Promise<void>;
|
package/dist/base.js
CHANGED
|
@@ -2,15 +2,24 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.UsageFlowAPI = void 0;
|
|
4
4
|
const socket_1 = require("./socket");
|
|
5
|
+
const logger_1 = require("@usageflow/logger");
|
|
5
6
|
class UsageFlowAPI {
|
|
6
|
-
constructor() {
|
|
7
|
+
constructor(config = { apiKey: "", poolSize: 5 }) {
|
|
7
8
|
this.apiKey = null;
|
|
8
9
|
this.usageflowUrl = "https://api.usageflow.io";
|
|
9
|
-
this.webServer =
|
|
10
|
+
this.webServer = "express";
|
|
10
11
|
this.apiConfigs = [];
|
|
11
12
|
this.configUpdateInterval = null;
|
|
12
13
|
this.socketManager = null;
|
|
13
14
|
this.applicationId = false;
|
|
15
|
+
// Initialize logger with service name
|
|
16
|
+
this.logger = (0, logger_1.usLogger)({ service: 'USAGEFLOW_BASE', pretty: true, silent: process.env.UF_LOGS_ENABLED !== 'true' });
|
|
17
|
+
// Set default context for all logs
|
|
18
|
+
this.logger.setContext({
|
|
19
|
+
component: 'UsageFlowAPI',
|
|
20
|
+
webServer: this.webServer,
|
|
21
|
+
});
|
|
22
|
+
this.init(config.apiKey, this.usageflowUrl, config.poolSize);
|
|
14
23
|
}
|
|
15
24
|
/**
|
|
16
25
|
* Initialize the UsageFlow API with credentials
|
|
@@ -24,15 +33,27 @@ class UsageFlowAPI {
|
|
|
24
33
|
// this.startConfigUpdater();
|
|
25
34
|
this.socketManager = new socket_1.UsageFlowSocketManger(apiKey, poolSize);
|
|
26
35
|
// Connect the socket manager
|
|
27
|
-
this.socketManager
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
this.
|
|
36
|
+
this.socketManager
|
|
37
|
+
.connect()
|
|
38
|
+
.catch((error) => {
|
|
39
|
+
this.logger.error("[UsageFlow] Failed to establish WebSocket connection:", error);
|
|
40
|
+
})
|
|
41
|
+
.then(() => {
|
|
42
|
+
var _a;
|
|
43
|
+
this.logger.info("WebSocket connection established");
|
|
44
|
+
if ((_a = this.socketManager) === null || _a === void 0 ? void 0 : _a.isConnected()) {
|
|
45
|
+
this.startConfigUpdater();
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
this.logger.error("WebSocket connection failed");
|
|
49
|
+
}
|
|
31
50
|
});
|
|
32
51
|
return this;
|
|
33
52
|
}
|
|
34
53
|
setWebServer(webServer) {
|
|
35
54
|
this.webServer = webServer;
|
|
55
|
+
// Update logger context with new web server
|
|
56
|
+
this.logger.setContext({ webServer });
|
|
36
57
|
}
|
|
37
58
|
getApiKey() {
|
|
38
59
|
return this.apiKey;
|
|
@@ -44,39 +65,74 @@ class UsageFlowAPI {
|
|
|
44
65
|
* Start background config update process
|
|
45
66
|
*/
|
|
46
67
|
startConfigUpdater() {
|
|
68
|
+
this.logger.info("Starting background config update process");
|
|
47
69
|
if (this.configUpdateInterval) {
|
|
48
70
|
clearInterval(this.configUpdateInterval);
|
|
49
71
|
}
|
|
50
|
-
this.fetchApiPolicies().catch(
|
|
72
|
+
this.fetchApiPolicies().catch(this.logger.error);
|
|
51
73
|
this.configUpdateInterval = setInterval(async () => {
|
|
52
74
|
await this.fetchApiPolicies();
|
|
53
75
|
}, 60000);
|
|
54
76
|
// this.fetchApiConfig().catch(console.error);
|
|
55
77
|
}
|
|
56
78
|
getRoutePattern(request) {
|
|
57
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
58
|
-
if (this.webServer ===
|
|
59
|
-
return ((_a = request === null || request === void 0 ? void 0 : request.routeOptions) === null || _a === void 0 ? void 0 : _a.url) || request.url ||
|
|
79
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
80
|
+
if (this.webServer === "fastify") {
|
|
81
|
+
return ((_a = request === null || request === void 0 ? void 0 : request.routeOptions) === null || _a === void 0 ? void 0 : _a.url) || request.url || "";
|
|
60
82
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
83
|
+
// For NestJS, prioritize route.path with baseUrl
|
|
84
|
+
if (this.webServer === "nestjs") {
|
|
85
|
+
// Method 1: Use request.route.path (available after route matching in NestJS)
|
|
86
|
+
if ((_b = request.route) === null || _b === void 0 ? void 0 : _b.path) {
|
|
87
|
+
const baseUrl = request.baseUrl || "";
|
|
88
|
+
const routePath = request.route.path;
|
|
89
|
+
// Combine baseUrl and routePath to get full pattern
|
|
90
|
+
const fullPath = baseUrl + routePath;
|
|
91
|
+
return fullPath || "/";
|
|
67
92
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
93
|
+
// Method 2: Reconstruct pattern from params (NestJS-specific)
|
|
94
|
+
// This is more reliable for NestJS when route.path is not available
|
|
95
|
+
if (request.params && Object.keys(request.params).length > 0) {
|
|
96
|
+
let path = request.path || ((_c = request.url) === null || _c === void 0 ? void 0 : _c.split("?")[0]) || "/";
|
|
97
|
+
// Split path into segments and replace matching segments with param names
|
|
98
|
+
const pathSegments = path
|
|
99
|
+
.split("/")
|
|
100
|
+
.filter((segment) => segment !== "");
|
|
101
|
+
const paramEntries = Object.entries(request.params);
|
|
102
|
+
// Replace segments that match param values with :paramName
|
|
103
|
+
for (let i = 0; i < pathSegments.length; i++) {
|
|
104
|
+
for (const [paramName, paramValue] of paramEntries) {
|
|
105
|
+
if (pathSegments[i] === String(paramValue)) {
|
|
106
|
+
pathSegments[i] = `:${paramName}`;
|
|
107
|
+
break; // Only replace once per segment
|
|
108
|
+
}
|
|
109
|
+
}
|
|
72
110
|
}
|
|
111
|
+
// Reconstruct the full path pattern
|
|
112
|
+
const pattern = "/" + pathSegments.join("/");
|
|
113
|
+
return pattern;
|
|
73
114
|
}
|
|
74
|
-
}
|
|
115
|
+
}
|
|
116
|
+
const routePattern = ((_d = request.route) === null || _d === void 0 ? void 0 : _d.path) ||
|
|
117
|
+
((_f = (_e = request.app._router.stack.find((route) => {
|
|
118
|
+
// a => a.path == request.url
|
|
119
|
+
if (!route.route)
|
|
120
|
+
return false;
|
|
121
|
+
if (route.path) {
|
|
122
|
+
return route.path == request.url;
|
|
123
|
+
}
|
|
124
|
+
if (route.regexp) {
|
|
125
|
+
const patterned = route.regexp.test(request.url);
|
|
126
|
+
if (patterned) {
|
|
127
|
+
return patterned;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
})) === null || _e === void 0 ? void 0 : _e.route) === null || _f === void 0 ? void 0 : _f.path);
|
|
75
131
|
if (routePattern) {
|
|
76
132
|
return routePattern;
|
|
77
133
|
}
|
|
78
134
|
// Method 1: Use request.route.path (available after route matching)
|
|
79
|
-
if ((
|
|
135
|
+
if ((_g = request.route) === null || _g === void 0 ? void 0 : _g.path) {
|
|
80
136
|
const baseUrl = request.baseUrl || "";
|
|
81
137
|
const routePath = request.route.path;
|
|
82
138
|
// Combine baseUrl and routePath to get full pattern
|
|
@@ -89,13 +145,13 @@ class UsageFlowAPI {
|
|
|
89
145
|
const router = app._router;
|
|
90
146
|
if (router && router.stack) {
|
|
91
147
|
const method = request.method.toLowerCase();
|
|
92
|
-
const path = request.path || ((
|
|
148
|
+
const path = request.path || ((_h = request.url) === null || _h === void 0 ? void 0 : _h.split("?")[0]) || "";
|
|
93
149
|
// Search through router stack for matching route
|
|
94
150
|
for (const layer of router.stack) {
|
|
95
151
|
if (layer.route) {
|
|
96
152
|
const route = layer.route;
|
|
97
153
|
const routePath = route.path;
|
|
98
|
-
const routeMethods = Object.keys(route.methods).map(m => m.toLowerCase());
|
|
154
|
+
const routeMethods = Object.keys(route.methods).map((m) => m.toLowerCase());
|
|
99
155
|
// Check if method matches and path pattern matches
|
|
100
156
|
if (routeMethods.includes(method) || routeMethods.includes("*")) {
|
|
101
157
|
// Try to match the pattern by checking if params match
|
|
@@ -104,7 +160,7 @@ class UsageFlowAPI {
|
|
|
104
160
|
if (request.params && Object.keys(request.params).length > 0) {
|
|
105
161
|
// Check if route path contains the param names
|
|
106
162
|
const paramNames = Object.keys(request.params);
|
|
107
|
-
const routeHasParams = paramNames.some(param => routePath.includes(`:${param}`));
|
|
163
|
+
const routeHasParams = paramNames.some((param) => routePath.includes(`:${param}`));
|
|
108
164
|
if (routeHasParams) {
|
|
109
165
|
return pattern;
|
|
110
166
|
}
|
|
@@ -117,7 +173,9 @@ class UsageFlowAPI {
|
|
|
117
173
|
}
|
|
118
174
|
}
|
|
119
175
|
}
|
|
120
|
-
else if (layer.name === "router" &&
|
|
176
|
+
else if (layer.name === "router" &&
|
|
177
|
+
layer.handle &&
|
|
178
|
+
layer.handle.stack) {
|
|
121
179
|
// Handle router middleware (nested routers)
|
|
122
180
|
const mountPath = layer.regexp.source
|
|
123
181
|
.replace("\\/?", "")
|
|
@@ -130,19 +188,21 @@ class UsageFlowAPI {
|
|
|
130
188
|
if (subLayer.route) {
|
|
131
189
|
const route = subLayer.route;
|
|
132
190
|
const routePath = route.path;
|
|
133
|
-
const routeMethods = Object.keys(route.methods).map(m => m.toLowerCase());
|
|
134
|
-
if (routeMethods.includes(method) ||
|
|
191
|
+
const routeMethods = Object.keys(route.methods).map((m) => m.toLowerCase());
|
|
192
|
+
if (routeMethods.includes(method) ||
|
|
193
|
+
routeMethods.includes("*")) {
|
|
135
194
|
const fullPath = mountPath + routePath;
|
|
136
195
|
// Check if this route matches by examining params
|
|
137
|
-
if (request.params &&
|
|
196
|
+
if (request.params &&
|
|
197
|
+
Object.keys(request.params).length > 0) {
|
|
138
198
|
const paramNames = Object.keys(request.params);
|
|
139
|
-
const routeHasParams = paramNames.some(param => routePath.includes(`:${param}`));
|
|
199
|
+
const routeHasParams = paramNames.some((param) => routePath.includes(`:${param}`));
|
|
140
200
|
if (routeHasParams) {
|
|
141
201
|
return fullPath;
|
|
142
202
|
}
|
|
143
203
|
}
|
|
144
204
|
else if (!routePath.includes(":")) {
|
|
145
|
-
const currentPath = request.path || ((
|
|
205
|
+
const currentPath = request.path || ((_j = request.url) === null || _j === void 0 ? void 0 : _j.split("?")[0]) || "";
|
|
146
206
|
if (currentPath === fullPath || currentPath === routePath) {
|
|
147
207
|
return fullPath;
|
|
148
208
|
}
|
|
@@ -160,7 +220,7 @@ class UsageFlowAPI {
|
|
|
160
220
|
}
|
|
161
221
|
// Method 3: Reconstruct pattern from params and path
|
|
162
222
|
if (request.params && Object.keys(request.params).length > 0) {
|
|
163
|
-
let path = request.path || ((
|
|
223
|
+
let path = request.path || ((_k = request.url) === null || _k === void 0 ? void 0 : _k.split("?")[0]) || "/";
|
|
164
224
|
const baseUrl = request.baseUrl || "";
|
|
165
225
|
// Split path into segments and replace matching segments with param names
|
|
166
226
|
const pathSegments = path.split("/");
|
|
@@ -179,13 +239,13 @@ class UsageFlowAPI {
|
|
|
179
239
|
}
|
|
180
240
|
// Fallback: return baseUrl + path or just path
|
|
181
241
|
const baseUrl = request.baseUrl || "";
|
|
182
|
-
const path = request.path || ((
|
|
242
|
+
const path = request.path || ((_l = request.url) === null || _l === void 0 ? void 0 : _l.split("?")[0]) || "/";
|
|
183
243
|
return baseUrl + path || "/";
|
|
184
244
|
}
|
|
185
|
-
guessLedgerId(request) {
|
|
245
|
+
guessLedgerId(request, overrideUrl) {
|
|
186
246
|
var _a, _b, _c;
|
|
187
247
|
const method = request.method;
|
|
188
|
-
const url = this.getRoutePattern(request);
|
|
248
|
+
const url = overrideUrl || this.getRoutePattern(request);
|
|
189
249
|
const configs = this.apiConfigs;
|
|
190
250
|
if (!configs.length) {
|
|
191
251
|
return `${method} ${url}`;
|
|
@@ -210,7 +270,7 @@ class UsageFlowAPI {
|
|
|
210
270
|
}
|
|
211
271
|
break;
|
|
212
272
|
case "bearer_token":
|
|
213
|
-
const authHeader = this.getHeaderValue(request.headers,
|
|
273
|
+
const authHeader = this.getHeaderValue(request.headers, "authorization");
|
|
214
274
|
const token = this.extractBearerToken(authHeader || undefined);
|
|
215
275
|
if (token) {
|
|
216
276
|
const claims = this.decodeJwtUnverified(token);
|
|
@@ -237,17 +297,19 @@ class UsageFlowAPI {
|
|
|
237
297
|
type: "get_application_policies",
|
|
238
298
|
payload: null,
|
|
239
299
|
});
|
|
240
|
-
if (response.type ===
|
|
300
|
+
if (response.type === "success") {
|
|
241
301
|
this.apiConfigs = ((_a = response.payload) === null || _a === void 0 ? void 0 : _a.policies) || [];
|
|
242
302
|
}
|
|
243
303
|
}
|
|
244
304
|
}
|
|
245
305
|
async useAllocationRequest(payload) {
|
|
246
306
|
if (this.socketManager && this.socketManager.isConnected()) {
|
|
247
|
-
this.socketManager
|
|
307
|
+
this.socketManager
|
|
308
|
+
.sendAsync({
|
|
248
309
|
type: "use_allocation",
|
|
249
|
-
payload
|
|
250
|
-
})
|
|
310
|
+
payload,
|
|
311
|
+
})
|
|
312
|
+
.catch((error) => {
|
|
251
313
|
console.error("[UsageFlow] Error sending finalization via WebSocket:", error);
|
|
252
314
|
throw error;
|
|
253
315
|
});
|
|
@@ -258,12 +320,12 @@ class UsageFlowAPI {
|
|
|
258
320
|
try {
|
|
259
321
|
const allocationResponse = await this.socketManager.sendAsync({
|
|
260
322
|
type: "request_for_allocation",
|
|
261
|
-
payload
|
|
323
|
+
payload,
|
|
262
324
|
});
|
|
263
|
-
if (allocationResponse.type ===
|
|
325
|
+
if (allocationResponse.type === "error") {
|
|
264
326
|
throw new Error(allocationResponse.message || allocationResponse.error);
|
|
265
327
|
}
|
|
266
|
-
if (allocationResponse.type ===
|
|
328
|
+
if (allocationResponse.type === "success") {
|
|
267
329
|
request.usageflow.eventId = allocationResponse.payload.allocationId;
|
|
268
330
|
request.usageflow.metadata = metadata;
|
|
269
331
|
return;
|
|
@@ -358,8 +420,8 @@ class UsageFlowAPI {
|
|
|
358
420
|
return null;
|
|
359
421
|
}
|
|
360
422
|
// Check if it's a bearer token
|
|
361
|
-
const parts = authHeader.split(
|
|
362
|
-
if (parts.length !== 2 || parts[0].toLowerCase() !==
|
|
423
|
+
const parts = authHeader.split(" ");
|
|
424
|
+
if (parts.length !== 2 || parts[0].toLowerCase() !== "bearer") {
|
|
363
425
|
return null;
|
|
364
426
|
}
|
|
365
427
|
return parts[1];
|
|
@@ -369,12 +431,12 @@ class UsageFlowAPI {
|
|
|
369
431
|
*/
|
|
370
432
|
getHeaderValue(headers, key) {
|
|
371
433
|
// Check if it's a Headers object (from Fetch API) by checking for the 'get' method
|
|
372
|
-
if (headers && typeof headers.get ===
|
|
434
|
+
if (headers && typeof headers.get === "function") {
|
|
373
435
|
return headers.get(key) || null;
|
|
374
436
|
}
|
|
375
437
|
// Otherwise, treat it as a Record
|
|
376
438
|
const value = headers[key];
|
|
377
|
-
if (typeof value ===
|
|
439
|
+
if (typeof value === "string") {
|
|
378
440
|
return value;
|
|
379
441
|
}
|
|
380
442
|
if (Array.isArray(value) && value.length > 0) {
|
|
@@ -390,13 +452,13 @@ class UsageFlowAPI {
|
|
|
390
452
|
decodeJwtUnverified(token) {
|
|
391
453
|
try {
|
|
392
454
|
// Split the token into parts
|
|
393
|
-
const parts = token.split(
|
|
455
|
+
const parts = token.split(".");
|
|
394
456
|
if (parts.length !== 3) {
|
|
395
457
|
return null;
|
|
396
458
|
}
|
|
397
459
|
// Decode the payload (claims)
|
|
398
460
|
const payload = parts[1];
|
|
399
|
-
const decoded = Buffer.from(payload,
|
|
461
|
+
const decoded = Buffer.from(payload, "base64").toString("utf-8");
|
|
400
462
|
return JSON.parse(decoded);
|
|
401
463
|
}
|
|
402
464
|
catch (error) {
|