Live video on the AT Protocol
1package api
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "net/http"
8 "strconv"
9 "strings"
10
11 "github.com/julienschmidt/httprouter"
12 "stream.place/streamplace/pkg/aqtime"
13 apierrors "stream.place/streamplace/pkg/errors"
14 "stream.place/streamplace/pkg/log"
15)
16
17const BRANCH = "latest"
18
19func formatRequest(r *http.Request) string {
20 // Create return string
21 var request []string
22 // Add the request string
23 url := fmt.Sprintf("%v %v %v", r.Method, r.URL, r.Proto)
24 request = append(request, url)
25 // Add the host
26 request = append(request, fmt.Sprintf("Host: %v", r.Host))
27 // Loop through headers
28 for name, headers := range r.Header {
29 name = strings.ToLower(name)
30 for _, h := range headers {
31 request = append(request, fmt.Sprintf("%v: %v", name, h))
32 }
33 }
34
35 // If this is a POST, add post data
36 if r.Method == "POST" {
37 _ = r.ParseForm()
38 request = append(request, "\n")
39 request = append(request, r.Form.Encode())
40 }
41 // Return the request as a string
42 return strings.Join(request, "\n")
43}
44
45type MacManifestUpdateTo struct {
46 Version string `json:"version"`
47 PubDate string `json:"pub_date"`
48 Notes string `json:"notes"`
49 Name string `json:"name"`
50 URL string `json:"url"`
51}
52
53type MacManifestRelease struct {
54 Version string `json:"version"`
55 UpdateTo MacManifestUpdateTo `json:"updateTo"`
56}
57
58type MacManifest struct {
59 CurrentRelease string `json:"currentRelease"`
60 Releases []MacManifestRelease `json:"releases"`
61}
62
63func (a *StreamplaceAPI) HandleDesktopUpdates(ctx context.Context) httprouter.Handle {
64 mac := a.HandleMacDesktopUpdates(ctx)
65 win := a.HandleWindowsDesktopUpdates(ctx)
66 return func(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
67 platform := params.ByName("platform")
68 if platform == "darwin" {
69 mac(w, req, params)
70 } else if platform == "windows" {
71 win(w, req, params)
72 } else {
73 apierrors.WriteHTTPBadRequest(w, fmt.Sprintf("unsupported platform: %s", platform), nil)
74 }
75 }
76}
77
78func (a *StreamplaceAPI) HandleMacDesktopUpdates(ctx context.Context) httprouter.Handle {
79 return func(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
80 platform := params.ByName("platform")
81 architecture := params.ByName("architecture")
82 clientVersion := params.ByName("version")
83 clientBuildTime := params.ByName("buildTime")
84 file := params.ByName("file")
85 if file != "RELEASES.json" {
86 apierrors.WriteHTTPNotFound(w, fmt.Sprintf("unknown file: %s", file), nil)
87 return
88 }
89 log.Log(ctx, formatRequest(req),
90 "platform", platform,
91 "architecture", architecture,
92 "clientVersion", clientVersion,
93 "clientBuildTime", clientBuildTime,
94 )
95 clientBuildSec, err := strconv.ParseInt(clientBuildTime, 10, 64)
96 if err != nil {
97 apierrors.WriteHTTPBadRequest(w, "build time must be a number", err)
98 return
99 }
100 var mani MacManifest
101 if clientBuildSec >= a.CLI.Build.BuildTime {
102 // client is newer or the same as server
103 mani = MacManifest{
104 CurrentRelease: clientVersion,
105 Releases: []MacManifestRelease{},
106 }
107 } else {
108 // we're newer than the client, tell it to update
109 aqt := aqtime.FromSec(a.CLI.Build.BuildTime)
110 // sigh. but at least it's only for dev versions.
111 serverVersionZ := strings.ReplaceAll(a.CLI.Build.Version, "-", "-z")
112 updateTo := MacManifestUpdateTo{
113 Version: serverVersionZ,
114 PubDate: aqt.String(),
115 Notes: fmt.Sprintf("Streamplace %s", clientVersion),
116 Name: fmt.Sprintf("Streamplace %s", clientVersion),
117 URL: fmt.Sprintf("https://%s/dl/%s/streamplace-desktop-%s-%s.zip", req.Host, BRANCH, platform, architecture),
118 }
119
120 mani = MacManifest{
121 CurrentRelease: serverVersionZ,
122 Releases: []MacManifestRelease{
123 {
124 Version: clientVersion,
125 UpdateTo: updateTo,
126 },
127 // todo: this is straight from their example, but why does this version upgrade to itself...?
128 {
129 Version: serverVersionZ,
130 UpdateTo: updateTo,
131 },
132 },
133 }
134 }
135
136 w.Header().Set("content-type", "application/json")
137 w.WriteHeader(200)
138 bs, err := json.Marshal(mani)
139 if err != nil {
140 log.Log(ctx, "error marshaling mac update manifest", "error", err)
141 }
142 if _, err := w.Write(bs); err != nil {
143 log.Error(ctx, "error writing response", "error", err)
144 }
145 }
146}
147
148func (a *StreamplaceAPI) HandleWindowsDesktopUpdates(ctx context.Context) httprouter.Handle {
149 return func(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
150 platform := params.ByName("platform")
151 architecture := params.ByName("architecture")
152 clientVersion := params.ByName("version")
153 clientBuildTime := params.ByName("buildTime")
154 file := params.ByName("file")
155 log.Log(ctx, formatRequest(req),
156 "platform", platform,
157 "architecture", architecture,
158 "clientVersion", clientVersion,
159 "clientBuildTime", clientBuildTime,
160 )
161
162 clientBuildSec, err := strconv.ParseInt(clientBuildTime, 10, 64)
163 if err != nil {
164 apierrors.WriteHTTPBadRequest(w, "build time must be a number", err)
165 return
166 }
167
168 files, err := a.getGitlabPackage(BRANCH)
169 if err != nil {
170 apierrors.WriteHTTPInternalServerError(w, "could not find gitlab package", err)
171 return
172 }
173
174 var gitlabFile *GitlabFile
175 for _, f := range files {
176 if f.Extension == "nupkg" {
177 gitlabFile = &f
178 break
179 }
180 }
181 if gitlabFile == nil {
182 apierrors.WriteHTTPInternalServerError(w, "could not find gitlab package", err)
183 return
184 }
185
186 if file == "RELEASES" {
187 if clientBuildSec >= a.CLI.Build.BuildTime {
188 // client is newer or the same as server
189 fmt.Fprintf(w, "0000000000000000000000000000000000000000 streamplace_desktop-%s-full.nupkg 1", clientVersion)
190 return
191 }
192 fmt.Fprintf(w, "%s streamplace_desktop-%s-full.nupkg %d", gitlabFile.SHA1, gitlabFile.Version, gitlabFile.Size)
193 return
194 }
195 http.Redirect(w, req, gitlabFile.URL(), http.StatusTemporaryRedirect)
196 }
197}