diff --git a/Packages/Infrastructure/Sources/Public/Types/Analytics.swift b/Packages/Infrastructure/Sources/Public/Types/Analytics.swift index 4cccf66..2754ffb 100644 --- a/Packages/Infrastructure/Sources/Public/Types/Analytics.swift +++ b/Packages/Infrastructure/Sources/Public/Types/Analytics.swift @@ -60,18 +60,14 @@ public struct Analytics: Sendable { // MARK: Computed - /// The tracker script's attributes: the website id and reporting domains, then each enabled behavior flag; disabled flags are omitted. + /// The tracker script's attributes: the website id, the reporting domains when set, then each enabled behavior flag; disabled flags are omitted. public var attributes: [Attribute] { var attributes: [Attribute] = [ - .init("data-website-id", value: websiteID), - .init("data-domains", value: domains) + .init("data-website-id", value: websiteID) ] if !domains.isEmpty { - attributes.append(( - name: "data-domains", - value: domains - )) + attributes.append(.init("data-domains", value: domains)) } if excludeHash { diff --git a/Services/Website/Sources/App/Extensions/ConfigReader+Properties.swift b/Services/Website/Sources/App/Extensions/ConfigReader+Properties.swift index 7f115f4..f0b7399 100644 --- a/Services/Website/Sources/App/Extensions/ConfigReader+Properties.swift +++ b/Services/Website/Sources/App/Extensions/ConfigReader+Properties.swift @@ -240,6 +240,17 @@ package extension ConfigReader { ) } + /// The public origin the site is served at (scheme and host, no trailing slash), read from the `site.origin` key. + /// + /// The redirects and any absolute links derive from it, so a staging deployment can point it at itself — or leave it unset — without the + /// production origin leaking into its markup. + var siteOrigin: String { + string( + forKey: .Site.origin, + default: .Site.origin + ) + } + /// The directory the static files are served from, read from the `path.staticFiles` key. var staticFilesPath: String { string( diff --git a/Services/Website/Sources/Library/Public/Extensions/ConfigKey+Constants.swift b/Services/Website/Sources/Library/Public/Extensions/ConfigKey+Constants.swift index 5199d99..d33245c 100644 --- a/Services/Website/Sources/Library/Public/Extensions/ConfigKey+Constants.swift +++ b/Services/Website/Sources/Library/Public/Extensions/ConfigKey+Constants.swift @@ -98,4 +98,9 @@ extension ConfigKey { /// The configuration key for the `Strict-Transport-Security` header value (omitted when unset). public static let strictTransportSecurity: ConfigKey = "security.strictTransportSecurity" } + /// A namespace for the site configuration keys. + public enum Site { + /// The configuration key for the public origin the site is served at (scheme and host, no trailing slash). + public static let origin: ConfigKey = "site.origin" + } } diff --git a/Services/Website/Sources/Library/Public/Extensions/String+Constants.swift b/Services/Website/Sources/Library/Public/Extensions/String+Constants.swift index e95e120..3eda526 100644 --- a/Services/Website/Sources/Library/Public/Extensions/String+Constants.swift +++ b/Services/Website/Sources/Library/Public/Extensions/String+Constants.swift @@ -50,4 +50,12 @@ extension String { /// The website server's name. public static let name = "SiteWebsite" } + /// A namespace for the site string constants. + public enum Site { + /// The default public origin the site is served at (scheme and host, no trailing slash): empty, which leaves the HTTPS redirect off. + /// + /// A deployment that trusts the forwarded-protocol header without also setting `site.origin` would otherwise redirect at a host the + /// template guessed for it, and a `301` is cached for a long time. + public static let origin = "" + } }