Merge pull request #2 from wuhewuhe/master
websocket public & private examples and impl
This commit is contained in:
commit
e557c96982
|
@ -31,15 +31,6 @@ type Client struct {
|
|||
|
||||
type doFunc func(req *http.Request) (*http.Response, error)
|
||||
|
||||
// Globals
|
||||
const (
|
||||
timestampKey = "X-BAPI-TIMESTAMP"
|
||||
signatureKey = "X-BAPI-SIGN"
|
||||
apiRequestKey = "X-BAPI-API-KEY"
|
||||
recvWindowKey = "X-BAPI-RECV-WINDOW"
|
||||
signTypeKey = "2"
|
||||
)
|
||||
|
||||
func currentTimestamp() int64 {
|
||||
return FormatTimestamp(time.Now())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
package bybit
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/websocket"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MessageHandler func(message string) error
|
||||
|
||||
func (b *WebSocket) handleIncomingMessages() {
|
||||
for {
|
||||
_, message, err := b.conn.ReadMessage()
|
||||
if err != nil {
|
||||
fmt.Println("Error reading:", err)
|
||||
return
|
||||
}
|
||||
|
||||
if b.onMessage != nil {
|
||||
err := b.onMessage(string(message))
|
||||
if err != nil {
|
||||
fmt.Println("Error handling message:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *WebSocket) SetMessageHandler(handler MessageHandler) {
|
||||
b.onMessage = handler
|
||||
}
|
||||
|
||||
type WebSocket struct {
|
||||
conn *websocket.Conn
|
||||
url string
|
||||
apiKey string
|
||||
apiSecret string
|
||||
onMessage MessageHandler
|
||||
}
|
||||
|
||||
func NewBybitPrivateWebSocket(url, apiKey, apiSecret string, handler MessageHandler) *WebSocket {
|
||||
return &WebSocket{
|
||||
url: url,
|
||||
apiKey: apiKey,
|
||||
apiSecret: apiSecret,
|
||||
onMessage: handler,
|
||||
}
|
||||
}
|
||||
|
||||
func NewBybitPublicWebSocket(url string, handler MessageHandler) *WebSocket {
|
||||
return &WebSocket{
|
||||
url: url,
|
||||
onMessage: handler,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *WebSocket) Connect(args []string) error {
|
||||
var err error
|
||||
b.conn, _, err = websocket.DefaultDialer.Dial(b.url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if b.requiresAuthentication() {
|
||||
if err = b.sendAuth(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
go b.handleIncomingMessages()
|
||||
|
||||
Ping(b)
|
||||
|
||||
return b.sendSubscription(args)
|
||||
}
|
||||
|
||||
func Ping(b *WebSocket) {
|
||||
ticker := time.NewTicker(15 * time.Second)
|
||||
go func() {
|
||||
defer ticker.Stop() // Ensure the ticker is stopped when this goroutine ends
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C: // Wait until the ticker sends a signal
|
||||
if err := b.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
|
||||
fmt.Println("Failed to send ping:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (b *WebSocket) Disconnect() error {
|
||||
return b.conn.Close()
|
||||
}
|
||||
|
||||
func (b *WebSocket) Send(message string) error {
|
||||
return b.conn.WriteMessage(websocket.TextMessage, []byte(message))
|
||||
}
|
||||
|
||||
func (b *WebSocket) requiresAuthentication() bool {
|
||||
return b.url == WEBSOCKET_PRIVATE_MAINNET ||
|
||||
b.url == WEBSOCKET_PRIVATE_TESTNET ||
|
||||
b.url == V3_CONTRACT_PRIVATE ||
|
||||
b.url == V3_UNIFIED_PRIVATE ||
|
||||
b.url == V3_SPOT_PRIVATE
|
||||
}
|
||||
|
||||
func (b *WebSocket) sendAuth() error {
|
||||
// Get current Unix time in milliseconds
|
||||
expires := time.Now().UnixNano()/1e6 + 10000
|
||||
val := fmt.Sprintf("GET/realtime%d", expires)
|
||||
|
||||
h := hmac.New(sha256.New, []byte(b.apiSecret))
|
||||
h.Write([]byte(val))
|
||||
|
||||
// Convert to hexadecimal instead of base64
|
||||
signature := hex.EncodeToString(h.Sum(nil))
|
||||
fmt.Println("signature generated : " + signature)
|
||||
|
||||
authMessage := map[string]interface{}{
|
||||
"req_id": uuid.New(), // You would need to implement or find a package for generating GUID
|
||||
"op": "auth",
|
||||
"args": []interface{}{b.apiKey, expires, signature},
|
||||
}
|
||||
fmt.Println("auth args:", fmt.Sprintf("%v", authMessage["args"]))
|
||||
return b.SendAsJson(authMessage)
|
||||
}
|
||||
|
||||
func (b *WebSocket) sendSubscription(args []string) error {
|
||||
subMessage := map[string]interface{}{
|
||||
"req_id": uuid.New(),
|
||||
"op": "subscribe",
|
||||
"args": args,
|
||||
}
|
||||
fmt.Println("subscribe msg:", fmt.Sprintf("%v", subMessage["args"]))
|
||||
return b.SendAsJson(subMessage)
|
||||
}
|
||||
|
||||
func (b *WebSocket) SendAsJson(v interface{}) error {
|
||||
data, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return b.Send(string(data))
|
||||
}
|
32
consts.go
32
consts.go
|
@ -1,5 +1,33 @@
|
|||
package bybit
|
||||
|
||||
const Name = "bybit.api.go"
|
||||
const (
|
||||
Name = "bybit.api.go"
|
||||
Version = "1.0.0"
|
||||
// WebSocket public channel - Mainnet
|
||||
SPOT_MAINNET = "wss://stream.bybit.com/v5/public/spot"
|
||||
LINEAR_MAINNET = "wss://stream.bybit.com/v5/public/linear"
|
||||
INVERSE_MAINNET = "wss://stream.bybit.com/v5/public/inverse"
|
||||
OPTION_MAINNET = "wss://stream.bybit.com/v5/public/option"
|
||||
|
||||
const Version = "1.0.0"
|
||||
// WebSocket public channel - Testnet
|
||||
SPOT_TESTNET = "wss://stream-testnet.bybit.com/v5/public/spot"
|
||||
LINEAR_TESTNET = "wss://stream-testnet.bybit.com/v5/public/linear"
|
||||
INVERSE_TESTNET = "wss://stream-testnet.bybit.com/v5/public/inverse"
|
||||
OPTION_TESTNET = "wss://stream-testnet.bybit.com/v5/public/option"
|
||||
|
||||
// WebSocket private channel
|
||||
WEBSOCKET_PRIVATE_MAINNET = "wss://stream.bybit.com/v5/private"
|
||||
WEBSOCKET_PRIVATE_TESTNET = "wss://stream-testnet.bybit.com/v5/private"
|
||||
|
||||
// V3
|
||||
V3_CONTRACT_PRIVATE = "wss://stream.bybit.com/contract/private/v3"
|
||||
V3_UNIFIED_PRIVATE = "wss://stream.bybit.com/unified/private/v3"
|
||||
V3_SPOT_PRIVATE = "wss://stream.bybit.com/spot/private/v3"
|
||||
|
||||
// Globals
|
||||
timestampKey = "X-BAPI-TIMESTAMP"
|
||||
signatureKey = "X-BAPI-SIGN"
|
||||
apiRequestKey = "X-BAPI-API-KEY"
|
||||
recvWindowKey = "X-BAPI-RECV-WINDOW"
|
||||
signTypeKey = "2"
|
||||
)
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
bybit "bybit.go.api"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ws := bybit.NewBybitPrivateWebSocket("wss://stream-testnet.bybit.com/v5/private", "8wYkmpLsMg10eNQyPm", "Ouxc34myDnXvei54XsBZgoQzfGxO4bkr2Zsj", func(message string) error {
|
||||
fmt.Println("Received:", message)
|
||||
return nil
|
||||
})
|
||||
// Connect and subscribe to the desired topic
|
||||
_ = ws.Connect([]string{"order"})
|
||||
select {}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
bybit "bybit.go.api"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create a public WebSocket client
|
||||
ws := bybit.NewBybitPublicWebSocket("wss://stream.bybit.com/v5/public/spot", func(message string) error {
|
||||
fmt.Println("Received:", message)
|
||||
return nil
|
||||
})
|
||||
// Connect and subscribe to the desired topic
|
||||
_ = ws.Connect([]string{"orderbook.1.BTCUSDT"})
|
||||
select {}
|
||||
}
|
1
go.mod
1
go.mod
|
@ -4,5 +4,6 @@ go 1.21
|
|||
|
||||
require (
|
||||
github.com/bitly/go-simplejson v0.5.1
|
||||
github.com/google/uuid v1.4.0
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
)
|
4
go.sum
4
go.sum
|
@ -1,3 +1,7 @@
|
|||
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
|
||||
github.com/bitly/go-simplejson v0.5.1/go.mod h1:YOPVLzCfwK14b4Sff3oP1AmGhI9T9Vsg84etUnlyp+Q=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
|
|
Loading…
Reference in New Issue