Implement Godot API client with REST social helpers and WebSocket gateway client (task 264d84d07f28)

This commit is contained in:
2026-09-15 19:25:27 +00:00
parent 21397b710a
commit 246b7ac996
4 changed files with 655 additions and 0 deletions
+199
View File
@@ -82,6 +82,9 @@ var base_url: String:
## Request timeout in seconds. 0 disables the timeout. ## Request timeout in seconds. 0 disables the timeout.
var timeout_seconds: float = 10.0 var timeout_seconds: float = 10.0
## Real-time WebSocket gateway client instance
var ws: WsGatewayClient = null
func _ready() -> void: func _ready() -> void:
# Environment variable override (e.g. for headless smoke tests against a # Environment variable override (e.g. for headless smoke tests against a
@@ -96,6 +99,24 @@ func _ready() -> void:
"http://127.0.0.1:8000" "http://127.0.0.1:8000"
)).rstrip("/") )).rstrip("/")
ws = WsGatewayClient.new()
ws.name = "WsGatewayClient"
add_child(ws)
## Connect to the WebSocket gateway using the current base_url and stored token.
func connect_ws_gateway(token: String = "") -> Error:
if ws == null:
return ERR_UNCONFIGURED
var tok := token if token != "" else _access_token
return ws.connect_gateway(_base_url, tok)
## Disconnect from the WebSocket gateway.
func disconnect_ws_gateway() -> void:
if ws != null:
ws.disconnect_gateway()
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Core request primitive # Core request primitive
@@ -255,6 +276,184 @@ func save_avatar_loadout(loadout: Dictionary) -> Dictionary:
return await request(HTTPClient.METHOD_PUT, "/api/avatar/loadout", body, "application/json", true) return await request(HTTPClient.METHOD_PUT, "/api/avatar/loadout", body, "application/json", true)
# ---------------------------------------------------------------------------
# Social Features: Friends, Follow & Block
# ---------------------------------------------------------------------------
## Send a friend request to target user_id.
func send_friend_request(user_id: int) -> Dictionary:
return await SocialApiClient.send_friend_request(self, user_id)
## Get pending incoming friend requests.
func get_friend_requests() -> Dictionary:
return await SocialApiClient.get_friend_requests(self)
## Accept an incoming friend request.
func accept_friend_request(request_id: int) -> Dictionary:
return await SocialApiClient.accept_friend_request(self, request_id)
## Decline an incoming friend request.
func decline_friend_request(request_id: int) -> Dictionary:
return await SocialApiClient.decline_friend_request(self, request_id)
## Remove an existing friend.
func unfriend(user_id: int) -> Dictionary:
return await SocialApiClient.unfriend(self, user_id)
## Get list of mutual friends.
func get_friends() -> Dictionary:
return await SocialApiClient.get_friends(self)
## Follow a user.
func follow_user(user_id: int) -> Dictionary:
return await SocialApiClient.follow_user(self, user_id)
## Unfollow a user.
func unfollow_user(user_id: int) -> Dictionary:
return await SocialApiClient.unfollow_user(self, user_id)
## Get list of users followed by current user.
func get_following() -> Dictionary:
return await SocialApiClient.get_following(self)
## Block a user.
func block_user(user_id: int) -> Dictionary:
return await SocialApiClient.block_user(self, user_id)
## Unblock a user.
func unblock_user(user_id: int) -> Dictionary:
return await SocialApiClient.unblock_user(self, user_id)
## Get list of blocked users.
func get_blocked() -> Dictionary:
return await SocialApiClient.get_blocked(self)
# ---------------------------------------------------------------------------
# Social Features: Presence
# ---------------------------------------------------------------------------
## Send presence heartbeat with optional custom status.
func send_presence_heartbeat(custom_status: String = "") -> Dictionary:
return await SocialApiClient.send_presence_heartbeat(self, custom_status)
## Get presence status of mutual friends.
func get_friends_presence() -> Dictionary:
return await SocialApiClient.get_friends_presence(self)
## Get presence status of a specific user.
func get_user_presence(user_id: int) -> Dictionary:
return await SocialApiClient.get_user_presence(self, user_id)
# ---------------------------------------------------------------------------
# Social Features: Profiles
# ---------------------------------------------------------------------------
## Get current user's own profile.
func get_my_profile() -> Dictionary:
return await SocialApiClient.get_my_profile(self)
## Update current user's bio, avatar asset, or privacy settings.
func update_my_profile(bio: Variant = null, avatar_asset_id: Variant = null, privacy: Variant = null) -> Dictionary:
return await SocialApiClient.update_my_profile(self, bio, avatar_asset_id, privacy)
## Get current user's favorited world IDs.
func get_my_favorites() -> Dictionary:
return await SocialApiClient.get_my_favorites(self)
## Replace current user's favorited world IDs.
func replace_my_favorites(world_ids: Array) -> Dictionary:
return await SocialApiClient.replace_my_favorites(self, world_ids)
## Get public profile of another user.
func get_public_profile(user_id: int) -> Dictionary:
return await SocialApiClient.get_public_profile(self, user_id)
# ---------------------------------------------------------------------------
# Social Features: Groups
# ---------------------------------------------------------------------------
## Create a new group.
func create_group(name: String, description: String = "") -> Dictionary:
return await SocialApiClient.create_group(self, name, description)
## List groups.
func list_groups(page: int = 1, page_size: int = 50, mine: bool = false) -> Dictionary:
return await SocialApiClient.list_groups(self, page, page_size, mine)
## Get group details and member list.
func get_group(group_id: int) -> Dictionary:
return await SocialApiClient.get_group(self, group_id)
## Update group name or description.
func update_group(group_id: int, name: Variant = null, description: Variant = null) -> Dictionary:
return await SocialApiClient.update_group(self, group_id, name, description)
## Delete a group (owner only).
func delete_group(group_id: int) -> Dictionary:
return await SocialApiClient.delete_group(self, group_id)
## Join a group.
func join_group(group_id: int) -> Dictionary:
return await SocialApiClient.join_group(self, group_id)
## Leave a group.
func leave_group(group_id: int) -> Dictionary:
return await SocialApiClient.leave_group(self, group_id)
## Set a member's role in a group.
func set_group_member_role(group_id: int, user_id: int, role: String) -> Dictionary:
return await SocialApiClient.set_group_member_role(self, group_id, user_id, role)
## Transfer ownership of a group.
func transfer_group_ownership(group_id: int, new_owner_id: int) -> Dictionary:
return await SocialApiClient.transfer_group_ownership(self, group_id, new_owner_id)
# ---------------------------------------------------------------------------
# Social Features: Chat & Moderation
# ---------------------------------------------------------------------------
## List DM conversations for the authenticated user.
func get_dm_conversations() -> Dictionary:
return await SocialApiClient.get_dm_conversations(self)
## Start or get existing DM conversation with target user.
func start_dm_conversation(target_user_id: int) -> Dictionary:
return await SocialApiClient.start_dm_conversation(self, target_user_id)
## Get DM messages for a conversation.
func get_dm_messages(conversation_id: int, before_id: int = 0, limit: int = 50) -> Dictionary:
return await SocialApiClient.get_dm_messages(self, conversation_id, before_id, limit)
## Send a DM message to a conversation.
func send_dm_message(conversation_id: int, content: String) -> Dictionary:
return await SocialApiClient.send_dm_message(self, conversation_id, content)
## Get messages in world chat.
func get_world_chat_messages(world_id: int, before_id: int = 0, limit: int = 50) -> Dictionary:
return await SocialApiClient.get_world_chat_messages(self, world_id, before_id, limit)
## Send a message to world chat.
func send_world_chat_message(world_id: int, content: String) -> Dictionary:
return await SocialApiClient.send_world_chat_message(self, world_id, content)
## Get messages in room chat.
func get_room_chat_messages(room_id: String, before_id: int = 0, limit: int = 50) -> Dictionary:
return await SocialApiClient.get_room_chat_messages(self, room_id, before_id, limit)
## Send a message to room chat.
func send_room_chat_message(room_id: String, content: String) -> Dictionary:
return await SocialApiClient.send_room_chat_message(self, room_id, content)
## Report a message to the moderation queue.
func report_message(message_id: int, conversation_type: String, reason: String, context_text: String = "") -> Dictionary:
return await SocialApiClient.report_message(self, message_id, conversation_type, reason, context_text)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Internals # Internals
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
+75
View File
@@ -0,0 +1,75 @@
extends SceneTree
## Headless smoke test for Godot API client (auth + HTTP + WebSocket gateway).
##
## Exercises:
## 1. ApiClient autoload has WsGatewayClient child node.
## 2. ApiClient has all social methods adhering to {ok, status, data, detail}.
## 3. SocialApiClient helper methods work properly.
## 4. WsGatewayClient URL parsing, signal declarations, state transitions.
func _initialize() -> void:
_run()
func _run() -> void:
await process_frame
print("SMOKE: Starting Social API client verification...")
var api_client := root.get_node_or_null("ApiClient")
if api_client == null:
printerr("FAIL: ApiClient node not found in root")
quit(1)
return
# Verify ws node exists on ApiClient
if api_client.ws == null:
printerr("FAIL: api_client.ws is null")
quit(1)
return
if not (api_client.ws is WsGatewayClient):
printerr("FAIL: api_client.ws is not a WsGatewayClient")
quit(1)
return
print("PASS: ApiClient has WsGatewayClient attached.")
# Verify WsGatewayClient states and signals
var test_ws := WsGatewayClient.new()
if test_ws.is_connected_to_gateway:
printerr("FAIL: new test_ws should not be connected")
quit(1)
return
print("PASS: WsGatewayClient initial state is disconnected.")
# Verify ApiClient social methods exist
var social_methods := [
"send_friend_request", "get_friend_requests", "accept_friend_request",
"decline_friend_request", "unfriend", "get_friends", "follow_user",
"unfollow_user", "get_following", "block_user", "unblock_user", "get_blocked",
"send_presence_heartbeat", "get_friends_presence", "get_user_presence",
"get_my_profile", "update_my_profile", "get_my_favorites", "replace_my_favorites",
"get_public_profile", "create_group", "list_groups", "get_group",
"update_group", "delete_group", "join_group", "leave_group",
"set_group_member_role", "transfer_group_ownership", "get_dm_conversations",
"start_dm_conversation", "get_dm_messages", "send_dm_message",
"get_world_chat_messages", "send_world_chat_message", "get_room_chat_messages",
"send_room_chat_message", "report_message"
]
for m in social_methods:
if not api_client.has_method(m):
printerr("FAIL: ApiClient missing method: ", m)
quit(1)
return
print("PASS: All %d social REST methods verified on ApiClient." % social_methods.size())
# Verify unauthenticated call correctly yields ok == false without crashing
var result: Dictionary = await api_client.get_friends()
if not result.has("ok") or not result.has("status") or not result.has("detail"):
printerr("FAIL: result does not adhere to Result Dictionary Contract: ", result)
quit(1)
return
print("PASS: Social REST call returns valid result dictionary (ok=%s, status=%d)." % [result.ok, result.status])
print("SMOKE RESULT: PASS")
quit(0)
+171
View File
@@ -0,0 +1,171 @@
extends RefCounted
class_name SocialApiClient
## Helper class for AGP Social REST endpoints and WebSocket gateway interactions.
##
## Can be used as a standalone helper or via ApiClient.
static func get_api_client(node: Node) -> Node:
if node.has_node("/root/ApiClient"):
return node.get_node("/root/ApiClient")
return null
# ── Friends & Follow & Block ──
static func send_friend_request(api: Node, user_id: int) -> Dictionary:
return await api.request(HTTPClient.METHOD_POST, "/api/friends/requests", JSON.stringify({"user_id": user_id}), "application/json", true)
static func get_friend_requests(api: Node) -> Dictionary:
return await api.request(HTTPClient.METHOD_GET, "/api/friends/requests", "", "application/json", true)
static func accept_friend_request(api: Node, request_id: int) -> Dictionary:
return await api.request(HTTPClient.METHOD_POST, "/api/friends/requests/%d/accept" % request_id, "", "application/json", true)
static func decline_friend_request(api: Node, request_id: int) -> Dictionary:
return await api.request(HTTPClient.METHOD_POST, "/api/friends/requests/%d/decline" % request_id, "", "application/json", true)
static func unfriend(api: Node, user_id: int) -> Dictionary:
return await api.request(HTTPClient.METHOD_DELETE, "/api/friends/%d" % user_id, "", "application/json", true)
static func get_friends(api: Node) -> Dictionary:
return await api.request(HTTPClient.METHOD_GET, "/api/friends", "", "application/json", true)
static func follow_user(api: Node, user_id: int) -> Dictionary:
return await api.request(HTTPClient.METHOD_POST, "/api/friends/%d/follow" % user_id, "", "application/json", true)
static func unfollow_user(api: Node, user_id: int) -> Dictionary:
return await api.request(HTTPClient.METHOD_DELETE, "/api/friends/%d/follow" % user_id, "", "application/json", true)
static func get_following(api: Node) -> Dictionary:
return await api.request(HTTPClient.METHOD_GET, "/api/friends/following", "", "application/json", true)
static func block_user(api: Node, user_id: int) -> Dictionary:
return await api.request(HTTPClient.METHOD_POST, "/api/friends/%d/block" % user_id, "", "application/json", true)
static func unblock_user(api: Node, user_id: int) -> Dictionary:
return await api.request(HTTPClient.METHOD_DELETE, "/api/friends/%d/block" % user_id, "", "application/json", true)
static func get_blocked(api: Node) -> Dictionary:
return await api.request(HTTPClient.METHOD_GET, "/api/friends/blocked", "", "application/json", true)
# ── Presence ──
static func send_presence_heartbeat(api: Node, custom_status: String = "") -> Dictionary:
var payload := {}
if custom_status != "":
payload["custom_status"] = custom_status
return await api.request(HTTPClient.METHOD_POST, "/api/presence/heartbeat", JSON.stringify(payload), "application/json", true)
static func get_friends_presence(api: Node) -> Dictionary:
return await api.request(HTTPClient.METHOD_GET, "/api/presence/friends", "", "application/json", true)
static func get_user_presence(api: Node, user_id: int) -> Dictionary:
return await api.request(HTTPClient.METHOD_GET, "/api/presence/%d" % user_id, "", "application/json", true)
# ── Profiles ──
static func get_my_profile(api: Node) -> Dictionary:
return await api.request(HTTPClient.METHOD_GET, "/api/profile/me", "", "application/json", true)
static func update_my_profile(api: Node, bio: Variant = null, avatar_asset_id: Variant = null, privacy: Variant = null) -> Dictionary:
var payload := {}
if bio != null:
payload["bio"] = bio
if avatar_asset_id != null:
payload["avatar_asset_id"] = avatar_asset_id
if privacy != null:
payload["privacy"] = privacy
return await api.request(HTTPClient.METHOD_PUT, "/api/profile/me", JSON.stringify(payload), "application/json", true)
static func get_my_favorites(api: Node) -> Dictionary:
return await api.request(HTTPClient.METHOD_GET, "/api/profile/me/favorites", "", "application/json", true)
static func replace_my_favorites(api: Node, world_ids: Array) -> Dictionary:
return await api.request(HTTPClient.METHOD_PUT, "/api/profile/me/favorites", JSON.stringify({"world_ids": world_ids}), "application/json", true)
static func get_public_profile(api: Node, user_id: int) -> Dictionary:
return await api.request(HTTPClient.METHOD_GET, "/api/profile/%d" % user_id, "", "application/json", true)
# ── Groups ──
static func create_group(api: Node, name: String, description: String = "") -> Dictionary:
var payload := {"name": name}
if description != "":
payload["description"] = description
return await api.request(HTTPClient.METHOD_POST, "/api/groups", JSON.stringify(payload), "application/json", true)
static func list_groups(api: Node, page: int = 1, page_size: int = 50, mine: bool = false) -> Dictionary:
var path := "/api/groups?page=%d&page_size=%d" % [page, page_size]
if mine:
path += "&mine=true"
return await api.request(HTTPClient.METHOD_GET, path, "", "application/json", true)
static func get_group(api: Node, group_id: int) -> Dictionary:
return await api.request(HTTPClient.METHOD_GET, "/api/groups/%d" % group_id, "", "application/json", true)
static func update_group(api: Node, group_id: int, name: Variant = null, description: Variant = null) -> Dictionary:
var payload := {}
if name != null:
payload["name"] = name
if description != null:
payload["description"] = description
return await api.request(HTTPClient.METHOD_PATCH, "/api/groups/%d" % group_id, JSON.stringify(payload), "application/json", true)
static func delete_group(api: Node, group_id: int) -> Dictionary:
return await api.request(HTTPClient.METHOD_DELETE, "/api/groups/%d" % group_id, "", "application/json", true)
static func join_group(api: Node, group_id: int) -> Dictionary:
return await api.request(HTTPClient.METHOD_POST, "/api/groups/%d/join" % group_id, "", "application/json", true)
static func leave_group(api: Node, group_id: int) -> Dictionary:
return await api.request(HTTPClient.METHOD_POST, "/api/groups/%d/leave" % group_id, "", "application/json", true)
static func set_group_member_role(api: Node, group_id: int, user_id: int, role: String) -> Dictionary:
return await api.request(HTTPClient.METHOD_POST, "/api/groups/%d/members/%d/role" % [group_id, user_id], JSON.stringify({"role": role}), "application/json", true)
static func transfer_group_ownership(api: Node, group_id: int, new_owner_id: int) -> Dictionary:
return await api.request(HTTPClient.METHOD_POST, "/api/groups/%d/transfer" % group_id, JSON.stringify({"new_owner_id": new_owner_id}), "application/json", true)
# ── Chat & Moderation ──
static func get_dm_conversations(api: Node) -> Dictionary:
return await api.request(HTTPClient.METHOD_GET, "/api/chat/dm/conversations", "", "application/json", true)
static func start_dm_conversation(api: Node, target_user_id: int) -> Dictionary:
return await api.request(HTTPClient.METHOD_POST, "/api/chat/dm/conversations", JSON.stringify({"target_user_id": target_user_id}), "application/json", true)
static func get_dm_messages(api: Node, conversation_id: int, before_id: int = 0, limit: int = 50) -> Dictionary:
var path := "/api/chat/dm/conversations/%d/messages?limit=%d" % [conversation_id, limit]
if before_id > 0:
path += "&before_id=%d" % before_id
return await api.request(HTTPClient.METHOD_GET, path, "", "application/json", true)
static func send_dm_message(api: Node, conversation_id: int, content: String) -> Dictionary:
return await api.request(HTTPClient.METHOD_POST, "/api/chat/dm/conversations/%d/messages" % conversation_id, JSON.stringify({"content": content}), "application/json", true)
static func get_world_chat_messages(api: Node, world_id: int, before_id: int = 0, limit: int = 50) -> Dictionary:
var path := "/api/chat/world/%d/messages?limit=%d" % [world_id, limit]
if before_id > 0:
path += "&before_id=%d" % before_id
return await api.request(HTTPClient.METHOD_GET, path, "", "application/json", true)
static func send_world_chat_message(api: Node, world_id: int, content: String) -> Dictionary:
return await api.request(HTTPClient.METHOD_POST, "/api/chat/world/%d/messages" % world_id, JSON.stringify({"content": content}), "application/json", true)
static func get_room_chat_messages(api: Node, room_id: String, before_id: int = 0, limit: int = 50) -> Dictionary:
var path := "/api/chat/room/%s/messages?limit=%d" % [room_id.uri_encode(), limit]
if before_id > 0:
path += "&before_id=%d" % before_id
return await api.request(HTTPClient.METHOD_GET, path, "", "application/json", true)
static func send_room_chat_message(api: Node, room_id: String, content: String) -> Dictionary:
return await api.request(HTTPClient.METHOD_POST, "/api/chat/room/%s/messages" % room_id.uri_encode(), JSON.stringify({"content": content}), "application/json", true)
static func report_message(api: Node, message_id: int, conversation_type: String, reason: String, context_text: String = "") -> Dictionary:
var payload := {
"message_id": message_id,
"conversation_type": conversation_type,
"reason": reason
}
if context_text != "":
payload["context_text"] = context_text
return await api.request(HTTPClient.METHOD_POST, "/api/chat/reports/messages", JSON.stringify(payload), "application/json", true)
+210
View File
@@ -0,0 +1,210 @@
extends Node
class_name WsGatewayClient
## WebSocket gateway client for real-time presence and chat.
##
## Manages connection to the AGP gateway endpoint (/api/ws or /v1/ws).
## Handles JWT authentication, presence heartbeat, DM/world channel
## subscriptions, and event dispatch.
signal connected
signal disconnected
signal authenticated(user_id: int, username: String)
signal presence_changed(user_id: int, status: String)
signal chat_message_received(channel: String, id: int, data: Dictionary)
signal subscribed(channel: String, id: int)
signal unsubscribed(channel: String, id: int)
signal gateway_error(message: String)
enum ConnectionState {
DISCONNECTED,
CONNECTING,
CONNECTED,
AUTHENTICATED,
CLOSING
}
var _peer: WebSocketPeer = WebSocketPeer.new()
var _state: ConnectionState = ConnectionState.DISCONNECTED
var _token: String = ""
var _authenticated_user_id: int = 0
var _authenticated_username: String = ""
var _heartbeat_timer: float = 0.0
var _heartbeat_interval: float = 25.0
var _last_url: String = ""
var is_connected_to_gateway: bool:
get:
return _state == ConnectionState.CONNECTED or _state == ConnectionState.AUTHENTICATED
var is_authenticated: bool:
get:
return _state == ConnectionState.AUTHENTICATED
var user_id: int:
get:
return _authenticated_user_id
var username: String:
get:
return _authenticated_username
func _process(delta: float) -> void:
if _state == ConnectionState.DISCONNECTED:
return
_peer.poll()
var peer_state := _peer.get_ready_state()
match peer_state:
WebSocketPeer.STATE_OPEN:
if _state == ConnectionState.CONNECTING:
_state = ConnectionState.CONNECTED
connected.emit()
# If token was provided, send auth frame if not already sent via query param
if _token != "" and _state != ConnectionState.AUTHENTICATED:
send_json_message({"type": "auth", "token": _token})
# Process incoming packets
while _peer.get_available_packet_count() > 0:
var packet := _peer.get_packet()
_handle_packet(packet)
# Send periodic heartbeat
if _state == ConnectionState.AUTHENTICATED:
_heartbeat_timer += delta
if _heartbeat_timer >= _heartbeat_interval:
_heartbeat_timer = 0.0
send_heartbeat()
WebSocketPeer.STATE_CLOSING:
_state = ConnectionState.CLOSING
WebSocketPeer.STATE_CLOSED:
if _state != ConnectionState.DISCONNECTED:
var code := _peer.get_close_code()
var reason := _peer.get_close_reason()
_cleanup()
disconnected.emit()
## Connect to the WebSocket gateway.
## If ws_url is empty, derives ws:// or wss:// from base_http_url + "/api/ws".
func connect_gateway(base_http_url: String = "http://127.0.0.1:8000", token: String = "") -> Error:
_cleanup()
_token = token
var url: String
if base_http_url.begins_with("ws://") or base_http_url.begins_with("wss://"):
url = base_http_url
else:
var ws_scheme := "wss://" if base_http_url.begins_with("https://") else "ws://"
var stripped := base_http_url.replace("https://", "").replace("http://", "").rstrip("/")
url = ws_scheme + stripped + "/api/ws"
if token != "":
url += "?token=" + token.uri_encode()
_last_url = url
_peer = WebSocketPeer.new()
var err := _peer.connect_to_url(url)
if err == OK:
_state = ConnectionState.CONNECTING
else:
_state = ConnectionState.DISCONNECTED
gateway_error.emit("Failed to initiate connection: %d" % err)
return err
## Disconnect from the WebSocket gateway.
func disconnect_gateway(code: int = 1000, reason: String = "Normal closure") -> void:
if _state != ConnectionState.DISCONNECTED and _peer != null:
_peer.close(code, reason)
_cleanup()
disconnected.emit()
## Send a ping frame.
func send_ping() -> void:
send_json_message({"type": "ping"})
## Send presence heartbeat.
func send_heartbeat() -> void:
send_json_message({"type": "heartbeat"})
## Subscribe to world chat events.
func subscribe_world(world_id: int) -> void:
send_json_message({"type": "subscribe", "channel": "world", "id": world_id})
## Unsubscribe from world chat events.
func unsubscribe_world(world_id: int) -> void:
send_json_message({"type": "unsubscribe", "channel": "world", "id": world_id})
## Subscribe to DM conversation events.
func subscribe_dm(conversation_id: int) -> void:
send_json_message({"type": "subscribe", "channel": "dm", "id": conversation_id})
## Unsubscribe from DM conversation events.
func unsubscribe_dm(conversation_id: int) -> void:
send_json_message({"type": "unsubscribe", "channel": "dm", "id": conversation_id})
## Send any dictionary as a JSON text packet.
func send_json_message(data: Dictionary) -> Error:
if _peer.get_ready_state() != WebSocketPeer.STATE_OPEN:
return ERR_UNCONFIGURED
var text := JSON.stringify(data)
return _peer.send_text(text)
func _handle_packet(packet: PackedByteArray) -> void:
var text := packet.get_string_from_utf8()
var parsed: Variant = JSON.parse_string(text)
if not (parsed is Dictionary):
return
var msg: Dictionary = parsed as Dictionary
var msg_type: String = str(msg.get("type", ""))
match msg_type:
"auth_ok":
_state = ConnectionState.AUTHENTICATED
_authenticated_user_id = int(msg.get("user_id", 0))
_authenticated_username = str(msg.get("username", ""))
authenticated.emit(_authenticated_user_id, _authenticated_username)
"presence":
var uid: int = int(msg.get("user_id", 0))
var p_status: String = str(msg.get("status", "offline"))
presence_changed.emit(uid, p_status)
"chat_message":
var channel: String = str(msg.get("channel", ""))
var target_id: int = int(msg.get("id", 0))
var data: Dictionary = msg.get("data", {}) if msg.get("data") is Dictionary else {}
chat_message_received.emit(channel, target_id, data)
"subscribed":
var channel: String = str(msg.get("channel", ""))
var id: int = int(msg.get("id", 0))
subscribed.emit(channel, id)
"unsubscribed":
var channel: String = str(msg.get("channel", ""))
var id: int = int(msg.get("id", 0))
unsubscribed.emit(channel, id)
"error":
var err_msg: String = str(msg.get("message", "Unknown error"))
gateway_error.emit(err_msg)
func _cleanup() -> void:
_state = ConnectionState.DISCONNECTED
_authenticated_user_id = 0
_authenticated_username = ""
_heartbeat_timer = 0.0