0.2.45 - Advanced analytics, logout old versions
This commit is contained in:
@@ -17,6 +17,12 @@ import (
|
||||
// speaks, next to the build's own version.
|
||||
const ProtocolVersion = 1
|
||||
|
||||
// forcedUpdateFloor retires builds whose update behaviour is no longer reliable enough
|
||||
// to leave optional. The floor only takes effect once an enabled policy points at this
|
||||
// version (or a newer one) and carries a download URL, so deploying the gateway before
|
||||
// publishing the APK cannot lock televisions out.
|
||||
const forcedUpdateFloor = "0.2.44"
|
||||
|
||||
// updatePolicyCache keeps the policy in memory. It is read on every home request, and a
|
||||
// database round trip per home load to answer "nothing to say" would be wasteful.
|
||||
type updatePolicyCache struct {
|
||||
@@ -90,13 +96,54 @@ func compatibilityFor(r *http.Request) (bool, string) {
|
||||
return true, ""
|
||||
}
|
||||
|
||||
// effectiveUpdatePolicy applies the server-owned emergency floor without weakening a
|
||||
// higher minimum the operator has already selected.
|
||||
func effectiveUpdatePolicy(policy appupdate.Policy) appupdate.Policy {
|
||||
if !policy.Enabled || strings.TrimSpace(policy.DownloadURL) == "" ||
|
||||
appupdate.CompareVersions(policy.LatestVersion, forcedUpdateFloor) < 0 {
|
||||
return policy
|
||||
}
|
||||
if strings.TrimSpace(policy.MinimumVersion) == "" ||
|
||||
appupdate.CompareVersions(policy.MinimumVersion, forcedUpdateFloor) < 0 {
|
||||
policy.MinimumVersion = forcedUpdateFloor
|
||||
}
|
||||
return policy
|
||||
}
|
||||
|
||||
func (s *Server) updateDecision(r *http.Request) appupdate.Decision {
|
||||
return appupdate.Decide(effectiveUpdatePolicy(s.updatePolicy.get()), clientVersion(r))
|
||||
}
|
||||
|
||||
// mustRetireForUpdate is narrower than "mandatory": an operator may temporarily force a
|
||||
// newer release without wanting every otherwise supported session destroyed. Only builds
|
||||
// below the permanent compatibility floor are signed out.
|
||||
func mustRetireForUpdate(decision appupdate.Decision, version string) bool {
|
||||
return decision.Status == appupdate.StatusMandatory && decision.DownloadURL != "" &&
|
||||
appupdate.CompareVersions(version, forcedUpdateFloor) < 0
|
||||
}
|
||||
|
||||
// requireSupportedClient prevents a retired build from signing straight back in after
|
||||
// the authenticated gate has removed its old session. Its public update check remains
|
||||
// available and will keep returning the actionable mandatory verdict.
|
||||
func (s *Server) requireSupportedClient(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
decision := s.updateDecision(r)
|
||||
if mustRetireForUpdate(decision, clientVersion(r)) {
|
||||
w.Header().Set("X-Memby-Update-Required", decision.Version)
|
||||
writeJSON(w, http.StatusUpgradeRequired, decision)
|
||||
return
|
||||
}
|
||||
next(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// handleUpdate answers the client's version check.
|
||||
//
|
||||
// Its own public endpoint rather than a field on /v1/home: update policy belongs to the
|
||||
// app build, not a viewer or login. The only client input is its build-version header and
|
||||
// the answer comes from memory, so checking it never reads or mutates a user session.
|
||||
// app build, not a viewer or login. The verdict comes from memory; when a bearer token is
|
||||
// present the route resolves it only to attribute an offered update to the affected viewer.
|
||||
func (s *Server) handleUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
decision := appupdate.Decide(s.updatePolicy.get(), clientVersion(r))
|
||||
decision := s.updateDecision(r)
|
||||
// Only a verdict that asks a television to do something is worth a line. Every TV
|
||||
// checks on every launch, and "nothing to say" logged each time would bury the
|
||||
// launch where an update was actually offered — or forced.
|
||||
|
||||
Reference in New Issue
Block a user