259 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			259 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import { BaseEnumerator } from "../CoroutineV2/Core/BaseEnumerator";
 | |
| import { Action } from "../CSharp/System/Action";
 | |
| import { Encoding } from "../CSharp/System/Text/Encoding";
 | |
| import { INetRequest } from "./Core/INetRequest";
 | |
| import { INetResponse } from "./Core/INetResponse";
 | |
| import NetConfig from "./NetConfig";
 | |
| 
 | |
| export class NetConnector {
 | |
| 	readonly OnDataReceived: Action<INetResponse<any>> = new Action<INetResponse<any>>();
 | |
| 	readonly OnDisconnected: Action<void> = new Action<void>();
 | |
| 	readonly OnLoadUIMask: Action<boolean> = new Action<boolean>();
 | |
| 
 | |
| 	get IsConnected() {
 | |
| 		return this._ws && this._ws.readyState === WebSocket.OPEN;
 | |
| 	}
 | |
| 
 | |
| 	private _host: string;
 | |
| 	private _ws: WebSocket;
 | |
| 	private _waitings: WsRequestEnumerator[] = [];
 | |
| 
 | |
| 	constructor(host: string, port: number/*, ip: string*/) {
 | |
| 		let checkHttp: string = "";
 | |
| 		let index: number = host.indexOf("https://");
 | |
| 		if (index != -1) {
 | |
| 			checkHttp = "https";
 | |
| 			host = host.replace("https://", "");
 | |
| 		} else {
 | |
| 			checkHttp = window.location.href.substring(0, 5);
 | |
| 			host = host.replace("http://", "");
 | |
| 		}
 | |
| 		if (CC_DEBUG) {
 | |
| 			cc.log("[事件]checkHttp=", checkHttp, host, port);
 | |
| 		}
 | |
| 		if (checkHttp != "https") {
 | |
| 			//this._host = `ws://${host}:${port}/?ip=${ip}`;
 | |
| 			this._host = `ws://${host}:${port}`
 | |
| 		}
 | |
| 		else {
 | |
| 			//this._host = `wss://${host}:${port}/?ip=${ip}`;
 | |
| 			this._host = `wss://${host}:${port}`;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	ConnectAsync() {
 | |
| 		if (this._ws) {
 | |
| 			throw new Error("請先執行CasinoNetManager.Disconnect()中斷連線");
 | |
| 		}
 | |
| 		if (cc.sys.isNative && cc.sys.os == cc.sys.OS_ANDROID && this._host.indexOf("wss") !== -1) {
 | |
| 			let cacert = cc.url.raw('resources/cacert.cer');
 | |
| 			if (cc.loader.md5Pipe) {
 | |
| 				cacert = cc.loader.md5Pipe.transformURL(cacert)
 | |
| 			}
 | |
| 			//@ts-ignore
 | |
| 			this._ws = new WebSocket(this._host, null, cacert)
 | |
| 		} else {
 | |
| 			//@ts-ignore
 | |
| 			this._ws = new WebSocket(this._host);
 | |
| 		}
 | |
| 
 | |
| 		this._ws.binaryType = 'arraybuffer';
 | |
| 		this._ws.onopen = this.OnWebSocketOpen.bind(this);
 | |
| 		this._ws.onmessage = this.OnWebSocketMessage.bind(this);
 | |
| 		this._ws.onclose = this.OnWebSocketClose.bind(this);
 | |
| 
 | |
| 		return new WsConnectEnumerator(this._ws);
 | |
| 	}
 | |
| 
 | |
| 	Send(req: INetRequest<any, any>) {
 | |
| 		if (!this.IsConnected) return;
 | |
| 
 | |
| 		let json = [req.Method];
 | |
| 		if (req.Data != null && req.Data != undefined && req.Data != NaN) {
 | |
| 			json[1] = req.Data;
 | |
| 		}
 | |
| 
 | |
| 		if (CC_DEBUG && NetConfig.ShowServerLog) {
 | |
| 			if (req.Data != null && req.Data != undefined && req.Data != NaN) {
 | |
| 				cc.log(`[RPC] 傳送server資料: ${req.Method}(${JSON.stringify(req.Data)})`);
 | |
| 			} else {
 | |
| 				cc.log(`[RPC] 傳送server資料: ${req.Method}()`);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		let str = JSON.stringify(json);
 | |
| 		if (str.length > 65535) {
 | |
| 			throw new Error('要傳的資料太大囉');
 | |
| 		}
 | |
| 
 | |
| 		let strary = Encoding.UTF8.GetBytes(str);
 | |
| 		let buffer = new Uint8Array(4 + strary.byteLength);
 | |
| 		let u16ary = new Uint16Array(buffer.buffer, 0, 3);
 | |
| 		u16ary[0] = strary.byteLength;
 | |
| 		buffer[3] = 0x01;
 | |
| 		buffer.set(strary, 4);
 | |
| 
 | |
| 		this._ws.send(buffer);
 | |
| 	}
 | |
| 
 | |
| 	SendAsync(req: INetRequest<any, any>, mask: boolean) {
 | |
| 		let iterator = new WsRequestEnumerator(req);
 | |
| 		if (!this.IsConnected) {
 | |
| 			iterator.SetResponse(ErrorResponse);
 | |
| 		} else {
 | |
| 			this._waitings.push(iterator);
 | |
| 			if (mask) {
 | |
| 				this.OnLoadUIMask.DispatchCallback(true);
 | |
| 			}
 | |
| 			this.Send(req);
 | |
| 		}
 | |
| 		return iterator;
 | |
| 	};
 | |
| 
 | |
| 	Disconnect() {
 | |
| 		this.WebSocketEnded();
 | |
| 	}
 | |
| 
 | |
| 	private WebSocketEnded() {
 | |
| 		if (!this._ws) return;
 | |
| 
 | |
| 		this._ws.close();
 | |
| 		this._ws.onopen = null;
 | |
| 		this._ws.onmessage = null;
 | |
| 		this._ws.onclose = () => { };
 | |
| 		this._ws = null;
 | |
| 
 | |
| 		this.CleanWaitings();
 | |
| 		this.OnDisconnected.DispatchCallback();
 | |
| 	}
 | |
| 
 | |
| 	private CleanWaitings() {
 | |
| 		for (let w of this._waitings) {
 | |
| 			w.SetResponse(ErrorResponse);
 | |
| 			this.OnLoadUIMask.DispatchCallback(false);
 | |
| 		}
 | |
| 		this._waitings.length = 0;
 | |
| 	}
 | |
| 
 | |
| 	private OnWebSocketOpen(e: Event) {
 | |
| 		if (CC_DEBUG) {
 | |
| 			cc.log(`[RPC] ${this._host} Connected.`);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private OnWebSocketMessage(e: MessageEvent) {
 | |
| 		if (e.data instanceof ArrayBuffer) {
 | |
| 			this.ParseRpcMessage(e.data);
 | |
| 		} else if (e.data instanceof Blob) {
 | |
| 			let reader = new FileReader();
 | |
| 			reader.onload = (e) => { this.ParseRpcMessage(<ArrayBuffer>reader.result); reader.onload = null; }
 | |
| 			reader.readAsArrayBuffer(e.data);
 | |
| 		} else {
 | |
| 			throw new Error(`未知的OnWebSocketMessage(e.data)類型: ${e.data}`);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private ParseRpcMessage(buffer: ArrayBuffer) {
 | |
| 		let startIndex = 0, byteLength = buffer.byteLength;
 | |
| 		while (startIndex + 4 < byteLength) {
 | |
| 			let strlen = new DataView(buffer, startIndex, 3).getUint16(0, true);
 | |
| 			let str = Encoding.UTF8.GetString(new Uint8Array(buffer, startIndex + 4, strlen));
 | |
| 			startIndex += strlen + 4;
 | |
| 
 | |
| 			try {
 | |
| 				let json = JSON.parse(str);
 | |
| 				let method = <string>json[0];
 | |
| 				let status = <number>json[1][0];
 | |
| 				let data = json[1][1];
 | |
| 
 | |
| 				let resp = <INetResponse<any>>{
 | |
| 					Method: method,
 | |
| 					Status: status,
 | |
| 					Data: data,
 | |
| 					IsValid: method && status === 0
 | |
| 				};
 | |
| 
 | |
| 				if (CC_DEBUG && NetConfig.ShowServerLog) {
 | |
| 					if (data) {
 | |
| 						cc.log(`[RPC] 收到server呼叫:(${resp.Status}): ${resp.Method}(${JSON.stringify(resp.Data)})`);
 | |
| 					} else {
 | |
| 						cc.log(`[RPC] 收到server呼叫:(${resp.Status}): ${resp.Method}()`);
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				let dispatch = true;
 | |
| 				for (let i = 0, len = this._waitings.length; i < len; i++) {
 | |
| 					let w = this._waitings[i];
 | |
| 					if (w.MethodBack === resp.Method) {
 | |
| 						dispatch = false;
 | |
| 						this._waitings.splice(i, 1);
 | |
| 						w.SetResponse(resp);
 | |
| 						this.OnLoadUIMask.DispatchCallback(false);
 | |
| 						break;
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				if (dispatch) {
 | |
| 					this.OnDataReceived.DispatchCallback(resp);
 | |
| 				}
 | |
| 			}
 | |
| 			catch
 | |
| 			{
 | |
| 				throw new Error(`[RPC] 無法解析Server回應: ${str}`);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private OnWebSocketClose(e: CloseEvent) {
 | |
| 		this.WebSocketEnded();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| const ErrorResponse: INetResponse<any> = {
 | |
| 	Status: -1,
 | |
| 	Method: "",
 | |
| 	Data: {},
 | |
| 	IsValid: false,
 | |
| };
 | |
| 
 | |
| class WsConnectEnumerator extends BaseEnumerator {
 | |
| 	private _ws: WebSocket;
 | |
| 
 | |
| 	constructor(ws: WebSocket) {
 | |
| 		super();
 | |
| 		this._ws = ws;
 | |
| 	}
 | |
| 
 | |
| 	next(value?: any): IteratorResult<any> {
 | |
| 		return {
 | |
| 			done: this._ws.readyState === WebSocket.OPEN || this._ws.readyState === WebSocket.CLOSED,
 | |
| 			value: undefined
 | |
| 		};
 | |
| 	}
 | |
| }
 | |
| 
 | |
| class WsRequestEnumerator extends BaseEnumerator {
 | |
| 	readonly MethodBack: string;
 | |
| 
 | |
| 	private _req: INetRequest<any, any>;
 | |
| 	private _done: boolean = false;
 | |
| 
 | |
| 	constructor(req: INetRequest<any, any>) {
 | |
| 		super();
 | |
| 
 | |
| 		this._req = req;
 | |
| 		this.MethodBack = req.MethodBack;
 | |
| 	}
 | |
| 
 | |
| 	SetResponse(resp: INetResponse<any>) {
 | |
| 		this._req.Result = resp;
 | |
| 		this._done = true;
 | |
| 	}
 | |
| 
 | |
| 	next(value?: any): IteratorResult<any> {
 | |
| 		return {
 | |
| 			done: this._done,
 | |
| 			value: undefined
 | |
| 		};
 | |
| 	}
 | |
| } |