Merge remote-tracking branch 'origin/master' into feat/pricing-plan
Project CI / Backend tests (pull_request) Failing after 48s
Project CI / Native shell tests (pull_request) Failing after 3m50s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 5m21s
Project CI / Frontend tests (pull_request) Failing after 47s
Project CI / AI game creator shell web tests (pull_request) Failing after 39s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m6s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m23s
Project CI / Repository checks (pull_request) Successful in 6m31s

# Conflicts:
#	docs/project-memory/shared-memory/decision-log.md
This commit is contained in:
2026-10-05 10:50:30 +08:00
165 changed files with 13827 additions and 1670 deletions
@@ -16,8 +16,8 @@ use crate::{
pub struct FrontendRuntimeConfigResponse {
pub image_editor_agent_sidebar_enabled: bool,
pub agc_template_library_enabled: bool,
/// 游戏发布灰度:运营未配置 `game-distribution:publish` 时对已登录作者默认开放,
/// 显式收紧后只有白名单/灰度命中的作者拿到 `true`;匿名一律 `false`(发布必须先登录)。
/// 游戏发布灰度:未配置 `game-distribution:publish` 或 `enabled=false` 时不开放;
/// 显式配置后按 enabled、白名单、标签和灰度比例判定,匿名一律 `false`(发布必须先登录)。
pub game_distribution_publish_enabled: bool,
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,43 @@
(function () {
function makeStorage() {
var values = Object.create(null);
return {
get length() {
return Object.keys(values).length;
},
key: function (index) {
return Object.keys(values)[index] || null;
},
getItem: function (key) {
key = String(key);
return Object.prototype.hasOwnProperty.call(values, key)
? values[key]
: null;
},
setItem: function (key, value) {
values[String(key)] = String(value);
},
removeItem: function (key) {
delete values[String(key)];
},
clear: function () {
values = Object.create(null);
},
};
}
function install(name) {
try {
Object.defineProperty(window, name, {
configurable: true,
enumerable: true,
value: makeStorage(),
});
} catch (_) {
// opaque-origin native storage may reject property replacement; keep the sandbox intact.
}
}
install('localStorage');
install('sessionStorage');
})();
+39 -1
View File
@@ -3,7 +3,7 @@ use std::sync::Mutex;
#[cfg(test)]
use std::sync::atomic::AtomicUsize;
use std::{
collections::BTreeMap,
collections::{BTreeMap, HashMap},
error::Error,
fmt,
sync::{
@@ -137,6 +137,12 @@ impl BackpressureState {
}
}
#[derive(Clone, Debug)]
pub(crate) struct GameDistributionPreviewSession {
pub(crate) version_id: String,
pub(crate) expires_at: OffsetDateTime,
}
#[derive(Clone)]
pub struct AppState(Arc<AppStateInner>);
@@ -307,6 +313,8 @@ pub struct AppStateInner {
ai_task_service: AiTaskService,
spacetime_client: SpacetimeClient,
external_api_auth_state: ExternalApiAuthState,
game_distribution_preview_sessions:
Arc<AsyncMutex<HashMap<String, GameDistributionPreviewSession>>>,
editor_project_state: EditorProjectState,
#[cfg(any())]
puzzle_gallery_cache: PuzzleGalleryCache,
@@ -698,6 +706,7 @@ impl AppState {
test_fail_editor_generation_enqueue: AtomicBool::new(false),
#[cfg(test)]
test_external_background_removal_enqueue: Arc::new(Mutex::new(None)),
game_distribution_preview_sessions: Arc::new(AsyncMutex::new(HashMap::new())),
oss_client,
project_snapshot_oss_client,
template_library_store,
@@ -759,6 +768,35 @@ impl AppState {
&self.refresh_cookie_config
}
pub(crate) async fn create_game_distribution_preview_session(
&self,
version_id: String,
expires_at: OffsetDateTime,
) -> String {
let token = uuid::Uuid::new_v4().to_string();
let now = OffsetDateTime::now_utc();
let mut sessions = self.game_distribution_preview_sessions.lock().await;
sessions.retain(|_, session| session.expires_at > now);
sessions.insert(
token.clone(),
GameDistributionPreviewSession {
version_id,
expires_at,
},
);
token
}
pub(crate) async fn get_game_distribution_preview_session(
&self,
token: &str,
) -> Option<GameDistributionPreviewSession> {
let now = OffsetDateTime::now_utc();
let mut sessions = self.game_distribution_preview_sessions.lock().await;
sessions.retain(|_, session| session.expires_at > now);
sessions.get(token).cloned()
}
pub fn http_request_permit_pools(&self) -> HttpRequestPermitPools {
self.http_request_permit_pools.clone()
}