730 lines
23 KiB
C#
730 lines
23 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Text.RegularExpressions;
|
|
using UnityEngine;
|
|
using UnityEngine.Events;
|
|
using UnityEngine.Networking;
|
|
using WebSocketSharp;
|
|
using Newtonsoft.Json;
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
namespace WefLab
|
|
{
|
|
/// <summary>
|
|
/// Donation data structure
|
|
/// </summary>
|
|
[System.Serializable]
|
|
public class DonationData
|
|
{
|
|
public string platform;
|
|
public string donationType;
|
|
public int amount;
|
|
public string message;
|
|
public string donorName;
|
|
public long timestamp;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unity event with donation data parameter
|
|
/// </summary>
|
|
[System.Serializable]
|
|
public class DonationEvent : UnityEvent<DonationData> { }
|
|
|
|
/// <summary>
|
|
/// Donation event trigger based on amount range
|
|
/// </summary>
|
|
[System.Serializable]
|
|
public class DonationEventTrigger
|
|
{
|
|
[Tooltip("Name for this donation trigger (e.g., 'Small Donation', 'Large Donation')")]
|
|
public string triggerName = "New Trigger";
|
|
|
|
[Header("Amount Range")]
|
|
[Tooltip("Minimum donation amount (inclusive)")]
|
|
public int minAmount = 0;
|
|
|
|
[Tooltip("Maximum donation amount (inclusive, -1 for unlimited)")]
|
|
public int maxAmount = -1;
|
|
|
|
[Header("Event Settings")]
|
|
[Tooltip("Number of times to repeat the event (minimum 1)")]
|
|
[Min(1)]
|
|
public int repeatCount = 1;
|
|
|
|
[Tooltip("Delay between each repeat in seconds (0 for immediate)")]
|
|
[Min(0)]
|
|
public float repeatDelay = 0f;
|
|
|
|
[Header("Event")]
|
|
[Tooltip("Unity event to trigger when donation amount is in range")]
|
|
public DonationEvent onDonation;
|
|
|
|
/// <summary>
|
|
/// Check if donation amount is within range
|
|
/// </summary>
|
|
public bool IsInRange(int amount)
|
|
{
|
|
if (amount < minAmount)
|
|
return false;
|
|
|
|
if (maxAmount >= 0 && amount > maxAmount)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// WefLab WebSocket client for receiving donation events
|
|
/// Implements Engine.IO v4 and Socket.IO protocol
|
|
/// </summary>
|
|
public class WefLabWebSocketClient : MonoBehaviour
|
|
{
|
|
[Header("WefLab Settings")]
|
|
[Tooltip("WefLab page URL (e.g., https://weflab.com/page/guPK2ODAmmRvYG4)")]
|
|
public string pageUrl = "https://weflab.com/page/guPK2ODAmmRvYG4";
|
|
|
|
[Header("Donation Event Triggers")]
|
|
[Tooltip("Donation event triggers based on amount ranges")]
|
|
public DonationEventTrigger[] donationTriggers = Array.Empty<DonationEventTrigger>();
|
|
|
|
// Hidden connection info
|
|
[HideInInspector] public bool isConnected = false;
|
|
[HideInInspector] public string currentSid = "";
|
|
[HideInInspector] public string extractedUserIdx = "";
|
|
|
|
// WebSocket connection
|
|
private WebSocket ws;
|
|
private string userIdx = ""; // Will be extracted from page
|
|
private string socketSid = "";
|
|
private bool isExtracting = false;
|
|
|
|
// Ping/Pong settings
|
|
private float pingInterval = 30f;
|
|
private float lastPingTime = 0f;
|
|
|
|
// Thread-safe action queue for main thread
|
|
private Queue<Action> mainThreadActions = new Queue<Action>();
|
|
private object actionLock = new object();
|
|
|
|
void Start()
|
|
{
|
|
// Extract user idx from page URL and then connect
|
|
StartCoroutine(ExtractUserIdxAndConnect());
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
// Execute queued actions on main thread
|
|
lock (actionLock)
|
|
{
|
|
while (mainThreadActions.Count > 0)
|
|
{
|
|
mainThreadActions.Dequeue()?.Invoke();
|
|
}
|
|
}
|
|
}
|
|
|
|
void OnDestroy()
|
|
{
|
|
Disconnect();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extract user index from page URL and connect
|
|
/// </summary>
|
|
private IEnumerator ExtractUserIdxAndConnect()
|
|
{
|
|
if (string.IsNullOrEmpty(pageUrl))
|
|
{
|
|
Debug.LogError("[WefLab] Page URL is empty! Please set the page URL in Inspector.");
|
|
yield break;
|
|
}
|
|
|
|
isExtracting = true;
|
|
Debug.Log($"[WefLab] Fetching page URL: {pageUrl}");
|
|
|
|
// Fetch the page HTML
|
|
using (UnityWebRequest request = UnityWebRequest.Get(pageUrl))
|
|
{
|
|
yield return request.SendWebRequest();
|
|
|
|
if (request.result != UnityWebRequest.Result.Success)
|
|
{
|
|
Debug.LogError($"[WefLab] Failed to fetch page: {request.error}");
|
|
isExtracting = false;
|
|
yield break;
|
|
}
|
|
|
|
string htmlContent = request.downloadHandler.text;
|
|
|
|
// Extract userIdx from JavaScript in the page
|
|
// Look for pattern: loginData = {...}
|
|
Match loginDataMatch = Regex.Match(htmlContent, @"loginData\s*=\s*(\{[^;]+\});", RegexOptions.Singleline);
|
|
|
|
if (loginDataMatch.Success)
|
|
{
|
|
try
|
|
{
|
|
string loginDataJson = loginDataMatch.Groups[1].Value;
|
|
var loginData = JsonConvert.DeserializeObject<JObject>(loginDataJson);
|
|
|
|
// Extract idx from loginData
|
|
userIdx = loginData["idx"]?.ToString() ?? "";
|
|
|
|
if (!string.IsNullOrEmpty(userIdx))
|
|
{
|
|
extractedUserIdx = userIdx;
|
|
Debug.Log($"[WefLab] Successfully extracted userIdx: {userIdx}");
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError("[WefLab] userIdx not found in loginData");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"[WefLab] Error parsing loginData: {ex.Message}");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError("[WefLab] Could not find loginData in page HTML");
|
|
}
|
|
}
|
|
|
|
isExtracting = false;
|
|
|
|
// Connect if we successfully extracted the userIdx
|
|
if (!string.IsNullOrEmpty(userIdx))
|
|
{
|
|
Connect();
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError("[WefLab] Cannot connect - userIdx extraction failed");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Connect to WefLab WebSocket server
|
|
/// </summary>
|
|
public void Connect()
|
|
{
|
|
if (ws != null && ws.IsAlive)
|
|
{
|
|
Debug.LogWarning("[WefLab] Already connected");
|
|
return;
|
|
}
|
|
|
|
// Build WebSocket URL
|
|
string wsUrl = $"wss://ssmain.weflab.com/socket.io/?idx={userIdx}&type=page&page=alert&EIO=4&transport=websocket";
|
|
|
|
Debug.Log($"[WefLab] Connecting to: {wsUrl}");
|
|
|
|
ws = new WebSocket(wsUrl);
|
|
|
|
// Event handlers
|
|
ws.OnOpen += OnWebSocketOpen;
|
|
ws.OnMessage += OnWebSocketMessage;
|
|
ws.OnError += OnWebSocketError;
|
|
ws.OnClose += OnWebSocketClose;
|
|
|
|
// Connect
|
|
ws.ConnectAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disconnect from WebSocket
|
|
/// </summary>
|
|
public void Disconnect()
|
|
{
|
|
if (ws != null)
|
|
{
|
|
ws.Close();
|
|
ws = null;
|
|
}
|
|
|
|
isConnected = false;
|
|
socketSid = "";
|
|
currentSid = "";
|
|
}
|
|
|
|
#region WebSocket Event Handlers
|
|
|
|
private void OnWebSocketOpen(object sender, EventArgs e)
|
|
{
|
|
EnqueueMainThreadAction(() =>
|
|
{
|
|
Debug.Log("[WefLab] WebSocket connection opened");
|
|
});
|
|
}
|
|
|
|
private void OnWebSocketMessage(object sender, MessageEventArgs e)
|
|
{
|
|
string data = e.Data;
|
|
|
|
EnqueueMainThreadAction(() =>
|
|
{
|
|
ProcessMessage(data);
|
|
});
|
|
}
|
|
|
|
private void OnWebSocketError(object sender, ErrorEventArgs e)
|
|
{
|
|
EnqueueMainThreadAction(() =>
|
|
{
|
|
Debug.LogError($"[WefLab] WebSocket error: {e.Message}");
|
|
if (e.Exception != null)
|
|
{
|
|
Debug.LogError($"[WefLab] Exception: {e.Exception}");
|
|
}
|
|
});
|
|
}
|
|
|
|
private void OnWebSocketClose(object sender, CloseEventArgs e)
|
|
{
|
|
EnqueueMainThreadAction(() =>
|
|
{
|
|
Debug.Log($"[WefLab] WebSocket closed. Code: {e.Code}, Reason: {e.Reason}");
|
|
isConnected = false;
|
|
socketSid = "";
|
|
currentSid = "";
|
|
});
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Message Processing
|
|
|
|
/// <summary>
|
|
/// Process incoming WebSocket message
|
|
/// </summary>
|
|
private void ProcessMessage(string data)
|
|
{
|
|
if (string.IsNullOrEmpty(data))
|
|
return;
|
|
|
|
// Get message type (first character or first two characters)
|
|
string messageType = data.Length >= 2 && char.IsDigit(data[1])
|
|
? data.Substring(0, 2)
|
|
: data.Substring(0, 1);
|
|
|
|
string payload = data.Substring(messageType.Length);
|
|
|
|
switch (messageType)
|
|
{
|
|
case "0": // Engine.IO OPEN
|
|
HandleEngineOpen(payload);
|
|
break;
|
|
|
|
case "2": // Engine.IO PING
|
|
HandlePing();
|
|
break;
|
|
|
|
case "3": // Engine.IO PONG
|
|
Debug.Log("[WefLab] Received PONG");
|
|
break;
|
|
|
|
case "40": // Socket.IO CONNECT
|
|
HandleSocketConnect(payload);
|
|
break;
|
|
|
|
case "42": // Socket.IO EVENT
|
|
HandleSocketEvent(payload);
|
|
break;
|
|
|
|
default:
|
|
Debug.Log($"[WefLab] Unknown message type: {messageType} | Data: {data}");
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle Engine.IO OPEN message (handshake)
|
|
/// </summary>
|
|
private void HandleEngineOpen(string payload)
|
|
{
|
|
try
|
|
{
|
|
var openData = JsonConvert.DeserializeObject<JObject>(payload);
|
|
currentSid = openData["sid"]?.ToString() ?? "";
|
|
|
|
if (openData["pingInterval"] != null)
|
|
{
|
|
pingInterval = openData["pingInterval"].Value<float>() / 1000f; // Convert to seconds
|
|
}
|
|
|
|
Debug.Log($"[WefLab] Engine.IO OPEN - SID: {currentSid}, PingInterval: {pingInterval}s");
|
|
Debug.Log($"[WefLab] Full handshake data: {payload}");
|
|
|
|
// Send Socket.IO CONNECT
|
|
SendSocketConnect();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"[WefLab] Error parsing OPEN message: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle Engine.IO PING
|
|
/// </summary>
|
|
private void HandlePing()
|
|
{
|
|
Debug.Log("[WefLab] Received PING, sending PONG");
|
|
SendMessage("3"); // Send PONG
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle Socket.IO CONNECT response
|
|
/// </summary>
|
|
private void HandleSocketConnect(string payload)
|
|
{
|
|
try
|
|
{
|
|
if (!string.IsNullOrEmpty(payload))
|
|
{
|
|
var connectData = JsonConvert.DeserializeObject<JObject>(payload);
|
|
socketSid = connectData["sid"]?.ToString() ?? "";
|
|
Debug.Log($"[WefLab] Socket.IO CONNECT - SID: {socketSid}");
|
|
Debug.Log($"[WefLab] Full connect data: {payload}");
|
|
}
|
|
|
|
isConnected = true;
|
|
|
|
// Send join message
|
|
SendJoinMessage();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"[WefLab] Error parsing Socket.IO CONNECT: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle Socket.IO EVENT message (donation data)
|
|
/// </summary>
|
|
private void HandleSocketEvent(string payload)
|
|
{
|
|
try
|
|
{
|
|
var eventArray = JsonConvert.DeserializeObject<JArray>(payload);
|
|
|
|
if (eventArray == null || eventArray.Count < 2)
|
|
{
|
|
Debug.LogWarning($"[WefLab] Invalid event format: {payload}");
|
|
return;
|
|
}
|
|
|
|
string eventName = eventArray[0].ToString();
|
|
var eventData = eventArray[1] as JObject;
|
|
|
|
Debug.Log($"[WefLab] ========== EVENT RECEIVED ==========");
|
|
Debug.Log($"[WefLab] Event Name: {eventName}");
|
|
Debug.Log($"[WefLab] Event Data: {eventData?.ToString(Formatting.Indented)}");
|
|
Debug.Log($"[WefLab] ===================================");
|
|
|
|
if (eventName == "msg")
|
|
{
|
|
HandleMessageEvent(eventData);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"[WefLab] Error parsing Socket.IO EVENT: {ex.Message}");
|
|
Debug.LogError($"[WefLab] Payload: {payload}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle "msg" event (donation notifications)
|
|
/// </summary>
|
|
private void HandleMessageEvent(JObject msgData)
|
|
{
|
|
if (msgData == null)
|
|
return;
|
|
|
|
string msgType = msgData["type"]?.ToString() ?? "";
|
|
|
|
switch (msgType)
|
|
{
|
|
case "test_donation":
|
|
HandleTestDonation(msgData);
|
|
break;
|
|
|
|
case "donation":
|
|
case "SENDBALLOON":
|
|
case "cheese":
|
|
case "superchat":
|
|
case "bits":
|
|
HandleDonation(msgData);
|
|
break;
|
|
|
|
default:
|
|
Debug.Log($"[WefLab] Unhandled message type: {msgType}");
|
|
Debug.Log($"[WefLab] Data: {msgData.ToString(Formatting.Indented)}");
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Normalize donation amount to unified currency (Chzzk standard: 1 = 1 KRW)
|
|
/// SOOP (Afreeca): 1 balloon = 100 KRW → multiply by 100
|
|
/// Chzzk: 1 cheese = 1 KRW → no conversion
|
|
/// YouTube: Keep as-is (already in KRW equivalent)
|
|
/// Twitch: Keep as-is
|
|
/// </summary>
|
|
private static int NormalizeDonationAmount(string platform, int rawAmount)
|
|
{
|
|
platform = platform.ToLower();
|
|
|
|
if (platform == "afreeca" || platform == "soop")
|
|
{
|
|
// SOOP: 1 balloon = 100 KRW
|
|
return rawAmount * 100;
|
|
}
|
|
|
|
// Chzzk, YouTube, Twitch: no conversion needed
|
|
return rawAmount;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle test donation event
|
|
/// </summary>
|
|
private void HandleTestDonation(JObject msgData)
|
|
{
|
|
if (msgData["data"] is not JObject donationDataJson)
|
|
return;
|
|
|
|
string platform = donationDataJson["platform"]?.ToString() ?? "unknown";
|
|
string type = donationDataJson["type"]?.ToString() ?? "unknown";
|
|
int rawAmount = 0;
|
|
|
|
// Parse amount
|
|
if (int.TryParse(donationDataJson["value"]?.ToString(), out int parsedAmount))
|
|
{
|
|
rawAmount = parsedAmount;
|
|
}
|
|
|
|
// Normalize amount based on platform
|
|
int amount = NormalizeDonationAmount(platform, rawAmount);
|
|
|
|
string message = donationDataJson["msg"]?.ToString() ?? "";
|
|
long timestamp = donationDataJson["time"]?.Value<long>() ?? 0;
|
|
|
|
Debug.Log($"[WefLab] *** TEST DONATION ***");
|
|
Debug.Log($"[WefLab] Platform: {platform}");
|
|
Debug.Log($"[WefLab] Type: {type}");
|
|
Debug.Log($"[WefLab] Raw Amount: {rawAmount} → Normalized: {amount} KRW");
|
|
Debug.Log($"[WefLab] Message: {message}");
|
|
Debug.Log($"[WefLab] Timestamp: {timestamp}");
|
|
|
|
// Create donation data and trigger events
|
|
DonationData donation = new()
|
|
{
|
|
platform = platform,
|
|
donationType = type,
|
|
amount = amount,
|
|
message = message,
|
|
donorName = "Test Donor",
|
|
timestamp = timestamp
|
|
};
|
|
|
|
TriggerDonationEvents(donation);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle real donation event
|
|
/// </summary>
|
|
private void HandleDonation(JObject msgData)
|
|
{
|
|
Debug.Log($"[WefLab] *** REAL DONATION ***");
|
|
Debug.Log($"[WefLab] Full donation data: {msgData.ToString(Formatting.Indented)}");
|
|
|
|
if (msgData["data"] is not JObject donationDataJson)
|
|
return;
|
|
|
|
string platform = msgData["platform"]?.ToString() ?? "unknown";
|
|
string type = msgData["type"]?.ToString() ?? "unknown";
|
|
int rawAmount = 0;
|
|
|
|
// Parse amount from different possible fields
|
|
if (donationDataJson["value"] != null && int.TryParse(donationDataJson["value"].ToString(), out int parsedValue))
|
|
{
|
|
rawAmount = parsedValue;
|
|
}
|
|
else if (donationDataJson["amount"] != null && int.TryParse(donationDataJson["amount"].ToString(), out int parsedAmount))
|
|
{
|
|
rawAmount = parsedAmount;
|
|
}
|
|
|
|
// Normalize amount based on platform
|
|
int amount = NormalizeDonationAmount(platform, rawAmount);
|
|
|
|
string message = donationDataJson["msg"]?.ToString() ?? donationDataJson["message"]?.ToString() ?? "";
|
|
string donorName = donationDataJson["nickname"]?.ToString() ?? donationDataJson["name"]?.ToString() ?? "Anonymous";
|
|
long timestamp = donationDataJson["time"]?.Value<long>() ?? DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
|
|
|
// Create donation data
|
|
DonationData donation = new()
|
|
{
|
|
platform = platform,
|
|
donationType = type,
|
|
amount = amount,
|
|
message = message,
|
|
donorName = donorName,
|
|
timestamp = timestamp
|
|
};
|
|
|
|
Debug.Log($"[WefLab] Donation: {donorName} - {rawAmount} → {amount} KRW ({platform})");
|
|
|
|
// Trigger events
|
|
TriggerDonationEvents(donation);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Trigger donation events based on amount range
|
|
/// </summary>
|
|
private void TriggerDonationEvents(DonationData donation)
|
|
{
|
|
if (donationTriggers == null || donationTriggers.Length == 0)
|
|
{
|
|
Debug.LogWarning("[WefLab] No donation triggers configured");
|
|
return;
|
|
}
|
|
|
|
bool triggeredAny = false;
|
|
|
|
// Check each trigger
|
|
foreach (var trigger in donationTriggers)
|
|
{
|
|
if (trigger == null)
|
|
continue;
|
|
|
|
// Check if donation amount is in range
|
|
if (trigger.IsInRange(donation.amount))
|
|
{
|
|
Debug.Log($"[WefLab] Triggering: {trigger.triggerName} (Amount: {donation.amount}, Range: {trigger.minAmount}-{(trigger.maxAmount >= 0 ? trigger.maxAmount.ToString() : "unlimited")}, Repeat: {trigger.repeatCount}x)");
|
|
|
|
// Start coroutine to handle repeated invocation
|
|
StartCoroutine(InvokeRepeatedEvent(trigger, donation));
|
|
triggeredAny = true;
|
|
}
|
|
}
|
|
|
|
if (!triggeredAny)
|
|
{
|
|
Debug.LogWarning($"[WefLab] No triggers matched for donation amount: {donation.amount}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Coroutine to invoke event multiple times with delay
|
|
/// </summary>
|
|
private IEnumerator InvokeRepeatedEvent(DonationEventTrigger trigger, DonationData donation)
|
|
{
|
|
for (int i = 0; i < trigger.repeatCount; i++)
|
|
{
|
|
// Invoke the event
|
|
trigger.onDonation?.Invoke(donation);
|
|
|
|
// Wait for delay before next repetition (skip on last iteration)
|
|
if (i < trigger.repeatCount - 1 && trigger.repeatDelay > 0)
|
|
{
|
|
yield return new WaitForSeconds(trigger.repeatDelay);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Send Messages
|
|
|
|
/// <summary>
|
|
/// Send Socket.IO CONNECT message (40)
|
|
/// </summary>
|
|
private void SendSocketConnect()
|
|
{
|
|
Debug.Log("[WefLab] Sending Socket.IO CONNECT (40)");
|
|
SendMessage("40");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send join message to subscribe to donation events
|
|
/// </summary>
|
|
private void SendJoinMessage()
|
|
{
|
|
var joinData = new
|
|
{
|
|
type = "join",
|
|
page = "page",
|
|
idx = userIdx,
|
|
pageid = "alert",
|
|
preset = "0"
|
|
};
|
|
|
|
var message = new object[] { "msg", joinData };
|
|
string json = JsonConvert.SerializeObject(message);
|
|
string fullMessage = "42" + json;
|
|
|
|
Debug.Log($"[WefLab] Sending JOIN message: {fullMessage}");
|
|
SendMessage(fullMessage);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send raw message through WebSocket
|
|
/// </summary>
|
|
private void SendMessage(string message)
|
|
{
|
|
if (ws != null && ws.IsAlive)
|
|
{
|
|
ws.Send(message);
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning("[WefLab] Cannot send message - WebSocket not connected");
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Thread Safety
|
|
|
|
/// <summary>
|
|
/// Enqueue action to be executed on main thread
|
|
/// </summary>
|
|
private void EnqueueMainThreadAction(Action action)
|
|
{
|
|
lock (actionLock)
|
|
{
|
|
mainThreadActions.Enqueue(action);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public Methods
|
|
|
|
/// <summary>
|
|
/// Reconnect to WebSocket
|
|
/// </summary>
|
|
public void Reconnect()
|
|
{
|
|
Disconnect();
|
|
Connect();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if connected
|
|
/// </summary>
|
|
public bool IsConnected()
|
|
{
|
|
return isConnected && ws != null && ws.IsAlive;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|