working v1
This commit is contained in:
217
cmd/test-websocket-direct/main.go
Normal file
217
cmd/test-websocket-direct/main.go
Normal file
@@ -0,0 +1,217 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
const (
|
||||
userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
||||
appVersion = "4364"
|
||||
wsURL = "wss://engine.kosmi.io/gql-ws"
|
||||
)
|
||||
|
||||
func main() {
|
||||
token := flag.String("token", "", "JWT token from captured session")
|
||||
roomID := flag.String("room", "@hyperspaceout", "Room ID")
|
||||
flag.Parse()
|
||||
|
||||
if *token == "" {
|
||||
fmt.Fprintf(os.Stderr, "Error: -token is required\n")
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s -token <JWT_TOKEN> -room <ROOM_ID>\n", os.Args[0])
|
||||
fmt.Fprintf(os.Stderr, "\nTo get a token, run: ./capture-auth and extract it from auth-data.json\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("🔌 Testing direct WebSocket connection with JWT token")
|
||||
fmt.Printf(" Token: %s...\n", truncate(*token, 50))
|
||||
fmt.Printf(" Room: %s\n\n", *roomID)
|
||||
|
||||
// Connect WebSocket
|
||||
fmt.Println("1️⃣ Connecting to WebSocket...")
|
||||
conn, err := connectWebSocket(*token)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "❌ Failed to connect: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer conn.Close()
|
||||
fmt.Println("✅ WebSocket connected!")
|
||||
|
||||
// Wait for connection_ack
|
||||
fmt.Println("\n2️⃣ Waiting for connection_ack...")
|
||||
if err := waitForAck(conn); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "❌ Failed to receive ack: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("✅ Received connection_ack!")
|
||||
|
||||
// Subscribe to messages
|
||||
fmt.Printf("\n3️⃣ Subscribing to messages in room %s...\n", *roomID)
|
||||
if err := subscribeToMessages(conn, *roomID); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "❌ Failed to subscribe: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("✅ Subscribed!")
|
||||
|
||||
// Listen for messages
|
||||
fmt.Println("\n👂 Listening for messages (press Ctrl+C to exit)...\n")
|
||||
|
||||
messageCount := 0
|
||||
for {
|
||||
var msg map[string]interface{}
|
||||
if err := conn.ReadJSON(&msg); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error reading message: %v\n", err)
|
||||
break
|
||||
}
|
||||
|
||||
msgType, _ := msg["type"].(string)
|
||||
|
||||
switch msgType {
|
||||
case "next":
|
||||
payload, _ := msg["payload"].(map[string]interface{})
|
||||
data, _ := payload["data"].(map[string]interface{})
|
||||
|
||||
if newMessage, ok := data["newMessage"].(map[string]interface{}); ok {
|
||||
messageCount++
|
||||
body, _ := newMessage["body"].(string)
|
||||
user, _ := newMessage["user"].(map[string]interface{})
|
||||
username, _ := user["displayName"].(string)
|
||||
if username == "" {
|
||||
username, _ = user["username"].(string)
|
||||
}
|
||||
timestamp, _ := newMessage["time"].(float64)
|
||||
|
||||
t := time.Unix(int64(timestamp), 0)
|
||||
fmt.Printf("[%s] %s: %s\n", t.Format("15:04:05"), username, body)
|
||||
}
|
||||
case "complete":
|
||||
id, _ := msg["id"].(string)
|
||||
fmt.Printf(" [Subscription %s completed]\n", id)
|
||||
case "error":
|
||||
fmt.Printf(" ⚠️ Error: %+v\n", msg)
|
||||
default:
|
||||
// Ignore other message types (ka, etc)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("\n📊 Total messages received: %d\n", messageCount)
|
||||
}
|
||||
|
||||
func connectWebSocket(token string) (*websocket.Conn, error) {
|
||||
dialer := websocket.Dialer{
|
||||
Subprotocols: []string{"graphql-ws"},
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
}
|
||||
|
||||
headers := http.Header{
|
||||
"Origin": []string{"https://app.kosmi.io"},
|
||||
"User-Agent": []string{userAgent},
|
||||
"Sec-WebSocket-Protocol": []string{"graphql-ws"},
|
||||
"Cache-Control": []string{"no-cache"},
|
||||
"Pragma": []string{"no-cache"},
|
||||
}
|
||||
|
||||
conn, resp, err := dialer.Dial(wsURL, headers)
|
||||
if err != nil {
|
||||
if resp != nil {
|
||||
fmt.Printf(" Response status: %d\n", resp.StatusCode)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Send connection_init with token
|
||||
uaEncoded := base64.StdEncoding.EncodeToString([]byte(userAgent))
|
||||
|
||||
initMsg := map[string]interface{}{
|
||||
"type": "connection_init",
|
||||
"payload": map[string]interface{}{
|
||||
"token": token,
|
||||
"ua": uaEncoded,
|
||||
"v": appVersion,
|
||||
"r": "",
|
||||
},
|
||||
}
|
||||
|
||||
if err := conn.WriteJSON(initMsg); err != nil {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("failed to send connection_init: %w", err)
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func waitForAck(conn *websocket.Conn) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
done := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
var msg map[string]interface{}
|
||||
if err := conn.ReadJSON(&msg); err != nil {
|
||||
done <- err
|
||||
return
|
||||
}
|
||||
|
||||
msgType, _ := msg["type"].(string)
|
||||
fmt.Printf(" Received: %s\n", msgType)
|
||||
|
||||
if msgType == "connection_ack" {
|
||||
done <- nil
|
||||
return
|
||||
}
|
||||
|
||||
// Keep reading other messages (like ka)
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-done:
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
return fmt.Errorf("timeout waiting for connection_ack")
|
||||
}
|
||||
}
|
||||
|
||||
func subscribeToMessages(conn *websocket.Conn, roomID string) error {
|
||||
query := fmt.Sprintf(`
|
||||
subscription {
|
||||
newMessage(roomId: "%s") {
|
||||
body
|
||||
time
|
||||
user {
|
||||
displayName
|
||||
username
|
||||
}
|
||||
}
|
||||
}
|
||||
`, roomID)
|
||||
|
||||
subMsg := map[string]interface{}{
|
||||
"id": "newMessage-subscription",
|
||||
"type": "subscribe",
|
||||
"payload": map[string]interface{}{
|
||||
"query": query,
|
||||
"variables": map[string]interface{}{},
|
||||
},
|
||||
}
|
||||
|
||||
return conn.WriteJSON(subMsg)
|
||||
}
|
||||
|
||||
func truncate(s string, maxLen int) string {
|
||||
if len(s) <= maxLen {
|
||||
return s
|
||||
}
|
||||
return s[:maxLen] + "..."
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user