@zintrust/redis-rpc 2.4.3 → 2.4.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.
Files changed (2) hide show
  1. package/dist/server.js +158 -22
  2. package/package.json +1 -1
package/dist/server.js CHANGED
@@ -18,33 +18,169 @@ const json = (response, status, payload) => {
18
18
  });
19
19
  response.end(JSON.stringify(payload));
20
20
  };
21
+ const getHeaderSecret = (request) => {
22
+ const raw = request.headers['x-redis-rpc-secret'];
23
+ if (Array.isArray(raw)) {
24
+ return raw[0] ?? '';
25
+ }
26
+ return typeof raw === 'string' ? raw : '';
27
+ };
28
+ const previewSecret = (value) => value.slice(0, 5);
29
+ const logStep = (step, details) => {
30
+ Logger.debug(`[redis-rpc][server] ${step}`, details);
31
+ };
32
+ const getRequestContext = (request, settings) => {
33
+ const method = request.method ?? '';
34
+ const url = new URL(request.url || '/', `http://${request.headers.host || 'localhost'}`);
35
+ const headerSecret = getHeaderSecret(request);
36
+ const settingsSecret = settings.secret || '';
37
+ return {
38
+ method,
39
+ url,
40
+ headerSecret,
41
+ settingsSecret,
42
+ };
43
+ };
44
+ const logRequestReceived = (context) => {
45
+ logStep('request.received', {
46
+ method: context.method,
47
+ path: context.url.pathname,
48
+ host: context.url.host,
49
+ headerSecretPreview: previewSecret(context.headerSecret),
50
+ settingsSecretPreview: previewSecret(context.settingsSecret),
51
+ });
52
+ };
53
+ const isHealthRoute = (context) => context.method === 'GET' && context.url.pathname === '/health';
54
+ const isRpcRoute = (context) => context.method === 'POST' && context.url.pathname === '/rpc';
55
+ const logHealthRoute = (prefix, path) => {
56
+ logStep('route.health', {
57
+ path,
58
+ prefix,
59
+ });
60
+ };
61
+ const logRouteCheck = (context) => {
62
+ logStep('route.check', {
63
+ method: context.method,
64
+ path: context.url.pathname,
65
+ isRpcRoute: isRpcRoute(context),
66
+ });
67
+ };
68
+ const logRouteNotFound = (context) => {
69
+ logStep('route.notFound', {
70
+ method: context.method,
71
+ path: context.url.pathname,
72
+ });
73
+ };
74
+ const validateRequestSecret = (context) => {
75
+ logStep('secret.validate.start', {
76
+ headerSecretPreview: previewSecret(context.headerSecret),
77
+ settingsSecretPreview: previewSecret(context.settingsSecret),
78
+ });
79
+ const secretMatches = context.settingsSecret !== '' && context.headerSecret === context.settingsSecret;
80
+ logStep('secret.validate.result', {
81
+ headerSecretPreview: previewSecret(context.headerSecret),
82
+ settingsSecretPreview: previewSecret(context.settingsSecret),
83
+ secretMatches,
84
+ });
85
+ return secretMatches;
86
+ };
87
+ const logSecretValidationFailure = (context) => {
88
+ logStep('secret.validate.failed', {
89
+ headerSecretPreview: previewSecret(context.headerSecret),
90
+ settingsSecretPreview: previewSecret(context.settingsSecret),
91
+ });
92
+ };
93
+ const readRpcBody = async (request) => {
94
+ logStep('body.read.start', {});
95
+ const bodyText = await readBody(request);
96
+ logStep('body.read.complete', {
97
+ length: bodyText.length,
98
+ isEmpty: bodyText.trim().length === 0,
99
+ });
100
+ try {
101
+ const body = (bodyText.trim().length === 0 ? {} : JSON.parse(bodyText));
102
+ logStep('body.parse.complete', {
103
+ requestId: body.requestId ?? null,
104
+ service: String(body.service || ''),
105
+ method: String(body.method || ''),
106
+ hasPayload: body.payload !== undefined,
107
+ });
108
+ return body;
109
+ }
110
+ catch (error) {
111
+ logStep('body.parse.failed', {
112
+ error: error instanceof Error ? error.message : String(error),
113
+ });
114
+ throw error;
115
+ }
116
+ };
117
+ const dispatchRpcRequest = async (backend, body) => {
118
+ logStep('dispatch.start', {
119
+ requestId: body.requestId ?? null,
120
+ service: String(body.service || ''),
121
+ method: String(body.method || ''),
122
+ });
123
+ const result = await backend.dispatch(String(body.service || ''), String(body.method || ''), body.payload ?? {});
124
+ logStep('dispatch.complete', {
125
+ requestId: body.requestId ?? null,
126
+ service: String(body.service || ''),
127
+ method: String(body.method || ''),
128
+ });
129
+ return result;
130
+ };
131
+ const sendRpcSuccess = (response, requestId, result) => {
132
+ json(response, 200, { ok: true, requestId, result, error: null });
133
+ logStep('response.sent', {
134
+ status: 200,
135
+ requestId,
136
+ });
137
+ };
138
+ const handleRequestError = (response, error) => {
139
+ const payload = toErrorPayload(error);
140
+ logStep('request.error', {
141
+ status: payload.status,
142
+ message: payload.body && typeof payload.body === 'object' && 'message' in payload.body
143
+ ? String(payload.body.message ?? '')
144
+ : '',
145
+ });
146
+ json(response, payload.status, payload.body);
147
+ };
148
+ const handleRpcRequest = async (request, response, context, backend) => {
149
+ logRouteCheck(context);
150
+ if (!isRpcRoute(context)) {
151
+ logRouteNotFound(context);
152
+ throw createRpcNotFoundError('Unknown Redis RPC route');
153
+ }
154
+ if (!validateRequestSecret(context)) {
155
+ logSecretValidationFailure(context);
156
+ throw createRpcUnauthorizedError('Invalid Redis RPC secret');
157
+ }
158
+ const body = await readRpcBody(request);
159
+ const result = await dispatchRpcRequest(backend, body);
160
+ sendRpcSuccess(response, body.requestId ?? null, result);
161
+ };
162
+ const handleHealthRequest = (response, backend) => {
163
+ logHealthRoute(backend.prefix, '/health');
164
+ json(response, 200, { ok: true, service: 'redis-rpc', prefix: backend.prefix });
165
+ };
166
+ const handleIncomingRequest = async (request, response, settings, backend) => {
167
+ const context = getRequestContext(request, settings);
168
+ logRequestReceived(context);
169
+ if (isHealthRoute(context)) {
170
+ handleHealthRequest(response, backend);
171
+ return;
172
+ }
173
+ await handleRpcRequest(request, response, context, backend);
174
+ };
21
175
  export const createRedisRpcServer = (options = {}) => {
22
176
  const settings = { ...rpcServerOptions(), ...options };
23
177
  const backend = isObject(options.backend)
24
178
  ? options.backend
25
179
  : createRedisRpcBackend(settings);
26
- const server = http.createServer(async (request, response) => {
27
- try {
28
- const url = new URL(request.url || '/', `http://${request.headers.host || 'localhost'}`);
29
- if (request.method === 'GET' && url.pathname === '/health') {
30
- json(response, 200, { ok: true, service: 'redis-rpc', prefix: backend.prefix });
31
- return;
32
- }
33
- if (request.method !== 'POST' || url.pathname !== '/rpc') {
34
- throw createRpcNotFoundError('Unknown Redis RPC route');
35
- }
36
- if (!settings.secret || request.headers['x-redis-rpc-secret'] !== settings.secret) {
37
- throw createRpcUnauthorizedError('Invalid Redis RPC secret');
38
- }
39
- const bodyText = await readBody(request);
40
- const body = (bodyText.trim().length === 0 ? {} : JSON.parse(bodyText));
41
- const result = await backend.dispatch(String(body.service || ''), String(body.method || ''), body.payload ?? {});
42
- json(response, 200, { ok: true, requestId: body.requestId ?? null, result, error: null });
43
- }
44
- catch (error) {
45
- const payload = toErrorPayload(error);
46
- json(response, payload.status, payload.body);
47
- }
180
+ const server = http.createServer((request, response) => {
181
+ void handleIncomingRequest(request, response, settings, backend).catch((error) => {
182
+ handleRequestError(response, error);
183
+ });
48
184
  });
49
185
  return { server, backend, settings };
50
186
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zintrust/redis-rpc",
3
- "version": "2.4.3",
3
+ "version": "2.4.4",
4
4
  "description": "Redis RPC backend for BullMQ queue operations in ZinTrust.",
5
5
  "private": false,
6
6
  "type": "module",