· 6 years ago · Apr 30, 2020, 10:20 AM
1package main
2
3import (
4 "bytes"
5 "context"
6 "crypto/sha256"
7 "encoding/hex"
8 "encoding/json"
9 "fmt"
10 "io/ioutil"
11 "net/http"
12 "time"
13
14 "github.com/dgrijalva/jwt-go"
15)
16
17const (
18 GQLFuncByTime = "GetEvents(appId: \"%s\", after: { startEventTimeInMsecs: %.1f }, count: %d)"
19 GQLFuncByCursor = "GetEvents(appId: \"%s\", after: { nextCursor: \"%s\" }, count: %d )"
20 GQLSelectFields = `
21 events {
22 eventType
23 externalId
24 snapId
25 eventTimeInMsecs
26 snap {
27 captureTimeInMsecs
28 expirationTimeInMsecs
29 media {
30 mediaType
31 orientationType
32 isFrontFacing
33 durationSecs
34 isInfiniteDuration
35 encryptionKey
36 encryptionIv
37 prefixUrl
38 previewUrl
39 mediaUrl
40 }
41 }
42 }
43 nextCursor`
44)
45
46type SnapEvents struct {
47 Data struct {
48 GetEvents struct {
49 Events []struct {
50 EventType string
51 ExternalID string
52 SnapID string
53 EventTimeInMSecs float64
54 Snap struct {
55 CaptureTimeInMSecs float64
56 ExpirationTimeInMSecs float64
57 Media struct {
58 MediaType string
59 OrientationType string
60 IsFrontFacing bool
61 DurationSecs float64
62 IsFiniteDuration bool
63 EncryptionKey string
64 EncryptionIV string
65 PrefixURL string
66 PreviewURL string
67 MediaURL string
68 }
69 }
70 }
71 NextCursor string
72 }
73 }
74}
75
76type SnapError struct {
77 Errors []struct {
78 Message string
79 Locations []struct {
80 Line int
81 Column int
82 }
83 }
84}
85
86func MakeS2SAuthToken(body []byte, key []byte, kid string, issuer string,
87 audience string, issueAt int64, expireAt int64) (s2sToken string, err error) {
88 // The body content needs to be hashed and encoded to
89 // hexadecimal as a string before assigned it to the JWT
90 hash := sha256.New()
91 hash.Write(body)
92 hashedHexBody := hex.EncodeToString(hash.Sum(nil))
93
94 // Generate unsigned JWT
95 token := jwt.New(jwt.SigningMethodES256)
96 payload := jwt.MapClaims{
97 "iss": issuer,
98 "aud": audience,
99 "iat": issueAt,
100 "exp": expireAt,
101 "hash": hashedHexBody,
102 }
103 token.Claims = payload
104 token.Header["kid"] = kid
105 // Sign the JWT
106 privateKey, _ := jwt.ParseECPrivateKeyFromPEM(key)
107 signedToken, _ := token.SignedString(privateKey)
108 return fmt.Sprintf("Bearer %s", signedToken), nil
109}
110
111func FetchSnapEvents(cursor string, startAt time.Time, count uint) {
112 // Generate GQL query
113 var queryFuncStr string
114 appID := ""
115 if cursor != "" {
116 queryFuncStr = fmt.Sprintf(GQLFuncByCursor, appID, cursor,
117 count)
118 } else {
119 // queryFuncStr = fmt.Sprintf(GQLFuncByTime,
120 // appID,
121 // float64(startAt.Unix()*1000), count)
122 queryFuncStr = fmt.Sprintf(
123 GQLFuncByTime,
124 appID,
125 float64(1588225409000),
126 count)
127 }
128
129 query := fmt.Sprintf("{ %s { %s }}", queryFuncStr, GQLSelectFields)
130
131 // Embed GQL in JSON
132 jsonQuery := map[string]string{
133 "query": query,
134 }
135 jsonQueryData, _ := json.Marshal(jsonQuery)
136
137 // fmt.Println(string(jsonQueryData))
138
139 req, err := http.NewRequest("POST",
140 "https://api.snapkit.com/v1/stories/app",
141 bytes.NewReader(jsonQueryData))
142 req.Header.Add("Accept", "application/json; charset=UTF-8")
143
144 privateKeyData, _ := ioutil.ReadFile("")
145
146 kid := ""
147 issuer := ""
148 audience := "API:StoryKit:AppStories"
149 now := time.Now()
150
151 authToken, err := MakeS2SAuthToken(jsonQueryData, privateKeyData, kid,
152 issuer, audience,
153 now.Unix(), now.AddDate(0, 1, 0).Unix())
154
155 if err != nil {
156 fmt.Printf("Error generating S2S auth token, %v", err)
157 return
158 }
159
160 req.Header.Add("X-Snap-Kit-S2S-Auth", authToken)
161 req = req.WithContext(context.Background())
162 // Send the request
163 client := &http.Client{}
164 res, err := client.Do(req)
165 if err != nil {
166 fmt.Printf("Error calling Snap Events API, %v", err)
167 return
168 }
169
170 fmt.Printf("RESPONSE: Status %s", res.Status)
171 body, err := ioutil.ReadAll(res.Body)
172 if err != nil {
173 fmt.Printf("request to fetch snap events failed, %v", err)
174 }
175
176 // Parse result
177 var queryResult SnapEvents
178 var queryError SnapError
179 // 1. On success call, the API returns a JSON of type SnapEventPayload
180 // 2. On failed call, the API returns a JSON of type ErrorPayload
181 // So we do a quick test for the data attribute using interface{} as the type
182 err = json.Unmarshal(body, &queryError)
183 _, ok := err.(*json.UnmarshalTypeError)
184 // The snap events query returns nil error when there's no error and the
185 // connection states query returns an empty slice [].
186 queryIsError := (queryError.Errors != nil && len(queryError.Errors) > 0) || ok
187 if !queryIsError {
188 // Parse Snap Event
189 json.Unmarshal(body, &queryResult)
190 // Log
191 fmt.Println()
192 fmt.Printf("RESPONSE: Result: %+v", queryResult)
193 }
194}
195
196func main() {
197 FetchSnapEvents("", time.Now(), 10)
198}