@memberjunction/server 5.47.0 → 5.49.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/README.md +9 -0
- package/dist/auth/magicLink/MagicLinkKeys.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkKeys.js +11 -4
- package/dist/auth/magicLink/MagicLinkKeys.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.js +2 -2
- package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.js +4 -0
- package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
- package/dist/config.d.ts +40 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +12 -0
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +65 -3
- package/dist/context.js.map +1 -1
- package/dist/generated/generated.d.ts +122 -0
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +629 -0
- package/dist/generated/generated.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +4 -2
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts +1 -0
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +12 -0
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +99 -11
- package/dist/index.js.map +1 -1
- package/dist/integration/CustomColumnPromoter.d.ts +15 -2
- package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
- package/dist/integration/CustomColumnPromoter.js +113 -11
- package/dist/integration/CustomColumnPromoter.js.map +1 -1
- package/dist/integration/EntityMapLifecycle.d.ts +66 -0
- package/dist/integration/EntityMapLifecycle.d.ts.map +1 -0
- package/dist/integration/EntityMapLifecycle.js +189 -0
- package/dist/integration/EntityMapLifecycle.js.map +1 -0
- package/dist/realtimeProxy/RealtimeProxyServer.d.ts +58 -0
- package/dist/realtimeProxy/RealtimeProxyServer.d.ts.map +1 -0
- package/dist/realtimeProxy/RealtimeProxyServer.js +246 -0
- package/dist/realtimeProxy/RealtimeProxyServer.js.map +1 -0
- package/dist/realtimeWidget/WidgetRouter.d.ts.map +1 -1
- package/dist/realtimeWidget/WidgetRouter.js +6 -2
- package/dist/realtimeWidget/WidgetRouter.js.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.d.ts +17 -0
- package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.js +81 -25
- package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
- package/dist/resolvers/GetDataResolver.d.ts +16 -0
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +26 -1
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts +53 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +522 -60
- package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
- package/dist/resolvers/QueryResolver.d.ts.map +1 -1
- package/dist/resolvers/QueryResolver.js +0 -6
- package/dist/resolvers/QueryResolver.js.map +1 -1
- package/dist/resolvers/RSUResolver.d.ts +10 -0
- package/dist/resolvers/RSUResolver.d.ts.map +1 -1
- package/dist/resolvers/RSUResolver.js +28 -0
- package/dist/resolvers/RSUResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts +9 -6
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +135 -132
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/TestQuerySQLResolver.d.ts +1 -0
- package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -1
- package/dist/resolvers/TestQuerySQLResolver.js +5 -0
- package/dist/resolvers/TestQuerySQLResolver.js.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.d.ts +8 -1
- package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.js +29 -1
- package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
- package/dist/resolvers/adhoc-query-helpers.d.ts +27 -0
- package/dist/resolvers/adhoc-query-helpers.d.ts.map +1 -0
- package/dist/resolvers/adhoc-query-helpers.js +42 -0
- package/dist/resolvers/adhoc-query-helpers.js.map +1 -0
- package/dist/rest/OAuthCallbackHandler.d.ts +9 -0
- package/dist/rest/OAuthCallbackHandler.d.ts.map +1 -1
- package/dist/rest/OAuthCallbackHandler.js +21 -4
- package/dist/rest/OAuthCallbackHandler.js.map +1 -1
- package/dist/telephony/media-upgrade-router.d.ts +14 -0
- package/dist/telephony/media-upgrade-router.d.ts.map +1 -1
- package/dist/telephony/media-upgrade-router.js +17 -1
- package/dist/telephony/media-upgrade-router.js.map +1 -1
- package/package.json +88 -88
- package/src/__tests__/AdhocQueryResolver.pagination.test.ts +114 -0
- package/src/__tests__/EntityMapLifecycle.dagWarnings.test.ts +91 -0
- package/src/__tests__/OAuthCallbackHandler.xss.test.ts +83 -0
- package/src/__tests__/RealtimeProxyServer.test.ts +122 -0
- package/src/__tests__/adhoc-query-helpers.test.ts +50 -0
- package/src/__tests__/magicLinkService.provisionLog.test.ts +106 -0
- package/src/__tests__/media-upgrade-router.test.ts +43 -0
- package/src/__tests__/unifiedAuth.test.ts +18 -3
- package/src/auth/magicLink/MagicLinkKeys.ts +12 -6
- package/src/auth/magicLink/MagicLinkRouter.ts +2 -2
- package/src/auth/magicLink/MagicLinkService.ts +4 -0
- package/src/config.ts +14 -0
- package/src/context.ts +74 -3
- package/src/generated/generated.ts +446 -1
- package/src/generic/ResolverBase.ts +5 -2
- package/src/generic/RunViewResolver.ts +11 -0
- package/src/index.ts +100 -11
- package/src/integration/CustomColumnPromoter.ts +123 -6
- package/src/integration/EntityMapLifecycle.ts +221 -0
- package/src/realtimeProxy/RealtimeProxyServer.ts +265 -0
- package/src/realtimeWidget/WidgetRouter.ts +6 -2
- package/src/resolvers/AdhocQueryResolver.ts +97 -30
- package/src/resolvers/GetDataResolver.ts +30 -1
- package/src/resolvers/IntegrationDiscoveryResolver.ts +514 -60
- package/src/resolvers/QueryResolver.ts +1 -7
- package/src/resolvers/RSUResolver.ts +28 -0
- package/src/resolvers/RunAIAgentResolver.ts +74 -70
- package/src/resolvers/TestQuerySQLResolver.ts +4 -0
- package/src/resolvers/TransactionGroupResolver.ts +36 -2
- package/src/resolvers/__tests__/GetDataResolver.tokens.test.ts +138 -0
- package/src/resolvers/adhoc-query-helpers.ts +50 -0
- package/src/rest/OAuthCallbackHandler.ts +22 -4
- package/src/telephony/media-upgrade-router.ts +18 -1
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { WebSocket as WsClient, WebSocketServer } from 'ws';
|
|
2
|
+
import { BaseSingleton, ShutdownRegistry } from '@memberjunction/global';
|
|
3
|
+
import { RealtimeProxyRegistry, REALTIME_PROXY_PATH } from '@memberjunction/ai';
|
|
4
|
+
/**
|
|
5
|
+
* MJAPI's realtime websocket **proxy** — the transport half of the self-hosted realtime provider story.
|
|
6
|
+
*
|
|
7
|
+
* A provider driver (e.g. `HuggingFaceRealtime`) mints a one-time ticket into the shared
|
|
8
|
+
* {@link RealtimeProxyRegistry} pointing at an INTERNAL realtime endpoint, and hands the browser a
|
|
9
|
+
* `wss://<mjapi-public>{REALTIME_PROXY_PATH}?ticket=<id>` URL. The browser opens its socket HERE; this
|
|
10
|
+
* proxy consumes the ticket, opens the authenticated upstream leg (injecting any auth server-side), and
|
|
11
|
+
* pumps frames transparently in both directions. The internal endpoint + auth never reach the browser,
|
|
12
|
+
* and the internal box needs no browser-facing ingress — MJAPI stays the single ingress point.
|
|
13
|
+
*
|
|
14
|
+
* Deliberately provider-agnostic: it does no protocol translation (the same-keyed client driver owns the
|
|
15
|
+
* wire vocabulary) and knows nothing about any specific provider — it is a pure authenticated byte tunnel,
|
|
16
|
+
* reusable by any future self-hosted realtime provider that mints a ticket.
|
|
17
|
+
*
|
|
18
|
+
* A {@link BaseSingleton} + {@link IShutdownable}: it tracks live tunnels and closes them on graceful
|
|
19
|
+
* shutdown (drained by MJServer's `ShutdownRegistry` before `httpServer.close()`).
|
|
20
|
+
*/
|
|
21
|
+
export class RealtimeProxyServer extends BaseSingleton {
|
|
22
|
+
constructor() {
|
|
23
|
+
super();
|
|
24
|
+
this.ShutdownName = 'RealtimeProxyServer';
|
|
25
|
+
/** `noServer` so THIS server never binds its own upgrade listener — MJServer routes upgrades to it by path. */
|
|
26
|
+
this.wss = new WebSocketServer({ noServer: true });
|
|
27
|
+
/** Live browser↔upstream tunnels, tracked for shutdown teardown. */
|
|
28
|
+
this.tunnels = new Set();
|
|
29
|
+
this.registered = false;
|
|
30
|
+
}
|
|
31
|
+
/** Process-wide singleton accessor. */
|
|
32
|
+
static get Instance() {
|
|
33
|
+
return super.getInstance();
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Routes an HTTP `upgrade` for {@link REALTIME_PROXY_PATH} to the proxy. Returns `true` when it OWNS
|
|
37
|
+
* (handled/rejected) the request, `false` when the path is not the proxy's — so the caller leaves the
|
|
38
|
+
* socket for the GraphQL websocket server's own upgrade listener. NEVER destroys a socket it doesn't own.
|
|
39
|
+
*/
|
|
40
|
+
TryHandleUpgrade(request, socket, head) {
|
|
41
|
+
const url = RealtimeProxyServer.parseUrl(request.url);
|
|
42
|
+
if (!url || url.pathname !== REALTIME_PROXY_PATH) {
|
|
43
|
+
return false; // not ours — leave it for the GraphQL WS server
|
|
44
|
+
}
|
|
45
|
+
this.ensureRegistered();
|
|
46
|
+
// Optional Origin allowlist (MJ_REALTIME_PROXY_ALLOWED_ORIGINS: comma-separated origins).
|
|
47
|
+
// Unset ⇒ prior behavior (the single-use short-TTL ticket remains the primary guard);
|
|
48
|
+
// set ⇒ a browser page on a foreign origin cannot ride a leaked ticket id.
|
|
49
|
+
if (!RealtimeProxyServer.originAllowed(request.headers?.origin)) {
|
|
50
|
+
RealtimeProxyServer.rejectUpgrade(socket, 403, 'Forbidden');
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
const ticketId = url.searchParams.get('ticket') ?? '';
|
|
54
|
+
const entry = RealtimeProxyRegistry.Instance.Consume(ticketId);
|
|
55
|
+
if (!entry) {
|
|
56
|
+
RealtimeProxyServer.rejectUpgrade(socket, 401, 'Unauthorized');
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
this.wss.handleUpgrade(request, socket, head, (browserWs) => this.openTunnel(browserWs, entry));
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Evaluates the optional Origin allowlist. Comparison is exact on the normalized origin
|
|
64
|
+
* (scheme://host[:port], lowercased, no trailing slash). An upgrade WITHOUT an Origin header
|
|
65
|
+
* (non-browser client, e.g. a native app or test rig) passes — the allowlist targets
|
|
66
|
+
* cross-origin BROWSER pages, which always send the header.
|
|
67
|
+
*
|
|
68
|
+
* @param origin The upgrade request's Origin header, if any.
|
|
69
|
+
* @returns True when allowed (or no allowlist is configured).
|
|
70
|
+
*/
|
|
71
|
+
static originAllowed(origin) {
|
|
72
|
+
const raw = process.env['MJ_REALTIME_PROXY_ALLOWED_ORIGINS'];
|
|
73
|
+
if (!raw || raw.trim().length === 0) {
|
|
74
|
+
return true; // no allowlist configured — prior behavior
|
|
75
|
+
}
|
|
76
|
+
if (!origin) {
|
|
77
|
+
return true; // non-browser upgrade (no Origin header) — tickets remain the guard
|
|
78
|
+
}
|
|
79
|
+
const normalize = (value) => value.trim().toLowerCase().replace(/\/+$/, '');
|
|
80
|
+
const allowed = raw.split(',').map(normalize).filter((v) => v.length > 0);
|
|
81
|
+
return allowed.includes(normalize(origin));
|
|
82
|
+
}
|
|
83
|
+
/** Opens the upstream leg and wires a bidirectional pump between the browser socket and it. */
|
|
84
|
+
openTunnel(browserWs, entry) {
|
|
85
|
+
const tunnel = new RealtimeProxyTunnel(browserWs, entry, () => this.tunnels.delete(tunnel));
|
|
86
|
+
this.tunnels.add(tunnel);
|
|
87
|
+
tunnel.Start();
|
|
88
|
+
}
|
|
89
|
+
/** Registers for graceful-shutdown draining exactly once (lazily, on first real use). */
|
|
90
|
+
ensureRegistered() {
|
|
91
|
+
if (!this.registered) {
|
|
92
|
+
ShutdownRegistry.Instance.Register(this);
|
|
93
|
+
this.registered = true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/** Closes every live tunnel and the proxy server. Idempotent; never throws. */
|
|
97
|
+
Shutdown() {
|
|
98
|
+
for (const tunnel of [...this.tunnels]) {
|
|
99
|
+
tunnel.Close();
|
|
100
|
+
}
|
|
101
|
+
this.tunnels.clear();
|
|
102
|
+
try {
|
|
103
|
+
this.wss.close();
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
/* already closing */
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/** Parses `request.url` (a path+query) into a URL, or `null` when absent/unparseable. */
|
|
110
|
+
static parseUrl(rawUrl) {
|
|
111
|
+
if (!rawUrl) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
return new URL(rawUrl, 'http://internal'); // base is only for parsing path+query
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/** Writes a minimal HTTP error response and destroys the socket (used for a rejected upgrade). */
|
|
122
|
+
static rejectUpgrade(socket, code, reason) {
|
|
123
|
+
try {
|
|
124
|
+
socket.write(`HTTP/1.1 ${code} ${reason}\r\nConnection: close\r\n\r\n`);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
/* socket already gone */
|
|
128
|
+
}
|
|
129
|
+
socket.destroy();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* One live browser↔upstream tunnel. Buffers browser→upstream frames until the upstream socket is open,
|
|
134
|
+
* then pumps both directions byte-transparently (text and binary alike). Closing either side closes the other.
|
|
135
|
+
*/
|
|
136
|
+
class RealtimeProxyTunnel {
|
|
137
|
+
/** How long the upstream websocket may take to OPEN before the tunnel is torn down (ms). */
|
|
138
|
+
static { this.UPSTREAM_OPEN_TIMEOUT_MS = 15_000; }
|
|
139
|
+
/**
|
|
140
|
+
* Cap on frames buffered browser→upstream before the upstream opens. A half-connected
|
|
141
|
+
* upstream (TCP accepted, WS handshake never completes) must not let a browser streaming PCM
|
|
142
|
+
* grow an unbounded in-memory array on MJAPI; past the cap the OLDEST frames are dropped
|
|
143
|
+
* (voice frames are perishable — late delivery of stale audio is worthless anyway).
|
|
144
|
+
*/
|
|
145
|
+
static { this.MAX_PENDING_FRAMES = 512; }
|
|
146
|
+
constructor(browser, entry, onClosed) {
|
|
147
|
+
this.browser = browser;
|
|
148
|
+
this.entry = entry;
|
|
149
|
+
this.onClosed = onClosed;
|
|
150
|
+
this.upstream = null;
|
|
151
|
+
/** Frames the browser sent before the upstream opened; flushed in order once it's ready. */
|
|
152
|
+
this.pending = [];
|
|
153
|
+
this.openDeadline = null;
|
|
154
|
+
this.droppedPendingFrames = 0;
|
|
155
|
+
this.closed = false;
|
|
156
|
+
}
|
|
157
|
+
/** Opens the upstream socket and wires both legs. */
|
|
158
|
+
Start() {
|
|
159
|
+
const headers = this.entry.UpstreamAuthHeader ? { Authorization: this.entry.UpstreamAuthHeader } : undefined;
|
|
160
|
+
const upstream = new WsClient(this.entry.UpstreamUrl, { headers });
|
|
161
|
+
this.upstream = upstream;
|
|
162
|
+
// Deadline: an upstream that accepts TCP but never completes the WS handshake would
|
|
163
|
+
// otherwise hold the tunnel (and its buffer) open forever.
|
|
164
|
+
this.openDeadline = setTimeout(() => {
|
|
165
|
+
this.openDeadline = null;
|
|
166
|
+
if (!this.closed && upstream.readyState !== WsClient.OPEN) {
|
|
167
|
+
console.warn(`[RealtimeProxy] upstream did not open within ${RealtimeProxyTunnel.UPSTREAM_OPEN_TIMEOUT_MS}ms — closing tunnel`);
|
|
168
|
+
this.Close();
|
|
169
|
+
}
|
|
170
|
+
}, RealtimeProxyTunnel.UPSTREAM_OPEN_TIMEOUT_MS);
|
|
171
|
+
this.openDeadline.unref?.();
|
|
172
|
+
upstream.on('open', () => {
|
|
173
|
+
if (this.openDeadline) {
|
|
174
|
+
clearTimeout(this.openDeadline);
|
|
175
|
+
this.openDeadline = null;
|
|
176
|
+
}
|
|
177
|
+
this.flushPending();
|
|
178
|
+
});
|
|
179
|
+
upstream.on('message', (data, isBinary) => this.forward(this.browser, data, isBinary));
|
|
180
|
+
upstream.on('close', () => this.Close());
|
|
181
|
+
upstream.on('error', () => this.Close());
|
|
182
|
+
this.browser.on('message', (data, isBinary) => this.fromBrowser(data, isBinary));
|
|
183
|
+
this.browser.on('close', () => this.Close());
|
|
184
|
+
this.browser.on('error', () => this.Close());
|
|
185
|
+
}
|
|
186
|
+
/** Browser→upstream: forward immediately when the upstream is open, else queue until it is. */
|
|
187
|
+
fromBrowser(data, isBinary) {
|
|
188
|
+
if (this.upstream && this.upstream.readyState === WsClient.OPEN) {
|
|
189
|
+
this.forward(this.upstream, data, isBinary);
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
this.pending.push({ data, isBinary });
|
|
193
|
+
if (this.pending.length > RealtimeProxyTunnel.MAX_PENDING_FRAMES) {
|
|
194
|
+
this.pending.shift(); // drop-oldest: stale voice frames are worthless anyway
|
|
195
|
+
this.droppedPendingFrames++;
|
|
196
|
+
if (this.droppedPendingFrames === 1 || this.droppedPendingFrames % 100 === 0) {
|
|
197
|
+
console.warn(`[RealtimeProxy] pre-open buffer cap hit — dropped ${this.droppedPendingFrames} oldest frame(s) awaiting upstream open`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/** Drains any frames queued before the upstream opened. */
|
|
203
|
+
flushPending() {
|
|
204
|
+
if (!this.upstream) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
for (const frame of this.pending) {
|
|
208
|
+
this.forward(this.upstream, frame.data, frame.isBinary);
|
|
209
|
+
}
|
|
210
|
+
this.pending.length = 0;
|
|
211
|
+
}
|
|
212
|
+
/** Sends one frame on a socket, preserving the text/binary distinction; failures close the tunnel. */
|
|
213
|
+
forward(target, data, isBinary) {
|
|
214
|
+
if (target.readyState !== WsClient.OPEN) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
try {
|
|
218
|
+
target.send(data, { binary: isBinary });
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
this.Close();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/** Closes both legs and detaches the tunnel from the server. Idempotent. */
|
|
225
|
+
Close() {
|
|
226
|
+
if (this.openDeadline) {
|
|
227
|
+
clearTimeout(this.openDeadline);
|
|
228
|
+
this.openDeadline = null;
|
|
229
|
+
}
|
|
230
|
+
if (this.closed) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
this.closed = true;
|
|
234
|
+
this.pending.length = 0;
|
|
235
|
+
for (const sock of [this.browser, this.upstream]) {
|
|
236
|
+
try {
|
|
237
|
+
sock?.close();
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
/* already closing */
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
this.onClosed();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
//# sourceMappingURL=RealtimeProxyServer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RealtimeProxyServer.js","sourceRoot":"","sources":["../../src/realtimeProxy/RealtimeProxyServer.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WidgetRouter.d.ts","sourceRoot":"","sources":["../../src/realtimeWidget/WidgetRouter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,MAAM,EAAqC,MAAM,SAAS,CAAC;AAGpE,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAM7D,+DAA+D;AAC/D,eAAO,MAAM,iBAAiB,YAAY,CAAC;AAyB3C;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,mBAAmB,EAAE,MAAM,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"WidgetRouter.d.ts","sourceRoot":"","sources":["../../src/realtimeWidget/WidgetRouter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,MAAM,EAAqC,MAAM,SAAS,CAAC;AAGpE,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAM7D,+DAA+D;AAC/D,eAAO,MAAM,iBAAiB,YAAY,CAAC;AAyB3C;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,mBAAmB,EAAE,MAAM,CAAA;CAAE,CAqDlI"}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* @module @memberjunction/server/widget
|
|
11
11
|
*/
|
|
12
12
|
import { Router, json } from 'express';
|
|
13
|
-
import { rateLimit } from 'express-rate-limit';
|
|
13
|
+
import { rateLimit, ipKeyGenerator } from 'express-rate-limit';
|
|
14
14
|
import { LogStatus } from '@memberjunction/core';
|
|
15
15
|
import { configInfo } from '../config.js';
|
|
16
16
|
import { MagicLinkKeyManager } from '../auth/magicLink/MagicLinkKeys.js';
|
|
@@ -57,7 +57,11 @@ export function createWidgetHandler(publicUrl, config) {
|
|
|
57
57
|
windowMs: config.rateLimitWindowMs,
|
|
58
58
|
standardHeaders: 'draft-7',
|
|
59
59
|
legacyHeaders: false,
|
|
60
|
-
|
|
60
|
+
// ipKeyGenerator collapses IPv6 addresses to their /56 subnet before keying. Raw `req.ip`
|
|
61
|
+
// would give every IPv6 client a practically unlimited supply of distinct keys (one per
|
|
62
|
+
// address in their delegated prefix), letting them sidestep the per-IP bucket entirely —
|
|
63
|
+
// express-rate-limit v8 refuses to start with a keyGenerator that reads req.ip without it.
|
|
64
|
+
keyGenerator: (req) => `${readWidgetKey(req)}:${req.ip ? ipKeyGenerator(req.ip) : 'noip'}`,
|
|
61
65
|
limit: async (req) => service.ResolvePerInstanceRateLimit(readWidgetKey(req)),
|
|
62
66
|
message: { success: false, errorCode: 'rate_limited', error: 'Too many widget session requests. Try again later.' },
|
|
63
67
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WidgetRouter.js","sourceRoot":"","sources":["../../src/realtimeWidget/WidgetRouter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,MAAM,EAAE,IAAI,EAA+B,MAAM,SAAS,CAAC;AACpE,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/
|
|
1
|
+
{"version":3,"file":"WidgetRouter.js","sourceRoot":"","sources":["../../src/realtimeWidget/WidgetRouter.ts"],"names":[],"mappings":"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"}
|
|
@@ -24,6 +24,23 @@ declare class AdhocQueryInput {
|
|
|
24
24
|
*/
|
|
25
25
|
export declare class AdhocQueryResolver extends ResolverBase {
|
|
26
26
|
ExecuteAdhocQuery(input: AdhocQueryInput, context: AppContext): Promise<RunQueryResultType>;
|
|
27
|
+
/**
|
|
28
|
+
* Runs the page's data SQL, then — only when the page is FULL (so more rows may
|
|
29
|
+
* exist) — a COUNT(*) for the true total. A short (or unpaged) page needs no
|
|
30
|
+
* count: the exact total is `startRow + rowsReturned` (see {@link exactTotalFromPage}).
|
|
31
|
+
*
|
|
32
|
+
* The count is NON-FATAL: some queries page fine but cannot be counted — e.g.
|
|
33
|
+
* duplicate column names are legal in a result set but rejected inside the COUNT
|
|
34
|
+
* CTE wrap. A count failure must never sink the whole result, so we log it and
|
|
35
|
+
* report a lower-bound total (`startRow + rowsReturned`) and let the data render.
|
|
36
|
+
*/
|
|
37
|
+
private executeDataAndCount;
|
|
38
|
+
/**
|
|
39
|
+
* Executes one SQL statement on the read-only pool, racing it against the shared
|
|
40
|
+
* wall-clock `deadline`. The timer is always cleared on completion so a settled
|
|
41
|
+
* query never leaves a dangling timeout armed.
|
|
42
|
+
*/
|
|
43
|
+
private runSqlWithDeadline;
|
|
27
44
|
private buildErrorResult;
|
|
28
45
|
}
|
|
29
46
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdhocQueryResolver.d.ts","sourceRoot":"","sources":["../../src/resolvers/AdhocQueryResolver.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"AdhocQueryResolver.d.ts","sourceRoot":"","sources":["../../src/resolvers/AdhocQueryResolver.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAIxD;;;GAGG;AACH,cACM,eAAe;IAEjB,GAAG,EAAE,MAAM,CAAC;IAGZ,cAAc,CAAC,EAAE,MAAM,CAAC;IAGxB,OAAO,CAAC,EAAE,MAAM,CAAC;IAGjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;GAUG;AACH,qBACa,kBAAmB,SAAQ,YAAY;IAE1C,iBAAiB,CACkB,KAAK,EAAE,eAAe,EACpD,OAAO,EAAE,UAAU,GAC3B,OAAO,CAAC,kBAAkB,CAAC;IAsH9B;;;;;;;;;OASG;YACW,mBAAmB;IA6BjC;;;;OAIG;YACW,kBAAkB;IAwBhC,OAAO,CAAC,gBAAgB;CAc3B"}
|
|
@@ -17,6 +17,7 @@ import { RenderPipeline } from '@memberjunction/generic-database-provider';
|
|
|
17
17
|
import { GetReadOnlyDataSource, GetReadOnlyProvider } from '../util.js';
|
|
18
18
|
import { ResolverBase } from '../generic/ResolverBase.js';
|
|
19
19
|
import { RunQueryResultType } from './QueryResolver.js';
|
|
20
|
+
import { exactTotalFromPage, resolveAdhocTotalRowCount } from './adhoc-query-helpers.js';
|
|
20
21
|
import sql from 'mssql';
|
|
21
22
|
/**
|
|
22
23
|
* Input type for executing ad-hoc SQL queries directly.
|
|
@@ -37,7 +38,7 @@ __decorate([
|
|
|
37
38
|
__metadata("design:type", Number)
|
|
38
39
|
], AdhocQueryInput.prototype, "MaxRows", void 0);
|
|
39
40
|
__decorate([
|
|
40
|
-
Field(() => Int, { nullable: true, description: 'Zero-based offset for pagination.
|
|
41
|
+
Field(() => Int, { nullable: true, description: 'Zero-based offset for pagination. Whenever MaxRows > 0 the query is paged via OFFSET/FETCH — including the first page (StartRow 0).' }),
|
|
41
42
|
__metadata("design:type", Number)
|
|
42
43
|
], AdhocQueryInput.prototype, "StartRow", void 0);
|
|
43
44
|
AdhocQueryInput = __decorate([
|
|
@@ -86,49 +87,49 @@ let AdhocQueryResolver = class AdhocQueryResolver extends ResolverBase {
|
|
|
86
87
|
// 4. Route the SQL through RenderPipeline so composition tokens
|
|
87
88
|
// resolve, comments and templates are processed, and the row cap
|
|
88
89
|
// is applied at the database (via TOP / LIMIT / OFFSET-FETCH).
|
|
89
|
-
|
|
90
|
+
//
|
|
91
|
+
// Page whenever a positive MaxRows is requested — INCLUDING the first
|
|
92
|
+
// page (StartRow 0). This yields OFFSET/FETCH data SQL *and* a
|
|
93
|
+
// COUNT(*) query so the response reports the true total row count
|
|
94
|
+
// rather than just the returned page size. It mirrors the saved-query
|
|
95
|
+
// path (GenericDatabaseProvider.InternalRunQuery → WrapWithPaging).
|
|
96
|
+
// (Previously paging was gated on StartRow > 0, so page 1 fell back to
|
|
97
|
+
// a TOP-N cap with TotalRowCount = page size — hiding the pager.)
|
|
98
|
+
// Clamp StartRow to a non-negative integer. A negative offset must not slip
|
|
99
|
+
// past the paging gate (which would then run the query with no row cap at all).
|
|
100
|
+
const startRow = Math.max(0, Number.isInteger(input.StartRow) ? input.StartRow : 0);
|
|
90
101
|
const maxRows = input.MaxRows;
|
|
91
|
-
const usePaging = maxRows != null &&
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
Number.isInteger(startRow) &&
|
|
95
|
-
startRow > 0;
|
|
96
|
-
let executableSql;
|
|
102
|
+
const usePaging = maxRows != null && Number.isInteger(maxRows) && maxRows > 0;
|
|
103
|
+
let dataSQL;
|
|
104
|
+
let countSQL = null;
|
|
97
105
|
try {
|
|
98
106
|
const rendered = RenderPipeline.Run(input.SQL, {
|
|
99
107
|
Platform: platform,
|
|
100
108
|
ContextUser: contextUser,
|
|
101
|
-
...(usePaging
|
|
102
|
-
? { Paging: { StartRow: startRow, MaxRows: maxRows } }
|
|
103
|
-
: maxRows != null && maxRows > 0
|
|
104
|
-
? { MaxRows: maxRows }
|
|
105
|
-
: {}),
|
|
109
|
+
...(usePaging ? { Paging: { StartRow: startRow, MaxRows: maxRows } } : {}),
|
|
106
110
|
});
|
|
107
|
-
|
|
111
|
+
dataSQL = rendered.FinalSQL;
|
|
112
|
+
countSQL = rendered.PagingResult?.CountSQL ?? null;
|
|
108
113
|
}
|
|
109
114
|
catch (renderErr) {
|
|
110
115
|
const renderMsg = renderErr instanceof Error ? renderErr.message : String(renderErr);
|
|
111
116
|
return this.buildErrorResult(`Ad-hoc query rendering failed: ${renderMsg}`);
|
|
112
117
|
}
|
|
113
|
-
// 5. Execute
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
request.query(executableSql),
|
|
118
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error('Query timeout exceeded')), timeoutMs))
|
|
119
|
-
]);
|
|
118
|
+
// 5. Execute the page (and, only when a full page needs it, the count) under
|
|
119
|
+
// a shared wall-clock deadline derived from the request's timeout budget.
|
|
120
|
+
const deadline = startTime + (input.TimeoutSeconds ?? 30) * 1000;
|
|
121
|
+
const { recordset, totalRowCount } = await this.executeDataAndCount(readOnlyDS, dataSQL, countSQL, startRow, usePaging ? maxRows : null, deadline);
|
|
120
122
|
const executionTimeMs = Date.now() - startTime;
|
|
121
123
|
// 6. Return as RunQueryResultType
|
|
122
|
-
const recordset = result.recordset ?? [];
|
|
123
124
|
return {
|
|
124
125
|
QueryID: '',
|
|
125
126
|
QueryName: 'Ad-Hoc Query',
|
|
126
127
|
Success: true,
|
|
127
128
|
Results: JSON.stringify(recordset),
|
|
128
129
|
RowCount: recordset.length,
|
|
129
|
-
TotalRowCount:
|
|
130
|
-
PageNumber:
|
|
131
|
-
PageSize: maxRows
|
|
130
|
+
TotalRowCount: totalRowCount,
|
|
131
|
+
PageNumber: usePaging ? Math.floor(startRow / maxRows) + 1 : undefined,
|
|
132
|
+
PageSize: usePaging ? maxRows : undefined,
|
|
132
133
|
ExecutionTime: executionTimeMs,
|
|
133
134
|
ErrorMessage: ''
|
|
134
135
|
};
|
|
@@ -166,6 +167,61 @@ let AdhocQueryResolver = class AdhocQueryResolver extends ResolverBase {
|
|
|
166
167
|
};
|
|
167
168
|
}
|
|
168
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* Runs the page's data SQL, then — only when the page is FULL (so more rows may
|
|
172
|
+
* exist) — a COUNT(*) for the true total. A short (or unpaged) page needs no
|
|
173
|
+
* count: the exact total is `startRow + rowsReturned` (see {@link exactTotalFromPage}).
|
|
174
|
+
*
|
|
175
|
+
* The count is NON-FATAL: some queries page fine but cannot be counted — e.g.
|
|
176
|
+
* duplicate column names are legal in a result set but rejected inside the COUNT
|
|
177
|
+
* CTE wrap. A count failure must never sink the whole result, so we log it and
|
|
178
|
+
* report a lower-bound total (`startRow + rowsReturned`) and let the data render.
|
|
179
|
+
*/
|
|
180
|
+
async executeDataAndCount(ds, dataSQL, countSQL, startRow, maxRows, deadline) {
|
|
181
|
+
const dataResult = await this.runSqlWithDeadline(ds, dataSQL, deadline);
|
|
182
|
+
const recordset = (dataResult.recordset ?? []);
|
|
183
|
+
// Total already known from the page alone (unpaged, or a short page)? Skip the count.
|
|
184
|
+
const exact = exactTotalFromPage(startRow, recordset.length, maxRows);
|
|
185
|
+
if (exact != null || !countSQL) {
|
|
186
|
+
return { recordset, totalRowCount: exact ?? recordset.length };
|
|
187
|
+
}
|
|
188
|
+
// Full page — a COUNT(*) is required to know the true total.
|
|
189
|
+
const lowerBound = startRow + recordset.length;
|
|
190
|
+
try {
|
|
191
|
+
const countResult = await this.runSqlWithDeadline(ds, countSQL, deadline);
|
|
192
|
+
return { recordset, totalRowCount: resolveAdhocTotalRowCount(countResult.recordset, lowerBound) };
|
|
193
|
+
}
|
|
194
|
+
catch (countErr) {
|
|
195
|
+
const msg = countErr instanceof Error ? countErr.message : String(countErr);
|
|
196
|
+
LogError(`Ad-hoc query row-count failed; reporting a lower-bound total (${lowerBound}). ${msg}`);
|
|
197
|
+
return { recordset, totalRowCount: lowerBound };
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Executes one SQL statement on the read-only pool, racing it against the shared
|
|
202
|
+
* wall-clock `deadline`. The timer is always cleared on completion so a settled
|
|
203
|
+
* query never leaves a dangling timeout armed.
|
|
204
|
+
*/
|
|
205
|
+
async runSqlWithDeadline(ds, sqlText, deadline) {
|
|
206
|
+
const remaining = deadline - Date.now();
|
|
207
|
+
if (remaining <= 0) {
|
|
208
|
+
throw new Error('Query timeout exceeded');
|
|
209
|
+
}
|
|
210
|
+
let timer;
|
|
211
|
+
try {
|
|
212
|
+
return await Promise.race([
|
|
213
|
+
new sql.Request(ds).query(sqlText),
|
|
214
|
+
new Promise((_, reject) => {
|
|
215
|
+
timer = setTimeout(() => reject(new Error('Query timeout exceeded')), remaining);
|
|
216
|
+
}),
|
|
217
|
+
]);
|
|
218
|
+
}
|
|
219
|
+
finally {
|
|
220
|
+
if (timer) {
|
|
221
|
+
clearTimeout(timer);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
169
225
|
buildErrorResult(errorMessage) {
|
|
170
226
|
return {
|
|
171
227
|
QueryID: '',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdhocQueryResolver.js","sourceRoot":"","sources":["../../src/resolvers/AdhocQueryResolver.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAChF,OAAO,EAAoB,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,2CAA2C,CAAC;AAE3E,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,GAAG,MAAM,OAAO,CAAC;AAExB;;;GAGG;AAEH,IAAM,eAAe,GAArB,MAAM,eAAe;CAYpB,CAAA;AAVG;IADC,KAAK,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,iEAAiE,EAAE,CAAC;;4CAC5F;AAGZ;IADC,KAAK,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,2CAA2C,EAAE,CAAC;;uDACvE;AAGxB;IADC,KAAK,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,oFAAoF,EAAE,CAAC;;gDACvH;AAGjB;IADC,KAAK,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"AdhocQueryResolver.js","sourceRoot":"","sources":["../../src/resolvers/AdhocQueryResolver.ts"],"names":[],"mappings":"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"}
|
|
@@ -68,6 +68,22 @@ export declare class GetDataAccessToken {
|
|
|
68
68
|
RequestorPayload: any;
|
|
69
69
|
TokenUses: TokenUseLog[];
|
|
70
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Removes every token whose `ExpiresAt` has already passed (and its `TokenUses`
|
|
73
|
+
* log along with it). Without this sweep `__accessTokens` grows forever — nothing
|
|
74
|
+
* else in this module ever removes an expired token, `deleteAccessToken()` has no
|
|
75
|
+
* production caller (tokens are consumed implicitly via short-lived `GetData`
|
|
76
|
+
* calls, never explicitly released by the requesting system), and `isTokenValid()`
|
|
77
|
+
* only checks the expiry timestamp without pruning it. Called from
|
|
78
|
+
* `registerAccessToken()` — the natural "allocation" moment — so the array
|
|
79
|
+
* self-bounds to roughly "tokens registered within one lifespan window" instead
|
|
80
|
+
* of growing with total lifetime registrations (Memory Leak Audit Round 7,
|
|
81
|
+
* Critical finding).
|
|
82
|
+
* @param now injectable for tests; defaults to the real current time
|
|
83
|
+
*/
|
|
84
|
+
export declare function pruneExpiredTokens(now?: Date): void;
|
|
85
|
+
/** Number of tokens currently tracked (post-sweep, at last mutation). Exposed for tests/diagnostics. */
|
|
86
|
+
export declare function getAccessTokenCount(): number;
|
|
71
87
|
export declare function registerAccessToken(token?: string, lifeSpan?: number, requestorPayload?: any): GetDataAccessToken;
|
|
72
88
|
export declare function deleteAccessToken(token: string): void;
|
|
73
89
|
export declare function tokenExists(token: string): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GetDataResolver.d.ts","sourceRoot":"","sources":["../../src/resolvers/GetDataResolver.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAWzC,qBACa,gBAAgB;IAGzB,KAAK,EAAE,MAAM,CAAC;IAGd,OAAO,EAAE,MAAM,EAAE,CAAC;CACrB;AAGD,qBACa,iBAAiB;IAE1B,OAAO,EAAE,OAAO,CAAC;IAGjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAGlB,aAAa,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;IAEjC;;OAEG;IAEH,OAAO,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;CAC9B;AAED,qBACa,sBAAsB;IAE/B,OAAO,EAAE,OAAO,CAAC;IAGjB,YAAY,CAAC,EAAE,MAAM,CAAC;IAGtB,OAAO,EAAE,sBAAsB,EAAE,CAAC;CACrC;AAED,qBACa,sBAAsB;IAE/B,EAAE,EAAE,MAAM,CAAC;IAGX,IAAI,EAAE,MAAM,CAAC;IAGb,WAAW,CAAC,EAAE,MAAM,CAAC;IAGrB,UAAU,EAAE,MAAM,CAAC;IAGnB,QAAQ,EAAE,MAAM,CAAC;IAGjB,SAAS,EAAE,MAAM,CAAC;IAGlB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,SAAS,CAAC,EAAE,MAAM,CAAC;IAGnB,MAAM,EAAE,2BAA2B,EAAE,CAAC;CACzC;AAED,qBACa,2BAA2B;IAEpC,EAAE,EAAE,MAAM,CAAC;IAGX,IAAI,EAAE,MAAM,CAAC;IAGb,WAAW,CAAC,EAAE,MAAM,CAAC;IAGrB,IAAI,EAAE,MAAM,CAAC;IAGb,UAAU,EAAE,OAAO,CAAC;IAGpB,SAAS,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,qBAAa,eAAe;IACxB;;;;OAIG;IAGG,OAAO,CACyB,KAAK,EAAE,gBAAgB,EACtD,OAAO,EAAE,UAAU,GACvB,OAAO,CAAC,iBAAiB,CAAC;IAqGvB,cAAc,CACb,OAAO,EAAE,UAAU,GACvB,OAAO,CAAC,sBAAsB,CAAC;CAsCrC;AAGD,qBAAa,WAAW;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,IAAI,CAAC;IACb,UAAU,EAAE,GAAG,CAAC;CACnB;AACD;;;;GAIG;AACH,qBAAa,kBAAkB;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;IAChB,UAAU,EAAE,IAAI,CAAC;IACjB;;OAEG;IACH,gBAAgB,EAAE,GAAG,CAAC;IACtB,SAAS,EAAE,WAAW,EAAE,CAAC;CAC5B;
|
|
1
|
+
{"version":3,"file":"GetDataResolver.d.ts","sourceRoot":"","sources":["../../src/resolvers/GetDataResolver.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAWzC,qBACa,gBAAgB;IAGzB,KAAK,EAAE,MAAM,CAAC;IAGd,OAAO,EAAE,MAAM,EAAE,CAAC;CACrB;AAGD,qBACa,iBAAiB;IAE1B,OAAO,EAAE,OAAO,CAAC;IAGjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAGlB,aAAa,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;IAEjC;;OAEG;IAEH,OAAO,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;CAC9B;AAED,qBACa,sBAAsB;IAE/B,OAAO,EAAE,OAAO,CAAC;IAGjB,YAAY,CAAC,EAAE,MAAM,CAAC;IAGtB,OAAO,EAAE,sBAAsB,EAAE,CAAC;CACrC;AAED,qBACa,sBAAsB;IAE/B,EAAE,EAAE,MAAM,CAAC;IAGX,IAAI,EAAE,MAAM,CAAC;IAGb,WAAW,CAAC,EAAE,MAAM,CAAC;IAGrB,UAAU,EAAE,MAAM,CAAC;IAGnB,QAAQ,EAAE,MAAM,CAAC;IAGjB,SAAS,EAAE,MAAM,CAAC;IAGlB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,SAAS,CAAC,EAAE,MAAM,CAAC;IAGnB,MAAM,EAAE,2BAA2B,EAAE,CAAC;CACzC;AAED,qBACa,2BAA2B;IAEpC,EAAE,EAAE,MAAM,CAAC;IAGX,IAAI,EAAE,MAAM,CAAC;IAGb,WAAW,CAAC,EAAE,MAAM,CAAC;IAGrB,IAAI,EAAE,MAAM,CAAC;IAGb,UAAU,EAAE,OAAO,CAAC;IAGpB,SAAS,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,qBAAa,eAAe;IACxB;;;;OAIG;IAGG,OAAO,CACyB,KAAK,EAAE,gBAAgB,EACtD,OAAO,EAAE,UAAU,GACvB,OAAO,CAAC,iBAAiB,CAAC;IAqGvB,cAAc,CACb,OAAO,EAAE,UAAU,GACvB,OAAO,CAAC,sBAAsB,CAAC;CAsCrC;AAGD,qBAAa,WAAW;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,IAAI,CAAC;IACb,UAAU,EAAE,GAAG,CAAC;CACnB;AACD;;;;GAIG;AACH,qBAAa,kBAAkB;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;IAChB,UAAU,EAAE,IAAI,CAAC;IACjB;;OAEG;IACH,gBAAgB,EAAE,GAAG,CAAC;IACtB,SAAS,EAAE,WAAW,EAAE,CAAC;CAC5B;AAID;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,GAAE,IAAiB,GAAG,IAAI,CAM/D;AAED,wGAAwG;AACxG,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED,wBAAgB,mBAAmB,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,QAAQ,GAAE,MAA+B,EAAE,gBAAgB,CAAC,EAAE,GAAG,GAAG,kBAAkB,CAiBzI;AACD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,QAQ9C;AACD,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,WAExC;AACD,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,WAMzC;AACD,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,QAW5D"}
|
|
@@ -305,8 +305,33 @@ export class TokenUseLog {
|
|
|
305
305
|
export class GetDataAccessToken {
|
|
306
306
|
}
|
|
307
307
|
const __accessTokens = [];
|
|
308
|
-
const __defaultTokenLifeSpan = 1000 * 60 * 5; // 5 minutes
|
|
308
|
+
const __defaultTokenLifeSpan = 1000 * 60 * 5; // 5 minutes
|
|
309
|
+
/**
|
|
310
|
+
* Removes every token whose `ExpiresAt` has already passed (and its `TokenUses`
|
|
311
|
+
* log along with it). Without this sweep `__accessTokens` grows forever — nothing
|
|
312
|
+
* else in this module ever removes an expired token, `deleteAccessToken()` has no
|
|
313
|
+
* production caller (tokens are consumed implicitly via short-lived `GetData`
|
|
314
|
+
* calls, never explicitly released by the requesting system), and `isTokenValid()`
|
|
315
|
+
* only checks the expiry timestamp without pruning it. Called from
|
|
316
|
+
* `registerAccessToken()` — the natural "allocation" moment — so the array
|
|
317
|
+
* self-bounds to roughly "tokens registered within one lifespan window" instead
|
|
318
|
+
* of growing with total lifetime registrations (Memory Leak Audit Round 7,
|
|
319
|
+
* Critical finding).
|
|
320
|
+
* @param now injectable for tests; defaults to the real current time
|
|
321
|
+
*/
|
|
322
|
+
export function pruneExpiredTokens(now = new Date()) {
|
|
323
|
+
for (let i = __accessTokens.length - 1; i >= 0; i--) {
|
|
324
|
+
if (__accessTokens[i].ExpiresAt <= now) {
|
|
325
|
+
__accessTokens.splice(i, 1);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
/** Number of tokens currently tracked (post-sweep, at last mutation). Exposed for tests/diagnostics. */
|
|
330
|
+
export function getAccessTokenCount() {
|
|
331
|
+
return __accessTokens.length;
|
|
332
|
+
}
|
|
309
333
|
export function registerAccessToken(token, lifeSpan = __defaultTokenLifeSpan, requestorPayload) {
|
|
334
|
+
pruneExpiredTokens();
|
|
310
335
|
const tokenToUse = token || uuidv4();
|
|
311
336
|
if (tokenExists(tokenToUse)) {
|
|
312
337
|
// should never happen if we used the uuidv4() function but could happen if someone tries to use a custom token
|