71 lines
3.2 KiB
Go
71 lines
3.2 KiB
Go
package liveview
|
|
|
|
import (
|
|
"html/template"
|
|
"net/http"
|
|
"yovision.local/sense/app/sense/identity"
|
|
"yovision.local/sense/internal/platform"
|
|
)
|
|
|
|
type Module struct{ service *Service }
|
|
|
|
func NewModule(service *Service) *Module { return &Module{service: service} }
|
|
func (m *Module) Register(app *platform.App) {
|
|
app.Handle("GET /api/v1/liveview/routes", identity.Require(identity.PermissionMediaRead, http.HandlerFunc(m.routes)))
|
|
app.Handle("POST /api/v1/liveview/sessions", identity.Require(identity.PermissionMediaRead, http.HandlerFunc(m.create)))
|
|
app.Handle("GET /api/v1/liveview/sessions/{id}", identity.Require(identity.PermissionMediaRead, http.HandlerFunc(m.get)))
|
|
app.Handle("GET /api/v1/liveview/sessions/{id}/player", identity.RequireSameOriginFrame(identity.PermissionMediaRead, http.HandlerFunc(m.player)))
|
|
}
|
|
func (m *Module) routes(w http.ResponseWriter, r *http.Request) {
|
|
items, err := m.service.Routes(r.Context())
|
|
if err != nil {
|
|
platform.WriteError(w, err)
|
|
return
|
|
}
|
|
platform.WriteJSON(w, http.StatusOK, map[string]any{"items": items})
|
|
}
|
|
func (m *Module) create(w http.ResponseWriter, r *http.Request) {
|
|
var req struct {
|
|
RouteID string `json:"route_id"`
|
|
}
|
|
if err := platform.DecodeJSON(r, &req); err != nil {
|
|
platform.WriteError(w, err)
|
|
return
|
|
}
|
|
principal, _ := identity.PrincipalFromContext(r.Context())
|
|
session, err := m.service.Create(r.Context(), principal.UserID, req.RouteID)
|
|
if err != nil {
|
|
platform.WriteError(w, &platform.APIError{Status: http.StatusBadRequest, Code: "liveview_unavailable", Message: err.Error()})
|
|
return
|
|
}
|
|
platform.WriteJSON(w, http.StatusCreated, session)
|
|
}
|
|
func (m *Module) get(w http.ResponseWriter, r *http.Request) {
|
|
principal, _ := identity.PrincipalFromContext(r.Context())
|
|
session, err := m.service.Get(r.Context(), principal.UserID, r.PathValue("id"))
|
|
if err != nil {
|
|
platform.WriteError(w, &platform.APIError{Status: http.StatusGone, Code: "session_expired", Message: "播放会话已过期,请重新打开"})
|
|
return
|
|
}
|
|
platform.WriteJSON(w, http.StatusOK, session)
|
|
}
|
|
|
|
var playerTemplate = template.Must(template.New("player").Parse(`<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width"><style>html,body,iframe{width:100%;height:100%;margin:0;border:0;background:#111}</style></head><body><iframe src="{{.}}" allow="autoplay; fullscreen" title="Sense live video"></iframe></body></html>`))
|
|
|
|
func (m *Module) player(w http.ResponseWriter, r *http.Request) {
|
|
principal, _ := identity.PrincipalFromContext(r.Context())
|
|
target, err := m.service.EmbedURL(r.Context(), principal.UserID, r.PathValue("id"))
|
|
if err != nil {
|
|
http.Error(w, "播放会话已过期", http.StatusGone)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
w.Header().Set("Cache-Control", "no-store")
|
|
// This endpoint is the authenticated, same-origin wrapper loaded by the
|
|
// live-view page. Keep the global DENY policy everywhere else, and allow
|
|
// only Sense itself to embed this wrapper.
|
|
w.Header().Set("X-Frame-Options", "SAMEORIGIN")
|
|
w.Header().Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'self'; frame-src http: https:; style-src 'unsafe-inline'")
|
|
_ = playerTemplate.Execute(w, target)
|
|
}
|