@luckystack/core 0.1.8 → 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/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/client.js
CHANGED
|
@@ -1,485 +1,133 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
contentTypeOptions: "nosniff"
|
|
55
|
-
},
|
|
56
|
-
cors: {
|
|
57
|
-
allowedMethods: "GET, POST, PUT, DELETE, OPTIONS",
|
|
58
|
-
allowedHeaders: "Content-Type, Authorization, X-Session-Based-Token, X-CSRF-Token, X-Request-Id",
|
|
59
|
-
exposedHeaders: "X-Session-Token, X-Request-Id",
|
|
60
|
-
credentials: true,
|
|
61
|
-
allowedOrigins: [],
|
|
62
|
-
allowLocalhost: false
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
auth: {
|
|
66
|
-
oauthStateTtlSeconds: 60 * 10,
|
|
67
|
-
passwordMinLength: 8,
|
|
68
|
-
passwordMaxLength: 191,
|
|
69
|
-
passwordPolicy: {
|
|
70
|
-
minLength: 8,
|
|
71
|
-
maxLength: 191,
|
|
72
|
-
requireUppercase: false,
|
|
73
|
-
requireLowercase: false,
|
|
74
|
-
requireNumber: false,
|
|
75
|
-
requireSpecial: false,
|
|
76
|
-
forbidCommon: true
|
|
77
|
-
},
|
|
78
|
-
emailMaxLength: 191,
|
|
79
|
-
nameMaxLength: 191,
|
|
80
|
-
bcryptRounds: 10,
|
|
81
|
-
providerAccountStrategy: "per-provider",
|
|
82
|
-
forgotPassword: "disabled",
|
|
83
|
-
passwordResetTtlSeconds: 60 * 60,
|
|
84
|
-
emailChangeTtlSeconds: 60 * 60
|
|
85
|
-
},
|
|
86
|
-
socket: {
|
|
87
|
-
maxHttpBufferSize: 5 * 1024 * 1024,
|
|
88
|
-
pingTimeout: 2e4,
|
|
89
|
-
pingInterval: 25e3
|
|
90
|
-
},
|
|
91
|
-
sync: {
|
|
92
|
-
streamThrottle: {
|
|
93
|
-
flushAtChars: 32,
|
|
94
|
-
flushEveryMs: 50,
|
|
95
|
-
field: "chunk"
|
|
96
|
-
},
|
|
97
|
-
fanoutYieldEvery: 100,
|
|
98
|
-
fanoutYieldMs: 1
|
|
99
|
-
},
|
|
100
|
-
offlineQueue: {
|
|
101
|
-
maxSize: 200,
|
|
102
|
-
maxAgeMs: 60 * 60 * 1e3,
|
|
103
|
-
dropPolicy: "drop-oldest"
|
|
104
|
-
},
|
|
105
|
-
dev: {
|
|
106
|
-
hotReloadDebounceMs: 120,
|
|
107
|
-
watcherStabilityThresholdMs: 120,
|
|
108
|
-
watcherPollIntervalMs: 20
|
|
109
|
-
},
|
|
110
|
-
paths: {
|
|
111
|
-
srcDir: "src",
|
|
112
|
-
serverDir: "server",
|
|
113
|
-
sharedDir: "shared",
|
|
114
|
-
uploadsDir: "uploads",
|
|
115
|
-
publicDir: "public",
|
|
116
|
-
serverFunctionsDir: "server/functions",
|
|
117
|
-
serverFunctionDirs: ["functions", "shared"],
|
|
118
|
-
generatedSocketTypes: "src/_sockets/apiTypes.generated.ts",
|
|
119
|
-
generatedApiSchemas: "src/_sockets/apiInputSchemas.generated.ts",
|
|
120
|
-
generatedApiDocs: "src/docs/apiDocs.generated.json"
|
|
121
|
-
},
|
|
122
|
-
defaultLanguage: "en",
|
|
123
|
-
defaultTheme: "light",
|
|
124
|
-
socketActivityBroadcaster: false,
|
|
125
|
-
socketStatusIndicator: false,
|
|
126
|
-
locationProviderEnabled: false,
|
|
127
|
-
loginRedirectUrl: "/"
|
|
128
|
-
};
|
|
129
|
-
var isPlainObject = (value) => {
|
|
130
|
-
if (value === null || typeof value !== "object") return false;
|
|
131
|
-
const proto = Object.getPrototypeOf(value);
|
|
132
|
-
return proto === Object.prototype || proto === null;
|
|
133
|
-
};
|
|
134
|
-
var deepMerge = (base, override) => {
|
|
135
|
-
if (override === void 0) return base;
|
|
136
|
-
if (!isPlainObject(base) || !isPlainObject(override)) {
|
|
137
|
-
return override ?? base;
|
|
138
|
-
}
|
|
139
|
-
const result = { ...base };
|
|
140
|
-
for (const [key, overrideValue] of Object.entries(override)) {
|
|
141
|
-
if (overrideValue === void 0) continue;
|
|
142
|
-
const baseValue = base[key];
|
|
143
|
-
result[key] = isPlainObject(baseValue) && isPlainObject(overrideValue) ? deepMerge(baseValue, overrideValue) : overrideValue;
|
|
144
|
-
}
|
|
145
|
-
return result;
|
|
146
|
-
};
|
|
147
|
-
var activeConfig = DEFAULT_PROJECT_CONFIG;
|
|
148
|
-
var isRegistered = false;
|
|
149
|
-
var registerProjectConfig = (config) => {
|
|
150
|
-
activeConfig = deepMerge(DEFAULT_PROJECT_CONFIG, config);
|
|
151
|
-
isRegistered = true;
|
|
152
|
-
};
|
|
153
|
-
var getProjectConfig = () => activeConfig;
|
|
154
|
-
var isProjectConfigRegistered = () => isRegistered;
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_PAGE_ROUTE_RULES,
|
|
3
|
+
buildApiResponseEventName,
|
|
4
|
+
buildApiStreamEventName,
|
|
5
|
+
buildGetJoinedRoomsResponseEventName,
|
|
6
|
+
buildJoinRoomResponseEventName,
|
|
7
|
+
buildLeaveRoomResponseEventName,
|
|
8
|
+
buildSyncProgressEventName,
|
|
9
|
+
buildSyncResponseEventName,
|
|
10
|
+
clearCsrfToken,
|
|
11
|
+
dispatchClientHook,
|
|
12
|
+
dispatchVetoableClientHook,
|
|
13
|
+
enqueueApiRequest,
|
|
14
|
+
enqueueSyncRequest,
|
|
15
|
+
flushApiQueue,
|
|
16
|
+
flushSyncQueue,
|
|
17
|
+
getApiQueueSize,
|
|
18
|
+
getCsrfToken,
|
|
19
|
+
getNotifier,
|
|
20
|
+
getRegisteredApiMethod,
|
|
21
|
+
getSyncQueueSize,
|
|
22
|
+
httpFetch,
|
|
23
|
+
incrementResponseIndex,
|
|
24
|
+
isApiMethodMapRegistered,
|
|
25
|
+
isGetMethodName,
|
|
26
|
+
isOnline,
|
|
27
|
+
normalizeErrorResponseCore,
|
|
28
|
+
notify,
|
|
29
|
+
parseServiceRouteName,
|
|
30
|
+
registerApiMethodMap,
|
|
31
|
+
registerClientHook,
|
|
32
|
+
registerNotifier,
|
|
33
|
+
removeApiQueueItem,
|
|
34
|
+
removeApiQueueItemsByKey,
|
|
35
|
+
removeSyncQueueItem,
|
|
36
|
+
setSocket,
|
|
37
|
+
sleep,
|
|
38
|
+
socket,
|
|
39
|
+
socketEventNames,
|
|
40
|
+
tryCatchSync,
|
|
41
|
+
validatePagePath,
|
|
42
|
+
waitForSocket
|
|
43
|
+
} from "./chunk-OZTB2RL2.js";
|
|
44
|
+
import {
|
|
45
|
+
getProjectConfig,
|
|
46
|
+
isProjectConfigRegistered,
|
|
47
|
+
registerProjectConfig
|
|
48
|
+
} from "./chunk-W62GGT2P.js";
|
|
49
|
+
import {
|
|
50
|
+
getLogger,
|
|
51
|
+
registerLogger
|
|
52
|
+
} from "./chunk-V2WKGTZH.js";
|
|
53
|
+
import "./chunk-USQA6QTX.js";
|
|
155
54
|
|
|
156
|
-
// src/
|
|
157
|
-
var
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
error: (message, error, context) => {
|
|
180
|
-
if (error !== void 0 && context !== void 0) console.error(message, error, context);
|
|
181
|
-
else if (error !== void 0) console.error(message, error);
|
|
182
|
-
else if (context === void 0) {
|
|
183
|
-
console.error(message);
|
|
184
|
-
} else {
|
|
185
|
-
console.error(message, context);
|
|
55
|
+
// src/apiInterceptors.ts
|
|
56
|
+
var requestInterceptors = /* @__PURE__ */ new Set();
|
|
57
|
+
var responseInterceptors = /* @__PURE__ */ new Set();
|
|
58
|
+
function registerApiRequestInterceptor(interceptor) {
|
|
59
|
+
requestInterceptors.add(interceptor);
|
|
60
|
+
return () => {
|
|
61
|
+
requestInterceptors.delete(interceptor);
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function registerApiResponseInterceptor(interceptor) {
|
|
65
|
+
responseInterceptors.add(interceptor);
|
|
66
|
+
return () => {
|
|
67
|
+
responseInterceptors.delete(interceptor);
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
async function dispatchApiRequestInterceptors(ctx) {
|
|
71
|
+
if (requestInterceptors.size === 0) return;
|
|
72
|
+
const interceptors = [...requestInterceptors];
|
|
73
|
+
for (const interceptor of interceptors) {
|
|
74
|
+
try {
|
|
75
|
+
await interceptor(ctx);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
getLogger().error("[apiRequestInterceptor] handler threw", error);
|
|
186
78
|
}
|
|
187
79
|
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
var getRegisteredApiMethod = (pagePath, apiName, version) => {
|
|
203
|
-
if (!activeMap) return void 0;
|
|
204
|
-
return activeMap[pagePath]?.[apiName]?.[version];
|
|
205
|
-
};
|
|
206
|
-
var isApiMethodMapRegistered = () => activeMap !== null;
|
|
207
|
-
|
|
208
|
-
// src/socketState.ts
|
|
209
|
-
var socket = null;
|
|
210
|
-
var setSocket = (next) => {
|
|
211
|
-
socket = next;
|
|
212
|
-
};
|
|
213
|
-
var responseIndex = 0;
|
|
214
|
-
var WAIT_FOR_SOCKET_INTERVAL_MS = 10;
|
|
215
|
-
var WAIT_FOR_SOCKET_MAX_ITERATIONS = 500;
|
|
216
|
-
var incrementResponseIndex = () => {
|
|
217
|
-
responseIndex = responseIndex + 1;
|
|
218
|
-
return responseIndex;
|
|
219
|
-
};
|
|
220
|
-
var waitForSocket = async () => {
|
|
221
|
-
let i = 0;
|
|
222
|
-
while (!socket) {
|
|
223
|
-
await new Promise((resolve2) => setTimeout(resolve2, WAIT_FOR_SOCKET_INTERVAL_MS));
|
|
224
|
-
i++;
|
|
225
|
-
if (i > WAIT_FOR_SOCKET_MAX_ITERATIONS) {
|
|
226
|
-
return null;
|
|
80
|
+
}
|
|
81
|
+
function dispatchApiResponseInterceptors(ctx) {
|
|
82
|
+
if (responseInterceptors.size === 0) return;
|
|
83
|
+
const interceptors = [...responseInterceptors];
|
|
84
|
+
for (const interceptor of interceptors) {
|
|
85
|
+
try {
|
|
86
|
+
const result = interceptor(ctx);
|
|
87
|
+
if (result && typeof result.catch === "function") {
|
|
88
|
+
result.catch((error) => {
|
|
89
|
+
getLogger().error("[apiResponseInterceptor] async handler rejected", error);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
} catch (error) {
|
|
93
|
+
getLogger().error("[apiResponseInterceptor] handler threw", error);
|
|
227
94
|
}
|
|
228
95
|
}
|
|
229
|
-
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
// src/notifier.ts
|
|
233
|
-
var noopNotifier = {
|
|
234
|
-
success: () => {
|
|
235
|
-
},
|
|
236
|
-
error: () => {
|
|
237
|
-
},
|
|
238
|
-
info: () => {
|
|
239
|
-
},
|
|
240
|
-
warning: () => {
|
|
241
|
-
}
|
|
242
|
-
};
|
|
243
|
-
var activeNotifier = noopNotifier;
|
|
244
|
-
var registerNotifier = (notifier) => {
|
|
245
|
-
activeNotifier = notifier;
|
|
246
|
-
};
|
|
247
|
-
var getNotifier = () => activeNotifier;
|
|
248
|
-
var notify = {
|
|
249
|
-
success: (input) => {
|
|
250
|
-
activeNotifier.success(input);
|
|
251
|
-
},
|
|
252
|
-
error: (input) => {
|
|
253
|
-
activeNotifier.error(input);
|
|
254
|
-
},
|
|
255
|
-
info: (input) => {
|
|
256
|
-
activeNotifier.info(input);
|
|
257
|
-
},
|
|
258
|
-
warning: (input) => {
|
|
259
|
-
activeNotifier.warning(input);
|
|
260
|
-
}
|
|
261
|
-
};
|
|
96
|
+
}
|
|
262
97
|
|
|
263
|
-
// src/
|
|
264
|
-
var
|
|
265
|
-
var
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
for (let i = queue.length - 1; i >= 0; i -= 1) {
|
|
277
|
-
const item = queue[i];
|
|
278
|
-
if (item && item.createdAt < cutoff) {
|
|
279
|
-
queue.splice(i, 1);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
};
|
|
283
|
-
var enqueueWithPolicy = (queue, item, label) => {
|
|
284
|
-
evictExpired(queue);
|
|
285
|
-
const { maxSize, dropPolicy: globalPolicy } = getProjectConfig().offlineQueue;
|
|
286
|
-
const effectivePolicy = item.dropPolicy ?? globalPolicy;
|
|
287
|
-
if (queue.length < maxSize) {
|
|
288
|
-
queue.push(item);
|
|
289
|
-
return true;
|
|
290
|
-
}
|
|
291
|
-
if (effectivePolicy === "drop-oldest") {
|
|
292
|
-
queue.shift();
|
|
293
|
-
queue.push(item);
|
|
294
|
-
getLogger().debug(`offlineQueue:${label} full \u2014 dropped oldest item (policy=${effectivePolicy})`);
|
|
295
|
-
return true;
|
|
296
|
-
}
|
|
297
|
-
if (effectivePolicy === "drop-newest") {
|
|
298
|
-
getLogger().debug(`offlineQueue:${label} full \u2014 dropped newest item (policy=${effectivePolicy})`);
|
|
299
|
-
return false;
|
|
300
|
-
}
|
|
301
|
-
getLogger().warn(`offlineQueue:${label} full \u2014 rejecting enqueue (policy=${effectivePolicy})`);
|
|
302
|
-
return false;
|
|
303
|
-
};
|
|
304
|
-
var enqueueApiRequest = (item) => enqueueWithPolicy(apiQueue, item, "api");
|
|
305
|
-
var enqueueSyncRequest = (item) => enqueueWithPolicy(syncQueue, item, "sync");
|
|
306
|
-
var removeApiQueueItem = (id) => {
|
|
307
|
-
const index = apiQueue.findIndex((item) => item.id === id);
|
|
308
|
-
if (index !== -1) {
|
|
309
|
-
apiQueue.splice(index, 1);
|
|
310
|
-
}
|
|
311
|
-
};
|
|
312
|
-
var removeSyncQueueItem = (id) => {
|
|
313
|
-
const index = syncQueue.findIndex((item) => item.id === id);
|
|
314
|
-
if (index !== -1) {
|
|
315
|
-
syncQueue.splice(index, 1);
|
|
316
|
-
}
|
|
317
|
-
};
|
|
318
|
-
var removeApiQueueItemsByKey = (key) => {
|
|
319
|
-
for (let i = apiQueue.length - 1; i >= 0; i -= 1) {
|
|
320
|
-
if (apiQueue[i]?.key === key) {
|
|
321
|
-
apiQueue.splice(i, 1);
|
|
98
|
+
// src/apiRequest.ts
|
|
99
|
+
var abortControllers = /* @__PURE__ */ new Map();
|
|
100
|
+
var stableStringify = (value) => {
|
|
101
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
102
|
+
const walk = (input) => {
|
|
103
|
+
if (input === null || typeof input !== "object") return input;
|
|
104
|
+
if (seen.has(input)) return "[circular]";
|
|
105
|
+
seen.add(input);
|
|
106
|
+
if (Array.isArray(input)) return input.map((entry) => walk(entry));
|
|
107
|
+
const record = input;
|
|
108
|
+
const out = {};
|
|
109
|
+
for (const key of Object.keys(record).toSorted()) {
|
|
110
|
+
out[key] = walk(record[key]);
|
|
322
111
|
}
|
|
323
|
-
|
|
324
|
-
};
|
|
325
|
-
var flushApiQueue = (canRun, socketInstance) => {
|
|
326
|
-
if (isFlushingApi) return;
|
|
327
|
-
evictExpired(apiQueue);
|
|
328
|
-
if (apiQueue.length === 0) return;
|
|
329
|
-
if (!canRun()) return;
|
|
330
|
-
isFlushingApi = true;
|
|
331
|
-
while (apiQueue.length > 0) {
|
|
332
|
-
if (!canRun()) break;
|
|
333
|
-
const item = apiQueue.shift();
|
|
334
|
-
item?.run(socketInstance);
|
|
335
|
-
}
|
|
336
|
-
isFlushingApi = false;
|
|
337
|
-
};
|
|
338
|
-
var flushSyncQueue = (canRun, socketInstance) => {
|
|
339
|
-
if (isFlushingSync) return;
|
|
340
|
-
evictExpired(syncQueue);
|
|
341
|
-
if (syncQueue.length === 0) return;
|
|
342
|
-
if (!canRun()) return;
|
|
343
|
-
isFlushingSync = true;
|
|
344
|
-
while (syncQueue.length > 0) {
|
|
345
|
-
if (!canRun()) break;
|
|
346
|
-
const item = syncQueue.shift();
|
|
347
|
-
item?.run(socketInstance);
|
|
348
|
-
}
|
|
349
|
-
isFlushingSync = false;
|
|
350
|
-
};
|
|
351
|
-
var getApiQueueSize = () => apiQueue.length;
|
|
352
|
-
var getSyncQueueSize = () => syncQueue.length;
|
|
353
|
-
|
|
354
|
-
// src/responseNormalizer.ts
|
|
355
|
-
var INVALID_ERROR_RESPONSE_CODE = "error.invalidResponse";
|
|
356
|
-
var isErrorParamArray = (value) => {
|
|
357
|
-
return Array.isArray(value) && value.every((item) => {
|
|
358
|
-
return !!item && typeof item === "object" && typeof item.key === "string" && ["string", "number", "boolean"].includes(typeof item.value);
|
|
359
|
-
});
|
|
360
|
-
};
|
|
361
|
-
var normalizeErrorResponseCore = ({
|
|
362
|
-
response,
|
|
363
|
-
fallbackHttpStatus,
|
|
364
|
-
fallbackErrorCode,
|
|
365
|
-
resolveMessage
|
|
366
|
-
}) => {
|
|
367
|
-
const normalizedParams = isErrorParamArray(response.errorParams) ? response.errorParams : void 0;
|
|
368
|
-
const errorCodeValue = typeof response.errorCode === "string" ? response.errorCode.trim() : "";
|
|
369
|
-
const hasErrorCode = errorCodeValue.length > 0;
|
|
370
|
-
const finalErrorCode = hasErrorCode ? errorCodeValue : fallbackErrorCode ?? INVALID_ERROR_RESPONSE_CODE;
|
|
371
|
-
const providedHttpStatus = typeof response.httpStatus === "number" ? response.httpStatus : void 0;
|
|
372
|
-
const message = resolveMessage ? resolveMessage({ errorCode: finalErrorCode, errorParams: normalizedParams }) : finalErrorCode;
|
|
373
|
-
return {
|
|
374
|
-
status: "error",
|
|
375
|
-
message,
|
|
376
|
-
errorCode: finalErrorCode,
|
|
377
|
-
errorParams: normalizedParams,
|
|
378
|
-
httpStatus: providedHttpStatus ?? fallbackHttpStatus
|
|
112
|
+
return out;
|
|
379
113
|
};
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
var normalizeRawRoute = (value) => {
|
|
384
|
-
return value.trim().replaceAll("\\", "/").replaceAll(/^\/+/g, "").replaceAll(/\/+$/g, "");
|
|
385
|
-
};
|
|
386
|
-
var hasEmptySegments = (value) => {
|
|
387
|
-
return value.split("/").some((segment) => segment.length === 0);
|
|
388
|
-
};
|
|
389
|
-
var parseServiceRouteName = (value) => {
|
|
390
|
-
const normalized = normalizeRawRoute(value);
|
|
391
|
-
if (!normalized) {
|
|
392
|
-
return { status: "error", reason: "Route name cannot be empty." };
|
|
114
|
+
const [error, json] = tryCatchSync(() => JSON.stringify(walk(value)));
|
|
115
|
+
if (error || typeof json !== "string") {
|
|
116
|
+
return `__unserializable__:${String(Date.now())}-${String(Math.random())}`;
|
|
393
117
|
}
|
|
394
|
-
|
|
395
|
-
return { status: "error", reason: "Route name cannot contain empty segments." };
|
|
396
|
-
}
|
|
397
|
-
const segments = normalized.split("/");
|
|
398
|
-
if (segments.length < 2) {
|
|
399
|
-
return { status: "error", reason: "Route name must include service and route segments." };
|
|
400
|
-
}
|
|
401
|
-
const [service, ...routeSegments] = segments;
|
|
402
|
-
const routeName = routeSegments.join("/");
|
|
403
|
-
if (!service || !routeName) {
|
|
404
|
-
return { status: "error", reason: "Route name must include service and route segments." };
|
|
405
|
-
}
|
|
406
|
-
return {
|
|
407
|
-
status: "success",
|
|
408
|
-
normalizedRouteName: `${service}/${routeName}`,
|
|
409
|
-
service,
|
|
410
|
-
routeName
|
|
411
|
-
};
|
|
412
|
-
};
|
|
413
|
-
|
|
414
|
-
// src/socketEvents.ts
|
|
415
|
-
var withIndex = (prefix, responseIndex2) => {
|
|
416
|
-
return `${prefix}${String(responseIndex2)}`;
|
|
417
|
-
};
|
|
418
|
-
var socketEventNames = {
|
|
419
|
-
apiRequest: "apiRequest",
|
|
420
|
-
apiCancel: "apiCancel",
|
|
421
|
-
sync: "sync",
|
|
422
|
-
syncCancel: "syncCancel",
|
|
423
|
-
joinRoom: "joinRoom",
|
|
424
|
-
leaveRoom: "leaveRoom",
|
|
425
|
-
getJoinedRooms: "getJoinedRooms",
|
|
426
|
-
updateLocation: "updateLocation",
|
|
427
|
-
updateSession: "updateSession",
|
|
428
|
-
sessionReplaced: "sessionReplaced",
|
|
429
|
-
logout: "logout",
|
|
430
|
-
intentionalDisconnect: "intentionalDisconnect",
|
|
431
|
-
intentionalReconnect: "intentionalReconnect",
|
|
432
|
-
userAfk: "userAfk",
|
|
433
|
-
userBack: "userBack",
|
|
434
|
-
connect: "connect",
|
|
435
|
-
disconnect: "disconnect",
|
|
436
|
-
reconnectAttempt: "reconnect_attempt",
|
|
437
|
-
connectError: "connect_error",
|
|
438
|
-
apiResponsePrefix: "apiResponse-",
|
|
439
|
-
apiStreamPrefix: "apiStream-",
|
|
440
|
-
syncResponsePrefix: "sync-",
|
|
441
|
-
syncProgressPrefix: "sync-progress-",
|
|
442
|
-
joinRoomResponsePrefix: "joinRoom-",
|
|
443
|
-
leaveRoomResponsePrefix: "leaveRoom-",
|
|
444
|
-
getJoinedRoomsResponsePrefix: "getJoinedRooms-"
|
|
118
|
+
return json;
|
|
445
119
|
};
|
|
446
|
-
var
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
var buildApiStreamEventName = (responseIndex2) => {
|
|
450
|
-
return withIndex(socketEventNames.apiStreamPrefix, responseIndex2);
|
|
451
|
-
};
|
|
452
|
-
var buildSyncResponseEventName = (responseIndex2) => {
|
|
453
|
-
return withIndex(socketEventNames.syncResponsePrefix, responseIndex2);
|
|
454
|
-
};
|
|
455
|
-
var buildSyncProgressEventName = (responseIndex2) => {
|
|
456
|
-
return withIndex(socketEventNames.syncProgressPrefix, responseIndex2);
|
|
457
|
-
};
|
|
458
|
-
var buildJoinRoomResponseEventName = (responseIndex2) => {
|
|
459
|
-
return withIndex(socketEventNames.joinRoomResponsePrefix, responseIndex2);
|
|
460
|
-
};
|
|
461
|
-
var buildLeaveRoomResponseEventName = (responseIndex2) => {
|
|
462
|
-
return withIndex(socketEventNames.leaveRoomResponsePrefix, responseIndex2);
|
|
463
|
-
};
|
|
464
|
-
var buildGetJoinedRoomsResponseEventName = (responseIndex2) => {
|
|
465
|
-
return withIndex(socketEventNames.getJoinedRoomsResponsePrefix, responseIndex2);
|
|
466
|
-
};
|
|
467
|
-
|
|
468
|
-
// src/apiRequest.ts
|
|
469
|
-
var abortControllers = /* @__PURE__ */ new Map();
|
|
470
|
-
var isGetMethodByPrefix = (apiName) => {
|
|
471
|
-
const lower = apiName.toLowerCase();
|
|
472
|
-
return lower.startsWith("get") || lower.startsWith("fetch") || lower.startsWith("list");
|
|
473
|
-
};
|
|
474
|
-
var isGetMethod = (apiName, version) => {
|
|
120
|
+
var buildAbortKey = (fullName, data) => `${fullName}::${stableStringify(data)}`;
|
|
121
|
+
var isGetMethodByPrefix = (apiName) => isGetMethodName(apiName.toLowerCase());
|
|
122
|
+
var resolveGetMethod = (apiName, version) => {
|
|
475
123
|
const lastSlash = apiName.lastIndexOf("/");
|
|
476
124
|
if (lastSlash > 0) {
|
|
477
125
|
const pagePath = apiName.slice(0, lastSlash);
|
|
478
126
|
const leaf = apiName.slice(lastSlash + 1);
|
|
479
127
|
const method = getRegisteredApiMethod(pagePath, leaf, version);
|
|
480
|
-
if (method) return method === "GET";
|
|
128
|
+
if (method) return { isGet: method === "GET", usingHeuristic: false };
|
|
481
129
|
}
|
|
482
|
-
return isGetMethodByPrefix(apiName);
|
|
130
|
+
return { isGet: isGetMethodByPrefix(apiName), usingHeuristic: true };
|
|
483
131
|
};
|
|
484
132
|
var canSendNow = (socketInstance) => {
|
|
485
133
|
if (!socketInstance.connected) return false;
|
|
@@ -494,10 +142,12 @@ var shouldNotifyDev = () => getLogging().devNotifications;
|
|
|
494
142
|
var shouldLogStream = () => getLogging().stream;
|
|
495
143
|
var shouldUseAbortController = ({
|
|
496
144
|
abortable,
|
|
497
|
-
isGet
|
|
145
|
+
isGet,
|
|
146
|
+
usingHeuristic
|
|
498
147
|
}) => {
|
|
499
148
|
if (abortable === true) return true;
|
|
500
149
|
if (abortable === false) return false;
|
|
150
|
+
if (usingHeuristic) return false;
|
|
501
151
|
return isGet;
|
|
502
152
|
};
|
|
503
153
|
var normalizeApiError = ({
|
|
@@ -526,9 +176,9 @@ var normalizeApiError = ({
|
|
|
526
176
|
};
|
|
527
177
|
function apiRequest(params) {
|
|
528
178
|
const runtimeParams = params;
|
|
529
|
-
const { name, version, disableErrorMessage = false, onStream, signal: externalSignal } = runtimeParams;
|
|
179
|
+
const { name, version, disableErrorMessage = false, onStream, signal: externalSignal, timeoutMs } = runtimeParams;
|
|
530
180
|
const payloadData = runtimeParams.data;
|
|
531
|
-
return new Promise((resolve2
|
|
181
|
+
return new Promise((resolve2) => {
|
|
532
182
|
void (async () => {
|
|
533
183
|
if (externalSignal?.aborted) {
|
|
534
184
|
resolve2(normalizeApiError({
|
|
@@ -597,58 +247,102 @@ function apiRequest(params) {
|
|
|
597
247
|
}));
|
|
598
248
|
return;
|
|
599
249
|
}
|
|
600
|
-
const isGet =
|
|
250
|
+
const { isGet, usingHeuristic } = resolveGetMethod(sanitizedName, version);
|
|
601
251
|
const useAbortController = shouldUseAbortController({
|
|
602
252
|
abortable: runtimeParams.abortable,
|
|
603
|
-
isGet
|
|
253
|
+
isGet,
|
|
254
|
+
usingHeuristic
|
|
604
255
|
});
|
|
605
256
|
const fullName = `api/${sanitizedName}/${version}`;
|
|
257
|
+
const abortKey = buildAbortKey(fullName, data);
|
|
606
258
|
let signal = null;
|
|
607
259
|
let abortHandler = null;
|
|
608
260
|
let queueId = null;
|
|
609
261
|
let cleanupStreamListener = null;
|
|
262
|
+
let cleanupResponseListener = null;
|
|
263
|
+
let responseTimeout = null;
|
|
264
|
+
const clearResponseTimeout = () => {
|
|
265
|
+
if (responseTimeout) {
|
|
266
|
+
clearTimeout(responseTimeout);
|
|
267
|
+
responseTimeout = null;
|
|
268
|
+
}
|
|
269
|
+
};
|
|
610
270
|
const cleanupAbortController = () => {
|
|
611
271
|
if (signal && abortHandler) {
|
|
612
272
|
signal.removeEventListener("abort", abortHandler);
|
|
613
273
|
}
|
|
614
|
-
abortControllers.delete(
|
|
274
|
+
abortControllers.delete(abortKey);
|
|
615
275
|
};
|
|
616
276
|
if (useAbortController) {
|
|
617
|
-
if (abortControllers.has(
|
|
618
|
-
const prevAbortController = abortControllers.get(
|
|
277
|
+
if (abortControllers.has(abortKey)) {
|
|
278
|
+
const prevAbortController = abortControllers.get(abortKey);
|
|
619
279
|
prevAbortController?.abort();
|
|
620
280
|
}
|
|
621
281
|
const abortController = new AbortController();
|
|
622
|
-
abortControllers.set(
|
|
282
|
+
abortControllers.set(abortKey, abortController);
|
|
623
283
|
signal = abortController.signal;
|
|
624
284
|
abortHandler = () => {
|
|
285
|
+
clearResponseTimeout();
|
|
625
286
|
cleanupStreamListener?.();
|
|
626
287
|
cleanupStreamListener = null;
|
|
288
|
+
cleanupResponseListener?.();
|
|
289
|
+
cleanupResponseListener = null;
|
|
627
290
|
cleanupAbortController();
|
|
628
291
|
if (queueId) {
|
|
629
292
|
removeApiQueueItem(queueId);
|
|
630
293
|
}
|
|
631
|
-
|
|
294
|
+
resolve2(normalizeApiError({
|
|
295
|
+
response: { status: "error", errorCode: "request.aborted" },
|
|
296
|
+
fallbackErrorCode: "request.aborted"
|
|
297
|
+
}));
|
|
632
298
|
};
|
|
633
299
|
signal.addEventListener("abort", abortHandler);
|
|
634
300
|
}
|
|
635
301
|
const runRequest = (socketInstance) => {
|
|
636
302
|
if (!canSendNow(socketInstance)) {
|
|
637
303
|
queueId ??= createQueueId();
|
|
304
|
+
let suppressOnDrop = true;
|
|
638
305
|
const enqueued = enqueueApiRequest({
|
|
639
306
|
id: queueId,
|
|
640
307
|
key: fullName,
|
|
641
308
|
run: (nextSocket) => {
|
|
642
309
|
runRequest(nextSocket);
|
|
643
310
|
},
|
|
644
|
-
createdAt: Date.now()
|
|
311
|
+
createdAt: Date.now(),
|
|
312
|
+
//? CORE: mirror SYNC-09. Settle the awaiting caller when this
|
|
313
|
+
//? QUEUED request is later EVICTED (drop-oldest by a future
|
|
314
|
+
//? enqueue / age expiry) instead of ever running. Without it the
|
|
315
|
+
//? promise hangs forever, since `run` is the only thing that could
|
|
316
|
+
//? otherwise resolve it — `apiRequest` had drifted from the sibling
|
|
317
|
+
//? `syncRequest` which already had this fix. Also tear down the
|
|
318
|
+
//? abort-controller registry entry so it isn't leaked.
|
|
319
|
+
onDrop: (reason) => {
|
|
320
|
+
if (suppressOnDrop) return;
|
|
321
|
+
cleanupAbortController();
|
|
322
|
+
resolve2(normalizeApiError({
|
|
323
|
+
response: { status: "error", errorCode: "offline.dropped", errorParams: [{ key: "reason", value: reason }] },
|
|
324
|
+
fallbackErrorCode: "offline.dropped"
|
|
325
|
+
}));
|
|
326
|
+
}
|
|
645
327
|
});
|
|
328
|
+
suppressOnDrop = false;
|
|
646
329
|
if (!enqueued) {
|
|
647
330
|
resolve2(normalizeApiError({
|
|
648
331
|
response: { status: "error", errorCode: "offline.queueFull", httpStatus: 503 },
|
|
649
332
|
fallbackErrorCode: "offline.queueFull",
|
|
650
333
|
fallbackHttpStatus: 503
|
|
651
334
|
}));
|
|
335
|
+
} else if (externalSignal && queueId) {
|
|
336
|
+
const capturedQueueId = queueId;
|
|
337
|
+
const queueAbortHandler = () => {
|
|
338
|
+
removeApiQueueItem(capturedQueueId);
|
|
339
|
+
cleanupAbortController();
|
|
340
|
+
resolve2(normalizeApiError({
|
|
341
|
+
response: { status: "error", errorCode: "request.aborted" },
|
|
342
|
+
fallbackErrorCode: "request.aborted"
|
|
343
|
+
}));
|
|
344
|
+
};
|
|
345
|
+
externalSignal.addEventListener("abort", queueAbortHandler, { once: true });
|
|
652
346
|
}
|
|
653
347
|
return;
|
|
654
348
|
}
|
|
@@ -660,9 +354,12 @@ function apiRequest(params) {
|
|
|
660
354
|
let cleanupExternalAbort = null;
|
|
661
355
|
if (externalSignal) {
|
|
662
356
|
const externalAbortHandler = () => {
|
|
357
|
+
clearResponseTimeout();
|
|
663
358
|
socketInstance.emit(socketEventNames.apiCancel, { responseIndex: tempIndex });
|
|
664
359
|
cleanupStreamListener?.();
|
|
665
360
|
cleanupStreamListener = null;
|
|
361
|
+
cleanupResponseListener?.();
|
|
362
|
+
cleanupResponseListener = null;
|
|
666
363
|
cleanupAbortController();
|
|
667
364
|
cleanupExternalAbort?.();
|
|
668
365
|
cleanupExternalAbort = null;
|
|
@@ -676,6 +373,28 @@ function apiRequest(params) {
|
|
|
676
373
|
externalSignal.removeEventListener("abort", externalAbortHandler);
|
|
677
374
|
};
|
|
678
375
|
}
|
|
376
|
+
clearResponseTimeout();
|
|
377
|
+
const effectiveTimeoutMs = timeoutMs ?? getProjectConfig().api.requestTimeoutMs;
|
|
378
|
+
if (typeof effectiveTimeoutMs === "number" && effectiveTimeoutMs > 0) {
|
|
379
|
+
responseTimeout = setTimeout(() => {
|
|
380
|
+
responseTimeout = null;
|
|
381
|
+
cleanupStreamListener?.();
|
|
382
|
+
cleanupStreamListener = null;
|
|
383
|
+
cleanupResponseListener?.();
|
|
384
|
+
cleanupResponseListener = null;
|
|
385
|
+
cleanupExternalAbort?.();
|
|
386
|
+
cleanupExternalAbort = null;
|
|
387
|
+
cleanupAbortController();
|
|
388
|
+
if (queueId) {
|
|
389
|
+
removeApiQueueItem(queueId);
|
|
390
|
+
}
|
|
391
|
+
resolve2(normalizeApiError({
|
|
392
|
+
response: { status: "error", errorCode: "api.timeout", httpStatus: 504 },
|
|
393
|
+
fallbackErrorCode: "api.timeout",
|
|
394
|
+
fallbackHttpStatus: 504
|
|
395
|
+
}));
|
|
396
|
+
}, effectiveTimeoutMs);
|
|
397
|
+
}
|
|
679
398
|
if (typeof onStream === "function") {
|
|
680
399
|
const streamEventName = buildApiStreamEventName(tempIndex);
|
|
681
400
|
const streamListener = (streamPayload) => {
|
|
@@ -695,17 +414,23 @@ function apiRequest(params) {
|
|
|
695
414
|
if (shouldLogDev()) {
|
|
696
415
|
getLogger().debug(`Client API Request(${String(tempIndex)})`, { APINAME: sanitizedName, data });
|
|
697
416
|
}
|
|
698
|
-
|
|
417
|
+
const responseEventName = buildApiResponseEventName(tempIndex);
|
|
418
|
+
const responseListener = (response) => {
|
|
699
419
|
if (signal?.aborted) {
|
|
420
|
+
clearResponseTimeout();
|
|
700
421
|
cleanupExternalAbort?.();
|
|
701
422
|
cleanupExternalAbort = null;
|
|
702
423
|
return;
|
|
703
424
|
}
|
|
425
|
+
clearResponseTimeout();
|
|
704
426
|
cleanupStreamListener?.();
|
|
705
427
|
cleanupStreamListener = null;
|
|
706
428
|
cleanupExternalAbort?.();
|
|
707
429
|
cleanupExternalAbort = null;
|
|
430
|
+
cleanupResponseListener?.();
|
|
431
|
+
cleanupResponseListener = null;
|
|
708
432
|
const status = response.status;
|
|
433
|
+
dispatchApiResponseInterceptors({ name: sanitizedName, version, response });
|
|
709
434
|
if (shouldLogDev()) {
|
|
710
435
|
getLogger().debug(`Server API Response(${String(tempIndex)})`, { ...response, APINAME: sanitizedName });
|
|
711
436
|
}
|
|
@@ -725,154 +450,63 @@ function apiRequest(params) {
|
|
|
725
450
|
}
|
|
726
451
|
cleanupAbortController();
|
|
727
452
|
resolve2(response);
|
|
728
|
-
}
|
|
453
|
+
};
|
|
454
|
+
socketInstance.once(responseEventName, responseListener);
|
|
455
|
+
cleanupResponseListener = () => {
|
|
456
|
+
socketInstance.off(responseEventName, responseListener);
|
|
457
|
+
};
|
|
458
|
+
const disconnectHandler = () => {
|
|
459
|
+
if (cleanupResponseListener) {
|
|
460
|
+
cleanupResponseListener();
|
|
461
|
+
cleanupResponseListener = null;
|
|
462
|
+
}
|
|
463
|
+
cleanupStreamListener?.();
|
|
464
|
+
cleanupStreamListener = null;
|
|
465
|
+
cleanupExternalAbort?.();
|
|
466
|
+
cleanupExternalAbort = null;
|
|
467
|
+
clearResponseTimeout();
|
|
468
|
+
cleanupAbortController();
|
|
469
|
+
resolve2(normalizeApiError({
|
|
470
|
+
response: { status: "error", errorCode: "api.disconnected" },
|
|
471
|
+
fallbackErrorCode: "api.disconnected"
|
|
472
|
+
}));
|
|
473
|
+
};
|
|
474
|
+
socketInstance.once("disconnect", disconnectHandler);
|
|
475
|
+
const originalCleanupResponseListener = cleanupResponseListener;
|
|
476
|
+
cleanupResponseListener = () => {
|
|
477
|
+
socketInstance.off("disconnect", disconnectHandler);
|
|
478
|
+
originalCleanupResponseListener();
|
|
479
|
+
};
|
|
729
480
|
};
|
|
481
|
+
await dispatchApiRequestInterceptors({
|
|
482
|
+
name: sanitizedName,
|
|
483
|
+
version,
|
|
484
|
+
data
|
|
485
|
+
});
|
|
486
|
+
if (externalSignal?.aborted) {
|
|
487
|
+
resolve2(normalizeApiError({
|
|
488
|
+
response: { status: "error", errorCode: "request.aborted" },
|
|
489
|
+
fallbackErrorCode: "request.aborted"
|
|
490
|
+
}));
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
730
493
|
runRequest(socket);
|
|
731
494
|
})();
|
|
732
495
|
});
|
|
733
496
|
}
|
|
734
497
|
|
|
735
|
-
// src/
|
|
736
|
-
function sleep(ms) {
|
|
737
|
-
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
// src/errorTrackerRegistry.ts
|
|
741
|
-
var activeTrackers = [];
|
|
742
|
-
var captureExceptionAcrossTrackers = (error, context) => {
|
|
743
|
-
for (const tracker of activeTrackers) {
|
|
744
|
-
try {
|
|
745
|
-
tracker.captureException(error, context);
|
|
746
|
-
} catch {
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
};
|
|
750
|
-
|
|
751
|
-
// src/sentrySetup.ts
|
|
752
|
-
var sentry;
|
|
753
|
-
var captureException = (error, context) => {
|
|
754
|
-
if (sentry) {
|
|
755
|
-
if (context) {
|
|
756
|
-
sentry.captureException(error, { extra: context });
|
|
757
|
-
} else {
|
|
758
|
-
sentry.captureException(error);
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
captureExceptionAcrossTrackers(error, context);
|
|
762
|
-
};
|
|
763
|
-
|
|
764
|
-
// src/tryCatch.ts
|
|
498
|
+
// src/tryCatchClient.ts
|
|
765
499
|
async function tryCatch(func, params, context) {
|
|
766
500
|
try {
|
|
767
501
|
const response = await func(params);
|
|
768
502
|
return [null, response];
|
|
769
503
|
} catch (error) {
|
|
504
|
+
const { captureException } = await import("./sentrySetup-Q4BKUAQ3.js");
|
|
770
505
|
captureException(error, context);
|
|
771
506
|
return [error, null];
|
|
772
507
|
}
|
|
773
508
|
}
|
|
774
509
|
|
|
775
|
-
// src/csrfConfig.ts
|
|
776
|
-
var DEFAULT_CSRF_CONFIG = {
|
|
777
|
-
cookieName: "csrf-token",
|
|
778
|
-
headerName: "x-csrf-token",
|
|
779
|
-
tokenLength: 32,
|
|
780
|
-
cookieOptions: {
|
|
781
|
-
sameSite: "lax",
|
|
782
|
-
secure: true,
|
|
783
|
-
httpOnly: false,
|
|
784
|
-
path: "/",
|
|
785
|
-
maxAgeMs: 24 * 60 * 60 * 1e3
|
|
786
|
-
}
|
|
787
|
-
};
|
|
788
|
-
var active = DEFAULT_CSRF_CONFIG;
|
|
789
|
-
var getCsrfConfig = () => active;
|
|
790
|
-
|
|
791
|
-
// src/csrf.ts
|
|
792
|
-
var cachedToken = null;
|
|
793
|
-
var inflightFetch = null;
|
|
794
|
-
var resolveBackendUrl = () => {
|
|
795
|
-
if (globalThis.window !== void 0 && globalThis.location?.origin) {
|
|
796
|
-
return globalThis.location.origin;
|
|
797
|
-
}
|
|
798
|
-
return "";
|
|
799
|
-
};
|
|
800
|
-
var fetchCsrfToken = async () => {
|
|
801
|
-
const base = resolveBackendUrl();
|
|
802
|
-
const [error, token] = await tryCatch(async () => {
|
|
803
|
-
const response = await fetch(`${base}/auth/csrf`, {
|
|
804
|
-
method: "GET",
|
|
805
|
-
credentials: "include"
|
|
806
|
-
});
|
|
807
|
-
if (!response.ok) return null;
|
|
808
|
-
const body = await response.json();
|
|
809
|
-
return body.csrfToken ?? null;
|
|
810
|
-
});
|
|
811
|
-
if (error) return null;
|
|
812
|
-
return token ?? null;
|
|
813
|
-
};
|
|
814
|
-
var getCsrfToken = async () => {
|
|
815
|
-
if (getProjectConfig().session.basedToken) {
|
|
816
|
-
return null;
|
|
817
|
-
}
|
|
818
|
-
if (cachedToken) return cachedToken;
|
|
819
|
-
if (inflightFetch) return inflightFetch;
|
|
820
|
-
inflightFetch = fetchCsrfToken().then((token) => {
|
|
821
|
-
cachedToken = token;
|
|
822
|
-
inflightFetch = null;
|
|
823
|
-
return token;
|
|
824
|
-
});
|
|
825
|
-
return inflightFetch;
|
|
826
|
-
};
|
|
827
|
-
var clearCsrfToken = () => {
|
|
828
|
-
cachedToken = null;
|
|
829
|
-
inflightFetch = null;
|
|
830
|
-
};
|
|
831
|
-
var STATE_CHANGING = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
832
|
-
var isCsrfMismatchResponse = async (response) => {
|
|
833
|
-
if (response.status !== 403) return false;
|
|
834
|
-
const [error, isMismatch] = await tryCatch(async () => {
|
|
835
|
-
const cloned = response.clone();
|
|
836
|
-
const body = await cloned.json();
|
|
837
|
-
return body.errorCode === "auth.csrfMismatch";
|
|
838
|
-
});
|
|
839
|
-
if (error) return false;
|
|
840
|
-
return isMismatch ?? false;
|
|
841
|
-
};
|
|
842
|
-
var httpFetch = async (input, init = {}) => {
|
|
843
|
-
const method = (init.method ?? "GET").toUpperCase();
|
|
844
|
-
const headers = new Headers(init.headers);
|
|
845
|
-
if (!headers.has("Content-Type") && init.body && typeof init.body === "string") {
|
|
846
|
-
headers.set("Content-Type", "application/json");
|
|
847
|
-
}
|
|
848
|
-
const csrfHeaderName = getCsrfConfig().headerName;
|
|
849
|
-
const send = async (csrfToken) => {
|
|
850
|
-
const finalHeaders = new Headers(headers);
|
|
851
|
-
if (csrfToken && STATE_CHANGING.has(method) && !finalHeaders.has(csrfHeaderName)) {
|
|
852
|
-
finalHeaders.set(csrfHeaderName, csrfToken);
|
|
853
|
-
}
|
|
854
|
-
return fetch(input, {
|
|
855
|
-
...init,
|
|
856
|
-
method,
|
|
857
|
-
headers: finalHeaders,
|
|
858
|
-
credentials: init.credentials ?? "include"
|
|
859
|
-
});
|
|
860
|
-
};
|
|
861
|
-
if (!STATE_CHANGING.has(method) || getProjectConfig().session.basedToken) {
|
|
862
|
-
return send(null);
|
|
863
|
-
}
|
|
864
|
-
const token = await getCsrfToken();
|
|
865
|
-
let response = await send(token);
|
|
866
|
-
if (await isCsrfMismatchResponse(response)) {
|
|
867
|
-
clearCsrfToken();
|
|
868
|
-
const refreshed = await getCsrfToken();
|
|
869
|
-
if (refreshed) {
|
|
870
|
-
response = await send(refreshed);
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
return response;
|
|
874
|
-
};
|
|
875
|
-
|
|
876
510
|
// src/localesRegistry.ts
|
|
877
511
|
var activeLocales = {};
|
|
878
512
|
var activeLanguageSource = null;
|
|
@@ -912,98 +546,6 @@ var registerPageMiddleware = (path, middleware) => {
|
|
|
912
546
|
var getPageMiddleware = (path) => pageMiddlewares.get(path);
|
|
913
547
|
var hasPageMiddleware = (path) => pageMiddlewares.has(path);
|
|
914
548
|
|
|
915
|
-
// src/clientHookBus.ts
|
|
916
|
-
var handlerSets = {};
|
|
917
|
-
function registerClientHook(name, handler) {
|
|
918
|
-
const existing = handlerSets[name];
|
|
919
|
-
const set = existing ?? /* @__PURE__ */ new Set();
|
|
920
|
-
if (!existing) handlerSets[name] = set;
|
|
921
|
-
const wrapped = handler;
|
|
922
|
-
set.add(wrapped);
|
|
923
|
-
return () => {
|
|
924
|
-
set.delete(wrapped);
|
|
925
|
-
};
|
|
926
|
-
}
|
|
927
|
-
function dispatchClientHook(name, payload) {
|
|
928
|
-
const set = handlerSets[name];
|
|
929
|
-
if (!set || set.size === 0) return;
|
|
930
|
-
for (const handler of set) {
|
|
931
|
-
try {
|
|
932
|
-
const result = handler(payload);
|
|
933
|
-
if (result && typeof result.catch === "function") {
|
|
934
|
-
result.catch((error) => {
|
|
935
|
-
console.error(`[clientHook:${name}] async handler rejected:`, error);
|
|
936
|
-
});
|
|
937
|
-
}
|
|
938
|
-
} catch (error) {
|
|
939
|
-
console.error(`[clientHook:${name}] handler threw:`, error);
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
async function dispatchVetoableClientHook(name, payload) {
|
|
944
|
-
const set = handlerSets[name];
|
|
945
|
-
if (!set || set.size === 0) return { stopped: false };
|
|
946
|
-
for (const handler of set) {
|
|
947
|
-
let result;
|
|
948
|
-
try {
|
|
949
|
-
result = await handler(payload);
|
|
950
|
-
} catch (error) {
|
|
951
|
-
console.error(`[clientHook:${name}] vetoable handler threw:`, error);
|
|
952
|
-
continue;
|
|
953
|
-
}
|
|
954
|
-
if (result?.stop) {
|
|
955
|
-
return { stopped: true, signal: result };
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
return { stopped: false };
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
// src/pageRouteValidation.ts
|
|
962
|
-
var DEFAULT_PAGE_ROUTE_RULES = {
|
|
963
|
-
privateFolderPrefix: "_",
|
|
964
|
-
scaffoldIgnoredFolders: [
|
|
965
|
-
"_api",
|
|
966
|
-
"_sync",
|
|
967
|
-
"_function",
|
|
968
|
-
"_functions",
|
|
969
|
-
"_component",
|
|
970
|
-
"_components",
|
|
971
|
-
"_provider",
|
|
972
|
-
"_providers",
|
|
973
|
-
"_locale",
|
|
974
|
-
"_locales",
|
|
975
|
-
"_socket",
|
|
976
|
-
"_sockets",
|
|
977
|
-
"_shared",
|
|
978
|
-
"_server"
|
|
979
|
-
]
|
|
980
|
-
};
|
|
981
|
-
var validatePagePath = (srcRelativePath, rules = DEFAULT_PAGE_ROUTE_RULES) => {
|
|
982
|
-
const normalized = srcRelativePath.replaceAll("\\", "/").replace(/^\.\//, "").replace(/^\/+/, "");
|
|
983
|
-
const segments = normalized.split("/").filter((segment) => segment.length > 0);
|
|
984
|
-
if (segments.length === 0) {
|
|
985
|
-
return { valid: false, reason: "empty path" };
|
|
986
|
-
}
|
|
987
|
-
const filename = segments.at(-1);
|
|
988
|
-
if (filename !== "page.tsx" && filename !== "page.jsx") {
|
|
989
|
-
return { valid: false, reason: "not a page file (expected page.tsx)" };
|
|
990
|
-
}
|
|
991
|
-
const folderSegments = segments.slice(0, -1);
|
|
992
|
-
const prefix = rules.privateFolderPrefix;
|
|
993
|
-
const reserved = rules.scaffoldIgnoredFolders;
|
|
994
|
-
for (const reservedName of reserved) {
|
|
995
|
-
if (folderSegments.includes(reservedName)) {
|
|
996
|
-
return { valid: false, reason: `page.tsx cannot live inside reserved folder ${reservedName}` };
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
const visibleSegments = folderSegments.filter((segment) => !segment.startsWith(prefix));
|
|
1000
|
-
if (folderSegments.length > 0 && visibleSegments.length === 0) {
|
|
1001
|
-
return { valid: false, reason: "no URL segment left after stripping underscore folders" };
|
|
1002
|
-
}
|
|
1003
|
-
const route = visibleSegments.length === 0 ? "/" : `/${visibleSegments.join("/")}`;
|
|
1004
|
-
return { valid: true, route };
|
|
1005
|
-
};
|
|
1006
|
-
|
|
1007
549
|
// src/react/sessionContext.ts
|
|
1008
550
|
import { createContext, use } from "react";
|
|
1009
551
|
var DEFAULT_VALUE = {
|
|
@@ -1119,8 +661,10 @@ function translate({ translationList, key, params }) {
|
|
|
1119
661
|
let finalResult = result;
|
|
1120
662
|
for (const param of params) {
|
|
1121
663
|
if (!param.key) continue;
|
|
1122
|
-
const
|
|
1123
|
-
|
|
664
|
+
const escapedKey = param.key.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
665
|
+
const regex = new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g");
|
|
666
|
+
const replacement = String(param.value);
|
|
667
|
+
finalResult = finalResult.replace(regex, () => replacement);
|
|
1124
668
|
}
|
|
1125
669
|
return finalResult;
|
|
1126
670
|
}
|
|
@@ -1223,7 +767,7 @@ function Middleware({ children }) {
|
|
|
1223
767
|
} else if (result?.redirect) {
|
|
1224
768
|
void navigate(result.redirect);
|
|
1225
769
|
} else {
|
|
1226
|
-
void navigate(
|
|
770
|
+
void navigate(getProjectConfig().loginRedirectUrl ?? "/");
|
|
1227
771
|
}
|
|
1228
772
|
setChecking(false);
|
|
1229
773
|
})();
|
|
@@ -1312,6 +856,8 @@ export {
|
|
|
1312
856
|
parseServiceRouteName,
|
|
1313
857
|
proposeLogin,
|
|
1314
858
|
registerApiMethodMap,
|
|
859
|
+
registerApiRequestInterceptor,
|
|
860
|
+
registerApiResponseInterceptor,
|
|
1315
861
|
registerClientHook,
|
|
1316
862
|
registerLanguageSource,
|
|
1317
863
|
registerLocales,
|