Compare commits
No commits in common. "main" and "worktree-agent-ae238fec" have entirely different histories.
main
...
worktree-a
24
.gitignore
vendored
24
.gitignore
vendored
@ -136,31 +136,7 @@ celerybeat.pid
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
.envrc
|
||||
|
||||
# Private keys and certificates
|
||||
*.pem
|
||||
*.key
|
||||
*.p12
|
||||
*.pfx
|
||||
*.jks
|
||||
*.keystore
|
||||
|
||||
# Service credentials
|
||||
credentials.json
|
||||
service-account.json
|
||||
*-credentials.json
|
||||
|
||||
# SSH keys
|
||||
id_rsa
|
||||
id_ecdsa
|
||||
id_ed25519
|
||||
|
||||
# Other sensitive files
|
||||
*.secrets
|
||||
.htpasswd
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
|
||||
@ -1,300 +0,0 @@
|
||||
{
|
||||
"version": "1.5.0",
|
||||
"plugins_used": [
|
||||
{
|
||||
"name": "ArtifactoryDetector"
|
||||
},
|
||||
{
|
||||
"name": "AWSKeyDetector"
|
||||
},
|
||||
{
|
||||
"name": "AzureStorageKeyDetector"
|
||||
},
|
||||
{
|
||||
"name": "Base64HighEntropyString",
|
||||
"limit": 4.5
|
||||
},
|
||||
{
|
||||
"name": "BasicAuthDetector"
|
||||
},
|
||||
{
|
||||
"name": "CloudantDetector"
|
||||
},
|
||||
{
|
||||
"name": "DiscordBotTokenDetector"
|
||||
},
|
||||
{
|
||||
"name": "GitHubTokenDetector"
|
||||
},
|
||||
{
|
||||
"name": "GitLabTokenDetector"
|
||||
},
|
||||
{
|
||||
"name": "HexHighEntropyString",
|
||||
"limit": 3.0
|
||||
},
|
||||
{
|
||||
"name": "IbmCloudIamDetector"
|
||||
},
|
||||
{
|
||||
"name": "IbmCosHmacDetector"
|
||||
},
|
||||
{
|
||||
"name": "IPPublicDetector"
|
||||
},
|
||||
{
|
||||
"name": "JwtTokenDetector"
|
||||
},
|
||||
{
|
||||
"name": "KeywordDetector",
|
||||
"keyword_exclude": ""
|
||||
},
|
||||
{
|
||||
"name": "MailchimpDetector"
|
||||
},
|
||||
{
|
||||
"name": "NpmDetector"
|
||||
},
|
||||
{
|
||||
"name": "OpenAIDetector"
|
||||
},
|
||||
{
|
||||
"name": "PrivateKeyDetector"
|
||||
},
|
||||
{
|
||||
"name": "PypiTokenDetector"
|
||||
},
|
||||
{
|
||||
"name": "SendGridDetector"
|
||||
},
|
||||
{
|
||||
"name": "SlackDetector"
|
||||
},
|
||||
{
|
||||
"name": "SoftlayerDetector"
|
||||
},
|
||||
{
|
||||
"name": "SquareOAuthDetector"
|
||||
},
|
||||
{
|
||||
"name": "StripeDetector"
|
||||
},
|
||||
{
|
||||
"name": "TelegramBotTokenDetector"
|
||||
},
|
||||
{
|
||||
"name": "TwilioKeyDetector"
|
||||
}
|
||||
],
|
||||
"filters_used": [
|
||||
{
|
||||
"path": "detect_secrets.filters.allowlist.is_line_allowlisted"
|
||||
},
|
||||
{
|
||||
"path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies",
|
||||
"min_level": 2
|
||||
},
|
||||
{
|
||||
"path": "detect_secrets.filters.heuristic.is_indirect_reference"
|
||||
},
|
||||
{
|
||||
"path": "detect_secrets.filters.heuristic.is_likely_id_string"
|
||||
},
|
||||
{
|
||||
"path": "detect_secrets.filters.heuristic.is_lock_file"
|
||||
},
|
||||
{
|
||||
"path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string"
|
||||
},
|
||||
{
|
||||
"path": "detect_secrets.filters.heuristic.is_potential_uuid"
|
||||
},
|
||||
{
|
||||
"path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign"
|
||||
},
|
||||
{
|
||||
"path": "detect_secrets.filters.heuristic.is_sequential_string"
|
||||
},
|
||||
{
|
||||
"path": "detect_secrets.filters.heuristic.is_swagger_file"
|
||||
},
|
||||
{
|
||||
"path": "detect_secrets.filters.heuristic.is_templated_secret"
|
||||
},
|
||||
{
|
||||
"path": "detect_secrets.filters.regex.should_exclude_file",
|
||||
"pattern": [
|
||||
"\\.env\\.example$",
|
||||
"server/\\.env\\.example$"
|
||||
]
|
||||
}
|
||||
],
|
||||
"results": {
|
||||
"INSTALL.md": [
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "INSTALL.md",
|
||||
"hashed_secret": "365e24291fd19bba10a0d8504c0ed90d5c8bef7f",
|
||||
"is_verified": false,
|
||||
"line_number": 75
|
||||
},
|
||||
{
|
||||
"type": "Basic Auth Credentials",
|
||||
"filename": "INSTALL.md",
|
||||
"hashed_secret": "4f4944a7117fd2e95169da2b40af33b68a65a161",
|
||||
"is_verified": false,
|
||||
"line_number": 114
|
||||
},
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "INSTALL.md",
|
||||
"hashed_secret": "c35bdb821a941808a150db95d0f934f449bbff17",
|
||||
"is_verified": false,
|
||||
"line_number": 182
|
||||
},
|
||||
{
|
||||
"type": "Basic Auth Credentials",
|
||||
"filename": "INSTALL.md",
|
||||
"hashed_secret": "c35bdb821a941808a150db95d0f934f449bbff17",
|
||||
"is_verified": false,
|
||||
"line_number": 225
|
||||
},
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "INSTALL.md",
|
||||
"hashed_secret": "001c1654cb8dff7c4ddb1ae6d2203d0dd15a6096",
|
||||
"is_verified": false,
|
||||
"line_number": 391
|
||||
},
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "INSTALL.md",
|
||||
"hashed_secret": "53fe8c55272f9c3ceebb5e6058788e8981a359cb",
|
||||
"is_verified": false,
|
||||
"line_number": 397
|
||||
}
|
||||
],
|
||||
"docker-compose.dev.yml": [
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "docker-compose.dev.yml",
|
||||
"hashed_secret": "4f4944a7117fd2e95169da2b40af33b68a65a161",
|
||||
"is_verified": false,
|
||||
"line_number": 44
|
||||
}
|
||||
],
|
||||
"docs/v2/V2_BUILD_PLAN.md": [
|
||||
{
|
||||
"type": "Basic Auth Credentials",
|
||||
"filename": "docs/v2/V2_BUILD_PLAN.md",
|
||||
"hashed_secret": "e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4",
|
||||
"is_verified": false,
|
||||
"line_number": 301
|
||||
}
|
||||
],
|
||||
"scripts/docker-build.sh": [
|
||||
{
|
||||
"type": "Basic Auth Credentials",
|
||||
"filename": "scripts/docker-build.sh",
|
||||
"hashed_secret": "9d4e1e23bd5b727046a9e3b4b7db57bd8d6ee684",
|
||||
"is_verified": false,
|
||||
"line_number": 40
|
||||
}
|
||||
],
|
||||
"scripts/install.sh": [
|
||||
{
|
||||
"type": "Basic Auth Credentials",
|
||||
"filename": "scripts/install.sh",
|
||||
"hashed_secret": "4f4944a7117fd2e95169da2b40af33b68a65a161",
|
||||
"is_verified": false,
|
||||
"line_number": 156
|
||||
},
|
||||
{
|
||||
"type": "Basic Auth Credentials",
|
||||
"filename": "scripts/install.sh",
|
||||
"hashed_secret": "7205a0abf00d1daec13c63ece029057c974795a9",
|
||||
"is_verified": false,
|
||||
"line_number": 267
|
||||
}
|
||||
],
|
||||
"server/RULES.md": [
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "server/RULES.md",
|
||||
"hashed_secret": "a6778f1880744bd1a342a8e3789135412d8f9da2",
|
||||
"is_verified": false,
|
||||
"line_number": 904
|
||||
},
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "server/RULES.md",
|
||||
"hashed_secret": "aafdc23870ecbcd3d557b6423a8982134e17927e",
|
||||
"is_verified": false,
|
||||
"line_number": 949
|
||||
}
|
||||
],
|
||||
"server/config.py": [
|
||||
{
|
||||
"type": "Basic Auth Credentials",
|
||||
"filename": "server/config.py",
|
||||
"hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8",
|
||||
"is_verified": false,
|
||||
"line_number": 123
|
||||
}
|
||||
],
|
||||
"server/game_analyzer.py": [
|
||||
{
|
||||
"type": "Basic Auth Credentials",
|
||||
"filename": "server/game_analyzer.py",
|
||||
"hashed_secret": "4f4944a7117fd2e95169da2b40af33b68a65a161",
|
||||
"is_verified": false,
|
||||
"line_number": 616
|
||||
}
|
||||
],
|
||||
"server/test_auth.py": [
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "server/test_auth.py",
|
||||
"hashed_secret": "cbfdac6008f9cab4083784cbd1874f76618d2a97",
|
||||
"is_verified": false,
|
||||
"line_number": 38
|
||||
},
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "server/test_auth.py",
|
||||
"hashed_secret": "f0578f1e7174b1a41c4ea8c6e17f7a8a3b88c92a",
|
||||
"is_verified": false,
|
||||
"line_number": 50
|
||||
},
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "server/test_auth.py",
|
||||
"hashed_secret": "8be52126a6fde450a7162a3651d589bb51e9579d",
|
||||
"is_verified": false,
|
||||
"line_number": 64
|
||||
},
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "server/test_auth.py",
|
||||
"hashed_secret": "74913f5cd5f61ec0bcfdb775414c2fb3d161b620",
|
||||
"is_verified": false,
|
||||
"line_number": 74
|
||||
},
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "server/test_auth.py",
|
||||
"hashed_secret": "9d4e1e23bd5b727046a9e3b4b7db57bd8d6ee684",
|
||||
"is_verified": false,
|
||||
"line_number": 91
|
||||
},
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "server/test_auth.py",
|
||||
"hashed_secret": "1e99b09f6eb835305555cc43c3e0768b1a39226b",
|
||||
"is_verified": false,
|
||||
"line_number": 103
|
||||
}
|
||||
]
|
||||
},
|
||||
"generated_at": "2026-03-06T03:45:28Z"
|
||||
}
|
||||
@ -81,7 +81,6 @@ class GolfGame {
|
||||
this.initCardTooltips();
|
||||
this.bindEvents();
|
||||
this.initMobileDetection();
|
||||
this.initDesktopScorecard();
|
||||
this.checkUrlParams();
|
||||
}
|
||||
|
||||
@ -112,11 +111,9 @@ class GolfGame {
|
||||
this.isMobile = e.matches;
|
||||
document.body.classList.toggle('mobile-portrait', e.matches);
|
||||
setAppHeight();
|
||||
// Close any open drawers/overlays on layout change
|
||||
// Close any open drawers on layout change
|
||||
if (!e.matches) {
|
||||
this.closeDrawers();
|
||||
} else {
|
||||
this.closeDesktopScorecard();
|
||||
}
|
||||
};
|
||||
mql.addEventListener('change', update);
|
||||
@ -157,31 +154,6 @@ class GolfGame {
|
||||
if (bottomBar) bottomBar.classList.remove('hidden');
|
||||
}
|
||||
|
||||
initDesktopScorecard() {
|
||||
if (!this.desktopScorecardBtn) return;
|
||||
|
||||
this.desktopScorecardBtn.addEventListener('click', () => {
|
||||
const isOpen = this.desktopScorecardOverlay.classList.contains('open');
|
||||
if (isOpen) {
|
||||
this.closeDesktopScorecard();
|
||||
} else {
|
||||
this.desktopScorecardOverlay.classList.add('open');
|
||||
this.desktopScorecardBtn.classList.add('active');
|
||||
this.desktopScorecardBackdrop.classList.add('visible');
|
||||
}
|
||||
});
|
||||
|
||||
this.desktopScorecardBackdrop.addEventListener('click', () => {
|
||||
this.closeDesktopScorecard();
|
||||
});
|
||||
}
|
||||
|
||||
closeDesktopScorecard() {
|
||||
if (this.desktopScorecardOverlay) this.desktopScorecardOverlay.classList.remove('open');
|
||||
if (this.desktopScorecardBtn) this.desktopScorecardBtn.classList.remove('active');
|
||||
if (this.desktopScorecardBackdrop) this.desktopScorecardBackdrop.classList.remove('visible');
|
||||
}
|
||||
|
||||
initAudio() {
|
||||
// Initialize audio context on first user interaction
|
||||
const initCtx = () => {
|
||||
@ -572,13 +544,6 @@ class GolfGame {
|
||||
this.gameUsername = document.getElementById('game-username');
|
||||
this.gameLogoutBtn = document.getElementById('game-logout-btn');
|
||||
this.authBar = document.getElementById('auth-bar');
|
||||
|
||||
// Desktop scorecard overlay elements
|
||||
this.desktopScorecardBtn = document.getElementById('desktop-scorecard-btn');
|
||||
this.desktopScorecardOverlay = document.getElementById('desktop-scorecard-overlay');
|
||||
this.desktopScorecardBackdrop = document.getElementById('desktop-scorecard-backdrop');
|
||||
this.desktopStandingsList = document.getElementById('desktop-standings-list');
|
||||
this.desktopScoreTable = document.getElementById('desktop-score-table')?.querySelector('tbody');
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
@ -1499,8 +1464,12 @@ class GolfGame {
|
||||
this.swapAnimationCardEl = handCardEl;
|
||||
this.swapAnimationHandCardEl = handCardEl;
|
||||
|
||||
// Hide discard button during animation (held card hidden later by onStart)
|
||||
// Hide originals and UI during animation
|
||||
handCardEl.classList.add('swap-out');
|
||||
this.discardBtn.classList.add('hidden');
|
||||
if (this.heldCardFloating) {
|
||||
this.heldCardFloating.style.visibility = 'hidden';
|
||||
}
|
||||
|
||||
// Store drawn card data before clearing
|
||||
const drawnCardData = this.drawnCard;
|
||||
@ -1523,12 +1492,6 @@ class GolfGame {
|
||||
{
|
||||
rotation: 0,
|
||||
wasHandFaceDown: false,
|
||||
onStart: () => {
|
||||
handCardEl.classList.add('swap-out');
|
||||
if (this.heldCardFloating) {
|
||||
this.heldCardFloating.style.visibility = 'hidden';
|
||||
}
|
||||
},
|
||||
onComplete: () => {
|
||||
handCardEl.classList.remove('swap-out');
|
||||
if (this.heldCardFloating) {
|
||||
@ -1592,12 +1555,6 @@ class GolfGame {
|
||||
{
|
||||
rotation: 0,
|
||||
wasHandFaceDown: true,
|
||||
onStart: () => {
|
||||
if (handCardEl) handCardEl.classList.add('swap-out');
|
||||
if (this.heldCardFloating) {
|
||||
this.heldCardFloating.style.visibility = 'hidden';
|
||||
}
|
||||
},
|
||||
onComplete: () => {
|
||||
if (handCardEl) handCardEl.classList.remove('swap-out');
|
||||
if (this.heldCardFloating) {
|
||||
@ -2930,6 +2887,9 @@ class GolfGame {
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide the source card during animation
|
||||
sourceCardEl.classList.add('swap-out');
|
||||
|
||||
// Use unified swap animation
|
||||
if (window.cardAnimations) {
|
||||
const heldRect = window.cardAnimations.getHoldingRect();
|
||||
@ -2942,9 +2902,6 @@ class GolfGame {
|
||||
{
|
||||
rotation: sourceRotation,
|
||||
wasHandFaceDown: !wasFaceUp,
|
||||
onStart: () => {
|
||||
sourceCardEl.classList.add('swap-out');
|
||||
},
|
||||
onComplete: () => {
|
||||
if (sourceCardEl) sourceCardEl.classList.remove('swap-out');
|
||||
this.opponentSwapAnimation = null;
|
||||
@ -4390,11 +4347,6 @@ class GolfGame {
|
||||
`;
|
||||
this.scoreTable.appendChild(tr);
|
||||
});
|
||||
|
||||
// Mirror to desktop overlay
|
||||
if (this.desktopScoreTable) {
|
||||
this.desktopScoreTable.innerHTML = this.scoreTable.innerHTML;
|
||||
}
|
||||
}
|
||||
|
||||
updateStandings() {
|
||||
@ -4432,7 +4384,7 @@ class GolfGame {
|
||||
return `<div class="rank-row ${holesRank === 0 && p.rounds_won > 0 ? 'leader' : ''}"><span class="rank-pos">${medal}</span><span class="rank-name">${name}</span><span class="rank-val">${p.rounds_won} wins</span></div>`;
|
||||
}).join('');
|
||||
|
||||
const standingsContent = `
|
||||
this.standingsList.innerHTML = `
|
||||
<div class="standings-section">
|
||||
<div class="standings-title">By Score</div>
|
||||
${pointsHtml}
|
||||
@ -4442,10 +4394,6 @@ class GolfGame {
|
||||
${holesHtml}
|
||||
</div>
|
||||
`;
|
||||
this.standingsList.innerHTML = standingsContent;
|
||||
if (this.desktopStandingsList) {
|
||||
this.desktopStandingsList.innerHTML = standingsContent;
|
||||
}
|
||||
}
|
||||
|
||||
renderCard(card, clickable, selected) {
|
||||
@ -4525,11 +4473,6 @@ class GolfGame {
|
||||
this.scoreTable.appendChild(tr);
|
||||
});
|
||||
|
||||
// Mirror to desktop overlay
|
||||
if (this.desktopScoreTable) {
|
||||
this.desktopScoreTable.innerHTML = this.scoreTable.innerHTML;
|
||||
}
|
||||
|
||||
// Show rankings announcement only for final results
|
||||
const existingAnnouncement = document.getElementById('rankings-announcement');
|
||||
if (existingAnnouncement) existingAnnouncement.remove();
|
||||
|
||||
@ -1105,7 +1105,7 @@ class CardAnimations {
|
||||
// heldRect: position of the held card (or null to use default holding position)
|
||||
// options: { rotation, wasHandFaceDown, onComplete }
|
||||
animateUnifiedSwap(handCardData, heldCardData, handRect, heldRect, options = {}) {
|
||||
const { rotation = 0, wasHandFaceDown = false, onComplete, onStart } = options;
|
||||
const { rotation = 0, wasHandFaceDown = false, onComplete } = options;
|
||||
const T = window.TIMING?.swap || { lift: 100, arc: 320, settle: 100 };
|
||||
const discardRect = this.getDiscardRect();
|
||||
|
||||
@ -1137,15 +1137,15 @@ class CardAnimations {
|
||||
delete el.dataset.animating;
|
||||
el.remove();
|
||||
});
|
||||
this._runUnifiedSwap(handCardData, heldCardData, handRect, heldRect, discardRect, T, rotation, wasHandFaceDown, onComplete, onStart);
|
||||
this._runUnifiedSwap(handCardData, heldCardData, handRect, heldRect, discardRect, T, rotation, wasHandFaceDown, onComplete);
|
||||
}, 350);
|
||||
return;
|
||||
}
|
||||
|
||||
this._runUnifiedSwap(handCardData, heldCardData, handRect, heldRect, discardRect, T, rotation, wasHandFaceDown, onComplete, onStart);
|
||||
this._runUnifiedSwap(handCardData, heldCardData, handRect, heldRect, discardRect, T, rotation, wasHandFaceDown, onComplete);
|
||||
}
|
||||
|
||||
_runUnifiedSwap(handCardData, heldCardData, handRect, heldRect, discardRect, T, rotation, wasHandFaceDown, onComplete, onStart) {
|
||||
_runUnifiedSwap(handCardData, heldCardData, handRect, heldRect, discardRect, T, rotation, wasHandFaceDown, onComplete) {
|
||||
// Create the two traveling cards
|
||||
const travelingHand = this.createCardFromData(handCardData, handRect, rotation);
|
||||
const travelingHeld = this.createCardFromData(heldCardData, heldRect, 0);
|
||||
@ -1154,9 +1154,6 @@ class CardAnimations {
|
||||
document.body.appendChild(travelingHand);
|
||||
document.body.appendChild(travelingHeld);
|
||||
|
||||
// Now that overlays cover the originals, hide them
|
||||
if (onStart) onStart();
|
||||
|
||||
this.playSound('card');
|
||||
|
||||
// If hand card was face-down, flip it first
|
||||
|
||||
@ -782,17 +782,9 @@ class Game:
|
||||
for i, player in enumerate(self.players):
|
||||
if player.id == player_id:
|
||||
removed = self.players.pop(i)
|
||||
if self.players:
|
||||
# Adjust dealer_idx if needed after removal
|
||||
if self.dealer_idx >= len(self.players):
|
||||
if self.players and self.dealer_idx >= len(self.players):
|
||||
self.dealer_idx = 0
|
||||
# Adjust current_player_index after removal
|
||||
if i < self.current_player_index:
|
||||
# Removed player was before current: shift back
|
||||
self.current_player_index -= 1
|
||||
elif self.current_player_index >= len(self.players):
|
||||
# Removed player was at/after current and index is now OOB
|
||||
self.current_player_index = 0
|
||||
self._emit("player_left", player_id=player_id, reason=reason)
|
||||
return removed
|
||||
return None
|
||||
@ -815,8 +807,6 @@ class Game:
|
||||
def current_player(self) -> Optional[Player]:
|
||||
"""Get the player whose turn it currently is."""
|
||||
if self.players:
|
||||
if self.current_player_index >= len(self.players):
|
||||
self.current_player_index = self.current_player_index % len(self.players)
|
||||
return self.players[self.current_player_index]
|
||||
return None
|
||||
|
||||
|
||||
@ -313,6 +313,22 @@ async def handle_swap(data: dict, ctx: ConnectionContext, *, broadcast_game_stat
|
||||
reason=f"swapped {drawn_card.rank.value} into position {position}, replaced {old_rank}",
|
||||
)
|
||||
|
||||
# Broadcast reveal of old face-down card before state update
|
||||
if old_card_data:
|
||||
reveal_msg = {
|
||||
"type": "card_revealed",
|
||||
"player_id": ctx.player_id,
|
||||
"position": position,
|
||||
"card": old_card_data,
|
||||
}
|
||||
for pid, p in ctx.current_room.players.items():
|
||||
if not p.is_cpu and p.websocket:
|
||||
try:
|
||||
await p.websocket.send_json(reveal_msg)
|
||||
except Exception:
|
||||
pass
|
||||
await asyncio.sleep(1.0)
|
||||
|
||||
await broadcast_game_state(ctx.current_room)
|
||||
await asyncio.sleep(1.0)
|
||||
check_and_run_cpu_turn(ctx.current_room)
|
||||
|
||||
@ -431,7 +431,7 @@ async def _close_all_websockets():
|
||||
app = FastAPI(
|
||||
title="Golf Card Game",
|
||||
debug=config.DEBUG,
|
||||
version="3.2.0",
|
||||
version="3.1.6",
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
@ -943,18 +943,7 @@ if os.path.exists(client_path):
|
||||
return FileResponse(os.path.join(client_path, "index.html"))
|
||||
|
||||
# Mount static files for everything else (JS, CSS, SVG, etc.)
|
||||
# Wrap StaticFiles to reject WebSocket requests gracefully instead of
|
||||
# crashing with AssertionError (starlette asserts scope["type"] == "http").
|
||||
static_files = StaticFiles(directory=client_path)
|
||||
|
||||
async def safe_static_files(scope, receive, send):
|
||||
if scope["type"] != "http":
|
||||
if scope["type"] == "websocket":
|
||||
await send({"type": "websocket.close", "code": 1000})
|
||||
return
|
||||
await static_files(scope, receive, send)
|
||||
|
||||
app.mount("/", safe_static_files, name="static")
|
||||
app.mount("/", StaticFiles(directory=client_path), name="static")
|
||||
|
||||
|
||||
def run():
|
||||
|
||||
Loading…
Reference in New Issue
Block a user