321 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @Author: Gongxh
* @Date: 2025-03-28
* @Description: 网络socket
*/
import { Platform } from "../../global/Platform";
import { debug, warn } from "../../tool/log";
type BinaryType = "blob" | "arraybuffer";
interface SocketOptions {
/**
* 给原生平台 和 web 用
* 一个协议字符串或者一个包含协议字符串的数组。
* 这些字符串用于指定子协议,这样单个服务器可以实现多个 WebSocket 子协议(
* 例如你可能希望一台服务器能够根据指定的协议protocol处理不同类型的交互。
* 如果不指定协议字符串,则假定为空字符串。
*/
protocols?: string[];
/**
* 使用 Blob 对象处理二进制数据。这是默认值
* 使用 ArrayBuffer 对象处理二进制数据
* @url https://developer.mozilla.org/docs/Web/API/WebSocket/binaryType
*/
binaryType?: BinaryType;
/** 超时时间 默认3000毫秒 */
timeout?: number;
}
export class Socket {
/**
* socket对象
* @internal
*/
private _socket: WebSocket | WechatMiniprogram.SocketTask | AliyMiniprogram.SocketTask | BytedanceMiniprogram.SocketTask;
/**
* @param {string} url 要连接的 URL这应该是 WebSocket 服务器将响应的 URL
* @param {SocketOptions} options 可选参数 针对不同平台的一些特殊参数 详细信息见定义
*/
constructor(url: string, options?: SocketOptions) {
if (Platform.isWX) {
this._socket = this.createWechatSocket(url, options?.timeout || 3000, options?.protocols);
} else if (Platform.isAlipay) {
this._socket = this.createAliSocket(url, options?.timeout || 3000, options?.protocols);
} else if (Platform.isBytedance) {
this._socket = this.createBytedanceSocket(url, options?.timeout || 3000, options?.protocols);
} else {
this._socket = this.createOtherSocket(url, options?.binaryType, options?.timeout || 3000, options?.protocols);
}
}
/**
* 微信小游戏创建socket
* @internal
*/
private createWechatSocket(url: string, timeout?: number, protocols?: string[]): WechatMiniprogram.SocketTask {
let socket = wx.connectSocket({
url,
protocols: protocols,
timeout: timeout,
success: () => { debug("socket success") },
fail: () => { warn("socket fail") }
});
socket.onOpen(() => {
this.onopen && this.onopen();
});
socket.onMessage((res: { data: string | ArrayBuffer }) => {
this.onmessage && this.onmessage(res.data);
});
socket.onError((res: { errMsg: string }) => {
// 微信上socket和原生平台以及浏览器不一致 所以这里特殊处理 给他一个默认的错误码
this.onclose?.(1000, res?.errMsg);
});
socket.onClose((res: { code: number, reason: string }) => {
this.onclose?.(res.code, res.reason);
});
return socket;
}
/**
* 支付宝小游戏创建socket
* @internal
*/
private createAliSocket(url: string, timeout?: number, protocols?: string[]): AliyMiniprogram.SocketTask {
let socket = my.connectSocket({
url,
protocols: protocols,
multiple: true,
timeout: timeout,
success: () => { debug("socket success") },
fail: () => { warn("socket fail") }
});
socket.onOpen((info: AliyMiniprogram.OnOpenData) => {
this.onopen && this.onopen();
});
socket.onMessage((info: AliyMiniprogram.OnMessageData) => {
if (!this.onmessage) {
return;
}
if (info.isBuffer) {
if (my.base64ToArrayBuffer) {
this.onmessage(my.base64ToArrayBuffer(info.data.data));
} else if (atob) {
this.onmessage(this.base64ToArrayBuffer(info.data.data));
} else {
this.onmessage(info.data.data);
}
this.onmessage(info.data.data);
} else {
this.onmessage(info.data.data);
}
});
socket.onError((info: { data: AliyMiniprogram.CallBack.Fail }) => {
this.onclose && this.onclose(info.data.error, info.data.errorMessage);
});
socket.onClose((info: { code: number, reason: string, data: { code: number, reason: string } }) => {
this.onclose && this.onclose(info.code, info.reason);
});
return socket;
}
/**
* 抖音小游戏创建socket
* @internal
*/
private createBytedanceSocket(url: string, timeout?: number, protocols?: string[]): BytedanceMiniprogram.SocketTask {
let socket: BytedanceMiniprogram.SocketTask = tt.connectSocket({
url,
protocols: protocols,
success: () => { debug("socket success") },
fail: () => { warn("socket fail") }
});
let timer = setTimeout(() => {
socket.close({});
}, timeout);
socket.onOpen(() => {
timer && clearTimeout(timer);
timer = null;
this.onopen && this.onopen();
});
socket.onMessage((info: { data: string | ArrayBuffer }) => {
this.onmessage && this.onmessage(info.data);
});
socket.onError((res: { errMsg: string }) => {
timer && clearTimeout(timer);
timer = null;
// 微信上socket和原生平台以及浏览器不一致 所以这里特殊处理 给他一个默认的错误码
this.onclose?.(1000, res.errMsg);
});
socket.onClose((res: { code: string, reason: string }) => {
timer && clearTimeout(timer);
timer = null;
this.onclose?.(Number(res.code), res.reason);
});
return socket;
}
/**
* 除微信小游戏、支付宝小游戏、抖音小游戏之外的平台创建socket
* @internal
*/
private createOtherSocket(url: string, binaryType: BinaryType, timeout?: number, protocols?: string[]): WebSocket {
let socket = new WebSocket(url, protocols);
if (binaryType) {
socket.binaryType = binaryType;
}
let timer = setTimeout(() => {
socket.close();
}, timeout);
socket.onopen = () => {
timer && clearTimeout(timer);
timer = null;
this.onopen?.();
}
socket.onmessage = (event: MessageEvent) => {
this.onmessage?.(event.data);
}
socket.onerror = () => {
timer && clearTimeout(timer);
timer = null;
this.onerror?.();
}
socket.onclose = (event: CloseEvent) => {
timer && clearTimeout(timer);
timer = null;
this.onclose?.(event?.code, event?.reason);
}
return socket;
}
/**
* 发送文本数据
* @param data - 文本数据
*/
public send(data: string): void {
if (Platform.isWX) {
(this._socket as WechatMiniprogram.SocketTask).send({ data: data });
} else if (Platform.isAlipay) {
(this._socket as AliyMiniprogram.SocketTask).send({ data: data });
} else if (Platform.isBytedance) {
(this._socket as BytedanceMiniprogram.SocketTask).send({ data: data });
} else {
(this._socket as WebSocket).send(data);
}
}
/**
* 发送二进制数据
* @param data - 二进制数据
*/
public sendBuffer(data: ArrayBuffer): void {
if (Platform.isWX) {
(this._socket as WechatMiniprogram.SocketTask).send({ data: data });
} else if (Platform.isAlipay) {
if (my.arrayBufferToBase64) {
(this._socket as AliyMiniprogram.SocketTask).send({ data: my.arrayBufferToBase64(data), isBuffer: true });
} else if (btoa) {
(this._socket as AliyMiniprogram.SocketTask).send({ data: this.uint8ArrayToBase64(new Uint8Array(data)), isBuffer: true });
} else {
(this._socket as AliyMiniprogram.SocketTask).send({ data: data });
}
} else if (Platform.isBytedance) {
(this._socket as BytedanceMiniprogram.SocketTask).send({ data: data });
} else {
(this._socket as WebSocket).send(data);
}
}
/**
* 客户端主动断开
* @param code - 关闭代码: 如果没有传这个参数默认使用1000, 客户端可使用的数字范围: [3001-3999]
* @param reason - 关闭原因: 一个人类可读的字符串,它解释了连接关闭的原因。这个 UTF-8 编码的字符串不能超过 123 个字节
*/
public close(code?: number, reason?: string): void {
if (Platform.isWX) {
(this._socket as WechatMiniprogram.SocketTask).close({ code: code, reason: reason });
} else if (Platform.isAlipay) {
(this._socket as AliyMiniprogram.SocketTask).close({ code: code, reason: reason });
} else if (Platform.isBytedance) {
(this._socket as BytedanceMiniprogram.SocketTask).close({ code: code, reason: reason });
} else {
(this._socket as WebSocket).close(code, reason);
}
}
/**
* 获取socket示例
* 在微信小游戏、支付宝小游戏、抖音小游戏 返回的是他们平台的socket实例类型
*/
public socket<T>(): T {
return this._socket as T;
}
/**
* socket已准备好 open成功
* 当前连接已经准备好发送和接受数据
*/
public onopen: () => void;
/**
* 接收到服务端发送的消息
* @param data - 消息数据
*/
public onmessage: (data: string | ArrayBuffer) => void;
/**
* 监听可能发生的错误,一般用不到
*/
public onerror: () => void;
/**
* 关闭连接
* @param code - 关闭代码
* @param reason - 关闭原因
*/
public onclose: (code: number, reason: string) => void;
/**
* 二进制数据转成Base64 (支付宝用)
* @internal
*/
private uint8ArrayToBase64(u8Array: Uint8Array): string {
let CHUNK_SIZE = 0x8000; // 32768
let index = 0;
let length = u8Array.length;
let result = '';
let slice: Uint8Array<ArrayBufferLike>;;
// 分段处理,避免`btoa`输入字符串过长
for (; index < length; index += CHUNK_SIZE) {
slice = u8Array.subarray(index, Math.min(index + CHUNK_SIZE, length));
// 将Uint8Array转换为字符串并使用btoa进行Base64编码
result += btoa(String.fromCharCode(...slice));
}
return result;
}
/**
* base64转成ArrayBuffer (支付宝用)
* @internal
*/
private base64ToArrayBuffer(base64: string): ArrayBuffer {
let binary_string = atob(base64);
let len = binary_string.length;
let bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}
}