create-caspian-app 0.3.0-rc.2 → 0.3.0-rc.21
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/dist/.github/copilot-instructions.md +33 -3
- package/dist/AGENTS.md +17 -4
- package/dist/caspian.js +1 -1
- package/dist/index.js +1 -1
- package/dist/main-websocket.py +1001 -0
- package/dist/main.py +86 -6
- package/dist/public/js/pp-reactive-v2.js +1 -1
- package/dist/pyproject.toml +1 -1
- package/dist/settings/bs-config.json +6 -6
- package/dist/settings/bs-config.ts +132 -62
- package/dist/settings/component-map.ts +6 -6
- package/dist/settings/python-server.ts +8 -18
- package/dist/settings/utils.ts +3 -3
- package/dist/src/lib/auth/auth_config.py +1 -1
- package/dist/src/lib/mcp/mcp_server.py +8 -8
- package/dist/src/lib/websocket/websocket_security.py +84 -0
- package/package.json +1 -1
package/dist/pyproject.toml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
{
|
|
2
|
-
"local": "http://localhost:
|
|
3
|
-
"external": "http://
|
|
4
|
-
"ui": "http://localhost:
|
|
5
|
-
"uiExternal": "http://
|
|
6
|
-
"backend": "http://localhost:
|
|
1
|
+
{
|
|
2
|
+
"local": "http://localhost:5091",
|
|
3
|
+
"external": "http://192.168.1.109:5091",
|
|
4
|
+
"ui": "http://localhost:3003",
|
|
5
|
+
"uiExternal": "http://192.168.1.109:3003",
|
|
6
|
+
"backend": "http://localhost:5201"
|
|
7
7
|
}
|
|
@@ -8,6 +8,7 @@ import { DebouncedWorker, createSrcWatcher, DEFAULT_AWF } from "./utils.js";
|
|
|
8
8
|
import {
|
|
9
9
|
startPythonServer,
|
|
10
10
|
restartPythonServer,
|
|
11
|
+
stopPythonServer,
|
|
11
12
|
waitForPort,
|
|
12
13
|
} from "./python-server.js";
|
|
13
14
|
import { componentMap } from "./component-map.js";
|
|
@@ -25,6 +26,7 @@ const PUBLIC_ROOT = join(WORKSPACE_ROOT, PUBLIC_DIR);
|
|
|
25
26
|
const PUBLIC_IGNORE_DIRS = ["uploads"];
|
|
26
27
|
let previousRouteFiles: string[] = [];
|
|
27
28
|
let lastChangedFile: string | null = null;
|
|
29
|
+
let shuttingDown = false;
|
|
28
30
|
|
|
29
31
|
let pythonPort = 0;
|
|
30
32
|
let bsPort = 0;
|
|
@@ -173,7 +175,9 @@ const pipeline = new DebouncedWorker(
|
|
|
173
175
|
if (isReady && bs.active) {
|
|
174
176
|
bs.reload();
|
|
175
177
|
} else {
|
|
176
|
-
console.error(
|
|
178
|
+
console.error(
|
|
179
|
+
chalk.red("Warning: Server failed to start or timed out."),
|
|
180
|
+
);
|
|
177
181
|
}
|
|
178
182
|
return;
|
|
179
183
|
}
|
|
@@ -199,7 +203,7 @@ const pipeline = new DebouncedWorker(
|
|
|
199
203
|
if (previousRouteFiles.length > 0 && routesChanged) {
|
|
200
204
|
console.log(
|
|
201
205
|
chalk.yellow(
|
|
202
|
-
"
|
|
206
|
+
"-> Structure changed (New/Deleted file), restarting Python server...",
|
|
203
207
|
),
|
|
204
208
|
);
|
|
205
209
|
await restartPythonServer(pythonPort, bsPort);
|
|
@@ -238,13 +242,48 @@ function isIgnoredPublicPath(absPath: string): boolean {
|
|
|
238
242
|
|
|
239
243
|
const publicPipeline = new DebouncedWorker(
|
|
240
244
|
async () => {
|
|
241
|
-
console.log(
|
|
245
|
+
console.log(
|
|
246
|
+
chalk.cyan("-> Public directory changed, reloading browser..."),
|
|
247
|
+
);
|
|
242
248
|
if (bs.active) bs.reload();
|
|
243
249
|
},
|
|
244
250
|
350,
|
|
245
251
|
"bs-public-pipeline",
|
|
246
252
|
);
|
|
247
253
|
|
|
254
|
+
type ClosableWatcher = {
|
|
255
|
+
close: () => Promise<void>;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const sourceWatchers: ClosableWatcher[] = [];
|
|
259
|
+
|
|
260
|
+
async function closeWatchers(): Promise<void> {
|
|
261
|
+
await Promise.all(
|
|
262
|
+
sourceWatchers.splice(0).map(async (watcher) => {
|
|
263
|
+
try {
|
|
264
|
+
await watcher.close();
|
|
265
|
+
} catch {}
|
|
266
|
+
}),
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async function shutdown(exitCode: number): Promise<void> {
|
|
271
|
+
if (shuttingDown) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
shuttingDown = true;
|
|
276
|
+
|
|
277
|
+
await closeWatchers();
|
|
278
|
+
|
|
279
|
+
if (bs.active) {
|
|
280
|
+
bs.exit();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
stopPythonServer();
|
|
284
|
+
process.exit(exitCode);
|
|
285
|
+
}
|
|
286
|
+
|
|
248
287
|
(async () => {
|
|
249
288
|
const reservedPorts = getReservedPorts();
|
|
250
289
|
|
|
@@ -253,77 +292,90 @@ const publicPipeline = new DebouncedWorker(
|
|
|
253
292
|
|
|
254
293
|
updateRouteFilesCache();
|
|
255
294
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
295
|
+
sourceWatchers.push(
|
|
296
|
+
createSrcWatcher(join(SRC_DIR, "**", "*"), {
|
|
297
|
+
onEvent: (_ev, _abs, rel) => {
|
|
298
|
+
if (rel.includes("__pycache__") || rel.endsWith(".pyc")) return;
|
|
299
|
+
lastChangedFile = rel;
|
|
300
|
+
pipeline.schedule(rel);
|
|
301
|
+
},
|
|
302
|
+
awaitWriteFinish: DEFAULT_AWF,
|
|
303
|
+
logPrefix: "watch-src",
|
|
304
|
+
usePolling: true,
|
|
305
|
+
interval: 1000,
|
|
306
|
+
}),
|
|
307
|
+
);
|
|
267
308
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
309
|
+
sourceWatchers.push(
|
|
310
|
+
createSrcWatcher(join(PUBLIC_DIR, "**", "*"), {
|
|
311
|
+
onEvent: (_ev, abs, _) => {
|
|
312
|
+
const relFromPublic = relative(PUBLIC_ROOT, abs).replace(/\\/g, "/");
|
|
313
|
+
if (isIgnoredPublicPath(abs)) return;
|
|
314
|
+
publicPipeline.schedule(relFromPublic);
|
|
315
|
+
},
|
|
316
|
+
awaitWriteFinish: DEFAULT_AWF,
|
|
317
|
+
logPrefix: "watch-public",
|
|
318
|
+
usePolling: true,
|
|
319
|
+
interval: 1000,
|
|
320
|
+
}),
|
|
321
|
+
);
|
|
279
322
|
|
|
280
323
|
const viteFlagFile = join(__dirname, "..", ".casp", ".vite-build-complete");
|
|
281
324
|
mkdirSync(dirname(viteFlagFile), { recursive: true });
|
|
282
325
|
if (!existsSync(viteFlagFile)) writeFileSync(viteFlagFile, "0");
|
|
283
326
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
bs.
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
327
|
+
sourceWatchers.push(
|
|
328
|
+
createSrcWatcher(viteFlagFile, {
|
|
329
|
+
onEvent: (ev) => {
|
|
330
|
+
if (ev === "change" && bs.active) {
|
|
331
|
+
bs.reload();
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 },
|
|
335
|
+
logPrefix: "watch-vite",
|
|
336
|
+
usePolling: true,
|
|
337
|
+
interval: 500,
|
|
338
|
+
}),
|
|
339
|
+
);
|
|
295
340
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
341
|
+
sourceWatchers.push(
|
|
342
|
+
createSrcWatcher(join(__dirname, "..", "utils", "**", "*.py"), {
|
|
343
|
+
onEvent: async (_ev, _abs, _) => {
|
|
344
|
+
if (_abs.includes("__pycache__")) return;
|
|
345
|
+
await restartPythonServer(pythonPort, bsPort);
|
|
346
|
+
const isReady = await waitForPort(pythonPort);
|
|
347
|
+
if (isReady && bs.active) bs.reload();
|
|
348
|
+
},
|
|
349
|
+
awaitWriteFinish: DEFAULT_AWF,
|
|
350
|
+
logPrefix: "watch-utils",
|
|
351
|
+
usePolling: true,
|
|
352
|
+
interval: 1000,
|
|
353
|
+
}),
|
|
354
|
+
);
|
|
308
355
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
356
|
+
sourceWatchers.push(
|
|
357
|
+
createSrcWatcher(join(__dirname, "..", "main.py"), {
|
|
358
|
+
onEvent: async (_ev, _abs, _) => {
|
|
359
|
+
if (_abs.includes("__pycache__")) return;
|
|
360
|
+
await restartPythonServer(pythonPort, bsPort);
|
|
361
|
+
const isReady = await waitForPort(pythonPort);
|
|
362
|
+
if (isReady && bs.active) bs.reload();
|
|
363
|
+
},
|
|
364
|
+
awaitWriteFinish: DEFAULT_AWF,
|
|
365
|
+
logPrefix: "watch-main",
|
|
366
|
+
usePolling: true,
|
|
367
|
+
interval: 1000,
|
|
368
|
+
}),
|
|
369
|
+
);
|
|
321
370
|
|
|
322
371
|
startPythonServer(pythonPort, bsPort);
|
|
323
372
|
|
|
324
373
|
bs.init(
|
|
325
374
|
{
|
|
326
|
-
proxy:
|
|
375
|
+
proxy: {
|
|
376
|
+
target: `http://localhost:${pythonPort}`,
|
|
377
|
+
ws: true,
|
|
378
|
+
},
|
|
327
379
|
port: bsPort,
|
|
328
380
|
online: true,
|
|
329
381
|
middleware: [
|
|
@@ -356,7 +408,7 @@ const publicPipeline = new DebouncedWorker(
|
|
|
356
408
|
const uiExtUrl = urls.get("ui-external");
|
|
357
409
|
|
|
358
410
|
console.log("");
|
|
359
|
-
console.log(chalk.green.bold("
|
|
411
|
+
console.log(chalk.green.bold("OK Ports Configured:"));
|
|
360
412
|
console.log(
|
|
361
413
|
` ${chalk.blue.bold("Frontend (BrowserSync):")} ${chalk.magenta(localUrl)}`,
|
|
362
414
|
);
|
|
@@ -394,3 +446,21 @@ const publicPipeline = new DebouncedWorker(
|
|
|
394
446
|
},
|
|
395
447
|
);
|
|
396
448
|
})();
|
|
449
|
+
|
|
450
|
+
process.once("SIGINT", () => {
|
|
451
|
+
void shutdown(0);
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
process.once("SIGTERM", () => {
|
|
455
|
+
void shutdown(0);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
process.once("uncaughtException", (error) => {
|
|
459
|
+
console.error(error);
|
|
460
|
+
void shutdown(1);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
process.once("unhandledRejection", (reason) => {
|
|
464
|
+
console.error(reason);
|
|
465
|
+
void shutdown(1);
|
|
466
|
+
});
|
|
@@ -981,7 +981,7 @@ function analyzeFile(filePath: string, rootDir: string): ComponentMetadata[] {
|
|
|
981
981
|
|
|
982
982
|
function loadConfig(): CaspianConfig {
|
|
983
983
|
if (!fs.existsSync(CONFIG_PATH)) {
|
|
984
|
-
console.error(
|
|
984
|
+
console.error(`Error: Configuration file not found at: ${CONFIG_PATH}`);
|
|
985
985
|
process.exit(1);
|
|
986
986
|
}
|
|
987
987
|
return JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8"));
|
|
@@ -1024,7 +1024,7 @@ function walkDirectory(
|
|
|
1024
1024
|
*/
|
|
1025
1025
|
|
|
1026
1026
|
export async function componentMap() {
|
|
1027
|
-
console.log(
|
|
1027
|
+
console.log("Starting Component Analysis (Lezer AST-first)...");
|
|
1028
1028
|
const config = loadConfig();
|
|
1029
1029
|
let allFiles: string[] = [];
|
|
1030
1030
|
|
|
@@ -1036,7 +1036,7 @@ export async function componentMap() {
|
|
|
1036
1036
|
);
|
|
1037
1037
|
});
|
|
1038
1038
|
|
|
1039
|
-
console.log(
|
|
1039
|
+
console.log(`Found ${allFiles.length} Python files.`);
|
|
1040
1040
|
const componentRegistry: ComponentMetadata[] = [];
|
|
1041
1041
|
|
|
1042
1042
|
allFiles.forEach((file) => {
|
|
@@ -1044,15 +1044,15 @@ export async function componentMap() {
|
|
|
1044
1044
|
const foundComponents = analyzeFile(file, PROJECT_ROOT);
|
|
1045
1045
|
componentRegistry.push(...foundComponents);
|
|
1046
1046
|
} catch (e) {
|
|
1047
|
-
console.warn(
|
|
1047
|
+
console.warn(`Warning: Failed to parse ${file}:`, e);
|
|
1048
1048
|
}
|
|
1049
1049
|
});
|
|
1050
1050
|
|
|
1051
|
-
console.log(
|
|
1051
|
+
console.log(`Discovered ${componentRegistry.length} Components.`);
|
|
1052
1052
|
|
|
1053
1053
|
const outputPath = path.join(__dirname, "component-map.json");
|
|
1054
1054
|
if (componentRegistry.length > 0 || !fs.existsSync(outputPath)) {
|
|
1055
1055
|
fs.writeFileSync(outputPath, JSON.stringify(componentRegistry, null, 2));
|
|
1056
|
-
console.log(
|
|
1056
|
+
console.log(`Component map written to: ${outputPath}`);
|
|
1057
1057
|
}
|
|
1058
1058
|
}
|
|
@@ -17,7 +17,7 @@ function getVenvPythonPath(): string {
|
|
|
17
17
|
: join(".venv", "bin", "python");
|
|
18
18
|
|
|
19
19
|
if (!existsSync(venvPython)) {
|
|
20
|
-
console.warn(
|
|
20
|
+
console.warn("Warning: Virtual environment not found, using system python");
|
|
21
21
|
return isWindows() ? "python" : "python3";
|
|
22
22
|
}
|
|
23
23
|
return venvPython;
|
|
@@ -118,7 +118,7 @@ function spawnPython(port: number, browserSyncPort?: number): ChildProcess {
|
|
|
118
118
|
const pythonPath = getVenvPythonPath();
|
|
119
119
|
const args = ["-u", "main.py"];
|
|
120
120
|
|
|
121
|
-
console.log(
|
|
121
|
+
console.log(`-> Starting Python server on port ${port}...`);
|
|
122
122
|
|
|
123
123
|
const env = {
|
|
124
124
|
...process.env,
|
|
@@ -156,7 +156,7 @@ export async function restartPythonServer(
|
|
|
156
156
|
isRestarting = true;
|
|
157
157
|
|
|
158
158
|
try {
|
|
159
|
-
console.log("
|
|
159
|
+
console.log("-> Restarting Python server...");
|
|
160
160
|
const prev = pythonProcess;
|
|
161
161
|
pythonProcess = null;
|
|
162
162
|
|
|
@@ -171,18 +171,8 @@ export async function restartPythonServer(
|
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
export function stopPythonServer(): void {
|
|
175
|
-
const prev = pythonProcess;
|
|
176
|
-
pythonProcess = null;
|
|
177
|
-
if (prev) killProcessTree(prev);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
process.on("exit", () => stopPythonServer());
|
|
181
|
-
process.on("SIGINT", () => {
|
|
182
|
-
stopPythonServer();
|
|
183
|
-
process.exit(0);
|
|
184
|
-
});
|
|
185
|
-
process.on("SIGTERM", () => {
|
|
186
|
-
stopPythonServer();
|
|
187
|
-
process.exit(0);
|
|
188
|
-
});
|
|
174
|
+
export function stopPythonServer(): void {
|
|
175
|
+
const prev = pythonProcess;
|
|
176
|
+
pythonProcess = null;
|
|
177
|
+
if (prev) killProcessTree(prev);
|
|
178
|
+
}
|
package/dist/settings/utils.ts
CHANGED
|
@@ -109,7 +109,7 @@ export class DebouncedWorker {
|
|
|
109
109
|
) {}
|
|
110
110
|
|
|
111
111
|
schedule(reason?: string) {
|
|
112
|
-
if (reason) console.log(`[${this.name}] ${reason}
|
|
112
|
+
if (reason) console.log(`[${this.name}] ${reason} -> scheduled`);
|
|
113
113
|
if (this.timer) clearTimeout(this.timer);
|
|
114
114
|
this.timer = setTimeout(() => {
|
|
115
115
|
this.timer = null;
|
|
@@ -203,7 +203,7 @@ export function createRestartableProcess(spec: {
|
|
|
203
203
|
async function stop(): Promise<void> {
|
|
204
204
|
if (!child || child.killed) return;
|
|
205
205
|
const pid = child.pid!;
|
|
206
|
-
console.log(`[${name}] Stopping
|
|
206
|
+
console.log(`[${name}] Stopping...`);
|
|
207
207
|
|
|
208
208
|
if (process.platform === "win32" && windowsKillTree) {
|
|
209
209
|
await killOnWindows(pid);
|
|
@@ -245,7 +245,7 @@ export function createRestartableProcess(spec: {
|
|
|
245
245
|
|
|
246
246
|
export function onExit(fn: () => Promise<void> | void) {
|
|
247
247
|
const wrap = (sig: string) => async () => {
|
|
248
|
-
console.log(`[proc] Received ${sig}, shutting down
|
|
248
|
+
console.log(`[proc] Received ${sig}, shutting down...`);
|
|
249
249
|
try {
|
|
250
250
|
await fn();
|
|
251
251
|
} finally {
|
|
@@ -26,7 +26,7 @@ def build_auth_settings() -> AuthSettings:
|
|
|
26
26
|
# This app-owned starter config begins public-first; switch to True only when most routes require auth.
|
|
27
27
|
# Use all-private mode when only a few routes should stay public.
|
|
28
28
|
is_all_routes_private=False,
|
|
29
|
-
public_routes=["/"],
|
|
29
|
+
public_routes=["/", "/health"],
|
|
30
30
|
# Sign-in and signup stay public by default; only change this when the app explicitly needs it.
|
|
31
31
|
auth_routes=["/signin", "/signup"],
|
|
32
32
|
private_routes=[], # unused when all-routes-private is True
|
|
@@ -14,13 +14,13 @@ FILES_LIST_PATH = PROJECT_ROOT / "settings" / "files-list.json"
|
|
|
14
14
|
COMPONENT_MAP_PATH = PROJECT_ROOT / "settings" / "component-map.json"
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
mcp = FastMCP(
|
|
18
|
-
name="
|
|
19
|
-
instructions=(
|
|
20
|
-
"Read-only workspace metadata for the
|
|
21
|
-
"Use these tools for project configuration, generated file inventory, and component discovery."
|
|
22
|
-
),
|
|
23
|
-
)
|
|
17
|
+
mcp = FastMCP(
|
|
18
|
+
name="Caspian MCP",
|
|
19
|
+
instructions=(
|
|
20
|
+
"Read-only workspace metadata for the Caspian application. "
|
|
21
|
+
"Use these tools for project configuration, generated file inventory, and component discovery."
|
|
22
|
+
),
|
|
23
|
+
)
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
def _load_json(path: Path, fallback: Any) -> Any:
|
|
@@ -88,4 +88,4 @@ def component_inventory() -> dict[str, Any]:
|
|
|
88
88
|
return {
|
|
89
89
|
"count": len(components),
|
|
90
90
|
"components": components,
|
|
91
|
-
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import MutableMapping
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from fastapi import WebSocket
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_websocket_session(websocket: WebSocket) -> MutableMapping[str, Any]:
|
|
11
|
+
session = getattr(websocket, "session", None)
|
|
12
|
+
if isinstance(session, MutableMapping):
|
|
13
|
+
return session
|
|
14
|
+
|
|
15
|
+
scope_session = websocket.scope.get("session")
|
|
16
|
+
if isinstance(scope_session, MutableMapping):
|
|
17
|
+
return scope_session
|
|
18
|
+
|
|
19
|
+
return {}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_authenticated_payload_from_session(
|
|
23
|
+
session: MutableMapping[str, Any],
|
|
24
|
+
auth_instance: Any,
|
|
25
|
+
) -> dict[str, Any] | None:
|
|
26
|
+
payload_key = getattr(auth_instance, "PAYLOAD_SESSION_KEY", "")
|
|
27
|
+
payload_name = getattr(auth_instance, "PAYLOAD_NAME", "")
|
|
28
|
+
|
|
29
|
+
payload_wrapper = session.get(payload_key)
|
|
30
|
+
if not isinstance(payload_wrapper, dict):
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
exp = payload_wrapper.get("exp")
|
|
34
|
+
if exp is not None:
|
|
35
|
+
try:
|
|
36
|
+
if float(exp) < datetime.now(timezone.utc).timestamp():
|
|
37
|
+
session.pop(payload_key, None)
|
|
38
|
+
return None
|
|
39
|
+
except Exception:
|
|
40
|
+
session.pop(payload_key, None)
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
payload = payload_wrapper.get(payload_name)
|
|
44
|
+
if payload is None:
|
|
45
|
+
session.pop(payload_key, None)
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
if isinstance(payload, dict):
|
|
49
|
+
if getattr(getattr(auth_instance, "settings", None), "token_auto_refresh", False):
|
|
50
|
+
refreshed_wrapper = dict(payload_wrapper)
|
|
51
|
+
refreshed_wrapper["exp"] = auth_instance._calculate_expiration(
|
|
52
|
+
auth_instance.settings.default_token_validity
|
|
53
|
+
)
|
|
54
|
+
session[payload_key] = refreshed_wrapper
|
|
55
|
+
return payload
|
|
56
|
+
|
|
57
|
+
return {"value": payload}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class WebSocketConnectionManager:
|
|
61
|
+
def __init__(self) -> None:
|
|
62
|
+
self._connections: set[WebSocket] = set()
|
|
63
|
+
|
|
64
|
+
async def connect(self, websocket: WebSocket) -> None:
|
|
65
|
+
await websocket.accept()
|
|
66
|
+
self._connections.add(websocket)
|
|
67
|
+
|
|
68
|
+
def disconnect(self, websocket: WebSocket) -> None:
|
|
69
|
+
self._connections.discard(websocket)
|
|
70
|
+
|
|
71
|
+
async def broadcast_json(self, payload: dict[str, Any]) -> None:
|
|
72
|
+
stale: list[WebSocket] = []
|
|
73
|
+
for websocket in list(self._connections):
|
|
74
|
+
try:
|
|
75
|
+
await websocket.send_json(payload)
|
|
76
|
+
except RuntimeError:
|
|
77
|
+
stale.append(websocket)
|
|
78
|
+
|
|
79
|
+
for websocket in stale:
|
|
80
|
+
self.disconnect(websocket)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
websocket_connections = WebSocketConnectionManager()
|
|
84
|
+
public_websocket_connections = WebSocketConnectionManager()
|