forked from Abdulkhalek-1/GoRing
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmessages.go
More file actions
214 lines (184 loc) · 6.04 KB
/
messages.go
File metadata and controls
214 lines (184 loc) · 6.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
package protocol
import (
"encoding/json"
"fmt"
)
// Message types for WebSocket communication
const (
// Client -> Server (Call Control)
TypeCallStart = "call.start"
TypeCallAccept = "call.accept"
TypeCallReject = "call.reject"
TypeCallEnd = "call.end"
TypeCallCheck = "call.check" // Validate session before ringing (for push-woken clients)
// Client <-> Server (WebRTC Signaling - relayed to peer)
TypeWebRTCOffer = "webrtc.offer"
TypeWebRTCAnswer = "webrtc.answer"
TypeWebRTCICE = "webrtc.ice"
// Server -> Client
TypeCallRinging = "call.ringing" // Sent to caller when call starts
TypeCallRing = "call.ring" // Sent to callee for incoming call
TypeCallAccepted = "call.accepted"
TypeCallRejected = "call.rejected"
TypeCallEnded = "call.ended"
TypeCallCheckResult = "call.check_result" // Response to call.check
TypeError = "error"
// Internal (Server -> Server via pub/sub)
TypeDisconnect = "internal.disconnect"
)
// Call states stored in Redis
const (
StateRinging = "ringing"
StateAccepted = "accepted"
StateRejected = "rejected"
StateEnded = "ended"
)
// Message is the envelope for all WebSocket messages
type Message struct {
Type string `json:"type"`
Payload json.RawMessage `json:"payload"`
}
// UserInfo contains user profile data for event payloads
type UserInfo struct {
UserID string `json:"user_id"`
Name string `json:"name,omitempty"`
Username string `json:"username,omitempty"`
ImageProfile string `json:"image_profile,omitempty"`
}
// CallStartPayload is sent by caller to initiate a call
type CallStartPayload struct {
CalleeID string `json:"callee_id"`
CalleeDeviceToken string `json:"callee_device_token,omitempty"` // FCM or APNs device token
CalleeOS string `json:"callee_os,omitempty"` // "android" or "ios"
}
// CallRingPayload is sent to callee when incoming call
type CallRingPayload struct {
SessionID string `json:"session_id"`
CallerID string `json:"caller_id"`
CallerInfo *UserInfo `json:"caller_info,omitempty"`
}
// CallRingingPayload is sent to caller when call starts ringing
type CallRingingPayload struct {
SessionID string `json:"session_id"`
CalleeID string `json:"callee_id"`
}
// CallSessionPayload is used for accept/reject/end operations
type CallSessionPayload struct {
SessionID string `json:"session_id"`
}
// CallCheckPayload is sent by callee to validate a session after waking from push notification
type CallCheckPayload struct {
SessionID string `json:"session_id"`
}
// CallCheckResultPayload is the server's response to a call.check message
type CallCheckResultPayload struct {
SessionID string `json:"session_id"`
Exists bool `json:"exists"`
State string `json:"state,omitempty"`
CallerID string `json:"caller_id,omitempty"`
}
// CallAcceptedPayload is sent to caller when call is accepted
type CallAcceptedPayload struct {
SessionID string `json:"session_id"`
CalleeInfo *UserInfo `json:"callee_info,omitempty"`
}
// CallRejectedPayload is sent to caller when call is rejected
type CallRejectedPayload struct {
SessionID string `json:"session_id"`
CalleeInfo *UserInfo `json:"callee_info,omitempty"`
}
// CallEndedPayload is sent to both parties when call ends
type CallEndedPayload struct {
SessionID string `json:"session_id"`
Reason string `json:"reason"`
PeerInfo *UserInfo `json:"peer_info,omitempty"`
}
// WebRTCOfferPayload is sent by caller to initiate WebRTC connection
type WebRTCOfferPayload struct {
SessionID string `json:"session_id"`
SDP string `json:"sdp"`
}
// WebRTCAnswerPayload is sent by callee in response to offer
type WebRTCAnswerPayload struct {
SessionID string `json:"session_id"`
SDP string `json:"sdp"`
}
// WebRTCICEPayload is sent by both parties for ICE candidate exchange
type WebRTCICEPayload struct {
SessionID string `json:"session_id"`
Candidate string `json:"candidate"`
SDPMid string `json:"sdpMid,omitempty"`
SDPMLineIndex *int `json:"sdpMLineIndex,omitempty"`
}
// ErrorPayload is sent when an error occurs
type ErrorPayload struct {
Code string `json:"code"`
Message string `json:"message"`
}
// DisconnectPayload is sent internally to kick a user's existing connection
type DisconnectPayload struct {
Reason string `json:"reason"`
}
// Error codes
const (
ErrCodeInvalidMessage = "invalid_message"
ErrCodeUnauthorized = "unauthorized"
ErrCodeUserBusy = "user_busy"
ErrCodeUserOffline = "user_offline"
ErrCodeSessionNotFound = "session_not_found"
ErrCodeInvalidState = "invalid_state"
ErrCodeInternalError = "internal_error"
)
// NewMessage creates a new message with the given type and payload
func NewMessage(msgType string, payload any) (*Message, error) {
data, err := json.Marshal(payload)
if err != nil {
return nil, fmt.Errorf("marshal payload: %w", err)
}
return &Message{
Type: msgType,
Payload: data,
}, nil
}
// MustNewMessage creates a new message, panics on error
func MustNewMessage(msgType string, payload any) *Message {
msg, err := NewMessage(msgType, payload)
if err != nil {
panic(err)
}
return msg
}
// ParsePayload unmarshals the payload into the given target
func (m *Message) ParsePayload(target any) error {
if err := json.Unmarshal(m.Payload, target); err != nil {
return fmt.Errorf("unmarshal payload: %w", err)
}
return nil
}
// Bytes serializes the message to JSON bytes
func (m *Message) Bytes() ([]byte, error) {
return json.Marshal(m)
}
// MustBytes serializes the message, panics on error
func (m *Message) MustBytes() []byte {
data, err := m.Bytes()
if err != nil {
panic(err)
}
return data
}
// ParseMessage parses a JSON message from bytes
func ParseMessage(data []byte) (*Message, error) {
var msg Message
if err := json.Unmarshal(data, &msg); err != nil {
return nil, fmt.Errorf("unmarshal message: %w", err)
}
return &msg, nil
}
// NewErrorMessage creates an error message
func NewErrorMessage(code, message string) *Message {
return MustNewMessage(TypeError, ErrorPayload{
Code: code,
Message: message,
})
}