@luckystack/core 0.1.9 → 0.2.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/CHANGELOG.md +25 -25
- package/CLAUDE.md +243 -209
- package/LICENSE +21 -21
- package/README.md +93 -93
- package/dist/chunk-OZTB2RL2.js +699 -0
- package/dist/chunk-OZTB2RL2.js.map +1 -0
- package/dist/chunk-USQA6QTX.js +28 -0
- package/dist/chunk-USQA6QTX.js.map +1 -0
- package/dist/chunk-V2WKGTZH.js +357 -0
- package/dist/chunk-V2WKGTZH.js.map +1 -0
- package/dist/chunk-W62GGT2P.js +241 -0
- package/dist/chunk-W62GGT2P.js.map +1 -0
- package/dist/client.d.ts +57 -4
- package/dist/client.js +251 -705
- package/dist/client.js.map +1 -1
- package/dist/eslint/index.js +4 -3
- package/dist/eslint/index.js.map +1 -1
- package/dist/index.d.ts +671 -22
- package/dist/index.js +808 -1156
- package/dist/index.js.map +1 -1
- package/dist/{pageRouteValidation-CxZJVwZh.d.ts → pageRouteValidation-BoZ-rQWi.d.ts} +336 -7
- package/dist/projectConfig-JX563K43.js +16 -0
- package/dist/projectConfig-JX563K43.js.map +1 -0
- package/dist/sentrySetup-Q4BKUAQ3.js +16 -0
- package/dist/sentrySetup-Q4BKUAQ3.js.map +1 -0
- package/docs/app-bootstrap.md +418 -418
- package/docs/config-registry.md +350 -350
- package/docs/csrf-config.md +88 -88
- package/docs/error-tracker-registry.md +174 -174
- package/docs/hooks.md +298 -298
- package/docs/rate-limit-strategy.md +170 -170
- package/docs/redis-adapter.md +111 -111
- package/docs/session-types.md +180 -180
- package/docs/socket-bootstrap.md +366 -366
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1,350 +1,156 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_CSRF_CONFIG,
|
|
3
|
+
DEFAULT_PAGE_ROUTE_RULES,
|
|
4
|
+
INVALID_ERROR_RESPONSE_CODE,
|
|
5
|
+
buildApiResponseEventName,
|
|
6
|
+
buildApiStreamEventName,
|
|
7
|
+
buildGetJoinedRoomsResponseEventName,
|
|
8
|
+
buildJoinRoomResponseEventName,
|
|
9
|
+
buildLeaveRoomResponseEventName,
|
|
10
|
+
buildSyncProgressEventName,
|
|
11
|
+
buildSyncResponseEventName,
|
|
12
|
+
clearCsrfToken,
|
|
13
|
+
defaultHttpStatusForResponse,
|
|
14
|
+
enqueueApiRequest,
|
|
15
|
+
enqueueSyncRequest,
|
|
16
|
+
flushApiQueue,
|
|
17
|
+
flushSyncQueue,
|
|
18
|
+
getApiQueueSize,
|
|
19
|
+
getCsrfConfig,
|
|
20
|
+
getCsrfToken,
|
|
21
|
+
getEffectiveHttpMethod,
|
|
22
|
+
getNotifier,
|
|
23
|
+
getRegisteredApiMethod,
|
|
24
|
+
getSyncQueueSize,
|
|
25
|
+
httpFetch,
|
|
26
|
+
incrementResponseIndex,
|
|
27
|
+
inferHttpMethod,
|
|
28
|
+
isApiMethodMapRegistered,
|
|
29
|
+
isErrorParamArray,
|
|
30
|
+
isGetMethodName,
|
|
31
|
+
isMethodAllowed,
|
|
32
|
+
isOnline,
|
|
33
|
+
normalizeErrorResponseCore,
|
|
34
|
+
notify,
|
|
35
|
+
parseServiceRouteName,
|
|
36
|
+
parseTransportRouteName,
|
|
37
|
+
registerApiMethodMap,
|
|
38
|
+
registerCsrfConfig,
|
|
39
|
+
registerNotifier,
|
|
40
|
+
removeApiQueueItem,
|
|
41
|
+
removeApiQueueItemsByKey,
|
|
42
|
+
removeSyncQueueItem,
|
|
43
|
+
resetCsrfConfigForTests,
|
|
44
|
+
setSocket,
|
|
45
|
+
sleep,
|
|
46
|
+
socket,
|
|
47
|
+
socketEventNames,
|
|
48
|
+
tryCatch,
|
|
49
|
+
tryCatchSync,
|
|
50
|
+
validatePagePath,
|
|
51
|
+
waitForSocket
|
|
52
|
+
} from "./chunk-OZTB2RL2.js";
|
|
53
|
+
import {
|
|
54
|
+
DEFAULT_PROJECT_CONFIG,
|
|
55
|
+
deepMerge,
|
|
56
|
+
getProjectConfig,
|
|
57
|
+
getProjectName,
|
|
58
|
+
isPlainObject,
|
|
59
|
+
isProjectConfigRegistered,
|
|
60
|
+
registerProjectConfig
|
|
61
|
+
} from "./chunk-W62GGT2P.js";
|
|
62
|
+
import {
|
|
63
|
+
DEFAULT_REDACTED_LOG_KEYS,
|
|
64
|
+
DEPTH_TRUNCATED_PLACEHOLDER,
|
|
65
|
+
REDACTED_PLACEHOLDER,
|
|
66
|
+
appendErrorTracker,
|
|
67
|
+
captureException,
|
|
68
|
+
captureExceptionAcrossTrackers,
|
|
69
|
+
captureMessage,
|
|
70
|
+
captureMessageAcrossTrackers,
|
|
71
|
+
createDevLogger,
|
|
72
|
+
flushErrorTrackers,
|
|
73
|
+
getActiveErrorTrackers,
|
|
74
|
+
getCurrentErrorTrackerIdentity,
|
|
75
|
+
getLogger,
|
|
76
|
+
getRedactedLogKeys,
|
|
77
|
+
initSharedSentry,
|
|
78
|
+
isLoggerRegistered,
|
|
79
|
+
isRedactedLogKey,
|
|
80
|
+
recordMetricAcrossTrackers,
|
|
81
|
+
registerErrorTracker,
|
|
82
|
+
registerErrorTrackers,
|
|
83
|
+
registerLogger,
|
|
84
|
+
registerPreCaptureFilter,
|
|
85
|
+
registerRedactedLogKeys,
|
|
86
|
+
resetLoggerForTests,
|
|
87
|
+
resetRedactedLogKeysForTests,
|
|
88
|
+
runWithErrorTrackerIdentity,
|
|
89
|
+
runWithErrorTrackerIdentityScope,
|
|
90
|
+
sanitizeErrorString,
|
|
91
|
+
sanitizeErrorStrings,
|
|
92
|
+
sanitizeForLog,
|
|
93
|
+
setCurrentErrorTrackerIdentity,
|
|
94
|
+
setErrorTrackerUser,
|
|
95
|
+
setSentryUser,
|
|
96
|
+
startSpan,
|
|
97
|
+
startSpanAcrossTrackers,
|
|
98
|
+
startSpanHandle
|
|
99
|
+
} from "./chunk-V2WKGTZH.js";
|
|
100
|
+
import {
|
|
101
|
+
createRegistry
|
|
102
|
+
} from "./chunk-USQA6QTX.js";
|
|
5
103
|
|
|
6
|
-
// src/
|
|
7
|
-
var
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
};
|
|
14
|
-
var
|
|
15
|
-
var captureExceptionAcrossTrackers = (error, context) => {
|
|
16
|
-
for (const tracker of activeTrackers) {
|
|
17
|
-
try {
|
|
18
|
-
tracker.captureException(error, context);
|
|
19
|
-
} catch {
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
};
|
|
23
|
-
var captureMessageAcrossTrackers = (message, level, context) => {
|
|
24
|
-
for (const tracker of activeTrackers) {
|
|
25
|
-
try {
|
|
26
|
-
tracker.captureMessage(message, level, context);
|
|
27
|
-
} catch {
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
var setErrorTrackerUser = (user) => {
|
|
32
|
-
for (const tracker of activeTrackers) {
|
|
33
|
-
try {
|
|
34
|
-
tracker.setUser(user);
|
|
35
|
-
} catch {
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
var recordMetricAcrossTrackers = (name, value, tags) => {
|
|
40
|
-
for (const tracker of activeTrackers) {
|
|
41
|
-
if (tracker.recordMetric) {
|
|
42
|
-
try {
|
|
43
|
-
tracker.recordMetric(name, value, tags);
|
|
44
|
-
} catch {
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
var startSpanAcrossTrackers = (name, op, fn) => {
|
|
50
|
-
const first = activeTrackers.find((t) => t.startSpan);
|
|
51
|
-
if (!first?.startSpan) return fn();
|
|
52
|
-
return first.startSpan(name, op, fn);
|
|
53
|
-
};
|
|
104
|
+
// src/escapeHtml.ts
|
|
105
|
+
var HTML_ESCAPE_MAP = {
|
|
106
|
+
"&": "&",
|
|
107
|
+
"<": "<",
|
|
108
|
+
">": ">",
|
|
109
|
+
'"': """,
|
|
110
|
+
"'": "'"
|
|
111
|
+
};
|
|
112
|
+
var escapeHtml = (str) => str.replaceAll(/[&<>"']/g, (c) => HTML_ESCAPE_MAP[c] ?? c);
|
|
54
113
|
|
|
55
|
-
// src/
|
|
56
|
-
|
|
57
|
-
var
|
|
58
|
-
|
|
59
|
-
};
|
|
60
|
-
var captureException = (error, context) => {
|
|
61
|
-
if (sentry) {
|
|
62
|
-
if (context) {
|
|
63
|
-
sentry.captureException(error, { extra: context });
|
|
64
|
-
} else {
|
|
65
|
-
sentry.captureException(error);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
captureExceptionAcrossTrackers(error, context);
|
|
69
|
-
};
|
|
70
|
-
var captureMessage = (message, level = "info", context) => {
|
|
71
|
-
if (sentry) {
|
|
72
|
-
if (context) {
|
|
73
|
-
sentry.captureMessage(message, { level, extra: context });
|
|
74
|
-
} else {
|
|
75
|
-
sentry.captureMessage(message, level);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
captureMessageAcrossTrackers(message, level, context);
|
|
79
|
-
};
|
|
80
|
-
var setSentryUser = (user) => {
|
|
81
|
-
sentry?.setUser(user);
|
|
82
|
-
setErrorTrackerUser(user);
|
|
83
|
-
};
|
|
84
|
-
var startSpan = (name, op) => {
|
|
85
|
-
if (sentry?.startInactiveSpan) {
|
|
86
|
-
return sentry.startInactiveSpan({ name, op });
|
|
87
|
-
}
|
|
88
|
-
startSpanAcrossTrackers(name, op, () => {
|
|
89
|
-
});
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
// src/tryCatch.ts
|
|
93
|
-
async function tryCatch(func, params, context) {
|
|
114
|
+
// src/peerDeps.ts
|
|
115
|
+
import { createRequire } from "module";
|
|
116
|
+
var defaultRequire = createRequire(import.meta.url);
|
|
117
|
+
var ensurePeerDepInstalled = (packageName, hint, requireFn = defaultRequire) => {
|
|
94
118
|
try {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// src/serviceRoute.ts
|
|
104
|
-
var VERSION_SEGMENT_REGEX = /^v\d+$/;
|
|
105
|
-
var normalizeRawRoute = (value) => {
|
|
106
|
-
return value.trim().replaceAll("\\", "/").replaceAll(/^\/+/g, "").replaceAll(/\/+$/g, "");
|
|
107
|
-
};
|
|
108
|
-
var hasEmptySegments = (value) => {
|
|
109
|
-
return value.split("/").some((segment) => segment.length === 0);
|
|
110
|
-
};
|
|
111
|
-
var parseServiceRouteName = (value) => {
|
|
112
|
-
const normalized = normalizeRawRoute(value);
|
|
113
|
-
if (!normalized) {
|
|
114
|
-
return { status: "error", reason: "Route name cannot be empty." };
|
|
115
|
-
}
|
|
116
|
-
if (hasEmptySegments(normalized)) {
|
|
117
|
-
return { status: "error", reason: "Route name cannot contain empty segments." };
|
|
118
|
-
}
|
|
119
|
-
const segments = normalized.split("/");
|
|
120
|
-
if (segments.length < 2) {
|
|
121
|
-
return { status: "error", reason: "Route name must include service and route segments." };
|
|
122
|
-
}
|
|
123
|
-
const [service, ...routeSegments] = segments;
|
|
124
|
-
const routeName = routeSegments.join("/");
|
|
125
|
-
if (!service || !routeName) {
|
|
126
|
-
return { status: "error", reason: "Route name must include service and route segments." };
|
|
127
|
-
}
|
|
128
|
-
return {
|
|
129
|
-
status: "success",
|
|
130
|
-
normalizedRouteName: `${service}/${routeName}`,
|
|
131
|
-
service,
|
|
132
|
-
routeName
|
|
133
|
-
};
|
|
134
|
-
};
|
|
135
|
-
var parseTransportRouteName = ({
|
|
136
|
-
value,
|
|
137
|
-
prefix
|
|
138
|
-
}) => {
|
|
139
|
-
const normalized = normalizeRawRoute(value);
|
|
140
|
-
if (!normalized) {
|
|
141
|
-
return { status: "error", reason: "Transport route cannot be empty." };
|
|
142
|
-
}
|
|
143
|
-
if (hasEmptySegments(normalized)) {
|
|
144
|
-
return { status: "error", reason: "Transport route cannot contain empty segments." };
|
|
145
|
-
}
|
|
146
|
-
const segments = normalized.split("/");
|
|
147
|
-
const routeSegments = segments[0] === prefix ? segments.slice(1) : segments;
|
|
148
|
-
if (routeSegments.length < 3) {
|
|
149
|
-
return {
|
|
150
|
-
status: "error",
|
|
151
|
-
reason: `Transport route must use ${prefix}/{service}/{name}/{version} format.`
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
const version = routeSegments.at(-1);
|
|
155
|
-
if (!version || !VERSION_SEGMENT_REGEX.test(version)) {
|
|
156
|
-
return { status: "error", reason: "Route version segment must match v{number}." };
|
|
157
|
-
}
|
|
158
|
-
const serviceRouteToken = routeSegments.slice(0, -1).join("/");
|
|
159
|
-
const serviceRoute = parseServiceRouteName(serviceRouteToken);
|
|
160
|
-
if (serviceRoute.status === "error") {
|
|
161
|
-
return {
|
|
162
|
-
status: "error",
|
|
163
|
-
reason: serviceRoute.reason
|
|
164
|
-
};
|
|
119
|
+
requireFn.resolve(packageName);
|
|
120
|
+
} catch {
|
|
121
|
+
throw new Error(
|
|
122
|
+
`The \`${packageName}\` package is not installed but a feature that needs it was used. ${hint}`
|
|
123
|
+
);
|
|
165
124
|
}
|
|
166
|
-
return {
|
|
167
|
-
status: "success",
|
|
168
|
-
normalizedFullName: `${prefix}/${serviceRoute.normalizedRouteName}/${version}`,
|
|
169
|
-
version,
|
|
170
|
-
serviceRoute
|
|
171
|
-
};
|
|
172
125
|
};
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
return `${prefix}${String(responseIndex2)}`;
|
|
177
|
-
};
|
|
178
|
-
var socketEventNames = {
|
|
179
|
-
apiRequest: "apiRequest",
|
|
180
|
-
apiCancel: "apiCancel",
|
|
181
|
-
sync: "sync",
|
|
182
|
-
syncCancel: "syncCancel",
|
|
183
|
-
joinRoom: "joinRoom",
|
|
184
|
-
leaveRoom: "leaveRoom",
|
|
185
|
-
getJoinedRooms: "getJoinedRooms",
|
|
186
|
-
updateLocation: "updateLocation",
|
|
187
|
-
updateSession: "updateSession",
|
|
188
|
-
sessionReplaced: "sessionReplaced",
|
|
189
|
-
logout: "logout",
|
|
190
|
-
intentionalDisconnect: "intentionalDisconnect",
|
|
191
|
-
intentionalReconnect: "intentionalReconnect",
|
|
192
|
-
userAfk: "userAfk",
|
|
193
|
-
userBack: "userBack",
|
|
194
|
-
connect: "connect",
|
|
195
|
-
disconnect: "disconnect",
|
|
196
|
-
reconnectAttempt: "reconnect_attempt",
|
|
197
|
-
connectError: "connect_error",
|
|
198
|
-
apiResponsePrefix: "apiResponse-",
|
|
199
|
-
apiStreamPrefix: "apiStream-",
|
|
200
|
-
syncResponsePrefix: "sync-",
|
|
201
|
-
syncProgressPrefix: "sync-progress-",
|
|
202
|
-
joinRoomResponsePrefix: "joinRoom-",
|
|
203
|
-
leaveRoomResponsePrefix: "leaveRoom-",
|
|
204
|
-
getJoinedRoomsResponsePrefix: "getJoinedRooms-"
|
|
205
|
-
};
|
|
206
|
-
var buildApiResponseEventName = (responseIndex2) => {
|
|
207
|
-
return withIndex(socketEventNames.apiResponsePrefix, responseIndex2);
|
|
208
|
-
};
|
|
209
|
-
var buildApiStreamEventName = (responseIndex2) => {
|
|
210
|
-
return withIndex(socketEventNames.apiStreamPrefix, responseIndex2);
|
|
211
|
-
};
|
|
212
|
-
var buildSyncResponseEventName = (responseIndex2) => {
|
|
213
|
-
return withIndex(socketEventNames.syncResponsePrefix, responseIndex2);
|
|
214
|
-
};
|
|
215
|
-
var buildSyncProgressEventName = (responseIndex2) => {
|
|
216
|
-
return withIndex(socketEventNames.syncProgressPrefix, responseIndex2);
|
|
217
|
-
};
|
|
218
|
-
var buildJoinRoomResponseEventName = (responseIndex2) => {
|
|
219
|
-
return withIndex(socketEventNames.joinRoomResponsePrefix, responseIndex2);
|
|
220
|
-
};
|
|
221
|
-
var buildLeaveRoomResponseEventName = (responseIndex2) => {
|
|
222
|
-
return withIndex(socketEventNames.leaveRoomResponsePrefix, responseIndex2);
|
|
223
|
-
};
|
|
224
|
-
var buildGetJoinedRoomsResponseEventName = (responseIndex2) => {
|
|
225
|
-
return withIndex(socketEventNames.getJoinedRoomsResponsePrefix, responseIndex2);
|
|
126
|
+
var loadPeer = (packageName, hint, requireFn = defaultRequire) => {
|
|
127
|
+
ensurePeerDepInstalled(packageName, hint, requireFn);
|
|
128
|
+
return requireFn(packageName);
|
|
226
129
|
};
|
|
227
130
|
|
|
228
|
-
// src/
|
|
229
|
-
var
|
|
230
|
-
var
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
var
|
|
236
|
-
|
|
237
|
-
fallbackHttpStatus,
|
|
238
|
-
fallbackErrorCode,
|
|
239
|
-
resolveMessage
|
|
240
|
-
}) => {
|
|
241
|
-
const normalizedParams = isErrorParamArray(response.errorParams) ? response.errorParams : void 0;
|
|
242
|
-
const errorCodeValue = typeof response.errorCode === "string" ? response.errorCode.trim() : "";
|
|
243
|
-
const hasErrorCode = errorCodeValue.length > 0;
|
|
244
|
-
const finalErrorCode = hasErrorCode ? errorCodeValue : fallbackErrorCode ?? INVALID_ERROR_RESPONSE_CODE;
|
|
245
|
-
const providedHttpStatus = typeof response.httpStatus === "number" ? response.httpStatus : void 0;
|
|
246
|
-
const message = resolveMessage ? resolveMessage({ errorCode: finalErrorCode, errorParams: normalizedParams }) : finalErrorCode;
|
|
247
|
-
return {
|
|
248
|
-
status: "error",
|
|
249
|
-
message,
|
|
250
|
-
errorCode: finalErrorCode,
|
|
251
|
-
errorParams: normalizedParams,
|
|
252
|
-
httpStatus: providedHttpStatus ?? fallbackHttpStatus
|
|
253
|
-
};
|
|
254
|
-
};
|
|
255
|
-
var defaultHttpStatusForResponse = ({
|
|
256
|
-
status,
|
|
257
|
-
explicitHttpStatus,
|
|
258
|
-
fallbackErrorStatus = 400
|
|
259
|
-
}) => {
|
|
260
|
-
if (typeof explicitHttpStatus === "number") return explicitHttpStatus;
|
|
261
|
-
return status === "success" ? 200 : fallbackErrorStatus;
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
// src/loggerRegistry.ts
|
|
265
|
-
var defaultLogger = {
|
|
266
|
-
debug: (message, context) => {
|
|
267
|
-
if (context === void 0) {
|
|
268
|
-
console.debug(message);
|
|
269
|
-
} else {
|
|
270
|
-
console.debug(message, context);
|
|
271
|
-
}
|
|
272
|
-
},
|
|
273
|
-
info: (message, context) => {
|
|
274
|
-
if (context === void 0) {
|
|
275
|
-
console.info(message);
|
|
276
|
-
} else {
|
|
277
|
-
console.info(message, context);
|
|
278
|
-
}
|
|
279
|
-
},
|
|
280
|
-
warn: (message, context) => {
|
|
281
|
-
if (context === void 0) {
|
|
282
|
-
console.warn(message);
|
|
283
|
-
} else {
|
|
284
|
-
console.warn(message, context);
|
|
285
|
-
}
|
|
286
|
-
},
|
|
287
|
-
error: (message, error, context) => {
|
|
288
|
-
if (error !== void 0 && context !== void 0) console.error(message, error, context);
|
|
289
|
-
else if (error !== void 0) console.error(message, error);
|
|
290
|
-
else if (context === void 0) {
|
|
291
|
-
console.error(message);
|
|
292
|
-
} else {
|
|
293
|
-
console.error(message, context);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
};
|
|
297
|
-
var createDevLogger = () => ({
|
|
298
|
-
debug: (message, context) => {
|
|
299
|
-
if (context === void 0) {
|
|
300
|
-
console.log(message, "cyan");
|
|
301
|
-
} else {
|
|
302
|
-
console.log(message, context, "cyan");
|
|
303
|
-
}
|
|
304
|
-
},
|
|
305
|
-
info: (message, context) => {
|
|
306
|
-
if (context === void 0) {
|
|
307
|
-
console.log(message);
|
|
308
|
-
} else {
|
|
309
|
-
console.log(message, context);
|
|
310
|
-
}
|
|
311
|
-
},
|
|
312
|
-
warn: (message, context) => {
|
|
313
|
-
if (context === void 0) {
|
|
314
|
-
console.log(message, "yellow");
|
|
315
|
-
} else {
|
|
316
|
-
console.log(message, context, "yellow");
|
|
317
|
-
}
|
|
318
|
-
},
|
|
319
|
-
error: (message, error, context) => {
|
|
320
|
-
if (error !== void 0 && context !== void 0) console.log(message, error, context, "red");
|
|
321
|
-
else if (error !== void 0) console.log(message, error, "red");
|
|
322
|
-
else if (context === void 0) {
|
|
323
|
-
console.log(message, "red");
|
|
324
|
-
} else {
|
|
325
|
-
console.log(message, context, "red");
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
});
|
|
329
|
-
var activeLogger = defaultLogger;
|
|
330
|
-
var isRegistered = false;
|
|
331
|
-
var registerLogger = (logger) => {
|
|
332
|
-
activeLogger = logger;
|
|
333
|
-
isRegistered = true;
|
|
334
|
-
};
|
|
335
|
-
var getLogger = () => activeLogger;
|
|
336
|
-
var isLoggerRegistered = () => isRegistered;
|
|
337
|
-
var resetLoggerForTests = () => {
|
|
338
|
-
activeLogger = defaultLogger;
|
|
339
|
-
isRegistered = false;
|
|
340
|
-
};
|
|
131
|
+
// src/roomNameFormatterRegistry.ts
|
|
132
|
+
var identityFormatter = (rawRoomName) => rawRoomName;
|
|
133
|
+
var registry = createRegistry(identityFormatter);
|
|
134
|
+
var registerRoomNameFormatter = (formatter2) => {
|
|
135
|
+
registry.register(formatter2);
|
|
136
|
+
};
|
|
137
|
+
var getRoomNameFormatter = () => registry.get();
|
|
138
|
+
var formatRoomName = (rawRoomName, ctx) => registry.get()(rawRoomName, ctx);
|
|
139
|
+
var defaultRoomNameFormatter = identityFormatter;
|
|
341
140
|
|
|
342
141
|
// src/hooks/registry.ts
|
|
343
142
|
var hookHandlers = /* @__PURE__ */ new Map();
|
|
344
143
|
var registerHook = (name, handler) => {
|
|
345
144
|
const existing = hookHandlers.get(name) ?? [];
|
|
346
|
-
|
|
145
|
+
const wrapped = handler;
|
|
146
|
+
existing.push(wrapped);
|
|
347
147
|
hookHandlers.set(name, existing);
|
|
148
|
+
return () => {
|
|
149
|
+
const handlers = hookHandlers.get(name);
|
|
150
|
+
if (!handlers) return;
|
|
151
|
+
const index = handlers.indexOf(wrapped);
|
|
152
|
+
if (index !== -1) handlers.splice(index, 1);
|
|
153
|
+
};
|
|
348
154
|
};
|
|
349
155
|
var dispatchHook = async (name, payload) => {
|
|
350
156
|
const handlers = hookHandlers.get(name) ?? [];
|
|
@@ -353,8 +159,14 @@ var dispatchHook = async (name, payload) => {
|
|
|
353
159
|
try {
|
|
354
160
|
result = await handler(payload);
|
|
355
161
|
} catch (error) {
|
|
356
|
-
|
|
357
|
-
|
|
162
|
+
try {
|
|
163
|
+
getLogger().error(`hook: handler for "${name}" threw`, error, { hook: name });
|
|
164
|
+
} catch {
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
captureException(error, { hook: name });
|
|
168
|
+
} catch {
|
|
169
|
+
}
|
|
358
170
|
continue;
|
|
359
171
|
}
|
|
360
172
|
if (result !== void 0) {
|
|
@@ -370,8 +182,15 @@ var clearAllHooks = () => {
|
|
|
370
182
|
var syncHookHandlers = /* @__PURE__ */ new Map();
|
|
371
183
|
var registerSyncHook = (name, handler) => {
|
|
372
184
|
const existing = syncHookHandlers.get(name) ?? [];
|
|
373
|
-
|
|
185
|
+
const wrapped = handler;
|
|
186
|
+
existing.push(wrapped);
|
|
374
187
|
syncHookHandlers.set(name, existing);
|
|
188
|
+
return () => {
|
|
189
|
+
const handlers = syncHookHandlers.get(name);
|
|
190
|
+
if (!handlers) return;
|
|
191
|
+
const index = handlers.indexOf(wrapped);
|
|
192
|
+
if (index !== -1) handlers.splice(index, 1);
|
|
193
|
+
};
|
|
375
194
|
};
|
|
376
195
|
var dispatchSyncHook = (name, payload) => {
|
|
377
196
|
const handlers = syncHookHandlers.get(name) ?? [];
|
|
@@ -379,8 +198,14 @@ var dispatchSyncHook = (name, payload) => {
|
|
|
379
198
|
try {
|
|
380
199
|
handler(payload);
|
|
381
200
|
} catch (error) {
|
|
382
|
-
|
|
383
|
-
|
|
201
|
+
try {
|
|
202
|
+
getLogger().error(`hook: sync handler for "${name}" threw`, error, { hook: name });
|
|
203
|
+
} catch {
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
captureException(error, { hook: name });
|
|
207
|
+
} catch {
|
|
208
|
+
}
|
|
384
209
|
}
|
|
385
210
|
}
|
|
386
211
|
};
|
|
@@ -467,81 +292,33 @@ var resolveErrorMessage = (input) => activeNormalizer.resolveErrorMessage(input)
|
|
|
467
292
|
var extractLanguageFromHeader = (header) => activeNormalizer.extractLanguageFromHeader(header);
|
|
468
293
|
|
|
469
294
|
// src/errorFormatterRegistry.ts
|
|
470
|
-
var
|
|
295
|
+
var registry2 = createRegistry(null);
|
|
471
296
|
var registerErrorFormatter = (formatter2) => {
|
|
472
|
-
|
|
297
|
+
registry2.register(formatter2);
|
|
473
298
|
};
|
|
474
|
-
var getErrorFormatter = () =>
|
|
299
|
+
var getErrorFormatter = () => registry2.get();
|
|
475
300
|
var applyErrorFormatter = (input) => {
|
|
476
301
|
const { response, routeName, transport, userId, perRouteFormatter } = input;
|
|
477
302
|
if (response.status !== "error") return response;
|
|
478
303
|
const ctx = { routeName, transport, userId };
|
|
479
304
|
const errorEnvelope = response;
|
|
480
305
|
if (perRouteFormatter) {
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
306
|
+
const [error, formatted] = tryCatchSync(() => perRouteFormatter(errorEnvelope, ctx));
|
|
307
|
+
if (!error && formatted) return formatted;
|
|
308
|
+
if (error) {
|
|
484
309
|
console.error(`[errorFormatter] per-route formatter for ${routeName} threw:`, error);
|
|
485
310
|
}
|
|
486
311
|
}
|
|
487
|
-
const globalFormatter =
|
|
312
|
+
const globalFormatter = registry2.get();
|
|
488
313
|
if (globalFormatter) {
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
return response;
|
|
494
|
-
}
|
|
314
|
+
const [error, formatted] = tryCatchSync(() => globalFormatter(errorEnvelope, ctx));
|
|
315
|
+
if (!error && formatted) return formatted;
|
|
316
|
+
console.error(`[errorFormatter] global formatter threw on ${routeName}:`, error);
|
|
317
|
+
return response;
|
|
495
318
|
}
|
|
496
319
|
return response;
|
|
497
320
|
};
|
|
498
321
|
|
|
499
|
-
// src/pageRouteValidation.ts
|
|
500
|
-
var DEFAULT_PAGE_ROUTE_RULES = {
|
|
501
|
-
privateFolderPrefix: "_",
|
|
502
|
-
scaffoldIgnoredFolders: [
|
|
503
|
-
"_api",
|
|
504
|
-
"_sync",
|
|
505
|
-
"_function",
|
|
506
|
-
"_functions",
|
|
507
|
-
"_component",
|
|
508
|
-
"_components",
|
|
509
|
-
"_provider",
|
|
510
|
-
"_providers",
|
|
511
|
-
"_locale",
|
|
512
|
-
"_locales",
|
|
513
|
-
"_socket",
|
|
514
|
-
"_sockets",
|
|
515
|
-
"_shared",
|
|
516
|
-
"_server"
|
|
517
|
-
]
|
|
518
|
-
};
|
|
519
|
-
var validatePagePath = (srcRelativePath, rules = DEFAULT_PAGE_ROUTE_RULES) => {
|
|
520
|
-
const normalized = srcRelativePath.replaceAll("\\", "/").replace(/^\.\//, "").replace(/^\/+/, "");
|
|
521
|
-
const segments = normalized.split("/").filter((segment) => segment.length > 0);
|
|
522
|
-
if (segments.length === 0) {
|
|
523
|
-
return { valid: false, reason: "empty path" };
|
|
524
|
-
}
|
|
525
|
-
const filename = segments.at(-1);
|
|
526
|
-
if (filename !== "page.tsx" && filename !== "page.jsx") {
|
|
527
|
-
return { valid: false, reason: "not a page file (expected page.tsx)" };
|
|
528
|
-
}
|
|
529
|
-
const folderSegments = segments.slice(0, -1);
|
|
530
|
-
const prefix = rules.privateFolderPrefix;
|
|
531
|
-
const reserved = rules.scaffoldIgnoredFolders;
|
|
532
|
-
for (const reservedName of reserved) {
|
|
533
|
-
if (folderSegments.includes(reservedName)) {
|
|
534
|
-
return { valid: false, reason: `page.tsx cannot live inside reserved folder ${reservedName}` };
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
const visibleSegments = folderSegments.filter((segment) => !segment.startsWith(prefix));
|
|
538
|
-
if (folderSegments.length > 0 && visibleSegments.length === 0) {
|
|
539
|
-
return { valid: false, reason: "no URL segment left after stripping underscore folders" };
|
|
540
|
-
}
|
|
541
|
-
const route = visibleSegments.length === 0 ? "/" : `/${visibleSegments.join("/")}`;
|
|
542
|
-
return { valid: true, route };
|
|
543
|
-
};
|
|
544
|
-
|
|
545
322
|
// src/socketTypes.ts
|
|
546
323
|
var _ioInstance = null;
|
|
547
324
|
var setIoInstance = (io) => {
|
|
@@ -549,288 +326,80 @@ var setIoInstance = (io) => {
|
|
|
549
326
|
};
|
|
550
327
|
var getIoInstance = () => _ioInstance;
|
|
551
328
|
|
|
552
|
-
// src/projectConfig.ts
|
|
553
|
-
var DEFAULT_PROJECT_CONFIG = {
|
|
554
|
-
app: {
|
|
555
|
-
publicUrl: ""
|
|
556
|
-
},
|
|
557
|
-
logging: {
|
|
558
|
-
devLogs: false,
|
|
559
|
-
devNotifications: false,
|
|
560
|
-
socketStatus: false,
|
|
561
|
-
socketStartup: false,
|
|
562
|
-
stream: false
|
|
563
|
-
},
|
|
564
|
-
rateLimiting: {
|
|
565
|
-
enabled: true,
|
|
566
|
-
store: "memory",
|
|
567
|
-
redisKeyPrefix: "rate-limit",
|
|
568
|
-
defaultApiLimit: 60,
|
|
569
|
-
defaultIpLimit: 100,
|
|
570
|
-
windowMs: 6e4,
|
|
571
|
-
cleanupIntervalMs: 6e4
|
|
572
|
-
},
|
|
573
|
-
session: {
|
|
574
|
-
basedToken: false,
|
|
575
|
-
expiryDays: 7,
|
|
576
|
-
allowMultiple: false,
|
|
577
|
-
perBrowser: "single",
|
|
578
|
-
perUser: "single",
|
|
579
|
-
maxConcurrentPerUser: null,
|
|
580
|
-
onConflict: "revokeOld",
|
|
581
|
-
notifyOldDeviceOnRevoke: true,
|
|
582
|
-
//? Empty default — `getProjectName()` reads `process.env.PROJECT_NAME` at
|
|
583
|
-
//? call time and falls back to `'luckystack'`. Avoids capturing env at
|
|
584
|
-
//? module-load before dotenv runs.
|
|
585
|
-
projectName: ""
|
|
586
|
-
},
|
|
587
|
-
http: {
|
|
588
|
-
sessionCookieName: "token",
|
|
589
|
-
sessionCookieSameSite: "Strict",
|
|
590
|
-
sessionCookiePath: "/",
|
|
591
|
-
requestBodyMaxBytes: 1024 * 1024,
|
|
592
|
-
healthEndpoint: "/_health",
|
|
593
|
-
liveEndpoint: "/livez",
|
|
594
|
-
readyEndpoint: "/readyz",
|
|
595
|
-
testResetEndpoint: "/_test/reset",
|
|
596
|
-
stream: {
|
|
597
|
-
queryParam: "stream",
|
|
598
|
-
enabledValue: "true",
|
|
599
|
-
connectedComment: ": connected"
|
|
600
|
-
},
|
|
601
|
-
securityHeaders: {
|
|
602
|
-
frameOptions: "SAMEORIGIN",
|
|
603
|
-
referrerPolicy: "no-referrer",
|
|
604
|
-
xssProtection: "1; mode=block",
|
|
605
|
-
contentTypeOptions: "nosniff"
|
|
606
|
-
},
|
|
607
|
-
cors: {
|
|
608
|
-
allowedMethods: "GET, POST, PUT, DELETE, OPTIONS",
|
|
609
|
-
allowedHeaders: "Content-Type, Authorization, X-Session-Based-Token, X-CSRF-Token, X-Request-Id",
|
|
610
|
-
exposedHeaders: "X-Session-Token, X-Request-Id",
|
|
611
|
-
credentials: true,
|
|
612
|
-
allowedOrigins: [],
|
|
613
|
-
allowLocalhost: false
|
|
614
|
-
}
|
|
615
|
-
},
|
|
616
|
-
auth: {
|
|
617
|
-
oauthStateTtlSeconds: 60 * 10,
|
|
618
|
-
passwordMinLength: 8,
|
|
619
|
-
passwordMaxLength: 191,
|
|
620
|
-
passwordPolicy: {
|
|
621
|
-
minLength: 8,
|
|
622
|
-
maxLength: 191,
|
|
623
|
-
requireUppercase: false,
|
|
624
|
-
requireLowercase: false,
|
|
625
|
-
requireNumber: false,
|
|
626
|
-
requireSpecial: false,
|
|
627
|
-
forbidCommon: true
|
|
628
|
-
},
|
|
629
|
-
emailMaxLength: 191,
|
|
630
|
-
nameMaxLength: 191,
|
|
631
|
-
bcryptRounds: 10,
|
|
632
|
-
providerAccountStrategy: "per-provider",
|
|
633
|
-
forgotPassword: "disabled",
|
|
634
|
-
passwordResetTtlSeconds: 60 * 60,
|
|
635
|
-
emailChangeTtlSeconds: 60 * 60
|
|
636
|
-
},
|
|
637
|
-
socket: {
|
|
638
|
-
maxHttpBufferSize: 5 * 1024 * 1024,
|
|
639
|
-
pingTimeout: 2e4,
|
|
640
|
-
pingInterval: 25e3
|
|
641
|
-
},
|
|
642
|
-
sync: {
|
|
643
|
-
streamThrottle: {
|
|
644
|
-
flushAtChars: 32,
|
|
645
|
-
flushEveryMs: 50,
|
|
646
|
-
field: "chunk"
|
|
647
|
-
},
|
|
648
|
-
fanoutYieldEvery: 100,
|
|
649
|
-
fanoutYieldMs: 1
|
|
650
|
-
},
|
|
651
|
-
offlineQueue: {
|
|
652
|
-
maxSize: 200,
|
|
653
|
-
maxAgeMs: 60 * 60 * 1e3,
|
|
654
|
-
dropPolicy: "drop-oldest"
|
|
655
|
-
},
|
|
656
|
-
dev: {
|
|
657
|
-
hotReloadDebounceMs: 120,
|
|
658
|
-
watcherStabilityThresholdMs: 120,
|
|
659
|
-
watcherPollIntervalMs: 20
|
|
660
|
-
},
|
|
661
|
-
paths: {
|
|
662
|
-
srcDir: "src",
|
|
663
|
-
serverDir: "server",
|
|
664
|
-
sharedDir: "shared",
|
|
665
|
-
uploadsDir: "uploads",
|
|
666
|
-
publicDir: "public",
|
|
667
|
-
serverFunctionsDir: "server/functions",
|
|
668
|
-
serverFunctionDirs: ["functions", "shared"],
|
|
669
|
-
generatedSocketTypes: "src/_sockets/apiTypes.generated.ts",
|
|
670
|
-
generatedApiSchemas: "src/_sockets/apiInputSchemas.generated.ts",
|
|
671
|
-
generatedApiDocs: "src/docs/apiDocs.generated.json"
|
|
672
|
-
},
|
|
673
|
-
defaultLanguage: "en",
|
|
674
|
-
defaultTheme: "light",
|
|
675
|
-
socketActivityBroadcaster: false,
|
|
676
|
-
socketStatusIndicator: false,
|
|
677
|
-
locationProviderEnabled: false,
|
|
678
|
-
loginRedirectUrl: "/"
|
|
679
|
-
};
|
|
680
|
-
var isPlainObject = (value) => {
|
|
681
|
-
if (value === null || typeof value !== "object") return false;
|
|
682
|
-
const proto = Object.getPrototypeOf(value);
|
|
683
|
-
return proto === Object.prototype || proto === null;
|
|
684
|
-
};
|
|
685
|
-
var deepMerge = (base, override) => {
|
|
686
|
-
if (override === void 0) return base;
|
|
687
|
-
if (!isPlainObject(base) || !isPlainObject(override)) {
|
|
688
|
-
return override ?? base;
|
|
689
|
-
}
|
|
690
|
-
const result = { ...base };
|
|
691
|
-
for (const [key, overrideValue] of Object.entries(override)) {
|
|
692
|
-
if (overrideValue === void 0) continue;
|
|
693
|
-
const baseValue = base[key];
|
|
694
|
-
result[key] = isPlainObject(baseValue) && isPlainObject(overrideValue) ? deepMerge(baseValue, overrideValue) : overrideValue;
|
|
695
|
-
}
|
|
696
|
-
return result;
|
|
697
|
-
};
|
|
698
|
-
var activeConfig = DEFAULT_PROJECT_CONFIG;
|
|
699
|
-
var isRegistered2 = false;
|
|
700
|
-
var registerProjectConfig = (config) => {
|
|
701
|
-
activeConfig = deepMerge(DEFAULT_PROJECT_CONFIG, config);
|
|
702
|
-
isRegistered2 = true;
|
|
703
|
-
};
|
|
704
|
-
var getProjectConfig = () => activeConfig;
|
|
705
|
-
var getProjectName = () => {
|
|
706
|
-
const fromConfig = activeConfig.session.projectName;
|
|
707
|
-
if (fromConfig && fromConfig.length > 0) return fromConfig;
|
|
708
|
-
const fromEnv = process.env.PROJECT_NAME;
|
|
709
|
-
if (fromEnv && fromEnv.length > 0) return fromEnv;
|
|
710
|
-
return "luckystack";
|
|
711
|
-
};
|
|
712
|
-
var isProjectConfigRegistered = () => isRegistered2;
|
|
713
|
-
|
|
714
329
|
// src/runtimeMapsRegistry.ts
|
|
715
330
|
var emptyApi = { apisObject: {}, functionsObject: {} };
|
|
716
331
|
var emptySync = { syncObject: {}, functionsObject: {} };
|
|
717
|
-
var
|
|
332
|
+
var registry3 = createRegistry({
|
|
718
333
|
getRuntimeApiMaps: () => Promise.resolve(emptyApi),
|
|
719
334
|
getRuntimeSyncMaps: () => Promise.resolve(emptySync)
|
|
335
|
+
});
|
|
336
|
+
var registerRuntimeMapsProvider = (provider2) => {
|
|
337
|
+
registry3.register(provider2);
|
|
720
338
|
};
|
|
721
|
-
var
|
|
722
|
-
var
|
|
723
|
-
|
|
724
|
-
providerRegistered = true;
|
|
725
|
-
};
|
|
726
|
-
var isRuntimeMapsProviderRegistered = () => providerRegistered;
|
|
727
|
-
var getRuntimeApiMaps = async () => activeProvider.getRuntimeApiMaps();
|
|
728
|
-
var getRuntimeSyncMaps = async () => activeProvider.getRuntimeSyncMaps();
|
|
729
|
-
|
|
730
|
-
// src/apiMethodMapRegistry.ts
|
|
731
|
-
var activeMap = null;
|
|
732
|
-
var registerApiMethodMap = (map) => {
|
|
733
|
-
activeMap = map;
|
|
734
|
-
};
|
|
735
|
-
var getRegisteredApiMethod = (pagePath, apiName, version) => {
|
|
736
|
-
if (!activeMap) return void 0;
|
|
737
|
-
return activeMap[pagePath]?.[apiName]?.[version];
|
|
738
|
-
};
|
|
739
|
-
var isApiMethodMapRegistered = () => activeMap !== null;
|
|
740
|
-
|
|
741
|
-
// src/notifier.ts
|
|
742
|
-
var noopNotifier = {
|
|
743
|
-
success: () => {
|
|
744
|
-
},
|
|
745
|
-
error: () => {
|
|
746
|
-
},
|
|
747
|
-
info: () => {
|
|
748
|
-
},
|
|
749
|
-
warning: () => {
|
|
750
|
-
}
|
|
751
|
-
};
|
|
752
|
-
var activeNotifier = noopNotifier;
|
|
753
|
-
var registerNotifier = (notifier) => {
|
|
754
|
-
activeNotifier = notifier;
|
|
755
|
-
};
|
|
756
|
-
var getNotifier = () => activeNotifier;
|
|
757
|
-
var notify = {
|
|
758
|
-
success: (input) => {
|
|
759
|
-
activeNotifier.success(input);
|
|
760
|
-
},
|
|
761
|
-
error: (input) => {
|
|
762
|
-
activeNotifier.error(input);
|
|
763
|
-
},
|
|
764
|
-
info: (input) => {
|
|
765
|
-
activeNotifier.info(input);
|
|
766
|
-
},
|
|
767
|
-
warning: (input) => {
|
|
768
|
-
activeNotifier.warning(input);
|
|
769
|
-
}
|
|
770
|
-
};
|
|
339
|
+
var isRuntimeMapsProviderRegistered = () => registry3.isRegistered();
|
|
340
|
+
var getRuntimeApiMaps = async () => registry3.get().getRuntimeApiMaps();
|
|
341
|
+
var getRuntimeSyncMaps = async () => registry3.get().getRuntimeSyncMaps();
|
|
771
342
|
|
|
772
343
|
// src/emailRegistry.ts
|
|
773
344
|
var activeSender = null;
|
|
774
|
-
var
|
|
345
|
+
var registry4 = {};
|
|
775
346
|
var registerEmailSender = (sender) => {
|
|
776
347
|
activeSender = sender;
|
|
777
|
-
|
|
348
|
+
registry4 = { ...registry4, default: sender };
|
|
778
349
|
};
|
|
779
350
|
var registerEmailSenders = (senders) => {
|
|
780
|
-
|
|
351
|
+
registry4 = { ...senders };
|
|
781
352
|
if (senders.default) {
|
|
782
353
|
activeSender = senders.default;
|
|
783
354
|
}
|
|
784
355
|
};
|
|
785
|
-
var getEmailSender = () => activeSender ??
|
|
356
|
+
var getEmailSender = () => activeSender ?? registry4.default ?? null;
|
|
786
357
|
var getEmailSenderByName = (name) => {
|
|
787
|
-
if (
|
|
358
|
+
if (registry4[name]) return registry4[name];
|
|
788
359
|
if (name === "default" && activeSender) return activeSender;
|
|
789
360
|
return null;
|
|
790
361
|
};
|
|
791
|
-
var listEmailSenderNames = () => Object.keys(
|
|
792
|
-
var isEmailSenderRegistered = () => activeSender !== null || Object.keys(
|
|
362
|
+
var listEmailSenderNames = () => Object.keys(registry4);
|
|
363
|
+
var isEmailSenderRegistered = () => activeSender !== null || Object.keys(registry4).length > 0;
|
|
793
364
|
|
|
794
365
|
// src/deployConfigRegistry.ts
|
|
795
366
|
var DEFAULT_DEPLOY_CONFIG = {
|
|
796
367
|
resources: {}
|
|
797
368
|
};
|
|
798
|
-
var
|
|
799
|
-
var
|
|
800
|
-
var
|
|
801
|
-
|
|
802
|
-
registered = true;
|
|
803
|
-
return activeConfig2;
|
|
804
|
-
};
|
|
805
|
-
var getDeployConfig = () => activeConfig2;
|
|
806
|
-
var isDeployConfigRegistered = () => registered;
|
|
369
|
+
var registry5 = createRegistry(DEFAULT_DEPLOY_CONFIG);
|
|
370
|
+
var registerDeployConfig = (config) => registry5.register(config);
|
|
371
|
+
var getDeployConfig = () => registry5.get();
|
|
372
|
+
var isDeployConfigRegistered = () => registry5.isRegistered();
|
|
807
373
|
|
|
808
374
|
// src/servicesConfigRegistry.ts
|
|
809
|
-
var
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
375
|
+
var registry6 = createRegistry(
|
|
376
|
+
//? Unregistered sentinel — `get` never returns this because `resolveDefault`
|
|
377
|
+
//? throws first. Typed as the real shape so the slot stays well-typed.
|
|
378
|
+
{ services: {}, presets: {} },
|
|
379
|
+
{
|
|
380
|
+
resolveDefault: () => {
|
|
381
|
+
throw new Error(
|
|
382
|
+
"@luckystack/core: services config has not been registered. Call `registerServicesConfig({...})` from your `services.config.ts` (which should be imported as a side-effect during boot)."
|
|
383
|
+
);
|
|
384
|
+
}
|
|
819
385
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
var
|
|
386
|
+
);
|
|
387
|
+
var registerServicesConfig = (config) => registry6.register(config);
|
|
388
|
+
var getServicesConfig = () => registry6.get();
|
|
389
|
+
var isServicesConfigRegistered = () => registry6.isRegistered();
|
|
823
390
|
|
|
824
391
|
// src/localeReloader.ts
|
|
825
|
-
var
|
|
392
|
+
var registry7 = createRegistry(null);
|
|
826
393
|
var registerLocaleReloader = (reloader) => {
|
|
827
|
-
|
|
394
|
+
registry7.register(reloader);
|
|
828
395
|
return reloader;
|
|
829
396
|
};
|
|
830
|
-
var getLocaleReloader = () =>
|
|
397
|
+
var getLocaleReloader = () => registry7.get();
|
|
831
398
|
|
|
832
399
|
// src/env.ts
|
|
833
|
-
import {
|
|
400
|
+
import { existsSync, readFileSync } from "fs";
|
|
401
|
+
import path from "path";
|
|
402
|
+
import { config as loadDotenv, parse as parseDotenv } from "dotenv";
|
|
834
403
|
import { z } from "zod";
|
|
835
404
|
var EnvSchema = z.object({
|
|
836
405
|
NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
|
|
@@ -851,10 +420,33 @@ var getEnvFiles = () => {
|
|
|
851
420
|
}
|
|
852
421
|
return DEFAULT_ENV_FILES;
|
|
853
422
|
};
|
|
423
|
+
var reportDuplicateEnvKeys = (files) => {
|
|
424
|
+
const seenIn = /* @__PURE__ */ new Map();
|
|
425
|
+
for (const file of files) {
|
|
426
|
+
const filePath = path.resolve(process.cwd(), file);
|
|
427
|
+
if (!existsSync(filePath)) continue;
|
|
428
|
+
const parsed = parseDotenv(readFileSync(filePath, "utf8"));
|
|
429
|
+
for (const key of Object.keys(parsed)) {
|
|
430
|
+
const list = seenIn.get(key) ?? [];
|
|
431
|
+
list.push(file);
|
|
432
|
+
seenIn.set(key, list);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
for (const [key, sources] of seenIn) {
|
|
436
|
+
if (sources.length > 1) {
|
|
437
|
+
const winner = sources.at(-1);
|
|
438
|
+
console.warn(`[env] "${key}" is defined in ${sources.join(" + ")}; "${winner}" wins. Removing it from only one file lets the other file's value resurface.`);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
};
|
|
854
442
|
var loadEnvFiles = () => {
|
|
855
|
-
|
|
443
|
+
const files = getEnvFiles();
|
|
444
|
+
for (const [index, file] of files.entries()) {
|
|
856
445
|
loadDotenv({ path: file, override: index > 0 });
|
|
857
446
|
}
|
|
447
|
+
if (process.env.LUCKYSTACK_ENV_DEBUG) {
|
|
448
|
+
reportDuplicateEnvKeys(files);
|
|
449
|
+
}
|
|
858
450
|
};
|
|
859
451
|
var applyResolvedDefaultsToProcessEnv = (resolvedEnv) => {
|
|
860
452
|
process.env.NODE_ENV = process.env.NODE_ENV ?? resolvedEnv.NODE_ENV;
|
|
@@ -881,6 +473,7 @@ var bootstrapEnv = () => {
|
|
|
881
473
|
};
|
|
882
474
|
var env = bootstrapEnv();
|
|
883
475
|
var isProduction = env.NODE_ENV === "production";
|
|
476
|
+
var getEnv = () => bootstrapEnv();
|
|
884
477
|
|
|
885
478
|
// src/db.ts
|
|
886
479
|
import { PrismaClient } from "@prisma/client";
|
|
@@ -938,9 +531,6 @@ var buildDefaultPrismaClient = () => {
|
|
|
938
531
|
return globalThis.__luckystackPrisma;
|
|
939
532
|
};
|
|
940
533
|
setDefaultPrismaResolver(buildDefaultPrismaClient);
|
|
941
|
-
if (process.env.NODE_ENV !== "production") {
|
|
942
|
-
buildDefaultPrismaClient();
|
|
943
|
-
}
|
|
944
534
|
var prisma = new Proxy({}, {
|
|
945
535
|
//? Bind methods to the real client so a CAPTURED method keeps its `this`.
|
|
946
536
|
//? Without binding, a detached call (`const q = prisma.$queryRaw; q(...)`)
|
|
@@ -951,9 +541,9 @@ var prisma = new Proxy({}, {
|
|
|
951
541
|
//? Mirrors the `redis` proxy. Non-function reads (model delegates like
|
|
952
542
|
//? `prisma.user`) pass through untouched, so their methods bind to the
|
|
953
543
|
//? delegate as before.
|
|
954
|
-
get: (_target, prop,
|
|
544
|
+
get: (_target, prop, _receiver) => {
|
|
955
545
|
const real = getPrismaClient();
|
|
956
|
-
const value = Reflect.get(real, prop,
|
|
546
|
+
const value = Reflect.get(real, prop, real);
|
|
957
547
|
if (typeof value !== "function") return value;
|
|
958
548
|
const fn = value;
|
|
959
549
|
return fn.bind(real);
|
|
@@ -984,22 +574,31 @@ var applyStrayKeyPrefix = (key) => key.includes(":") ? key : `${getProjectName()
|
|
|
984
574
|
|
|
985
575
|
// src/redis.ts
|
|
986
576
|
var cachedDefault = null;
|
|
987
|
-
var
|
|
577
|
+
var DEFAULT_MAX_REDIS_RECONNECT_ATTEMPTS = 50;
|
|
578
|
+
var DEFAULT_MAX_REDIS_BACKOFF_MS = 2e3;
|
|
579
|
+
var readPositiveIntEnv = (name, fallback) => {
|
|
580
|
+
const raw = process.env[name];
|
|
581
|
+
if (!raw) return fallback;
|
|
582
|
+
const parsed = Number.parseInt(raw, 10);
|
|
583
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
584
|
+
};
|
|
988
585
|
var buildDefaultRedisClient = () => {
|
|
989
586
|
if (cachedDefault) return cachedDefault;
|
|
587
|
+
const maxReconnectAttempts = readPositiveIntEnv("LUCKYSTACK_REDIS_MAX_RECONNECTS", DEFAULT_MAX_REDIS_RECONNECT_ATTEMPTS);
|
|
588
|
+
const maxBackoffMs = readPositiveIntEnv("LUCKYSTACK_REDIS_MAX_BACKOFF_MS", DEFAULT_MAX_REDIS_BACKOFF_MS);
|
|
990
589
|
cachedDefault = new Redis({
|
|
991
590
|
host: env.REDIS_HOST,
|
|
992
591
|
port: Number.parseInt(env.REDIS_PORT, 10),
|
|
993
592
|
...process.env.REDIS_USER && { username: process.env.REDIS_USER },
|
|
994
593
|
...process.env.REDIS_PASSWORD && { password: process.env.REDIS_PASSWORD },
|
|
995
594
|
retryStrategy(times) {
|
|
996
|
-
if (times >
|
|
595
|
+
if (times > maxReconnectAttempts) {
|
|
997
596
|
getLogger().error(
|
|
998
|
-
`Redis unreachable after ${String(
|
|
597
|
+
`Redis unreachable after ${String(maxReconnectAttempts)} reconnect attempts; giving up. Check REDIS_HOST / REDIS_PORT / credentials and that Redis is reachable. Raise LUCKYSTACK_REDIS_MAX_RECONNECTS for longer outage tolerance.`
|
|
999
598
|
);
|
|
1000
599
|
return null;
|
|
1001
600
|
}
|
|
1002
|
-
return Math.min(times * 50,
|
|
601
|
+
return Math.min(times * 50, maxBackoffMs);
|
|
1003
602
|
}
|
|
1004
603
|
});
|
|
1005
604
|
cachedDefault.on("ready", async () => {
|
|
@@ -1074,13 +673,32 @@ var STRAY_PREFIX_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
1074
673
|
"setbit",
|
|
1075
674
|
"bitcount"
|
|
1076
675
|
]);
|
|
676
|
+
var ALL_ARGS_ARE_KEYS_COMMANDS = /* @__PURE__ */ new Set([
|
|
677
|
+
"del",
|
|
678
|
+
"unlink",
|
|
679
|
+
"exists",
|
|
680
|
+
"touch",
|
|
681
|
+
"mget"
|
|
682
|
+
]);
|
|
683
|
+
var registerStrayPrefixCommand = (...commands) => {
|
|
684
|
+
for (const command of commands) {
|
|
685
|
+
STRAY_PREFIX_COMMANDS.add(command.toLowerCase());
|
|
686
|
+
}
|
|
687
|
+
};
|
|
1077
688
|
var redisProxy = new Proxy({}, {
|
|
1078
|
-
get: (_target, prop,
|
|
689
|
+
get: (_target, prop, _receiver) => {
|
|
1079
690
|
const real = getRedisClient();
|
|
1080
|
-
const value = Reflect.get(real, prop,
|
|
691
|
+
const value = Reflect.get(real, prop, real);
|
|
1081
692
|
if (typeof value !== "function") return value;
|
|
1082
693
|
const fn = value;
|
|
1083
|
-
|
|
694
|
+
const command = typeof prop === "string" ? prop.toLowerCase() : "";
|
|
695
|
+
if (command && ALL_ARGS_ARE_KEYS_COMMANDS.has(command)) {
|
|
696
|
+
return (...args) => {
|
|
697
|
+
const prefixed = args.map((arg) => typeof arg === "string" ? applyStrayKeyPrefix(arg) : arg);
|
|
698
|
+
return fn.apply(real, prefixed);
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
if (command && STRAY_PREFIX_COMMANDS.has(command)) {
|
|
1084
702
|
return (...args) => {
|
|
1085
703
|
if (args.length > 0 && typeof args[0] === "string") {
|
|
1086
704
|
args[0] = applyStrayKeyPrefix(args[0]);
|
|
@@ -1093,6 +711,7 @@ var redisProxy = new Proxy({}, {
|
|
|
1093
711
|
has: (_target, prop) => Reflect.has(getRedisClient(), prop)
|
|
1094
712
|
});
|
|
1095
713
|
var redis = redisProxy;
|
|
714
|
+
var redis_default = redisProxy;
|
|
1096
715
|
|
|
1097
716
|
// src/lease.ts
|
|
1098
717
|
import { randomBytes } from "crypto";
|
|
@@ -1136,16 +755,16 @@ var releaseLease = async (name, token) => {
|
|
|
1136
755
|
|
|
1137
756
|
// src/socketRedisAdapter.ts
|
|
1138
757
|
import { createAdapter } from "@socket.io/redis-adapter";
|
|
1139
|
-
var attachSocketRedisAdapter = (io) => {
|
|
1140
|
-
const pubClient = redis.duplicate();
|
|
1141
|
-
const subClient = redis.duplicate();
|
|
758
|
+
var attachSocketRedisAdapter = (io, options = {}) => {
|
|
759
|
+
const pubClient = options.pubClient ?? redis.duplicate();
|
|
760
|
+
const subClient = options.subClient ?? redis.duplicate();
|
|
1142
761
|
pubClient.on("error", (err) => {
|
|
1143
762
|
getLogger().error("[socket-redis-adapter] pub client error", err);
|
|
1144
763
|
});
|
|
1145
764
|
subClient.on("error", (err) => {
|
|
1146
765
|
getLogger().error("[socket-redis-adapter] sub client error", err);
|
|
1147
766
|
});
|
|
1148
|
-
io.adapter(createAdapter(pubClient, subClient));
|
|
767
|
+
io.adapter(createAdapter(pubClient, subClient, options.adapterOptions));
|
|
1149
768
|
};
|
|
1150
769
|
|
|
1151
770
|
// src/bootUuid.ts
|
|
@@ -1168,9 +787,32 @@ var readBootUuid = async (envKey) => {
|
|
|
1168
787
|
};
|
|
1169
788
|
|
|
1170
789
|
// src/synchronizedEnvHashes.ts
|
|
1171
|
-
import { createHash } from "crypto";
|
|
1172
|
-
var
|
|
1173
|
-
|
|
790
|
+
import { createHash, createHmac } from "crypto";
|
|
791
|
+
var plainHash = (value) => createHash("sha256").update(value).digest("hex");
|
|
792
|
+
var hashSynchronizedValueWith = (config, value) => {
|
|
793
|
+
const { mode, salt } = config;
|
|
794
|
+
if (mode === "plain" || salt.length === 0) return plainHash(value);
|
|
795
|
+
if (mode === "salted") return createHash("sha256").update(salt).update(value).digest("hex");
|
|
796
|
+
return createHmac("sha256", salt).update(value).digest("hex");
|
|
797
|
+
};
|
|
798
|
+
var resolveHealthHashConfig = (bootUuid) => {
|
|
799
|
+
const { mode, salt } = getProjectConfig().http.healthHash;
|
|
800
|
+
if (salt === "@bootUuid") {
|
|
801
|
+
if ((mode === "salted" || mode === "hmac") && bootUuid && bootUuid.length > 0) {
|
|
802
|
+
return { mode, salt: bootUuid };
|
|
803
|
+
}
|
|
804
|
+
return { mode: "plain", salt: "" };
|
|
805
|
+
}
|
|
806
|
+
return { mode, salt };
|
|
807
|
+
};
|
|
808
|
+
var describeHealthHashConfig = () => {
|
|
809
|
+
const { mode, salt } = getProjectConfig().http.healthHash;
|
|
810
|
+
return { mode, bootUuidSalt: salt === "@bootUuid" };
|
|
811
|
+
};
|
|
812
|
+
var resolveHealthHashConfigFromDescriptor = (descriptor, bootUuid) => {
|
|
813
|
+
if (descriptor.mode === "plain") return { mode: "plain", salt: "" };
|
|
814
|
+
if (descriptor.bootUuidSalt) return { mode: descriptor.mode, salt: bootUuid };
|
|
815
|
+
return null;
|
|
1174
816
|
};
|
|
1175
817
|
var collectSynchronizedEnvKeys = () => {
|
|
1176
818
|
const keys = /* @__PURE__ */ new Set();
|
|
@@ -1181,16 +823,17 @@ var collectSynchronizedEnvKeys = () => {
|
|
|
1181
823
|
}
|
|
1182
824
|
return [...keys].toSorted();
|
|
1183
825
|
};
|
|
1184
|
-
var computeSynchronizedEnvHashes = () => {
|
|
826
|
+
var computeSynchronizedEnvHashes = (bootUuid) => {
|
|
1185
827
|
const keys = collectSynchronizedEnvKeys();
|
|
828
|
+
const config = resolveHealthHashConfig(bootUuid);
|
|
1186
829
|
const out = {};
|
|
1187
830
|
for (const key of keys) {
|
|
1188
831
|
const value = process.env[key];
|
|
1189
|
-
out[key] = value === void 0 ? null :
|
|
832
|
+
out[key] = value === void 0 ? null : hashSynchronizedValueWith(config, value);
|
|
1190
833
|
}
|
|
1191
834
|
return out;
|
|
1192
835
|
};
|
|
1193
|
-
var hashSynchronizedValue = (value) =>
|
|
836
|
+
var hashSynchronizedValue = (value, bootUuid) => hashSynchronizedValueWith(resolveHealthHashConfig(bootUuid), value);
|
|
1194
837
|
|
|
1195
838
|
// src/consoleLog.ts
|
|
1196
839
|
var COLORS = {
|
|
@@ -1226,13 +869,10 @@ var initConsolelog = () => {
|
|
|
1226
869
|
}
|
|
1227
870
|
const extractedInfo = extractFrameLabel(frame);
|
|
1228
871
|
let colorCode = COLORS.white;
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
args.splice(index, 1);
|
|
1234
|
-
break;
|
|
1235
|
-
}
|
|
872
|
+
const lastArg = args.at(-1);
|
|
873
|
+
if (typeof lastArg === "string" && Object.prototype.hasOwnProperty.call(COLORS, lastArg)) {
|
|
874
|
+
colorCode = COLORS[lastArg];
|
|
875
|
+
args.pop();
|
|
1236
876
|
}
|
|
1237
877
|
if (typeof args[0] === "object") {
|
|
1238
878
|
originalLog(`${colorCode}${extractedInfo}${COLORS.reset}`);
|
|
@@ -1243,33 +883,6 @@ var initConsolelog = () => {
|
|
|
1243
883
|
};
|
|
1244
884
|
};
|
|
1245
885
|
|
|
1246
|
-
// src/redactedLogKeys.ts
|
|
1247
|
-
var DEFAULT_REDACTED_LOG_KEYS = [
|
|
1248
|
-
"password",
|
|
1249
|
-
"confirmpassword",
|
|
1250
|
-
"token",
|
|
1251
|
-
"newtoken",
|
|
1252
|
-
"authorization",
|
|
1253
|
-
"cookie",
|
|
1254
|
-
"set-cookie"
|
|
1255
|
-
];
|
|
1256
|
-
var redactedKeys = new Set(DEFAULT_REDACTED_LOG_KEYS.map((key) => key.toLowerCase()));
|
|
1257
|
-
var registerRedactedLogKeys = (keys) => {
|
|
1258
|
-
for (const key of keys) {
|
|
1259
|
-
redactedKeys.add(key.toLowerCase());
|
|
1260
|
-
}
|
|
1261
|
-
};
|
|
1262
|
-
var getRedactedLogKeys = () => [...redactedKeys];
|
|
1263
|
-
var isRedactedLogKey = (key) => {
|
|
1264
|
-
return redactedKeys.has(key.toLowerCase());
|
|
1265
|
-
};
|
|
1266
|
-
var resetRedactedLogKeysForTests = () => {
|
|
1267
|
-
redactedKeys.clear();
|
|
1268
|
-
for (const key of DEFAULT_REDACTED_LOG_KEYS) {
|
|
1269
|
-
redactedKeys.add(key.toLowerCase());
|
|
1270
|
-
}
|
|
1271
|
-
};
|
|
1272
|
-
|
|
1273
886
|
// src/cookies.ts
|
|
1274
887
|
var escapeRegExp = (value) => {
|
|
1275
888
|
return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
@@ -1294,52 +907,38 @@ var getCookieValue = (cookieHeader, cookieName) => {
|
|
|
1294
907
|
var hasCookie = (cookieHeader, cookieName) => {
|
|
1295
908
|
return getCookieValue(cookieHeader, cookieName) !== null;
|
|
1296
909
|
};
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
const nameParts = apiName.split("/");
|
|
1301
|
-
const lastPart = nameParts.at(-1)?.toLowerCase() ?? "";
|
|
1302
|
-
const methodName = /^v\d+$/.test(lastPart) ? (nameParts.at(-2) ?? "").toLowerCase() : lastPart;
|
|
1303
|
-
if (methodName.startsWith("get") || methodName.startsWith("fetch") || methodName.startsWith("list")) {
|
|
1304
|
-
return "GET";
|
|
910
|
+
var applyCookiePrefixConstraints = (baseName, prefix, secureOverride) => {
|
|
911
|
+
if (prefix === "__Host-") {
|
|
912
|
+
return { name: `__Host-${baseName}`, secure: true, forcePathRoot: true, forbidDomain: true };
|
|
1305
913
|
}
|
|
1306
|
-
if (
|
|
1307
|
-
return
|
|
914
|
+
if (prefix === "__Secure-") {
|
|
915
|
+
return { name: `__Secure-${baseName}`, secure: true, forcePathRoot: false, forbidDomain: false };
|
|
1308
916
|
}
|
|
1309
|
-
|
|
1310
|
-
return "PUT";
|
|
1311
|
-
}
|
|
1312
|
-
return "POST";
|
|
1313
|
-
};
|
|
1314
|
-
var getEffectiveHttpMethod = (apiConfig, apiName) => {
|
|
1315
|
-
return apiConfig.httpMethod ?? inferHttpMethod(apiName);
|
|
1316
|
-
};
|
|
1317
|
-
var isMethodAllowed = (requestMethod, allowedMethod) => {
|
|
1318
|
-
return requestMethod === allowedMethod || requestMethod === "OPTIONS";
|
|
917
|
+
return { name: baseName, secure: secureOverride ?? false, forcePathRoot: false, forbidDomain: false };
|
|
1319
918
|
};
|
|
1320
919
|
|
|
1321
920
|
// src/paths.ts
|
|
1322
|
-
import
|
|
921
|
+
import path2 from "path";
|
|
1323
922
|
import fs from "fs";
|
|
1324
923
|
import { fileURLToPath } from "url";
|
|
1325
924
|
var __filename = fileURLToPath(import.meta.url);
|
|
1326
|
-
var __dirname =
|
|
925
|
+
var __dirname = path2.dirname(__filename);
|
|
1327
926
|
var isWorkspaceRoot = (candidate) => {
|
|
1328
|
-
return fs.existsSync(
|
|
927
|
+
return fs.existsSync(path2.join(candidate, "package.json")) && (fs.existsSync(path2.join(candidate, "tsconfig.json")) || fs.existsSync(path2.join(candidate, "tsconfig.server.json")));
|
|
1329
928
|
};
|
|
1330
929
|
var findWorkspaceRoot = (startDir) => {
|
|
1331
|
-
let current =
|
|
930
|
+
let current = path2.resolve(startDir);
|
|
1332
931
|
while (true) {
|
|
1333
932
|
if (isWorkspaceRoot(current)) {
|
|
1334
933
|
return current;
|
|
1335
934
|
}
|
|
1336
|
-
const parent =
|
|
935
|
+
const parent = path2.dirname(current);
|
|
1337
936
|
if (parent === current) return null;
|
|
1338
937
|
current = parent;
|
|
1339
938
|
}
|
|
1340
939
|
};
|
|
1341
940
|
var ROOT_DIR = findWorkspaceRoot(process.cwd()) ?? findWorkspaceRoot(__dirname) ?? process.cwd();
|
|
1342
|
-
var resolveAgainstRoot = (relative) =>
|
|
941
|
+
var resolveAgainstRoot = (relative) => path2.isAbsolute(relative) ? relative : path2.join(ROOT_DIR, relative);
|
|
1343
942
|
var getSrcDir = () => resolveAgainstRoot(getProjectConfig().paths.srcDir);
|
|
1344
943
|
var getServerDir = () => resolveAgainstRoot(getProjectConfig().paths.serverDir);
|
|
1345
944
|
var getSharedDir = () => resolveAgainstRoot(getProjectConfig().paths.sharedDir);
|
|
@@ -1364,17 +963,17 @@ var getServerFunctionsDir = () => {
|
|
|
1364
963
|
var getGeneratedSocketTypesPath = () => resolveAgainstRoot(getProjectConfig().paths.generatedSocketTypes);
|
|
1365
964
|
var getGeneratedApiSchemasPath = () => resolveAgainstRoot(getProjectConfig().paths.generatedApiSchemas);
|
|
1366
965
|
var getGeneratedApiDocsPath = () => resolveAgainstRoot(getProjectConfig().paths.generatedApiDocs);
|
|
1367
|
-
var SRC_DIR =
|
|
1368
|
-
var SERVER_DIR =
|
|
1369
|
-
var SHARED_DIR =
|
|
1370
|
-
var UPLOADS_DIR =
|
|
1371
|
-
var PUBLIC_DIR =
|
|
1372
|
-
var SERVER_FUNCTIONS_DIR =
|
|
1373
|
-
var GENERATED_SOCKET_TYPES_PATH =
|
|
1374
|
-
var GENERATED_API_SCHEMAS_PATH =
|
|
1375
|
-
var GENERATED_API_DOCS_PATH =
|
|
966
|
+
var SRC_DIR = path2.join(ROOT_DIR, "src");
|
|
967
|
+
var SERVER_DIR = path2.join(ROOT_DIR, "server");
|
|
968
|
+
var SHARED_DIR = path2.join(ROOT_DIR, "shared");
|
|
969
|
+
var UPLOADS_DIR = path2.join(ROOT_DIR, "uploads");
|
|
970
|
+
var PUBLIC_DIR = path2.join(ROOT_DIR, "public");
|
|
971
|
+
var SERVER_FUNCTIONS_DIR = path2.join(SERVER_DIR, "functions");
|
|
972
|
+
var GENERATED_SOCKET_TYPES_PATH = path2.join(SRC_DIR, "_sockets", "apiTypes.generated.ts");
|
|
973
|
+
var GENERATED_API_SCHEMAS_PATH = path2.join(SRC_DIR, "_sockets", "apiInputSchemas.generated.ts");
|
|
974
|
+
var GENERATED_API_DOCS_PATH = path2.join(SRC_DIR, "docs", "apiDocs.generated.json");
|
|
1376
975
|
var TSCONFIG_ALIAS_FILES = ["tsconfig.server.json", "tsconfig.client.json"];
|
|
1377
|
-
var resolveFromRoot = (...segments) =>
|
|
976
|
+
var resolveFromRoot = (...segments) => path2.join(ROOT_DIR, ...segments);
|
|
1378
977
|
|
|
1379
978
|
// src/socketMiddlewareRegistry.ts
|
|
1380
979
|
var middlewares = [];
|
|
@@ -1391,50 +990,30 @@ var applySocketMiddlewares = (io) => {
|
|
|
1391
990
|
}
|
|
1392
991
|
};
|
|
1393
992
|
|
|
1394
|
-
// src/csrfConfig.ts
|
|
1395
|
-
var DEFAULT_CSRF_CONFIG = {
|
|
1396
|
-
cookieName: "csrf-token",
|
|
1397
|
-
headerName: "x-csrf-token",
|
|
1398
|
-
tokenLength: 32,
|
|
1399
|
-
cookieOptions: {
|
|
1400
|
-
sameSite: "lax",
|
|
1401
|
-
secure: true,
|
|
1402
|
-
httpOnly: false,
|
|
1403
|
-
path: "/",
|
|
1404
|
-
maxAgeMs: 24 * 60 * 60 * 1e3
|
|
1405
|
-
}
|
|
1406
|
-
};
|
|
1407
|
-
var active = DEFAULT_CSRF_CONFIG;
|
|
1408
|
-
var registerCsrfConfig = (input) => {
|
|
1409
|
-
active = {
|
|
1410
|
-
...active,
|
|
1411
|
-
...input,
|
|
1412
|
-
cookieOptions: { ...active.cookieOptions, ...input.cookieOptions }
|
|
1413
|
-
};
|
|
1414
|
-
};
|
|
1415
|
-
var getCsrfConfig = () => active;
|
|
1416
|
-
var resetCsrfConfigForTests = () => {
|
|
1417
|
-
active = DEFAULT_CSRF_CONFIG;
|
|
1418
|
-
};
|
|
1419
|
-
|
|
1420
993
|
// src/serveAvatars.ts
|
|
1421
|
-
import
|
|
994
|
+
import path3 from "path";
|
|
1422
995
|
import { access } from "fs/promises";
|
|
1423
996
|
import fs2 from "fs";
|
|
997
|
+
import stream from "stream";
|
|
1424
998
|
|
|
1425
999
|
// src/avatarConfig.ts
|
|
1426
1000
|
var DEFAULT_AVATAR_CONFIG = {
|
|
1427
1001
|
formats: [{ extension: "webp", contentType: "image/webp" }],
|
|
1428
1002
|
cacheControl: "public, max-age=86400"
|
|
1429
1003
|
};
|
|
1430
|
-
var
|
|
1431
|
-
|
|
1432
|
-
|
|
1004
|
+
var registry8 = createRegistry(DEFAULT_AVATAR_CONFIG, {
|
|
1005
|
+
//? Bespoke merge (not a generic deep-merge): an empty `formats` array falls
|
|
1006
|
+
//? back to the default formats rather than wiping serving. Built fresh from
|
|
1007
|
+
//? the defaults each call, matching the historical behaviour.
|
|
1008
|
+
transform: (config) => ({
|
|
1433
1009
|
formats: config.formats?.length ? config.formats : DEFAULT_AVATAR_CONFIG.formats,
|
|
1434
1010
|
cacheControl: config.cacheControl ?? DEFAULT_AVATAR_CONFIG.cacheControl
|
|
1435
|
-
}
|
|
1011
|
+
})
|
|
1012
|
+
});
|
|
1013
|
+
var registerAvatarConfig = (config) => {
|
|
1014
|
+
registry8.register(config);
|
|
1436
1015
|
};
|
|
1437
|
-
var getAvatarConfig = () =>
|
|
1016
|
+
var getAvatarConfig = () => registry8.get();
|
|
1438
1017
|
|
|
1439
1018
|
// src/serveAvatars.ts
|
|
1440
1019
|
var FILE_ID_REGEX = /^[A-Za-z0-9_-]{1,128}$/;
|
|
@@ -1443,22 +1022,33 @@ var serveAvatar = async ({
|
|
|
1443
1022
|
res
|
|
1444
1023
|
}) => {
|
|
1445
1024
|
const uploadsFolder = getUploadsDir();
|
|
1446
|
-
const fileId =
|
|
1025
|
+
const fileId = path3.basename(routePath, path3.extname(routePath));
|
|
1447
1026
|
if (!fileId || !FILE_ID_REGEX.test(fileId)) {
|
|
1448
1027
|
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
1449
1028
|
res.end("File not found");
|
|
1450
1029
|
return;
|
|
1451
1030
|
}
|
|
1031
|
+
const preDispatch = await dispatchHook("preAvatarServe", { routePath, fileId });
|
|
1032
|
+
if (preDispatch.stopped) {
|
|
1033
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
1034
|
+
res.end("File not found");
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1452
1037
|
const { formats, cacheControl } = getAvatarConfig();
|
|
1453
1038
|
for (const { extension, contentType } of formats) {
|
|
1454
|
-
const filePath =
|
|
1039
|
+
const filePath = path3.join(uploadsFolder, `${fileId}.${extension}`);
|
|
1455
1040
|
const [accessError] = await tryCatch(() => access(filePath));
|
|
1456
1041
|
if (accessError) continue;
|
|
1457
1042
|
res.writeHead(200, {
|
|
1458
1043
|
"Content-Type": contentType,
|
|
1459
1044
|
"Cache-Control": cacheControl
|
|
1460
1045
|
});
|
|
1461
|
-
fs2.createReadStream(filePath)
|
|
1046
|
+
stream.pipeline(fs2.createReadStream(filePath), res, (pipelineError) => {
|
|
1047
|
+
if (pipelineError) {
|
|
1048
|
+
getLogger().debug("avatar: stream pipeline failed", { routePath, fileId, error: pipelineError });
|
|
1049
|
+
}
|
|
1050
|
+
});
|
|
1051
|
+
void dispatchHook("postAvatarServe", { routePath, fileId, extension, contentType });
|
|
1462
1052
|
return;
|
|
1463
1053
|
}
|
|
1464
1054
|
getLogger().debug("avatar: file not found", { routePath, fileId });
|
|
@@ -1493,13 +1083,13 @@ var processUpload = async (input) => {
|
|
|
1493
1083
|
};
|
|
1494
1084
|
|
|
1495
1085
|
// src/bindAddress.ts
|
|
1496
|
-
var
|
|
1086
|
+
var registered = null;
|
|
1497
1087
|
var registerBindAddress = (address) => {
|
|
1498
|
-
|
|
1088
|
+
registered = address;
|
|
1499
1089
|
};
|
|
1500
1090
|
var getBindAddress = () => {
|
|
1501
|
-
if (
|
|
1502
|
-
return { ip:
|
|
1091
|
+
if (registered) {
|
|
1092
|
+
return { ip: registered.ip, port: String(registered.port) };
|
|
1503
1093
|
}
|
|
1504
1094
|
return {
|
|
1505
1095
|
ip: process.env.SERVER_IP ?? "127.0.0.1",
|
|
@@ -1508,124 +1098,177 @@ var getBindAddress = () => {
|
|
|
1508
1098
|
};
|
|
1509
1099
|
|
|
1510
1100
|
// src/getParams.ts
|
|
1101
|
+
var writeJsonErrorAndResolve = (res, status, errorCode, resolve) => {
|
|
1102
|
+
res.setHeader("Content-Type", "application/json");
|
|
1103
|
+
res.writeHead(status);
|
|
1104
|
+
res.end(JSON.stringify({
|
|
1105
|
+
status: "error",
|
|
1106
|
+
httpStatus: status,
|
|
1107
|
+
message: errorCode,
|
|
1108
|
+
errorCode
|
|
1109
|
+
}));
|
|
1110
|
+
resolve(null);
|
|
1111
|
+
};
|
|
1511
1112
|
async function getParams({ method, req, res: _res, queryString }) {
|
|
1512
|
-
if (method
|
|
1513
|
-
return Object.fromEntries(new URLSearchParams(queryString
|
|
1113
|
+
if (method === "GET") {
|
|
1114
|
+
return Object.fromEntries(new URLSearchParams(queryString ?? ""));
|
|
1514
1115
|
}
|
|
1515
|
-
return new Promise((resolve
|
|
1516
|
-
const contentType = req.headers["content-type"]
|
|
1116
|
+
return new Promise((resolve) => {
|
|
1117
|
+
const contentType = req.headers["content-type"] ?? "";
|
|
1517
1118
|
const contentLengthHeader = req.headers["content-length"];
|
|
1518
1119
|
const declaredLength = typeof contentLengthHeader === "string" ? Number(contentLengthHeader) : Number.NaN;
|
|
1519
1120
|
const maxBodyBytes = getProjectConfig().http.requestBodyMaxBytes;
|
|
1520
1121
|
if (Number.isFinite(declaredLength) && declaredLength > maxBodyBytes) {
|
|
1521
|
-
_res.
|
|
1522
|
-
|
|
1523
|
-
_res.end(JSON.stringify({
|
|
1524
|
-
status: "error",
|
|
1525
|
-
httpStatus: 413,
|
|
1526
|
-
message: "api.payloadTooLarge",
|
|
1527
|
-
errorCode: "api.payloadTooLarge"
|
|
1528
|
-
}));
|
|
1529
|
-
resolve(null);
|
|
1122
|
+
writeJsonErrorAndResolve(_res, 413, "api.payloadTooLarge", resolve);
|
|
1123
|
+
req.destroy();
|
|
1530
1124
|
return;
|
|
1531
1125
|
}
|
|
1532
|
-
|
|
1126
|
+
const chunks = [];
|
|
1533
1127
|
let bodySize = 0;
|
|
1534
1128
|
req.on("data", (chunk) => {
|
|
1129
|
+
if (_res.headersSent) return;
|
|
1535
1130
|
bodySize += chunk.length;
|
|
1536
1131
|
if (bodySize > maxBodyBytes) {
|
|
1537
|
-
_res.
|
|
1538
|
-
_res.writeHead(413);
|
|
1539
|
-
_res.end(JSON.stringify({
|
|
1540
|
-
status: "error",
|
|
1541
|
-
httpStatus: 413,
|
|
1542
|
-
message: "api.payloadTooLarge",
|
|
1543
|
-
errorCode: "api.payloadTooLarge"
|
|
1544
|
-
}));
|
|
1132
|
+
writeJsonErrorAndResolve(_res, 413, "api.payloadTooLarge", resolve);
|
|
1545
1133
|
req.destroy();
|
|
1546
|
-
resolve(null);
|
|
1547
1134
|
return;
|
|
1548
1135
|
}
|
|
1549
|
-
|
|
1136
|
+
chunks.push(chunk);
|
|
1550
1137
|
});
|
|
1551
|
-
req.on("end",
|
|
1552
|
-
|
|
1553
|
-
const
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1138
|
+
req.on("end", () => {
|
|
1139
|
+
void (async () => {
|
|
1140
|
+
const body = Buffer.concat(chunks).toString("utf8");
|
|
1141
|
+
if (contentType.startsWith("application/x-www-form-urlencoded")) {
|
|
1142
|
+
const parseData = () => {
|
|
1143
|
+
const data = new URLSearchParams(body);
|
|
1144
|
+
return Object.fromEntries(data);
|
|
1145
|
+
};
|
|
1146
|
+
const [, response] = await tryCatch(parseData);
|
|
1147
|
+
if (response) {
|
|
1148
|
+
resolve(response);
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
writeJsonErrorAndResolve(_res, 400, "api.invalidRequestFormat", resolve);
|
|
1560
1152
|
return;
|
|
1561
1153
|
}
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
}
|
|
1573
|
-
if (contentType.startsWith("application/json")) {
|
|
1574
|
-
const parseData = () => {
|
|
1575
|
-
return JSON.parse(body || "{}");
|
|
1576
|
-
};
|
|
1577
|
-
const [, response] = await tryCatch(parseData);
|
|
1578
|
-
if (response && typeof response === "object" && !Array.isArray(response)) {
|
|
1579
|
-
resolve(response);
|
|
1154
|
+
if (contentType.startsWith("application/json")) {
|
|
1155
|
+
const parseData = () => {
|
|
1156
|
+
return JSON.parse(body || "{}");
|
|
1157
|
+
};
|
|
1158
|
+
const [, response] = await tryCatch(parseData);
|
|
1159
|
+
if (response && typeof response === "object" && !Array.isArray(response)) {
|
|
1160
|
+
resolve(response);
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
writeJsonErrorAndResolve(_res, 400, "api.invalidRequestFormat", resolve);
|
|
1580
1164
|
return;
|
|
1581
1165
|
}
|
|
1582
|
-
_res.
|
|
1583
|
-
|
|
1584
|
-
_res.end(JSON.stringify({
|
|
1585
|
-
status: "error",
|
|
1586
|
-
httpStatus: 400,
|
|
1587
|
-
message: "api.invalidRequestFormat",
|
|
1588
|
-
errorCode: "api.invalidRequestFormat"
|
|
1589
|
-
}));
|
|
1590
|
-
resolve(null);
|
|
1591
|
-
return;
|
|
1592
|
-
}
|
|
1593
|
-
_res.setHeader("Content-Type", "application/json");
|
|
1594
|
-
_res.writeHead(415);
|
|
1595
|
-
_res.end(JSON.stringify({
|
|
1596
|
-
status: "error",
|
|
1597
|
-
httpStatus: 415,
|
|
1598
|
-
message: "api.unsupportedMediaType",
|
|
1599
|
-
errorCode: "api.unsupportedMediaType"
|
|
1600
|
-
}));
|
|
1601
|
-
resolve(null);
|
|
1166
|
+
writeJsonErrorAndResolve(_res, 415, "api.unsupportedMediaType", resolve);
|
|
1167
|
+
})();
|
|
1602
1168
|
});
|
|
1603
|
-
req.on("error", (
|
|
1604
|
-
|
|
1169
|
+
req.on("error", () => {
|
|
1170
|
+
resolve(null);
|
|
1605
1171
|
});
|
|
1606
1172
|
});
|
|
1607
1173
|
}
|
|
1608
1174
|
|
|
1609
1175
|
// src/extractToken.ts
|
|
1610
1176
|
var extractTokenFromSocket = (socket2) => {
|
|
1177
|
+
const config = getProjectConfig();
|
|
1611
1178
|
const cookie = socket2.handshake.headers.cookie;
|
|
1612
|
-
const sessionToken = typeof socket2.handshake.auth
|
|
1613
|
-
const cookieToken = getCookieValue(cookie,
|
|
1614
|
-
if (
|
|
1179
|
+
const sessionToken = typeof socket2.handshake.auth.token === "string" ? socket2.handshake.auth.token : null;
|
|
1180
|
+
const cookieToken = getCookieValue(cookie, config.http.sessionCookieName);
|
|
1181
|
+
if (config.session.basedToken) {
|
|
1615
1182
|
return sessionToken ?? cookieToken;
|
|
1616
1183
|
}
|
|
1617
|
-
|
|
1184
|
+
if (config.http.acceptBearerInCookieMode) {
|
|
1185
|
+
return cookieToken ?? sessionToken;
|
|
1186
|
+
}
|
|
1187
|
+
return cookieToken;
|
|
1618
1188
|
};
|
|
1619
1189
|
|
|
1620
1190
|
// src/extractTokenFromRequest.ts
|
|
1621
1191
|
var extractTokenFromRequest = (req) => {
|
|
1622
|
-
const
|
|
1192
|
+
const rawAuthHeader = req.headers.authorization;
|
|
1193
|
+
const authHeader = typeof rawAuthHeader === "string" ? rawAuthHeader : Array.isArray(rawAuthHeader) && typeof rawAuthHeader[0] === "string" ? rawAuthHeader[0] : void 0;
|
|
1623
1194
|
const bearerToken = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
|
|
1624
|
-
const
|
|
1625
|
-
|
|
1195
|
+
const config = getProjectConfig();
|
|
1196
|
+
const cookieToken = getCookieValue(req.headers.cookie, config.http.sessionCookieName);
|
|
1197
|
+
if (config.session.basedToken) {
|
|
1626
1198
|
return bearerToken ?? cookieToken;
|
|
1627
1199
|
}
|
|
1628
|
-
|
|
1200
|
+
if (config.http.acceptBearerInCookieMode) {
|
|
1201
|
+
return cookieToken ?? bearerToken;
|
|
1202
|
+
}
|
|
1203
|
+
return cookieToken;
|
|
1204
|
+
};
|
|
1205
|
+
|
|
1206
|
+
// src/resolveClientIp.ts
|
|
1207
|
+
import { createHash as createHash2 } from "crypto";
|
|
1208
|
+
var UNKNOWN_CLIENT_IP = "unknown";
|
|
1209
|
+
var canonicalizeIp = (value) => {
|
|
1210
|
+
let ip = value.startsWith("::ffff:") ? value.slice("::ffff:".length) : value;
|
|
1211
|
+
const zoneIdx = ip.indexOf("%");
|
|
1212
|
+
if (zoneIdx !== -1) ip = ip.slice(0, zoneIdx);
|
|
1213
|
+
return ip.includes(":") ? ip.toLowerCase() : ip;
|
|
1214
|
+
};
|
|
1215
|
+
var firstHeaderValue = (raw) => {
|
|
1216
|
+
if (raw === void 0) return void 0;
|
|
1217
|
+
const single = Array.isArray(raw) ? raw[0] : raw;
|
|
1218
|
+
if (typeof single !== "string") return void 0;
|
|
1219
|
+
const trimmed = single.trim();
|
|
1220
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
1221
|
+
};
|
|
1222
|
+
var forwardedForHops = (raw) => {
|
|
1223
|
+
if (raw === void 0) return [];
|
|
1224
|
+
const parts = Array.isArray(raw) ? raw : [raw];
|
|
1225
|
+
return parts.flatMap((part) => typeof part === "string" ? part.split(",") : []).map((hop) => hop.trim()).filter((hop) => hop.length > 0);
|
|
1226
|
+
};
|
|
1227
|
+
var resolveRaw = ({ rawAddress, headers, trustProxy, trustedProxyHopCount }) => {
|
|
1228
|
+
const rawFallback = rawAddress && rawAddress.length > 0 ? canonicalizeIp(rawAddress) : UNKNOWN_CLIENT_IP;
|
|
1229
|
+
if (!trustProxy) return rawFallback;
|
|
1230
|
+
const hops = forwardedForHops(headers["x-forwarded-for"]);
|
|
1231
|
+
if (hops.length > 0) {
|
|
1232
|
+
const skip = Math.max(1, Math.floor(trustedProxyHopCount));
|
|
1233
|
+
const index = Math.max(0, hops.length - skip);
|
|
1234
|
+
const chosen = hops[index];
|
|
1235
|
+
if (chosen && chosen.length > 0) return canonicalizeIp(chosen);
|
|
1236
|
+
}
|
|
1237
|
+
const realIp = firstHeaderValue(headers["x-real-ip"]);
|
|
1238
|
+
if (realIp) return canonicalizeIp(realIp);
|
|
1239
|
+
return rawFallback;
|
|
1240
|
+
};
|
|
1241
|
+
var isLoopbackIp = (ip) => {
|
|
1242
|
+
if (ip === UNKNOWN_CLIENT_IP) return true;
|
|
1243
|
+
const canonical = ip.startsWith("::ffff:") ? ip.slice("::ffff:".length) : ip;
|
|
1244
|
+
if (canonical === "::1") return true;
|
|
1245
|
+
return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(canonical);
|
|
1246
|
+
};
|
|
1247
|
+
var TOKEN_HASH_LENGTH = 32;
|
|
1248
|
+
var deriveTokenBucketId = (token) => createHash2("sha256").update(token).digest("hex").slice(0, TOKEN_HASH_LENGTH);
|
|
1249
|
+
var resolveClientIp = ({ rawAddress, headers, trustProxy = false, trustedProxyHopCount = 1 }) => {
|
|
1250
|
+
const resolved = resolveRaw({ rawAddress, headers, trustProxy, trustedProxyHopCount });
|
|
1251
|
+
if (resolved === UNKNOWN_CLIENT_IP) {
|
|
1252
|
+
getLogger().warn('rate-limit: client IP unresolved \u2014 keyed into the shared "unknown" bucket', { trustProxy });
|
|
1253
|
+
}
|
|
1254
|
+
return resolved;
|
|
1255
|
+
};
|
|
1256
|
+
|
|
1257
|
+
// src/sessionProviderRegistry.ts
|
|
1258
|
+
var provider = null;
|
|
1259
|
+
var registerSessionProvider = (next) => {
|
|
1260
|
+
provider = next;
|
|
1261
|
+
};
|
|
1262
|
+
var getRegisteredSessionProvider = () => provider;
|
|
1263
|
+
var isSessionProviderRegistered = () => provider !== null;
|
|
1264
|
+
var resetSessionProviderForTests = () => {
|
|
1265
|
+
provider = null;
|
|
1266
|
+
};
|
|
1267
|
+
var readSession = async (token) => provider ? provider.getSession(token) : null;
|
|
1268
|
+
var writeSession = async (token, data, newUser, options) => provider ? provider.saveSession(token, data, newUser, options) : { ok: true };
|
|
1269
|
+
var removeSession = async (token, options) => provider ? provider.deleteSession(token, options) : false;
|
|
1270
|
+
var performLogout = async (input) => {
|
|
1271
|
+
if (provider) await provider.logout(input);
|
|
1629
1272
|
};
|
|
1630
1273
|
|
|
1631
1274
|
// src/validateRequest.ts
|
|
@@ -1636,7 +1279,7 @@ var validateRequest = ({
|
|
|
1636
1279
|
auth,
|
|
1637
1280
|
user
|
|
1638
1281
|
}) => {
|
|
1639
|
-
if (!auth.additional) {
|
|
1282
|
+
if (!auth.additional || auth.additional.length === 0) {
|
|
1640
1283
|
return { status: "success" };
|
|
1641
1284
|
}
|
|
1642
1285
|
const forbid = (key) => ({
|
|
@@ -1645,6 +1288,9 @@ var validateRequest = ({
|
|
|
1645
1288
|
errorParams: [{ key: "key", value: key }],
|
|
1646
1289
|
httpStatus: 403
|
|
1647
1290
|
});
|
|
1291
|
+
if (user === null || user === void 0) {
|
|
1292
|
+
return forbid(auth.additional[0]?.key ?? "");
|
|
1293
|
+
}
|
|
1648
1294
|
for (const condition of auth.additional) {
|
|
1649
1295
|
if (!(condition.key in user)) {
|
|
1650
1296
|
return {
|
|
@@ -1673,6 +1319,7 @@ var validateRequest = ({
|
|
|
1673
1319
|
};
|
|
1674
1320
|
|
|
1675
1321
|
// src/rateLimiter.ts
|
|
1322
|
+
var MAX_MEMORY_STORE_SIZE = 5e4;
|
|
1676
1323
|
var rateLimitStore = /* @__PURE__ */ new Map();
|
|
1677
1324
|
var getRedisPrefix = () => formatKey(`:${getProjectConfig().rateLimiting.redisKeyPrefix}`, "");
|
|
1678
1325
|
var isRedisMode = () => getProjectConfig().rateLimiting.store === "redis";
|
|
@@ -1682,7 +1329,8 @@ var buildAllowedResult = (limit) => ({
|
|
|
1682
1329
|
remaining: Math.max(0, limit),
|
|
1683
1330
|
resetIn: 0
|
|
1684
1331
|
});
|
|
1685
|
-
var
|
|
1332
|
+
var REDIS_FALLBACK_LOG_COOLDOWN_MS = 6e4;
|
|
1333
|
+
var redisFallbackLastLoggedAt = 0;
|
|
1686
1334
|
var RATE_LIMIT_INCREMENT_SCRIPT = `
|
|
1687
1335
|
local current = redis.call('INCR', KEYS[1])
|
|
1688
1336
|
if current == 1 then
|
|
@@ -1692,7 +1340,7 @@ local ttl = redis.call('PTTL', KEYS[1])
|
|
|
1692
1340
|
return { current, ttl }
|
|
1693
1341
|
`;
|
|
1694
1342
|
var normalizeWindowMs = (windowMs) => {
|
|
1695
|
-
if (!Number.isFinite(windowMs) || windowMs <= 0) {
|
|
1343
|
+
if (windowMs === void 0 || !Number.isFinite(windowMs) || windowMs <= 0) {
|
|
1696
1344
|
return getProjectConfig().rateLimiting.windowMs;
|
|
1697
1345
|
}
|
|
1698
1346
|
return windowMs;
|
|
@@ -1701,12 +1349,19 @@ var getRedisRateLimitKey = (key) => `${getRedisPrefix()}:${key}`;
|
|
|
1701
1349
|
var checkRateLimitInMemory = ({
|
|
1702
1350
|
key,
|
|
1703
1351
|
limit,
|
|
1704
|
-
|
|
1352
|
+
//? No default here — `normalizeWindowMs` reads `projectConfig.rateLimiting.windowMs`
|
|
1353
|
+
//? when `windowMs` is undefined or non-finite, so a hardcoded `60_000` would
|
|
1354
|
+
//? shadow the configured value (CORE-N6).
|
|
1355
|
+
windowMs
|
|
1705
1356
|
}) => {
|
|
1706
1357
|
const safeWindowMs = normalizeWindowMs(windowMs);
|
|
1707
1358
|
const now = Date.now();
|
|
1708
1359
|
const entry = rateLimitStore.get(key);
|
|
1709
1360
|
if (!entry || entry.resetAt < now) {
|
|
1361
|
+
if (rateLimitStore.size >= MAX_MEMORY_STORE_SIZE) {
|
|
1362
|
+
const oldest = rateLimitStore.keys().next().value;
|
|
1363
|
+
if (oldest !== void 0) rateLimitStore.delete(oldest);
|
|
1364
|
+
}
|
|
1710
1365
|
rateLimitStore.set(key, { count: 1, resetAt: now + safeWindowMs });
|
|
1711
1366
|
return { allowed: true, remaining: limit - 1, resetIn: Math.ceil(safeWindowMs / 1e3) };
|
|
1712
1367
|
}
|
|
@@ -1720,7 +1375,8 @@ var checkRateLimitInMemory = ({
|
|
|
1720
1375
|
var checkRateLimitInRedis = async ({
|
|
1721
1376
|
key,
|
|
1722
1377
|
limit,
|
|
1723
|
-
|
|
1378
|
+
//? No default — same reasoning as `checkRateLimitInMemory` (CORE-N6).
|
|
1379
|
+
windowMs
|
|
1724
1380
|
}) => {
|
|
1725
1381
|
const safeWindowMs = normalizeWindowMs(windowMs);
|
|
1726
1382
|
const redisKey = getRedisRateLimitKey(key);
|
|
@@ -1772,11 +1428,20 @@ var getRateLimitStatusInRedis = async (key, limit) => {
|
|
|
1772
1428
|
resetIn: Math.ceil(ttlMs / 1e3)
|
|
1773
1429
|
};
|
|
1774
1430
|
};
|
|
1431
|
+
var getOnStoreError = () => getProjectConfig().rateLimiting.onStoreError;
|
|
1775
1432
|
var logRedisFallback = () => {
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1433
|
+
const now = Date.now();
|
|
1434
|
+
if (now - redisFallbackLastLoggedAt < REDIS_FALLBACK_LOG_COOLDOWN_MS) return;
|
|
1435
|
+
redisFallbackLastLoggedAt = now;
|
|
1436
|
+
getLogger().warn(
|
|
1437
|
+
getOnStoreError() === "deny" ? "[RateLimiter] Redis mode unavailable \u2014 denying request (onStoreError=deny)" : "[RateLimiter] Redis mode unavailable, falling back to in-memory mode (onStoreError=memory)"
|
|
1438
|
+
);
|
|
1779
1439
|
};
|
|
1440
|
+
var buildDeniedResult = () => ({
|
|
1441
|
+
allowed: false,
|
|
1442
|
+
remaining: 0,
|
|
1443
|
+
resetIn: Math.ceil(getProjectConfig().rateLimiting.windowMs / 1e3)
|
|
1444
|
+
});
|
|
1780
1445
|
var defaultRateLimitStrategy = {
|
|
1781
1446
|
name: "default-memory-redis",
|
|
1782
1447
|
async check(params) {
|
|
@@ -1785,6 +1450,7 @@ var defaultRateLimitStrategy = {
|
|
|
1785
1450
|
const redisResult = await checkRateLimitInRedis(params);
|
|
1786
1451
|
if (redisResult) return redisResult;
|
|
1787
1452
|
logRedisFallback();
|
|
1453
|
+
if (getOnStoreError() === "deny") return buildDeniedResult();
|
|
1788
1454
|
}
|
|
1789
1455
|
return checkRateLimitInMemory(params);
|
|
1790
1456
|
},
|
|
@@ -1794,6 +1460,7 @@ var defaultRateLimitStrategy = {
|
|
|
1794
1460
|
const redisResult = await getRateLimitStatusInRedis(key, limit);
|
|
1795
1461
|
if (redisResult) return redisResult;
|
|
1796
1462
|
logRedisFallback();
|
|
1463
|
+
if (getOnStoreError() === "deny") return buildDeniedResult();
|
|
1797
1464
|
}
|
|
1798
1465
|
return getRateLimitStatusInMemory(key, limit);
|
|
1799
1466
|
},
|
|
@@ -1821,16 +1488,32 @@ var defaultRateLimitStrategy = {
|
|
|
1821
1488
|
rateLimitStore.clear();
|
|
1822
1489
|
}
|
|
1823
1490
|
};
|
|
1824
|
-
var
|
|
1491
|
+
var strategyRegistry = createRegistry(defaultRateLimitStrategy, {
|
|
1492
|
+
onRegister: (strategy) => {
|
|
1493
|
+
getLogger().debug(`[RateLimiter] active strategy \u2192 ${strategy.name}`);
|
|
1494
|
+
}
|
|
1495
|
+
});
|
|
1825
1496
|
var registerRateLimitStrategy = (strategy) => {
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
var
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
var
|
|
1497
|
+
strategyRegistry.register(strategy);
|
|
1498
|
+
};
|
|
1499
|
+
var getRateLimitStrategy = () => strategyRegistry.get();
|
|
1500
|
+
var checkRateLimit = async (params) => {
|
|
1501
|
+
ensureCleanupScheduled();
|
|
1502
|
+
return strategyRegistry.get().check(params);
|
|
1503
|
+
};
|
|
1504
|
+
var getRateLimitStatus = async (key, limit) => {
|
|
1505
|
+
ensureCleanupScheduled();
|
|
1506
|
+
return strategyRegistry.get().getStatus(key, limit);
|
|
1507
|
+
};
|
|
1508
|
+
var clearRateLimit = async (key) => strategyRegistry.get().clear(key);
|
|
1509
|
+
var clearAllRateLimits = async () => {
|
|
1510
|
+
if (process.env.NODE_ENV === "production") {
|
|
1511
|
+
getLogger().warn("[RateLimiter] clearAllRateLimits() called in production \u2014 ignored. Use clearRateLimit(key) for scoped resets.");
|
|
1512
|
+
return;
|
|
1513
|
+
}
|
|
1514
|
+
return strategyRegistry.get().clearAll();
|
|
1515
|
+
};
|
|
1516
|
+
var cleanupStarted = false;
|
|
1834
1517
|
var scheduleCleanup = () => {
|
|
1835
1518
|
const intervalMs = getProjectConfig().rateLimiting.cleanupIntervalMs;
|
|
1836
1519
|
const timer = setTimeout(() => {
|
|
@@ -1849,7 +1532,16 @@ var scheduleCleanup = () => {
|
|
|
1849
1532
|
}, intervalMs);
|
|
1850
1533
|
timer.unref();
|
|
1851
1534
|
};
|
|
1852
|
-
|
|
1535
|
+
var ensureCleanupScheduled = () => {
|
|
1536
|
+
if (cleanupStarted) return;
|
|
1537
|
+
cleanupStarted = true;
|
|
1538
|
+
if (!isRedisMode() && getOnStoreError() === "deny") {
|
|
1539
|
+
getLogger().warn(
|
|
1540
|
+
'[RateLimiter] onStoreError="deny" has no effect in memory mode (only Redis mode has a fallback path). Switch to store:"redis" or remove the setting.'
|
|
1541
|
+
);
|
|
1542
|
+
}
|
|
1543
|
+
scheduleCleanup();
|
|
1544
|
+
};
|
|
1853
1545
|
|
|
1854
1546
|
// src/runtimeTypeValidation.ts
|
|
1855
1547
|
var splitTopLevel = (value, splitter) => {
|
|
@@ -1857,24 +1549,78 @@ var splitTopLevel = (value, splitter) => {
|
|
|
1857
1549
|
let depthParen = 0;
|
|
1858
1550
|
let depthBrace = 0;
|
|
1859
1551
|
let depthBracket = 0;
|
|
1552
|
+
let inString = null;
|
|
1860
1553
|
let token = "";
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
if (char === "
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1554
|
+
const codePoints = [...value];
|
|
1555
|
+
let i = 0;
|
|
1556
|
+
while (i < codePoints.length) {
|
|
1557
|
+
const char = codePoints[i] ?? "";
|
|
1558
|
+
i += 1;
|
|
1559
|
+
if (inString && char === "\\") {
|
|
1560
|
+
token += char;
|
|
1561
|
+
const next = codePoints[i] ?? "";
|
|
1562
|
+
token += next;
|
|
1563
|
+
i += 1;
|
|
1564
|
+
continue;
|
|
1565
|
+
}
|
|
1566
|
+
if (char === '"' || char === "'") {
|
|
1567
|
+
if (inString === char) {
|
|
1568
|
+
inString = null;
|
|
1569
|
+
} else {
|
|
1570
|
+
inString ??= char;
|
|
1571
|
+
}
|
|
1572
|
+
token += char;
|
|
1871
1573
|
continue;
|
|
1872
1574
|
}
|
|
1575
|
+
if (!inString) {
|
|
1576
|
+
if (char === "(") depthParen += 1;
|
|
1577
|
+
if (char === ")") depthParen -= 1;
|
|
1578
|
+
if (char === "{") depthBrace += 1;
|
|
1579
|
+
if (char === "}") depthBrace -= 1;
|
|
1580
|
+
if (char === "[") depthBracket += 1;
|
|
1581
|
+
if (char === "]") depthBracket -= 1;
|
|
1582
|
+
if (char === splitter && depthParen === 0 && depthBrace === 0 && depthBracket === 0) {
|
|
1583
|
+
items.push(token.trim());
|
|
1584
|
+
token = "";
|
|
1585
|
+
continue;
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1873
1588
|
token += char;
|
|
1874
1589
|
}
|
|
1875
1590
|
if (token.trim()) items.push(token.trim());
|
|
1876
1591
|
return items;
|
|
1877
1592
|
};
|
|
1593
|
+
var QUOTED_FIELD_RE = /^("|')((?:[^\\]|\\.)*?)\1(\?)?\s*:\s*([\s\S]+)$/;
|
|
1594
|
+
var PLAIN_FIELD_RE = /^([A-Za-z_][A-Za-z0-9_]*)(\?)?\s*:\s*([\s\S]+)$/;
|
|
1595
|
+
var INDEX_SIG_RE = /^\[\s*([A-Za-z_][A-Za-z0-9_]*)\s*:\s*([^\]]+)\]\s*:\s*([\s\S]+)$/;
|
|
1596
|
+
var parseFieldOrIndex = (trimmed, fields, indexSignatures) => {
|
|
1597
|
+
const quotedMatch = QUOTED_FIELD_RE.exec(trimmed);
|
|
1598
|
+
if (quotedMatch) {
|
|
1599
|
+
fields.push({
|
|
1600
|
+
key: (quotedMatch[2] ?? "").replaceAll(String.raw`\'`, "'").replaceAll(String.raw`\"`, '"'),
|
|
1601
|
+
optional: Boolean(quotedMatch[3]),
|
|
1602
|
+
type: (quotedMatch[4] ?? "").trim()
|
|
1603
|
+
});
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
const plainMatch = PLAIN_FIELD_RE.exec(trimmed);
|
|
1607
|
+
if (plainMatch) {
|
|
1608
|
+
fields.push({
|
|
1609
|
+
key: plainMatch[1] ?? "",
|
|
1610
|
+
optional: Boolean(plainMatch[2]),
|
|
1611
|
+
type: (plainMatch[3] ?? "").trim()
|
|
1612
|
+
});
|
|
1613
|
+
return;
|
|
1614
|
+
}
|
|
1615
|
+
const indexMatch = INDEX_SIG_RE.exec(trimmed);
|
|
1616
|
+
if (indexMatch) {
|
|
1617
|
+
indexSignatures.push({
|
|
1618
|
+
keyName: (indexMatch[1] ?? "").trim(),
|
|
1619
|
+
keyType: (indexMatch[2] ?? "").trim(),
|
|
1620
|
+
type: (indexMatch[3] ?? "").trim()
|
|
1621
|
+
});
|
|
1622
|
+
}
|
|
1623
|
+
};
|
|
1878
1624
|
var parseObjectFields = (typeText) => {
|
|
1879
1625
|
const clean = typeText.trim();
|
|
1880
1626
|
if (!clean.startsWith("{") || !clean.endsWith("}")) {
|
|
@@ -1891,25 +1637,7 @@ var parseObjectFields = (typeText) => {
|
|
|
1891
1637
|
if (char === ";" && depth === 0) {
|
|
1892
1638
|
const trimmed = part.trim();
|
|
1893
1639
|
if (trimmed) {
|
|
1894
|
-
|
|
1895
|
-
if (fieldMatch) {
|
|
1896
|
-
const keyMatch = /^("|')?[A-Za-z_][A-Za-z0-9_]*("|')?/.exec(trimmed);
|
|
1897
|
-
const rawKey = keyMatch?.[0] ?? "";
|
|
1898
|
-
fields.push({
|
|
1899
|
-
key: rawKey.replaceAll(/^['"]|['"]$/g, ""),
|
|
1900
|
-
optional: Boolean(fieldMatch[3]),
|
|
1901
|
-
type: (fieldMatch[4] ?? "").trim()
|
|
1902
|
-
});
|
|
1903
|
-
} else {
|
|
1904
|
-
const indexMatch = /^\[\s*([A-Za-z_][A-Za-z0-9_]*)\s*:\s*([^\]]+)\]\s*:\s*([\s\S]+)$/.exec(trimmed);
|
|
1905
|
-
if (indexMatch) {
|
|
1906
|
-
indexSignatures.push({
|
|
1907
|
-
keyName: (indexMatch[1] ?? "").trim(),
|
|
1908
|
-
keyType: (indexMatch[2] ?? "").trim(),
|
|
1909
|
-
type: (indexMatch[3] ?? "").trim()
|
|
1910
|
-
});
|
|
1911
|
-
}
|
|
1912
|
-
}
|
|
1640
|
+
parseFieldOrIndex(trimmed, fields, indexSignatures);
|
|
1913
1641
|
}
|
|
1914
1642
|
part = "";
|
|
1915
1643
|
continue;
|
|
@@ -1918,25 +1646,7 @@ var parseObjectFields = (typeText) => {
|
|
|
1918
1646
|
}
|
|
1919
1647
|
const final = part.trim();
|
|
1920
1648
|
if (final) {
|
|
1921
|
-
|
|
1922
|
-
if (fieldMatch) {
|
|
1923
|
-
const keyMatch = /^("|')?[A-Za-z_][A-Za-z0-9_]*("|')?/.exec(final);
|
|
1924
|
-
const rawKey = keyMatch?.[0] ?? "";
|
|
1925
|
-
fields.push({
|
|
1926
|
-
key: rawKey.replaceAll(/^['"]|['"]$/g, ""),
|
|
1927
|
-
optional: Boolean(fieldMatch[3]),
|
|
1928
|
-
type: (fieldMatch[4] ?? "").trim()
|
|
1929
|
-
});
|
|
1930
|
-
} else {
|
|
1931
|
-
const indexMatch = /^\[\s*([A-Za-z_][A-Za-z0-9_]*)\s*:\s*([^\]]+)\]\s*:\s*([\s\S]+)$/.exec(final);
|
|
1932
|
-
if (indexMatch) {
|
|
1933
|
-
indexSignatures.push({
|
|
1934
|
-
keyName: (indexMatch[1] ?? "").trim(),
|
|
1935
|
-
keyType: (indexMatch[2] ?? "").trim(),
|
|
1936
|
-
type: (indexMatch[3] ?? "").trim()
|
|
1937
|
-
});
|
|
1938
|
-
}
|
|
1939
|
-
}
|
|
1649
|
+
parseFieldOrIndex(final, fields, indexSignatures);
|
|
1940
1650
|
}
|
|
1941
1651
|
return { fields, indexSignatures };
|
|
1942
1652
|
};
|
|
@@ -1961,78 +1671,120 @@ var isPrimitiveMatch = (type, value) => {
|
|
|
1961
1671
|
if (type === "false") return value === false;
|
|
1962
1672
|
if (type === "null") return value === null;
|
|
1963
1673
|
if (type === "undefined") return value === void 0;
|
|
1964
|
-
if (type === "Date") return typeof value === "string"
|
|
1674
|
+
if (type === "Date") return value instanceof Date || typeof value === "string" && !Number.isNaN(Date.parse(value));
|
|
1965
1675
|
return false;
|
|
1966
1676
|
};
|
|
1967
1677
|
var isPrimitiveType = (type) => {
|
|
1968
1678
|
return ["string", "number", "boolean", "true", "false", "null", "undefined", "Date"].includes(type);
|
|
1969
1679
|
};
|
|
1970
|
-
var
|
|
1680
|
+
var PROTO_POLLUTION_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
1681
|
+
var parseRecordParts = (type) => {
|
|
1682
|
+
const inner = type.slice("Record<".length, -1);
|
|
1683
|
+
let depth = 0;
|
|
1684
|
+
for (let index = 0; index < inner.length; index += 1) {
|
|
1685
|
+
const char = inner[index];
|
|
1686
|
+
if (char === "<" || char === "{" || char === "[" || char === "(") depth += 1;
|
|
1687
|
+
else if (char === ">" || char === "}" || char === "]" || char === ")") depth -= 1;
|
|
1688
|
+
else if (char === "," && depth === 0) {
|
|
1689
|
+
return {
|
|
1690
|
+
keyType: inner.slice(0, index).trim(),
|
|
1691
|
+
valueType: inner.slice(index + 1).trim()
|
|
1692
|
+
};
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
return null;
|
|
1696
|
+
};
|
|
1697
|
+
var MAX_VALIDATION_DEPTH = 64;
|
|
1698
|
+
var validateType = (typeText, value, path4, depth = 0) => {
|
|
1699
|
+
if (depth > MAX_VALIDATION_DEPTH) {
|
|
1700
|
+
return { status: "error", message: `${path4}: input nesting exceeds the maximum depth of ${String(MAX_VALIDATION_DEPTH)}` };
|
|
1701
|
+
}
|
|
1971
1702
|
const type = typeText.trim();
|
|
1972
1703
|
if (type.startsWith("__RUNTIME_UNRESOLVED__::")) {
|
|
1973
1704
|
const unresolvedMessage = type.replace("__RUNTIME_UNRESOLVED__::", "").trim();
|
|
1974
|
-
return { status: "error", message: `${
|
|
1705
|
+
return { status: "error", message: `${path4}: ${unresolvedMessage}` };
|
|
1975
1706
|
}
|
|
1976
1707
|
if (type.startsWith("(") && type.endsWith(")")) {
|
|
1977
|
-
return validateType(type.slice(1, -1), value,
|
|
1708
|
+
return validateType(type.slice(1, -1), value, path4, depth + 1);
|
|
1978
1709
|
}
|
|
1979
1710
|
if (type.includes("|")) {
|
|
1980
1711
|
const unionParts = splitTopLevel(type, "|").filter(Boolean);
|
|
1981
1712
|
if (unionParts.length > 1) {
|
|
1982
1713
|
for (const unionType of unionParts) {
|
|
1983
|
-
const result = validateType(unionType, value,
|
|
1714
|
+
const result = validateType(unionType, value, path4, depth + 1);
|
|
1984
1715
|
if (result.status === "success") return result;
|
|
1985
1716
|
}
|
|
1986
|
-
return { status: "error", message: `${
|
|
1717
|
+
return { status: "error", message: `${path4} does not match union type ${type}` };
|
|
1718
|
+
}
|
|
1719
|
+
if (unionParts.length === 1) {
|
|
1720
|
+
return validateType(unionParts[0] ?? type, value, path4, depth + 1);
|
|
1987
1721
|
}
|
|
1988
1722
|
}
|
|
1989
1723
|
if (type.includes("&")) {
|
|
1990
1724
|
const intersectionParts = splitTopLevel(type, "&").filter(Boolean);
|
|
1991
1725
|
if (intersectionParts.length > 1) {
|
|
1992
1726
|
for (const intersectionType of intersectionParts) {
|
|
1993
|
-
const result = validateType(intersectionType, value,
|
|
1727
|
+
const result = validateType(intersectionType, value, path4, depth + 1);
|
|
1994
1728
|
if (result.status === "error") return result;
|
|
1995
1729
|
}
|
|
1996
1730
|
return { status: "success" };
|
|
1997
1731
|
}
|
|
1732
|
+
if (intersectionParts.length === 1) {
|
|
1733
|
+
return validateType(intersectionParts[0] ?? type, value, path4, depth + 1);
|
|
1734
|
+
}
|
|
1998
1735
|
}
|
|
1999
1736
|
if (type.endsWith("[]")) {
|
|
2000
1737
|
if (!Array.isArray(value)) {
|
|
2001
|
-
return { status: "error", message: `${
|
|
1738
|
+
return { status: "error", message: `${path4} should be an array` };
|
|
2002
1739
|
}
|
|
2003
1740
|
const itemType = type.slice(0, -2).trim();
|
|
2004
|
-
if (itemType === type) {
|
|
2005
|
-
return { status: "
|
|
1741
|
+
if (itemType === "" || itemType === type) {
|
|
1742
|
+
return { status: "error", message: `${path4}: unvalidatable array element type for ${type}` };
|
|
2006
1743
|
}
|
|
2007
1744
|
for (const [index, element] of value.entries()) {
|
|
2008
|
-
const result = validateType(itemType, element, `${
|
|
1745
|
+
const result = validateType(itemType, element, `${path4}[${String(index)}]`, depth + 1);
|
|
2009
1746
|
if (result.status === "error") return result;
|
|
2010
1747
|
}
|
|
2011
1748
|
return { status: "success" };
|
|
2012
1749
|
}
|
|
2013
1750
|
if (type.startsWith("'") && type.endsWith("'") || type.startsWith('"') && type.endsWith('"')) {
|
|
2014
1751
|
const literal = type.slice(1, -1);
|
|
2015
|
-
return value === literal ? { status: "success" } : { status: "error", message: `${
|
|
1752
|
+
return value === literal ? { status: "success" } : { status: "error", message: `${path4} should equal ${literal}` };
|
|
2016
1753
|
}
|
|
2017
1754
|
if (isPrimitiveMatch(type, value)) {
|
|
2018
1755
|
return { status: "success" };
|
|
2019
1756
|
}
|
|
2020
1757
|
if (isPrimitiveType(type)) {
|
|
2021
1758
|
const expectedType = type === "Date" ? "Date (ISO string or Date)" : type;
|
|
2022
|
-
return { status: "error", message: `${
|
|
1759
|
+
return { status: "error", message: `${path4} should be ${expectedType}` };
|
|
2023
1760
|
}
|
|
2024
1761
|
if (type === "any" || type === "unknown") {
|
|
2025
1762
|
return { status: "success" };
|
|
2026
1763
|
}
|
|
2027
1764
|
if (/^Record<.+>$/.test(type)) {
|
|
2028
|
-
if (typeof value
|
|
2029
|
-
return { status: "
|
|
1765
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1766
|
+
return { status: "error", message: `${path4} should be an object` };
|
|
2030
1767
|
}
|
|
2031
|
-
|
|
1768
|
+
const record = value;
|
|
1769
|
+
const recordParts = parseRecordParts(type);
|
|
1770
|
+
if (!recordParts) {
|
|
1771
|
+
return { status: "error", message: `${path4}: unvalidatable Record type ${type} \u2014 model it explicitly` };
|
|
1772
|
+
}
|
|
1773
|
+
for (const key of Object.keys(record)) {
|
|
1774
|
+
if (PROTO_POLLUTION_KEYS.has(key)) {
|
|
1775
|
+
return { status: "error", message: `${path4}.${key} is not allowed` };
|
|
1776
|
+
}
|
|
1777
|
+
if ((recordParts.keyType === "number" || recordParts.keyType.includes("|") || recordParts.keyType.startsWith("'") || recordParts.keyType.startsWith('"')) && !matchesIndexKeyType({ keyType: recordParts.keyType, key })) {
|
|
1778
|
+
return { status: "error", message: `${path4}.${key} is not allowed` };
|
|
1779
|
+
}
|
|
1780
|
+
const valueResult = validateType(recordParts.valueType, record[key], `${path4}.${key}`, depth + 1);
|
|
1781
|
+
if (valueResult.status === "error") return valueResult;
|
|
1782
|
+
}
|
|
1783
|
+
return { status: "success" };
|
|
2032
1784
|
}
|
|
2033
1785
|
if (type.startsWith("{") && type.endsWith("}")) {
|
|
2034
1786
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
2035
|
-
return { status: "error", message: `${
|
|
1787
|
+
return { status: "error", message: `${path4} should be an object` };
|
|
2036
1788
|
}
|
|
2037
1789
|
const { fields, indexSignatures } = parseObjectFields(type);
|
|
2038
1790
|
const input = value;
|
|
@@ -2040,9 +1792,9 @@ var validateType = (typeText, value, path3) => {
|
|
|
2040
1792
|
const fieldValue = input[field.key];
|
|
2041
1793
|
if (fieldValue === void 0) {
|
|
2042
1794
|
if (field.optional) continue;
|
|
2043
|
-
return { status: "error", message: `${
|
|
1795
|
+
return { status: "error", message: `${path4}.${field.key} is required` };
|
|
2044
1796
|
}
|
|
2045
|
-
const result = validateType(field.type, fieldValue, `${
|
|
1797
|
+
const result = validateType(field.type, fieldValue, `${path4}.${field.key}`, depth + 1);
|
|
2046
1798
|
if (result.status === "error") return result;
|
|
2047
1799
|
}
|
|
2048
1800
|
const allowedKeys = new Set(fields.map((field) => field.key));
|
|
@@ -2050,8 +1802,11 @@ var validateType = (typeText, value, path3) => {
|
|
|
2050
1802
|
if (allowedKeys.has(key)) {
|
|
2051
1803
|
continue;
|
|
2052
1804
|
}
|
|
1805
|
+
if (PROTO_POLLUTION_KEYS.has(key)) {
|
|
1806
|
+
return { status: "error", message: `${path4}.${key} is not allowed` };
|
|
1807
|
+
}
|
|
2053
1808
|
if (indexSignatures.length === 0) {
|
|
2054
|
-
return { status: "error", message: `${
|
|
1809
|
+
return { status: "error", message: `${path4}.${key} is not allowed` };
|
|
2055
1810
|
}
|
|
2056
1811
|
const indexValue = input[key];
|
|
2057
1812
|
let matched = false;
|
|
@@ -2059,25 +1814,32 @@ var validateType = (typeText, value, path3) => {
|
|
|
2059
1814
|
if (!matchesIndexKeyType({ keyType: indexSignature.keyType, key })) {
|
|
2060
1815
|
continue;
|
|
2061
1816
|
}
|
|
2062
|
-
const indexResult = validateType(indexSignature.type, indexValue, `${
|
|
1817
|
+
const indexResult = validateType(indexSignature.type, indexValue, `${path4}.${key}`, depth + 1);
|
|
2063
1818
|
if (indexResult.status === "success") {
|
|
2064
1819
|
matched = true;
|
|
2065
1820
|
break;
|
|
2066
1821
|
}
|
|
2067
1822
|
}
|
|
2068
1823
|
if (!matched) {
|
|
2069
|
-
return { status: "error", message: `${
|
|
1824
|
+
return { status: "error", message: `${path4}.${key} is not allowed` };
|
|
2070
1825
|
}
|
|
2071
1826
|
}
|
|
2072
1827
|
return { status: "success" };
|
|
2073
1828
|
}
|
|
2074
1829
|
if (/^[A-Za-z_][A-Za-z0-9_]*(?:<.+>)?$/.test(type)) {
|
|
2075
1830
|
if (/^[A-Za-z_][A-Za-z0-9_]*<.+>$/.test(type)) {
|
|
2076
|
-
return { status: "error", message: `${
|
|
1831
|
+
return { status: "error", message: `${path4}: unresolved utility ${type}` };
|
|
2077
1832
|
}
|
|
2078
|
-
return { status: "error", message: `${
|
|
1833
|
+
return { status: "error", message: `${path4}: unresolved type ${type}` };
|
|
2079
1834
|
}
|
|
2080
|
-
return { status: "
|
|
1835
|
+
return { status: "error", message: `${path4}: unvalidatable type ${type} \u2014 model it explicitly or set validation: 'relaxed' on the route` };
|
|
1836
|
+
};
|
|
1837
|
+
var safeValidateType = (typeText, value, path4) => {
|
|
1838
|
+
const [error, result] = tryCatchSync(() => validateType(typeText, value, path4));
|
|
1839
|
+
if (error || !result) {
|
|
1840
|
+
return { status: "error", message: `${path4}: input validation failed` };
|
|
1841
|
+
}
|
|
1842
|
+
return result;
|
|
2081
1843
|
};
|
|
2082
1844
|
var validateInputByType = async ({
|
|
2083
1845
|
typeText,
|
|
@@ -2089,7 +1851,11 @@ var validateInputByType = async ({
|
|
|
2089
1851
|
return { status: "success" };
|
|
2090
1852
|
}
|
|
2091
1853
|
if (process.env.NODE_ENV === "production") {
|
|
2092
|
-
|
|
1854
|
+
const { getProjectConfig: getProjectConfig2 } = await import("./projectConfig-JX563K43.js");
|
|
1855
|
+
if (getProjectConfig2().validation.runtimeMode === "off") {
|
|
1856
|
+
return { status: "success" };
|
|
1857
|
+
}
|
|
1858
|
+
return safeValidateType(typeText, value, rootKey);
|
|
2093
1859
|
}
|
|
2094
1860
|
const devkitModuleId = "@luckystack/devkit";
|
|
2095
1861
|
const devkit = await import(devkitModuleId);
|
|
@@ -2097,7 +1863,7 @@ var validateInputByType = async ({
|
|
|
2097
1863
|
if (resolvedType.status === "error") {
|
|
2098
1864
|
return { status: "error", message: `${rootKey}: ${resolvedType.message}` };
|
|
2099
1865
|
}
|
|
2100
|
-
return
|
|
1866
|
+
return safeValidateType(resolvedType.typeText, value, rootKey);
|
|
2101
1867
|
};
|
|
2102
1868
|
|
|
2103
1869
|
// src/checkOrigin.ts
|
|
@@ -2114,24 +1880,25 @@ var normalizeOrigin = ({ value, secure }) => {
|
|
|
2114
1880
|
return extractedOrigin.replace(/^http:\/\/(.+):80$/i, "http://$1").replace(/^https:\/\/(.+):443$/i, "https://$1");
|
|
2115
1881
|
};
|
|
2116
1882
|
var isLocalhostOrigin = (normalized) => {
|
|
2117
|
-
return /^https?:\/\/localhost(:\d+)?$/i.test(normalized);
|
|
1883
|
+
return /^https?:\/\/(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/i.test(normalized);
|
|
2118
1884
|
};
|
|
2119
1885
|
var allowedOrigin = (origin) => {
|
|
2120
1886
|
const secure = process.env.SECURE === "true";
|
|
2121
1887
|
const cors = getProjectConfig().http.cors;
|
|
2122
1888
|
const configured = cors.allowedOrigins;
|
|
2123
1889
|
const { ip: bindIp, port: bindPort } = getBindAddress();
|
|
2124
|
-
const
|
|
1890
|
+
const rawLocation = bindPort ? `http${secure ? "s" : ""}://${bindIp}:${bindPort}` : `http${secure ? "s" : ""}://${bindIp}`;
|
|
1891
|
+
const location = normalizeOrigin({ value: rawLocation, secure });
|
|
2125
1892
|
const normalizedOrigin = normalizeOrigin({ value: origin, secure });
|
|
2126
1893
|
if (cors.allowLocalhost && normalizedOrigin && isLocalhostOrigin(normalizedOrigin)) {
|
|
2127
1894
|
return true;
|
|
2128
1895
|
}
|
|
2129
1896
|
if (typeof configured === "function") {
|
|
2130
|
-
if (normalizedOrigin === location) return true;
|
|
2131
|
-
if (configured(
|
|
1897
|
+
if (normalizedOrigin && normalizedOrigin === location) return true;
|
|
1898
|
+
if (configured(normalizedOrigin)) return true;
|
|
2132
1899
|
} else {
|
|
2133
1900
|
const normalizedAllowedOrigins = new Set(
|
|
2134
|
-
[location, ...configured
|
|
1901
|
+
[location, ...configured.map((value) => normalizeOrigin({ value, secure }))].filter(Boolean)
|
|
2135
1902
|
);
|
|
2136
1903
|
if (normalizedOrigin && normalizedAllowedOrigins.has(normalizedOrigin)) {
|
|
2137
1904
|
return true;
|
|
@@ -2156,121 +1923,6 @@ var allowedOrigin = (origin) => {
|
|
|
2156
1923
|
};
|
|
2157
1924
|
var checkOrigin_default = allowedOrigin;
|
|
2158
1925
|
|
|
2159
|
-
// src/offlineQueue.ts
|
|
2160
|
-
var apiQueue = [];
|
|
2161
|
-
var syncQueue = [];
|
|
2162
|
-
var isFlushingApi = false;
|
|
2163
|
-
var isFlushingSync = false;
|
|
2164
|
-
var isOnline = () => {
|
|
2165
|
-
if (typeof navigator === "undefined") return true;
|
|
2166
|
-
return navigator.onLine;
|
|
2167
|
-
};
|
|
2168
|
-
var evictExpired = (queue) => {
|
|
2169
|
-
const maxAgeMs = getProjectConfig().offlineQueue.maxAgeMs;
|
|
2170
|
-
if (maxAgeMs <= 0) return;
|
|
2171
|
-
const cutoff = Date.now() - maxAgeMs;
|
|
2172
|
-
for (let i = queue.length - 1; i >= 0; i -= 1) {
|
|
2173
|
-
const item = queue[i];
|
|
2174
|
-
if (item && item.createdAt < cutoff) {
|
|
2175
|
-
queue.splice(i, 1);
|
|
2176
|
-
}
|
|
2177
|
-
}
|
|
2178
|
-
};
|
|
2179
|
-
var enqueueWithPolicy = (queue, item, label) => {
|
|
2180
|
-
evictExpired(queue);
|
|
2181
|
-
const { maxSize, dropPolicy: globalPolicy } = getProjectConfig().offlineQueue;
|
|
2182
|
-
const effectivePolicy = item.dropPolicy ?? globalPolicy;
|
|
2183
|
-
if (queue.length < maxSize) {
|
|
2184
|
-
queue.push(item);
|
|
2185
|
-
return true;
|
|
2186
|
-
}
|
|
2187
|
-
if (effectivePolicy === "drop-oldest") {
|
|
2188
|
-
queue.shift();
|
|
2189
|
-
queue.push(item);
|
|
2190
|
-
getLogger().debug(`offlineQueue:${label} full \u2014 dropped oldest item (policy=${effectivePolicy})`);
|
|
2191
|
-
return true;
|
|
2192
|
-
}
|
|
2193
|
-
if (effectivePolicy === "drop-newest") {
|
|
2194
|
-
getLogger().debug(`offlineQueue:${label} full \u2014 dropped newest item (policy=${effectivePolicy})`);
|
|
2195
|
-
return false;
|
|
2196
|
-
}
|
|
2197
|
-
getLogger().warn(`offlineQueue:${label} full \u2014 rejecting enqueue (policy=${effectivePolicy})`);
|
|
2198
|
-
return false;
|
|
2199
|
-
};
|
|
2200
|
-
var enqueueApiRequest = (item) => enqueueWithPolicy(apiQueue, item, "api");
|
|
2201
|
-
var enqueueSyncRequest = (item) => enqueueWithPolicy(syncQueue, item, "sync");
|
|
2202
|
-
var removeApiQueueItem = (id) => {
|
|
2203
|
-
const index = apiQueue.findIndex((item) => item.id === id);
|
|
2204
|
-
if (index !== -1) {
|
|
2205
|
-
apiQueue.splice(index, 1);
|
|
2206
|
-
}
|
|
2207
|
-
};
|
|
2208
|
-
var removeSyncQueueItem = (id) => {
|
|
2209
|
-
const index = syncQueue.findIndex((item) => item.id === id);
|
|
2210
|
-
if (index !== -1) {
|
|
2211
|
-
syncQueue.splice(index, 1);
|
|
2212
|
-
}
|
|
2213
|
-
};
|
|
2214
|
-
var removeApiQueueItemsByKey = (key) => {
|
|
2215
|
-
for (let i = apiQueue.length - 1; i >= 0; i -= 1) {
|
|
2216
|
-
if (apiQueue[i]?.key === key) {
|
|
2217
|
-
apiQueue.splice(i, 1);
|
|
2218
|
-
}
|
|
2219
|
-
}
|
|
2220
|
-
};
|
|
2221
|
-
var flushApiQueue = (canRun, socketInstance) => {
|
|
2222
|
-
if (isFlushingApi) return;
|
|
2223
|
-
evictExpired(apiQueue);
|
|
2224
|
-
if (apiQueue.length === 0) return;
|
|
2225
|
-
if (!canRun()) return;
|
|
2226
|
-
isFlushingApi = true;
|
|
2227
|
-
while (apiQueue.length > 0) {
|
|
2228
|
-
if (!canRun()) break;
|
|
2229
|
-
const item = apiQueue.shift();
|
|
2230
|
-
item?.run(socketInstance);
|
|
2231
|
-
}
|
|
2232
|
-
isFlushingApi = false;
|
|
2233
|
-
};
|
|
2234
|
-
var flushSyncQueue = (canRun, socketInstance) => {
|
|
2235
|
-
if (isFlushingSync) return;
|
|
2236
|
-
evictExpired(syncQueue);
|
|
2237
|
-
if (syncQueue.length === 0) return;
|
|
2238
|
-
if (!canRun()) return;
|
|
2239
|
-
isFlushingSync = true;
|
|
2240
|
-
while (syncQueue.length > 0) {
|
|
2241
|
-
if (!canRun()) break;
|
|
2242
|
-
const item = syncQueue.shift();
|
|
2243
|
-
item?.run(socketInstance);
|
|
2244
|
-
}
|
|
2245
|
-
isFlushingSync = false;
|
|
2246
|
-
};
|
|
2247
|
-
var getApiQueueSize = () => apiQueue.length;
|
|
2248
|
-
var getSyncQueueSize = () => syncQueue.length;
|
|
2249
|
-
|
|
2250
|
-
// src/socketState.ts
|
|
2251
|
-
var socket = null;
|
|
2252
|
-
var setSocket = (next) => {
|
|
2253
|
-
socket = next;
|
|
2254
|
-
};
|
|
2255
|
-
var responseIndex = 0;
|
|
2256
|
-
var WAIT_FOR_SOCKET_INTERVAL_MS = 10;
|
|
2257
|
-
var WAIT_FOR_SOCKET_MAX_ITERATIONS = 500;
|
|
2258
|
-
var incrementResponseIndex = () => {
|
|
2259
|
-
responseIndex = responseIndex + 1;
|
|
2260
|
-
return responseIndex;
|
|
2261
|
-
};
|
|
2262
|
-
var waitForSocket = async () => {
|
|
2263
|
-
let i = 0;
|
|
2264
|
-
while (!socket) {
|
|
2265
|
-
await new Promise((resolve) => setTimeout(resolve, WAIT_FOR_SOCKET_INTERVAL_MS));
|
|
2266
|
-
i++;
|
|
2267
|
-
if (i > WAIT_FOR_SOCKET_MAX_ITERATIONS) {
|
|
2268
|
-
return null;
|
|
2269
|
-
}
|
|
2270
|
-
}
|
|
2271
|
-
return socket;
|
|
2272
|
-
};
|
|
2273
|
-
|
|
2274
1926
|
// src/cancelRegistry.ts
|
|
2275
1927
|
var syncControllers = /* @__PURE__ */ new Map();
|
|
2276
1928
|
var apiControllers = /* @__PURE__ */ new Map();
|
|
@@ -2291,16 +1943,16 @@ var abortSyncByCb = (socketId, cb) => {
|
|
|
2291
1943
|
syncControllers.delete(key);
|
|
2292
1944
|
return true;
|
|
2293
1945
|
};
|
|
2294
|
-
var registerApiAbortController = (socketId,
|
|
2295
|
-
const key = buildKey(socketId,
|
|
1946
|
+
var registerApiAbortController = (socketId, responseIndex, controller) => {
|
|
1947
|
+
const key = buildKey(socketId, responseIndex);
|
|
2296
1948
|
apiControllers.set(key, controller);
|
|
2297
1949
|
return key;
|
|
2298
1950
|
};
|
|
2299
1951
|
var unregisterApiAbortController = (key) => {
|
|
2300
1952
|
apiControllers.delete(key);
|
|
2301
1953
|
};
|
|
2302
|
-
var abortApiByResponseIndex = (socketId,
|
|
2303
|
-
const key = buildKey(socketId,
|
|
1954
|
+
var abortApiByResponseIndex = (socketId, responseIndex) => {
|
|
1955
|
+
const key = buildKey(socketId, responseIndex);
|
|
2304
1956
|
const controller = apiControllers.get(key);
|
|
2305
1957
|
if (!controller) return false;
|
|
2306
1958
|
controller.abort();
|
|
@@ -2323,90 +1975,40 @@ var abortAllForSocket = (socketId) => {
|
|
|
2323
1975
|
}
|
|
2324
1976
|
};
|
|
2325
1977
|
|
|
2326
|
-
// src/
|
|
2327
|
-
|
|
2328
|
-
var
|
|
2329
|
-
var
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
const response = await fetch(`${base}/auth/csrf`, {
|
|
2339
|
-
method: "GET",
|
|
2340
|
-
credentials: "include"
|
|
2341
|
-
});
|
|
2342
|
-
if (!response.ok) return null;
|
|
2343
|
-
const body = await response.json();
|
|
2344
|
-
return body.csrfToken ?? null;
|
|
2345
|
-
});
|
|
2346
|
-
if (error) return null;
|
|
2347
|
-
return token ?? null;
|
|
2348
|
-
};
|
|
2349
|
-
var getCsrfToken = async () => {
|
|
2350
|
-
if (getProjectConfig().session.basedToken) {
|
|
2351
|
-
return null;
|
|
2352
|
-
}
|
|
2353
|
-
if (cachedToken) return cachedToken;
|
|
2354
|
-
if (inflightFetch) return inflightFetch;
|
|
2355
|
-
inflightFetch = fetchCsrfToken().then((token) => {
|
|
2356
|
-
cachedToken = token;
|
|
2357
|
-
inflightFetch = null;
|
|
2358
|
-
return token;
|
|
2359
|
-
});
|
|
2360
|
-
return inflightFetch;
|
|
2361
|
-
};
|
|
2362
|
-
var clearCsrfToken = () => {
|
|
2363
|
-
cachedToken = null;
|
|
2364
|
-
inflightFetch = null;
|
|
2365
|
-
};
|
|
2366
|
-
var STATE_CHANGING = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
2367
|
-
var isCsrfMismatchResponse = async (response) => {
|
|
2368
|
-
if (response.status !== 403) return false;
|
|
2369
|
-
const [error, isMismatch] = await tryCatch(async () => {
|
|
2370
|
-
const cloned = response.clone();
|
|
2371
|
-
const body = await cloned.json();
|
|
2372
|
-
return body.errorCode === "auth.csrfMismatch";
|
|
2373
|
-
});
|
|
2374
|
-
if (error) return false;
|
|
2375
|
-
return isMismatch ?? false;
|
|
2376
|
-
};
|
|
2377
|
-
var httpFetch = async (input, init = {}) => {
|
|
2378
|
-
const method = (init.method ?? "GET").toUpperCase();
|
|
2379
|
-
const headers = new Headers(init.headers);
|
|
2380
|
-
if (!headers.has("Content-Type") && init.body && typeof init.body === "string") {
|
|
2381
|
-
headers.set("Content-Type", "application/json");
|
|
2382
|
-
}
|
|
2383
|
-
const csrfHeaderName = getCsrfConfig().headerName;
|
|
2384
|
-
const send = async (csrfToken) => {
|
|
2385
|
-
const finalHeaders = new Headers(headers);
|
|
2386
|
-
if (csrfToken && STATE_CHANGING.has(method) && !finalHeaders.has(csrfHeaderName)) {
|
|
2387
|
-
finalHeaders.set(csrfHeaderName, csrfToken);
|
|
1978
|
+
// src/oneTimeToken.ts
|
|
1979
|
+
import { createHash as createHash3, randomBytes as randomBytes2 } from "crypto";
|
|
1980
|
+
var TOKEN_BYTES = 32;
|
|
1981
|
+
var hashToken = (token) => createHash3("sha256").update(token).digest("hex");
|
|
1982
|
+
var tokenKey = (namespace, token) => formatKey(namespace, hashToken(token));
|
|
1983
|
+
var issueOneTimeToken = (namespace, ttlSeconds, payload) => {
|
|
1984
|
+
const token = randomBytes2(TOKEN_BYTES).toString("hex");
|
|
1985
|
+
const value = typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
1986
|
+
return {
|
|
1987
|
+
token,
|
|
1988
|
+
store: async () => {
|
|
1989
|
+
await redis_default.set(tokenKey(namespace, token), value, "EX", ttlSeconds);
|
|
2388
1990
|
}
|
|
2389
|
-
return fetch(input, {
|
|
2390
|
-
...init,
|
|
2391
|
-
method,
|
|
2392
|
-
headers: finalHeaders,
|
|
2393
|
-
credentials: init.credentials ?? "include"
|
|
2394
|
-
});
|
|
2395
1991
|
};
|
|
2396
|
-
if (!STATE_CHANGING.has(method) || getProjectConfig().session.basedToken) {
|
|
2397
|
-
return send(null);
|
|
2398
|
-
}
|
|
2399
|
-
const token = await getCsrfToken();
|
|
2400
|
-
let response = await send(token);
|
|
2401
|
-
if (await isCsrfMismatchResponse(response)) {
|
|
2402
|
-
clearCsrfToken();
|
|
2403
|
-
const refreshed = await getCsrfToken();
|
|
2404
|
-
if (refreshed) {
|
|
2405
|
-
response = await send(refreshed);
|
|
2406
|
-
}
|
|
2407
|
-
}
|
|
2408
|
-
return response;
|
|
2409
1992
|
};
|
|
1993
|
+
var consumeOneTimeToken = async (namespace, token) => {
|
|
1994
|
+
if (!token || typeof token !== "string") return null;
|
|
1995
|
+
const key = tokenKey(namespace, token);
|
|
1996
|
+
const txResult = await redis_default.multi().get(key).del(key).exec();
|
|
1997
|
+
if (!txResult || txResult.length === 0) return null;
|
|
1998
|
+
const first = txResult[0];
|
|
1999
|
+
if (!first) return null;
|
|
2000
|
+
const [getErr, value] = first;
|
|
2001
|
+
if (getErr) return null;
|
|
2002
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
2003
|
+
};
|
|
2004
|
+
var consumeOneTimeTokenJson = async (namespace, token) => {
|
|
2005
|
+
const raw = await consumeOneTimeToken(namespace, token);
|
|
2006
|
+
if (raw === null) return null;
|
|
2007
|
+
const [parseErr, parsed] = tryCatchSync(() => JSON.parse(raw));
|
|
2008
|
+
if (parseErr) return null;
|
|
2009
|
+
return parsed;
|
|
2010
|
+
};
|
|
2011
|
+
var oneTimeTokenKey = tokenKey;
|
|
2410
2012
|
export {
|
|
2411
2013
|
BOOT_KEY_PREFIX,
|
|
2412
2014
|
DEFAULT_AVATAR_CONFIG,
|
|
@@ -2415,23 +2017,29 @@ export {
|
|
|
2415
2017
|
DEFAULT_ENV_FILES,
|
|
2416
2018
|
DEFAULT_PAGE_ROUTE_RULES,
|
|
2417
2019
|
DEFAULT_PROJECT_CONFIG,
|
|
2020
|
+
DEFAULT_REDACTED_LOG_KEYS,
|
|
2021
|
+
DEPTH_TRUNCATED_PLACEHOLDER,
|
|
2418
2022
|
GENERATED_API_DOCS_PATH,
|
|
2419
2023
|
GENERATED_API_SCHEMAS_PATH,
|
|
2420
2024
|
GENERATED_SOCKET_TYPES_PATH,
|
|
2421
2025
|
INVALID_ERROR_RESPONSE_CODE,
|
|
2422
2026
|
PUBLIC_DIR,
|
|
2027
|
+
REDACTED_PLACEHOLDER,
|
|
2423
2028
|
ROOT_DIR,
|
|
2424
2029
|
SERVER_DIR,
|
|
2425
2030
|
SERVER_FUNCTIONS_DIR,
|
|
2426
2031
|
SHARED_DIR,
|
|
2427
2032
|
SRC_DIR,
|
|
2428
2033
|
TSCONFIG_ALIAS_FILES,
|
|
2034
|
+
UNKNOWN_CLIENT_IP,
|
|
2429
2035
|
UPLOADS_DIR,
|
|
2430
2036
|
abortAllForSocket,
|
|
2431
2037
|
abortApiByResponseIndex,
|
|
2432
2038
|
abortSyncByCb,
|
|
2433
2039
|
acquireLease,
|
|
2434
2040
|
checkOrigin_default as allowedOrigin,
|
|
2041
|
+
appendErrorTracker,
|
|
2042
|
+
applyCookiePrefixConstraints,
|
|
2435
2043
|
applyErrorFormatter,
|
|
2436
2044
|
applySocketMiddlewares,
|
|
2437
2045
|
applyStrayKeyPrefix,
|
|
@@ -2456,22 +2064,33 @@ export {
|
|
|
2456
2064
|
clearSocketMiddlewares,
|
|
2457
2065
|
collectSynchronizedEnvKeys,
|
|
2458
2066
|
computeSynchronizedEnvHashes,
|
|
2067
|
+
consumeOneTimeToken,
|
|
2068
|
+
consumeOneTimeTokenJson,
|
|
2459
2069
|
createDevLogger,
|
|
2460
2070
|
createLocalizedNormalizer,
|
|
2071
|
+
createRegistry,
|
|
2072
|
+
deepMerge,
|
|
2461
2073
|
defaultHttpStatusForResponse,
|
|
2462
2074
|
defaultRateLimitStrategy,
|
|
2463
2075
|
defaultRedisKeyFormatter,
|
|
2076
|
+
defaultRoomNameFormatter,
|
|
2077
|
+
deriveTokenBucketId,
|
|
2078
|
+
describeHealthHashConfig,
|
|
2464
2079
|
dispatchHook,
|
|
2465
2080
|
dispatchSyncHook,
|
|
2466
2081
|
enqueueApiRequest,
|
|
2467
2082
|
enqueueSyncRequest,
|
|
2083
|
+
ensurePeerDepInstalled,
|
|
2468
2084
|
env,
|
|
2085
|
+
escapeHtml,
|
|
2469
2086
|
extractLanguageFromHeader,
|
|
2470
2087
|
extractTokenFromRequest,
|
|
2471
2088
|
extractTokenFromSocket,
|
|
2472
2089
|
flushApiQueue,
|
|
2090
|
+
flushErrorTrackers,
|
|
2473
2091
|
flushSyncQueue,
|
|
2474
2092
|
formatKey,
|
|
2093
|
+
formatRoomName,
|
|
2475
2094
|
getActiveErrorTrackers,
|
|
2476
2095
|
getApiQueueSize,
|
|
2477
2096
|
getAvatarConfig,
|
|
@@ -2479,10 +2098,12 @@ export {
|
|
|
2479
2098
|
getCookieValue,
|
|
2480
2099
|
getCsrfConfig,
|
|
2481
2100
|
getCsrfToken,
|
|
2101
|
+
getCurrentErrorTrackerIdentity,
|
|
2482
2102
|
getDeployConfig,
|
|
2483
2103
|
getEffectiveHttpMethod,
|
|
2484
2104
|
getEmailSender,
|
|
2485
2105
|
getEmailSenderByName,
|
|
2106
|
+
getEnv,
|
|
2486
2107
|
getEnvFiles,
|
|
2487
2108
|
getErrorFormatter,
|
|
2488
2109
|
getGeneratedApiDocsPath,
|
|
@@ -2509,6 +2130,8 @@ export {
|
|
|
2509
2130
|
getRedisConnectionOptions,
|
|
2510
2131
|
getRedisKeyFormatter,
|
|
2511
2132
|
getRegisteredApiMethod,
|
|
2133
|
+
getRegisteredSessionProvider,
|
|
2134
|
+
getRoomNameFormatter,
|
|
2512
2135
|
getRuntimeApiMaps,
|
|
2513
2136
|
getRuntimeSyncMaps,
|
|
2514
2137
|
getServerDir,
|
|
@@ -2522,6 +2145,7 @@ export {
|
|
|
2522
2145
|
getUploadsDir,
|
|
2523
2146
|
hasCookie,
|
|
2524
2147
|
hashSynchronizedValue,
|
|
2148
|
+
hashSynchronizedValueWith,
|
|
2525
2149
|
httpFetch,
|
|
2526
2150
|
incrementResponseIndex,
|
|
2527
2151
|
inferHttpMethod,
|
|
@@ -2532,10 +2156,13 @@ export {
|
|
|
2532
2156
|
isEmailSenderRegistered,
|
|
2533
2157
|
isErrorParamArray,
|
|
2534
2158
|
isFalsy,
|
|
2159
|
+
isGetMethodName,
|
|
2535
2160
|
isLocalizedNormalizerRegistered,
|
|
2536
2161
|
isLoggerRegistered,
|
|
2162
|
+
isLoopbackIp,
|
|
2537
2163
|
isMethodAllowed,
|
|
2538
2164
|
isOnline,
|
|
2165
|
+
isPlainObject,
|
|
2539
2166
|
isPrismaClientRegistered,
|
|
2540
2167
|
isProduction,
|
|
2541
2168
|
isProjectConfigRegistered,
|
|
@@ -2543,16 +2170,23 @@ export {
|
|
|
2543
2170
|
isRedisClientRegistered,
|
|
2544
2171
|
isRuntimeMapsProviderRegistered,
|
|
2545
2172
|
isServicesConfigRegistered,
|
|
2173
|
+
isSessionProviderRegistered,
|
|
2174
|
+
issueOneTimeToken,
|
|
2546
2175
|
listEmailSenderNames,
|
|
2547
2176
|
loadEnvFiles,
|
|
2177
|
+
loadPeer,
|
|
2548
2178
|
normalizeErrorResponse,
|
|
2549
2179
|
normalizeErrorResponseCore,
|
|
2180
|
+
normalizeOrigin,
|
|
2550
2181
|
notify,
|
|
2182
|
+
oneTimeTokenKey,
|
|
2551
2183
|
parseServiceRouteName,
|
|
2552
2184
|
parseTransportRouteName,
|
|
2185
|
+
performLogout,
|
|
2553
2186
|
prisma,
|
|
2554
2187
|
processUpload,
|
|
2555
2188
|
readBootUuid,
|
|
2189
|
+
readSession,
|
|
2556
2190
|
recordMetricAcrossTrackers,
|
|
2557
2191
|
redis,
|
|
2558
2192
|
registerApiAbortController,
|
|
@@ -2571,20 +2205,25 @@ export {
|
|
|
2571
2205
|
registerLocalizedNormalizer,
|
|
2572
2206
|
registerLogger,
|
|
2573
2207
|
registerNotifier,
|
|
2208
|
+
registerPreCaptureFilter,
|
|
2574
2209
|
registerPrismaClient,
|
|
2575
2210
|
registerProjectConfig,
|
|
2576
2211
|
registerRateLimitStrategy,
|
|
2577
2212
|
registerRedactedLogKeys,
|
|
2578
2213
|
registerRedisClient,
|
|
2579
2214
|
registerRedisKeyFormatter,
|
|
2215
|
+
registerRoomNameFormatter,
|
|
2580
2216
|
registerRuntimeMapsProvider,
|
|
2581
2217
|
registerServicesConfig,
|
|
2218
|
+
registerSessionProvider,
|
|
2582
2219
|
registerSocketMiddleware,
|
|
2220
|
+
registerStrayPrefixCommand,
|
|
2583
2221
|
registerSyncAbortController,
|
|
2584
2222
|
registerSyncHook,
|
|
2585
2223
|
releaseLease,
|
|
2586
2224
|
removeApiQueueItem,
|
|
2587
2225
|
removeApiQueueItemsByKey,
|
|
2226
|
+
removeSession,
|
|
2588
2227
|
removeSyncQueueItem,
|
|
2589
2228
|
renewLease,
|
|
2590
2229
|
resetClientsForTests,
|
|
@@ -2592,10 +2231,20 @@ export {
|
|
|
2592
2231
|
resetLoggerForTests,
|
|
2593
2232
|
resetRedactedLogKeysForTests,
|
|
2594
2233
|
resetRedisKeyFormatterForTests,
|
|
2234
|
+
resetSessionProviderForTests,
|
|
2235
|
+
resolveClientIp,
|
|
2595
2236
|
resolveEnvKey,
|
|
2596
2237
|
resolveErrorMessage,
|
|
2597
2238
|
resolveFromRoot,
|
|
2239
|
+
resolveHealthHashConfig,
|
|
2240
|
+
resolveHealthHashConfigFromDescriptor,
|
|
2241
|
+
runWithErrorTrackerIdentity,
|
|
2242
|
+
runWithErrorTrackerIdentityScope,
|
|
2243
|
+
sanitizeErrorString,
|
|
2244
|
+
sanitizeErrorStrings,
|
|
2245
|
+
sanitizeForLog,
|
|
2598
2246
|
serveAvatar,
|
|
2247
|
+
setCurrentErrorTrackerIdentity,
|
|
2599
2248
|
setErrorTrackerUser,
|
|
2600
2249
|
setIoInstance,
|
|
2601
2250
|
setSentryUser,
|
|
@@ -2605,13 +2254,16 @@ export {
|
|
|
2605
2254
|
socketEventNames,
|
|
2606
2255
|
startSpan,
|
|
2607
2256
|
startSpanAcrossTrackers,
|
|
2257
|
+
startSpanHandle,
|
|
2608
2258
|
tryCatch,
|
|
2259
|
+
tryCatchSync,
|
|
2609
2260
|
unregisterApiAbortController,
|
|
2610
2261
|
unregisterSyncAbortController,
|
|
2611
2262
|
validateInputByType,
|
|
2612
2263
|
validatePagePath,
|
|
2613
2264
|
validateRequest,
|
|
2614
2265
|
waitForSocket,
|
|
2615
|
-
writeBootUuid
|
|
2266
|
+
writeBootUuid,
|
|
2267
|
+
writeSession
|
|
2616
2268
|
};
|
|
2617
2269
|
//# sourceMappingURL=index.js.map
|