Socket.received_stream_presence signal never sent

purpose: making a online user count realtime display using the stream method in godot. The reason I don’t use normal nk.stream_user_leave method is it cannot catch player force shut down that the stream signal will not be sent and I cannot update the number upon they leaving
according to document (Streams - Heroic Labs Documentation)
where " Receiving stream presence events

When a new presence joins a stream or an existing presence leaves the server will broadcast presence events to all users currently on the stream."
The socket.received_stream_presence signal should be sent to every use every time a user leave or join the server.
However from the docker log:
{“level”:“debug”,“ts”:“2025-05-19T13:46:26.816Z”,“caller”:“server/tracker.go:912”,“msg”:“Processing presence event”,“joins”:0,“leaves”:1}
the handle of presence event is presence.
however in my godot, the socket.received_stream_presence signal was never sent.
here is my lua code:

local nk = require("nakama")
local stream_id = { mode = 2, label = "Global Chat Room" }


local function join(context, _)
	local hidden = false
	local persistence = false
	  -- Debugging: Log session ID
    nk.logger_info("User ID: " .. context.user_id)
    nk.logger_info("Session ID: " .. tostring(context.session_id))

    -- Ensure session_id is valid before joining stream
	nk.stream_user_join(context.user_id, context.session_id, stream_id)
	user_count_update()
end
nk.register_rpc(join, "join")
local function leave(context, _)
    nk.logger_info("User Leaving: " .. context.user_id)

    -- Remove the user from the stream
    nk.stream_user_leave(context.user_id, context.session_id, stream_id)

    user_count_update()
end

nk.register_rpc(leave, "leave")
function user_count_update()
	local payload = nk.json_encode({ event_type=1,context=nk.stream_count(stream_id)})
	nk.stream_send(stream_id, payload)
end
nk.register_rpc(user_count_update,"count")

my gdscript

extends Control

var server_key: String = "defaultkey"
var host: String = "127.0.0.1"
var port: int = 7350
var scheme: String = "http"
var timeout : int = 10

var client : NakamaClient 
var socket : NakamaSocket
var device_id = OS.get_unique_id()
var session:NakamaSession
var user_id 
signal session_changed (nakama_session)

var onLogInPage:bool = 1
var onSignInPage:bool = 0
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
	client = Nakama.create_client(server_key,host,port,scheme,timeout)
	
	pass # Replace with function body.


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
	pass


func _on_login_button_button_down() -> void:
	$Panel/LoginPanel.show()
	$Panel/SigninPanel.hide()
	onLogInPage = 1
	onSignInPage = 0
	pass # Replace with function body.


func _on_sign_in_button_button_down() -> void:
	$Panel/LoginPanel.hide()
	$Panel/SigninPanel.show()
	onLogInPage = 0
	onSignInPage = 1
	pass # Replace with function body.

#login menu
func _on_enter_button_button_down() -> void:
	#when field present and in login page
	if onLogInPage and $Panel/LoginPanel/EmailInput.text !="" and $Panel/LoginPanel/PasswordInput.text!="":
		session = await client.authenticate_email_async($Panel/LoginPanel/EmailInput.text,$Panel/LoginPanel/PasswordInput.text)
		
		socket = Nakama.create_socket_from(client)
		socket.connected.connect(onSocketConnected)
		socket.connection_error.connect(onSocketConnectionError)
		await socket.connect_async(session)
		if session.created:
			$Panel/Debug.text = "Please sign up before login"
			_on_sign_in_button_button_down()
			client.delete_account_async(session)
			client.session_logout_async(session)
			return
	elif onSignInPage and $Panel/SigninPanel/EmailInput.text != "" and $Panel/SigninPanel/PasswordInput.text != "" and $Panel/SigninPanel/DisplayNameInput.text != "" and $Panel/SigninPanel/NameInput.text != "":
		session = await client.authenticate_email_async($Panel/SigninPanel/EmailInput.text,$Panel/SigninPanel/PasswordInput.text)
		socket = Nakama.create_socket_from(client)
		socket.connected.connect(onSocketConnected)
		await socket.connect_async(session)
		await client.update_account_async(session,$Panel/SigninPanel/NameInput.text,$Panel/SigninPanel/DisplayNameInput.text)
	else:
		$Panel/Debug.text = "At least one field is not filled in"
		return
	socket.closed.connect(onSocketClosed)
	socket.received_error.connect(onSocketReceivedError)
	socket.received_stream_state.connect(onSocketReceivedStreamState)
	socket.received_channel_message.connect(onReceivedChannelMessage)
	socket.received_match_presence.connect(onMatchPresence)	
	socket.received_match_state.connect(onMatchState)
	socket.received_stream_presence.connect(onSocketReceivedStreamPresence)
	socket.received_status_presence.connect(onSocketReceivedStreamState)
	pass # Replace with function body.
#region socket signals

func onMatchPresence(presence:NakamaRTAPI.MatchPresenceEvent):
	print(presence)
func onMatchState(state:NakamaRTAPI.MatchData):
	print(state.data)
func onSocketConnected():
	print("Socket Connected")
	if onLogInPage:
		$Panel/Debug.text = "Successfully login"
	else:
		$Panel/Debug.text = "Successfully sign up"
	var response = await socket.rpc_async("join")
	socket.received_stream_presence.connect(self._on_stream_presence)
func onSocketClosed():
	print("Socket Closed")
func onSocketReceivedError(err):
	print("Socket Error:"+str(err))
func onSocketConnectionError(err):
	print("Socket Error:"+str(err))
	$Panel/Debug.text = "Password incorrect"
func onSocketReceivedStreamState(state: NakamaRTAPI.StreamData):
	print("Socket stream state:"+str(JSON.parse_string(state.state)))
	if JSON.parse_string(state.state)["event_type"]==1:
		$Panel2/PlayerCount.text = str(int(JSON.parse_string(state.state)["context"])) + " Online"
func onReceivedChannelMessage(message):
	print("Received message:"+str(message))
func onSocketReceivedStreamPresence(state):
	print("someone joined or someone leaved"+state)
func _on_stream_presence(p_presence : NakamaRTAPI.StreamPresenceEvent):
	print("Received presences on stream: %s" % [p_presence.stream])
	for p in p_presence.joins:
		print("User ID: %s, Username: %s, Status: %s" % [p.user_id, p.username, p.status])
	for p in p_presence.leaves:
		print("User ID: %s, Username: %s, Status: %s" % [p.user_id, p.username, p.status])
#endregion


func _on_disconnect_button_button_down() -> void:
	var response = await socket.rpc_async("leave")
	pass # Replace with function body.

and here is the godot output when I login 2 account then disconnect one using disconnect button calling rpc of leave in lua

=== Nakama : DEBUG === Sending request [ID: 1, Method: POST, Uri: http://127.0.0.1:7350/v2/account/authenticate/email?create=true&, Headers: { "Authorization": "Basic ZGVmYXVsdGtleTo=" }, Body: {"email":"test@gmail.com","password":"password"}, Timeout: 10, Retries: 3, Backoff base: 10 ms]
=== Nakama : DEBUG === Freeing request 1
=== Nakama : DEBUG === Connecting to host: ws://127.0.0.1:7350/ws?lang=en&status=false&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0aWQiOiI3OWQ2MDhlMS05ODBiLTQ1MTMtOGFlMi1lZTRhY2IyMWI2MmEiLCJ1aWQiOiI0ZTM1MTBhMy0yNDAwLTRjZGMtOWMyYi1jNGQzNjNjODFmZmMiLCJ1c24iOiJ0ZXN0IiwiZXhwIjoxNzQ3NjY5NTgxfQ.-hefFztRCZrhm8o0CFHPwowCZpimZBPkNaWzOnUeP2s
Socket Connected
=== Nakama : DEBUG === Sending async request: http_key: <null>, id: join, payload: <null>, 
=== Nakama : INFO === Connected!
Socket stream state:{ "context": 1.0, "event_type": 1.0 }
=== Nakama : DEBUG === Resuming request: 1: { "cid": "1", "rpc": { "id": "join" } }
=== Nakama : DEBUG === Sending request [ID: 1, Method: POST, Uri: http://127.0.0.1:7350/v2/account/authenticate/email?create=true&, Headers: { "Authorization": "Basic ZGVmYXVsdGtleTo=" }, Body: {"email":"test2@gmail.com","password":"password"}, Timeout: 10, Retries: 3, Backoff base: 10 ms]
=== Nakama : DEBUG === Freeing request 1
=== Nakama : DEBUG === Connecting to host: ws://127.0.0.1:7350/ws?lang=en&status=false&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0aWQiOiI0NjViOTdiNi1lYWE5LTQyODctYmJiNC02YjA5NjgxNzY3YzciLCJ1aWQiOiJmNzY3ODczOC01ODdhLTQ2ZDktYTYwNC0xN2MxMzhiNGEzMzQiLCJ1c24iOiJ0ZXN0MiIsImV4cCI6MTc0NzY2OTU4NX0.1uynf1fj69kLFSNtb3tuSbvAs9FbmZ0R5j3Yts4N-j0
Socket Connected
=== Nakama : DEBUG === Sending async request: http_key: <null>, id: join, payload: <null>, 
=== Nakama : INFO === Connected!
Socket stream state:{ "context": 2.0, "event_type": 1.0 }
Socket stream state:{ "context": 2.0, "event_type": 1.0 }
=== Nakama : DEBUG === Resuming request: 1: { "cid": "1", "rpc": { "id": "join" } }
=== Nakama : DEBUG === Sending async request: http_key: <null>, id: leave, payload: <null>, 
=== Nakama : DEBUG === Resuming request: 2: { "cid": "2", "rpc": { "id": "leave" } }
Socket stream state:{ "context": 1.0, "event_type": 1.0 }

Sorry if the code and log too long and messy for read