Since Chatterbox does not have TLS service, according to Apple’s 2024 specification, any connection without TLS will be rejected for audit, so I used plugin.solarwebsockets to replace the Chatterbox function.
After three days of research, I finally successfully added TLS service to plugin.solarwebsockets.
First of all, I would like to thank joehinkle11 for making such a great plugin, and I would also like to thank develephant for teaching me the core operation of the server in the past.
The following tutorial is only for adding TLS service, which still depends on plugin.solarwebsockets, so you must refer to the API in the plugin to achieve other functions you want.
Since solarwebsockets itself does not support TLS server, you must add a JS configuration file to the server.
Next, I will introduce it in 3 parts, namely ubuntu, the js configuration file inside, and the logic used by the front-end solar2d.
Since I live in Taiwan, the annotation will be written in the language I am used to, and those who need it will be translate.
3 Likes
★首先是ubuntu的介紹,要先開起 port 443 然後支援Ubuntu 24 此外我已經在Ipad與Android手機和Mac電腦共同連線測試過了,目前我使用的是Lightsail但EC2大概也沒問題。
sudo passwd ubuntu
mkdir ~/solar-server
sudo nano /home/ubuntu/solar-server/yourdomain.key ### 將憑證添加進去
sudo nano /home/ubuntu/solar-server/yourdomain.crt
sudo apt update && sudo apt upgrade -y
sudo apt install curl git build-essential -y
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
node -v
npm -v
cd ~/solar-server
放入 tls_server.js ### 你可以使用WinSCP將檔案直接丟入或使用sudo nano編寫
npm init -y
npm install ws
sudo systemctl restart ssh
sudo node tls_server.js
1 Like
接著是JS檔案,要使用TLS服務的話,你必須編寫JS檔案
const fs = require('fs');
const https = require('https');
const WebSocket = require('ws');
// 讀取你的 key & cert 並確保剛剛編寫是否有與他們相同的路徑
const serverOptions = {
key: fs.readFileSync('/home/ubuntu/solar-server/yourdomain.key'),
cert: fs.readFileSync('/home/ubuntu/solar-server/yourdomain.crt'),
};
// 建立 HTTPS 伺服器
const httpsServer = https.createServer(serverOptions, (req, res) => {
res.writeHead(200);
res.end('WebSocket TLS Server is running\n');
});
const wss = new WebSocket.Server({ server: httpsServer });
const clients = new Map(); // 儲存所有連線使用者
function heartbeat() { // 伺服器端自動心跳清理 防止斷線卡住
this.isAlive = true;
}
function sendRoomUserList(TheRoomName) {
const usersInRoom = [];
wss.clients.forEach((EachClient) => {
if (EachClient.readyState === WebSocket.OPEN && EachClient.MyRoomName === TheRoomName) {
usersInRoom.push(EachClient.MyGameID);
}
});
const payload = JSON.stringify(["房間列表",usersInRoom]);
wss.clients.forEach((EachClient) => {
if (EachClient.readyState === WebSocket.OPEN && EachClient.MyRoomName === TheRoomName) {
EachClient.send(payload);
}
});
}
wss.on("connection", (URL_Data, TheReData) => { // URL_Data URL+自己給的名稱和房間號碼和其他想添加資料 , TheReData單純是習慣回應的寫法
//////////////////初始登入後的參數使用//////////////////
const params = new URLSearchParams(TheReData.url.replace(/^.*\?/, ""));
const MyGameID = params.get("MyGameID");
const MyRoomName = params.get("MyRoomName");
URL_Data.MyGameID = MyGameID;
URL_Data.MyRoomName = MyRoomName;
if (clients.has(MyGameID)) {
const oldSocket = clients.get(MyGameID);
console.log("⚠️ 重複登入,踢掉舊連線:", MyGameID); // 通知舊的連線
oldSocket.send(JSON.stringify(["剔除","您已從其他裝置登入"]));
oldSocket.terminate(); // 關閉舊連線
}
clients.set(MyGameID, URL_Data);
console.log(`🔌 ${MyGameID} joined room: ${MyRoomName}`); // 當你開起伺服器時 你可以藉由即時的log檔案來看自己有沒有成功連線
//////////////////初始登入後的參數使用//////////////////
//////////////////閃退檢查機制//////////////////
URL_Data.isAlive = true
URL_Data.on("pong", heartbeat);
//////////////////閃退檢查機制//////////////////
sendRoomUserList(MyRoomName); // 把房間內有誰的所有資料傳過去
//////////////////關閉檢查//////////////////
URL_Data.on("close", () => {
if (clients.get(MyGameID) === URL_Data) { // 防止踢錯人
clients.delete(MyGameID);
}
sendRoomUserList(MyRoomName); // 有人離開所以重新傳一次房間資料
});
//////////////////關閉檢查//////////////////
////////////////////////////////////訊息////////////////////////////////////
URL_Data.on("message", (message) => {
try {
const data = JSON.parse(message);
if (data.type === "chat" && typeof data.text === "string") { // 只廣播給同房玩家
wss.clients.forEach((EachClient) => {
if (EachClient.readyState === WebSocket.OPEN && EachClient.MyRoomName === MyRoomName) {
EachClient.send(JSON.stringify(["chat",data.text])); // 靠字串分解就能讀出發送者是誰 沒必要為了黑名單多傳一筆資料
}
});
}
} catch (e) {
console.error("Invalid message format:", message);
}
});
////////////////////////////////////訊息////////////////////////////////////
});
setInterval(() => {
wss.clients.forEach((EachClient) => {
if (!EachClient.missedHeartbeats) EachClient.missedHeartbeats = 0;
if (EachClient.isAlive === false) {
EachClient.missedHeartbeats++;
if (EachClient.missedHeartbeats >= 3) {
console.log("超過 3 次未回應,踢除:", EachClient.MyGameID);
return EachClient.terminate();
}
} else {
EachClient.missedHeartbeats = 0;
}
EachClient.isAlive = false;
EachClient.ping();
});
}, 40000); // 每 40 秒檢查一次
httpsServer.listen(443, () => {
console.log('TLS WebSocket server started on port 443');
});
1 Like
最後則是Solar2D前端的編寫
local solarwebsockets = require "plugin.solarwebsockets"
local json = require("json")
local function wsListener(event) -- init 只能跑一次 最好放在main.lua 並且考慮使用全域變數 這樣你就能靠connect切換伺服器了
if event.name == "join" then
print("加入後會馬上觸發message的房間列表")
elseif event.name == "message" then
local TheData = json.decode(event.message)
if TheData[1] == "房間列表" then
for k,v in pairs(TheData[2]) do
print("檢查房間所有人資料:", k,v)
end
elseif TheData[1] == "chat" then
native.showAlert( "測試聊天資料", TheData[2],{"Ok"} )
elseif TheData[1] == "剔除" then
solarwebsockets.disconnect()
native.showAlert( "被踢掉了", "被踢掉了",{"Ok"} )
end
elseif event.name == "leave" then
print("離開")
end
end
solarwebsockets.init(wsListener)
local MyGameID = "Player:"..(math.random(1,99999))
local GoConnect = display.newText( "GoConnect", 320, 540, native.systemFont, 30 )
local function GoConnectFun(event)
if event.numTaps == 1 then
local MyURL = "wss://yourdomain:443" -- 替換成你伺服器的 wss URL
local MyRoomName = "World"
solarwebsockets.connect(MyURL.."?MyGameID=" .. MyGameID .. "&MyRoomName=" .. MyRoomName)
end
end
GoConnect:addEventListener("tap", GoConnectFun)
local GoDis = display.newText( "GoDis", 900, 540, native.systemFont, 30 )
local function GoDisFun(event)
if event.numTaps == 1 then
solarwebsockets.disconnect()
end
end
GoDis:addEventListener("tap", GoDisFun)
local GoChat = display.newText( "GoChat", 1500, 540, native.systemFont, 30 )
local function GoChatFun(event)
if event.numTaps == 1 then
local chatMsg = {
type = "chat",
text = "哈囉大家好!★:"..MyGameID
}
solarwebsockets.sendServer(json.encode(chatMsg))
end
end
GoChat:addEventListener("tap", GoChatFun)
若您不確定自己的網域是否有新增TLS的話可以將domain放入SSL Lab進行檢測。
若要添加其他功能請參考原始網站https://github.com/joehinkle11/SolarWebSockets/tree/master
2 Likes