In this client module are a lot of bugs and memory leaks.
Bugfixes with integrated wss, added CloseConnection(), added SindBinaryFrame(), added Status at CloseConnection, allow unmasked transfers,
Code: Select all
; Websocketclient
;
; https://datatracker.ietf.org/doc/html/rfc6455
;
; by Netzvamp
; Version: 2016/01/08
;
; modified by infratec 2025/04/20
DeclareModule WebsocketClient
#Opcode_Continue = $00
#Opcode_Text = $01
#Opcode_Binary = $02
#Opcode_Closing = $08
#Opcode_Ping = $09
#Opcode_Pong = $0A
#Opcode_Unknown = $FF
Enumeration StatusCodes
#StatusCode_NormalClosure = 1000
#StatusCode_GoingAway
#StatusCode_ProtocolError
#StatusCode_NotAcceptedType
#StatusCode_Reserved_1
#StatusCode_Reserved_2
#StatusCode_DontUse
#StatusCode_MessageDoesNotFitType
#StatusCode_PolicyViolation
#StatusCode_MessageToBig
#StatusCode_ServerDoesNotHandleTheNeededExtension
#StatusCode_UnexpectedCondition
#StatusCode_Reserved_3 = 1015
EndEnumeration
Structure WebsocketClient_Structure
Connection.i
Protokol$
Servername$
Path$
Parameters$
Port.i
*ReceiveBuffer
frame_fragmentation.i
frame_masking.i
frame_type.i
frame_size.i
Status.i
Status$
EndStructure
Declare.i OpenConnection(*WebsocketClient.WebsocketClient_Structure, URL$)
Declare.i SendPingFrame(*WebsocketClient.WebsocketClient_Structure)
Declare.i SendTextFrame(*WebsocketClient.WebsocketClient_Structure, Text$, Masked.i=#True)
Declare.i SendBinaryFrame(*WebsocketClient.WebsocketClient_Structure, *Payload, Masked.i=#True)
Declare.i ReceiveFrame(*WebsocketClient.WebsocketClient_Structure)
Declare CloseConnection(*WebsocketClient.WebsocketClient_Structure, Status.i=#StatusCode_NormalClosure)
EndDeclareModule
Module WebsocketClient
EnableExplicit
;TODO: We don't support fragmetation right now
;TODO: Support to send/receive bigger frames
Macro dbg(txt)
CompilerIf #PB_Compiler_Debugger
Debug "WebsocketClient: " + FormatDate("%yyyy-%mm-%dd %hh:%ii:%ss",Date()) + " > " + txt
CompilerEndIf
EndMacro
Declare.i SendCloseFrame(*WebsocketClient.WebsocketClient_Structure, Status.i=#StatusCode_NormalClosure)
Procedure.i Handshake(*WebsocketClient.WebsocketClient_Structure)
Protected Request$, *Buffer, Size.i, Answer$, TimeoutCounter.i, Ok.i, Result.i
Request$ = "GET " + *WebsocketClient\Path$ + " HTTP/1.1"+ #CRLF$ +
"Host: " + *WebsocketClient\Servername$ + #CRLF$ +
"Upgrade: websocket" + #CRLF$ +
"Connection: Upgrade" + #CRLF$ +
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" + #CRLF$ +
"Sec-WebSocket-Version: 13" + #CRLF$ + #CRLF$
Debug Request$
SendNetworkString(*WebsocketClient\Connection, Request$)
*Buffer = AllocateMemory(65536, #PB_Memory_NoClear)
If *Buffer
; We wait for answer
TimeoutCounter = 1000
Repeat
Size = ReceiveNetworkData(*WebsocketClient\connection, *Buffer, 65536)
If Size > 0
Answer$ + PeekS(*Buffer, Size, #PB_UTF8|#PB_ByteLength)
Debug Answer$
If FindString(Answer$, #CRLF$ + #CRLF$)
Ok = #True
Break
EndIf
Else
Delay(1)
TimeoutCounter - 1
EndIf
Until Ok Or TimeoutCounter = 0
FreeMemory(*Buffer)
EndIf
If Ok
Answer$ = UCase(Answer$)
; Check answer
If FindString(Answer$, "HTTP/1.1 101") And FindString(Answer$, "CONNECTION: UPGRADE") And FindString(Answer$, "UPGRADE: WEBSOCKET")
Result = #True
EndIf
EndIf
ProcedureReturn Result
EndProcedure
Procedure.i OpenConnection(*WebsocketClient.WebsocketClient_Structure, URL$)
*WebsocketClient\Protokol$ = GetURLPart(URL$, #PB_URL_Protocol)
*WebsocketClient\Servername$ = GetURLPart(URL$, #PB_URL_Site)
*WebsocketClient\Port = Val(GetURLPart(URL$, #PB_URL_Port))
If *WebsocketClient\Port = 0
If *WebsocketClient\Protokol$ = "wss"
*WebsocketClient\Port = 443
Else
*WebsocketClient\Port = 80
EndIf
EndIf
*WebsocketClient\Path$ = GetURLPart(URL$, #PB_URL_Path)
If *WebsocketClient\Path$ = ""
*WebsocketClient\Path$ = "/"
Else
If Left(*WebsocketClient\Path$, 1) <> "/"
*WebsocketClient\Path$ = "/" + *WebsocketClient\Path$
EndIf
EndIf
*WebsocketClient\Parameters$ = GetURLPart(URL$, #PB_URL_Parameters)
If *WebsocketClient\Parameters$ <> ""
*WebsocketClient\Path$ + "?" + *WebsocketClient\Parameters$
EndIf
If *WebsocketClient\Protokol$ = "wss" ; If we connect with encryption (https)
UseNetworkTLS()
*WebsocketClient\Connection = OpenNetworkConnection(*WebsocketClient\Servername$, *WebsocketClient\Port, #PB_Network_TCP|#PB_Network_TLSv1, 1000)
ElseIf *WebsocketClient\Protokol$ = "ws"
*WebsocketClient\Connection = OpenNetworkConnection(*WebsocketClient\Servername$, *WebsocketClient\Port, #PB_Network_TCP, 1000)
EndIf
If *WebsocketClient\Connection
If Handshake(*WebsocketClient)
dbg("Connection and Handshake ok")
Else
dbg("Handshake-Error")
CloseNetworkConnection(*WebsocketClient\Connection)
*WebsocketClient\Connection = 0
EndIf
Else
dbg("Couldn't connect")
EndIf
ProcedureReturn *WebsocketClient\Connection
EndProcedure
Procedure.i SendFrame(*WebsocketClient.WebsocketClient_Structure, Opcode.i, *Payload, PayloadSize.q, Masked.i=#True)
Protected.i Pos, Size, i, Result, HeaderSize
Protected.i Dim Mask.a(3)
Protected *SendBuffer
dbg("PayloadSize to send: " + Str(PayloadSize))
; The Framebuffer, we fill with senddata
If PayloadSize <= 125
HeaderSize = 2
ElseIf PayloadSize >= 126 And PayloadSize <= 65535
HeaderSize = 4
Else
HeaderSize = 10
EndIf
If Masked
HeaderSize + 4
EndIf
dbg("Headersize to send: " + Str(HeaderSize))
*SendBuffer = AllocateMemory(HeaderSize + PayloadSize)
If *SendBuffer
PokeA(*SendBuffer, Opcode | $80)
Pos = 1 ; The byteposition in the framebuffer
; Second Byte: Masking(1),length(to 125bytes, else we have to extend)
If PayloadSize <= 125 ; Length fits in first byte
PokeA(*Sendbuffer + Pos, PayloadSize) : Pos + 1
ElseIf PayloadSize >= 126 And PayloadSize <= 65535 ; We have to extend length to third byte
PokeA(*Sendbuffer + Pos, 126) : Pos + 1 ; 126 For 2 extra length bytes
PokeA(*SendBuffer + Pos, (PayloadSize >> 8)) : Pos + 1 ; First Byte
PokeA(*SendBuffer + Pos, PayloadSize) : Pos + 1 ; Second Byte
Else ; It's bigger than 65535, we use 8 extra bytes
PokeA(*Sendbuffer + Pos, 127) : Pos + 1 ; 127 for 8 extra length bytes
PokeA(*SendBuffer + Pos, (PayloadSize >> 56) & $FF) : Pos + 1
PokeA(*SendBuffer + Pos, (PayloadSize >> 48) & $FF) : Pos + 1
PokeA(*SendBuffer + Pos, (PayloadSize >> 40) & $FF) : Pos + 1
PokeA(*SendBuffer + Pos, (PayloadSize >> 32) & $FF) : Pos + 1
PokeA(*Sendbuffer + Pos, (PayloadSize >> 24) & $FF) : Pos + 1
PokeA(*Sendbuffer + Pos, (PayloadSize >> 16) & $FF) : Pos + 1
PokeA(*Sendbuffer + Pos, (PayloadSize >> 8) & $FF) : Pos + 1
PokeA(*Sendbuffer + Pos, (PayloadSize & $FF)) : Pos + 1
EndIf
If Not Masked
CopyMemory(*Payload, *SendBuffer + Pos, PayloadSize)
Else
; We generate 4 random masking bytes
PokeA(*SendBuffer + 1, PeekA(*SendBuffer + 1) | $80) ; set mask bit
Mask(0) = Random(255)
Mask(1) = Random(255)
Mask(2) = Random(255)
Mask(3) = Random(255)
; Write Masking Bytes
PokeA(*SendBuffer + Pos, Mask(0)) : Pos + 1
PokeA(*SendBuffer + Pos, Mask(1)) : Pos + 1
PokeA(*SendBuffer + Pos, Mask(2)) : Pos + 1
PokeA(*SendBuffer + Pos, Mask(3)) : Pos + 1
CopyMemory(*Payload, *SendBuffer + Pos, PayloadSize)
Size = PayloadSize - 1
For i = 0 To Size
PokeA(*SendBuffer + Pos + i, PeekA(*SendBuffer + Pos + i) ! Mask(i % 4))
Next
EndIf
;ShowMemoryViewer(*SendBuffer, MemorySize(*SendBuffer))
If SendNetworkData(*WebsocketClient\connection, *SendBuffer, MemorySize(*SendBuffer)) = MemorySize(*SendBuffer)
dbg("Frame send, Bytes: " + Str(MemorySize(*SendBuffer)))
Result = #True
EndIf
FreeMemory(*SendBuffer)
EndIf
ProcedureReturn Result
EndProcedure
Procedure.i SendPingFrame(*WebsocketClient.WebsocketClient_Structure)
Protected *Payload, PayloadSize.q, Result.i
If *WebsocketClient\Connection
*Payload = UTF8("Ping")
If *Payload
PayloadSize = MemorySize(*Payload) - 1
Result = SendFrame(*WebsocketClient, #Opcode_Ping, *Payload, PayloadSize, #True)
FreeMemory(*Payload)
EndIf
EndIf
ProcedureReturn Result
EndProcedure
Procedure.i SendTextFrame(*WebsocketClient.WebsocketClient_Structure, Text$, Masked.i=#True)
Protected PayloadSize.q, *Payload, HeaderSize.i, *SendBuffer, Result.i
If *WebsocketClient\Connection
; Put String in Buffer
*Payload = UTF8(Text$)
If *Payload
PayloadSize = MemorySize(*Payload) - 1
Result = SendFrame(*WebsocketClient, #Opcode_Text, *Payload, PayloadSize, Masked)
FreeMemory(*Payload)
EndIf
EndIf
ProcedureReturn Result
EndProcedure
Procedure.i SendBinaryFrame(*WebsocketClient.WebsocketClient_Structure, *Payload, Masked.i=#True)
Protected.i Result, HeaderSize
Protected.q PayloadSize
Protected *SendBuffer
If *WebsocketClient\Connection
If *Payload
PayloadSize = MemorySize(*Payload)
Result = SendFrame(*WebsocketClient, #Opcode_Binary, *Payload, PayloadSize, Masked)
EndIf
EndIf
ProcedureReturn Result
EndProcedure
Procedure GetPayload(*WebsocketClient.WebsocketClient_Structure, *FrameBuffer, Pos.i)
Protected.i Size, i
Protected Dim Mask.a(3)
*WebsocketClient\ReceiveBuffer = AllocateMemory(*WebsocketClient\frame_size, #PB_Memory_NoClear)
If *WebsocketClient\frame_masking = #False
CopyMemory(*FrameBuffer + Pos, *WebsocketClient\ReceiveBuffer, *WebsocketClient\frame_size)
Else
Mask(0) = PeekA(*FrameBuffer + Pos) : Pos + 1
Mask(1) = PeekA(*FrameBuffer + Pos) : Pos + 1
Mask(2) = PeekA(*FrameBuffer + Pos) : Pos + 1
Mask(3) = PeekA(*FrameBuffer + Pos) : Pos + 1
CopyMemory(*FrameBuffer + Pos, *WebsocketClient\ReceiveBuffer, *WebsocketClient\frame_size)
Size = *WebsocketClient\frame_size - 1
For i = 0 To Size
PokeA(*WebsocketClient\ReceiveBuffer + i, PeekA(*WebsocketClient\ReceiveBuffer + i) ! Mask(i % 4))
Next
EndIf
EndProcedure
Procedure.i ReceiveFrame(*WebsocketClient.WebsocketClient_Structure)
Protected.i Size, pos, TimeoutCounter
Protected.q PayloadSize
Protected *FrameBuffer, *pongbuffer
*FrameBuffer = AllocateMemory(65536, #PB_Memory_NoClear)
If *FrameBuffer
TimeoutCounter = 1000
Repeat
Size = ReceiveNetworkData(*WebsocketClient\connection, *FrameBuffer, MemorySize(*FrameBuffer))
If Size < 1
Delay(1)
TimeoutCounter - 1
Else
TimeoutCounter = 1000
EndIf
Until Size > -1 Or TimeoutCounter = 0
dbg("Received Frame, Bytes: " + Str(Size))
;ShowMemoryViewer(*FrameBuffer, Size)
If Size > 0
; Getting informations about package
If PeekA(*FrameBuffer) & %10000000 > #False
;dbg("Frame not fragmented")
*WebsocketClient\frame_fragmentation = #False
Else
dbg("Frame fragmented! This not supported for now!")
*WebsocketClient\frame_fragmentation = #True
EndIf
; Check masking
If PeekA(*FrameBuffer + 1) & %10000000 = 128
*WebsocketClient\frame_masking = #True
Else
*WebsocketClient\frame_masking = #False
EndIf
dbg("Masking: " + Str(*WebsocketClient\frame_masking))
PayloadSize = PeekA(*FrameBuffer + 1) & $7F
Pos = 2
If PayloadSize > 125
If PayloadSize = 126
PayloadSize = (PeekA(*FrameBuffer + 2) << 8) | PeekA(*FrameBuffer + 3)
Pos = 4
Else
PayloadSize = (PeekA(*FrameBuffer + 2) << 56) | (PeekA(*FrameBuffer + 3) << 48) | (PeekA(*FrameBuffer + 4) << 40) | (PeekA(*FrameBuffer + 5) << 32) | (PeekA(*FrameBuffer + 6) << 24) | (PeekA(*FrameBuffer + 7) << 16) | (PeekA(*FrameBuffer + 8) << 8) | PeekA(*FrameBuffer + 9)
Pos = 10
EndIf
EndIf
*WebsocketClient\frame_size = PayloadSize
dbg("PayloadSize: " + Str(PayloadSize))
; Check for Opcodes
*WebsocketClient\frame_type = PeekA(*FrameBuffer) & $0F
Select *WebsocketClient\frame_type
Case #Opcode_Continue
Case #Opcode_Text
dbg("Text frame")
GetPayload(*WebsocketClient, *FrameBuffer, Pos)
Case #Opcode_Binary
dbg("Binary frame")
GetPayload(*WebsocketClient, *FrameBuffer, Pos)
Case #Opcode_Closing
dbg("Closing frame")
If PayloadSize > 1
*WebsocketClient\Status = PeekA(*FrameBuffer + Pos) << 8
Pos + 1
*WebsocketClient\Status | PeekA(*FrameBuffer + Pos)
Pos + 1
PayloadSize - 2
If PayloadSize > 0
*WebsocketClient\Status$ = PeekS(*FrameBuffer + Pos, PayloadSize, #PB_UTF8|#PB_ByteLength)
Else
*WebsocketClient\Status$ = ""
EndIf
EndIf
SendCloseFrame(*WebsocketClient)
CloseNetworkConnection(*WebsocketClient\Connection)
*WebsocketClient\Connection = 0
Case #Opcode_Ping
dbg("Ping frame, answere with Pong")
PokeA(*FrameBuffer + 0, $8A) ; FIN bit and opcode for pong
SendNetworkData(*WebsocketClient\Connection, *FrameBuffer, Size)
Case #Opcode_Pong
dbg("Pong frame")
GetPayload(*WebsocketClient, *FrameBuffer, Pos)
Default
dbg("Opcode unknown")
*WebsocketClient\frame_type = #Opcode_Unknown
EndSelect
EndIf
FreeMemory(*FrameBuffer)
EndIf
ProcedureReturn *WebsocketClient\frame_type
EndProcedure
Procedure.i SendCloseFrame(*WebsocketClient.WebsocketClient_Structure, Status.i=#StatusCode_NormalClosure)
Protected Result.i, *Buffer
If *WebsocketClient\Connection
*Buffer = AllocateMemory(8)
If *Buffer
PokeA(*Buffer + 0, $88) ; FIN bit + opcode x8
PokeA(*Buffer + 1, $82) ; MASK bit + 2 bytes payload
; MASK bytes
PokeA(*Buffer + 2, Random(255))
PokeA(*Buffer + 3, Random(255))
PokeA(*Buffer + 4, Random(255))
PokeA(*Buffer + 5, Random(255))
; masked status in network byte order
PokeA(*Buffer + 6, (Status >> 8) ! PeekA(*Buffer + 2))
PokeA(*Buffer + 7, (Status & $FF) ! PeekA(*Buffer + 3))
If SendNetworkData(*WebsocketClient\Connection, *Buffer, MemorySize(*Buffer)) = MemorySize(*Buffer)
Result = #True
EndIf
FreeMemory(*Buffer)
EndIf
EndIf
ProcedureReturn Result
EndProcedure
Procedure CloseConnection(*WebsocketClient.WebsocketClient_Structure, Status.i=#StatusCode_NormalClosure)
If SendCloseFrame(*WebsocketClient, Status)
ReceiveFrame(*WebsocketClient)
EndIf
If *WebsocketClient\Connection
CloseNetworkConnection(*WebsocketClient\Connection)
*WebsocketClient\Connection = #Null
EndIf
EndProcedure
EndModule
CompilerIf #PB_Compiler_IsMainFile
;-Demo
; Minimal example to send and receive textmessages
; The preconfigured testserver "echo.websocket.org" will just echo back everything you've send.
EnableExplicit
Enumeration Gadgets
#String_send
#String_url
#Button_connect
#Frame3D_io
#Button_send
#Checkbox_Masked
#Button_Ping
#ListView_Output
EndEnumeration
Define.i NetworkEvent, disconnected, event
Define Send$
Define WebsocketClient.WebsocketClient::WebsocketClient_Structure
OpenWindow(0, 0, 0, 600, 400, "Websocketclient :: Test-GUI", #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget | #PB_Window_ScreenCentered | #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget | #PB_Window_ScreenCentered)
StringGadget(#String_url, 10, 10, 470, 25, "wss://echo.websocket.org")
;StringGadget(#String_url, 10, 10, 470, 25, "wss://doko-cafe.de/test")
ButtonGadget(#Button_connect, 490, 10, 100, 25, "Connect")
FrameGadget(#Frame3D_io, 10, 40, 580, 350, "Input/Output")
StringGadget(#String_send, 20, 60, 400, 25, "")
CheckBoxGadget(#Checkbox_Masked, 430, 60, 50, 20, "Mask")
SetGadgetState(#Checkbox_Masked, #True)
ButtonGadget(#Button_send, 490, 60, 90, 25, "Send")
DisableGadget(#Button_send, #True)
ButtonGadget(#Button_Ping, 490, 90, 90, 25, "Ping")
DisableGadget(#Button_Ping, #True)
ListViewGadget(#ListView_Output, 20, 130, 560, 250)
Repeat
event = WaitWindowEvent(1)
Select event
Case #PB_Event_CloseWindow
Break
Case #PB_Event_Menu
Select EventMenu()
EndSelect
Case #PB_Event_Gadget
Select EventGadget()
Case #Button_connect
If GetGadgetText(#Button_connect) = "Connect"
Debug "Connect clicked"
WebsocketClient\Status = 0
WebsocketClient\Status$ = ""
If WebsocketClient::OpenConnection(@WebsocketClient, GetGadgetText(#String_url))
AddGadgetItem(#ListView_Output, -1, "# Connected to " + GetGadgetText(#String_url))
SetGadgetText(#Button_connect, "Disconnect")
If GetGadgetText(#String_send) <> ""
DisableGadget(#Button_send, #False)
EndIf
DisableGadget(#Button_Ping, #False)
EndIf
Else
WebsocketClient::CloseConnection(@WebsocketClient)
If WebsocketClient\Status > 0
AddGadgetItem(#ListView_Output, -1, "< Closing status: " + Str(WebsocketClient\Status) + " " + WebsocketClient\Status$)
EndIf
SetGadgetText(#Button_connect, "Connect")
DisableGadget(#Button_send, #True)
DisableGadget(#Button_Ping, #True)
EndIf
Case #String_send
If EventType() = #PB_EventType_Change
If GetGadgetText(#String_send) <> ""
If WebsocketClient\Connection
DisableGadget(#Button_send, #False)
EndIf
Else
DisableGadget(#Button_send, #True)
EndIf
EndIf
Case #Button_send
If Len(GetGadgetText(#String_send)) > 0 And WebsocketClient\connection
Debug "Send clicked"
If WebsocketClient::SendTextFrame(@WebsocketClient, GetGadgetText(#String_send), GetGadgetState(#Checkbox_Masked)) = #False
;If WebsocketClient::SendTextFrame(@WebsocketClient, "<-" + Space(800) + "->", GetGadgetState(#Checkbox_Masked)) = #False
Debug "Couldn't send. Are we disconnected?"
Else
AddGadgetItem(#ListView_Output, -1, "> " + GetGadgetText(#String_send))
EndIf
EndIf
Case #Button_Ping
If WebsocketClient::SendPingFrame(@WebsocketClient)
AddGadgetItem(#ListView_Output, -1, "> Ping sent")
EndIf
EndSelect
EndSelect
If WebsocketClient\connection
NetworkEvent = NetworkClientEvent(WebsocketClient\connection)
Select NetworkEvent
Case #PB_NetworkEvent_None
Case #PB_NetworkEvent_Data
Debug "NetworkEvent_Data"
WebsocketClient::ReceiveFrame(@WebsocketClient)
If WebsocketClient\ReceiveBuffer
If WebsocketClient\frame_type = WebsocketClient::#Opcode_Text
Debug "< " + PeekS(WebsocketClient\ReceiveBuffer, MemorySize(WebsocketClient\ReceiveBuffer), #PB_UTF8|#PB_ByteLength)
AddGadgetItem(#ListView_Output, -1, "< " + PeekS(WebsocketClient\ReceiveBuffer, MemorySize(WebsocketClient\ReceiveBuffer), #PB_UTF8|#PB_ByteLength))
ElseIf WebsocketClient\frame_type = WebsocketClient::#Opcode_Binary
AddGadgetItem(#ListView_Output, -1, "< Received Binaryframe" )
ElseIf WebsocketClient\frame_type = WebsocketClient::#Opcode_Pong
Debug "< " + PeekS(WebsocketClient\ReceiveBuffer, MemorySize(WebsocketClient\ReceiveBuffer), #PB_UTF8|#PB_ByteLength)
AddGadgetItem(#ListView_Output, -1, "< " + PeekS(WebsocketClient\ReceiveBuffer, MemorySize(WebsocketClient\ReceiveBuffer), #PB_UTF8|#PB_ByteLength))
EndIf
FreeMemory(WebsocketClient\ReceiveBuffer)
WebsocketClient\ReceiveBuffer = #Null
Else
If WebsocketClient\frame_type = WebsocketClient::#Opcode_Closing
SetGadgetText(#Button_connect, "Connect")
DisableGadget(#Button_send, #True)
DisableGadget(#Button_Ping, #True)
AddGadgetItem(#ListView_Output, -1, "< Closing status: " + Str(WebsocketClient\Status) + " " + WebsocketClient\Status$)
EndIf
EndIf
Case #PB_NetworkEvent_Disconnect
If WebsocketClient\Connection
Debug "NetworkEvent_Disconnect"
CloseNetworkConnection(WebsocketClient\Connection)
EndIf
SetGadgetText(#Button_connect, "Connect")
DisableGadget(#Button_send, #True)
DisableGadget(#Button_Ping, #True)
WebsocketClient\Connection = 0
EndSelect
EndIf
ForEver
CompilerEndIf
wss://demo.piesocket.com/v3/channel_123?api_key=VCXCEuvhGcBDP7XhiJJUDvR1e1D3eiVjgZ9VRiaV¬ify_self