@rxbenefits/server-utils 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.
- package/.turbo/turbo-build.log +5 -0
- package/.turbo/turbo-lint.log +5 -0
- package/README.md +384 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +23 -0
- package/src/api-client.ts +132 -0
- package/src/call-java-handler.ts +369 -0
- package/src/index.ts +13 -0
- package/tsconfig.json +9 -0
- package/tsconfig.tsbuildinfo +1 -0
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rxbenefits/server-utils",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"types": "./src/index.ts",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@opentelemetry/api": "^1.9.0",
|
|
9
|
+
"axios": "^1.7.9",
|
|
10
|
+
"formidable": "^3.5.2",
|
|
11
|
+
"next": "^16.1.1",
|
|
12
|
+
"node-fetch": "^3.3.2"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/formidable": "^3.4.5",
|
|
16
|
+
"@types/node": "^20"
|
|
17
|
+
},
|
|
18
|
+
"peerDependencies": {},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "echo 'Server utils uses source files directly'",
|
|
21
|
+
"lint": "eslint . --ext .ts,.tsx"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { context, trace } from '@opentelemetry/api';
|
|
2
|
+
import axios, {
|
|
3
|
+
type AxiosHeaderValue,
|
|
4
|
+
type AxiosInstance,
|
|
5
|
+
type AxiosRequestConfig,
|
|
6
|
+
type Method,
|
|
7
|
+
} from 'axios';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Configuration for creating a backend API client
|
|
11
|
+
*/
|
|
12
|
+
export interface ApiClientConfig {
|
|
13
|
+
/** Function to get WAF bypass token */
|
|
14
|
+
getWafBypassToken?: () => string | undefined;
|
|
15
|
+
/** Optional base URL for API requests */
|
|
16
|
+
baseURL?: string;
|
|
17
|
+
/** Enable tracing headers */
|
|
18
|
+
enableTracing?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates a configured axios instance with automatic header injection
|
|
23
|
+
* Automatically adds:
|
|
24
|
+
* - Authorization header (Bearer token)
|
|
25
|
+
* - WAF bypass header (X-RXB-Bypass)
|
|
26
|
+
* - Tracing headers (traceparent)
|
|
27
|
+
* - Application header
|
|
28
|
+
*/
|
|
29
|
+
export function createBackendApiClient(config: ApiClientConfig = {}): {
|
|
30
|
+
request: <T = unknown>(options: AxiosRequestConfig & { accessToken: string }) => Promise<T>;
|
|
31
|
+
getInstance: () => AxiosInstance;
|
|
32
|
+
} {
|
|
33
|
+
const { getWafBypassToken, baseURL, enableTracing = true } = config;
|
|
34
|
+
|
|
35
|
+
const instance = axios.create({
|
|
36
|
+
baseURL,
|
|
37
|
+
timeout: 30000,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Add request interceptor to inject headers
|
|
41
|
+
instance.interceptors.request.use((requestConfig) => {
|
|
42
|
+
const headers = requestConfig.headers || {};
|
|
43
|
+
|
|
44
|
+
// Get tracing context if enabled
|
|
45
|
+
if (enableTracing) {
|
|
46
|
+
const span = trace.getSpan(context.active());
|
|
47
|
+
const traceContext = span?.spanContext();
|
|
48
|
+
if (traceContext) {
|
|
49
|
+
headers['traceparent'] = `00-${traceContext.traceId}-${traceContext.spanId}-01`;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Add Application header
|
|
54
|
+
headers['Application'] = 'authorization';
|
|
55
|
+
|
|
56
|
+
requestConfig.headers = headers;
|
|
57
|
+
return requestConfig;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
/**
|
|
62
|
+
* Make an API request with automatic header injection
|
|
63
|
+
* @param options Axios request config with accessToken
|
|
64
|
+
* @returns Response data
|
|
65
|
+
*/
|
|
66
|
+
request: async <T = unknown>(
|
|
67
|
+
options: AxiosRequestConfig & { accessToken: string }
|
|
68
|
+
): Promise<T> => {
|
|
69
|
+
const { accessToken, ...axiosConfig } = options;
|
|
70
|
+
|
|
71
|
+
const headers: Record<string, AxiosHeaderValue> = {
|
|
72
|
+
...(axiosConfig.headers as Record<string, AxiosHeaderValue>),
|
|
73
|
+
Authorization: `Bearer ${accessToken}`,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Add WAF bypass header if available
|
|
77
|
+
const wafToken = getWafBypassToken?.();
|
|
78
|
+
if (wafToken) {
|
|
79
|
+
headers['X-RXB-Bypass'] = wafToken;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const response = await instance.request<T>({
|
|
83
|
+
...axiosConfig,
|
|
84
|
+
headers,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return response.data;
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
getInstance: () => instance,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Create headers for backend API requests with all required tokens
|
|
96
|
+
* Useful for fetch() or other HTTP clients
|
|
97
|
+
*/
|
|
98
|
+
export function createBackendHeaders(options: {
|
|
99
|
+
accessToken: string;
|
|
100
|
+
wafBypassToken?: string;
|
|
101
|
+
contentType?: string;
|
|
102
|
+
enableTracing?: boolean;
|
|
103
|
+
}): Record<string, string> {
|
|
104
|
+
const {
|
|
105
|
+
accessToken,
|
|
106
|
+
wafBypassToken,
|
|
107
|
+
contentType = 'application/json',
|
|
108
|
+
enableTracing = true,
|
|
109
|
+
} = options;
|
|
110
|
+
|
|
111
|
+
const headers: Record<string, string> = {
|
|
112
|
+
'Content-Type': contentType,
|
|
113
|
+
Authorization: `Bearer ${accessToken}`,
|
|
114
|
+
Application: 'authorization',
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// Add WAF bypass header if provided
|
|
118
|
+
if (wafBypassToken) {
|
|
119
|
+
headers['X-RXB-Bypass'] = wafBypassToken;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Add tracing if enabled
|
|
123
|
+
if (enableTracing) {
|
|
124
|
+
const span = trace.getSpan(context.active());
|
|
125
|
+
const traceContext = span?.spanContext();
|
|
126
|
+
if (traceContext) {
|
|
127
|
+
headers['traceparent'] = `00-${traceContext.traceId}-${traceContext.spanId}-01`;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return headers;
|
|
132
|
+
}
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
|
|
3
|
+
import { getLogger, getUser } from '@rxbenefits/utils/logger';
|
|
4
|
+
import { ProviderSetup } from '@rxbenefits/utils/tracer';
|
|
5
|
+
import { context, trace } from '@opentelemetry/api';
|
|
6
|
+
import axios, { type Method } from 'axios';
|
|
7
|
+
import * as formidable from 'formidable';
|
|
8
|
+
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
9
|
+
import fetch, { Blob, FormData } from 'node-fetch';
|
|
10
|
+
|
|
11
|
+
import { createBackendHeaders } from './api-client';
|
|
12
|
+
|
|
13
|
+
const logger = getLogger();
|
|
14
|
+
const shouldLogAuthDebug = Boolean(process.env.AWS_REGION || process.env.AUTH0_DEBUG);
|
|
15
|
+
|
|
16
|
+
const getCookieNames = (cookieHeader?: string | string[]) => {
|
|
17
|
+
const raw = Array.isArray(cookieHeader) ? cookieHeader.join(';') : cookieHeader;
|
|
18
|
+
if (!raw) return [];
|
|
19
|
+
return raw
|
|
20
|
+
.split(';')
|
|
21
|
+
.map((cookie) => cookie.trim().split('=')[0])
|
|
22
|
+
.filter(Boolean);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const buildAuthDebugInfo = (req: NextApiRequest) => {
|
|
26
|
+
const cookieNames = getCookieNames(req.headers.cookie);
|
|
27
|
+
const hasDefaultSession = cookieNames.some(
|
|
28
|
+
(name) => name === 'appSession' || name.startsWith('appSession.')
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
method: req.method,
|
|
33
|
+
url: req.url,
|
|
34
|
+
host: req.headers.host,
|
|
35
|
+
forwardedHost: req.headers['x-forwarded-host'],
|
|
36
|
+
forwardedProto: req.headers['x-forwarded-proto'],
|
|
37
|
+
referer: req.headers.referer,
|
|
38
|
+
cookieCount: cookieNames.length,
|
|
39
|
+
cookieNames,
|
|
40
|
+
hasAdminPortalSession: cookieNames.includes('admin_portal_session'),
|
|
41
|
+
hasAdminPortalTransaction: cookieNames.includes('admin_portal_auth_verification'),
|
|
42
|
+
hasDefaultSession,
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const buildUpstreamDebugInfo = (params: {
|
|
47
|
+
route: string;
|
|
48
|
+
targetUrl: string;
|
|
49
|
+
method: Method;
|
|
50
|
+
sendType: string;
|
|
51
|
+
error: unknown;
|
|
52
|
+
status?: number;
|
|
53
|
+
}) => {
|
|
54
|
+
const error = params.error as { name?: string; code?: string; message?: string } | undefined;
|
|
55
|
+
return {
|
|
56
|
+
route: params.route,
|
|
57
|
+
targetUrl: params.targetUrl,
|
|
58
|
+
method: params.method,
|
|
59
|
+
sendType: params.sendType,
|
|
60
|
+
status: params.status,
|
|
61
|
+
errorName: error?.name,
|
|
62
|
+
errorCode: error?.code,
|
|
63
|
+
errorMessage: error?.message,
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Service configuration for callJava handler
|
|
69
|
+
* Can be a static object or a function that returns config (for lazy evaluation)
|
|
70
|
+
*
|
|
71
|
+
* AI-Modified: Added function support for lazy evaluation of env vars
|
|
72
|
+
* In Lambda/SSR, module-level code runs before loadAuth0Config() populates process.env
|
|
73
|
+
* Modified-Date: 2026-01-21
|
|
74
|
+
*/
|
|
75
|
+
export type ServiceConfig = Record<string, string> | (() => Record<string, string>);
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Request body shape for callJava endpoint
|
|
79
|
+
*/
|
|
80
|
+
export type CallJavaRequestBody = {
|
|
81
|
+
sendType?: 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'blob';
|
|
82
|
+
method: Method;
|
|
83
|
+
data?: Record<string, any>;
|
|
84
|
+
route: string;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Configuration for auth functions
|
|
89
|
+
*/
|
|
90
|
+
export interface CallJavaAuthConfig {
|
|
91
|
+
getAccessToken: (req: NextApiRequest, res: NextApiResponse) => Promise<{ accessToken?: string }>;
|
|
92
|
+
withApiAuthRequired: (handler: any) => any;
|
|
93
|
+
getWafBypassToken: () => string | undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Creates a Next.js API route handler for the callJava pattern
|
|
98
|
+
* Handles proxying requests to backend services with automatic header injection
|
|
99
|
+
*
|
|
100
|
+
* @param services Service key -> base URL mapping
|
|
101
|
+
* @param auth Auth0 functions for authentication and WAF bypass
|
|
102
|
+
* @returns Next.js API route handler
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```ts
|
|
106
|
+
* import { createCallJavaHandler } from '@rxbenefits/server-utils';
|
|
107
|
+
* import { getAccessToken, withApiAuthRequired, getWafBypassToken } from '../../lib/auth0';
|
|
108
|
+
*
|
|
109
|
+
* const services = {
|
|
110
|
+
* 'fms-commission-service': process.env.FMSCOMMISSIONAPIURI || '',
|
|
111
|
+
* 'ben-admin': process.env.BENADMINAPIURI || '',
|
|
112
|
+
* };
|
|
113
|
+
*
|
|
114
|
+
* export const config = { api: { bodyParser: false } };
|
|
115
|
+
* export default createCallJavaHandler(services, {
|
|
116
|
+
* getAccessToken,
|
|
117
|
+
* withApiAuthRequired,
|
|
118
|
+
* getWafBypassToken,
|
|
119
|
+
* });
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
export function createCallJavaHandler(services: ServiceConfig, auth: CallJavaAuthConfig) {
|
|
123
|
+
return auth.withApiAuthRequired(async function callJava(
|
|
124
|
+
req: NextApiRequest,
|
|
125
|
+
res: NextApiResponse
|
|
126
|
+
) {
|
|
127
|
+
ProviderSetup();
|
|
128
|
+
const span = trace.getSpan(context.active());
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const { accessToken } = await auth.getAccessToken(req, res);
|
|
132
|
+
|
|
133
|
+
if (!accessToken) {
|
|
134
|
+
if (shouldLogAuthDebug) {
|
|
135
|
+
logger.error('[AuthDebug] /api/callJava missing access token', buildAuthDebugInfo(req));
|
|
136
|
+
}
|
|
137
|
+
logger.error('no access token');
|
|
138
|
+
res.status(401).end(JSON.stringify({ message: 'no access token' }));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const username = getUser(accessToken);
|
|
143
|
+
span?.setAttribute('app.user', username);
|
|
144
|
+
|
|
145
|
+
const reqData: any = await getFormData(req);
|
|
146
|
+
const formattedData = formatData(reqData);
|
|
147
|
+
span?.setAttribute('http.target_route', formattedData.fields.route);
|
|
148
|
+
span?.setAttribute('http.target_method', formattedData.fields.method);
|
|
149
|
+
|
|
150
|
+
// legacy shape: move `data` to `body`
|
|
151
|
+
formattedData.fields.body = formattedData.fields.data;
|
|
152
|
+
delete formattedData.data;
|
|
153
|
+
|
|
154
|
+
// AI-Modified: Resolve services if it's a function (lazy evaluation for Lambda)
|
|
155
|
+
const resolvedServices = typeof services === 'function' ? services() : services;
|
|
156
|
+
|
|
157
|
+
// Debug logging for Lambda environment
|
|
158
|
+
if (shouldLogAuthDebug) {
|
|
159
|
+
const route = formattedData.fields?.route || '';
|
|
160
|
+
const serviceKey = Object.keys(resolvedServices).find(key => route.includes(key));
|
|
161
|
+
const serviceUrl = serviceKey ? resolvedServices[serviceKey] : 'unknown';
|
|
162
|
+
logger.info('[CallJava] Service resolution', {
|
|
163
|
+
route,
|
|
164
|
+
serviceKey: serviceKey || 'none',
|
|
165
|
+
serviceUrl: serviceUrl ? `${serviceUrl.substring(0, 50)}...` : 'EMPTY',
|
|
166
|
+
hasEnvVars: {
|
|
167
|
+
TASKSERVICEAPIURI: !!process.env.TASKSERVICEAPIURI,
|
|
168
|
+
ELIGIBILITYIMPORTAPIURI: !!process.env.ELIGIBILITYIMPORTAPIURI,
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const response: any = await axiosRequest(
|
|
174
|
+
accessToken,
|
|
175
|
+
auth.getWafBypassToken(),
|
|
176
|
+
formattedData,
|
|
177
|
+
resolvedServices
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
if (response?.headers?.['content-disposition']) {
|
|
181
|
+
res.setHeader('content-disposition', response.headers['content-disposition']);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// If axios response, send only the upstream payload
|
|
185
|
+
if (response && typeof response === 'object' && 'data' in response) {
|
|
186
|
+
res.send((response as { data: unknown }).data);
|
|
187
|
+
} else if (
|
|
188
|
+
response &&
|
|
189
|
+
typeof response === 'object' &&
|
|
190
|
+
typeof (response as any).pipe === 'function'
|
|
191
|
+
) {
|
|
192
|
+
// node-fetch stream body (multipart)
|
|
193
|
+
(response as any).pipe(res);
|
|
194
|
+
} else {
|
|
195
|
+
res.status(500).send({ message: 'Upstream request failed' });
|
|
196
|
+
}
|
|
197
|
+
span?.end();
|
|
198
|
+
} catch (error: any) {
|
|
199
|
+
if (shouldLogAuthDebug) {
|
|
200
|
+
logger.error('[AuthDebug] /api/callJava error', {
|
|
201
|
+
error: error instanceof Error ? error.message : String(error),
|
|
202
|
+
...buildAuthDebugInfo(req),
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
logger.error(error);
|
|
206
|
+
const status: number = error?.response?.status || 500;
|
|
207
|
+
// Avoid leaking upstream error details to the client
|
|
208
|
+
res.status(status).send({ message: 'Request failed' });
|
|
209
|
+
span?.end();
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const formatData = (data: any) => {
|
|
215
|
+
if (typeof data.fields.method === 'string') return data;
|
|
216
|
+
const newData = data;
|
|
217
|
+
const formattedData: Record<string, string> = {};
|
|
218
|
+
const keys = Object.keys(newData.fields);
|
|
219
|
+
for (let i = 0; i < keys.length; i++) {
|
|
220
|
+
const val = newData.fields[keys[i]][0];
|
|
221
|
+
formattedData[keys[i]] = val;
|
|
222
|
+
}
|
|
223
|
+
newData.fields = formattedData;
|
|
224
|
+
return newData;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const getFormData = async (req: any) => {
|
|
228
|
+
const data = await new Promise((resolve, reject) => {
|
|
229
|
+
const form = new formidable.Formidable();
|
|
230
|
+
|
|
231
|
+
form.parse(req, (err, fields, files) => {
|
|
232
|
+
if (err) reject({ err });
|
|
233
|
+
resolve({ err, fields, files });
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
return data;
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const findService = (url: string, services: Record<string, string>) => {
|
|
240
|
+
let newUrl = url;
|
|
241
|
+
const keys = Object.keys(services);
|
|
242
|
+
|
|
243
|
+
for (let i = 0; i < keys.length; i++) {
|
|
244
|
+
const key = keys[i];
|
|
245
|
+
const replacement = services[key];
|
|
246
|
+
if (replacement && newUrl.includes(key)) {
|
|
247
|
+
newUrl = newUrl.replace(key, replacement);
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return newUrl;
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const axiosRequest = async (
|
|
255
|
+
accessToken: string,
|
|
256
|
+
wafBypassToken: string | undefined,
|
|
257
|
+
requestData: any,
|
|
258
|
+
services: Record<string, string>
|
|
259
|
+
) => {
|
|
260
|
+
const {
|
|
261
|
+
route: url,
|
|
262
|
+
method = 'GET',
|
|
263
|
+
body,
|
|
264
|
+
sendType = 'application/json',
|
|
265
|
+
...rest
|
|
266
|
+
} = requestData.fields;
|
|
267
|
+
|
|
268
|
+
let sendBody = body;
|
|
269
|
+
|
|
270
|
+
// Create headers with WAF bypass token
|
|
271
|
+
const headers = createBackendHeaders({
|
|
272
|
+
accessToken,
|
|
273
|
+
wafBypassToken,
|
|
274
|
+
contentType: sendType,
|
|
275
|
+
enableTracing: true,
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
if (sendType === 'blob') {
|
|
279
|
+
(headers as any).responseType = 'arraybuffer';
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (sendType === 'multipart/form-data') {
|
|
283
|
+
sendBody = { ...rest };
|
|
284
|
+
const form = new FormData();
|
|
285
|
+
const keys = Object.keys(sendBody);
|
|
286
|
+
const fileKeys = Object.keys(requestData.files || {});
|
|
287
|
+
|
|
288
|
+
for (let i = 0; i < keys.length; i++) {
|
|
289
|
+
form.append(keys[i], sendBody[keys[i]]);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (fileKeys.length) {
|
|
293
|
+
for (let i = 0; i < fileKeys.length; i++) {
|
|
294
|
+
const file = requestData.files[fileKeys[i]]?.[0];
|
|
295
|
+
if (!file?.filepath) continue;
|
|
296
|
+
const buffer = fs.readFileSync(file.filepath);
|
|
297
|
+
const blob = new Blob([new Uint8Array(buffer)]);
|
|
298
|
+
form.append(fileKeys[i], blob, file?.originalFilename);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const targetUrl: string = findService(url, services) || '';
|
|
303
|
+
try {
|
|
304
|
+
const response = await fetch(targetUrl, {
|
|
305
|
+
method,
|
|
306
|
+
body: form,
|
|
307
|
+
headers,
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
if (!response.ok && shouldLogAuthDebug) {
|
|
311
|
+
logger.error(
|
|
312
|
+
buildUpstreamDebugInfo({
|
|
313
|
+
route: url,
|
|
314
|
+
targetUrl,
|
|
315
|
+
method,
|
|
316
|
+
sendType,
|
|
317
|
+
status: response.status,
|
|
318
|
+
error: new Error(response.statusText),
|
|
319
|
+
}),
|
|
320
|
+
'[ApiDebug] /api/callJava upstream fetch response'
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// For uploads/downloads, return the raw stream body
|
|
325
|
+
return response.body;
|
|
326
|
+
} catch (error) {
|
|
327
|
+
if (shouldLogAuthDebug) {
|
|
328
|
+
logger.error(
|
|
329
|
+
buildUpstreamDebugInfo({
|
|
330
|
+
route: url,
|
|
331
|
+
targetUrl,
|
|
332
|
+
method,
|
|
333
|
+
sendType,
|
|
334
|
+
error,
|
|
335
|
+
}),
|
|
336
|
+
'[ApiDebug] /api/callJava upstream fetch failed'
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
throw error;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const targetUrl: string = findService(url, services) || '';
|
|
344
|
+
|
|
345
|
+
try {
|
|
346
|
+
return await axios.request({
|
|
347
|
+
url: targetUrl,
|
|
348
|
+
method,
|
|
349
|
+
data: sendBody,
|
|
350
|
+
headers,
|
|
351
|
+
responseType: (headers as any).responseType,
|
|
352
|
+
});
|
|
353
|
+
} catch (error) {
|
|
354
|
+
if (shouldLogAuthDebug) {
|
|
355
|
+
logger.error(
|
|
356
|
+
buildUpstreamDebugInfo({
|
|
357
|
+
route: url,
|
|
358
|
+
targetUrl,
|
|
359
|
+
method,
|
|
360
|
+
sendType,
|
|
361
|
+
status: (error as any)?.response?.status,
|
|
362
|
+
error,
|
|
363
|
+
}),
|
|
364
|
+
'[ApiDebug] /api/callJava upstream request failed'
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
throw error;
|
|
368
|
+
}
|
|
369
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rxbenefits/server-utils
|
|
3
|
+
*
|
|
4
|
+
* Shared server-side utilities for Next.js API routes
|
|
5
|
+
* Provides centralized handling of:
|
|
6
|
+
* - WAF bypass headers
|
|
7
|
+
* - Authentication headers
|
|
8
|
+
* - Tracing headers
|
|
9
|
+
* - Backend API proxying
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export * from './api-client';
|
|
13
|
+
export * from './call-java-handler';
|