321 lines
11 KiB
TypeScript
Raw Normal View History

/**
* @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 -
*/
2025-04-06 21:25:34 +08:00
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;
}
}