@private.me/xbind 1.2.0 → 1.2.2
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 +13 -24
- package/dist-standalone/_deps/ux-helpers/cjs/errors.d.ts +4 -0
- package/dist-standalone/_deps/ux-helpers/cjs/errors.d.ts.map +1 -1
- package/dist-standalone/_deps/ux-helpers/cjs/errors.js +1 -1
- package/dist-standalone/_deps/ux-helpers/cjs/errors.js.map +1 -1
- package/dist-standalone/_deps/ux-helpers/cjs/pagination.js +1 -83
- package/dist-standalone/_deps/ux-helpers/cjs/progress.js +1 -143
- package/dist-standalone/_deps/ux-helpers/cjs/search.js +1 -119
- package/dist-standalone/_deps/ux-helpers/cjs/types.js +1 -8
- package/dist-standalone/_deps/ux-helpers/errors.d.ts +4 -0
- package/dist-standalone/_deps/ux-helpers/errors.d.ts.map +1 -1
- package/dist-standalone/_deps/ux-helpers/errors.js +1 -253
- package/dist-standalone/_deps/ux-helpers/errors.js.map +1 -1
- package/dist-standalone/_deps/ux-helpers/index.js +1 -16
- package/dist-standalone/_deps/ux-helpers/pagination.js +1 -79
- package/dist-standalone/_deps/ux-helpers/progress.js +1 -138
- package/dist-standalone/_deps/ux-helpers/search.js +1 -116
- package/dist-standalone/_deps/ux-helpers/types.js +1 -7
- package/dist-standalone/agent.d.ts +9 -4
- package/dist-standalone/agent.js +20 -4
- package/dist-standalone/cjs/agent.js +20 -4
- package/dist-standalone/cjs/correlation-id.js +339 -0
- package/dist-standalone/cjs/http-status-map.js +571 -0
- package/dist-standalone/cjs/index.js +14 -1
- package/dist-standalone/cjs/types/error-response.js +56 -0
- package/dist-standalone/correlation-id.d.ts +222 -0
- package/dist-standalone/correlation-id.js +326 -0
- package/dist-standalone/http-status-map.d.ts +136 -0
- package/dist-standalone/http-status-map.js +561 -0
- package/dist-standalone/index.d.ts +2 -0
- package/dist-standalone/index.js +1 -0
- package/package.json +2 -2
- package/share1.dat +0 -0
|
@@ -282,13 +282,18 @@ export declare class Agent {
|
|
|
282
282
|
* Create an Agent from a deterministic 32-byte seed.
|
|
283
283
|
*
|
|
284
284
|
* Uses HKDF-SHA256 to derive Ed25519 + X25519 keys from the seed.
|
|
285
|
-
* The same seed always produces the same DID and keys.
|
|
286
|
-
* registration — the caller is responsible for registering externally.
|
|
285
|
+
* The same seed always produces the same DID and keys.
|
|
287
286
|
*
|
|
288
|
-
*
|
|
287
|
+
* **Just-in-Time Registration (JITR):** Automatically registers identity
|
|
288
|
+
* with the trust registry on first use. Follows industry standards (AWS IoT
|
|
289
|
+
* JITR, OAuth DCR, MCP 2025 spec) for zero-config onboarding. Idempotent:
|
|
290
|
+
* safe to call multiple times (ignores ALREADY_REGISTERED errors).
|
|
291
|
+
*
|
|
292
|
+
* Ideal for IoT, servers, AI agents: zero-config deployment with automatic
|
|
293
|
+
* registration on first connection.
|
|
289
294
|
*
|
|
290
295
|
* @param seed - Exactly 32 bytes of high-entropy key material.
|
|
291
|
-
* @param opts - Agent options (registry, transport, etc.).
|
|
296
|
+
* @param opts - Agent options (registry, transport, scopes, etc.).
|
|
292
297
|
* @returns Agent instance or error.
|
|
293
298
|
*/
|
|
294
299
|
static fromSeed(seed: Uint8Array, opts: Omit<AgentCreateOptions, 'name'> & {
|
package/dist-standalone/agent.js
CHANGED
|
@@ -197,19 +197,35 @@ export class Agent {
|
|
|
197
197
|
* Create an Agent from a deterministic 32-byte seed.
|
|
198
198
|
*
|
|
199
199
|
* Uses HKDF-SHA256 to derive Ed25519 + X25519 keys from the seed.
|
|
200
|
-
* The same seed always produces the same DID and keys.
|
|
201
|
-
* registration — the caller is responsible for registering externally.
|
|
200
|
+
* The same seed always produces the same DID and keys.
|
|
202
201
|
*
|
|
203
|
-
*
|
|
202
|
+
* **Just-in-Time Registration (JITR):** Automatically registers identity
|
|
203
|
+
* with the trust registry on first use. Follows industry standards (AWS IoT
|
|
204
|
+
* JITR, OAuth DCR, MCP 2025 spec) for zero-config onboarding. Idempotent:
|
|
205
|
+
* safe to call multiple times (ignores ALREADY_REGISTERED errors).
|
|
206
|
+
*
|
|
207
|
+
* Ideal for IoT, servers, AI agents: zero-config deployment with automatic
|
|
208
|
+
* registration on first connection.
|
|
204
209
|
*
|
|
205
210
|
* @param seed - Exactly 32 bytes of high-entropy key material.
|
|
206
|
-
* @param opts - Agent options (registry, transport, etc.).
|
|
211
|
+
* @param opts - Agent options (registry, transport, scopes, etc.).
|
|
207
212
|
* @returns Agent instance or error.
|
|
208
213
|
*/
|
|
209
214
|
static async fromSeed(seed, opts) {
|
|
210
215
|
const idResult = await identityFromSeed(seed, { postQuantumSig: opts.postQuantumSig });
|
|
211
216
|
if (!idResult.ok)
|
|
212
217
|
return err('IDENTITY_FAILED:KEYGEN');
|
|
218
|
+
// JITR: Auto-register with trust registry (zero-config onboarding)
|
|
219
|
+
// Includes all keys: Ed25519 (signing), X25519 (encryption), ML-KEM, ML-DSA
|
|
220
|
+
const regResult = await opts.registry.register(idResult.value.did, idResult.value.rawPublicKey, opts.name ?? idResult.value.did, opts.scopes, idResult.value.rawX25519PublicKey, idResult.value.mlKemPublicKey, idResult.value.mlDsaPublicKey, opts.xchange ?? false);
|
|
221
|
+
// Idempotent: Ignore ALREADY_REGISTERED (agent may have registered before)
|
|
222
|
+
// Fail on other errors (network issues, invalid data, etc.)
|
|
223
|
+
if (!regResult.ok && regResult.error !== 'ALREADY_REGISTERED') {
|
|
224
|
+
const sub = regResult.error === 'NETWORK_ERROR'
|
|
225
|
+
? 'REGISTRATION_FAILED:NETWORK_ERROR'
|
|
226
|
+
: 'REGISTRATION_FAILED';
|
|
227
|
+
return err(sub);
|
|
228
|
+
}
|
|
213
229
|
const nonceStore = opts.nonceStore ?? new MemoryNonceStore();
|
|
214
230
|
const timestampWindowMs = opts.timestampWindowMs ?? TIMESTAMP_WINDOW_MS;
|
|
215
231
|
const transports = Array.isArray(opts.transport) ? opts.transport : [opts.transport];
|
|
@@ -235,19 +235,35 @@ class Agent {
|
|
|
235
235
|
* Create an Agent from a deterministic 32-byte seed.
|
|
236
236
|
*
|
|
237
237
|
* Uses HKDF-SHA256 to derive Ed25519 + X25519 keys from the seed.
|
|
238
|
-
* The same seed always produces the same DID and keys.
|
|
239
|
-
* registration — the caller is responsible for registering externally.
|
|
238
|
+
* The same seed always produces the same DID and keys.
|
|
240
239
|
*
|
|
241
|
-
*
|
|
240
|
+
* **Just-in-Time Registration (JITR):** Automatically registers identity
|
|
241
|
+
* with the trust registry on first use. Follows industry standards (AWS IoT
|
|
242
|
+
* JITR, OAuth DCR, MCP 2025 spec) for zero-config onboarding. Idempotent:
|
|
243
|
+
* safe to call multiple times (ignores ALREADY_REGISTERED errors).
|
|
244
|
+
*
|
|
245
|
+
* Ideal for IoT, servers, AI agents: zero-config deployment with automatic
|
|
246
|
+
* registration on first connection.
|
|
242
247
|
*
|
|
243
248
|
* @param seed - Exactly 32 bytes of high-entropy key material.
|
|
244
|
-
* @param opts - Agent options (registry, transport, etc.).
|
|
249
|
+
* @param opts - Agent options (registry, transport, scopes, etc.).
|
|
245
250
|
* @returns Agent instance or error.
|
|
246
251
|
*/
|
|
247
252
|
static async fromSeed(seed, opts) {
|
|
248
253
|
const idResult = await (0, identity_js_1.identityFromSeed)(seed, { postQuantumSig: opts.postQuantumSig });
|
|
249
254
|
if (!idResult.ok)
|
|
250
255
|
return (0, shared_1.err)('IDENTITY_FAILED:KEYGEN');
|
|
256
|
+
// JITR: Auto-register with trust registry (zero-config onboarding)
|
|
257
|
+
// Includes all keys: Ed25519 (signing), X25519 (encryption), ML-KEM, ML-DSA
|
|
258
|
+
const regResult = await opts.registry.register(idResult.value.did, idResult.value.rawPublicKey, opts.name ?? idResult.value.did, opts.scopes, idResult.value.rawX25519PublicKey, idResult.value.mlKemPublicKey, idResult.value.mlDsaPublicKey, opts.xchange ?? false);
|
|
259
|
+
// Idempotent: Ignore ALREADY_REGISTERED (agent may have registered before)
|
|
260
|
+
// Fail on other errors (network issues, invalid data, etc.)
|
|
261
|
+
if (!regResult.ok && regResult.error !== 'ALREADY_REGISTERED') {
|
|
262
|
+
const sub = regResult.error === 'NETWORK_ERROR'
|
|
263
|
+
? 'REGISTRATION_FAILED:NETWORK_ERROR'
|
|
264
|
+
: 'REGISTRATION_FAILED';
|
|
265
|
+
return (0, shared_1.err)(sub);
|
|
266
|
+
}
|
|
251
267
|
const nonceStore = opts.nonceStore ?? new nonce_store_js_1.MemoryNonceStore();
|
|
252
268
|
const timestampWindowMs = opts.timestampWindowMs ?? TIMESTAMP_WINDOW_MS;
|
|
253
269
|
const transports = Array.isArray(opts.transport) ? opts.transport : [opts.transport];
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module correlation-id
|
|
4
|
+
* Client-side correlation ID utilities for request tracking
|
|
5
|
+
*
|
|
6
|
+
* Correlation IDs enable distributed tracing across xBind agent operations,
|
|
7
|
+
* making it easier to debug issues, track requests across microservices,
|
|
8
|
+
* and correlate logs between client and server.
|
|
9
|
+
*
|
|
10
|
+
* Format: `req_{timestamp}_{random}`
|
|
11
|
+
* Example: `req_1716234567890_a3f5c9d2`
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { generateCorrelationId, attachCorrelationId } from '@private.me/xbind';
|
|
16
|
+
*
|
|
17
|
+
* // Generate a new correlation ID
|
|
18
|
+
* const id = generateCorrelationId();
|
|
19
|
+
*
|
|
20
|
+
* // Attach to request headers
|
|
21
|
+
* const headers = attachCorrelationId(new Headers(), id);
|
|
22
|
+
*
|
|
23
|
+
* // Validate format
|
|
24
|
+
* if (validateCorrelationId(id)) {
|
|
25
|
+
* console.log('Valid correlation ID');
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
+
exports.CORRELATION_ID_ALIASES = exports.CORRELATION_ID_HEADER = void 0;
|
|
31
|
+
exports.generateCorrelationId = generateCorrelationId;
|
|
32
|
+
exports.validateCorrelationId = validateCorrelationId;
|
|
33
|
+
exports.parseCorrelationId = parseCorrelationId;
|
|
34
|
+
exports.attachCorrelationId = attachCorrelationId;
|
|
35
|
+
exports.extractCorrelationId = extractCorrelationId;
|
|
36
|
+
exports.getOrCreateCorrelationId = getOrCreateCorrelationId;
|
|
37
|
+
exports.createCorrelationIdFromTimestamp = createCorrelationIdFromTimestamp;
|
|
38
|
+
exports.getCorrelationIdAge = getCorrelationIdAge;
|
|
39
|
+
exports.isCorrelationIdExpired = isCorrelationIdExpired;
|
|
40
|
+
exports.correlationIdMiddleware = correlationIdMiddleware;
|
|
41
|
+
/**
|
|
42
|
+
* Standard header name for correlation ID
|
|
43
|
+
*/
|
|
44
|
+
exports.CORRELATION_ID_HEADER = 'X-Correlation-ID';
|
|
45
|
+
/**
|
|
46
|
+
* Alternative header names for compatibility
|
|
47
|
+
*/
|
|
48
|
+
exports.CORRELATION_ID_ALIASES = [
|
|
49
|
+
'X-Request-ID',
|
|
50
|
+
'X-Trace-ID',
|
|
51
|
+
'X-Transaction-ID',
|
|
52
|
+
];
|
|
53
|
+
/**
|
|
54
|
+
* Regular expression for validating correlation ID format
|
|
55
|
+
*/
|
|
56
|
+
const CORRELATION_ID_PATTERN = /^req_\d{13}_[a-f0-9]{8}$/;
|
|
57
|
+
/**
|
|
58
|
+
* Generate a new correlation ID
|
|
59
|
+
*
|
|
60
|
+
* Format: `req_{timestamp}_{random}`
|
|
61
|
+
* - `req`: Static prefix for identification
|
|
62
|
+
* - `timestamp`: Unix timestamp in milliseconds (13 digits)
|
|
63
|
+
* - `random`: 8-character hex string for uniqueness
|
|
64
|
+
*
|
|
65
|
+
* @returns New correlation ID string
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```typescript
|
|
69
|
+
* const id = generateCorrelationId();
|
|
70
|
+
* // => "req_1716234567890_a3f5c9d2"
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
function generateCorrelationId() {
|
|
74
|
+
const timestamp = Date.now();
|
|
75
|
+
const random = generateRandomHex(8);
|
|
76
|
+
return `req_${timestamp}_${random}`;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Validate correlation ID format
|
|
80
|
+
*
|
|
81
|
+
* Checks if the provided string matches the expected correlation ID format.
|
|
82
|
+
*
|
|
83
|
+
* @param id - String to validate
|
|
84
|
+
* @returns True if valid, false otherwise
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```typescript
|
|
88
|
+
* validateCorrelationId('req_1716234567890_a3f5c9d2'); // => true
|
|
89
|
+
* validateCorrelationId('invalid'); // => false
|
|
90
|
+
* validateCorrelationId('req_123_abc'); // => false (wrong lengths)
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
function validateCorrelationId(id) {
|
|
94
|
+
if (typeof id !== 'string')
|
|
95
|
+
return false;
|
|
96
|
+
return CORRELATION_ID_PATTERN.test(id);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Parse correlation ID into components
|
|
100
|
+
*
|
|
101
|
+
* Extracts the timestamp and random components from a correlation ID.
|
|
102
|
+
*
|
|
103
|
+
* @param id - Correlation ID to parse
|
|
104
|
+
* @returns Parsed components or null if invalid
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```typescript
|
|
108
|
+
* const spec = parseCorrelationId('req_1716234567890_a3f5c9d2');
|
|
109
|
+
* // => { prefix: 'req', timestamp: 1716234567890, random: 'a3f5c9d2' }
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
function parseCorrelationId(id) {
|
|
113
|
+
if (!validateCorrelationId(id))
|
|
114
|
+
return null;
|
|
115
|
+
const parts = id.split('_');
|
|
116
|
+
return {
|
|
117
|
+
prefix: parts[0] ?? 'req',
|
|
118
|
+
timestamp: parseInt(parts[1] ?? '0', 10),
|
|
119
|
+
random: parts[2] ?? '',
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Attach correlation ID to request headers
|
|
124
|
+
*
|
|
125
|
+
* Adds the correlation ID to the X-Correlation-ID header.
|
|
126
|
+
* If no ID is provided, generates a new one.
|
|
127
|
+
* Compatible with both Headers API and plain objects.
|
|
128
|
+
*
|
|
129
|
+
* @param headers - Headers object or plain object
|
|
130
|
+
* @param id - Correlation ID (generates new if not provided)
|
|
131
|
+
* @returns Updated headers object
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```typescript
|
|
135
|
+
* // With Headers API
|
|
136
|
+
* const headers = new Headers();
|
|
137
|
+
* attachCorrelationId(headers);
|
|
138
|
+
*
|
|
139
|
+
* // With plain object
|
|
140
|
+
* const headers = { 'Content-Type': 'application/json' };
|
|
141
|
+
* attachCorrelationId(headers, 'req_1716234567890_a3f5c9d2');
|
|
142
|
+
*
|
|
143
|
+
* // With existing correlation ID
|
|
144
|
+
* const id = generateCorrelationId();
|
|
145
|
+
* attachCorrelationId(headers, id);
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
function attachCorrelationId(headers, id) {
|
|
149
|
+
const correlationId = id ?? generateCorrelationId();
|
|
150
|
+
if (!validateCorrelationId(correlationId)) {
|
|
151
|
+
throw new Error(`Invalid correlation ID format: ${correlationId}. Expected format: req_{timestamp}_{random}`);
|
|
152
|
+
}
|
|
153
|
+
if (headers instanceof Headers) {
|
|
154
|
+
headers.set(exports.CORRELATION_ID_HEADER, correlationId);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
headers[exports.CORRELATION_ID_HEADER] = correlationId;
|
|
158
|
+
}
|
|
159
|
+
return headers;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Extract correlation ID from request headers
|
|
163
|
+
*
|
|
164
|
+
* Checks the standard header and common aliases.
|
|
165
|
+
*
|
|
166
|
+
* @param headers - Headers object or plain object
|
|
167
|
+
* @returns Correlation ID if found, undefined otherwise
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* ```typescript
|
|
171
|
+
* const headers = new Headers({
|
|
172
|
+
* 'X-Correlation-ID': 'req_1716234567890_a3f5c9d2'
|
|
173
|
+
* });
|
|
174
|
+
* const id = extractCorrelationId(headers);
|
|
175
|
+
* // => 'req_1716234567890_a3f5c9d2'
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
function extractCorrelationId(headers) {
|
|
179
|
+
// Check primary header
|
|
180
|
+
const primary = headers instanceof Headers
|
|
181
|
+
? headers.get(exports.CORRELATION_ID_HEADER)
|
|
182
|
+
: headers[exports.CORRELATION_ID_HEADER];
|
|
183
|
+
if (primary && validateCorrelationId(primary)) {
|
|
184
|
+
return primary;
|
|
185
|
+
}
|
|
186
|
+
// Check aliases
|
|
187
|
+
for (const alias of exports.CORRELATION_ID_ALIASES) {
|
|
188
|
+
const value = headers instanceof Headers ? headers.get(alias) : headers[alias];
|
|
189
|
+
if (value && validateCorrelationId(value)) {
|
|
190
|
+
return value;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Get or create correlation ID from headers
|
|
197
|
+
*
|
|
198
|
+
* Returns existing correlation ID from headers, or generates a new one if not found.
|
|
199
|
+
* Does NOT modify the input headers.
|
|
200
|
+
*
|
|
201
|
+
* @param headers - Headers to check
|
|
202
|
+
* @returns Existing or new correlation ID
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* ```typescript
|
|
206
|
+
* const headers = new Headers();
|
|
207
|
+
* const id = getOrCreateCorrelationId(headers);
|
|
208
|
+
* // => Generates new ID if not found in headers
|
|
209
|
+
* ```
|
|
210
|
+
*/
|
|
211
|
+
function getOrCreateCorrelationId(headers) {
|
|
212
|
+
if (!headers)
|
|
213
|
+
return generateCorrelationId();
|
|
214
|
+
const existing = extractCorrelationId(headers);
|
|
215
|
+
return existing ?? generateCorrelationId();
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Create a correlation ID from a timestamp
|
|
219
|
+
*
|
|
220
|
+
* Useful for testing or when you need deterministic IDs.
|
|
221
|
+
*
|
|
222
|
+
* @param timestamp - Unix timestamp in milliseconds
|
|
223
|
+
* @param random - Optional random component (generates if not provided)
|
|
224
|
+
* @returns Correlation ID
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* ```typescript
|
|
228
|
+
* const id = createCorrelationIdFromTimestamp(1716234567890);
|
|
229
|
+
* // => 'req_1716234567890_a3f5c9d2'
|
|
230
|
+
*
|
|
231
|
+
* const deterministicId = createCorrelationIdFromTimestamp(1716234567890, 'aaaaaaaa');
|
|
232
|
+
* // => 'req_1716234567890_aaaaaaaa'
|
|
233
|
+
* ```
|
|
234
|
+
*/
|
|
235
|
+
function createCorrelationIdFromTimestamp(timestamp, random) {
|
|
236
|
+
const randomComponent = random ?? generateRandomHex(8);
|
|
237
|
+
if (!/^[a-f0-9]{8}$/.test(randomComponent)) {
|
|
238
|
+
throw new Error(`Invalid random component: ${randomComponent}. Expected 8 hex characters`);
|
|
239
|
+
}
|
|
240
|
+
return `req_${timestamp}_${randomComponent}`;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Calculate age of correlation ID in milliseconds
|
|
244
|
+
*
|
|
245
|
+
* @param id - Correlation ID
|
|
246
|
+
* @returns Age in milliseconds, or null if invalid
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* ```typescript
|
|
250
|
+
* const id = generateCorrelationId();
|
|
251
|
+
* setTimeout(() => {
|
|
252
|
+
* const age = getCorrelationIdAge(id);
|
|
253
|
+
* console.log(`Request age: ${age}ms`);
|
|
254
|
+
* }, 1000);
|
|
255
|
+
* ```
|
|
256
|
+
*/
|
|
257
|
+
function getCorrelationIdAge(id) {
|
|
258
|
+
const spec = parseCorrelationId(id);
|
|
259
|
+
if (!spec)
|
|
260
|
+
return null;
|
|
261
|
+
const now = Date.now();
|
|
262
|
+
return now - spec.timestamp;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Check if correlation ID is expired
|
|
266
|
+
*
|
|
267
|
+
* @param id - Correlation ID
|
|
268
|
+
* @param maxAgeMs - Maximum age in milliseconds (default: 5 minutes)
|
|
269
|
+
* @returns True if expired, false otherwise
|
|
270
|
+
*
|
|
271
|
+
* @example
|
|
272
|
+
* ```typescript
|
|
273
|
+
* const id = generateCorrelationId();
|
|
274
|
+
* if (isCorrelationIdExpired(id, 60000)) {
|
|
275
|
+
* console.log('Request older than 1 minute');
|
|
276
|
+
* }
|
|
277
|
+
* ```
|
|
278
|
+
*/
|
|
279
|
+
function isCorrelationIdExpired(id, maxAgeMs = 5 * 60 * 1000) {
|
|
280
|
+
const age = getCorrelationIdAge(id);
|
|
281
|
+
return age !== null && age > maxAgeMs;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Generate random hex string
|
|
285
|
+
*
|
|
286
|
+
* Uses Web Crypto API in browser, Node.js crypto in Node.
|
|
287
|
+
* Falls back to Math.random() if neither available (NOT cryptographically secure).
|
|
288
|
+
*
|
|
289
|
+
* @param length - Number of hex characters to generate
|
|
290
|
+
* @returns Random hex string
|
|
291
|
+
*
|
|
292
|
+
* @internal
|
|
293
|
+
*/
|
|
294
|
+
function generateRandomHex(length) {
|
|
295
|
+
const bytes = Math.ceil(length / 2);
|
|
296
|
+
// Try Web Crypto API (browser)
|
|
297
|
+
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
|
|
298
|
+
const buffer = new Uint8Array(bytes);
|
|
299
|
+
crypto.getRandomValues(buffer);
|
|
300
|
+
return Array.from(buffer)
|
|
301
|
+
.map((b) => b.toString(16).padStart(2, '0'))
|
|
302
|
+
.join('')
|
|
303
|
+
.substring(0, length);
|
|
304
|
+
}
|
|
305
|
+
// Try Node.js crypto
|
|
306
|
+
try {
|
|
307
|
+
const nodeCrypto = require('node:crypto');
|
|
308
|
+
return nodeCrypto.randomBytes(bytes).toString('hex').substring(0, length);
|
|
309
|
+
}
|
|
310
|
+
catch {
|
|
311
|
+
// SECURITY: Never fall back to Math.random() in production (OWASP violation)
|
|
312
|
+
// Correlation IDs must be cryptographically random to prevent enumeration attacks
|
|
313
|
+
throw new Error('Cryptographic random generation unavailable. ' +
|
|
314
|
+
'Install crypto polyfill or use environment with crypto support.');
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Middleware helper for Express/Koa-style frameworks
|
|
319
|
+
*
|
|
320
|
+
* Automatically attaches correlation ID to incoming requests.
|
|
321
|
+
*
|
|
322
|
+
* @example
|
|
323
|
+
* ```typescript
|
|
324
|
+
* import express from 'express';
|
|
325
|
+
* import { correlationIdMiddleware } from '@private.me/xbind';
|
|
326
|
+
*
|
|
327
|
+
* const app = express();
|
|
328
|
+
* app.use(correlationIdMiddleware());
|
|
329
|
+
* ```
|
|
330
|
+
*/
|
|
331
|
+
function correlationIdMiddleware() {
|
|
332
|
+
return (req, res, next) => {
|
|
333
|
+
const id = getOrCreateCorrelationId(req.headers);
|
|
334
|
+
req.correlationId = id;
|
|
335
|
+
res.setHeader(exports.CORRELATION_ID_HEADER, id);
|
|
336
|
+
if (typeof next === 'function')
|
|
337
|
+
next();
|
|
338
|
+
};
|
|
339
|
+
}
|