update
This commit is contained in:
25
apps/server/package.json
Normal file
25
apps/server/package.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "@game/server",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "nodemon --exec ts-node ./src/index -e ts"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.11.7",
|
||||
"@types/ws": "^8.5.3",
|
||||
"eslint": "^8.26.0",
|
||||
"nodemon": "^2.0.20",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.8.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/fs-extra": "^9.0.13",
|
||||
"fs-extra": "^10.1.0",
|
||||
"ws": "^8.10.0"
|
||||
}
|
||||
}
|
13
apps/server/src/base/Singleton.ts
Normal file
13
apps/server/src/base/Singleton.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export default class Singleton {
|
||||
private static _instance: any = null
|
||||
|
||||
static GetInstance<T>(): T {
|
||||
if (this._instance === null) {
|
||||
this._instance = new this()
|
||||
}
|
||||
return this._instance
|
||||
}
|
||||
|
||||
protected constructor() {
|
||||
}
|
||||
}
|
18
apps/server/src/biz/Player.ts
Normal file
18
apps/server/src/biz/Player.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import Connection from '../core/Connection'
|
||||
|
||||
export type IPlayer = Pick<Player, 'id' | 'nickname' | 'connection'>
|
||||
|
||||
export default class Player {
|
||||
id: number
|
||||
nickname: string
|
||||
connection: Connection
|
||||
rid: number
|
||||
|
||||
constructor({ id, nickname, connection }: IPlayer) {
|
||||
this.id = id
|
||||
this.nickname = nickname
|
||||
this.connection = connection
|
||||
this.connection.playerId = this.id
|
||||
this.rid = -1
|
||||
}
|
||||
}
|
55
apps/server/src/biz/PlayerManager.ts
Normal file
55
apps/server/src/biz/PlayerManager.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { ApiMsgEnum } from '..'
|
||||
import Singleton from '../base/Singleton'
|
||||
import type { IPlayer } from './Player'
|
||||
import Player from './Player'
|
||||
import RoomManager from './RoomManager'
|
||||
|
||||
export default class PlayerManager extends Singleton {
|
||||
static get Instance() {
|
||||
return super.GetInstance<PlayerManager>()
|
||||
}
|
||||
|
||||
playerId = 1
|
||||
players: Set<Player> = new Set()
|
||||
idMapPlayer: Map<number, Player> = new Map()
|
||||
|
||||
createPlayer({ connection, nickname }: Omit<IPlayer, 'id'>) {
|
||||
const player = new Player({ id: this.playerId++, connection, nickname })
|
||||
this.players.add(player)
|
||||
this.idMapPlayer.set(player.id, player)
|
||||
return player
|
||||
}
|
||||
|
||||
removePlayer(uid: number) {
|
||||
const player = this.idMapPlayer.get(uid)
|
||||
if (player) {
|
||||
const rid = player.rid
|
||||
if (rid !== undefined) {
|
||||
RoomManager.Instance.leaveRoom(rid, uid);
|
||||
RoomManager.Instance.syncRooms();
|
||||
RoomManager.Instance.syncRoom(rid);
|
||||
}
|
||||
this.players.delete(player)
|
||||
this.idMapPlayer.delete(uid)
|
||||
this.syncPlayers()
|
||||
}
|
||||
}
|
||||
|
||||
getPlayerById(uid: number) {
|
||||
return this.idMapPlayer.get(uid)
|
||||
}
|
||||
|
||||
syncPlayers() {
|
||||
for (const player of this.players) {
|
||||
player.connection.sendMsg(ApiMsgEnum.MsgPlayerList, { list: this.getPlayersView() })
|
||||
}
|
||||
}
|
||||
|
||||
getPlayersView(players: Set<Player> = this.players) {
|
||||
return [...players].map((player) => this.getPlayerView(player))
|
||||
}
|
||||
|
||||
getPlayerView({ id, nickname, rid }: Player) {
|
||||
return { id, nickname, rid }
|
||||
}
|
||||
}
|
39
apps/server/src/biz/Room.ts
Normal file
39
apps/server/src/biz/Room.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { ApiMsgEnum } from '..'
|
||||
import type Player from './Player'
|
||||
import PlayerManager from './PlayerManager'
|
||||
import RoomManager from './RoomManager'
|
||||
|
||||
export default class Room {
|
||||
id: number
|
||||
players: Set<Player> = new Set()
|
||||
|
||||
constructor(rid: number) {
|
||||
this.id = rid
|
||||
}
|
||||
|
||||
join(uid: number) {
|
||||
const player = PlayerManager.Instance.getPlayerById(uid)
|
||||
if (player) {
|
||||
player.rid = this.id
|
||||
this.players.add(player)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
leave(uid: number) {
|
||||
const player = PlayerManager.Instance.getPlayerById(uid)
|
||||
if (player) {
|
||||
player.rid = -1
|
||||
this.players.delete(player)
|
||||
if (!this.players.size) {
|
||||
RoomManager.Instance.closeRoom(this.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sync() {
|
||||
for (const player of this.players) {
|
||||
player.connection.sendMsg(ApiMsgEnum.MsgRoom, RoomManager.Instance.getRoomView(this))
|
||||
}
|
||||
}
|
||||
}
|
70
apps/server/src/biz/RoomManager.ts
Normal file
70
apps/server/src/biz/RoomManager.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { ApiMsgEnum } from '..'
|
||||
import Singleton from '../base/Singleton'
|
||||
import Player from './Player'
|
||||
import PlayerManager from './PlayerManager'
|
||||
import Room from './Room'
|
||||
|
||||
export default class RoomManager extends Singleton {
|
||||
static get Instance() {
|
||||
return super.GetInstance<RoomManager>()
|
||||
}
|
||||
|
||||
roomId = 1
|
||||
rooms: Set<Room> = new Set()
|
||||
idMapRoom: Map<number, Room> = new Map()
|
||||
|
||||
createRoom() {
|
||||
const room = new Room(this.roomId++)
|
||||
this.rooms.add(room)
|
||||
this.idMapRoom.set(room.id, room)
|
||||
return room
|
||||
}
|
||||
|
||||
joinRoom(rid: number, uid: number) {
|
||||
const room = this.getRoomById(rid)
|
||||
if (room) {
|
||||
room.join(uid)
|
||||
return room
|
||||
}
|
||||
}
|
||||
|
||||
leaveRoom(rid: number, uid: number) {
|
||||
const room = this.getRoomById(rid)
|
||||
if (room) {
|
||||
room.leave(uid)
|
||||
}
|
||||
}
|
||||
|
||||
closeRoom(rid: number) {
|
||||
const room = this.getRoomById(rid)
|
||||
if (room) {
|
||||
this.rooms.delete(room)
|
||||
this.idMapRoom.delete(rid)
|
||||
}
|
||||
}
|
||||
|
||||
getRoomById(id: number) {
|
||||
return this.idMapRoom.get(id)
|
||||
}
|
||||
|
||||
syncRooms() {
|
||||
for (const player of PlayerManager.Instance.players) {
|
||||
player.connection.sendMsg(ApiMsgEnum.MsgRoomList, { list: this.getRoomsView() })
|
||||
}
|
||||
}
|
||||
|
||||
syncRoom(rid: number) {
|
||||
const room = this.idMapRoom.get(rid)
|
||||
if (room) {
|
||||
room.sync()
|
||||
}
|
||||
}
|
||||
|
||||
getRoomsView(rooms: Set<Room> = this.rooms) {
|
||||
return [...rooms].map((room) => this.getRoomView(room))
|
||||
}
|
||||
|
||||
getRoomView({ id, players }: Room) {
|
||||
return { id, players: PlayerManager.Instance.getPlayersView(players) }
|
||||
}
|
||||
}
|
1
apps/server/src/common/index.ts
Normal file
1
apps/server/src/common/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
console.log(112221);
|
79
apps/server/src/core/Connection.ts
Normal file
79
apps/server/src/core/Connection.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import WebSocket from 'ws';
|
||||
import { EventEmitter } from 'stream';
|
||||
import MyServer, { IData } from '.';
|
||||
import { getTime } from '../utils';
|
||||
|
||||
export enum ConnectionEventEnum {
|
||||
Close = 'Close',
|
||||
}
|
||||
|
||||
export default class Connection extends EventEmitter {
|
||||
server: MyServer
|
||||
ws: WebSocket
|
||||
msgMap: Map<string, Function[]> = new Map()
|
||||
playerId?: number;
|
||||
|
||||
constructor(server: MyServer, ws: WebSocket) {
|
||||
super()
|
||||
|
||||
this.server = server
|
||||
this.ws = ws
|
||||
this.ws.on('close', (code: number, reason: Buffer) => {
|
||||
this.emit(ConnectionEventEnum.Close, code, reason.toString())
|
||||
})
|
||||
|
||||
this.ws.on('message', (buffer: Buffer) => {
|
||||
const str = buffer.toString()
|
||||
try {
|
||||
const { name, data } = JSON.parse(str)
|
||||
console.log(`${getTime()}接收|${this.playerId || -1}|${str}`)
|
||||
if (this.server.apiMap.has(name)) {
|
||||
try {
|
||||
const cb = this.server.apiMap.get(name)
|
||||
const res = cb?.(this, data)
|
||||
this.sendMsg(name, {
|
||||
success: true,
|
||||
res,
|
||||
})
|
||||
} catch (error) {
|
||||
this.sendMsg(name, {
|
||||
success: false,
|
||||
error: (error as Error)?.message,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (this.msgMap.has(name)) {
|
||||
this.msgMap.get(name)?.forEach(cb => cb(data))
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`解析失败,${str}不是合法的JSON格式:`, error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
listenMsg(name: string, cb: Function) {
|
||||
if (this.msgMap.has(name)) {
|
||||
this.msgMap.get(name)?.push(cb)
|
||||
} else {
|
||||
this.msgMap.set(name, [cb])
|
||||
}
|
||||
}
|
||||
|
||||
unlistenMsg(name: string, cb: Function) {
|
||||
if (this.msgMap.has(name)) {
|
||||
const index = this.msgMap.get(name)?.indexOf(cb) || -1
|
||||
index > -1 && this.msgMap.get(name)?.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
sendMsg(name: string, data: IData) {
|
||||
const msg = JSON.stringify({
|
||||
name,
|
||||
data
|
||||
})
|
||||
console.log(`${getTime()}发送|${this.playerId || -1}|${msg}`)
|
||||
this.ws.send(msg)
|
||||
}
|
||||
}
|
71
apps/server/src/core/index.ts
Normal file
71
apps/server/src/core/index.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { EventEmitter } from 'stream';
|
||||
import WebSocket, { WebSocketServer } from 'ws';
|
||||
import Connection, { ConnectionEventEnum } from './Connection';
|
||||
|
||||
export interface IMyServerOptions {
|
||||
port: number
|
||||
}
|
||||
|
||||
export type IData = Record<string, any>
|
||||
|
||||
export interface ICallApiRet {
|
||||
success: boolean;
|
||||
error?: Error;
|
||||
res?: IData
|
||||
}
|
||||
|
||||
export enum MyServerEventEnum {
|
||||
Connect = 'Connect',
|
||||
DisConnect = 'DisConnect',
|
||||
}
|
||||
|
||||
export default class MyServer extends EventEmitter {
|
||||
wss?: WebSocketServer
|
||||
port: number
|
||||
connections: Set<Connection> = new Set()
|
||||
apiMap: Map<string, Function> = new Map()
|
||||
|
||||
constructor({ port = 8080 }: Partial<IMyServerOptions>) {
|
||||
super()
|
||||
this.port = port
|
||||
}
|
||||
|
||||
start() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.wss = new WebSocketServer({ port: this.port });
|
||||
this.wss.on('connection', this.handleConnect.bind(this));
|
||||
|
||||
this.wss.on("error", (e) => {
|
||||
reject(e)
|
||||
})
|
||||
|
||||
this.wss.on("close", () => {
|
||||
console.log("MyServer 服务关闭");
|
||||
})
|
||||
|
||||
this.wss.on("listening", () => {
|
||||
resolve(true)
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
handleConnect(ws: WebSocket) {
|
||||
//初始化
|
||||
const connection = new Connection(this, ws)
|
||||
|
||||
//向外告知有人来了
|
||||
this.connections.add(connection)
|
||||
this.emit(MyServerEventEnum.Connect, connection)
|
||||
|
||||
//向外告知有人走了
|
||||
connection.on(ConnectionEventEnum.Close, (code: number, reason: string) => {
|
||||
this.connections.delete(connection)
|
||||
this.emit(MyServerEventEnum.DisConnect, connection, code, reason)
|
||||
})
|
||||
}
|
||||
|
||||
setApi(apiName: string, cb: Function) {
|
||||
this.apiMap.set(apiName, cb)
|
||||
}
|
||||
}
|
109
apps/server/src/index.ts
Normal file
109
apps/server/src/index.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import MyServer, { IData, MyServerEventEnum } from './core';
|
||||
import Player from './biz/Player';
|
||||
import PlayerManager from './biz/PlayerManager';
|
||||
import RoomManager from './biz/RoomManager';
|
||||
import Connection from './core/Connection';
|
||||
import { copyCommon, getTime } from './utils';
|
||||
|
||||
export enum ApiMsgEnum {
|
||||
ApiPlayerList = 'ApiPlayerList',
|
||||
ApiPlayerJoin = 'ApiPlayerJoin',
|
||||
ApiRoomList = 'ApiRoomList',
|
||||
ApiRoomCreate = 'ApiRoomCreate',
|
||||
ApiRoomJoin = 'ApiRoomJoin',
|
||||
ApiRoomLeave = 'ApiRoomLeave',
|
||||
MsgPlayerList = 'MsgPlayerList',
|
||||
MsgRoomList = 'MsgRoomList',
|
||||
MsgRoom = 'MsgRoom',
|
||||
}
|
||||
|
||||
copyCommon()
|
||||
|
||||
const server = new MyServer({ port: 8888 })
|
||||
|
||||
// event
|
||||
server.on(MyServerEventEnum.Connect, (connection: Connection) => {
|
||||
console.log(`${getTime()}来人|人数|${server.connections.size}`)
|
||||
})
|
||||
|
||||
server.on(MyServerEventEnum.DisConnect, (connection: Connection) => {
|
||||
console.log(`${getTime()}走人|人数|${server.connections.size}`)
|
||||
if (connection.playerId) {
|
||||
PlayerManager.Instance.removePlayer(connection.playerId)
|
||||
}
|
||||
})
|
||||
|
||||
// api
|
||||
server.setApi(ApiMsgEnum.ApiPlayerList, (connection: Connection, data: IData) => {
|
||||
return { list: PlayerManager.Instance.getPlayersView() }
|
||||
})
|
||||
|
||||
server.setApi(ApiMsgEnum.ApiPlayerJoin, (connection: Connection, data: IData) => {
|
||||
const nickname = data.nickname
|
||||
const player = PlayerManager.Instance.createPlayer({ connection, nickname })
|
||||
PlayerManager.Instance.syncPlayers()
|
||||
return PlayerManager.Instance.getPlayerView(player)
|
||||
})
|
||||
|
||||
server.setApi(ApiMsgEnum.ApiRoomList, (connection: Connection, data: IData) => {
|
||||
return { list: RoomManager.Instance.getRoomsView() }
|
||||
})
|
||||
|
||||
server.setApi(ApiMsgEnum.ApiRoomCreate, (connection: Connection, data: IData) => {
|
||||
if (connection.playerId) {
|
||||
const room = RoomManager.Instance.joinRoom(RoomManager.Instance.createRoom().id, connection.playerId)
|
||||
if (room) {
|
||||
RoomManager.Instance.syncRooms()
|
||||
PlayerManager.Instance.syncPlayers()
|
||||
return RoomManager.Instance.getRoomView(room)
|
||||
} else {
|
||||
throw new Error("ApiRoomCreate room不存在")
|
||||
}
|
||||
} else {
|
||||
throw new Error("ApiRoomCreate 玩家未登录")
|
||||
}
|
||||
})
|
||||
|
||||
server.setApi(ApiMsgEnum.ApiRoomJoin, (connection: Connection, data: IData) => {
|
||||
if (connection.playerId) {
|
||||
const room = RoomManager.Instance.joinRoom(data.rid, connection.playerId)
|
||||
if (room) {
|
||||
RoomManager.Instance.syncRooms()
|
||||
PlayerManager.Instance.syncPlayers()
|
||||
RoomManager.Instance.syncRoom(room.id)
|
||||
return RoomManager.Instance.getRoomView(room)
|
||||
} else {
|
||||
throw new Error("ApiRoomJoin room不存在")
|
||||
}
|
||||
} else {
|
||||
throw new Error("ApiRoomJoin 玩家未登录")
|
||||
}
|
||||
})
|
||||
|
||||
server.setApi(ApiMsgEnum.ApiRoomLeave, (connection: Connection, data: IData) => {
|
||||
if (connection.playerId) {
|
||||
const player = PlayerManager.Instance.getPlayerById(connection.playerId)
|
||||
if (player) {
|
||||
const rid = player.rid
|
||||
if (rid) {
|
||||
RoomManager.Instance.leaveRoom(rid, player.id)
|
||||
PlayerManager.Instance.syncPlayers()
|
||||
RoomManager.Instance.syncRooms()
|
||||
RoomManager.Instance.syncRoom(rid)
|
||||
} else {
|
||||
throw new Error("ApiRoomLeave 玩家不在房间")
|
||||
}
|
||||
} else {
|
||||
throw new Error("ApiRoomLeave 玩家不存在")
|
||||
}
|
||||
} else {
|
||||
throw new Error("ApiRoomLeave 玩家未登录")
|
||||
}
|
||||
})
|
||||
|
||||
// start!!
|
||||
server.start().then(() => {
|
||||
console.log("服务启动!")
|
||||
}).catch((e) => {
|
||||
console.log("服务异常", e)
|
||||
})
|
821
apps/server/src/utils/binary.ts
Normal file
821
apps/server/src/utils/binary.ts
Normal file
@@ -0,0 +1,821 @@
|
||||
/**
|
||||
* 类似于protobuffer,但此库及其精简且专为js打造
|
||||
* @author zp
|
||||
* @version 1.1.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* [注] nodejs环境下通过Uint8Array将ArrayBuffer和Buffer互相转换
|
||||
* @example
|
||||
* // Buffer ---> ArrayBuffer
|
||||
* function toArrayBuffer(buf) {
|
||||
* var ab = new ArrayBuffer(buf.length);
|
||||
* var view = new Uint8Array(ab);
|
||||
* for (var i = 0; i < buf.length; ++i) {
|
||||
* view[i] = buf[i];
|
||||
* }
|
||||
* return ab;
|
||||
* }
|
||||
* // ArrayBuffer ---> Buffer
|
||||
* function toBuffer(ab) {
|
||||
* var buf = new Buffer(ab.byteLength);
|
||||
* var view = new Uint8Array(ab);
|
||||
* for (var i = 0; i < buf.length; ++i) {
|
||||
* buf[i] = view[i];
|
||||
* }
|
||||
* return buf;
|
||||
* }
|
||||
*/
|
||||
|
||||
/**
|
||||
* @example
|
||||
* var { registerProto, Type, encode, decode, singleArray } = binary;
|
||||
* registerProto(1001, {
|
||||
* name: Type.String,
|
||||
* age: Type.Uint8,
|
||||
* sex: Type.Uint8
|
||||
* })
|
||||
* registerProto(1002, {
|
||||
* info: 1001,
|
||||
* gold: Type.Uint16,
|
||||
* items: [Type.Uint16, Type.String]
|
||||
* })
|
||||
* registerProto(1003, {
|
||||
* array0: Type.Array,
|
||||
* array1: singleArray(1002),
|
||||
* array2: singleArray([1001, 1002]),
|
||||
* array3: singleArray(Type.Uint16),
|
||||
* array4: singleArray([Type.Uint16, Type.String])
|
||||
* })
|
||||
|
||||
* var buffer = encode({ name: 'Mary', age: 18, sex: 0 }, 1001);
|
||||
* decode(buffer);
|
||||
|
||||
* var buffer = encode({ info: { name: 'Mary', age: 18, sex: 0 }, gold: 10, array: [100, 2, 3] }, 1002);
|
||||
* decode(buffer);
|
||||
|
||||
* var buffer = encode({
|
||||
* array0: ['你好啊','我很好'],
|
||||
* array1: [{ info: { name: 'James', age: 30, sex: 1 }, gold: 10, array: [100, 2, 3] }],
|
||||
* array2: [[{}, { info: { name: 'Mary', age: 18, sex: 0 }, gold: 10, array: [100, 2, 3] }]],
|
||||
* array3: [568],
|
||||
* array4: [[0, '零'], [1, '一'], [2, '二'], [3, '三']]
|
||||
* }, 1003);
|
||||
* decode(buffer);
|
||||
*/
|
||||
|
||||
/**
|
||||
* https://segmentfault.com/a/1190000014533505 中提到如果服务器开启了压缩了话,需要进行解压操作,并推荐了pako.js
|
||||
* (具体也不知道这个压缩怎么回事,测试的时候也没遇到这个问题,先写注释记下来)
|
||||
* @see https://github.com/nodeca/pako/edit/master/dist/pako.js
|
||||
* @example
|
||||
* let compressdata = new Uint8Array(buffer, byteOff, length);
|
||||
* let uncompress = pako.inflate(compressdata);//解压数据
|
||||
* let uncompressdata = uncompress.buffer;// ArrayBuffer {}
|
||||
* let dataViewData = new DataView(uncompressdata, 0);//解压后数据
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* A minimal base64 implementation for number arrays.
|
||||
* @memberof util
|
||||
* @namespace
|
||||
*/
|
||||
class Base64 {
|
||||
// Base64 encoding table
|
||||
private b64 = new Array(64);
|
||||
// Base64 decoding table
|
||||
private s64 = new Array(123);
|
||||
private invalidEncoding = "invalid encoding";
|
||||
|
||||
constructor() {
|
||||
// 65..90, 97..122, 48..57, 43, 47
|
||||
for (var i = 0; i < 64;) {
|
||||
this.s64[this.b64[i] = i < 26 ? i + 65 : i < 52 ? i + 71 : i < 62 ? i - 4 : i - 59 | 43] = i++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the byte length of a base64 encoded string.
|
||||
* @param {string} string Base64 encoded string
|
||||
* @returns {number} Byte length
|
||||
*/
|
||||
length(string: string): number {
|
||||
var p = string.length;
|
||||
if (!p)
|
||||
return 0;
|
||||
var n = 0;
|
||||
while (--p % 4 > 1 && string.charAt(p) === "=")
|
||||
++n;
|
||||
return Math.ceil(string.length * 3) / 4 - n;
|
||||
};
|
||||
|
||||
/**
|
||||
* Encodes a buffer to a base64 encoded string.
|
||||
* @param {DataView} buffer Source buffer
|
||||
* @param {number} start Source start
|
||||
* @param {number} end Source end
|
||||
* @returns {string} Base64 encoded string
|
||||
*/
|
||||
read(buffer: DataView, start: number, end: number): string {
|
||||
var parts = null,
|
||||
chunk = [];
|
||||
var i = 0, // output index
|
||||
j = 0, // goto index
|
||||
t; // temporary
|
||||
while (start < end) {
|
||||
var b = buffer.getUint8(start++);
|
||||
switch (j) {
|
||||
case 0:
|
||||
chunk[i++] = this.b64[b >> 2];
|
||||
t = (b & 3) << 4;
|
||||
j = 1;
|
||||
break;
|
||||
case 1:
|
||||
chunk[i++] = this.b64[t | b >> 4];
|
||||
t = (b & 15) << 2;
|
||||
j = 2;
|
||||
break;
|
||||
case 2:
|
||||
chunk[i++] = this.b64[t | b >> 6];
|
||||
chunk[i++] = this.b64[b & 63];
|
||||
j = 0;
|
||||
break;
|
||||
}
|
||||
if (i > 8191) {
|
||||
(parts || (parts = [])).push(String.fromCharCode.apply(String, chunk));
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
if (j) {
|
||||
chunk[i++] = this.b64[t];
|
||||
chunk[i++] = 61;
|
||||
if (j === 1)
|
||||
chunk[i++] = 61;
|
||||
}
|
||||
if (parts) {
|
||||
if (i)
|
||||
parts.push(String.fromCharCode.apply(String, chunk.slice(0, i)));
|
||||
return parts.join("");
|
||||
}
|
||||
return String.fromCharCode.apply(String, chunk.slice(0, i));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Decodes a base64 encoded string to a buffer.
|
||||
* @param {string} string Source string
|
||||
* @param {DataView} buffer Destination buffer
|
||||
* @param {number} offset Destination offset
|
||||
* @returns {number} Number of bytes written
|
||||
* @throws {Error} If encoding is invalid
|
||||
*/
|
||||
write(string: string, buffer: DataView, offset: number): number {
|
||||
var start = offset;
|
||||
var j = 0, // goto index
|
||||
t; // temporary
|
||||
for (var i = 0; i < string.length;) {
|
||||
var c = string.charCodeAt(i++);
|
||||
if (c === 61 && j > 1)
|
||||
break;
|
||||
if ((c = this.s64[c]) === undefined)
|
||||
throw Error(this.invalidEncoding);
|
||||
switch (j) {
|
||||
case 0:
|
||||
t = c;
|
||||
j = 1;
|
||||
break;
|
||||
case 1:
|
||||
buffer.setUint8(offset++, t << 2 | (c & 48) >> 4);
|
||||
t = c;
|
||||
j = 2;
|
||||
break;
|
||||
case 2:
|
||||
buffer.setUint8(offset++, (t & 15) << 4 | (c & 60) >> 2);
|
||||
t = c;
|
||||
j = 3;
|
||||
break;
|
||||
case 3:
|
||||
buffer.setUint8(offset++, (t & 3) << 6 | c);
|
||||
j = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (j === 1)
|
||||
throw Error(this.invalidEncoding);
|
||||
return offset - start;
|
||||
};
|
||||
}
|
||||
|
||||
const base64 = new Base64();
|
||||
|
||||
|
||||
/**
|
||||
* A minimal UTF8 implementation for number arrays.
|
||||
* @memberof util
|
||||
* @namespace
|
||||
*/
|
||||
class UTF8 {
|
||||
/**
|
||||
* Calculates the UTF8 byte length of a string.
|
||||
*/
|
||||
length(string: string): number {
|
||||
var len = 0,
|
||||
c = 0;
|
||||
for (var i = 0; i < string.length; ++i) {
|
||||
c = string.charCodeAt(i);
|
||||
if (c < 128)
|
||||
len += 1;
|
||||
else if (c < 2048)
|
||||
len += 2;
|
||||
else if ((c & 0xFC00) === 0xD800 && (string.charCodeAt(i + 1) & 0xFC00) === 0xDC00) {
|
||||
++i;
|
||||
len += 4;
|
||||
} else
|
||||
len += 3;
|
||||
}
|
||||
return len;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reads UTF8 bytes as a string.
|
||||
*/
|
||||
read(buffer: DataView, start: number, end: number): string {
|
||||
var len = end - start;
|
||||
if (len < 1)
|
||||
return "";
|
||||
var parts = null,
|
||||
chunk = [],
|
||||
i = 0, // char offset
|
||||
t; // temporary
|
||||
while (start < end) {
|
||||
t = buffer.getUint8(start++);
|
||||
if (t < 128)
|
||||
chunk[i++] = t;
|
||||
else if (t > 191 && t < 224)
|
||||
chunk[i++] = (t & 31) << 6 | buffer.getUint8(start++) & 63;
|
||||
else if (t > 239 && t < 365) {
|
||||
t = ((t & 7) << 18 | (buffer.getUint8(start++) & 63) << 12 | (buffer.getUint8(start++) & 63) << 6 | buffer.getUint8(start++) & 63) - 0x10000;
|
||||
chunk[i++] = 0xD800 + (t >> 10);
|
||||
chunk[i++] = 0xDC00 + (t & 1023);
|
||||
} else
|
||||
chunk[i++] = (t & 15) << 12 | (buffer.getUint8(start++) & 63) << 6 | buffer.getUint8(start++) & 63;
|
||||
if (i > 8191) {
|
||||
(parts || (parts = [])).push(String.fromCharCode.apply(String, chunk));
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
if (parts) {
|
||||
if (i)
|
||||
parts.push(String.fromCharCode.apply(String, chunk.slice(0, i)));
|
||||
return parts.join("");
|
||||
}
|
||||
return String.fromCharCode.apply(String, chunk.slice(0, i));
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes a string as UTF8 bytes.
|
||||
*/
|
||||
write(string: string, buffer: DataView, offset: number): number {
|
||||
var start = offset,
|
||||
c1, // character 1
|
||||
c2; // character 2
|
||||
for (var i = 0; i < string.length; ++i) {
|
||||
c1 = string.charCodeAt(i);
|
||||
if (c1 < 128) {
|
||||
buffer.setUint8(offset++, c1);
|
||||
} else if (c1 < 2048) {
|
||||
buffer.setUint8(offset++, c1 >> 6 | 192);
|
||||
buffer.setUint8(offset++, c1 & 63 | 128);
|
||||
} else if ((c1 & 0xFC00) === 0xD800 && ((c2 = string.charCodeAt(i + 1)) & 0xFC00) === 0xDC00) {
|
||||
c1 = 0x10000 + ((c1 & 0x03FF) << 10) + (c2 & 0x03FF);
|
||||
++i;
|
||||
buffer.setUint8(offset++, c1 >> 18 | 240);
|
||||
buffer.setUint8(offset++, c1 >> 12 & 63 | 128);
|
||||
buffer.setUint8(offset++, c1 >> 6 & 63 | 128);
|
||||
buffer.setUint8(offset++, c1 & 63 | 128);
|
||||
} else {
|
||||
buffer.setUint8(offset++, c1 >> 12 | 224);
|
||||
buffer.setUint8(offset++, c1 >> 6 & 63 | 128);
|
||||
buffer.setUint8(offset++, c1 & 63 | 128);
|
||||
}
|
||||
}
|
||||
return offset - start;
|
||||
};
|
||||
}
|
||||
|
||||
const utf8 = new UTF8();
|
||||
|
||||
class Encode {
|
||||
private buffer: ArrayBuffer = null;
|
||||
private view: DataView = null;
|
||||
private index: number = 0;
|
||||
|
||||
constructor(length: number) {
|
||||
this.buffer = new ArrayBuffer(length)
|
||||
this.view = new DataView(this.buffer);
|
||||
this.index = 0;
|
||||
}
|
||||
|
||||
Int8(data: number) {
|
||||
if (!isNumber(data)) data = 0;
|
||||
return this.view.setInt8(this.index++, data);
|
||||
}
|
||||
|
||||
Uint8(data: number) {
|
||||
if (!isNumber(data)) data = 0;
|
||||
return this.view.setUint8(this.index++, data);
|
||||
}
|
||||
|
||||
Int16(data: number) {
|
||||
if (!isNumber(data)) data = 0;
|
||||
var value = this.view.setInt16(this.index, data);
|
||||
this.index += 2;
|
||||
return value;
|
||||
}
|
||||
|
||||
Uint16(data: number) {
|
||||
if (!isNumber(data)) data = 0;
|
||||
var value = this.view.setUint16(this.index, data);
|
||||
this.index += 2;
|
||||
return value;
|
||||
}
|
||||
|
||||
Int32(data: number) {
|
||||
if (!isNumber(data)) data = 0;
|
||||
var value = this.view.setInt32(this.index, data);
|
||||
this.index += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
Uint32(data: number) {
|
||||
if (!isNumber(data)) data = 0;
|
||||
var value = this.view.setUint32(this.index, data);
|
||||
this.index += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
Float32(data: number) {
|
||||
if (!isNumber(data)) data = 0;
|
||||
var value = this.view.setFloat32(this.index, data);
|
||||
this.index += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
Float64(data: number) {
|
||||
if (!isNumber(data)) data = 0;
|
||||
var value = this.view.setFloat64(this.index, data);
|
||||
this.index += 8;
|
||||
return value;
|
||||
}
|
||||
|
||||
Boolean(data) {
|
||||
return this.Uint8(data ? 1 : 0);
|
||||
}
|
||||
|
||||
String(string) {
|
||||
if (!isString(string)) string = '';
|
||||
|
||||
const len = utf8.write(string, this.view, this.index + 2);
|
||||
this.Uint16(len);
|
||||
this.index += len;
|
||||
}
|
||||
|
||||
Base64(string) {
|
||||
if (!isBase64(string)) string = '';
|
||||
|
||||
const len = base64.write(string, this.view, this.index + 2);
|
||||
this.Uint16(len);
|
||||
this.index += len;
|
||||
}
|
||||
|
||||
Array(array) {
|
||||
if (isArray(array) && !isEmpty(array)) {
|
||||
return this.String(JSON.stringify(array));
|
||||
} else {
|
||||
return this.String('');
|
||||
}
|
||||
}
|
||||
|
||||
Object(obj) {
|
||||
if (isMap(obj) && !isEmpty(obj)) {
|
||||
return this.String(JSON.stringify(obj));
|
||||
} else {
|
||||
return this.String('');
|
||||
}
|
||||
}
|
||||
|
||||
Buffer() {
|
||||
return this.buffer;
|
||||
}
|
||||
}
|
||||
|
||||
class Decode {
|
||||
private view: DataView = null;
|
||||
private index: number = 0;
|
||||
|
||||
constructor(buffer: ArrayBuffer) {
|
||||
this.view = new DataView(buffer);
|
||||
this.index = 0;
|
||||
}
|
||||
|
||||
Int8() {
|
||||
return this.view.getInt8(this.index++);
|
||||
}
|
||||
|
||||
Uint8() {
|
||||
return this.view.getUint8(this.index++);
|
||||
}
|
||||
|
||||
Int16() {
|
||||
const value = this.view.getInt16(this.index);
|
||||
this.index += 2;
|
||||
return value;
|
||||
}
|
||||
|
||||
Uint16() {
|
||||
const value = this.view.getUint16(this.index);
|
||||
this.index += 2;
|
||||
return value;
|
||||
}
|
||||
|
||||
Int32() {
|
||||
const value = this.view.getInt32(this.index);
|
||||
this.index += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
Uint32() {
|
||||
const value = this.view.getUint32(this.index);
|
||||
this.index += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
Float32() {
|
||||
const value = this.view.getFloat32(this.index);
|
||||
this.index += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
Float64() {
|
||||
const value = this.view.getFloat64(this.index);
|
||||
this.index += 8;
|
||||
return value;
|
||||
}
|
||||
|
||||
Boolean() {
|
||||
return !!this.Uint8();
|
||||
}
|
||||
|
||||
String() {
|
||||
const len = this.Uint16();
|
||||
this.index += len;
|
||||
return utf8.read(this.view, this.index - len, this.index);
|
||||
}
|
||||
|
||||
Base64() {
|
||||
const len = this.Uint16();
|
||||
this.index += len;
|
||||
return base64.read(this.view, this.index - len, this.index);
|
||||
}
|
||||
|
||||
Array() {
|
||||
const str = this.String();
|
||||
return str ? JSON.parse(str) : [];
|
||||
}
|
||||
|
||||
Object() {
|
||||
const str = this.String();
|
||||
return str ? JSON.parse(str) : {};
|
||||
}
|
||||
}
|
||||
|
||||
const getType = function (param) {
|
||||
return Object.prototype.toString.call(param).slice(8, -1).toLowerCase();
|
||||
}
|
||||
|
||||
const isObject = function (param) {
|
||||
return param && typeof param === 'object';
|
||||
}
|
||||
|
||||
const isArray = function (param) {
|
||||
return getType(param) === 'array';
|
||||
}
|
||||
|
||||
const isMap = function (param) {
|
||||
return getType(param) === 'object';
|
||||
}
|
||||
|
||||
const isString = function (param) {
|
||||
return getType(param) === 'string';
|
||||
}
|
||||
|
||||
const isNumber = function (param) {
|
||||
return getType(param) === 'number';
|
||||
}
|
||||
|
||||
const isBoolean = function (param) {
|
||||
return getType(param) === 'boolean';
|
||||
}
|
||||
|
||||
const isBase64 = function (param) {
|
||||
return isString(param) && /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(param);
|
||||
}
|
||||
|
||||
function stringStartsWith(str1: string, str2: string) {
|
||||
if (str1 === str2) {
|
||||
return true;
|
||||
}
|
||||
for (let index = 0; index < str2.length; index++) {
|
||||
if (str1[index] !== str2[index]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function isEmpty(obj) {
|
||||
if (isArray(obj)) {
|
||||
return !obj.length;
|
||||
} else if (isMap(obj)) {
|
||||
for (const key in obj) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function compareStr(str1: string, str2: string) {
|
||||
if (str1 === str2) {
|
||||
return 0;
|
||||
}
|
||||
if (str1.length > str2.length) {
|
||||
return 1;
|
||||
}
|
||||
if (str1.length < str2.length) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (let i = 0, code1 = 0, code2 = 0; i < str1.length; i++) {
|
||||
if (str2.length <= i) {
|
||||
return 1;
|
||||
} else {
|
||||
code1 = str1.charCodeAt(i);
|
||||
code2 = str2.charCodeAt(i);
|
||||
if (code1 > code2) {
|
||||
return 1;
|
||||
} else if (code1 < code2) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function sortKeys(obj) {
|
||||
if (isMap(obj)) {
|
||||
let index = 0;
|
||||
const keys: string[] = [];
|
||||
for (const key in obj) {
|
||||
for (index = keys.length - 1; index >= 0; index--) {
|
||||
if (compareStr(key, keys[index]) >= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (index === keys.length - 1) {
|
||||
keys.push(key);
|
||||
} else {
|
||||
keys.splice(index + 1, 0, key);
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
} else if (isArray(obj)) {
|
||||
return obj.map(function (v, k) {
|
||||
return k;
|
||||
})
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
function realType(type) {
|
||||
if (isObject(type)) {
|
||||
return type;
|
||||
}
|
||||
return protoCache[type] || type;
|
||||
}
|
||||
|
||||
const singleArrayPrefix = 'SingleArray';
|
||||
|
||||
function isSingleArray(str: string) {
|
||||
return isString(str) && stringStartsWith(str, singleArrayPrefix);
|
||||
}
|
||||
|
||||
function SingleArrayProto(str: string) {
|
||||
const stringify = str.slice(singleArrayPrefix.length + 1, -1);
|
||||
return JSON.parse(stringify);
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记单一类型的数组
|
||||
* @param proto
|
||||
*/
|
||||
export const singleArray = function (proto) {
|
||||
return `${singleArrayPrefix}(${JSON.stringify(proto)})`;
|
||||
}
|
||||
|
||||
function DataLen(data: any, proto: any) {
|
||||
proto = realType(proto);
|
||||
|
||||
let length = 0;
|
||||
if (isMap(proto)) {
|
||||
if (!isMap(data)) data = {};
|
||||
for (const key in proto) {
|
||||
length += DataLen(data[key], proto[key]);
|
||||
}
|
||||
} else if (isArray(proto)) {
|
||||
if (!isArray(data)) data = [];
|
||||
proto.forEach(function (type, index) {
|
||||
length += DataLen(data[index], type);
|
||||
})
|
||||
} else if (proto === 'String') {
|
||||
// 如果是String的话,固定开头有2字节记录字符串长度
|
||||
length += 2;
|
||||
if (isString(data)) length += utf8.length(data);
|
||||
} else if (proto === 'Object' || proto === 'Array') {
|
||||
// Object和Array类型也会将数据通过JSON.stringify转成String格式
|
||||
length += 2;
|
||||
if (!isEmpty(data)) length += utf8.length(JSON.stringify(data));
|
||||
} else if (proto === 'Base64') {
|
||||
// 如果是Base64的话,固定开头有2字节记录字符串长度
|
||||
length += 2;
|
||||
if (isBase64(data)) length += base64.length(data);
|
||||
} else if (isSingleArray(proto)) {
|
||||
// 如果是SingleArray的话,固定开头有2字节记录数组长度
|
||||
length += 2;
|
||||
if (!isArray(data)) data = [];
|
||||
proto = realType(SingleArrayProto(proto));
|
||||
data.forEach(function (value) {
|
||||
length += DataLen(value, proto);
|
||||
})
|
||||
} else if (TypeByte[proto]) {
|
||||
length += TypeByte[proto];
|
||||
} else {
|
||||
throw new Error("'proto' is bad");
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
function encodeData(encode: Encode, data: any, proto: any) {
|
||||
proto = realType(proto);
|
||||
|
||||
if (isMap(proto)) {
|
||||
if (!isMap(data)) data = {};
|
||||
sortKeys(proto).forEach(function (key) {
|
||||
encodeData(encode, data[key], proto[key]);
|
||||
})
|
||||
} else if (isArray(proto)) {
|
||||
if (!isArray(data)) data = [];
|
||||
proto.forEach(function (type, index) {
|
||||
encodeData(encode, data[index], type);
|
||||
})
|
||||
} else if (isSingleArray(proto)) {
|
||||
if (!isArray(data)) data = [];
|
||||
encode.Uint16(data.length);
|
||||
proto = realType(SingleArrayProto(proto));
|
||||
data.forEach(function (value) {
|
||||
encodeData(encode, value, proto);
|
||||
})
|
||||
} else {
|
||||
encode[proto](data);
|
||||
}
|
||||
}
|
||||
|
||||
function decodeData(decode: Decode, proto: any) {
|
||||
proto = realType(proto);
|
||||
|
||||
if (isMap(proto)) {
|
||||
const obj = {};
|
||||
sortKeys(proto).forEach(function (key) {
|
||||
obj[key] = decodeData(decode, proto[key]);
|
||||
});
|
||||
return obj;
|
||||
} else if (isArray(proto)) {
|
||||
return proto.map(function (type) {
|
||||
return decodeData(decode, type);
|
||||
});
|
||||
} else if (isSingleArray(proto)) {
|
||||
const arr = [];
|
||||
const len = decode.Uint16();
|
||||
proto = realType(SingleArrayProto(proto));
|
||||
for (let index = 0; index < len; index++) {
|
||||
arr.push(decodeData(decode, proto));
|
||||
}
|
||||
return arr;
|
||||
} else {
|
||||
return decode[proto]();
|
||||
}
|
||||
}
|
||||
|
||||
const TypeByte = {
|
||||
'Int8': 1,
|
||||
'Uint8': 1,
|
||||
'Int16': 2,
|
||||
'Uint16': 2,
|
||||
'Int32': 4,
|
||||
'Uint32': 4,
|
||||
'Float32': 4,
|
||||
'Float64': 8,
|
||||
'BigInt64': 8,
|
||||
'BigUint64': 8,
|
||||
'Boolean': 1,
|
||||
'String': 1,
|
||||
'Base64': 1,
|
||||
'Array': 1,
|
||||
'Object': 1
|
||||
}
|
||||
|
||||
export const Type = {
|
||||
'Int8': 'Int8', // 1byte -128 to 127
|
||||
'Uint8': 'Uint8', // 1byte 0 to 255
|
||||
'Uint8Clamped': 'Uint8', // 1byte 0 to 255
|
||||
'Int16': 'Int16', // 2byte -32768 to 32767
|
||||
'Uint16': 'Uint16', // 2byte 0 to 65535
|
||||
'Int32': 'Int32', // 4byte -2147483648 to 2147483647
|
||||
'Uint32': 'Uint32', // 4byte 0 to 4294967295
|
||||
'Float32': 'Float32', // 4byte 1.2x10^-38 to 3.4x10^38
|
||||
'Float64': 'Float64', // 8byte 5.0x10^-324 to 1.8x10^308
|
||||
'BigInt64': 'BigInt64', // 8byte -2^63 to (2^63)-1
|
||||
'BigUint64': 'BigUint64', // 8byte 0 to (2^64)-1
|
||||
'Boolean': 'Boolean', // 1byte 0 to 255
|
||||
'String': 'String', // 1byte 0 to 255
|
||||
'Base64': 'Base64', // 1byte 0 to 255
|
||||
'Array': 'Array', // 1byte 0 to 255
|
||||
'Object': 'Object' // 1byte 0 to 255
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化
|
||||
* 开头2字节用来存储proto的id
|
||||
*/
|
||||
export const encode = function (obj: Object, id: number | string) {
|
||||
const proto = protoCache[id];
|
||||
if (proto) {
|
||||
const len = DataLen(obj, proto);
|
||||
const encode = new Encode(len + 2);
|
||||
encode.Uint16(Number(id));
|
||||
encodeData(encode, obj, proto);
|
||||
return encode.Buffer();
|
||||
} else {
|
||||
throw new Error("encode error: 'id' is bad");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 反序列化
|
||||
* 开头2字节代表proto的id
|
||||
*/
|
||||
export const decode = function (buffer: ArrayBuffer) {
|
||||
const decode = new Decode(buffer);
|
||||
const id = decode.Uint16();
|
||||
const proto = protoCache[id];
|
||||
if (proto) {
|
||||
return decodeData(decode, proto);
|
||||
} else {
|
||||
throw new Error("decode error: 'buffer' is bad");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* proto缓存
|
||||
*/
|
||||
const protoCache = {}
|
||||
|
||||
/**
|
||||
* 注册proto
|
||||
* id: 必须是个正整数(或正整数字符串), 取值范围[0,65535]
|
||||
*/
|
||||
export const registerProto = function (id: number | string, proto: any) {
|
||||
if (typeof id === 'string') id = Number(id);
|
||||
|
||||
if (isNumber(id) && Math.floor(id) === id && id >= 0 && id <= 65535 && !Type[id]) {
|
||||
protoCache[id] = proto;
|
||||
} else {
|
||||
throw new Error("registerProto error: 'id' is bad");
|
||||
}
|
||||
}
|
||||
|
||||
export const registerProtoMap = function (protoMap: any) {
|
||||
if (isMap(protoMap)) {
|
||||
for (const id in protoMap) {
|
||||
registerProto(id, protoMap[id]);
|
||||
}
|
||||
} else {
|
||||
throw new Error("registerProtoMap error: 'protoMap' is bad");
|
||||
}
|
||||
}
|
||||
|
||||
export const protoToJson = function () {
|
||||
return JSON.stringify(protoCache);
|
||||
}
|
402
apps/server/src/utils/eMath.ts
Normal file
402
apps/server/src/utils/eMath.ts
Normal file
@@ -0,0 +1,402 @@
|
||||
/**
|
||||
* @author zp
|
||||
*/
|
||||
/**
|
||||
* 多平台一致精确计算库,比decimal更轻量更快
|
||||
* [Math.round、Math.min、Math.max,Math.floor、Math.ceil,这些系统方法一般情况下是可以放心使用的]
|
||||
*/
|
||||
const exactMath = Object.create(null);
|
||||
module.exports = exactMath;
|
||||
|
||||
// 计算精度
|
||||
// sin、cos、tan方法的误差小数点后16位
|
||||
const ACCURACY_SIN_ERROR = 1e-16;
|
||||
const ACCURACY_COS_ERROR = 1e-16;
|
||||
const ACCURACY_TAN_ERROR = 1e-16;
|
||||
|
||||
// 角度弧度常量
|
||||
const DEG = 57.29577951308232;
|
||||
const RAD = 0.017453292519943295;
|
||||
|
||||
// 系统常量
|
||||
exactMath.PI = 3.141592653589793;
|
||||
exactMath.E = 2.718281828459045;
|
||||
exactMath.LN2 = 0.6931471805599453;
|
||||
exactMath.LN10 = 2.302585092994046;
|
||||
exactMath.LOG2E = 1.4426950408889634;
|
||||
exactMath.LOG10E = 0.4342944819032518;
|
||||
exactMath.SQRT1_2 = 0.7071067811865476;
|
||||
exactMath.SQRT2 = 1.4142135623730951;
|
||||
|
||||
/**
|
||||
* 链式调用
|
||||
* @example
|
||||
* const value = exactMath.value(10).add(20.123).mul(2).sqrt().value;
|
||||
*/
|
||||
let chain = null;
|
||||
exactMath.value = function (value) {
|
||||
if (!chain) {
|
||||
chain = {
|
||||
value: 0,
|
||||
valueOf() { return this.value; },
|
||||
toString() { return String(this.value); }
|
||||
}
|
||||
for (const key in exactMath) {
|
||||
if (key !== 'value' && typeof exactMath[key] === 'function') {
|
||||
chain[key] = function (...args) {
|
||||
this.value = exactMath[key].call(exactMath, this.value, ...args);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
chain.value = value;
|
||||
return chain;
|
||||
}
|
||||
|
||||
/****************************************************基础****************************************************/
|
||||
/**
|
||||
* 获得小数位数
|
||||
* @param {Number} num 浮点数
|
||||
* @returns {Number}
|
||||
*/
|
||||
exactMath.getDecimalPlace = function (num) {
|
||||
if (num && num !== Math.floor(num)) {
|
||||
for (let n = 1, m = 10, temp = 0; n < 20; n += 1, m *= 10) {
|
||||
temp = num * m;
|
||||
if (temp == Math.floor(temp)) return n;
|
||||
}
|
||||
return 20;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 保留n为小数,并四舍五入
|
||||
* @example
|
||||
* (2.335).toFixed(2)
|
||||
* exactMath.toFixed(2.335, 2)
|
||||
* @param {Number} num 浮点数
|
||||
* @param {Number} n 整数
|
||||
* @returns {Number}
|
||||
*/
|
||||
exactMath.toFixed = function (num, n = 0) {
|
||||
if (n == 0) {
|
||||
return Math.round(num);
|
||||
} else {
|
||||
const m = Math.pow(10, n);
|
||||
return Math.round(num * (m * 10) / 10) / m;
|
||||
}
|
||||
}
|
||||
|
||||
exactMath.abs = function (x) {
|
||||
return Math.abs(x);
|
||||
}
|
||||
exactMath.round = function (x) {
|
||||
return Math.round(x);
|
||||
}
|
||||
exactMath.ceil = function (x) {
|
||||
return Math.ceil(x)
|
||||
}
|
||||
exactMath.floor = function (x) {
|
||||
return Math.floor(x)
|
||||
}
|
||||
exactMath.min = function (...args) {
|
||||
return Math.min(...args);
|
||||
}
|
||||
exactMath.max = function (...args) {
|
||||
return Math.max(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 小数相加
|
||||
* @param {Number} num1 浮点数
|
||||
* @param {Number} num2 浮点数
|
||||
* @returns {Number}
|
||||
*/
|
||||
exactMath.add = function (...args) {
|
||||
if (args.length === 2) {
|
||||
const num1 = args[0];
|
||||
const num2 = args[1];
|
||||
const m = Math.pow(10, Math.max(this.getDecimalPlace(num1), this.getDecimalPlace(num2)));
|
||||
return (this.toFixed(num1 * m) + this.toFixed(num2 * m)) / m;
|
||||
} else {
|
||||
return args.reduce((a, b) => this.add(a, b))
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 小数相减
|
||||
* @param {Number} num1 浮点数
|
||||
* @param {Number} num2 浮点数
|
||||
* @returns {Number}
|
||||
*/
|
||||
exactMath.sub = function (...args) {
|
||||
if (args.length === 2) {
|
||||
const num1 = args[0];
|
||||
const num2 = args[1];
|
||||
const m = Math.pow(10, Math.max(this.getDecimalPlace(num1), this.getDecimalPlace(num2)));
|
||||
|
||||
return (this.toFixed(num1 * m) - this.toFixed(num2 * m)) / m;
|
||||
} else {
|
||||
return args.reduce((a, b) => this.sub(a, b))
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 小数相乘
|
||||
* @param {Number} num1 浮点数
|
||||
* @param {Number} num2 浮点数
|
||||
* @returns {Number}
|
||||
*/
|
||||
exactMath.mul = function (...args) {
|
||||
if (args.length === 2) {
|
||||
let num1 = args[0];
|
||||
let num2 = args[1];
|
||||
|
||||
// 方案1:
|
||||
// 直接相乘,但是相乘两数小数点过多会导致中间值[(n1 * m1) * (n2 * m2)]过大
|
||||
// const n1 = this.getDecimalPlace(num1);
|
||||
// const n2 = this.getDecimalPlace(num2);
|
||||
// const m1 = Math.pow(10, n1);
|
||||
// const m2 = Math.pow(10, n2);
|
||||
// return (n1 * m1) * (n2 * m2) / (m1 * m2);
|
||||
|
||||
// 方案2:
|
||||
// 用除法实现乘法,不会存在过大中间值
|
||||
let n1 = this.getDecimalPlace(num1);
|
||||
let n2 = this.getDecimalPlace(num2);
|
||||
|
||||
let m = Math.pow(10, n2);
|
||||
num2 = m / this.toFixed(num2 * m);
|
||||
|
||||
m = Math.pow(10, Math.max(n1, this.getDecimalPlace(num2)));
|
||||
m = this.toFixed(num1 * m) / this.toFixed(num2 * m);
|
||||
|
||||
let n = Math.min(this.getDecimalPlace(m), n1 + n2);
|
||||
return this.toFixed(m, n);
|
||||
} else {
|
||||
return args.reduce((a, b) => this.mul(a, b))
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 小数相除法
|
||||
* @param {Number} num1 浮点数
|
||||
* @param {Number} num2 浮点数
|
||||
* @returns {Number}
|
||||
*/
|
||||
exactMath.div = function (...args) {
|
||||
if (args.length === 2) {
|
||||
const num1 = args[0];
|
||||
const num2 = args[1];
|
||||
|
||||
const m = Math.pow(10, Math.max(this.getDecimalPlace(num1), this.getDecimalPlace(num2)));
|
||||
return this.toFixed(num1 * m) / this.toFixed(num2 * m);
|
||||
} else {
|
||||
return args.reduce((a, b) => this.div(a, b))
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 取余
|
||||
* @param {Number} num1 浮点数
|
||||
* @param {Number} num2 浮点数
|
||||
* @returns {Number}
|
||||
*/
|
||||
exactMath.rem = function (...args) {
|
||||
if (args.length === 2) {
|
||||
const num1 = args[0];
|
||||
const num2 = args[1];
|
||||
const m = Math.pow(10, Math.max(this.getDecimalPlace(num1), this.getDecimalPlace(num2)));
|
||||
|
||||
return this.toFixed(num1 * m) % this.toFixed(num2 * m) / m;
|
||||
} else {
|
||||
return args.reduce((a, b) => this.rem(a, b))
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* n次方,仅支持整数次方(正负都可以)
|
||||
* @param {Number} num 浮点数
|
||||
* @param {Number} n 整数
|
||||
*/
|
||||
exactMath.pow = function (num, n) {
|
||||
if (num == 0 && n == 0) {
|
||||
return 1;
|
||||
}
|
||||
if (num == 0 && n > 0) {
|
||||
return 0
|
||||
}
|
||||
if (num == 0 && n < 0) {
|
||||
return Infinity;
|
||||
}
|
||||
// num为负数,n为负小数,返回NaN
|
||||
if (num < 0 && n < 0 && Math.round(n) != n) {
|
||||
return NaN;
|
||||
}
|
||||
|
||||
if (Math.round(n) != n) {
|
||||
throw new Error('n must be an integer');
|
||||
}
|
||||
|
||||
let result = 1;
|
||||
|
||||
if (n > 0) {
|
||||
for (let index = 0; index < n; index++) {
|
||||
result = this.mul(result, num);
|
||||
}
|
||||
} else if (n < 0) {
|
||||
for (let index = 0, len = Math.abs(n); index < len; index++) {
|
||||
result = this.div(result, num);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
/**
|
||||
* 开方运算【牛顿迭代法】
|
||||
*
|
||||
* @param {Number} n
|
||||
* @returns
|
||||
*/
|
||||
exactMath.sqrt = function (n) {
|
||||
if (n < 0) return NaN;
|
||||
if (n === 0) return 0;
|
||||
if (n === 1) return 1;
|
||||
let last = 0;
|
||||
let res = 1;
|
||||
let c = 50;
|
||||
while (res != last && --c >= 0) {
|
||||
last = res;
|
||||
res = this.div(this.add(res, this.div(n, res)), 2)
|
||||
}
|
||||
return res;
|
||||
|
||||
// float InvSqrt(float x)
|
||||
// {
|
||||
// float xhalf = 0.5f * x;
|
||||
// int i = * (int *) & x; // get bits for floating VALUE
|
||||
// i = 0x5f375a86 - (i >> 1); // gives initial guess y0
|
||||
// x = * (float *) & i; // convert bits BACK to float
|
||||
// x = x * (1.5f - xhalf * x * x); // Newton step, repeating increases accuracy
|
||||
// x = x * (1.5f - xhalf * x * x); // Newton step, repeating increases accuracy
|
||||
// x = x * (1.5f - xhalf * x * x); // Newton step, repeating increases accuracy
|
||||
// return 1 / x;
|
||||
// }
|
||||
};
|
||||
|
||||
/****************************************************随机****************************************************/
|
||||
function getSeed(seed) {
|
||||
if (isNaN(seed)) {
|
||||
seed = Math.floor(Math.random() * 233280);
|
||||
} else {
|
||||
seed = Math.floor(seed % 233280);
|
||||
}
|
||||
return seed;
|
||||
}
|
||||
|
||||
let randomSeed = getSeed();
|
||||
|
||||
/**
|
||||
* 设置随机种子
|
||||
*/
|
||||
exactMath.setSeed = function (seed) {
|
||||
randomSeed = getSeed(seed);
|
||||
};
|
||||
|
||||
/**
|
||||
* 随机
|
||||
*/
|
||||
exactMath.random = function () {
|
||||
randomSeed = (randomSeed * 9301 + 49297) % 233280;
|
||||
return randomSeed / 233280.0;
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据随机种子随机
|
||||
* @param {number} seed
|
||||
*/
|
||||
exactMath.randomBySeed = function (seed) {
|
||||
seed = getSeed(seed);
|
||||
seed = (seed * 9301 + 49297) % 233280;
|
||||
return seed / 233280.0;
|
||||
};
|
||||
|
||||
/****************************************************角度弧度转换****************************************************/
|
||||
/**
|
||||
* 弧度数转角度数
|
||||
* @param {Number} radians 浮点数
|
||||
* @returns {Numbe} 浮点数
|
||||
*/
|
||||
exactMath.radiansToDegrees = function (radians) {
|
||||
return this.div(radians, RAD);
|
||||
};
|
||||
/**
|
||||
* 角度数转弧度数
|
||||
* @param {Number} degrees 浮点数
|
||||
* @returns {Numbe} 浮点数
|
||||
*/
|
||||
exactMath.degreesToRadians = function (degrees) {
|
||||
return this.div(degrees, DEG);
|
||||
};
|
||||
/**
|
||||
* 将角度值转换到[0, 360)范围内
|
||||
* @param {Number} angle 浮点数
|
||||
* @returns {Number} 整数
|
||||
*/
|
||||
exactMath.get0To360Angle = function (angle) {
|
||||
if (angle === 0) {
|
||||
return 0;
|
||||
} else if (angle < 0) {
|
||||
return this.add(this.rem(angle, 360), 360);
|
||||
} else {
|
||||
return this.rem(angle, 360);
|
||||
}
|
||||
};
|
||||
/****************************************************三角函数****************************************************/
|
||||
/**
|
||||
* 查表
|
||||
*/
|
||||
exactMath._sin = {};
|
||||
exactMath._cos = {};
|
||||
exactMath._tan = {};
|
||||
|
||||
/**
|
||||
* 3个三角函数,根据需求自行添加
|
||||
* 为了效率,应该尽量使用查表法
|
||||
* 表内查不到的,目前使用系统方法的结果并取前4位小数
|
||||
*/
|
||||
exactMath.sin = function (x) {
|
||||
if (this._sin.hasOwnProperty(x)) {
|
||||
return this._sin[x];
|
||||
}
|
||||
|
||||
// if (x == 0) {
|
||||
// return 0;
|
||||
// } else if (x == 90) {
|
||||
// return 1;
|
||||
// }
|
||||
|
||||
// let n = x, sum = 0, i = 1;
|
||||
// do {
|
||||
// i++;
|
||||
// sum = this.add(sum, n);
|
||||
// // n = -n * x * x / (2 * i - 1) / (2 * i - 2);
|
||||
// n = this.div(this.mul(-1, n, x, x), this.sub(this.mul(2, i), 1), this.sub(this.mul(2, i), 2));
|
||||
// } while (Math.abs(n) >= ACCURACY_SIN_ERROR);
|
||||
// return sum;
|
||||
|
||||
return this.toFixed(Math.sin(x), 4);
|
||||
};
|
||||
exactMath.cos = function (x) {
|
||||
if (this._cos.hasOwnProperty(x)) {
|
||||
return this._cos[x];
|
||||
}
|
||||
|
||||
return this.toFixed(Math.cos(x), 4);
|
||||
};
|
||||
exactMath.tan = function (x) {
|
||||
if (this._tan.hasOwnProperty(x)) {
|
||||
return this._tan[x];
|
||||
}
|
||||
|
||||
return this.toFixed(Math.tan(x), 4);
|
||||
};
|
22
apps/server/src/utils/index.ts
Normal file
22
apps/server/src/utils/index.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import fs from 'fs-extra'
|
||||
import path from 'path'
|
||||
|
||||
export const getTime = () => new Date().toLocaleString().split("├")[0]
|
||||
|
||||
export async function copyCommon() {
|
||||
const src = path.resolve('./common')
|
||||
const dst = path.resolve('../client/assets/Scripts/common')
|
||||
|
||||
console.log(src, dst);
|
||||
|
||||
// clean
|
||||
await fs.remove(dst)
|
||||
|
||||
//create
|
||||
await fs.ensureDir(dst)
|
||||
|
||||
// copy
|
||||
await fs.copy(src, dst)
|
||||
console.log('同步成功')
|
||||
}
|
||||
|
98
apps/server/tsconfig.json
Normal file
98
apps/server/tsconfig.json
Normal file
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"include": [
|
||||
"./src"
|
||||
],
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
/* Language and Environment */
|
||||
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
/* Modules */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
// "outDir": "./", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user