5
0
Fork 0

State transmission. Closes issue #2

master
UnicodingUnicorn 2019-03-08 19:34:42 +08:00
parent cbf2cc6d0c
commit 1f3a80c2e2
2 changed files with 70 additions and 9 deletions

View File

@ -13,6 +13,17 @@ Supply environment variables by either exporting them or editing ```.env```.
| LISTEN | Host and port number to listen on | :8080 | | LISTEN | Host and port number to listen on | :8080 |
| REDIS | Host and port of redis | :6379 | | REDIS | Host and port of redis | :6379 |
## Status Codes
The system supports arbitrary status codes. However, in the interest of standardisation, a system of codes (based on Skype statuses) is listed here:
| Code | Description |
| ---- | ----------- |
| 0 | Active |
| 1 | Away |
| 2 | Do not disturb |
| 3 | Invisible |
## API ## API
### Subscribe User ### Subscribe User
@ -21,16 +32,25 @@ Supply environment variables by either exporting them or editing ```.env```.
GET /subscribe/:userid/client/:clientid GET /subscribe/:userid/client/:clientid
``` ```
Subscribe to a user. Every time a user pings this service, the time will be sent to all subscribed users. Upon subscription, if it exists, the last cached time of the target user will be pushed immediately to the stream. Subscribe to a user. Every time a user pings this service, the time will be sent to all subscribed users. Upon subscription, if it exists, the last cached ping of the target user will be pushed immediately to the stream.
```js ```js
const es = new EventSource(`${host}/subscribe/${user}/client/${device}`); const es = new EventSource(`${host}/subscribe/${user}/client/${device}`);
es.onmessage = (e) => { es.onmessage = (e) => {
const timestamp = e.data; const timestamp = e.data;
// Do whatever with the timestamp // Do whatever with the ping data
}; };
``` ```
Ping data:
```json
{
"time": "<UTC epoch timestamp>",
"status": "<status code>"
}
```
#### URL Params #### URL Params
| Name | Type | Description | Required | | Name | Type | Description | Required |
@ -58,6 +78,12 @@ Ping the server.
| ---- | ----------- | | ---- | ----------- |
| X-User-Claim | Stringified user claim, populated by `backend-auth` called by `traefik` | | X-User-Claim | Stringified user claim, populated by `backend-auth` called by `traefik` |
#### Body
| Name | Type | Description |
| ---- | ---- | ----------- |
| status | String | Status code |
#### Success Response (200 OK) #### Success Response (200 OK)
Empty body. Empty body.

49
main.go
View File

@ -22,6 +22,11 @@ type RawClient struct {
ClientId string `json:"clientid"` ClientId string `json:"clientid"`
} }
type Ping struct {
Time string `json:"time"`
Status string `json:"status"`
}
var connections map[RawClient][]chan []byte var connections map[RawClient][]chan []byte
var redisClient *redis.Client var redisClient *redis.Client
@ -76,10 +81,18 @@ func Subscribe(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
ticker := time.NewTicker(25 * time.Second) ticker := time.NewTicker(25 * time.Second)
// Push cached value (if it exists) to the connection // Push cached value (if it exists) to the connection
cachedTime, err := redisClient.Get(client.UserId + client.ClientId).Result() cachedTime, err1 := redisClient.HGet(client.UserId + client.ClientId, "time").Result()
if err == nil { cachedStatus, err2 := redisClient.HGet(client.UserId + client.ClientId, "status").Result()
fmt.Fprintf(w, "data: %s\n\n", cachedTime) if err1 == nil && err2 == nil {
flusher.Flush() ping := Ping {
Time: cachedTime,
Status: cachedStatus,
}
pingBytes, err := json.Marshal(&ping)
if err == nil {
fmt.Fprintf(w, "data: %s\n\n", pingBytes)
flusher.Flush()
}
} }
for { for {
@ -97,6 +110,9 @@ func Subscribe(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
} }
} }
type PostTimeRequest struct {
Status string `json:"status"`
}
func PostTime(w http.ResponseWriter, r *http.Request, p httprouter.Params) { func PostTime(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
ua := r.Header.Get("X-User-Claim") ua := r.Header.Get("X-User-Claim")
if ua == "" { if ua == "" {
@ -112,12 +128,31 @@ func PostTime(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
return return
} }
time := []byte(strconv.FormatInt(time.Now().UTC().Unix(), 10)) // UTC Epoch Time in []byte decoder := json.NewDecoder(r.Body)
var ptRequest PostTimeRequest
err = decoder.Decode(&ptRequest)
if err != nil {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
ping := Ping {
Time: strconv.FormatInt(time.Now().UTC().Unix(), 10), // UTC Epoch time,
Status: ptRequest.Status,
}
key := client.UserId + client.ClientId key := client.UserId + client.ClientId
_ = redisClient.Set(key, time, 0) _ = redisClient.HSet(key, "time", []byte(ping.Time))
_ = redisClient.HSet(key, "status", []byte(ping.Status))
pingBytes, err := json.Marshal(&ping)
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
for _, connection := range connections[client] { for _, connection := range connections[client] {
connection <- time connection <- pingBytes
} }
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)