mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 13:05:32 +09:00
fix(app-proxy): tell the web client its release channel (#3448)
This commit is contained in:
1 parent
83bb9d89c8
commit
d514501d57
3 files changed
+81
-1
No files matched your search
@@ -24,6 +24,16 @@ if (!parsedBuildInfo.success) {
|
|||||||
}
|
}
|
||||||
const buildInfo = parsedBuildInfo.output;
|
const buildInfo = parsedBuildInfo.output;
|
||||||
|
|
||||||
|
const SERVED_RELEASE_CHANNEL_META_SELECTOR = 'meta[name="fluxer-release-channel"]';
|
||||||
|
|
||||||
|
function servedReleaseChannel(): 'stable' | 'canary' | null {
|
||||||
|
if (typeof document === 'undefined') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const value = document.querySelector<HTMLMetaElement>(SERVED_RELEASE_CHANNEL_META_SELECTOR)?.content;
|
||||||
|
return value === 'stable' || value === 'canary' ? value : null;
|
||||||
|
}
|
||||||
|
|
||||||
function clientReleaseChannel(value: (typeof buildInfo)['PUBLIC_RELEASE_CHANNEL']): 'stable' | 'canary' {
|
function clientReleaseChannel(value: (typeof buildInfo)['PUBLIC_RELEASE_CHANNEL']): 'stable' | 'canary' {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case 'stable':
|
case 'stable':
|
||||||
@@ -36,5 +46,5 @@ function clientReleaseChannel(value: (typeof buildInfo)['PUBLIC_RELEASE_CHANNEL'
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
PUBLIC_BUILD_VERSION: buildInfo.PUBLIC_BUILD_VERSION,
|
PUBLIC_BUILD_VERSION: buildInfo.PUBLIC_BUILD_VERSION,
|
||||||
PUBLIC_RELEASE_CHANNEL: clientReleaseChannel(buildInfo.PUBLIC_RELEASE_CHANNEL),
|
PUBLIC_RELEASE_CHANNEL: servedReleaseChannel() ?? clientReleaseChannel(buildInfo.PUBLIC_RELEASE_CHANNEL),
|
||||||
} as const;
|
} as const;
|
||||||
@@ -362,6 +362,7 @@ pub struct AppProxyConfig {
|
|||||||
pub csp: CspConfig,
|
pub csp: CspConfig,
|
||||||
pub same_origin_hosts: Vec<String>,
|
pub same_origin_hosts: Vec<String>,
|
||||||
pub manifest_scope_extensions: Vec<String>,
|
pub manifest_scope_extensions: Vec<String>,
|
||||||
|
pub release_channel: Option<&'static str>,
|
||||||
pub self_hosted: bool,
|
pub self_hosted: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -477,11 +478,31 @@ impl AppProxyConfig {
|
|||||||
"FLUXER_APP_PROXY_MANIFEST_SCOPE_EXTENSIONS",
|
"FLUXER_APP_PROXY_MANIFEST_SCOPE_EXTENSIONS",
|
||||||
&cfg::read_env("FLUXER_APP_PROXY_MANIFEST_SCOPE_EXTENSIONS", ""),
|
&cfg::read_env("FLUXER_APP_PROXY_MANIFEST_SCOPE_EXTENSIONS", ""),
|
||||||
),
|
),
|
||||||
|
release_channel: parse_release_channel(
|
||||||
|
"RELEASE_CHANNEL",
|
||||||
|
&cfg::read_env("RELEASE_CHANNEL", ""),
|
||||||
|
),
|
||||||
self_hosted: cfg::read_bool_env("FLUXER_SELF_HOSTED", false),
|
self_hosted: cfg::read_bool_env("FLUXER_SELF_HOSTED", false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_release_channel(name: &'static str, raw: &str) -> Option<&'static str> {
|
||||||
|
match raw.trim().to_ascii_lowercase().as_str() {
|
||||||
|
"" => None,
|
||||||
|
"stable" => Some("stable"),
|
||||||
|
"canary" => Some("canary"),
|
||||||
|
_ => {
|
||||||
|
warn_invalid(&InvalidAppProxyEnvironmentError::new(
|
||||||
|
name,
|
||||||
|
raw,
|
||||||
|
"stable or canary",
|
||||||
|
));
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_manifest_scope_extensions(name: &'static str, raw: &str) -> Vec<String> {
|
fn parse_manifest_scope_extensions(name: &'static str, raw: &str) -> Vec<String> {
|
||||||
let mut origins: Vec<String> = Vec::new();
|
let mut origins: Vec<String> = Vec::new();
|
||||||
for value in raw
|
for value in raw
|
||||||
@@ -591,6 +612,21 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn release_channel_accepts_only_stable_or_canary() {
|
||||||
|
assert_eq!(
|
||||||
|
parse_release_channel("TEST_CHANNEL", " Stable "),
|
||||||
|
Some("stable")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parse_release_channel("TEST_CHANNEL", "canary"),
|
||||||
|
Some("canary")
|
||||||
|
);
|
||||||
|
assert_eq!(parse_release_channel("TEST_CHANNEL", ""), None);
|
||||||
|
assert_eq!(parse_release_channel("TEST_CHANNEL", "development"), None);
|
||||||
|
assert_eq!(parse_release_channel("TEST_CHANNEL", "\"><script>"), None);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn manifest_scope_extensions_are_normalised_and_deduplicated() {
|
fn manifest_scope_extensions_are_normalised_and_deduplicated() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ const SHARED_SHELL_CACHE_CONTROL: &str = "public, max-age=0, s-maxage=1";
|
|||||||
const CSP_NONCE_PLACEHOLDER_ATTRIBUTE: &str = r#" nonce="{{CSP_NONCE_PLACEHOLDER}}""#;
|
const CSP_NONCE_PLACEHOLDER_ATTRIBUTE: &str = r#" nonce="{{CSP_NONCE_PLACEHOLDER}}""#;
|
||||||
const MEDIA_PRECONNECT_TAG: &str = r#"<link rel="preconnect" href="{{MEDIA_ENDPOINT}}">"#;
|
const MEDIA_PRECONNECT_TAG: &str = r#"<link rel="preconnect" href="{{MEDIA_ENDPOINT}}">"#;
|
||||||
const SAME_ORIGIN_API_META_TAG: &str = r#"<meta name="fluxer-api-origin" content="self">"#;
|
const SAME_ORIGIN_API_META_TAG: &str = r#"<meta name="fluxer-api-origin" content="self">"#;
|
||||||
|
const RELEASE_CHANNEL_META_NAME: &str = "fluxer-release-channel";
|
||||||
const HEAD_CLOSE_TAG: &str = "</head>";
|
const HEAD_CLOSE_TAG: &str = "</head>";
|
||||||
const TITLE_START: &str = "<title";
|
const TITLE_START: &str = "<title";
|
||||||
const TITLE_END: &str = "</title>";
|
const TITLE_END: &str = "</title>";
|
||||||
@@ -204,6 +205,10 @@ async fn serve_spa_index(state: &AppState, headers: &HeaderMap, request_path: &s
|
|||||||
} else {
|
} else {
|
||||||
raw_html
|
raw_html
|
||||||
};
|
};
|
||||||
|
let raw_html = match state.config.release_channel {
|
||||||
|
Some(channel) => mark_release_channel(&raw_html, channel),
|
||||||
|
None => raw_html,
|
||||||
|
};
|
||||||
|
|
||||||
let dev_buster = should_bust_dev_assets.then(current_dev_asset_cache_buster);
|
let dev_buster = should_bust_dev_assets.then(current_dev_asset_cache_buster);
|
||||||
let html = match render_spa_document(
|
let html = match render_spa_document(
|
||||||
@@ -260,6 +265,17 @@ fn mark_same_origin_api(html: &str) -> String {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn mark_release_channel(html: &str, channel: &str) -> String {
|
||||||
|
html.replacen(
|
||||||
|
HEAD_CLOSE_TAG,
|
||||||
|
&format!(
|
||||||
|
r#"<meta name="{RELEASE_CHANNEL_META_NAME}" content="{channel}">
|
||||||
|
{HEAD_CLOSE_TAG}"#
|
||||||
|
),
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct SpaDocumentSizeLimitError {
|
struct SpaDocumentSizeLimitError {
|
||||||
attempted_bytes: usize,
|
attempted_bytes: usize,
|
||||||
@@ -1166,6 +1182,24 @@ mod tests {
|
|||||||
assert!(!served.contains(SAME_ORIGIN_API_META_TAG));
|
assert!(!served.contains(SAME_ORIGIN_API_META_TAG));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn the_configured_release_channel_is_marked_in_the_document() {
|
||||||
|
let mut state = spa_state_serving(SHIPPED_APP_SHELL);
|
||||||
|
let served = read_document(serve_spa_index(&state, &HeaderMap::new(), "/").await).await;
|
||||||
|
assert!(!served.contains(RELEASE_CHANNEL_META_NAME));
|
||||||
|
|
||||||
|
for channel in ["stable", "canary"] {
|
||||||
|
Arc::make_mut(&mut state.config).release_channel = Some(channel);
|
||||||
|
let response = serve_spa_index(&state, &HeaderMap::new(), "/").await;
|
||||||
|
let policy = policy_of(&response);
|
||||||
|
let served = read_document(response).await;
|
||||||
|
let tag = format!(r#"<meta name="fluxer-release-channel" content="{channel}">"#);
|
||||||
|
assert_eq!(served.matches(&tag).count(), 1);
|
||||||
|
assert!(served.find(&tag).unwrap() < served.find("</head>").unwrap());
|
||||||
|
assert_every_inline_script_is_granted(&served, &policy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn the_configured_media_endpoint_is_preconnected() {
|
async fn the_configured_media_endpoint_is_preconnected() {
|
||||||
let mut state = assemble_spa_state(
|
let mut state = assemble_spa_state(
|
||||||
|
|||||||
Reference in new issue
Block a user