@usageflow/express 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.
@@ -0,0 +1 @@
1
+ export { ExpressUsageFlowAPI } from './plugin';
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ExpressUsageFlowAPI = void 0;
4
+ var plugin_1 = require("./plugin");
5
+ Object.defineProperty(exports, "ExpressUsageFlowAPI", { enumerable: true, get: function () { return plugin_1.ExpressUsageFlowAPI; } });
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,mCAA+C;AAAtC,6GAAA,mBAAmB,OAAA"}
@@ -0,0 +1,18 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+ import { UsageFlowAPI, Route, RequestMetadata } from '@usageflow/core';
3
+ declare global {
4
+ namespace Express {
5
+ interface Request {
6
+ usageflow?: {
7
+ startTime: number;
8
+ eventId?: string;
9
+ metadata?: RequestMetadata;
10
+ };
11
+ }
12
+ }
13
+ }
14
+ export declare class ExpressUsageFlowAPI extends UsageFlowAPI {
15
+ private collectRequestMetadata;
16
+ private executeRequestWithMetadata;
17
+ createMiddleware(routes: Route[], whitelistRoutes?: Route[]): (request: Request, response: Response, next: NextFunction) => Promise<void>;
18
+ }
package/dist/plugin.js ADDED
@@ -0,0 +1,168 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ExpressUsageFlowAPI = void 0;
4
+ const core_1 = require("@usageflow/core");
5
+ class ExpressUsageFlowAPI extends core_1.UsageFlowAPI {
6
+ async collectRequestMetadata(request) {
7
+ const headers = this.sanitizeHeaders(request.headers);
8
+ // Get client IP, handling forwarded headers
9
+ let clientIP = request.ip;
10
+ const forwardedFor = request.headers['x-forwarded-for'];
11
+ if (forwardedFor && typeof forwardedFor === 'string') {
12
+ clientIP = forwardedFor.split(',')[0].trim();
13
+ }
14
+ const metadata = {
15
+ method: request.method,
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
+ timestamp: new Date().toISOString(),
21
+ headers,
22
+ queryParams: request.query,
23
+ pathParams: request.params,
24
+ body: request.body
25
+ };
26
+ return metadata;
27
+ }
28
+ async executeRequestWithMetadata(ledgerId, metadata, request, response) {
29
+ if (!this.apiKey) {
30
+ throw new Error('API key not initialized');
31
+ }
32
+ const headers = {
33
+ 'x-usage-key': this.apiKey,
34
+ 'Content-Type': 'application/json'
35
+ };
36
+ const payload = {
37
+ alias: ledgerId,
38
+ amount: 1,
39
+ metadata
40
+ };
41
+ try {
42
+ const response = await fetch(`${this.usageflowUrl}/api/v1/ledgers/measure/allocate`, {
43
+ method: 'POST',
44
+ headers,
45
+ body: JSON.stringify(payload)
46
+ });
47
+ const data = await response.json();
48
+ if (response.status === 400) {
49
+ throw new Error(data.message);
50
+ }
51
+ if (response.ok) {
52
+ request.usageflow.eventId = data.eventId;
53
+ request.usageflow.metadata = metadata;
54
+ }
55
+ else {
56
+ throw new Error(data.message || 'Unknown error occurred');
57
+ }
58
+ }
59
+ catch (error) {
60
+ if (error.message == 'Failed to use resource after retries: Faile to preform operation') {
61
+ throw new Error('Failed to allocate resource');
62
+ }
63
+ throw error;
64
+ }
65
+ }
66
+ createMiddleware(routes, whitelistRoutes = []) {
67
+ const routesMap = this.createRoutesMap(routes);
68
+ const whitelistMap = this.createRoutesMap(whitelistRoutes);
69
+ const self = this;
70
+ return async (request, response, next) => {
71
+ const method = request.method;
72
+ const url = request.route?.path || request.path || request.url;
73
+ request.usageflow = {
74
+ startTime: Date.now()
75
+ };
76
+ if (this.shouldSkipRoute(method, url || '', whitelistMap)) {
77
+ return next();
78
+ }
79
+ if (!this.shouldMonitorRoute(method, url || '', routesMap)) {
80
+ return next();
81
+ }
82
+ const metadata = await this.collectRequestMetadata(request);
83
+ try {
84
+ await this.executeRequestWithMetadata(`${method} ${url}`, metadata, request, response);
85
+ // Capture response data
86
+ const originalEnd = response.end;
87
+ response.end = function (chunk, encoding, cb) {
88
+ if (!request.usageflow?.eventId) {
89
+ return originalEnd.call(this, chunk, encoding || 'utf8', cb);
90
+ }
91
+ const metadata = request.usageflow?.metadata || {};
92
+ metadata.responseStatusCode = response.statusCode;
93
+ // Add response body to metadata if it exists
94
+ if (chunk) {
95
+ try {
96
+ if (typeof chunk === 'string') {
97
+ // Try to parse as JSON if it's a string
98
+ try {
99
+ const parsed = JSON.parse(chunk);
100
+ metadata.body = parsed;
101
+ }
102
+ catch {
103
+ // If not valid JSON, use the string as is
104
+ metadata.body = chunk;
105
+ }
106
+ }
107
+ else if (Buffer.isBuffer(chunk)) {
108
+ const str = chunk.toString('utf8');
109
+ // Try to parse as JSON if it's a buffer
110
+ try {
111
+ const parsed = JSON.parse(str);
112
+ metadata.body = parsed;
113
+ }
114
+ catch {
115
+ // If not valid JSON, use the string as is
116
+ metadata.body = str;
117
+ }
118
+ }
119
+ else if (typeof chunk === 'object') {
120
+ metadata.body = chunk;
121
+ }
122
+ }
123
+ catch (error) {
124
+ console.error('Error parsing response body:', error);
125
+ }
126
+ }
127
+ const headers = {
128
+ 'x-usage-key': self.apiKey,
129
+ 'Content-Type': 'application/json'
130
+ };
131
+ const payload = {
132
+ alias: `${request.method} ${request.route?.path || request.path || request.url || '/'}`,
133
+ amount: 1,
134
+ allocationId: request.usageflow?.eventId,
135
+ metadata
136
+ };
137
+ fetch(`${self.usageflowUrl}/api/v1/ledgers/measure/allocate/use`, {
138
+ method: 'POST',
139
+ headers,
140
+ body: JSON.stringify(payload)
141
+ }).catch(error => {
142
+ console.error('Error finalizing request:', error);
143
+ });
144
+ // Handle the different overloads
145
+ if (typeof chunk === 'function') {
146
+ return originalEnd.call(this, undefined, 'utf8', chunk);
147
+ }
148
+ if (typeof encoding === 'function') {
149
+ return originalEnd.call(this, chunk, 'utf8', encoding);
150
+ }
151
+ return originalEnd.call(this, chunk, encoding || 'utf8', cb);
152
+ };
153
+ next();
154
+ }
155
+ catch (error) {
156
+ console.error('Error executing request with metadata:', error);
157
+ response.status(400).json({
158
+ message: error.message,
159
+ blocked: true
160
+ });
161
+ return;
162
+ }
163
+ };
164
+ }
165
+ }
166
+ exports.ExpressUsageFlowAPI = ExpressUsageFlowAPI;
167
+ ;
168
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@usageflow/express",
3
+ "version": "0.1.0",
4
+ "description": "UsageFlow plugin for Express applications",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "test": "jest",
10
+ "prepare": "npm run build"
11
+ },
12
+ "dependencies": {
13
+ "@usageflow/core": "^0.1.0",
14
+ "express": "^4.18.0"
15
+ },
16
+ "devDependencies": {
17
+ "@types/express": "^4.17.0",
18
+ "@types/node": "^20.0.0",
19
+ "typescript": "^5.0.0",
20
+ "jest": "^29.0.0",
21
+ "@types/jest": "^29.0.0",
22
+ "ts-jest": "^29.0.0"
23
+ },
24
+ "peerDependencies": {
25
+ "express": ">=4.17.0"
26
+ }
27
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export { ExpressUsageFlowAPI } from './plugin';
package/src/plugin.ts ADDED
@@ -0,0 +1,205 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+ import { UsageFlowAPI, Route, RequestMetadata } from '@usageflow/core';
3
+
4
+ declare global {
5
+ namespace Express {
6
+ interface Request {
7
+ usageflow?: {
8
+ startTime: number;
9
+ eventId?: string;
10
+ metadata?: RequestMetadata;
11
+ };
12
+ }
13
+ }
14
+ }
15
+
16
+ export class ExpressUsageFlowAPI extends UsageFlowAPI {
17
+ private async collectRequestMetadata(request: Request): Promise<RequestMetadata> {
18
+ const headers = this.sanitizeHeaders(request.headers as Record<string, string>);
19
+
20
+ // Get client IP, handling forwarded headers
21
+ let clientIP = request.ip;
22
+ const forwardedFor = request.headers['x-forwarded-for'];
23
+ if (forwardedFor && typeof forwardedFor === 'string') {
24
+ clientIP = forwardedFor.split(',')[0].trim();
25
+ }
26
+
27
+ const metadata: RequestMetadata = {
28
+ method: request.method,
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;
41
+ }
42
+
43
+ private async executeRequestWithMetadata(
44
+ ledgerId: string,
45
+ metadata: RequestMetadata,
46
+ request: Request,
47
+ response: Response
48
+ ): Promise<void> {
49
+ if (!this.apiKey) {
50
+ throw new Error('API key not initialized');
51
+ }
52
+
53
+ const headers = {
54
+ 'x-usage-key': this.apiKey,
55
+ 'Content-Type': 'application/json'
56
+ };
57
+
58
+ const payload = {
59
+ alias: ledgerId,
60
+ amount: 1,
61
+ metadata
62
+ };
63
+
64
+ try {
65
+ const response = await fetch(`${this.usageflowUrl}/api/v1/ledgers/measure/allocate`, {
66
+ method: 'POST',
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
+ }
89
+ }
90
+
91
+ public createMiddleware(routes: Route[], whitelistRoutes: Route[] = []) {
92
+ const routesMap = this.createRoutesMap(routes);
93
+ const whitelistMap = this.createRoutesMap(whitelistRoutes);
94
+ const self = this;
95
+
96
+ return async (request: Request, response: Response, next: NextFunction) => {
97
+ const method = request.method;
98
+ const url = request.route?.path || request.path || request.url;
99
+
100
+ request.usageflow = {
101
+ startTime: Date.now()
102
+ };
103
+
104
+ if (this.shouldSkipRoute(method, url || '', whitelistMap)) {
105
+ return next();
106
+ }
107
+
108
+ if (!this.shouldMonitorRoute(method, url || '', routesMap)) {
109
+ return next();
110
+ }
111
+
112
+ const metadata = await this.collectRequestMetadata(request);
113
+
114
+ try {
115
+ await this.executeRequestWithMetadata(
116
+ `${method} ${url}`,
117
+ metadata,
118
+ request,
119
+ response
120
+ );
121
+
122
+ // Capture response data
123
+ const originalEnd = response.end;
124
+ response.end = function (this: Response, chunk?: any, encoding?: BufferEncoding, cb?: () => void) {
125
+ if (!request.usageflow?.eventId) {
126
+ return originalEnd.call(this, chunk, encoding || 'utf8', cb);
127
+ }
128
+
129
+ const metadata = request.usageflow?.metadata || {};
130
+ (metadata as any).responseStatusCode = response.statusCode;
131
+
132
+ // Add response body to metadata if it exists
133
+ if (chunk) {
134
+ try {
135
+ if (typeof chunk === 'string') {
136
+ // Try to parse as JSON if it's a string
137
+ try {
138
+ const parsed = JSON.parse(chunk);
139
+ (metadata as any).body = parsed;
140
+ } catch {
141
+ // If not valid JSON, use the string as is
142
+ (metadata as any).body = chunk;
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
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src/**/*"],
8
+ "references": [{ "path": "../core" }]
9
+ }