Skip to content

Commit adcc2ac

Browse files
authored
Add files via upload
1 parent 377cb43 commit adcc2ac

File tree

1 file changed

+8
-6
lines changed

1 file changed

+8
-6
lines changed

ChemGame.html

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -129,22 +129,22 @@
129129
const isIpv6HostText=(h)=>{const s=String(h||"").trim();if(!s)return false;if(s.startsWith("[")&&s.endsWith("]"))return true;return s.includes(":")&&!/^[^:]+:\d+$/.test(s)};
130130
const isLocalHost=(h)=>{const s=String(h||"").trim().toLowerCase();return s==="localhost"||s==="127.0.0.1"||s==="::1"||s==="[::1]"||s==="0:0:0:0:0:0:0:1"};
131131
const pageIsHttps=(typeof location!=="undefined"&&location.protocol==="https:");
132-
const defaultWsProtoForHost=(_h)=>"ws";
133-
const resolveConnectProto=(_host,proto)=>((String(proto||"").toLowerCase()==="wss")?"wss":"ws");
132+
const defaultWsProtoForHost=(h)=>{const hs=String(h||"").trim().toLowerCase();if(pageIsHttps&&!isLocalHost(hs))return"wss";return"ws"};
133+
const resolveConnectProto=(host,proto)=>{let p=(String(proto||"").toLowerCase()==="wss")?"wss":"ws";if(p==="ws"&&pageIsHttps&&!isLocalHost(host))p="wss";return p};
134134
const normWsPath=(raw)=>{let p=String(raw??"").trim();if(!p||p==="/")return"";if(p.startsWith("?"))p=`/${p}`;if(!p.startsWith("/"))p=`/${p}`;p=p.replace(/\/{2,}/g,"/");return p};
135135
const hostSpec=(raw,fallbackProto="auto")=>{
136136
let s=String(raw??"").trim(),proto="",port=null,path="",protoExplicit=false;
137137
s=s.replace(/\uFF1A/g,":").replace(/\uFF0F/g,"/").replace(/\u3002/g,".").replace(/\uFF0E/g,".").replace(/\uFF0C/g,".").replace(/\u3000/g," ").trim();
138138
if(!s){
139-
const p=(fallbackProto==="auto")?"ws":fallbackProto;
139+
const p=(fallbackProto==="auto")?defaultWsProtoForHost("127.0.0.1"):fallbackProto;
140140
return{host:"127.0.0.1",proto:(String(p).toLowerCase()==="wss")?"wss":"ws",port:null,path:""};
141141
}
142142
const m=s.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):\/\/(.*)$/);
143143
if(m){
144144
protoExplicit=true;
145145
const sch=String(m[1]||"").toLowerCase();
146146
s=String(m[2]||"");
147-
if(sch==="ws"||sch==="wss")proto=sch;else if(sch==="http")proto="ws";else if(sch==="https")proto="ws";
147+
if(sch==="ws"||sch==="wss")proto=sch;else if(sch==="http")proto="ws";else if(sch==="https")proto="wss";
148148
}
149149
const slash=s.indexOf("/");
150150
if(slash>=0){path=s.slice(slash);s=s.slice(0,slash)}
@@ -162,10 +162,12 @@
162162
}
163163
s=s.trim().toLowerCase();
164164
if(!s)s="127.0.0.1";
165+
if(!proto&&port===443)proto="wss";
165166
if(!proto){
166167
const fb=(fallbackProto==="auto")?defaultWsProtoForHost(s):fallbackProto;
167168
proto=(String(fb).toLowerCase()==="wss")?"wss":"ws";
168169
}
170+
proto=resolveConnectProto(s,proto);
169171
if(!Number.isFinite(port)||port<=0)port=null;
170172
return{host:s,proto,port,path:normWsPath(path)};
171173
};
@@ -314,7 +316,7 @@
314316
}
315317
return dedupeBy(plans,(x)=>`${x.proto}|${nh(x.host)}|${i32(x.port,0)}|${normWsPath(x.path)}|${i32(x.roomPortHint,0)}`);
316318
};
317-
const FIREWALL_HINT="默认建议 WS/5000。若你通过 HTTPS 打开网页,浏览器可能阻止 ws:// 非本机地址;此时请改用 HTTP 页面或启用服务器 TLS 后使用 wss://。网页无法绕过设备防火墙策略本身。";
319+
const FIREWALL_HINT="默认优先 443/WSS(更不易被防火墙拦截);局域网或未配置 TLS 时可改用 WS/5000。若网页通过 HTTPS 打开,浏览器会阻止 ws:// 非本机地址。";
318320
class BrowserClientConnection{
319321
constructor({host,port,name,resumeIndex=null,resumeKey="",wsProto="ws",wsPath="",roomPortHint=0}){this.host=nh(host);this.port=i32(port,0);this.name=String(name||"玩家");this.ws_proto=(String(wsProto||"").toLowerCase()==="wss")?"wss":"ws";this.ws_path=normWsPath(wsPath);this.room_port_hint=i32(roomPortHint,0);this.session_port=this.room_port_hint>0?this.room_port_hint:this.port;this.ws=null;this.state=null;this.error=null;this.my_index=null;this.room_size=null;this.running=false;this.resume_index=resumeIndex;this.resume_key=String(resumeKey||"").trim();this._hbTimer=null}
320322
_send_packet(p){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)throw new Error("尚未连接到房间。");this.ws.send(JSON.stringify(p))}
@@ -370,7 +372,7 @@
370372
async _show_access(host,port){const d=mkDialog("房间信息"),body=d.querySelector(".mbody"),btns=d.querySelector(".mbtns");const l1=document.createElement("div");l1.className="mline";l1.innerHTML='<label>IP地址</label><textarea readonly></textarea>';l1.querySelector("textarea").value=String(host);body.appendChild(l1);const l2=document.createElement("div");l2.className="mline";l2.innerHTML='<label>端口</label><textarea readonly></textarea>';l2.querySelector("textarea").value=String(port);body.appendChild(l2);
371373
const row=document.createElement("div");row.className="mbtns";row.style.padding="0";const b1=document.createElement("button");b1.textContent="复制IP";b1.onclick=async()=>{try{await navigator.clipboard.writeText(String(host));this._status(`已复制IP:${host}`)}catch(_){this._status("IP复制失败。")}};const b2=document.createElement("button");b2.textContent="复制端口";b2.onclick=async()=>{try{await navigator.clipboard.writeText(String(port));this._status(`已复制端口:${port}`)}catch(_){this._status("端口复制失败。")}};row.appendChild(b1);row.appendChild(b2);body.appendChild(row);const note=document.createElement("div");note.className="mnote";note.textContent="房间信息仅在此处展示,请及时复制。";body.appendChild(note);
372374
btns.classList.add("single");const c=document.createElement("button");c.textContent="关闭";c.onclick=()=>closeDialog(d);btns.appendChild(c);d.addEventListener("cancel",e=>{e.preventDefault();closeDialog(d)});d.showModal()}
373-
async _open_remote(){const d=mkDialog("向服务器申请联机"),body=d.querySelector(".mbody"),btns=d.querySelector(".mbtns"),spinFrames=["◐","◓","◑","◒"];const addLine=(label,type,value,min,max,step)=>{const line=document.createElement("div");line.className="mline";const lab=document.createElement("label");lab.textContent=label;const ip=document.createElement("input");ip.type=type;ip.value=String(value??"");if(type==="number"){if(min!==undefined)ip.min=String(min);if(max!==undefined)ip.max=String(max);ip.step=String(step??1)}line.appendChild(lab);line.appendChild(ip);body.appendChild(line);return ip};const ipHost=addLine("服务器IP","text",this.remoteServerHost),ipName=addLine("你的昵称","text",this.remoteName),ipRoom=addLine("房间人数","number",this.remoteRoomSize,2,8,1),ipHand=addLine("初始手牌数","number",this.remoteHandSize,2,10,1);const note=document.createElement("div");note.className="mnote";note.textContent=`所有请求都由当前设备浏览器直接发起,不经过网页部署服务器。默认使用 ws://;仅在你显式输入 wss:// 时才使用加密连接。\n${FIREWALL_HINT}`;body.appendChild(note);const inputs=[ipHost,ipName,ipRoom,ipHand];const btnCancel=document.createElement("button");btnCancel.textContent="取消";const btnOk=document.createElement("button");btnOk.textContent="申请并加入对局";btns.appendChild(btnCancel);btns.appendChild(btnOk);let pending=false,spinTimer=null,spinIdx=0;const setPending=(on)=>{pending=!!on;for(const ip of inputs)ip.disabled=pending;btnCancel.disabled=pending;btnOk.disabled=pending;if(spinTimer!==null){clearInterval(spinTimer);spinTimer=null}if(pending){btnOk.textContent=`${spinFrames[0]} 申请并加入对局`;spinIdx=1;spinTimer=setInterval(()=>{if(!pending)return;btnOk.textContent=`${spinFrames[spinIdx%spinFrames.length]} 申请并加入对局`;spinIdx++},120)}else btnOk.textContent="申请并加入对局"};const closeIfIdle=()=>{if(pending)return;closeDialog(d)};btnCancel.onclick=()=>closeIfIdle();d.addEventListener("cancel",e=>{e.preventDefault();closeIfIdle()});btnOk.onclick=async()=>{if(pending)return;const hs=hostSpec(ipHost.value),host=hs.host,proto=hs.proto,path=hs.path,serverPort=i32(hs.port,0),name=String(ipName.value||"玩家").trim()||"玩家",rs=i32(ipRoom.value,3),hcnt=i32(ipHand.value,10);if(serverPort>0&&serverPort!==5000&&serverPort!==443){await alertDialog("错误","服务器主端口建议使用 5000 或 443。请仅填写 IP,或填写 IP:5000 / IP:443。");return}if(rs<2||rs>8){await alertDialog("错误","远程房间人数必须在 2~8 之间。");return}if(hcnt<2||hcnt>10){await alertDialog("错误","远程初始手牌数必须在 2~10 之间。");return}this.remoteServerHost=host;this.remoteWsProto=proto;this.remotePath=path;this.remoteName=name;this.remoteRoomSize=rs;this.remoteHandSize=hcnt;note.textContent="正在向服务器申请房间,请稍候...";setPending(true);let ok=false;try{ok=await this._start_remote()}finally{setPending(false)}if(ok){note.textContent="申请成功,可继续在本窗口修改参数后再次申请。";closeDialog(d)}else note.textContent="申请失败,请修改参数后重试。"};d.showModal();ipHost.focus();ipHost.select()}
375+
async _open_remote(){const d=mkDialog("向服务器申请联机"),body=d.querySelector(".mbody"),btns=d.querySelector(".mbtns"),spinFrames=["◐","◓","◑","◒"];const addLine=(label,type,value,min,max,step)=>{const line=document.createElement("div");line.className="mline";const lab=document.createElement("label");lab.textContent=label;const ip=document.createElement("input");ip.type=type;ip.value=String(value??"");if(type==="number"){if(min!==undefined)ip.min=String(min);if(max!==undefined)ip.max=String(max);ip.step=String(step??1)}line.appendChild(lab);line.appendChild(ip);body.appendChild(line);return ip};const ipHost=addLine("服务器IP","text",this.remoteServerHost),ipName=addLine("你的昵称","text",this.remoteName),ipRoom=addLine("房间人数","number",this.remoteRoomSize,2,8,1),ipHand=addLine("初始手牌数","number",this.remoteHandSize,2,10,1);const note=document.createElement("div");note.className="mnote";note.textContent=`所有请求都由当前设备浏览器直接发起,不经过网页部署服务器。默认会自动择优:HTTP 页面优先 ws://,HTTPS 页面(非本机)会自动改用 wss://;你也可以手动输入 ws:// 或 wss:// 覆盖。\n${FIREWALL_HINT}`;body.appendChild(note);const inputs=[ipHost,ipName,ipRoom,ipHand];const btnCancel=document.createElement("button");btnCancel.textContent="取消";const btnOk=document.createElement("button");btnOk.textContent="申请并加入对局";btns.appendChild(btnCancel);btns.appendChild(btnOk);let pending=false,spinTimer=null,spinIdx=0;const setPending=(on)=>{pending=!!on;for(const ip of inputs)ip.disabled=pending;btnCancel.disabled=pending;btnOk.disabled=pending;if(spinTimer!==null){clearInterval(spinTimer);spinTimer=null}if(pending){btnOk.textContent=`${spinFrames[0]} 申请并加入对局`;spinIdx=1;spinTimer=setInterval(()=>{if(!pending)return;btnOk.textContent=`${spinFrames[spinIdx%spinFrames.length]} 申请并加入对局`;spinIdx++},120)}else btnOk.textContent="申请并加入对局"};const closeIfIdle=()=>{if(pending)return;closeDialog(d)};btnCancel.onclick=()=>closeIfIdle();d.addEventListener("cancel",e=>{e.preventDefault();closeIfIdle()});btnOk.onclick=async()=>{if(pending)return;const hs=hostSpec(ipHost.value),host=hs.host,proto=hs.proto,path=hs.path,serverPort=i32(hs.port,0),name=String(ipName.value||"玩家").trim()||"玩家",rs=i32(ipRoom.value,3),hcnt=i32(ipHand.value,10);if(serverPort>0&&serverPort!==5000&&serverPort!==443){await alertDialog("错误","服务器主端口建议使用 5000 或 443。请仅填写 IP,或填写 IP:5000 / IP:443。");return}if(rs<2||rs>8){await alertDialog("错误","远程房间人数必须在 2~8 之间。");return}if(hcnt<2||hcnt>10){await alertDialog("错误","远程初始手牌数必须在 2~10 之间。");return}this.remoteServerHost=host;this.remoteWsProto=proto;this.remotePath=path;this.remoteName=name;this.remoteRoomSize=rs;this.remoteHandSize=hcnt;note.textContent="正在向服务器申请房间,请稍候...";setPending(true);let ok=false;try{ok=await this._start_remote()}finally{setPending(false)}if(ok){note.textContent="申请成功,可继续在本窗口修改参数后再次申请。";closeDialog(d)}else note.textContent="申请失败,请修改参数后重试。"};d.showModal();ipHost.focus();ipHost.select()}
374376
async _open_join(){const d=mkDialog("加入服务器房间"),body=d.querySelector(".mbody"),btns=d.querySelector(".mbtns"),spinFrames=["◐","◓","◑","◒"];const addLine=(label,type,value,min,max,step)=>{const line=document.createElement("div");line.className="mline";const lab=document.createElement("label");lab.textContent=label;const ip=document.createElement("input");ip.type=type;ip.value=String(value??"");if(type==="number"){if(min!==undefined)ip.min=String(min);if(max!==undefined)ip.max=String(max);ip.step=String(step??1)}line.appendChild(lab);line.appendChild(ip);body.appendChild(line);return ip};const ipName=addLine("你的昵称","text",this.joinName),ipHost=addLine("服务器地址","text",this.joinHost),ipPort=addLine("对局端口","number",this.joinPort,1,65535,1);const note=document.createElement("div");note.className="mnote";note.textContent=`连接请求由当前设备浏览器直接发起。注意:5000 是主端口,不是对局端口。\n${FIREWALL_HINT}`;body.appendChild(note);const inputs=[ipName,ipHost,ipPort];const btnCancel=document.createElement("button");btnCancel.textContent="取消";const btnOk=document.createElement("button");btnOk.textContent="加入对局";btns.appendChild(btnCancel);btns.appendChild(btnOk);let pending=false,spinTimer=null,spinIdx=0;const setPending=(on)=>{pending=!!on;for(const ip of inputs)ip.disabled=pending;btnCancel.disabled=pending;btnOk.disabled=pending;if(spinTimer!==null){clearInterval(spinTimer);spinTimer=null}if(pending){btnOk.textContent=`${spinFrames[0]} 加入对局`;spinIdx=1;spinTimer=setInterval(()=>{if(!pending)return;btnOk.textContent=`${spinFrames[spinIdx%spinFrames.length]} 加入对局`;spinIdx++},120)}else btnOk.textContent="加入对局"};const closeIfIdle=()=>{if(pending)return;closeDialog(d)};btnCancel.onclick=()=>closeIfIdle();d.addEventListener("cancel",e=>{e.preventDefault();closeIfIdle()});btnOk.onclick=async()=>{if(pending)return;const name=String(ipName.value||"玩家").trim()||"玩家",hs=hostSpec(ipHost.value),host=hs.host,proto=hs.proto,path=hs.path,embeddedPort=i32(hs.port,0),rawPort=String(ipPort.value??"").trim();let port=0;if(rawPort){port=i32(rawPort,0)}else if(embeddedPort>0){port=embeddedPort}if(embeddedPort>0&&rawPort&&port!==embeddedPort){await alertDialog("错误","地址中的端口与“对局端口”输入不一致,请统一后重试。");return}if(port<=0){await alertDialog("错误","端口输入无效。");return}if(port===5000){await alertDialog("错误","5000 是主端口,不能直接加入对局。请先“向服务器申请联机”或输入房主给出的对局端口。");return}this.joinName=name;this.joinHost=host;this.joinWsProto=proto;this.joinPath=path;this.joinPort=port;note.textContent="正在连接房间并等待服务器确认,请稍候...";setPending(true);let ok=false;try{ok=await this._join()}finally{setPending(false)}if(ok){note.textContent="连接成功,可继续在本窗口修改参数后再次加入。";closeDialog(d)}else note.textContent="连接失败,请检查服务器地址和端口后重试。"};d.showModal();ipName.focus();ipName.select()}
375377

376378
_cur_hand(){if(!this.lastState)return{};const you=i32(this.lastState.you_index,-1),ps=Array.isArray(this.lastState.players)?this.lastState.players:[];if(you<0||you>=ps.length)return{};const h=ps[you]?.hand;return(h&&typeof h==="object")?h:{}}

0 commit comments

Comments
 (0)